HashMap源码分析

HashMap用来存储key-value对,内部使用拉链法Hash表作为存储结构,key-value被封装成Entry<K, V>,Entry也是链表结点。

 

   1. Hash表的内部结构如下:

 

  Entry<K, V> table[];

 

Java代码   
  1. table[0]-->Entry(K,V)-->Entry(K,V)  
  2. table[1]-->Entry(K,V)  
  3. table[2]  
  4. table[3]-->Entry(K,V)  
  5. table[.]  

 

    Entry<K,V>数据域代码:

 

    static class Entry<K,V> implements Map.Entry<K,V> {

Java代码   
  1.             final K key;  
  2.             V value;  
  3.             final int hash;  
  4.            <strong> Entry<K,V> next;</strong>  
  5. }  

 

    HashMap的Key-Value对,被包装成Entry(K,V),根据key计算hash值,确定在table数组的下标位置,数组元素为链表的链表头,被计算hash映射到相同数组下标位置的key-value都被存储在这个链表当中,这就是解决hash冲突的办法 。

 

   2.HashMap中查找目标Entry<K,V>

    也就是说根据key进行hash只能找到Entry(K,V)在哪个链表当中,查找到具体确切的Entry(K,V)还需要遍历整个链表的每个节点,针对每个节点去匹配key值,如果链表很长,那么效率就会很低,体现不出Hash表的优势。理想的情况是每个链表不多于一个节点,这样通过hash就可以直接找到目标Entry(K,V),能够在O(1)内实现元素的查找,像数组一样,在存储内容与存储位置之间建立直接的映射关系。

 

    3.HashMap中的扩容

    为了提升Hash表的性能,在HashMap中存储的k-v对数目超过了预定的负载量threshold时,就对HashMap进行扩容,实际上就是使用table[]数组成倍增加,这样做的目的是使每个链表长度较为短小,能够实现快速的定位目标结点;但是扩容需要对原HashMap中的每个结点重新计算存储位置,迁移到新的table[]当中,这也是一笔不少的开销,应该减少扩容的次数,所以根据应用场景选择一个合适的loadFactor和capacity比较重要,loadFactor和capacity可以在HashMap的构造函数中设置。

    Threshold计算: threshold = (int)(capacity * loadFactor);( 当前容量 * 负载因子)

    默认的容量和负载因子:

    Static int final DEFAULT_INITIAL_CAPACITY = 16;

    Static int final DEFAULT_LOAD_FACTOR = 0.75f;

 

    4.HashMap不是线程安全的,代码中没有任何的同步措施,在多线程中环境中需要注意。

 

    5.代码分析

       构造函数

Java代码   
  1. //指定初始容量和负载因子的构造函数    
  2.  public HashMap(int initialCapacity, float loadFactor) {  
  3.               
  4.             //检测参数的合法性initialCapacity, loadFactor  
  5.             if (initialCapacity < 0)  
  6.                 throw new IllegalArgumentException("Illegal initial capacity: " +  
  7.                                                    initialCapacity);  
  8.             if (initialCapacity > MAXIMUM_CAPACITY)  
  9.                 initialCapacity = MAXIMUM_CAPACITY;  
  10.               
  11.             if (loadFactor <= 0 || Float.isNaN(loadFactor))  
  12.                 throw new IllegalArgumentException("Illegal load factor: " +  
  13.                                                    loadFactor);  
  14.   
  15.             // Find a power of 2 >= initialCapacity  
  16.             int capacity = 1;  
  17.             while (capacity < initialCapacity)   
  18.                 capacity <<= 1;  
  19.           
  20.             this.loadFactor = loadFactor;  
  21.               
  22.             //当HashMap存储的键值对数,超过threshold,就需要对整个hash table进行扩展  
  23.             //threshold = capactiy * loadFactor;通过负载因子计算得来.  
  24.             
  25.             threshold = (int)(capacity * loadFactor);  
  26.               
  27.             //为HashMap依赖的table申请空间  
  28.             table = new Entry[capacity];  
  29.             init();  
  30.         }  

 

   //使用默认的负载因子,默认的初始容器,

