锁定老帖子 主题:HashMap 死循环的探究
精华帖 (5) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (1)
|
|
---|---|
作者 | 正文 |
发表时间:2011-03-16
通常只读情况下,可以用HashMap,如果读写频繁,就要用ConcurrentHashMap。
楼主的文章总结的很好。 |
|
返回顶楼 | |
发表时间:2011-03-16
说到HashMap,看到项目中参数大部分用HashMap,而且大部分不是通过Map接口来定义的,实在无奈啊。
|
|
返回顶楼 | |
发表时间:2011-03-16
多线程并发put的时候才会有问题。
只要保证put的是同步的,就不会有这个问题了。 另外rehash是比较耗资源的事情,在一开始设置好容量,尽量不rehash. 实在不行,直接用ConcurrentHashMap。 |
|
返回顶楼 | |
发表时间:2011-03-16
最后修改:2011-03-16
在多线程下Entry数组的某个位置上可能出现循环链表。
|
|
返回顶楼 | |
发表时间:2011-03-16
xm_king 写道 chenyongxin 写道 void transfer(Entry[] newTable) {
Entry[] src = table; int newCapacity = newTable.length; /* * 在转换的过程中,HashMap相当于是把原来链表上元素的的顺序颠倒了。 * 比如说 原来某一个Entry[i]上链表的顺序是e1->e2->null,那么经过操作之后 * 就变成了e2->e1->null */ for (int j = 0; j < src.length; j++) { Entry<K,V> e = src[j]; if (e != null) { src[j] = null; do { //我认为此处是出现死循环的罪魁祸首 Entry<K,V> next = e.next; int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } while (e != null); } } } 你确定是倒序? 有点不解:在下慢慢道来 HashMap的put方法: public V put(K key, V value) { if (key == null) return putForNullKey(value); [color=red]int hash = hash(key.hashCode());[/color] int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; [color=red]addEntry(hash, key, value, i);[/color] return null; } void addEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; table[bucketIndex] = new [color=red]Entry<K,V>(hash, key, value, e);[/color] if (size++ >= threshold) resize(2 * table.length); } Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; [color=red]hash = h;[/color] } static int indexFor(int h, int length) { return h & (length-1); } static int hash(int h) { // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } 以下是我测试indexFor的代码和结果,不知道问题出在哪: Entry[] newTable = new Entry[3]; int newCapacity = newTable.length; System.out.println(hash("p1".hashCode())+"-->"+indexFor(hash("p1".hashCode()), newCapacity)); System.out.println(hash("p2".hashCode())+"-->"+indexFor(hash("p2".hashCode()), newCapacity)); System.out.println(hash("p3".hashCode())+"-->"+indexFor(hash("p3".hashCode()), newCapacity)); 结果:3334-->2 3333-->0 3332-->0 实际上,因为HashMap可以存放(null,value)即key可以是null,在resize的时候,所以需要遍历一边,否则,你怎么判断这个table[i]上到底有没有元素呢? 找到原因了:) e.next = newTable[i]; newTable[i] = e;就是把key通过hash算法和indexFor算法后的到的下标放到同一个链中,比如key="p1"和key="p2"通过“indexFor(hash("p1".hashCode()), newCapacity)”后如果他们的下标都是0时,那么他的存储结构是Entry[0]=Entry(hash,"p1","p1",Entry(hash,"p2","p2",null)); 倒序排列的就是p1和p2的顺序,那么代码 e.next = newTable[i]; newTable[i] = e;对Entry[0]中元素的操作我们可以简写成 e.next = e; 直到e为null,就是这样,原本的顺序是e1|e1.next-->e2|e2.next-->null 转换后 null<--e1.next|e1<--e2.next|e2如果在多线程下操作(我模拟的程序结果是有这样一种情况)e2<--e1.next|e1<--e2.next|e2,在Entry数组的某个位置上出现循环链表。 |
|
返回顶楼 | |
发表时间:2011-03-16
一直都在用多线程的并发操作,虽然还没碰到死锁的现象,但还是感谢一下楼主.涨见识啦.
|
|
返回顶楼 | |
发表时间:2011-03-16
chenyongxin 写道 xm_king 写道 chenyongxin 写道 void transfer(Entry[] newTable) {
Entry[] src = table; int newCapacity = newTable.length; /* * 在转换的过程中,HashMap相当于是把原来链表上元素的的顺序颠倒了。 * 比如说 原来某一个Entry[i]上链表的顺序是e1->e2->null,那么经过操作之后 * 就变成了e2->e1->null */ for (int j = 0; j < src.length; j++) { Entry<K,V> e = src[j]; if (e != null) { src[j] = null; do { //我认为此处是出现死循环的罪魁祸首 Entry<K,V> next = e.next; int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } while (e != null); } } } 你确定是倒序? 有点不解:在下慢慢道来 HashMap的put方法: public V put(K key, V value) { if (key == null) return putForNullKey(value); [color=red]int hash = hash(key.hashCode());[/color] int i = indexFor(hash, table.length); for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; [color=red]addEntry(hash, key, value, i);[/color] return null; } void addEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; table[bucketIndex] = new [color=red]Entry<K,V>(hash, key, value, e);[/color] if (size++ >= threshold) resize(2 * table.length); } Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; [color=red]hash = h;[/color] } static int indexFor(int h, int length) { return h & (length-1); } static int hash(int h) { // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } 以下是我测试indexFor的代码和结果,不知道问题出在哪: Entry[] newTable = new Entry[3]; int newCapacity = newTable.length; System.out.println(hash("p1".hashCode())+"-->"+indexFor(hash("p1".hashCode()), newCapacity)); System.out.println(hash("p2".hashCode())+"-->"+indexFor(hash("p2".hashCode()), newCapacity)); System.out.println(hash("p3".hashCode())+"-->"+indexFor(hash("p3".hashCode()), newCapacity)); 结果:3334-->2 3333-->0 3332-->0 实际上,因为HashMap可以存放(null,value)即key可以是null,在resize的时候,所以需要遍历一边,否则,你怎么判断这个table[i]上到底有没有元素呢? 找到原因了:) e.next = newTable[i]; newTable[i] = e;就是把key通过hash算法和indexFor算法后的到的下标放到同一个链中,比如key="p1"和key="p2"通过“indexFor(hash("p1".hashCode()), newCapacity)”后如果他们的下标都是0时,那么他的存储结构是Entry[0]=Entry(hash,"p1","p1",Entry(hash,"p2","p2",null)); 倒序排列的就是p1和p2的顺序,那么代码 e.next = newTable[i]; newTable[i] = e;对Entry[0]中元素的操作我们可以简写成 e.next = e; 直到e为null,就是这样,原本的顺序是e1|e1.next-->e2|e2.next-->null 转换后 null<--e1.next|e1<--e2.next|e2如果在多线程下操作(我模拟的程序结果是有这样一种情况)e2<--e1.next|e1<--e2.next|e2,在Entry数组的某个位置上出现循环链表。 |
|
返回顶楼 | |
发表时间:2011-03-16
那遇到需要用hashmap的时候直接用hashtable不更好?
|
|
返回顶楼 | |
发表时间:2011-03-16
总结的不错,这个地方确实要多考虑线程并发的情况。
|
|
返回顶楼 | |
发表时间:2011-03-16
[quote="xm_king"]
|
|
返回顶楼 | |