参考视频:剖析HashMap源码
1、 图解数据结构【有图有真相】
HaspMap数据结构是:数组 + 链表
hashmap原理:
采⽤数组+链表的结构来
实现,添加元素时,⾸先计算key的hash值,然后根据这个hash值,定位到下标,如果冲突的话,则在该下标节点处链上⼀个链表,以头插法
添加新元素为链表头结点,如果链表长度超过指定长度,则转换为红⿊树
。hashmap解决冲突的办法叫做链地址法
,或者叫拉链法
。
在jdk1.8之后为了解决HashMap链表过程,导致遍历产生的时间过长,引入了红黑树进行了进一步优化。
当链表长度太长(超过TREEIFY_THRESHOLD = 8)时,当Node[] table数组长度超过64(MIN_TREEIFY_THRESHOLD = 64)时,链表就转化为红黑树,利用红黑树快速增删改查的特点提高HashMap的性能。
1、HashMap的扰动函数怎么解释?
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
key.hashCode()函数调⽤的是key键值类型⾃带的哈希函数,返回的数据是-2147483648到2147483648,基本上可以解决Hash冲突的问题了。但是这个数据在内存中需要很⼤的空间保存。
右位移16位,正好是32bit的⼀半,⾃⼰的⾼半区和低半区做异或,就是为了混合原始哈希码的⾼位和低位,以此来加⼤低位的随机性。⽽且混合后的低位掺杂了⾼位的部分特征,这样⾼位的信息也被变相保留下来。
https://www.todaysoftmag.com/article/1663/an-introduction-to-optimising-a-h
ashing-strategy
扰动函数主要的作⽤就是为了降低Hash碰撞的发⽣
。
2、需要知道下⾯的位运算
⼀个求余的%等价为:X & (2^N - 1)?
SparseArray比较节省内存,如果我们对内存要求比较高,但是查找效率没那么高,就可以使用SparseArray
SparseArray核⼼特点:
- 以
键值对
形式进⾏存储,基于⼆分查找
,因此查找的时间复杂度为0(LogN); - 由于SparseArray中Key存储的是
数组
形式,因此可以直接以int作为Key。避免了HashMap的装箱拆箱操作,性能更⾼且int的存储开销远远⼩于Integer; - 采⽤了
延迟删除
的机制;
常⽤数据结构总结:
数组(Array)、链表(Linked List)、堆(Heap)、栈(Stack)、队队列列(Queue)、树(Tree)、图(Graph)、散散列表列表(Hash)
数组:有限个相同类型的数据的有序
的集合。
优点:
- 按照下标查询,查询速度快。
- 按照下标遍历⽅便。
缺点:
- 插⼊⼀个其它的需要移动位置,故⽽插⼊慢。
- 数据类型单⼀。
链表:物理存储单元上⾮连续、⾮顺序
的存储结构,数据元素的逻辑顺序是通过链表中的指针
链接次序实现的。分单向链表、双向链表、循环链表。
优点:
- 根据左右两个指针便可插⼊,故⽽插⼊速度快。
- 不需要初始化容量。
缺点:
- 需要遍历整个链表查询,故⽽查询慢。
- 含有⼤量指针域,占⽤空间⼤。
堆:⼀种特殊的树,完全⼆叉树。分⼤顶堆和⼩顶堆。
- ⼤顶堆:⼩数据的元素插⼊、删除快,⼤数据的元素慢。
- ⼩顶堆:⼤数据的元素插⼊、删除快,⼩数据的元素慢。
堆本质:二叉树的特性来维护一维数组
堆和树的区别:从数据结构上来讲最本质的区别树用指针来修饰;堆不使用指针,性能更高,节约内存
总结:
- hashmap的数据结构包括了初始数组、链表、红黑树;
- 插入数据的时候使用pos = key % size 来进行插入数据;
- 当两个或者两个以上的key的相同就会发生冲突,就会挂在数组初始位置的链表后;
- 当某个节点后出现过多的链表节点的时候,就会转换成红黑树以提高效率。
- 数组容量2的倍数:
1)提高运算速度;
2)增加散列度,降低冲突;
3)减少内存碎片。 - hash函数与pos定位:hashcode的高16位与低16位进行异或求模,增加了散列度,降低了冲突。
- 插入冲突:通过单链表解决冲突,如果链表长度超过(TREEIFY_THRESHOLD = 8),进行单链表和红黑树的转换以提高查询速度。
- 扩容:
1)扩容的条件:实际节点数大于等于容量的四分之三;
2)扩容后数据排布:要么是原下标的位置,要么是原下标 + 原容量的位置 - 序列化:只存储了数组的容量、实际节点数量和各个节点的key、value值。