zoukankan      html  css  js  c++  java
  • 深入理解-HashMap

    一、HashMap概述

      HashMap 在家族中位置:实现了Map接口,继承AbstractMap类。HashMap 允许key/value 都为null.

    二、HashMap存储结构

      

      HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。在其内部维护一个Entry类型数组,初始大小为16。

     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 }

    三、HashMap 的存取遍历

      1、存储 

     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 元素的下一个元素。
    11     for (Entry<K,V> e = table[i]; e != null; e = e.next) {
    12         Object k;
    13         if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
    14             V oldValue = e.value;
    15             e.value = value;
    16             e.recordAccess(this);
    17             return oldValue;
    18         }
    19     }
    20     // 如果i索引处的Entry为null,表明此处还没有Entry。
    21     modCount++;
    22     // 将key、value添加到i索引处。
    23     addEntry(hash, key, value, i);
    24     return null;
    25 }

    当我们往HashMap中put元素的时候,先根据key的hashCode重新计算hash值,根据hash值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。

     1     final int hash(Object k) {
     2         int h = hashSeed;
     3         if (0 != h && k instanceof String) {
     4             return sun.misc.Hashing.stringHash32((String) k);
     5         }
     6 
     7         h ^= k.hashCode();
     8 
     9         // This function ensures that hashCodes that differ only by
    10         // constant multiples at each bit position have a bounded
    11         // number of collisions (approximately 8 at default load factor).
    12         h ^= (h >>> 20) ^ (h >>> 12);
    13         return h ^ (h >>> 7) ^ (h >>> 4);
    14     }
    1     static int indexFor(int h, int length) {
    2         // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
    3         return h & (length-1);
    4     }

      获取的hash码之后对数组大小取模。

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

    1     void addEntry(int hash, K key, V value, int bucketIndex) {
    2         if ((size >= threshold) && (null != table[bucketIndex])) {
    3             resize(2 * table.length);
    4             hash = (null != key) ? hash(key) : 0;
    5             bucketIndex = indexFor(hash, table.length);
    6         }
    7 
    8         createEntry(hash, key, value, bucketIndex);
    9     }

    如果经过hash之后,数组的下标不在数组大小范围之内而且当前数组大小超过指定阈值,那么将会把hashmap 2倍扩容。

    2、读取数据  

    1     public V get(Object key) {
    2         if (key == null)
    3             return getForNullKey();
    4         Entry<K,V> entry = getEntry(key);
    5 
    6         return null == entry ? null : entry.getValue();
    7     }
     1     private V getForNullKey() {
     2         if (size == 0) {
     3             return null;
     4         }
     5         for (Entry<K,V> e = table[0]; e != null; e = e.next) {
     6             if (e.key == null)
     7                 return e.value;
     8         }
     9         return null;
    10     }
     1 final Entry<K,V> getEntry(Object key) {
     2         if (size == 0) {
     3             return null;
     4         }
     5 
     6         int hash = (key == null) ? 0 : hash(key);
     7         for (Entry<K,V> e = table[indexFor(hash, table.length)];
     8              e != null;
     9              e = e.next) {
    10             Object k;
    11             if (e.hash == hash &&
    12                 ((k = e.key) == key || (key != null && key.equals(k))))
    13                 return e;
    14         }
    15         return null;
    16     }

      首先,如果要获取的key==null,那么直接调用getForNullKey()从table[0]中,获取相应的value;如果key != null,那么将会获得该key的hashcode,之后获取table的下标,从中遍历出来相应的value。

    3、遍历  

    1         Set<String> keys = map.keySet();
    2         Iterator<String> iterator = keys.iterator();
    3         while (iterator.hasNext()) {
    4             String key = iterator.next();
    5             System.out.println("[" + key + "," + map.get(key) + "]");
    6         }

    4、resize() 再哈希

      当哈希表的容量超过默认容量时(在hashmap中,如果第一次hash之后,找到的数组下标不在合理的范围之内而且当前的大小超过规定阈值),必须调整table的大小。当容量已经达到最大可能值时,那么该方法就将容量调整到Integer.MAX_VALUE返回,这时,需要创建一张新表,将原表的映射到新表中。

     1     void resize(int newCapacity) {
     2         Entry[] oldTable = table;
     3         int oldCapacity = oldTable.length;
     4         if (oldCapacity == MAXIMUM_CAPACITY) {
     5             threshold = Integer.MAX_VALUE;
     6             return;
     7         }
     8 
     9         Entry[] newTable = new Entry[newCapacity];
    10         transfer(newTable, initHashSeedAsNeeded(newCapacity));
    11         table = newTable;
    12         threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    13     }

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

    四、性能

      Fail-Fast机制

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

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

    感谢:http://zhangshixi.iteye.com/blog/672697

  • 相关阅读:
    8.7题解
    2019.9.16 csp-s模拟测试44 反思总结
    洛谷P3168 [CQOI2015]任务查询系统
    洛谷P2468 [SDOI2010]粟粟的书架
    2019.8.14 NOIP模拟测试21 反思总结
    2019.8.13 NOIP模拟测试19 反思总结
    2019.8.12 NOIP模拟测试18 反思总结
    大约是个告别【草率极了】
    2019.8.10 NOIP模拟测试16 反思总结【基本更新完毕忽视咕咕咕】
    2019.8.9 NOIP模拟测试15 反思总结
  • 原文地址:https://www.cnblogs.com/plxx/p/4642627.html
Copyright © 2011-2022 走看看