爪哇|【集合】

集合的由来
java是面向对象语言,它对事物的描述都是通过对象来体现的。为了方便对这些对象进行操作,我们就必须把这些对象进行存储,存储这些对象,就不能是一个基本的变量,而应该是一个容器类型的变量。
数组和StringBuffer是容器类型的,StringBuffer的结果是一个字符串,不一定满足我们的要求,所以只能选择数组,这就是“对象数组”。而对象数组又不能适应变化的需求,因为数组的长度是固定的,为了适应变化的需求,java就提供了集合供我们使用。
数组和集合的区别
1 长度区别:
① 数组的长度固定
② 集合长度可变
2 元素的数据类型不同:
① 数组可以存储基本数据类型,也可以存储引用数据类型,存储引用数据类型就是对象数组
② 集合只能存储引用数据类型(集合的出现也就是用来存储对象的)

集合是存储多个元素的,存储多个元素也是有需求的,比如:多个元素中不能有相同的元素、多个元素按照某种规则排序一下。针对不同的需求,集合也有很多类,这些集合类的数据结构也不同。
数据结构:数据的存储方式。
虽然数据结构不同,但是它们有共性的内容(存储、获取、判断等),通过不断地向上提取,就可以得到一个集合的继承体系结构图,这个体系的老大是Collection。
爪哇|【集合】
文章图片

集合框架图
爪哇|【集合】
文章图片

Collection功能概述

