在看关于HashMap 1.7 BUG时,即多线程时会导致死锁。从而去查找相关资料,看看到底是什么原因造成的。
总结一下,在看HashMapsh源码中涉及到知识点:`
1. java运算符 与(&)、非(~)、或(|)、异或(^)
- 位异或运算(^): 两个数转为二进制,然后从高位开始比较,如果相同则为0,不相同则为1。
- 位与运算符(&): 两个数都转为二进制,然后从高位开始比较,如果两个数都为1则为1,否则为0。
- 位或运算符(|): 两个数都转为二进制,然后从高位开始比较,两个数只要有一个为1则为1,否则就为0。
- 位非运算符(~): 如果位为0,结果是1,如果位为1,结果是0.
- >>:带符号右移。正数右移高位补0,负数右移高位补1
- >>>:无符号右移。无论是正数还是负数,高位通通补0
2. HashMap多线程死循环,这个是jdk BUG资料也最多,很多都是贴代码,如下:
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i]; //注意这条语句
newTable[i] = e; //注意这条语句
e = next;
}
}
}
transfer方法代码解释:
1、原来的table进行两层遍历,外层遍历table,内层遍历table索引处的链表,从链头遍历到链尾。
2、从原来的链表取出头节点e,计算e的hash值以及在新的table中的bucketIndex值i。newTable[i]指向数组下标对应的元素,
也就是指向数组bucketIndex对应链表的头节点。用e.next指向原先的头节点引用newTable[i],newTable[i]再指向e,把e作为新的头节点。
3、不要忘了我们要不断从原来的链表取出头节点e,直到遍历到链尾为止。所以我们要在2操作开始之前,
Entry<K,V> next = e.next; 用next变量指向原来链表的下一个头节点,
在2操作执行完后, e = next;使e重新成为原来链表的头节点。
总结:说白了,还是不是很直观,怎么才能模拟出来这段代码是不是存在问题呢?反正我没有模拟出来
3. 泊松分布
一句话解释:泊松分布是单位时间内独立事件发生次数的概率分布,指数分布是独立事件的时间间隔的概率分布。
这个概念由容量因子引出来的:static final float DEFAULT_LOAD_FACTOR = 0.75f; //HashMap默认值
容量因子的作用:就是在HashMap的size大于size*0.75的时候,会自动进行扩容
但是源码中后面还跟了一个条件:if((size >= threshold) && (null != table[bucketIndex])){resize(2size)}, 即数组所在位置不为空的情况下,就说说发现那个table[i]有数据了才能进行扩容操作*
参考:泊松分布
问题1:为什么默认初始化桶数组大小为16,为什么加载因子的大小为0.75.这两个值的选取有什么特点
通过看上面的代码我们可以知道这两个值主要影响的threshold的大小,
这个值的数值是当前桶数组需不需要扩容的边界大小,
我们都知道桶数组如果扩容,会申请内存空间,
然后把原桶中的元素复制进新的桶数组中,这是一个比较耗时的过程。
既然这样,那为何不把这两个值都设置大一些呢,
threshold是两个数的乘积,设置大一些就不那么容易会进行扩容了啊。
原因是这样的,如果桶初始化桶数组设置太大,就会浪费内存空间,
16是一个折中的大小,既不会像1,2,3那样放几个元素就扩容,
也不会像几千几万那样可以只会利用一点点空间从而造成大量的浪费。
加载因子设置为0.75而不是1,是因为设置过大,桶中键值对碰撞的几率就会越大,
同一个桶位置可能会存放好几个value值,这样就会增加搜索的时间,性能下降,设置过小也不合适,
如果是0.1,那么10个桶,threshold为1,你放两个键值对就要扩容,太浪费空间了。
总结:0.75和泊松分布没有必然联系,看你怎么理解了,我觉得可以3/4方便计算,毕竟map的size都是2的幂次方
4. Eclipse中debug jdk源码
在debug hashmap时,发现变量都没有值,所以搜索了一下是否支持源码debug,还真有。
网友评论