一.特点:
HashMap的初始容量为16,填充因子为0.75.扩展容量是当前容量的2倍.
当要存入的数据量大于16*0.75(即大于12)时,底层数组会发生扩容,存储的数据会重新计算hashcode,链表及红黑树也会发生变化
※※※※※※※※快速扩容的方法※※※※※※※:
设置HashMap底层数组长度为:
要存入的数据量/0.75<=2的n次方,(取n的最小值)
例:
1000个值,则需设置底层数组的长度为1000/0.75<=2的n次方
结果为:16,即需设置初始长度为1024
二.HashMap的哈希算法:
- 1.得到key值的hashcode
- 2.对hashcode值进行异或运算
- 3.对异或的结果和初始容量(即数组大小)-1 做&运算
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
i = (n - 1) & hash
这样做的目的:
当数组容量很小的时候,计算元素在数组中的位置(n-1)&hash,只用到了hash值的低位,这样当不同的hash值低位相同,高位不同的时候会产生冲突。实际上的hash值将hashcode低16位与高16位做异或运算,相当于混合了高位和低位,增加了随机性。当然是冲突越少越好,元素的分布越随机越好。
三.HashMap存取元素(哈希表)
HashMap初始数组长度为16。数组的每个元素都保存着链表头的地址(或者为null),在向HashMap中put(key,value)的时候,先使用hash算法计算哈希值,然后再和数组的长度减一做与运算。计算出此键值对应该保存到数组的那个位置上,如果此位置没有元素,意思就是链表的头结点为null,那么就新建一个node结点,把key,value以及next保存。
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
....
......
........
}
这个保存键值对的node结点(即Node<K,V>)是一个实现了Map.Entry的内部类,里面的属性有hash值,key,value,以及next。
当执行put(key,value)的时候,如果计算出来的数组位置上有元素的话(说明计算出的hash值和此数组位置对应的单链表上的所有元素的hash值相同,即发生冲突。),就沿着此数组位置对应的单链表上的结点一个个比较(使用equals()方法),如果遇到相同的key,就用新的value替换掉旧的value,如果找不到相同的key,就新建一个node结点,保存hash值,key和value,然后插入到此单链表的尾部。插入之后,这里程序会判断此单链表上的结点个数(这里注意,不是全部的元素结点个数,而是此单链表上的结点个数,和其他数组位置上的单链表无关)是否超过限制(HashMap默认是8),如果超过限制,那么HashMap就会把此单链表转成红黑树,这样做的目的是为了提高get(key)的速度。由时间复杂度原来的O(n)变成了O(logn)。到这还不算完,一旦插入新的node,程序就会检查HashMap的装载量(全部键值对的个数)是否超过阈值,这个阈值是计算出来的,就是装载因子乘上数组容量。一旦装载量大于此阈值,程序就会执行resize()方法进行扩展容量,HashMap是直接扩容2倍,扩容之后,将原来链表数组的每一个链表分成奇偶两个子链表分别挂在新链表数组的散列位置,这样就减少了每个链表的长度,增加查找效率,但是扩容是很费时的。

四.源码:
1.put()方法

get()方法

本文详细解析了HashMap的工作原理,包括其初始容量、填充因子、扩容机制、哈希算法、存储与检索流程,以及链表和红黑树转换条件,是理解Java集合框架中HashMap不可或缺的指南。
1945

被折叠的 条评论
为什么被折叠?



