HashMap使用经验(上)

小时候妈妈都教过我们,不同的东西要放在不同的位置,需要时才能快速找到它。当然这个规则你必须记住,不然怎么都找不到了。HashMap之所以能够做到快速存、取,与我们下面要介绍的内容密切相关。

HashMap和HashSet是JavaCollection Framework的两个重要成员,其中HashMap是Map接口的常用实现类,HashSet是Set接口的常用实现类。虽然HashMap和HashSet实现的接口规范不同,但它们底层的Hash存储机制完全一样,甚至HashSet本身就采用HashMap来实现的。读者请注意,虽然集合号称存储的是Java对象,但实际上并不会真正将Java对象放入Set集合中,只是在Set集合中保留这些对象的引用而言。也就是说:Java集合实际上是多个引用变量所组成的集合,这些引用变量指向实际的Java对象。就像引用类型的数组一样,当我们把Java对象放入数组之时,并不是真正把Java对象放入数组中,只是把对象的引用放入数组中,每个数组元素都是一个引用变量。

我们首先来谈谈HashMap。HashMap是基于哈希表的Map接口的实现,HashMap将Hash值映射到内存地址,直接取得Key所对应的数据。在HashMap中,底层数据结构使用的是数组,所谓的内存地址即数组的下标索引。总的来说,除了非同步和允许使用Null之外,HashMap类与Hashtable大致相同。

HashMap实际上是一个链表的数组。基于HashMap的链表方式实现机制,只要hashCode()和hash()方法实现得足够好,能够尽可能地减少冲突的产生,那么对HashMap的操作几乎等价于对数组的随机访问操作,具有很好的性能。但是,如果hashCode()或者hash()方法实现较差,在大量冲突产生的情况下,HashMap事实上就退化为几个链表,对HashMap的操作等价于遍历链表,此时性能很差。

HashMap的一个功能缺点是它的无序性,即被存入到HashMap中的元素,在遍历HashMap时,其输出是无序的。如果希望元素保持输入的顺序,可以使用LinkedHashMap替代。

HashMap采用一种所谓的“Hash算法”来决定每个元素的存储位置。

当程序试图将多个key-value放入HashMap中时,如代码清单3-45所示。

代码清单3-45 HashMap初始化示例

HashMap<String , Double> map = new HashMap<String ,Double>();

map.put("xiao ming" , 80.0);

map.put("xiao hong" , 89.0);

map.put("xiaohua" , 78.2);

当程序执行map.put("xiao ming" ,80.0);时,系统将调用"xiao ming"的hashCode()方法得到其hashCode值——每个Java对象都有hashCode()方法,都可通过该方法获得它的hashCode值。当我们得到这个对象的hashCode值之后,系统会根据该hashCode值来决定该元素的存储位置。

我们来看一下HashMap源代码中的put(K key,V value)方式,代码如清单3-46所示。

代码清单3-46 HashMap源代码

public V put(K key, V value)   

{   

 // 如果 key 为 null,调用putForNullKey 方法进行处理

 if (key == null)   

     returnputForNullKey(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;   

     // 找到指定 key 与需要放入的 key 相等(hash 值相同

     // 通过 equals 比较放回 true)

     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;   

}  

清单3-46代码调用了一个hash方法用于生成hash值,它是一个纯粹的数学方法,源代码如清单3-47所示。

代码清单3-47 HashMap的hash方法源代码

static int hash(int h)

{   

    h ^= (h >>> 20) ^(h >>> 12);

    return h ^ (h >>>7) ^ (h >>> 4);

}


感兴趣的朋友,可以扫二维码关注 麦克叔叔每晚十点说,一起交流讨论。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章