添加功能 1 boolean add(Object obj)
2 boolean addAll(Collection c)
1 添加一个元素
2 添加一个集合的元素
删除功能 1 void clear()
2 boolean remove(Object o)
3 boolean removeAll(Collection c)
1 移除所有元素
2 移除一个元素
3 移除一个集合的元素。只要有一个元素被移除就返回true
遍历功能 Iterator iterator() 迭代器,集合的专用遍历方式 。可以用while循环,也可以用for循环遍历。
iterator()返回的是Iterator,是一个接口,实际返回的是实现类的对象,此接口方法有三个:
void remove():移除迭代器返回的最后一个元素
boolean hasNext():判断是否有元素,如果仍有元素就迭代,返回true
Object next():获取元素,并移动到下一个位置
判断功能 1 boolean contains(Object obj)
2 boolean containsAll(Collection c)
3 boolean isEmpty()
1 判断集合中是否包含指定元素
注意:contains()底层依赖的是equals()。案例:如果是去除ArrayList集合中的重复自定义对象元素,如果要被contains()包含的类对象中没有重写equals(),则默认调用的是Object的equals(),Object的equals()默认比较的是地址值,所以地址值都是不同的,所以被contains()包含的类对象中要重写equals()
【爪哇|【集合】】2 判断集合中是否包含指定集合元素。只有包含所有的元素才返回true
3 判断集合是否为空
长度功能 int size() 返回元素的个数
交集功能 boolean retainAll(Collection c) 两个集合都有的元素。
返回的元素去哪了?返回的boolean是什么意思?
两个集合存在交集,调用该方法的集合存储的是交集的元素,没有其他的元素,返回true。
两个集合不存在交集,调用该方法的集合存储的是空值,没有任何元素,也返回true。
假设有两个集合A,B。A对B做交集,最终的结果保存在A中,B不变;返回值表示的是:A集合的元素是否发生过改变,A集合原来保存的元素发生过改变则返回true,A集合原来保存的元素没有发生过任何改变,则返回false。
转换功能 Object[] toArray() 把集合转为数组。可以实现对数组的遍历
迭代器的使用及注意事项
学生类省略... ...
public class IteratorDemo { public static void main(String[] args) { Collection c = new ArrayList(); Student s = new Student("张三", 1); Student s2 = new Student("李四", 2); Student s3 = new Student("王五", 3); c.add(s); c.add(s2); c.add(s3); Iterator it = c.iterator(); //通过集合获取迭代器对象。迭代器是依赖于集合而存在的 while (it.hasNext()) { //先判断 Student student = (Student) it.next(); //在获取元素 System.out.println(student.getName()+", "+student.getAge()); } }}

上面除了可以使用while去循环遍历之外,也可以使用for循环。
it是局部变量,for循环执行完毕就是垃圾,提高内存利用率。只编写for循环部分,如下:
for (Iterator it = c.iterator(); it.hasNext(); ) { //没有条件控制 Student student = (Student) it.next(); System.out.println(student.getName()+", "+student.getAge()); }

注意事项:
/* * 如果将以下的注释用链式编程去编写,如果集合的元素是奇数个,则会报错java.util.NoSuchElementException */Iterator it = c.iterator(); while (it.hasNext()) { //Student student = (Student) it.next(); //System.out.println(student.getName() + ", " + student.getAge()); System.out.println(((Student) it.next()).getName() + ", " + ((Student) it.next()).getAge()); }

爪哇|【集合】
文章图片

从控制台的输出可以看出,张三的年龄为2,实际是1。之所以出现这样的问题是因为Iterator接口的next()是获取元素,并移动到下一个位置。这里第二次调用next(),就移动到了李四的年龄了,再移动就是王五的姓名,最后没有另一个人年龄了,就报错。如果是偶数个不会报错,但是遍历出来的数据是错乱的。所以不能多次使用next(),因为每次访问都是一个对象
迭代器的原理及源码解析
迭代器为什么不定义成一个类,而是一个接口?
假设迭代器定义成一个类,这样就可以通过创建该类的对象,调用该类的方法,来实现集合的遍历,定义成迭代器类,就是一个具体实现,具体实现,这些集合类存储元素的方式就是一样的。BUT! java中提供了这么多的集合类,而这些集合类的数据结构都是不同的,所以存储元素的方式应该是不同的,进而遍历的方式也应该是不一样的,所以就没有定义成一个迭代器类。
而无论是哪种集合,遍历集合都应该具备获取元素以及判断的功能,在获取元素之前先做判断,这样就不容易出错。而每种集合的方式又不太一样,所以这两个常用的判断跟获取元素功能给提取了出来,并不提供具体实现,这种方式就是接口。
那么,具体的实现类在哪呢?答案是:在具体的实现类中,以成员内部类的方式来体现的。源码如下:
//Iterator接口定义了如下两个方法 public interface Iterator { boolean hasNext(); Object next(); } public interface Iterable { Iterator iterator(); //该方法返回的是Iterator接口 } public interface Collection extends Iterable { Iterator iterator(); }public interface List extends Collection { Iterator iterator(); //到这还没有具体实现,因为List还是接口 }public class ArrayList implements List { public Iterator iterator() { //具体子类实现了 return new Itr(); }private class Itr implements Iterator { //Itr重写了这两个方法 public boolean hasNext() {} public Object next(){} } }

//遍历集合元素 Collection c = new ArrayList(); c.add("hello"); c.add("world"); c.add("java"); Iterator it = c.iterator(); //new Itr(); while(it.hasNext()) { String s = (String)it.next(); System.out.println(s); }

以上遍历集合元素代码解析:
通过c集合对象获取迭代器对象,在多态中通过父类对象访问成员方法的特点中,如果子类重写了父类的方法,输出的结果是子类成员方法重写父类成员方法后的内容。编译看左边,Collection有iterator(),不报错通过,运行看右边,子类ArrayList有iterator(),所以输出的是子类的iterator(),iterator()返回的是new Itr(),Itr是一个具体的实现类,所以,如果返回的是一个接口,实际返回的是实现类的对象。
List集合特点:
有序(存储顺序和取出顺序一致)、可重复
List集合特有功能概述
添加功能 void add(int index, Object element) 在指定位置添加元素
删除功能 Object remove(int index) 根据索引删除元素,返回被删除的元素
修改功能 Object set(int index, Object element) 根据索引修改元素,返回被修改的元素
获取(遍历)功能 Object get(int index) 获取指定位置的元素
遍历功能 ListIterator listIterator() 列表迭代器。List集合特有的迭代器
listIterator()返回ListIterator,是一个接口,继承了Iterator。常用的方法有:
boolean hasPrevious():与Iterator接口的hasNext()一样,只不过是逆向判断是否有元素。
Object previous():与Iterator接口的next()一样,只不过是获取上一个元素。
注意:要实现逆向遍历,首先必须实现正向遍历!所以一般无意义,不使用
List 集合特有遍历功能(普通for):size()结合get()
public class ListDemo { public static void main(String[] args) { List l = new ArrayList(); l.add("王麻子"); l.add("张飞"); l.add("武松"); for (int i = 0; i < l.size(); i++) { String s = (String) l.get(i); System.out.println(s); } } }

并发修改异常的产生原因及解决办法
public static void main(String[] args) { List l = new ArrayList(); l.add("王麻子"); l.add("张飞"); l.add("武松"); Iterator it = l.iterator(); while (it.hasNext()) { String s = (String) it.next(); if ("张飞".equals(s)) { l.add("好汉"); } } System.out.println(l); }

爪哇|【集合】
文章图片

产生原因:
以上的代码逻辑看似没有问题,如果两个字符串内容相等,则添加新的元素。但是却报错了,之所以报错是因为,迭代器是依赖集合而存在的,当判断到两个字符串内容相等就给集合添加了一个新的元素,之后在用迭代器的hasNext()判断,迭代器却不知道集合里面有新的元素,所以就报错了,这个错叫并发修改异常!
也就是说,迭代器遍历元素的时候,通过集合是不能修改元素的。
解决办法:
集合的创建、往集合添加元素省略
方法1 迭代器遍历元素,迭代器修改元素
/* * 方法1 * 因为Iterator迭代器没有添加功能,它的子接口ListIterator有 */ ListIterator it = l.listIterator(); while (it.hasNext()) { String s = (String) it.next(); if ("张飞".equals(s)) { it.add("好汉"); } } System.out.println(l);

方法2 集合遍历元素,集合修改元素(普通for)
//方法2 for (int i = 0; i < l.size(); i++) { String s = (String) l.get(i); if ("张飞".equals(s)) { l.add("梁山"); } } System.out.println(l);

方法1与方法2的区别是:方法1添加的新元素位于要比较元素的后面,方法2新添加的元素在整个元素的最后面
ArrayList集合类底层数据结构是数组,所以查询快,增删慢;线程不安全,所以效率高
ArrayList集合特有功能概述
转换功能 public T[] toArray(T[] a) 把集合转成数组
Vector集合类底层数据结构是数组,所以查询快,增删慢;线程安全,所以效率低
Vector集合特有功能概述
添加功能 void addElement(Object obj) 添加功能,jdk1.0出现的,被add()替代
获取功能 1 Object elementAt(int index)
2 Enumeration elements()
1 根据索引获取,类似于get() 。jdk1.0出现。被get()替代
2 获取多个元素,类似于迭代器。jdk1.0出现。被Iterator iterator()替代
elements()返回Enumeration,是一个接口,实际返回的是实现类的对象,该接口有2个方法:
boolean hasMoreElements():被hasNext()替代
Object nextElement():被next()替代
LinkedList底层数据结构是链表,所以查询慢,增删快;线程不安全,所以效率高
LinkedList集合特有功能概述
添加功能 1 void addFirst(Object obj)
2 void addLast(Object obj)
1 将指定元素插入到元素的开头位置处。新添加的元素总是在最前面 。
2 将指定元素插入到元素的末尾位置处
获取功能 1 Object getFirst()
2 Object getLast()
1 获取集合中的第一个元素。集合元素不变化
2 获取集合中的最后一个元素。集合元素不变化
删除功能 1 Object removeFirst()
2 Object removeLast()
1 删除集合中的第一个元素,并返回(获取)删除的元素
2 删除集合中的最后一个元素,并返回删除的元素
Arrays工具类功能概述
转换功能 public static List asList(T... a) 将数组转换为集合。这里需要注意的是,只能针对对象数组进行转换,对基本数据类型的数组转换不了。
排序功能 public static void sort(Object[] a) 对指定对象数组进行排序
对asList()的使用,代码如下:
// 例 public class ArraysDemo {public static void main(String[] args) { String[] arr = {"hello", "world", "java"}; List list = Arrays.asList(arr); //list.add("javaEE"); //是错误的,虽然转成了集合,但本质还是数组,不能改变集合的大小,会报错:UnsupportedOperationException for (String s : list) { //遍历集合。除了可以使用迭代器,还可以使用foreach System.out.println(s); } }}

//因为方法是可变参数,所以可以省略定义数组的步骤,直接这样编写: List list = Arrays.asList("hello", "world", "java");

注意事项:
此时虽然将一个数组转换为了一个集合,但是不能改变集合的大小,其本质还是一个数组。因为参数是以数组的形式保存下来的,数组的长度不可变。
如果给集合添加、删除元素则报错:UnsupportedOperationException
爪哇|【集合】
文章图片

Set集合特点
无序(存储顺序和取出顺序不一致)、唯一
HashSet集合类底层数据结构是哈希表;线程不安全,所以效率高。哈希表结构底层依赖HashCode()和equals()。哈希表:是一个元素为链表的数组,综合了数组和链表的优点。
HashSet保证元素唯一的源码解析
为什么List集合是可重复的,而Set集合是唯一的呢?
保证Set集合唯一的是它的add(),源码如下:
interface Collection{ ... } interface set extends Collection{ ... } class HashSet implements Set{ private static final Object PRESENT = new Object(); private transient HashMap map; public HashSet{ map = new HashMap<>(); //可以看出,HashSet底层是HashMap }public boolean add(E e){ return map.put(e, PRESENT) == null; //调用了HashMap的put() } }class HashMap implements Map{ public V put(K key, V value) { //看哈希表是否为空,如果为空,就开辟空间 if (table == EMPTY_TABLE) { inflateTable(threshold); }//判断对象是否为null if (key == null) return putForNullKey(value); int hash = hash(key); //算哈希值,调用了hash()。和添加进来元素对象的hashCode()相关//在哈希表中查找hash值 int i = indexFor(hash, table.length); for (Entry e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = https://www.it610.com/article/e.value; e.value = value; e.recordAccess(this); return oldValue; //走这里其实是没有添加元素 } }modCount++; addEntry(hash, key, value, i); //把元素添加 return null; }transient int hashSeed = 0; //hash() final int hash(Object k) { int h = hashSeed; if (0 != h && k instanceof String) { return sun.misc.Hashing.stringHash32((String) k); } h ^= k.hashCode(); //这里调用的是添加进来元素对象的hashCode()// This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). h ^= (h>>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } }

通过查看add()源码,可以看出这个方法底层依赖两个方法:hashCode()和equals()。也就是说由这两个方法保证元素唯一性的。
步骤:首先比较哈希值,如果相同,继续走,比较地址值或者比较equals(),如果不同,就直接添加到集合中
按照方法的步骤来说:先看hashCode()值是否相同
相同:继续比较equals()方法。返回true:说明元素重复,就不添加;返回false:说明元素不重复,就添加到集合。
不同:就直接把元素添加到集合。
如果类没有重写hashCode()和equals(),默认使用的Object()。一般来说不会相同。
而String类重写了hashCode()和equals()方法,所以,它就可以把内容相同的字符串去掉,只留下一个。

LinkedHashSet集合类
LinkedHashSet集合类继承自HashSet,LinkedHashSet底层数据结构由 哈希表和链表组成
特点:有序、唯一
链表保证元素有序(存储和取出顺序一致);
哈希表保证元素的唯一性。
TreeSet集合类
TreeSet底层数据结构是二叉树。(也称红黑树,是一种自平衡的二叉树)。TreeSet底层是TreeMap
如何存进去元素的?
第一个元素存储的时候,直接作为根节点存储;从第二个元素开始,每个元素与根节点进行比较:
小:作为左节点
大:作为右节点
相等:不存储
如何取出元素?
分 前序遍历、中序遍历、后序遍历。从根节点开始,按照左中右的原则依次取出元素。
特点:排序、唯一
能够对元素按照某种规则进行排序,具体取决于使用的构造方法,有2种:
1 自然排序 -- 无参构造
真正的比较是依赖于元素的compareTo(),这个方法定义在Comparable接口里面的。所以,要想重写compareTo(),就必须实现Comparable接口,这个接口表示的就是自然排序。
2 比较器排序 -- 带参构造
因为带参构造方法需要传入一个Comparator接口,实际要传入的是此接口的实现类的对象。传入此接口实现类的对象可以外部定义一个类实现此接口,重写compare(),但如果只用一次,一般使用匿名内部类,在匿名内部类重写compare()
TreeSet集合类保证元素排序和唯一的原理:
排序:
1 自然排序(是元素具备比较性)
让元素所属的类实现自然排序接口Comparable
2 比较器排序(是集合具备比较性)
让集合的构造方法接收一个比较器接口Comparator的实现类对象
唯一:
是根据比较的返回是否是0来决定的
Map集合
特点:Map集合的最大特点,就是它可以存储键值对的元素,键和值是映射的关系,将键映射到值,一个键映射一个值。键唯一,但值可以重复。无序。
Map集合和Collection集合的区别:
Map集合存储元素是成对出现的,Map集合的键是唯一的,值是可重复的;Collection集合是单独存储元素的,Collection集合的子接口Set是唯一的,List是可重复的。
Map集合的数据结构只针对键,与值无关;Collection集合数据结构针对的是元素
Map集合功能概述
添加功能 V put(K key,V value) 添加元素
删除功能 1 void clear()
2 V remove(Object Key)
1 移除所有键值对元素
2 根据键删除键值对元素,并把值返回
判断功能 1 boolean containsKey(Object key)
2 boolean containsValue(Object Value)
3 boolean isEmpty()
1 判断集合是否包含指定的键
2 判断集合是否包含指定的值
3 判断集合是否为空
获取(遍历)功能 1 Set> entrySet()
2 Set KeySet()
3 V get(Object key)
4 Collection values()
1 返回键值对对象的集合
Map.Entry是一个接口,此接口有常用的2个方法:
K getKey():返回与此项对应的键
V getValue():返回与此项对应的值
2 获取集合中所有的键的集合
3 根据键获取值
4 获取集合中所有的值的集合
长度功能 int size() 返回集合键值对的对数
Map集合的遍历
方法1:根据键找值
思路:获取集合中所有的键的集合;遍历键的集合,得到每一个键;根据键获取值。
先用KeySet(),然后用foreach循环遍历Set集合,得到每一个键,然后再用get(Object key)。
方法2:根据键值对对象找键和值
思路:获取所有键值对对象的集合;遍历键值对对象的集合,得到每一个键值对对象;根据键值对对象得到键和值
public class MapDemo {public static void main(String[] args) { Map map = new HashMap(); map.put("鹿晗", "关晓彤"); map.put("雪儿", "范范"); map.put("黄晓明", "angelababy"); Set set = map.entrySet(); for (Entry entry : set) { String key = entry.getKey(); String value = https://www.it610.com/article/entry.getValue(); System.out.println(key+", "+value); } }}

LinkedHashMap集合类
LinkedHashMap集合类继承HashMap。LinkedHashMap底层数据结构由 哈希表和链表组成
特点:有序、唯一
链表保证元素有序(存储和取出顺序一致);
哈希表保证元素的唯一性。
TreeMap集合类
TreeMap键的数据结构是二叉树。
特点:排序、唯一
HashMap和Hashtable的区别
Hashtable是jdk1.0出现的,所以它的命名不规范,它的底层也是Map集合的哈希表结构,用法几乎是一样的,HashMap的出现就是用来代替Hashtable的,就像ArrayList集合用来代替Vector。
Hashtable:线程安全的,所以效率低。不允许null键null值;
HashMap:线程不安全,所以效率高。允许null键null值。
Collections——集合操作工具类概述
要知道的几个静态方法
排序功能 public static void sort(List list) 默认自然排序
二分查找 public static int binarySearch(List list, T key)
最大值 public static T max(Collectin coll)
反转 public static void reverse(List list)
随机置换 public static void shuffle(List list)

    推荐阅读