美文网首页JDK 源码解析
JDK1.8 HashMap 源码解析

JDK1.8 HashMap 源码解析

作者: 闷油瓶_简书 | 来源:发表于2017-11-02 21:00 被阅读33次

JDK源码深揪,JDK1.8版本。

摘要

HashMap 经常用到的 java 内置的数据结构,今天就深入源码瞧瞧,刨一刨。

HashMap 是对 Map 接口的一种哈希表的实现,是 key-value(键-值对),通过 key 可以常数时间内找到 value 。它是非线程安全,如果需要满足线程安全,可以用 Collections 的 synchronizedMap 方法使 HashMap 具有线程安全的能力,或者使用 ConcurrentHashMap。但是 HashMap 不是 Collection 的子集,但是 HashMap 的 key 和value 都可以单独的转换成 Collection子集,比如转换成 Set。HashMap 允许存在 null的,最多只允许一条记录的键为 null,允许多条记录的值为 null。

那我就简单的看一看 HashMap 源码,也是我第一次看整个类,以前都是需要哪个方法 就跳进去 看看,过程又惊喜又痛苦。

源码解析

HashMap继承自AbstractMap,并且实现了Map,Cloneable,Serializable接口。

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {

常量

HashMap 的初始容量为16,最大的容量为1073741824(1<<30),当构造函数中没有指定参数时,使用的默认负载因子为0.75f , 使用了基于数组哈希表的结构,数组中每一个元素都是一个链表,把数组中的每一格称为一个桶(bin或bucket)。当HashMap中节点数量超过容量和装填因子的积,会进行扩容操作,扩容后的HashMap容量是之前容量的两倍(左位移操作)。

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16  初始容量
static final int MAXIMUM_CAPACITY = 1 << 30;        // 最大的容量
static final float DEFAULT_LOAD_FACTOR = 0.75f;     // 负载因子 , 加载因子 , 装填因子 

HashMap 的扩容及树化过程中会涉及下列常量,当好多的节点(node)被映射在同一个桶(bin)中的时,如果这个桶(bin)的数量小于TREEIFY_THRESHOLD,不会转换成树性结构;如果这个桶(bin)的节点(node)数量大于TREEIFY_THRESHOLD,但是容量(总节点数)却小于MIN_TREEIFY_CAPACITY,则依然使用链表结构进行存储,此时会对HashMap进行扩容,如果容量大于MIN_TREEIFY_CAPACITY就开始进行树形化。

static final int TREEIFY_THRESHOLD = 8;      
static final int UNTREEIFY_THRESHOLD = 6;     
static final int MIN_TREEIFY_CAPACITY = 64;   

成员变量

来看看成员变量, transient 此成员变量不可序列化。 都知道 HashMap 是数组链表(+红黑树),Node<K,V>[] table 是哈希桶数组。对于红黑树抽空搞一搞。

transient Node<K,V>[] table;
transient Set<Map.Entry<K,V>> entrySet;  // 保持缓存的entrySet()
transient int size;                   // 实际HashMap拥有key-value数 
transient int modCount;    // 此字段用于使HashMap的集合视图上的迭代器失效
int threshold;        // 下一次调整大小的阈值(capacity * load factor)。
final float loadFactor;   // 负载因子

节点

HashMap 的节点 , 第一个是链表节点,第二个是红黑树节点。

//链表节点
/**
* Basic hash bin node, used for most entries.  (See below for
* TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
*/
static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;

    Node(int hash, K key, V value, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }
//详情略。。。

//红黑树节点
/**
* Entry for Tree bins. Extends LinkedHashMap.Entry (which in turn
* extends Node) so can be used as extension of either regular or
* linked node.
*/
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
    TreeNode<K,V> parent;  // red-black tree links
    TreeNode<K,V> left;
    TreeNode<K,V> right;
    TreeNode<K,V> prev;    // needed to unlink next upon deletion
    boolean red;
    TreeNode(int hash, K key, V val, Node<K,V> next) {
         super(hash, key, val, next);
    }
//详情略。。。

构造函数

HashMap 有四个构造函数,主要涉及两个成员变量loadFactor(负载因子)threshold(下一次调整大小的阈值(capacity * load factor))。构造函数并不会创建空间,第一次put操作才会创建空间,懒加载过程。

public HashMap(int initialCapacity, float loadFactor) {
       
public HashMap(int initialCapacity) {

public HashMap() {
        
public HashMap(Map<? extends K, ? extends V> m) {
    this.loadFactor = DEFAULT_LOAD_FACTOR;
    putMapEntries(m, false);

final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) { 

//保证容量是2的幂 
static final int tableSizeFor(int cap) {
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}  

Hash 算法 和 扩容过程

Hash 算法

hash算法三个步骤:

  1. 用 key 的 hashCode() 获取 hash 值。
  2. 无符号右移16位(无符号,空位补0,一共32位),然后异或(^)运算。这样可以避免只靠低位数据来计算哈希时导致的冲突,计算结果由高低位结合决定,可以避免哈希值分布不均匀。
  3. 最后用table的长度求模(h%table.length)。比如,在获取和插入方法getNode(),putVal() 都有求模运算,只不过用与(&)运算来代替模运算来提高效率。
//hash 方法
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

//插入方法 和 查询方法 内部小块代码 ,table是node数组
Node<K,V>[] tab;
if ((tab = table) == null || (n = tab.length) == 0)
    n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)

图解,图片地址http://blog.csdn.net/u011240877/article/details/53351188

hash.png
hash1.png

扩容过程

这篇文章(http://blog.csdn.net/fan2012huan/article/details/51088211)中有笔者详细实验,详细记录了扩容过程,但是我这里就刨一刨代码是怎么实现的。

直接看代码。

/**
* Initializes or doubles table size.  If null, allocates in
* accord with initial capacity target held in field threshold.
* Otherwise, because we are using power-of-two expansion, the
* elements from each bin must either stay at same index, or move
* with a power of two offset in the new table.
*
* @return the table
*/
final Node<K,V>[] resize() {
    //复制一份 数据链表数据
    Node<K,V>[] oldTab = table;
    //保存旧的数据 阈值
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr = 0;
    if (oldCap > 0) {
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        //新增容量为以前的两倍
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)
        newThr = oldThr << 1; // double threshold
    }
    //如果旧的容量为0,旧的阈值>0,说明创建了hashtable 但是没有添加数据,
    //初始化容量等于阈值
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    else {               // zero initial threshold signifies using defaults
        //都是0,还没创建hashtable,所以初始化 ,构造函数里也用了扩容
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
        //如果新的阈值 为 0 ,就得重新 计算一次
    if (newThr == 0) {
         float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                (int)ft : Integer.MAX_VALUE);
    }
    threshold = newThr;
     @SuppressWarnings({"rawtypes","unchecked"})
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    // 遍历复制
    if (oldTab != null) {
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                 oldTab[j] = null;
                if (e.next == null)
                    newTab[e.hash & (newCap - 1)] = e;
                else if (e instanceof TreeNode)
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else { // preserve order
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    do {
                        next = e.next;
                        if ((e.hash & oldCap) == 0) {
                            if (loTail == null)
                                loHead = e;
                        else
                            loTail.next = e;
                        loTail = e;
                        }
                        else {
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                     if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    if (hiTail != null) {
                         hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}

公共方法

//返回size。
public int size()

// 是否为空。
public boolean isEmpty()

// 获取value,里面调用了final getNode(int hash, Object key)方法。
public V get(Object key)

// 是否存在key,里面调用了final getNode(int hash, Object key)方法。
public boolean containsKey(Object key)

// 与key相关联的上一个value,如果不存在key,是新加key-value,则返回null。
public V put(K key, V value)

// 将指定map的所有映射复制到此map。这个跟用map构造函数一样。
public void putAll(Map<? extends K, ? extends V> m)

// 删除key对应的key-value,删除成功返回上一个value值。
public V remove(Object key)

// 删除所有节点
public void clear()

//value 是否存在
public boolean containsValue(Object value)

// 返回key的set集合
public Set<K> keySet()

// 返回value的Colection集合 , 跟AbstractMap 里变量 values 有关。
public Collection<V> values()

// 返回key-value 的MapEntry
public Set<Map.Entry<K,V>> entrySet()

jdk1.8 扩展的方法,都是重写了Map 接口

// 重写Map接口的方法,获取value ,不存在则得到默认值
public V getOrDefault(Object key, V defaultValue)

// 重写Map接口的方法,如果指定的key尚未与value相关联(或映射到{@code null}),则将其与给定value相关联并返回{@code null},否则返回当前value。
public V putIfAbsent(K key, V value)

// 删除节点 , 里面返回了节点并且判断了是否为空。
public boolean remove(Object key, Object value)

// 仅当当前映射到指定的oldValue时,才能替换成newValue。
public boolean replace(K key, V oldValue, V newValue)

// 替换key 对应得value,返回以前的value 或 null。
public V replace(K key, V value)

// 如果指定的key尚未与value相关联(或映射到null),则尝试使用给定的映射函数计算其value,并将其输入到此映射中,除非为null。 返回与指定key相关联的当前(现有或计算)value,如果计算值为空,则为null。
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)

后面是一些 三个迭代器(key,value,entry) 和 TreeNode 相关代码,不看了,目前还没兴趣往下看。

参考文献

JDK1.8 源码
https://tech.meituan.com/java-hashmap.html
http://blog.csdn.net/fan2012huan/article/details/51088211
http://blog.jrwang.me/2016/java-collections-hashmap/
http://blog.csdn.net/u011240877/article/details/53351188

完结。

相关文章

  • HashMap实现原理、源码解析(jdk1.8)

    HashMap实现原理、源码解析(jdk1.8) 下面参考博文,感谢! Java 8系列之重新认识HashMap全...

  • 深入TreeMap源码解析(JDK1.8)

    TreeMap源码解析(JDK1.8) 1. 概述 Map 接口的实现类 HashMap、LinkedHashMa...

  • HashMap的源码解析

    今天我们要解析的HashMap源码是基于jdk1.8。在正式具体的看HashMap的源码之前,我们先简单的看下,M...

  • HashMap(JDK1.8)增强扩展

    我们在这篇文章中HashMap源码解析(JDK1.8)对HashMap进行了学习,在此基础上,继续总结HashMa...

  • HashMap源码解析(JDK1.8)

    HashMap我们经常使用,那么它的数据结构和底层实现是怎么样的,我们来啃一下。 HashMap的数据结构 Has...

  • JDK1.8 HashMap 源码解析

    JDK源码深揪,JDK1.8版本。 摘要 HashMap 经常用到的 java 内置的数据结构,今天就深入源码瞧瞧...

  • HashMap 源码解析(JDK1.8)

    HashMap是由数组加链表的结合体。如下图: 图中可以看出HashMap底层就是一个数组结构,每个数组中又存储着...

  • HashMap源码解析(JDK1.8)

    HashMap的实现用的是数组+链表+红黑树的结构,也叫哈希桶,在jdk 1.8之前都是数组+链表的结构,因为在链...

  • HashMap jdk1.8源码解析

    HashMap简介 HashMap主要用于存放键值对(key-value结构),它基于hash表的Map接口实现。...

  • HashMap源码解析JDK1.8

    先看看hashMap在jdk 1.8的结构,如下图,用的是数组+链表+红黑树的结构,也叫哈希桶,在jdk 1.8之...

网友评论

    本文标题:JDK1.8 HashMap 源码解析

    本文链接:https://www.haomeiwen.com/subject/stiapxtx.html