• 深入理解-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

  • 相关阅读:
    This counter can increment, decrement or skip ahead by an arbitrary amount
    LUT4/MUXF5/MUXF6 logic : Multiplexer 8:1
    synthesisable VHDL for a fixed ratio frequency divider
    Bucket Brigade FIFO SRL16E ( VHDL )
    srl16e fifo verilog
    DualPort Block RAM with Two Write Ports and Bytewide Write Enable in ReadFirst Mode
    Parametrilayze based on SRL16 shift register FIFO
    stm32 spi sdcard fatfs
    SPI bus master for System09 (2)
    SQLSERVER中的自旋锁
  • 原文地址:https://www.cnblogs.com/plxx/p/4642627.html
Copyright © 2020-2023  润新知