HashMap源码分析(转)

一、 数据结构   Map 将实际数据存储在 Entry 类的数组中。 代码片段:

 

 

  1. transient Entry[] table;//HashMap 的成员变量,存放数据   
  2. static class Entry<K,V> implements Map.Entry<K,V> {// 内部类 Entry  
  3. final K key;  
  4. V value;
  5. Entry<K,V> next; // 指向下一个数据   
  6. final int hash;  
  7. /** 
  8. * Creates new entry. 
  9. */  
  10. Entry( int h, K k, V v, Entry<K,V> n) {  
  11. value = v;
  12. next = n;
  13. key = k;
  14. hash = h;
  15. }

 

 

transient Entry[] table;//HashMap 的成员变量,存放数据

 

static class Entry<K,V> implements Map.Entry<K,V> {// 内部类 Entry

    final K key;

    V value;

    Entry<K,V> next;// 指向下一个数据

    final int hash;

 

    /**

     * Creates new entry.

     */

    Entry(int h, K k, V v, Entry<K,V> n) {

        value = v;

        next = n;

        key = k;

        hash = h;

    }

 

 

执行下面代码后,可能的存储内部结构是 : Map map = new HashMap();

 

map.put("key1","value1"); map.put("key2","value2"); map.put("key3","value3");  

执行 put 方法时根据 key hash 值来计算放到 table 数组的下标,如果 hash 到相同的下标,则新 put 进去的元素放到 Entry 链的头部,如上图所示。 put 方法的源码后面详细解释。  

 

二、属性和构造方
static final int DEFAULT_INITIAL_CAPACITY = 16;// 默认的初始大小,如果执行无参的构造方法,则默认初始大小为 16  

 

  static final int MAXIMUM_CAPACITY = 1 << 30;// 最大容量, 1073741824  

 

  static final float DEFAULT_LOAD_FACTOR = 0.75f;// 默认的负载因子,如果没有通过构造方法传入负载因子,则使用 0.75   

 

  transient Entry[] table; // 存放具体键值对的 Entry 数组   

 

  transient int size; //HashMap 的大小   

 

  int threshold;// 阀值   threshold = (int)(capacity * loadFactor);  即容量 * 负载因子,执行 put 方法时如果 size 大于 threshold 则进行扩容,后面 put 方法将会看到   

 

  final float loadFactor; // 用户设置的负载因子   

 

  transient volatile int modCount;//HashMap 实例被改变的次数,这个同 ArrayList  


static final int DEFAULT_INITIAL_CAPACITY = 16;// 默认的初始大小,如果执行无参的构造方法,则默认初始大小为 16

static final int MAXIMUM_CAPACITY = 1 << 30;// 最大容量, 1073741824

static final float DEFAULT_LOAD_FACTOR = 0.75f;// 默认的负载因子,如果没有通过构造方法传入负载因子,则使用 0.75

transient Entry[] table; // 存放具体键值对的 Entry 数组

transient int size; //HashMap 的大小

int threshold;// 阀值   threshold = (int)(capacity * loadFactor); 即容量 * 负载因子,执行 put 方法时如果 size 大于 threshold 则进行扩容,后面 put 方法将会看到

final float loadFactor; // 用户设置的负载因子

transient volatile int modCount;//HashMap 实例被改变的次数,这个同 ArrayList

 

 

构造方法一、

  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() {

    this.loadFactor = DEFAULT_LOAD_FACTOR;

    threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);

    table = new Entry[DEFAULT_INITIAL_CAPACITY];

    init();

}

 

 

使用了默认的容量和默认的负载因子。 构造方法二、

  1. public HashMap(int initialCapacity) {  
  2. this(initialCapacity, DEFAULT_LOAD_FACTOR);  
  3. }

 

 

public HashMap(int initialCapacity) {

    this(initialCapacity, DEFAULT_LOAD_FACTOR);

}

 

 

使用了用户设置的初始容量和默认的负载因子。 构造方法三、

  1. public HashMap(int initialCapacity, float loadFactor) {  
  2. if (initialCapacity < 0)  
  3. throw new IllegalArgumentException("Illegal initial capacity: " +  
  4. initialCapacity);
  5. if (initialCapacity > MAXIMUM_CAPACITY)  
  6. initialCapacity = MAXIMUM_CAPACITY;
  7. if (loadFactor <= 0 || Float.isNaN(loadFactor))  
  8. throw new IllegalArgumentException("Illegal load factor: " +  
  9. loadFactor);
  10. // Find a power of 2 >= initialCapacity  
  11. int capacity = 1;  
  12. while (capacity < initialCapacity)  
  13. capacity <<=  1;  
  14. this.loadFactor = loadFactor;  
  15. threshold = ( int)(capacity * loadFactor);  
  16. table =  new Entry[capacity];  
  17. init();
  18. }

 

 

public HashMap(int initialCapacity, float loadFactor) {

    if (initialCapacity < 0)

        throw new IllegalArgumentException("Illegal initial capacity: " +

                                           initialCapacity);

    if (initialCapacity > MAXIMUM_CAPACITY)

        initialCapacity = MAXIMUM_CAPACITY;

    if (loadFactor <= 0 || Float.isNaN(loadFactor))

        throw new IllegalArgumentException("Illegal load factor: " +

                                           loadFactor);

 

    // Find a power of 2 >= initialCapacity

    int capacity = 1;

    while (capacity < initialCapacity)

        capacity <<= 1;

 

    this.loadFactor = loadFactor;

    threshold = (int)(capacity * loadFactor);

    table = new Entry[capacity];

    init();

}

 

 

用户传入了初始容量和负载因子,这两个值是 HashMap 性能优化的关键,涉及到了 HashMap 的扩容问题。 HashMap 的容量永远是 2 的倍数,如果传入的不是 2 的倍数则被调整为大于传入值的最近的 2 的倍数,例如如果传入 130 ,则 capacity 计算后是 256 。是这段代码起的作用:

 

 

  1. while (capacity < initialCapacity)  
  2. capacity <<=  1;  

while (capacity < initialCapacity)

        capacity <<= 1;

 

 

构造方法四、

  1. public HashMap(Map<? extends K, ? extends V> m) {  
  2. this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,  
  3. DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR); // 计算 Map 的大小   
  4. putAllForCreate(m); // 初始化   
  5. }
  6. private void putAllForCreate(Map<? extends K, ? extends V> m) {  
  7. for (Iterator<? extends Map.Entry<? extends K, ? extends V>> i = m.entrySet().iterator(); i.hasNext(); ) {// 通过 entryset 进行遍历   
  8. Map.Entry<?  extends K, ? extends V> e = i.next();  
  9. putForCreate(e.getKey(), e.getValue());
  10. }
  11. }

