前言:写的比较简略,主要是用作记录与总结
HashMap的底层实现
数据结构
HashMap底层是用数组实现的,以链表的方式来解决散列冲突,当链表长度达到一定数值之后,将链表重构为红黑树
初始化
刚开始创建一个HashMap的时候,可以指定初始容量的大小,之后的HashCode就是在这个基础上生成的,以及数据的增长因子
插入
- 判断是否为空
- 计算hash值,判断是否存在
- 插入之后判断是否需要扩容
扩容
时机:当size > 允许的最大元素数量threshold
,就会触发扩容,threashold默认为数组大小的0.75(也就是加载因子)
大小:每次将数组大小扩大 2 n 2^n 2n倍
数据移动:数组元素链表中的第一个数组下标不会产生变化,在遍历链表其他元素中通过算法"e.hash & oldCap"!=0
则将链表元素放入新数据数组下标为[原始数据下标+原始数据长度]
取值
计算hash值,然后取值就行了
获取HashCode
不同类型的数据,有着不同生成HashCode的方法
String类型
将字符串转换成char数组,使用公式 s [ 0 ] ∗ 3 1 ( n − 1 ) + s [ 1 ] ∗ 3 1 ( n − 2 ) + . . . + s [ n − 1 ] s[0]*31^{(n-1)} + s[1]*31^{(n-2) }+ ... + s[n-1] s[0]∗31(n−1)+s[1]∗31(n−2)+...+s[n−1]进行计算得出最后的值
Integer类型
直接将值作为key,(其实是取余之后,比如初始size为4,那么1和5就在同一个hash值上
PS
- 数组默认初始大小为16,第一次插入的时候,一定会出发一次扩容,也就是将数组真正变为16,可以自己指定
- 在jdk1.8中,当链表长度大于等于8之后,就会被重构为红黑树
- 并发情况下,HashMap进行put操作会引起死循环,导致CPU利用率接近100%
HashMap与HashTable的区别
相同点:
- 最底层的数据结构是一样的,都是用数组实现的
区别:
- HashMap是非同步的,没有对读写等操作进行锁保护,所以是线程不安全的,在多线程场景下会出现数据不一致的问题。
而HashTable是同步的,所有的读写等操作都进行了锁(synchronized
)保护,在多线程环境下没有安全问题。但是锁保护也是有代价的,会对读写的效率产生较大影响。 - HashMap结构中,是允许保存
null
的,Entry.key
和Entry.value
均可以为null
。
但是HashTable中是不允许保存null
的。 - HashMap的迭代器(
Iterator
)是fail-fast迭代器。
但是Hashtable的迭代器(enumerator
)不是fail-fast的。如果有其它线程对HashMap进行的添加/删除元素,将会抛出ConcurrentModificationException
,但迭代器本身的remove
方法移除元素则不会抛出异常。这条同样也是Enumeration和Iterator的区别。
TreeMap
有序的map,不是基于hash(数组)实现,而是基于红黑树实现的
ArrayList
底层是用数组实现,可以动态增长
Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。
动态增长:新数组为旧数组的1.5倍,建立新的数组,然后将旧数组的数据复制过去
add(int index, E element)
在指定位置插入元素,原理是声明一个新的数组,然后复制和插入,所以开销很大
LinkedList
底层是用双向链表实现的,因为实现了Deque接口,所以还是一个队列,可以当成双端队列使用
参考
HashMap底层实现原理
HashTable原理和底层实现
Java ArrayList底层实现原理源码详细分析Jdk8