黑马程序员技术交流社区
标题: 深入hashMap[转] [打印本页]
作者: 79819275    时间: 2014-11-4 12:30
标题: 深入hashMap[转]
 本帖最后由 79819275 于 2014-11-4 12:30 编辑 
HashMap是基于哈希表的Map接口的非同步实现。允许使用null值和null键。

HashMap是一个“链表散列”的数据结构,即数组和链表的结合体。HashMap底层就是一个数组结构,数组中的每一项又是一个链表。当新建一个HashMap的时候,就会初始化一个数组。
- /** 
 -  * The table, resized as necessary. Length MUST Always be a power of two. 
 -  */  
 - transient Entry[] table;  
 -   
 - static class Entry<K,V> implements Map.Entry<K,V> {  
 -     final K key;  
 -     V value;  
 -     Entry<K,V> next;  
 -     final int hash;  
 -     ……  
 - }
 
 复制代码 Entry就是数组中的元素,每个 Map.Entry 其实就是一个key-value对,它持有一个指向下一个元素的引用,这就构成了链表。- public V put(K key, V value) {  
 -     // HashMap允许存放null键和null值。  
 -     // 当key为null时,调用putForNullKey方法,将value放置在数组第一个位置。  
 -     if (key == null)  
 -         return putForNullKey(value);  
 -     // 根据key的keyCode重新计算hash值。  
 -     int hash = hash(key.hashCode());  
 -     // 搜索指定hash值在对应table中的索引。  
 -     int i = indexFor(hash, table.length);  
 -     // 如果 i 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素。  
 -     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;  
 -         }  
 -     }  
 -     // 如果i索引处的Entry为null,表明此处还没有Entry。  
 -     modCount++;  
 -     // 将key、value添加到i索引处。  
 -     addEntry(hash, key, value, i);  
 -     return null;  
 - }
 
 复制代码
当我们往HashMap中put元素的时候,先根据key的hashCode重新计算hash值,根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。
addEntry(hash, key, value, i)方法根据计算出的hash值,将key-value对放在数组table的i索引处。addEntry 是HashMap 提供的一个包访问权限的方法,代码如下:
- void addEntry(int hash, K key, V value, int bucketIndex) {  
 -     // 获取指定 bucketIndex 索引处的 Entry   
 -     Entry<K,V> e = table[bucketIndex];  
 -     // 将新创建的 Entry 放入 bucketIndex 索引处,并让新的 Entry 指向原来的 Entry  
 -     table[bucketIndex] = new Entry<K,V>(hash, key, value, e);  
 -     // 如果 Map 中的 key-value 对的数量超过了极限  
 -     if (size++ >= threshold)  
 -     // 把 table 对象的长度扩充到原来的2倍。  
 -         resize(2 * table.length);  
 - }
 
 复制代码
hash(int h)方法根据key的hashCode重新计算一次散列。此算法加入了高位计算,防止低位不变,高位变化时,造成的hash冲突。
- static int hash(int h) {  
 -     h ^= (h >>> 20) ^ (h >>> 12);  
 -     return h ^ (h >>> 7) ^ (h >>> 4);  
 - }
 
 复制代码 对于任意给定的对象,只要它的 hashCode() 返回值相同,那么程序调用 hash(int h) 方法所计算得到的 hash 码值总是相同的。我们首先想到的就是把hash值对数组长度取模运算,这样一来,元素的分布相对来说是比较均匀的。但是,“模”运算的消耗还是比较大的,在HashMap中是这样做的:调用 indexFor(int h, int length) 方法来计算该对象应该保存在 table 数组的哪个索引处。indexFor(int h, int length) 方法的代码如下:
- static int indexFor(int h, int length) {  
 -     return h & (length-1);  
 - }
 
 复制代码 它通过 h & (table.length -1) 来得到该对象的保存位,而HashMap底层数组的长度总是 2 的n 次方,这是HashMap在速度上的优化。
- int capacity = 1;  
 -     while (capacity < initialCapacity)  
 -         capacity <<= 1;
 
 复制代码
这段代码保证初始化时HashMap的容量总是2的n次方,即底层数组的长度总是为2的n次方。
当length总是 2 的n次方时,h& (length-1)运算等价于对length取模,也就是h%length,但是&比%具有更高的效率。
- public V get(Object key) {  
 -     if (key == null)  
 -         return getForNullKey();  
 -     int hash = hash(key.hashCode());  
 -     for (Entry<K,V> e = table[indexFor(hash, table.length)];  
 -         e != null;  
 -         e = e.next) {  
 -         Object k;  
 -         if (e.hash == hash && ((k = e.key) == key || key.equals(k)))  
 -             return e.value;  
 -     }  
 -     return null;  
 - }
 
 复制代码 从HashMap中get元素时,首先计算key的hashCode,找到数组中对应位置的某一元素,然后通过key的equals方法在对应位置的链表中找到需要的元素。
归纳起来简单地说,HashMap 在底层将 key-value 当成一个整体进行处理,这个整体就是一个 Entry 对象。HashMap 底层采用一个 Entry[] 数组来保存所有的 key-value 对,当需要存储一个 Entry 对象时,会根据hash算法来决定其在数组中的存储位置,在根据equals方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry时,也会根据hash算法找到其在数组中的存储位置,再根据equals方法从该位置上的链表中取出该Entry。
我们知道java.util.HashMap不是线程安全的,因此如果在使用迭代器的过程中有其他线程修改了map,那么将抛出ConcurrentModificationException,这就是所谓fail-fast策略。
这一策略在源码中的实现是通过modCount域,modCount顾名思义就是修改次数,对HashMap内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给迭代器的expectedModCount。
- HashIterator() {  
 -     expectedModCount = modCount;  
 -     if (size > 0) { // advance to first entry  
 -     Entry[] t = table;  
 -     while (index < t.length && (next = t[index++]) == null)  
 -         ;  
 -     }  
 - }
 
 复制代码在迭代过程中,判断modCount跟expectedModCount是否相等,如果不相等就表示已经有其他线程修改了Map:
注意到modCount声明为volatile,保证线程之间修改的可见性。
- final Entry<K,V> nextEntry() {     
 -     if (modCount != expectedModCount)     
 -         throw new ConcurrentModificationException();
 
 复制代码
迭代器的快速失败行为不能得到保证,一般来说,存在非同步的并发修改时,不可能作出任何坚决的保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的做法是错误的,正确做法是:迭代器的快速失败行为应该
仅用于检测程序错误。
----------------------------------------------------------------------------------------------
作者:我爱物联网 
出处:http://www.cnblogs.com/yydcdut/ 
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
作者: 79819275    时间: 2014-11-4 12:30
沙发自己坐
作者: 79819275    时间: 2014-11-4 12:30
 本帖最后由 79819275 于 2014-11-4 12:31 编辑 
沙发自己坐
作者: 张周飞    时间: 2014-11-5 09:30
骚年  你写那那么长!!!
虽然仔细 太长容易疲劳!
不过受教了,加深了印象!!
作者: win_top1    时间: 2014-11-6 22:11
总结的很详细!
作者: 15621506590    时间: 2014-11-8 19:05
很不错   正好map这学的不是很好
作者: newLife    时间: 2014-11-9 12:57
又学习了:lol
作者: win_top1    时间: 2014-11-9 23:12
学习了!
| 欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) | 
黑马程序员IT技术论坛 X3.2 |