public HashMap(Map<? extends K, ? extends V> m) {

    this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,

                  DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);// 计算 Map 的大小

    putAllForCreate(m);// 初始化

}

 

private void putAllForCreate(Map<? extends K, ? extends V> m) {

    for (Iterator<? extends Map.Entry<? extends K, ? extends V>> i = m.entrySet().iterator(); i.hasNext(); ) {// 通过 entryset 进行遍历

        Map.Entry<? extends K, ? extends V> e = i.next();

        putForCreate(e.getKey(), e.getValue());

    }

}

 

 

根据传入的 map 进行初始化。  

 

三、关键方法   1 put

  1. public V put(K key, V value) {  
  2. if (key == null)  
  3. return putForNullKey(value);// 单独处理,总是放到 table[0]   
  4. int hash = hash(key.hashCode());// 计算 key hash 值,后面介绍性能的时候再说这个 hash 方法。   
  5. int i = indexFor(hash, table.length);// hash length-1 & 来得到数组的下表   
  6. for (Entry<K,V> e = table[i]; e != null; e = e.next) {  
  7. Object k;
  8. if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  
  9. V oldValue = e.value;
  10. e.value = value;
  11. e.recordAccess( this);  
  12. return oldValue;  
  13. }
  14. } // 如果这个 key 值,原来已经则替换后直接返回。   
  15. modCount++;
  16. addEntry(hash, key, value, i);
  17. return null;  
  18. }
  19. void addEntry(int hash, K key, V value, int bucketIndex) {  
  20. Entry<K,V> e = table[bucketIndex];
  21. table[bucketIndex] =  new Entry<K,V>(hash, key, value, e);// 如果 table[bucketIndex] 中已经存在 Entry 则放到头部。   
  22. if (size++ >= threshold)// 如果大于了阀值,则扩容到原来大小的 2 倍。   
  23. resize( 2 * table.length);  
  24. }
  25. void resize(int newCapacity) {  
  26. Entry[] oldTable = table;
  27. int oldCapacity = oldTable.length;  
  28. if (oldCapacity == MAXIMUM_CAPACITY) {  
  29. threshold = Integer.MAX_VALUE;
  30. return;  
  31. }
  32. Entry[] newTable =  new Entry[newCapacity];  
  33. transfer(newTable); // 赋值到新的 table 中,注意转移后会重新 hash ,所以位置可能会跟之前不同,目的是均匀分不到新的 table 中。   
  34. table = newTable;
  35. threshold = ( int)(newCapacity * loadFactor);  
  36. }

