HashMap实现原理

来源:互联网 发布:sql存储过程输出参数 编辑:程序博客网 时间:2024/05/01 18:36

1.    HashMap概述:

   HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。(注意HashTab不允许插入null值)

 

2.    HashMap的数据结构:

   在java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。

   从上图中可以看出,HashMap底层就是一个数组结构,数组中的每一项又是一个链表。当新建一个HashMap的时候,就会初始化一个数组。

   源码如下:

Java代码  收藏代码
  1. /** 
  2.  * The table, resized as necessary. Length MUST Always be a power of two. 
  3.  */  
  4. transient Entry[] table;  
  5.   
  6. static class Entry<K,V> implements Map.Entry<K,V> {  
  7.     final K key;  
  8.     V value;  
  9.     Entry<K,V> next;  
  10.     final int hash;  
  11.     ……  
  12. }  

   可以看出,Entry就是数组中的元素,每个 Map.Entry 其实就是一个key-value对,它持有一个指向下一个元素的引用,这就构成了链表。

 

3.    HashMap的存取实现:

   1) 存储:

Java代码  收藏代码
  1. public V put(K key, V value) {  
  2.     // HashMap允许存放null键和null值。  
  3.     // 当key为null时,调用putForNullKey方法,将value放置在数组第一个位置。  
  4.     if (key == null)  
  5.         return putForNullKey(value);  
  6.     // 根据key的keyCode重新计算hash值。  
  7.     int hash = hash(key.hashCode());  
  8.     // 搜索指定hash值在对应table中的索引。  
  9.     int i = indexFor(hash, table.length);  
  10.     // 如果 i 索引处的 Entry 不为 null,则需判断该键是否存在 ,通过循环不断遍历 e 元素的下一个元素。   如果table[i]索引处的Entry为null,表明此处还没有Entry,跳过循环。 
  11.     for (Entry<K,V> e = table[i]; e != null; e = e.next) {  
  12.         Object k;  
  13.         //如果hashMap中存在该键,则覆盖原有的值,并返回原来的值
  14.         if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
  15.             V oldValue = e.value;  
  16.             e.value = value;  
  17.             e.recordAccess(this);  
  18.             return oldValue;  
  19.         }  
  20.     }  
  21.      
  22.     modCount++;  
  23.     //添加新元素
  24.     addEntry(hash, key, value, i);  
  25.     return null;  
  26. }  

   

   addEntry(hash, key, value, i)方法根据计算出的hash值,将key-value对放在数组table的i索引处。addEntry 是HashMap 提供的一个包访问权限的方法,代码如下:

Java代码  收藏代码
  1. void addEntry(int hash, K key, V value, int bucketIndex) {  
  2.     // 获取指定 bucketIndex 索引处的 Entry   
  3.     Entry<K,V> e = table[bucketIndex];  
  4.     // 将新创建的 Entry 放入 bucketIndex 索引处,并让新的 Entry 指向原来的 Entry  
  5.     table[bucketIndex] = new Entry<K,V>(hash, key, value, e);  
  6.     // 如果 Map 中的 key-value 对的数量超过了极限  
  7.     if (size++ >= threshold)  
  8.     // 把 table 对象的长度扩充到原来的2倍。  
  9.         resize(2 * table.length);  
  10. }  

   当系统决定存储HashMap中的key-value对时,完全没有考虑Entry中的value,仅仅只是根据key来计算并决定每个Entry的存储位置。我们完全可以把 Map 集合中的 value 当成 key 的附属,当系统决定了 key 的存储位置之后,value 随之保存在那里即可。

    为什么会产生冲突呢? 
    其实每个不同的类的对象计算hashCode的方式是不同,可能会刚好两个键值恰巧相同,我们可以人为的定义hashCode 方法,使其不同的对象的hashCode 的值相同 

此刻存取方式 如上图一样,如果重复的元素过多,则其遍历的效率必将大打折扣,成为链表。

参考 :http://zhangshixi.iteye.com/blog/672697


0 0