java 集合总体分为两大类,一类是单列集合Collection,一类是双列集合Map
Collection 接口的子接口有List接口,Set接口
List: 一个有序的(元素存入集合的顺序和取出的顺序是一致的)容器,元素可以重复,可以插入多个null元素,元素都有索引;
List接口的实现类主要有ArrrayList,LinkedList,Vector等
Set: 一个无序(存入和取出顺序有可能不一致) 容器,不可以储存重复元素,只允许存入一个null元素,必须保证元素一致性
set接口的主要实现类有HashSet,TreeSet,LinkedHashSet等
Map 是一个键值对集合,存储键,值之间的映射.key无序,唯一,value不要求有序,允许重复
Map接口的主要实现类有: HashMap,TreeMap,HashTable,ConcurrentMap
1. List接口 ArrayList和LinkedList
(1) ArrayList优点: ArrayList底层以数组实现(在内存中是连续的),是一种随机访问模式,实现了RandomAccess 接口,因此查找的时候非常快,在顺序添加一个元素的时候非常方便;
RandomAccess 接口的主要目的是允许一般的算法能更改其行为,从而在随机或连续访问列表时能提供良好的性能。
通俗讲,就是在随机访问或者顺序遍历集合时候,我们可以根据集合实现类是否实现了RandomAccess接口,来决定使用不同的遍历策略。
如果集合类实现了RandomAccess接口,推荐使用for (int i=0, n=list.size(); i < n; i++)遍历,反之使用Iterator迭代器遍历集合。
不过现在机器性能很高了,对于集合大小很小的情况下,其实2种遍历方案性能差不多,如果数据量极大则可以根据情况进行决策。简而言之就是因材施教,切不可盲目滥用。
(2)缺点: 删除和插入的时候,需要做一次元素复制操作,如果复制的元素很多,那么就会比较耗费性能;
(3) LinkedList 是双向链表的数据结构实现的,ArrarList比LinkedList查找效率要高,因为LinkedList是线性的数据存储方式需要移动指针从前往后依次查找
增加和删除效率: 在非首尾的增加可删除操作,LinkedList效率要比ArrayList高,因为ArrayList增删操作要影响组内其他数据的下标
内存空间占用:LinkedList 比 ArrayList 更占内存,因为 LinkedList 的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。
双向链表: 包含两个指针,一个 prev 指向前一个节点,一个 next 指向后一个节点。
理解参考:java中ArrayList为什么比LinkedList查询速度快? - 知乎
ArrayList扩容:
以无参数构造方法创建 ArrayList
时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为 10
超过容量,新容量会以原先容量的1.5倍扩容
2.Set接口 HashSet和TreeSet
hashSet(无序,唯一) 基于HashMap实现的,底层采用哈希表,它的排序规则是按照hash函数决定的,无法人为设置
TreeSet(有序,唯一)底层是红黑树(自平衡的排序二叉树)可以使用自然排序或者比较器排序,扩容通过结点连接
红黑树: 又叫平衡二叉b树,红黑树的所有节点排成一条线后一定是左小右大,并且在插入新的节点时,
会通过左旋右旋的方式来平衡二叉树;
3.Map接口 HashMap,TreeMap和HashTable
HashMap: JDK1.7是有数组和链表组成的,数组是HashMap的主体,链表主要是为了解决哈希冲突而存在的,jdk1.8 由“数组+链表+红黑树”组成。当链表过长,比较影响HashMap的性能,
因此,JDK1.8 对数据结构做了进一步的优化,引入了红黑树,链表和红黑树在达到一定条件会进行转换:
-
当链表超过 8 且数据总量超过 64 才会转红黑树。
-
将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树,以减少搜索时间。
解决Hash冲突方法有:开放定址法、再哈希法、链地址法(拉链法)、建立公共溢出区。HashMap中采用的是 链地址法 。
链地址法就是将相同hash值的对象组织成一个链表放在hash值对应的槽位
hashMap的冲突是不得已的,在最优的情况下,应该每个值存在单独的节点上,冲突后才会挂在链表上。冲突超过8个才会转换为红黑树。很少会有这么多冲突,如果出现了,就说明hash方法不适合。
1)、hashMap1.7 扩容: 就是hashmap在存值的时候(默认大小为16,负载因子0.75,阈值12),可能达到最后存满16个值的时候,再存入第17个值才会发生扩容现象,因为前16个值,每个值在底层数组中分别占据一个位置,并没有发生hash碰撞。(集合长度达到阈值(集合长度*0.75)就扩容)
(2)、当然也有可能存储更多值(超多16个值,最多可以存26个值)都还没有扩容。原理:前11个值全部hash碰撞,存到数组的同一个位置(这时元素个数小于阈值12,不会扩容),后面所有存入的15个值全部分散到数组剩下的15个位置(这时元素个数大于等于阈值,但是每次存入的元素并没有发生hash碰撞,所以不会扩容),前面11+15=26,所以在存入第27个值的时候才同时满足上面两个条件,这时候才会发生扩容现象(发生哈希冲突)。
JDK1.8 是先添加,在扩容。具体put是否扩容需要满足一个条件:只要达到阈值就扩容
HashTable: 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的
hashTable 的key和value都不能为null;
hashTable是线程安全的,因为它的每一个方法都有synchronized 关键字,因此可直接用于多线程中。
虽然HashMap是线程不安全的,但是它的效率远远高于Hashtable,这样设计是合理的,因为大部分的使用场景都是单线程。当需要多线程操作的时候可以使用线程安全的ConcurrentHashMap。
ConcurrentHashMap虽然也是线程安全的,但是它的效率比Hashtable要高好多倍。因为ConcurrentHashMap使用了分段锁,并不对整个数据进行锁定。
TreeMap :红黑树(自平衡的排序二叉树)