public V put(K key, V value) {

    if (key == null)

        return putForNullKey(value);// 单独处理,总是放到 table[0]

    int hash = hash(key.hashCode());// 计算 key hash 值,后面介绍性能的时候再说这个 hash 方法。

    int i = indexFor(hash, table.length);// hash length-1 & 来得到数组的下表

    for (Entry<K,V> e = table[i]; e != null; e = e.next) {

        Object k;

        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {

            V oldValue = e.value;

            e.value = value;

            e.recordAccess(this);

            return oldValue;

        }

    }// 如果这个 key 值,原来已经则替换后直接返回。

    modCount++;

    addEntry(hash, key, value, i);

    return null;

}

void addEntry(int hash, K key, V value, int bucketIndex) {

Entry<K,V> e = table[bucketIndex];

       table[bucketIndex] = new Entry<K,V>(hash, key, value, e);// 如果 table[bucketIndex] 中已经存在 Entry 则放到头部。

       if (size++ >= threshold)// 如果大于了阀值,则扩容到原来大小的 2 倍。

           resize(2 * table.length);

   }

 

void resize(int newCapacity) {

    Entry[] oldTable = table;

    int oldCapacity = oldTable.length;

    if (oldCapacity == MAXIMUM_CAPACITY) {

        threshold = Integer.MAX_VALUE;

        return;

    }

 

    Entry[] newTable = new Entry[newCapacity];

    transfer(newTable);// 赋值到新的 table 中,注意转移后会重新 hash ,所以位置可能会跟之前不同,目的是均匀分不到新的 table 中。

    table = newTable;

    threshold = (int)(newCapacity * loadFactor);

}

 

 

2 get 方法

  1. public V get(Object key) {  
  2. if (key == null)  
  3. return getForNullKey();  
  4. int hash = hash(key.hashCode());  
  5. for (Entry<K,V> e = table[indexFor(hash, table.length)];// 找到数组的下表,进行遍历   
  6. e !=  null;  
  7. e = e.next) {
  8. Object k;
  9. if (e.hash == hash && ((k = e.key) == key || key.equals(k)))  
  10. return e.value;// 找到则返回   
  11. }
  12. return null;// 否则,返回 null  
  13. }

 

 

public V get(Object key) {

    if (key == null)

        return getForNullKey();

    int hash = hash(key.hashCode());

    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.equals(k)))

            return e.value;// 找到则返回

    }

    return null;// 否则,返回 null

}

 

 

3)remove 方法

  1. public V remove(Object key) {  
  2. Entry<K,V> e = removeEntryForKey(key);
  3. return (e == null ? null : e.value);  
  4. }
  5. final Entry<K,V> removeEntryForKey(Object key) {  
  6. int hash = (key == null) ? 0 : hash(key.hashCode());  
  7. int i = indexFor(hash, table.length);  
  8. Entry<K,V> prev = table[i];
  9. Entry<K,V> e = prev;
  10. while (e != null) {//Entry 链未遍历完则一直遍历   
  11. Entry<K,V> next = e.next;
  12. Object k;
  13. if (e.hash == hash &&  
  14. ((k = e.key) == key || (key !=  null && key.equals(k)))) {  
  15. modCount++;
  16. size--;
  17. if (prev == e)// 如果是第一个,则将 table[i] 执行 e.next  
  18. table[i] = next;
  19. else // 否则将前一个的 next 指向 e.next  
  20. prev.next = next;
  21. e.recordRemoval( this);  
  22. return e;  
  23. }
  24. prev = e; // 未找到则继续往后遍历   
  25. e = next;
  26. }
  27. return e;  
  28. }

public V remove(Object key) {

    Entry<K,V> e = removeEntryForKey(key);

    return (e == null ? null : e.value);

}

 

final Entry<K,V> removeEntryForKey(Object key) {

    int hash = (key == null) ? 0 : hash(key.hashCode());

    int i = indexFor(hash, table.length);

    Entry<K,V> prev = table[i];

    Entry<K,V> e = prev;

 

    while (e != null) {//Entry 链未遍历完则一直遍历

        Entry<K,V> next = e.next;

        Object k;

        if (e.hash == hash &&

            ((k = e.key) == key || (key != null && key.equals(k)))) {

            modCount++;

            size--;

            if (prev == e)// 如果是第一个,则将 table[i] 执行 e.next

                table[i] = next;

            else // 否则将前一个的 next 指向 e.next

                prev.next = next;

            e.recordRemoval(this);

            return e;

        }

        prev = e;// 未找到则继续往后遍历

        e = next;

    }

 

    return e;

}

 

 

