深入Java集合学习系列:HashMap的实现原理

个人CSDN 链接 :http://blog.csdn.net/tang06211015/article/details/50685635


1. HashMap概述:

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


2. HashMap的数据结构:

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

   blob.png

   blob.png

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

  源码如下:

 
static final Entry<?,?>[] EMPTY_TABLE = {};      
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;    
/** 
* The number of key-value mappings contained in this map. 
*/   
transient int size;   
int threshold;  // 临界值 它等于HashMap的容量乘以负载因子   
final float loadFactor;// 负载因子   
public V put(K key, V value) {   
   // 如果table为空,则使其不为空   
        if (table == EMPTY_TABLE) {   
            inflateTable(threshold);   
        }   
       // 如果key为null,调用putForNullKey处理   
        if (key == null)   
            return putForNullKey(value);   
        int hash = hash(key);   
     // 搜索指定hash值对应的索引   
        int i = indexFor(hash, table.length);   
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {   
            Object k;   
  // 如果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++;   
        addEntry(hash, key, value, i);   
        return null;   
}   
// 添加entry   
void addEntry(int hash, K key, V value, int bucketIndex) {   
        if ((size >= threshold) && (null != table[bucketIndex])) {   
            resize(2 * table.length);//原来长度的2倍   
            hash = (null != key) ? hash(key) : 0;   
            bucketIndex = indexFor(hash, table.length);   
        }   
      
        createEntry(hash, key, value, bucketIndex);   
}   
void createEntry(int hash, K key, V value, int bucketIndex) {   
        Entry<K,V> e = table[bucketIndex];   
  // 头插法建立链   
        table[bucketIndex] = new Entry<>(hash, key, value, e);   
        size++;   
 }

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

  3) 归纳起来简单地说,HashMap 在底层将 key-value 当成一个整体进行处理,这个整体就是一个 Entry 对象。HashMap 底层采用一个 Entry[] 数组来保存所有的 key-value 对,当需要存储一个 Entry 对象时,会根据hash算法来决定其在数组中的存储位置,在根据equals方法决定其在该数组位置上的链表中的存储位置;当需要取出一个Entry时,也会根据hash算法找到其在数组中的存储位置,再根据equals方法从该位置上的链表中取出该Entry。 

  HashMap的resize(rehash):

   当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,因为数组的长度是固定的。所以为了提高查询的效率,就要对HashMap的数组进行扩容,数组扩容这个操作也会出现在ArrayList中,这是一个常用的操作,而在HashMap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize。

   那么HashMap什么时候进行扩容呢?当HashMap中的元素个数超过数组大小*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,这是一个折中的取值。也就是说,默认情况下,数组大小为16,那么当HashMap中元素个数超过16*0.75=12的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。

 HashMap的性能参数:

   HashMap 包含如下几个构造器:

   HashMap():构建一个初始容量为 16,负载因子为 0.75 的 HashMap。

   HashMap(int initialCapacity):构建一个初始容量为 initialCapacity,负载因子为 0.75 的 HashMap。

   HashMap(int initialCapacity, float loadFactor):以指定初始容量、指定的负载因子创建一个 HashMap。

   HashMap的基础构造器HashMap(int initialCapacity, float loadFactor)带有两个参数,它们是初始容量initialCapacity和加载因子loadFactor。

   initialCapacity:HashMap的最大容量,即为底层数组的长度。

   loadFactor:负载因子loadFactor定义为:散列表的实际元素数目(n)/ 散列表的容量(m)。

   负载因子衡量的是一个散列表的空间的使用程度,负载因子越大表示散列表的装填程度越高,反之愈小。对于使用链表法的散列表来说,查找一个元素的平均时间是O(1+a),因此如果负载因子越大,对空间的利用更充分,然而后果是查找效率的降低;如果负载因子太小,那么散列表的数据将过于稀疏,对空间造成严重浪费。

  HashMap的实现中,通过threshold字段来判断HashMap的最大容量:  

  threshold = (int)(capacity * loadFactor);

  结合负载因子的定义公式可知,threshold就是在此loadFactor和capacity对应下允许的最大元素数目,超过这个数目就重新resize,以降低实际的负载因子。默认的的负载因子0.75是对空间和时间效率的一个平衡选择。当容量超出此最大容量时, resize后的HashMap容量是容量的两倍:

  1.   if (size++ >= threshold)     

  2.     resize(2 * table.length);   

 Fail-Fast机制:

   我们知道java.util.HashMap不是线程安全的,因此如果在使用迭代器的过程中有其他线程修改了map,那么将抛出ConcurrentModificationException,这就是所谓fail-fast策略。

  这一策略在源码中的实现是通过modCount域,modCount顾名思义就是修改次数,对HashMap内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给迭代器的expectedModCount。 

  1. HashIterator() {  

  2.     expectedModCount = modCount;  

  3.     if (size > 0) { // advance to first entry  

  4.     Entry[] t = table;  

  5.     while (index < t.length && (next = t[index++]) == null)  

  6.         ;  

  7.     }  

   在迭代过程中,判断modCount跟expectedModCount是否相等,如果不相等就表示已经有其他线程修改了Map:

   注意到modCount声明为volatile,保证线程之间修改的可见性。

  1. final Entry<K,V> nextEntry() {     

  2.     if (modCount != expectedModCount)     

  3.         throw new ConcurrentModificationException(); 

 在HashMap的API中指出:

   由所有HashMap类的“collection 视图方法”所返回的迭代器都是快速失败的:在迭代器创建之后,如果从结构上对映射进行修改,除非通过迭代器本身的 remove 方法,其他任何时间任何方式的修改,迭代器都将抛出ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不冒在将来不确定的时间发生任意不确定行为的风险。

   注意,迭代器的快速失败行为不能得到保证,一般来说,存在非同步的并发修改时,不可能作出任何坚决的保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的做法是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测程序错误。

   

  自定义Map ,便于更好的l理解Map底层数据结构原理: 

public class CustomHashMap<K,V>{  
    @SuppressWarnings("unchecked")  
    private LinkedList<Entry<K,V>>[]  arr  = new LinkedList[9]; //Map的底层结构就是:数组+链表!  
      
    private int size;  
      
    public void put(K key,V value){  
        Entry<K, V> entry=new Entry<K, V>(key, value);  
        //Hash key  
        int hashcode=key.hashCode();  
        hashcode=hashcode<0?-hashcode:hashcode;  
        int index=hashcode%arr.length;  
        LinkedList<Entry<K,V>> object=arr[index];  
        if(object==null){  
            LinkedList<Entry<K,V>> list=new LinkedList<>();  
            list.add(entry);  
            arr[index]=list;  
        }else{  
            for (int i = 0; i < object.size(); i++) {  
                Entry<K,V> e=object.get(i);  
                if(e.getKey().equals(key)){  
                    e.setValue(value);  
                    return;  //键值重复直接覆盖!  
                }  
            }  
            object.add(entry);  
        }  
        size++;  
    }  
      
    public V get(K key){  
        int index=key.hashCode()%arr.length;  
        LinkedList<Entry<K,V>> object=arr[index];  
        if(object!=null){  
            for (int i = 0; i < object.size(); i++) {  
                Entry<K,V> e=object.get(i);  
                if(e.getKey().equals(key)){  
                    return e.getValue();  
                }  
            }  
        }  
        return null;  
    }  
      
    public Entry<K,V> remove(K key){  
        if (size == 0) {  
           return null;  
        }  
           
        int index=key.hashCode()%arr.length;  
        LinkedList<Entry<K,V>> array=arr[index];  
        Entry<K,V> entry=null;  
        if(array!=null){  
            if(array.size()==1){ //说明只有一个值  
                Entry<K,V> e=array.get(0);  
                if(e.getKey().equals(key)){  
                    entry=e;  
                    array=null;  
                    arr[index]=null;  
                    size--;  
                }  
            }else{  
                for (Iterator<Entry<K, V>> iterator = array.iterator(); iterator.hasNext();) {  
                    Entry<K, V> e = (Entry<K, V>) iterator.next();  
                    if(e.getKey().equals(key)){  
                        entry=e;  
                        iterator.remove();  
                        break;  
                    }  
                }  
            }  
        }  
        return entry;  
    }  
      
      
    public boolean containsKey(K key){  
        int index=key.hashCode()%arr.length;  
        LinkedList<Entry<K,V>> array=arr[index];  
        for(int i=0;i<array.size();i++){  
            Entry<K,V> e=array.get(i);  
            if(e.getKey().equals(key)){  
                return true;  
            }  
        }  
        return false;  
    }  
      
    public boolean containsValue(V value){  
        for (int i = 0; i < size; i++) {  
            LinkedList<Entry<K,V>> array=arr[i];  
            if(array!=null){  
                for (int j = 0; j < array.size(); j++) {  
                    Entry<K,V> entry=array.get(j);  
                    if(entry!=null){  
                        if (value.equals(entry.value))  
                            return true;   
                    }  
                }  
            }  
        }  
        return false;  
    }  
      
      
      
    class Entry<K,V>{  
          
          
        private K key;  
          
        private V value;  
  
        public K getKey() {  
            return key;  
        }  
  
        public void setKey(K key) {  
            this.key = key;  
        }  
  
        public V getValue() {  
            return value;  
        }  
  
        public void setValue(V value) {  
            this.value = value;  
        }  
  
        public Entry(K key, V value) {  
            super();  
            this.key = key;  
            this.value = value;  
        }  
  
        @Override  
        public int hashCode() {  
              return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue());  
        }  
  
        @Override  
        public boolean equals(Object obj) {  
            if (this == obj)  
                return true;  
            if (obj == null)  
                return false;  
            if (getClass() != obj.getClass())  
                return false;  
            Entry other = (Entry) obj;  
            if (key == null) {  
                if (other.key != null)  
                    return false;  
            } else if (!key.equals(other.key))  
                return false;  
            if (value == null) {  
                if (other.value != null)  
                    return false;  
            } else if (!value.equals(other.value))  
                return false;  
            return true;  
        }  
          
    }  
      
    public static void main(String[] args) {  
        CustomHashMap<String,String> m = new CustomHashMap<String,String>();  
        m.put("测试", "谢霆锋");  
        m.put("测试", "刘德华");  
        m.put("测试2", "周润发");  
        System.out.println(m.size);  
        String w = m.get("测试");  
        m.remove("测试");  
        w = m.get("测试");  
        System.out.println(w);   
    }  
      
      
}

发表评论