日常注意
hashMap的扩容存在大量rehash,注意如果知道需存放容量,一次性设置初始容量。
避免16的反复扩容加载因子0.75属于泊松分布,没特殊必要不要骚改。
乘积值
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
获取hashCode 31作为乘积值,原因是减少Hash碰撞概率
扰动函数
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
(h = key.hashCode()) ^ (h >>> 16)。把哈希值右移16位,也就正好是自己长度的一半,之后与原哈希值做异或运算,这样就混合了原哈希值中的高位和低位,增大了随机性
初始化容量和负载因子
散列数组需要一个2的倍数的长度,因为只有2的倍数在减1的时候,才会出现01111这样的值
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
如果传入奇数的初始容量,最终会转换为最临近的2的倍数的二进制
扩容元素
原哈希值与扩容新增出来的长度16,进行&运算,如果值等于0,则下标位置不变。如果不为0,那么新的位置则是原来位置上加16
题外话
特别注意HashMap Key存放对象,需要重写hashcode和equal方法
JAVA8之后,HashMap的Value链表容量超过8,是改为红黑树,减少查询的时间复杂度O(logn)
类别 | key value为null |
---|---|
hashMap | 支持 |
ConcurrentSkipListMap | 不支持 |
ConcurrentHashMap | 不支持 |
HashTable | 不支持 |
linkedhashMap | 支持 |
treeMap | key不支持,value支持 |
为null存在二义性,无法边变是找不到,还是缺失value为null
Collections.synchronizedMap勉强实现同步,key value又可以为null
参考
https://www.jianshu.com/p/ecdf00e33b98
https://www.cnblogs.com/xinzhao/p/5644175.html
https://cloud.tencent.com/developer/article/1690271