4)HashMap 的遍历方法

  1. Map map =  new HashMap();  
  2. map.put( "key1","value1");  
  3. map.put( "key2","value2");  
  4. map.put( "key3", "value3");  
  5. for(Iterator it = map.entrySet().iterator();it.hasNext();){  
  6. Map.Entry e = (Map.Entry)it.next();
  7. System.out.println(e.getKey() +  "=" + e.getValue());  
  8. }
  9. System.out.println( "-----------------------------------------");  
  10. for(Iterator it = map.keySet().iterator();it.hasNext();){  
  11. Object key = it.next();
  12. Object value = map.get(key);
  13. System.out.println(key+ "="+value);  
  14. }
  15. System.out.println( "-----------------------------------------");  
  16. System.out.println(map.values());
  17. 输出为:
  18. key3=value3
  19. key2=value2
  20. key1=value1
  21. -----------------------------------------
  22. key3=value3
  23. key2=value2
  24. key1=value1
  25. -----------------------------------------
  26. [value3, value2, value1]

Map map = new HashMap();

map.put("key1","value1");

map.put("key2","value2");

map.put("key3", "value3");

for(Iterator it = map.entrySet().iterator();it.hasNext();){

    Map.Entry e = (Map.Entry)it.next();

    System.out.println(e.getKey() + "=" + e.getValue());

}

System.out.println("-----------------------------------------");

for(Iterator it = map.keySet().iterator();it.hasNext();){

    Object key = it.next();

    Object value = map.get(key);

    System.out.println(key+"="+value);

}

System.out.println("-----------------------------------------");

System.out.println(map.values());

 

输出为:

key3=value3

key2=value2

key1=value1

-----------------------------------------

key3=value3

key2=value2

key1=value1

-----------------------------------------

[value3, value2, value1]

 

 

四、性能相关   1 hash 如果总计算到相同的数组下标,则得进行 Entry 的遍历来取值和存放值,必然会影响性能。 所以 HashMap 提供了 hash 方法,来解决 key hashCode 方法质量不高问题。

  1. public V put(K key, V value) {  
  2. ...
  3. int hash = hash(key.hashCode());  
  4. ...
  5. }
  6. static int hash(int h) {  
  7. // This function ensures that hashCodes that differ only by  
  8. // constant multiples at each bit position have a bounded  
  9. // number of collisions (approximately 8 at default load factor).  
  10. h ^= (h >>>  20) ^ (h >>> 12);  
  11. return h ^ (h >>> 7) ^ (h >>> 4);  
  12. }

public V put(K key, V value) {

...

int hash = hash(key.hashCode());

...

}

 

static int hash(int h) {

    // This function ensures that hashCodes that differ only by

    // constant multiples at each bit position have a bounded

    // number of collisions (approximately 8 at default load factor).

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

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

}

 

 

2 )初始容量和负载因子 因为 put 的时候可能需要做扩容,扩容会导致性能损耗,所以如果可以预知 Map 大小的话,可以设置合理的初始大小和负载因子来避免 HashMap 的频繁扩容导致的性能消耗。

 

 

  1. void addEntry(int hash, K key, V value, int bucketIndex) {  
  2. ntry<K,V> e = table[bucketIndex];
  3. table[bucketIndex] =  new Entry<K,V>(hash, key, value, e);  
  4. if (size++ >= threshold)  
  5. resize( 2 * table.length);  
  6.   }

void addEntry(int hash, K key, V value, int bucketIndex) {

Entry<K,V> e = table[bucketIndex];

       table[bucketIndex] = new Entry<K,V>(hash, key, value, e);

       if (size++ >= threshold)

           resize(2 * table.length);

   }

 

 

五、同 Hashtable 的区别   1 HashMap 允许 key value 都可以为 null,Hashtable 都不可以为空。 HashMap put 方法,代码片段:

  1. if (key == null)  
  2. return putForNullKey(value);  

if (key == null)

        return putForNullKey(value);

 

 

Hashtable put 方法,代码片段:

  1. if (value == null) {  
  2. throw new NullPointerException();  
  3. }
  4. Entry tab[] = table;
  5. int hash = key.hashCode();  

if (value == null) {

    throw new NullPointerException();

}

Entry tab[] = table;

       int hash = key.hashCode();

 

 

2 HashMap 是非线程安全的, Hashtable 是线程安全的。 Hashtable put get 方法均为 synchronized 的。  


六、 ConcurrentHashMap   ConcurrentHashMap Doug Lea 写的线程安全的 HashMap 实现,将 HashMap 默认划分为了 16 Segment ,减少了锁的争用,另外通过写时加锁读时不加锁减少了锁的持有时间,优雅的解决了高并发下锁的高竞争问题。
全部评论

相关推荐

07-14 13:47
门头沟学院 Java
Lynn012:你评估好自己的位置了吗《顶尖应届》
投递小米集团等公司7个岗位
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务