Java代码   
  1. public HashMap() {  
  2.         this.loadFactor = DEFAULT_LOAD_FACTOR;  
  3.         threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);  
  4.         table = new Entry[DEFAULT_INITIAL_CAPACITY];  
  5.         init();  
  6.     }  

 

     public HashMap(int initialCapacity) {

Java代码   
  1. this(initialCapacity, DEFAULT_LOAD_FACTOR);  

 

    get(k)方法

 

Java代码   
  1. public V get(Object key) {  
  2.       
  3.     //如果key是null,返回一个Object对象作内部处理,HashMap中可以使用null作为key  
  4.     Object k = maskNull(key);  
  5.       
  6.     //计算此k(key)对应的hash值  
  7.     int hash = hash(k);  
  8.       
  9.     //indexFor:h & (length-1)  
  10.     //计算此key在hash表中的映射到的数组元素位置(每个元素是一个指向链表的头结点)  
  11.     int i = indexFor(hash, table.length);  
  12.       
  13.     //取得链表头,映射到数组同一位置的元素被组织成一个链表(冲突解决方法)  
  14.     Entry<K,V> e = table[i];   
  15.       
  16.     while (true) {  
  17.           
  18.         //如果查找到链表末尾,表示没有查找到,返回null  
  19.         if (e == null)  
  20.             return null;  
  21.           
  22.         //判断所以给key,与存储在HashTable中key是否完全相等  
  23.         //如果完全相等,则查找到目标key-value对,返回value object  
  24.         //x与y两个key完全相等的条件:x == y || x.equals(y);  
  25.         //也就是说如果两个key内容相等或者指向同一个对象引用,均算作相等。  
  26.         if (e.hash == hash && eq(k, e.key))   
  27.             return e.value;  
  28.           
  29.         //继续查找下一个元素  
  30.         e = e.next;  
  31.     }  
  32. }  

 

    put(k,V)

     public V put(K key, V value) {

Java代码   
  1.               
  2.             //如果key为null,通过maskNull转换成Object对象进行内部存储操作  
  3.             K k = maskNull(key);  
  4.               
  5.             //计算key对应的hash值,确定在Hash数组中的位置  
  6.             int hash = hash(k);  
  7.             int i = indexFor(hash, table.length);  
  8.   
  9.               
  10.             //找到链表的头结点后,首先在链表中确定此key是否被其它的key-value对所占用  
  11.             for (Entry<K,V> e = table[i]; e != null; e = e.next) {  
  12.                   
  13.                 <strong>//如果此key已经在HashMap中存在,则更新此key-value中的value值</strong>  
  14.                 if (e.hash == hash && eq(k, e.key)) {  
  15.                     V oldValue = e.value;  
  16.                     e.value = value;  
  17.                     e.recordAccess(this);  
  18.                       
  19.                     //并且返回oldValue  
  20.                     return oldValue;  
  21.                 }  
  22.             }  
  23.   
  24.             modCount++;  
  25.             //确定此key不存在HashMap中后,直接将key-value存入HashMap中,也就是插入链表中  
  26.             <strong>addEntry(hash, k, value, i);</strong>  
  27.             return null;  
  28.         }  
  29.   
  30.   
  31.   
  32. void addEntry(int hash, K key, V value, int bucketIndex) {  
  33.               
  34.             Entry<K,V> e = table[bucketIndex];  
  35.             //Entry内部用来封装key-value对  
  36.             table[bucketIndex] = new Entry<K,V>(hash, key, value, e);  
  37.             /<strong>/如果当前HashMap中存储的k-v对数目(size)超过threshold,需要对整个HashMap进行扩容  
  38.             //扩展成原来的2倍大小</strong>  
  39.             if (size++ >= threshold)  
  40.                 resize(2 * table.length);  
  41.         }  
  42.   
  43.   
  44. void resize(int newCapacity) {  
  45.             Entry[] oldTable = table;  
  46.             int oldCapacity = oldTable.length;  
  47.             if (oldCapacity == MAXIMUM_CAPACITY) {  
  48.                 threshold = Integer.MAX_VALUE;  
  49.                 return;  
  50.             }  
  51.               
  52.             //为HashMap分配新的内存空间  
  53.             Entry[] newTable = new Entry[newCapacity];  
  54.               
  55.             //迁移旧HashMap上的数据到新的空间newTable上  
  56.             transfer(newTable);  
  57.               
  58.             table = newTable;  
  59.               
  60.             //重新计算负载上限  
  61.             threshold = (int)(newCapacity * loadFactor);  
  62.         }  
  63.   
  64.   
  65.             void transfer(Entry[] newTable) {  
  66.             Entry[] src = table;  
  67.             int newCapacity = newTable.length;  
  68.             for (int j = 0; j < src.length; j++) {  
  69.                   
  70.                 //取出数组元素中的链表头结点  
  71.                 Entry<K,V> e = src[j];  
  72.                   
  73.                 //如果链表不是空的  
  74.                 if (e != null) {  
  75.                     src[j] = null;  
  76.                       
  77.                     //为链表中的每一个结点重新分配新的位置在newTable当中  
  78.                     //因为位置的计算是hash & (length-1);length改变了,所以存储位置也跟着变了  
  79.                       
  80.                     do {  
  81.                           
  82.                         Entry<K,V> next = e.next;  
  83.                           
  84.                         //计算结点e的新的存储位置在newTable中  
  85.                         int i = indexFor(e.hash, newCapacity);  
  86.                           
  87.                         //将结点添加到链表newTable[i]中  
  88.                         e.next = newTable[i];  
  89.                         newTable[i] = e;  
  90.                           
  91.                         //e指向下一个结点  
  92.                         e = next;  
  93.                     } while (e != null);  
  94.                       
  95.                 }  
  96.             }  
  97.         }  

 

    remove(k)方法

    public V remove(Object key) {

Java代码   
  1.         Entry<K,V> e = removeEntryForKey(key);  
  2.         return (e == null ? null : e.value);  
  3. }  
  4.   
  5.   
  6.   
  7. ntry<K,V> removeEntryForKey(Object key) {  
  8.           
  9.         //根据key计算hash值,映射到数组下标位置,找到链表头  
  10.         Object k = maskNull(key);  
  11.         int hash = hash(k);  
  12.         int i = indexFor(hash, table.length);  
  13.           
  14.         Entry<K,V> prev = table[i];  
  15.         Entry<K,V> e = prev;  
  16.   
  17.         while (e != null) {  
  18.               
  19.             Entry<K,V> next = e.next;  
  20.               
  21.             //查找封装目标key-value的Entry  
  22.             if (e.hash == hash && eq(k, e.key)) {  
  23.                 modCount++;  
  24.                 size--;  
  25.                   
  26.                 <strong>//这种情况只有在链表中只有一个结点时候才会才成立</strong>  
  27.                 if (prev == e)   
  28.                     table[i] = next;  
  29.                   
  30.                 else  
  31.                     prev.next = next;  
  32.                 e.recordRemoval(this);  
  33.                 return e;  
  34.             }  
  35.             //在遍历过程中记录当前结点e的前驱  
  36.             prev = e;  
  37.             e = next;  
  38.         }  
  39.      
  40.         return e;  
  41.     }  

 

   eq()用于比较两个key是否相等

   两个key相等的条件是:(1).两者指向同一个引用

                                  (2).两者equals相等(考虑是否要重写key类的equals(),根据需要)

    static boolean eq(Object x, Object y) {

Java代码   
  1. return x == y || x.equals(y);  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值