HashMap 原理和源码分析

1. HashMap 存储结构

采用哈希表来存储数据。结构如下:

《HashMap 原理和源码分析》

它在内部维护了一个Entry数组

transient Entry<K,V>[]table;

Entry 的结构如下:

static class Entry<K,V>implementsMap.Entry<K,V>{
  final K key;
  V value;
  Entry<K,V> next;
  int hash;
}


2. 容量的调整

HashMap 中table 的大小是动态变化的,并可以设置一装载因子(load factor)。当HashMap中元素总量超过阈值时(capacity*loadFactor),再向其中新增元素将会重新分配一个table 替换当前的。Table大小一定是2的幂,比如最小是16,然后增长的话会调整到32,64,128…,直到最大 map 大小2^30。

2.1 存储位置

每个元素根据其 hash的不同存储在 table 数组的不同位置:

static int indexFor(int h,int length){
    return h &(length-1);
}

2.2 获取数据

final Entry<K,V>getEntry(Object key){
    int hash = (key== null)? 0:hash(key);
    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!=null&&key.equals(k))))
        return e;
    }
    return null;
}

获取数据时直接找到相应 index位置的链表,遍历链表找到对应元素。

2.3 put 操作

如果已经有对应 key的元素,则用新元素替代,并返回旧元素。否则返回 null

Put的时候判断当前元素数是否达到阈值,如果达到需要resize table。先判断大小是否达到最大限制,没有的话 capacity 翻倍。然后将旧 table中的元素重新计算 bucket位置放入新table

void resize(int newCapacity){
    Entry[] oldTable=table;
    Int oldCapacity= oldTable.length;
    if(oldCapacity ==MAXIMUM_CAPACITY){
    threshold=Integer.MAX_VALUE;
    return;
    }
 
    Entry[] newTable= newEntry[newCapacity];
    Boolean oldAltHashing= useAltHashing;
    useAltHashing|=sun.misc.VM.isBooted()&&
        (newCapacity>=Holder.ALTERNATIVE_HASHING_THRESHOLD);
    Boolean rehash= oldAltHashing^ useAltHashing;
    transfer(newTable,rehash);
    Table = newTable;
    threshold = (int)Math.min(newCapacity*loadFactor,MAXIMUM_CAPACITY+1);
}

3. 多线程访问问题

HashMap访问迭代器的时候可能抛出 ConcurrentModificationException

 

final Entry<K,V> nextEntry(){
    if(modCount!=expectedModCount)
        throw new ConcurrentModificationException();
    Entry<K,V>e=next;
    if(e==null)
        throw new NoSuchElementException();
 
    if((next=e.next)==null){
        Entry[]t=table;
        while(index<t.length&&(next=t[index++])==null)
            ;
    }
    current = e;
    return e;
}

modCount在每次HashMap发生结构性变化的时候都会改变(新增、删除元素,rehash),而 expectedModCount 是迭代器维护的,并不会随着HashMap结构变化而改变。

需要注意,并不是一定是多线程访问才会触发 ConcurrentModificationException。在单线程的访问中,迭代器操作中不能直接通过HashMap.put HashMap.remove操作更改 HashMap。如果确实需要删除,可以通过迭代器提供的 remove方法删除元素,迭代器的 remove方法会修改expectedModCount。当然,多线程访问还是一定会有问题的。

public voidremove(){
    if(current==null)
        throw newIllegalStateException();
    if(modCount!=expectedModCount)
        throw newConcurrentModificationException();
    Objectk=current.key;
    current=null;
    HashMap.this.removeEntryForKey(k);
    expectedModCount =modCount;
}

4. 其他

还有几点需要注意

1. 初始化时,如果可以提前预知HashMap 的大小,可以指定一个initialSize,避免因为size 过小导致put 的时候多次 resize 操作

2. HashMap 最大size 大小为 1<<30, 也就是1G。因此需要注意向Map 中放入的内容一定不要超过1G。

    原文作者:地球原住民
    原文地址: https://blog.csdn.net/javafreely/article/details/51049748
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