集合总结(Jdk8+)
一、单列集合(实现Collection接口)
1、结构关系
Collection(单列集合根接口):
- List(子接口):
- ArrayList(子接口实现类)
- LinkedList(子接口实现类)
- Vector(子接口实现类)
- Set(子接口)
- HashSet(子接口实现类)
- LinkedHashSet(子接口实现类)
- TreeSet(子接口实现类)
2、List和Set两个子接口的区别
2.1、List
- List接口下的集合对象存储的数据有序性、可重复性 (“动态”数组)
2.2、Set
- Set接口下的集合对象储存的数据无序性、不可重复性
- 无序性:不等同于随机性,此处的无序性也是按照底层的某种规则进行的遍历
- 不可重复性:每个要添加的元素都执行了hashCode方法,得到的哈希值和已添加的元素的哈希值进行判断(效率低);然后进行equals判断,返回true,添加失败;反之,添加成功
3、List接口实现类底层分析(源码分析)
3.1、ArrayList
-
ArrayList底层是数组
-
调用无参构造创建集合对象:ArrayList list = new ArrayList();无参构造初始化数组为“{ }”;没有明确数组的长度,从而减小了内存的消耗;一旦开始往集合里面添加数据,就先初始化一个长度为10的数组;如果数组长度不够用,数组就会以1.5倍进行扩容,并将原来数组的数据复制到新扩容的数组中(延迟了数组的创建,节省内存)
-
一般建议调用有参构造方法,直接给定数组的大小;避免了“动态扩容”提高效率
3.2、LinkedList
- LinkedList底层是双向链表
3.3、Vector
- 无参构造初始化数组长度为10;扩容倍数2倍
- 线程安全,但效率低
3.4、ArrayList、LinkedList、Vector三者的区别
相同:
- 三个实现类都实现了List接口
- 存储的都是有序、可重复数据
不同点:
- ArrayList(底层:Object[ ] elementData数组):List接口的主实现类,线程不安全但是效率高,适合查询操作(数组有下标)
- LinkedList(底层:双向链表):适合频繁插入和删除的集合,在这种情况下比ArrayList效率高
- Vector(底层:Object[] elementData数组):List接口的老实现类,线程安全但是效率低
4、Set接口实现类底层分析(源码分析)
4.1、HashSet
- HashSet底层是数组+链表
- Set接口的主实现类
- 线程不安全,但是可以存null值
4.2、LinkedHashSet
- HashSet的子类
- 在添加数据的同时,还在每个元素的前面和后面添加了两个“指针”来分别指向上一个和下一个元素,因此遍历内部数据时,可以按照顺序进行遍历
- 优点:LinkedHashSet的遍历效率高于HashSet
4.3、TreeSet
- TreeSet底层是红黑树
- 可以按照添加的对象的属性进行排序(自然排序、指定排序)
- 向TreeSet对象中添加数据,必须是相同类型的对象
4.4、以HashSet为例,来理解HashSet添加数据的过程
- 首先计算添加元素的哈希值
- 根据某种算法(类似模运算,取余)来计算出该元素在HashSet底层数组的存放位置(数组的索引位置)
- 判断该位置是否有元素
- 无:直接插入
- 有:一个(或多个元素-以链表的形式存放)比较哈希值
- 哈希值相同:不能插入该元素(插入失败)
- 哈希值不同:调用equals方法逐个比较
- 返回true:不能插入该元素(插入失败)
- 返回false:可以插入,将数组中的元素替换出来,原来的元素放到链表
二、双列集合(实现Map接口)
1、结构关系
Map(双列集合根接口):
- HashMap(接口实现类)
- LinkedHashMap(HashMap子类)
- Hashtable(接口实现类)
- Properties(Hasntable子类)
- TreeMap(接口实现类)
2、Map接口实现类底层源码分析
2.1、HashMap
- 底层:数组+链表+红黑树
- 线程不安全,但是效率高
- key和value能存储null
1、LinkedHashMap(HashMap的子类)
- 保证在遍历map对象时,可以按照添加的顺序来遍历
- 在原有HashMap的底层结构基础上,添加了一对指针,分别指向前一个和后一个元素
- 对于频繁的遍历元素,LinkedHashMap的效率高于HashMap
2.2、Hashtable
- 作为Map的古老实现类
- 线程安全,但是效率低
- key和value不能存储null
1、Properties(HashTable子类)
- 常用来处理配置文件
- 它的key和value都是String类型
2.3、TreeMap
- 底层:红黑树
- 保证按照添加的key和value键值对来进行排序(和单列集合中的TreeSet实现类类似),这里按照key来进行排序(自然排序和定制排序)
2.4、HashMap底层分析
1、HashMap底层
- 数组+链表(jdk7及以前)
- 数组+链表+红黑树(jdk8之后)
2、HashMap底层实现原理(jdk7)
- 调用无参构造方法实例化集合(HashMap hashMap = new HashMap()),底层初始化长度为16的一维数组Entry[ ] table
- 在实例化的集合对象中,put添加数据:key1-value1
- 首先调用key1所在类的hashCode方法计算哈希值,然后根据特定算法,根据哈希值计算出Entry对象要存放的索引位置;先判断该位置有没有其他元素占用;
- 如果该位置没有元素,那么直接插入(情况一)
- 如果该位置有元素(一个或者多个元素以链表的形式存在),那么需要与该位置所有元素的哈希值进行比较
- 哈希值不同,那么直接插入(情况二)
- 哈希值相同,继续比较:调用key1所在类的equals方法进行比较
- 返回false,那么直接插入(情况三)
- 返回true,使用value1替换里面相同的value2
补充:
- 针对情况二和情况三,此时key1-value1和其他数据以链表的形式存放
- 在不断添加的过程中,会遇到扩容的问题,默认的扩容方式:扩容为原来的两倍,将原数组的数据复制到新的数组中
- 注意:扩容时机有个临界值,并不是数组存满的时候进行扩容>0.75
3、HashMap底层实现原理(jdk8相比于jdk7在底层方面实现的不同)
- 调用无参初始化数组时,没有给定数组长度
- jdk8底层的数组时Node[ ] 而不再是Entry[ ]
- 首次使用put添加数据初始数组长度为16
- jdk7底层:数组+链表;jdk8底层:数组+链表+红黑树
- 当数组某一个索引位置上的元素以链表的形式存放,数据个数>8且当前数组长度>64,此时索引位置上的所有数据,改为使用红黑树进行存储
2.5、说明
- ArrayList和HashMap都是线程不安全的;如果程序要求线程安全,一般的做法就是,我们可以将ArrayList和HashMap转换成线程安全的(Collections工具类下的方法)