结构
参考
https://mp.weixin.qq.com/s/bVOSat47L0Hskfx9akAN6Q
https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/collection/Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98.md
List
有序,可重复
ArrayList
参考:
https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/collection/ArrayList%E6%BA%90%E7%A0%81+%E6%89%A9%E5%AE%B9%E6%9C%BA%E5%88%B6%E5%88%86%E6%9E%90.md
https://mp.weixin.qq.com/s/WoGclm7SsbURGigI3Mwr3w
特点
查询效率高,遍历效率高,增删效率低(需要大量移动元素),线程不安全
- 底层结构
Object[]数组
容量大小
以无参数构造方法创建 ArrayList 时,实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为 10。
扩容
每次扩容之后容量都会变为原来的 1.5 倍左右(oldCapacity 为偶数就是 1.5 倍,否则是 1.5 倍左右)!
新增和删除使用的是Copy
Vector
- 特点
线程安全,效率低 - 底层结构
Object[]数组 - 扩容
每次扩容之后容量都会变为原来的 2倍左右
LinkedList
- 特点
查询效率低,遍历效率低,增删效率高,线程不安全 - 底层结构
双向链表
Set
无序,不重复
HashSet
- 特点
添加速度快,查询速度快,删除速度快,无序 - 底层结构
采用 Hashmap 的 key 来储存元素;
数值放在 map 中的 key 上,value 上放了个 PRESENT,是一个静态的 Object,相当于 place holder,每个 key 都指向这个 object。
TreeSet
- 特点
有序,查询速度(按照内容查询)比List快,查询速度没有HashSet快 - 底层结构
红黑树
LinkedHashSet
- 特点
有序 - 底层结构
HashSet + LinkedList 的结构
Map
Key-Value映射
HashMap
特点
线程不安全,使用key,value结构存储数据
底层结构
JDK1.7:数组(初始容量默认为16)+链表
JDK1.8:数组(初始容量默认为16)+链表+红黑树(默认当链表长度达到8的时候会转换成红黑树)
JDK1.8 PUT过程
加载因子
默认0.75
加载因子越小,容易扩容,浪费空间,但查找效率高 链表特别短 减少hash冲突
加载因子越大,不易扩容,对空间的利用更加充分,查找效率低(链表拉长)hash冲突比较多,链表比较长
扩容
- 什么时候扩容
当 现有容量 >总容量 × 负载因子 的时候进行扩容
例如:给定的默认容量为 16,加载因子为 0.75。Map 在使用过程中不断的往里面存放数据,当数量达到了 16 * 0.75 = 12 就需要将当前 16 的容量进行扩容 - 如何扩容
分为两步
扩容:创建一个新的Entry空数组,长度是原数组的2倍。
ReHash:遍历原Entry数组,把所有的Entry重新Hash到新数组。
扩容时死循环问题
JDK1.7:采用头插入法,会改变链表的顺序,在多线程操作的情况下,可能出现环形链表,造成死锁
JDK1.8:采用尾插入法,在扩容时会保持链表元素原本的顺序,就不会出现链表成环的问题了。
为什么初始容量是2的n次幂
首先看下index的计算公式:index = HashCode(Key) & (Length- 1)
ConcurrentHashMap
参考:
https://mp.weixin.qq.com/s/AHWzboztt53ZfFZmsSnMSw
https://mp.weixin.qq.com/s/AixdbEiXf3KfE724kg2YIw
特点
线程安全
-
JDK1.7
使用分段锁,使用CAS+Unsafe+ReentrantLock+Volatile,Segment 继承了 ReentrantLock -
JDK1.8
没有分段锁,使用CAS+Unsafe+Synchronized+Volatile
底层结构
-
JDK1.7
Segment 数组 + HashEntry 数组 + 链表,每个 Segment 里边是由 HashEntry 组成的数组,每个 HashEntry之间又可以形成链表。我们可以把每个 Segment 看成是一个小的 HashMap,其内部结构和 HashMap 是一模一样的。所以每一个 HashMap 的内部可以进行扩容。但是 Segment 的个数一旦初始化就不能改变,默认 Segment 的个数是 16 个,你也可以认为 ConcurrentHashMap 默认支持最多 16 个线程并发。 -
JDK1.8
Node 数组 + 链表 / 红黑树
PUT操作
- JDK1.7
- JDK1.8
HashTable
线程安全,get 和put 都会用 synchronized 关键字加锁。
Hashtable 初始容量为:11,负载因子默认都是:0.75。
Hashtable 扩容规则为当前容量翻倍 + 1。
TreeMap
红黑树(自平衡的排序二叉树),有序
LinkedHashMap
有序的HashMap