zoukankan      html  css  js  c++  java
  • JAVA数据结构——Map之HashMap

    JAVA数据结构——Map之HashMap

    一、原型及简介

      原型:public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable

      简介:HashMap基于散列表实现的一个key-value数据结构,能够实现通过key值快速查找。HashMap继承自AbstractMap抽闲类,实现了Map接口。

     二、数据结构原理介绍

      如下图所示,HashMap是利用数组与链表结合的形式构建的。竖列为数组结构,默认初始数量为16(1<<4)个,横列为链表结构用于解决散列冲突的问题。当数组中有值得元素超过了装载因子的比例(默认为0.75)时,会引发扩容的操作。此操作是为了避免元素过满时引起的链表长度过长,从而影响查找性能。

      这里写图片描述

    上图为jdk1.7之前的实现,jdk1.8实现方法是当某一个桶中的元素个数超过了8时,将此桶中的链表构建成红黑树。

    三、常用源码解析

      1、常量说明

     1     /**
     2      * 默认初始容量
     3      */
     4     static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
     5 
     6     /**
     7      * 最大元素数量
     8      */
     9     static final int MAXIMUM_CAPACITY = 1 << 30;
    10 
    11     /**
    12      * 默认装载因子
    13      */
    14     static final float DEFAULT_LOAD_FACTOR = 0.75f;
    15 
    16     /**
    17      * 当一个桶中的元素的数量大于8时,该链表结构可能被转化成一棵红黑树,优化查找
    18      */
    19     static final int TREEIFY_THRESHOLD = 8;
    20 
    21     /**
    22      * 当一个桶中的元素的数量小于6时,该树结构被转化成链表。
    23      */
    24     static final int UNTREEIFY_THRESHOLD = 6;
    25 
    26     /**
    27      * 桶被树化的另一个条件是,当hashmap中元素个数大于4 * MIN_TREEIFY_CAPACITY 。避免调整大小和treei阈值之间的冲突。
    28      */
    29     static final int MIN_TREEIFY_CAPACITY = 64;
    常量值说明

      2、变量说明

     1     /**
     2      * The table, initialized on first use, and resized as
     3      * necessary. When allocated, length is always a power of two.
     4      * (We also tolerate length zero in some operations to allow
     5      * bootstrapping mechanics that are currently not needed.)
     6      */
     7     transient Node<K,V>[] table;
     8 
     9     /**
    10      * Holds cached entrySet(). Note that AbstractMap fields are used
    11      * for keySet() and values().
    12      */
    13     transient Set<Map.Entry<K,V>> entrySet;
    14 
    15     /**
    16      * HashMap中当前元素个数
    17      */
    18     transient int size;
    19 
    20     /**
    21      * HashMap对象被修改次数,防止出现多个线程修改出现的线程不一致性,每次修改HashMap的值时,都会自增。当使用Iterator操作HashMap时,会用此值与Iterator内部的值做一次比较,从而判断HashMap有没有被其他线程修改。故建议每次遍历HashMap时都使用Iterator。
    22      */
    23     transient int modCount;
    24 
    25     /**
    26      * 装载因子
    27      */
    28     final float loadFactor;
    变量值说明

      漏了一个变量:threshold,代表着扩容的阈值,其值为  当前容量*装载因子

      3、节点数据结构

     1     static class Node<K,V> implements Map.Entry<K,V> {
     2         final int hash; //散列码
     3         final K key;  //key值
     4         V value;  //value值
     5         Node<K,V> next;  //链表结构指针,指向下一节点
     6 
     7         Node(int hash, K key, V value, Node<K,V> next) {
     8             this.hash = hash;
     9             this.key = key;
    10             this.value = value;
    11             this.next = next;
    12         }
    13 
    14         public final K getKey()        { return key; }
    15         public final V getValue()      { return value; }
    16         public final String toString() { return key + "=" + value; }
    17 
    18         // 返回该节点的散列码
    19         public final int hashCode() {
    20             // key值的散列码 幂运算 value值得散列码
    21             // 散列函数为空值返回0,非空值则返回该对象的32位JVM地址
    22             return Objects.hashCode(key) ^ Objects.hashCode(value);
    23         }
    24 
    25         public final V setValue(V newValue) {
    26             V oldValue = value;
    27             value = newValue;
    28             return oldValue;
    29         }
    30 
    31         public final boolean equals(Object o) {
    32             if (o == this)
    33                 return true;
    34             if (o instanceof Map.Entry) {
    35                 Map.Entry<?,?> e = (Map.Entry<?,?>)o;
    36                 if (Objects.equals(key, e.getKey()) &&
    37                     Objects.equals(value, e.getValue()))
    38                     return true;
    39             }
    40             return false;
    41         }
    42     }
    节点数据结构

      4、常用方法

     1     /**
     2      * 指定初始大小以及装载因子
     3      */
     4     public HashMap(int initialCapacity, float loadFactor) {
     5         if (initialCapacity < 0)
     6             throw new IllegalArgumentException("Illegal initial capacity: " +
     7                                                initialCapacity);
     8         if (initialCapacity > MAXIMUM_CAPACITY)
     9             initialCapacity = MAXIMUM_CAPACITY;
    10         if (loadFactor <= 0 || Float.isNaN(loadFactor))
    11             throw new IllegalArgumentException("Illegal load factor: " +
    12                                                loadFactor);
    13         this.loadFactor = loadFactor;
    14         this.threshold = tableSizeFor(initialCapacity);
    15     }
    16 
    17     /**
    18      * 返回一个比cap大的最小的2的幂次方整数
    19      */
    20     static final int tableSizeFor(int cap) {
    21         int n = cap - 1;
    22         n |= n >>> 1;
    23         n |= n >>> 2;
    24         n |= n >>> 4;
    25         n |= n >>> 8;
    26         n |= n >>> 16;
    27         return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    28     }
    构造方法

    因为HashMap的容量必须是2的幂次方,所以构造方法中有关于tableSizeFor方法,获得比给定容量大的最小的2的幂次方整数,很霸气的算法,其具体的说明可参考链接:

    【转载】http://blog.csdn.net/fan2012huan/article/details/51097331(写的很详细,很好)。

      1     /**
      2      * 将key-value键值对放入HashMap中
      3      */
      4     public V put(K key, V value) {
      5         return putVal(hash(key), key, value, false, true);
      6     }
      7 
      8     /**
      9      * 实际put的方法
     10      */
     11     final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
     12                    boolean evict) {
     13         Node<K,V>[] tab; //暂存HashMap节点数组
     14         Node<K,V> p; //暂存本次要插入的节点元素数据
     15         int n, i;
     16 
     17         //如果当前HashMap为空,则计算新分配空间
     18         if ((tab = table) == null || (n = tab.length) == 0)
     19             n = (tab = resize()).length;
     20         // 如果计算出的新节点位置(hash & (n - 1) 等价于 hash % n)是空,则将元素直接放入
     21         if ((p = tab[i = (n - 1) & hash]) == null)
     22             tab[i] = newNode(hash, key, value, null);
     23         //插入新的节点,并重新组织HashMap(确定位置并决定是否树化)
     24         else {
     25             Node<K,V> e; K k;
     26             // 插入了重复的key值(hash码一致且key值一致)
     27             if (p.hash == hash &&
     28                 ((k = p.key) == key || (key != null && key.equals(k))))
     29                 e = p;
     30             // 如果p是红黑树,则执行红黑树的插入操作
     31             else if (p instanceof TreeNode)
     32                 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
     33             // 此分支代表了链表的插入操作
     34             else {
     35                 for (int binCount = 0; ; ++binCount) {
     36                     // 到达链表尾端,则执行插入
     37                     if ((e = p.next) == null) {
     38                         //插入
     39                         p.next = newNode(hash, key, value, null);
     40                         如果节点数量超过阈值,则执行树化操作
     41                         if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
     42                             treeifyBin(tab, hash);
     43                         break;
     44                     }
     45                     // 插入了重复值
     46                     if (e.hash == hash &&
     47                         ((k = e.key) == key || (key != null && key.equals(k))))
     48                         break;
     49                     p = e;
     50                 }
     51             }
     52             if (e != null) { // existing mapping for key
     53                 V oldValue = e.value;
     54                 if (!onlyIfAbsent || oldValue == null)
     55                     e.value = value;
     56                 afterNodeAccess(e);
     57                 return oldValue;
     58             }
     59         }
     60         ++modCount;
     61         //如果元素个数超过阈值,则重新分配空间,并组织数据结构
     62         if (++size > threshold)
     63             resize();
     64         afterNodeInsertion(evict);
     65         return null;
     66     }
     67 
     68     /**
     69      * 针对每个桶重新分配空间
     70      */
     71     final Node<K,V>[] resize() {
     72         Node<K,V>[] oldTab = table; //暂存当前table结构
     73         int oldCap = (oldTab == null) ? 0 : oldTab.length; //暂存当前桶的数量
     74         int oldThr = threshold; //暂存扩容的阈值
     75         int newCap, newThr = 0; //定义新的容量和阈值
     76         // 如果原有HashMap不为空
     77         if (oldCap > 0) {
     78             //如果容量已经达到了上限,则不扩容,返回原oldTab
     79             if (oldCap >= MAXIMUM_CAPACITY) {
     80                 threshold = Integer.MAX_VALUE;
     81                 return oldTab;
     82             }
     83             //如果容量没有达到上限,则将容量及扩容阈值均翻倍
     84             else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
     85                      oldCap >= DEFAULT_INITIAL_CAPACITY)
     86                 newThr = oldThr << 1; // double threshold
     87         }
     88         // 容量为0但老的阈值大于0,则阈值保持不变
     89         else if (oldThr > 0) // initial capacity was placed in threshold
     90             newCap = oldThr;
     91         // 如果容量与阈值均为0,则执行初始化
     92         else {               // zero initial threshold signifies using defaults
     93             newCap = DEFAULT_INITIAL_CAPACITY;//容量为默认容量
     94             newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//阈值为默认容量*默认阀值(16*0.75)
     95         }
     96         // 如果现有容量翻倍后大于最大容量或现有容量小于系统默认值(16),才会出现新阈值=0的情况,
     97         if (newThr == 0) {
     98             float ft = (float)newCap * loadFactor;
     99             newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
    100                       (int)ft : Integer.MAX_VALUE);
    101         }
    102 
    103         // 设置最新的扩容阈值
    104         threshold = newThr;
    105 
    106         // 创建扩容后的桶数组
    107         @SuppressWarnings({"rawtypes","unchecked"})
    108             Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    109         table = newTab;
    110 
    111         //重新组织每个桶内的链表或树状结构
    112         if (oldTab != null) {
    113             //遍历每个桶,分别处理每个桶中的数据
    114             for (int j = 0; j < oldCap; ++j) {
    115                 Node<K,V> e;
    116 
    117                 //当前桶不为空,则需将oldTab中的内容组织到newTab中
    118                 if ((e = oldTab[j]) != null) {
    119                     oldTab[j] = null;
    120                     if (e.next == null) //e没有子节点,则根据e的hash值直接将此节点放到扩容后的桶数组中合适位置
    121                         // 此处e.hash & (newCap - 1)等价于e.hash % newCap
    122                         newTab[e.hash & (newCap - 1)] = e;
    123                     else if (e instanceof TreeNode) //如果e是个树型节点,则遍历红黑树,将树中的每个节点放到新的桶数组中合适的位置,并根据新的结构决定是否需要将每个桶做树化
    124                         ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
    125                     else { // e是个链式节点
    126                         Node<K,V> loHead = null, loTail = null;
    127                         Node<K,V> hiHead = null, hiTail = null;
    128                         Node<K,V> next;
    129                         do {
    130                             next = e.next;
    131                             // 因为容量扩大了二倍,则元素要么保持不变,要么放到index + oldCap位置
    132                             if ((e.hash & oldCap) == 0) {// 元素位置保持不变,先将元素放到lo链表中
    133                                 if (loTail == null)
    134                                     loHead = e;
    135                                 else
    136                                     loTail.next = e;
    137                                 loTail = e;
    138                             }
    139                             else {// 元素位置需要移动,先将元素放到hi链表中
    140                                 if (hiTail == null)
    141                                     hiHead = e;
    142                                 else
    143                                     hiTail.next = e;
    144                                 hiTail = e;
    145                             }
    146                         } while ((e = next) != null);
    147                         if (loTail != null) {//将lo链表放到newTab中原来(j)的位置
    148                             loTail.next = null;
    149                             newTab[j] = loHead;
    150                         }
    151                         if (hiTail != null) {//将hi链表放到newTab中扩容(j+oldCap)的位置
    152                             hiTail.next = null;
    153                             newTab[j + oldCap] = hiHead;
    154                         }
    155                     }
    156                 }
    157             }
    158         }
    159     
    160         //返回最新的结构
    161         return newTab;
    162     }
    public V put(K key, V value)

    put方法包含了HashMap的实际初始化及构建的过程,仔细研究put方法,可以更好的了解HashMap这种数据结构

     1     /**
     2      * 根据key值获取value值
     3      */
     4     public V get(Object key) {
     5         Node<K,V> e;
     6         return (e = getNode(hash(key), key)) == null ? null : e.value;
     7     }
     8 
     9     /**
    10      * 根据哈希码及key值获取value值
    11      */
    12     final Node<K,V> getNode(int hash, Object key) {
    13         Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    14 
    15         //如果表不为空且表的长度不为空且根据hash码定位到桶不为空
    16         if ((tab = table) != null && (n = tab.length) > 0 &&
    17             (first = tab[(n - 1) & hash]) != null) {
    18 
    19             //如果该桶的第一个元素hash码与传参相同且key值也相同,则返回该元素节点
    20             if (first.hash == hash && // always check first node
    21                 ((k = first.key) == key || (key != null && key.equals(k))))
    22                 return first;
    23 
    24             //如果该节点的下一个下一个节点不为空
    25             if ((e = first.next) != null) {
    26                 //如果该节点是树形节点,则遍历红黑树查找匹配节点
    27                 if (first instanceof TreeNode)
    28                     return ((TreeNode<K,V>)first).getTreeNode(hash, key);
    29 
    30                 //如果是链式节点,则遍历该链表查找匹配节点
    31                 do {
    32                     if (e.hash == hash &&
    33                         ((k = e.key) == key || (key != null && key.equals(k))))
    34                         return e;
    35                 } while ((e = e.next) != null);
    36             }
    37         }
    38         return null;
    39     }
    public V get(Object key)
  • 相关阅读:
    Again Prime? No Time.(uva10870+数论)
    Heaps(Contest2080
    Terminator快捷键
    Chrome快捷键
    STL函数static void (* set_malloc_handler(void (*f)()))()与函数指针解析
    C++中的new,operator new与placement new
    Qt5 中对 C++11 一些新特性的封装
    BFS提高效率的一点建议
    《C++ Primer》读书笔记 第三章
    《C++ Primer》读书笔记 第二章
  • 原文地址:https://www.cnblogs.com/qq455988971/p/8023502.html
Copyright © 2011-2022 走看看