资讯专栏INFORMATION COLUMN

HashMap源码分析_JDK1.8版本

0xE7A38A / 306人阅读

摘要:源码分析版本声明文章均为本人技术笔记,转载请注明出处声明结构图示基本数据结构本质是一个散列表,存储元素为键值对继承,实现了接口的是线程不安全的,它的都可以为默认装填因子,如果当前键值对个数最大容量装填因子,进行操作新加,链表最大长度,当桶中

HashMap源码分析_JDK1.8版本 声明

文章均为本人技术笔记,转载请注明出处
[1] https://segmentfault.com/u/yzwall
[2] blog.csdn.net/j_dark/

HashMap声明

public class HashMap extends AbstractMap implements Map, Cloneable, Serializable

HashMap结构图示

HashMap基本数据结构

HashMap本质是一个散列表,存储元素为键值对;

HashMap继承AbstractMap,实现了Map、Cloneable、java.io.Serializable接口;

HashMap的是线程不安全的,它的key、value都可以为null

final int loadFacotr

static final float DEFAULT_LOAD_FACTOR: 默认装填因子0.75,如果当前键值对个数 >= HashMap最大容量*装填因子,进行rehash操作;

int threshold

static final int TREEIFY_THRESHOLDJDK1.8 新加,Entry链表最大长度,当桶中节点数目大于该长度时,将链表转成红黑树存储;

static final int UNTREEIFY_THRESHOLDJDK1.8 新加,当桶中节点数小于该长度,将红黑树转为链表存储;

static final int DEFAULT_INITIAL_CAPACITY: 默认键值队个数为16;

transient Node[] table:键值对数组,长度动态增加,但是总为2的幂;用transient修饰表示对象序列化时该字段不可被序列化;

HashMap键值对描述:Node

JDK1.6用Entry描述键值对,JDK1.8中用Node代替Entry;

static class Node implements Map.Entry {
    // hash存储key的hashCode
    final int hash;
    // final:一个键值对的key不可改变
    final K key;
    V value;
    // 一个桶中的键值对用链表组织
    Node next;
    ...
}

HashMap中键值对的存储形式为链表节点,hashCode相同的节点(位于同一个桶)用链表组织;

public final int hashCode() {
    return Objects.hashCode(key) ^ Objects.hashCode(value);
}

键值对元素hashCode = key的hashCode与value的hashCode,高16位与低16位按位异或运算;

红黑树:TreeNode

static final class TreeNode extends LinkedHashMap.Entry

JDK1.8新增,用来支持桶的红黑树结构实现

HashMap重要方法分析 HashMap添加/更新键值对:put/putVal方法

public V put(K key, V value)内部调用putVal方法实现;

public V put(K key, V value) {
    // 倒数第二个参数false:表示允许旧值替换
    // 最后一个参数true:表示HashMap不处于创建模式
    return putVal(hash(key), key, value, false, true);
}
putVal方法分析:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
    Node[] tab; Node p; int n, i;
    // 槽数组未初始化或者未扩容,先调用resize()扩容
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    /**
     * Hash函数,(n - 1) & hash 计算key将被放置的槽位;
     * (n - 1) & hash 本质上是hash % n,位运算更快
     */
    if ((p = tab[i = (n - 1) & hash]) == null)
        // 空桶,创建新的键值对节点,放入槽数组中;
        tab[i] = newNode(hash, key, value, null);
    // 键值对已在对应桶中
    else {
        Node e; K k;
        // 与桶中首元素比较,如果key不同发生Hash冲突,在桶中添加新元素
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        // 当前桶中无该键值对,且桶是红黑树结构,按照红黑树结构插入
        else if (p instanceof TreeNode)
            e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
        // 当前桶中无该键值对,且桶是链表结构,按照链表结构插入到尾部
        else {
            for (int binCount = 0; ; ++binCount) {
                // 遍历到链表尾部
                if ((e = p.next) == null) {
                    // 创建链表节点并插入尾部
                    p.next = newNode(hash, key, value, null);
                    // 检查链表长度是否达到阈值,达到将该槽位节点组织形式转为红黑树
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                // 链表节点的与put操作相同时,不做重复操作,跳出循环
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        // 找到 or 新建一个key和hashCode与插入元素相等的键值对,进行put操作
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            /**
             * onlyIfAbsent为false或旧值为null时,允许替换旧值
             * 否则无需替换
             */
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    // 更新结构化修改信息
    ++modCount;
    // 键值对数目超过阈值时,进行rehash
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}
散列分布策略

键值对的槽位 = (容量 - 1) & hash(key)

键值对槽位是键值对在tab数组的索引,本质上 = hash(key) % 容量,位运算速度更快

本质上是除数取余法,尽可能的散列均匀;

Hash函数
// in HashMap
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

计算key的hashCode, h = Objects.hashCode(key);

h >>> 16表示对h无符号右移16位,高位补0;然后h与h >>> 16按位异或;

HashMap更新旧键值对 or 添加新键值对的核心思想

根据键值对key的hashCode计算键值对的在HashMap中槽位,

判断是否空桶 Or 是否发生Hash冲突(与桶中首元素不同)

解决Hash冲突:根据桶组织形式是红黑树 Or 链表进行对应插入操作;

链表形式完成插入后,检查是否超过链表阈值,超过将链表->红黑树;

最后检查键值对总数是否超过阈值,超过调用resize()进行rehash操作;

HashMap删除键值对:remove/removeNode方法 remove方法分析
public V remove(Object key) {
    Node e;
    return (e = removeNode(hash(key), key, null, false, true)) == null ?
        null : e.value;
}

remove()方法内部调用removeNode()方法实现

removeNode方法分析:
final Node removeNode(int hash, Object key, Object value,
                           boolean matchValue, boolean movable) {
    Node[] tab; Node p; int n, index;
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (p = tab[index = (n - 1) & hash]) != null) {
        Node node = null, e; K k; V v;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            node = p;
        // 待删除元素在桶中,但不是桶中首元素
        else if ((e = p.next) != null) {
            // 待删除元素在红黑树结构的桶中
            if (p instanceof TreeNode)
                // 查找红黑树
                node = ((TreeNode)p).getTreeNode(hash, key);
            else {
                // 遍历链表,查找待删除元素
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key ||
                         (key != null && key.equals(k)))) {
                        node = e;
                        break;
                    }
                    // p保存待删除节点的前一个节点,用于链表删除操作
                    p = e;
                } while ((e = e.next) != null);
            }
        }
        /**
         * matchValue为true:表示必须value相等才进行删除操作
         * matchValue为false:表示无须判断value,直接根据key进行删除操作
         */
        if (node != null && (!matchValue || (v = node.value) == value ||
                             (value != null && value.equals(v)))) {
            // 桶为红黑数结构,删除节点
            if (node instanceof TreeNode)
                // movable参数用于红黑树操作
                ((TreeNode)node).removeTreeNode(this, tab, movable);
            // 待删除节点是桶链表表头,将子节点放进桶位
            else if (node == p)
                tab[index] = node.next;
            // 待删除节点在桶链表中间
            else
                p.next = node.next;
            ++modCount;
            --size;
            afterNodeRemoval(node);
            return node;
        }
    }
    // 待删除元素不存在,返回null
    return null;
}
HashMap访问键值对:get/getNode方法 get方法
public V get(Object key) {
    Node e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

get方法通过指定key获得对应键值对,内部调用getNode方法实现;

getNode方法
final Node getNode(int hash, Object key) {
    Node[] tab; Node first, e; int n; K k;
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        if ((e = first.next) != null) {
            // 红黑树查找
            if (first instanceof TreeNode)
                return ((TreeNode)first).getTreeNode(hash, key);
            // 链表查找
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}

getNode方法查找过程和putVal一样,先查找对应桶的首元素,然后根据红黑树结构 Or 链表结构对应查找;

HashMap重散列操作:resize方法
final Node[] resize() {
    // 保存旧table,容量,阈值
    Node[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr = 0;
    if (oldCap > 0) {
        // 旧table容量已超过最大容量,更新阈值为Integer.MAX_VALUE
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        // 调整新容量为旧容量2倍,左移一位实现
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
    }
    // oldCap == 0 && oldThr > 0
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    // oldCap == 0 && oldThr == 0
    else {               // zero initial threshold signifies using defaults
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    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[] newTab = (Node[])new Node[newCap];
    table = newTab;
    if (oldTab != null) {
        for (int j = 0; j < oldCap; ++j) {
            Node e;
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                if (e.next == null)
                    newTab[e.hash & (newCap - 1)] = e;
                // 红黑树桶进行rehash操作
                else if (e instanceof TreeNode)
                    ((TreeNode)e).split(this, newTab, j, oldCap);
                
                // 链表桶进行rehash操作
                // 根据e.hash & oldCap)是否为0把链表分成两个链表,进行rehash
                else { // preserve order
                    Node loHead = null, loTail = null;
                    Node hiHead = null, hiTail = null;
                    Node 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;
}

当键值对总数超过阈值threshold, HashMap通过resize方法实现重散列/rehash

HashMap调整容量:tableSizeFor()

static final int tableSizeFor(int cap):得到>=cap的2的最小幂值;
由指定容量参数的构造器调用,计算rehash阈值threshold;

参考

[1] http://www.cnblogs.com/leesf456/p/5242233.html
[2] http://www.cnblogs.com/ToBeAProgrammer

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/66967.html

相关文章

  • 集合框架源码学习之HashMap(JDK1.8)

    摘要:所谓拉链法就是将链表和数组相结合。若遇到哈希冲突,则将冲突的值加到链表中即可。在编写程序中,要尽量避免。 目录: 0-1. 简介 0-2. 内部结构分析   0-2-1. JDK18之前   0-2-2. JDK18之后 0-3. LinkedList源码分析   0-3-1. 构造方法   0-3-2. put方法   0-3-3. get方法   0-3-4. resize方法 ...

    yangrd 评论0 收藏0
  • HashMap 源码详细分析(JDK1.8)

    摘要:则使用了拉链式的散列算法,并在中引入了红黑树优化过长的链表。如果大家对红黑树感兴趣,可以阅读我的另一篇文章红黑树详细分析。构造方法构造方法分析的构造方法不多,只有四个。 1.概述 本篇文章我们来聊聊大家日常开发中常用的一个集合类 - HashMap。HashMap 最早出现在 JDK 1.2中,底层基于散列算法实现。HashMap 允许 null 键和 null 值,在计算哈键的哈希值...

    monw3c 评论0 收藏0
  • 前百度面试官整理的——Java后端面试题(一)

    摘要:发生了线程不安全情况。本来在中,发生哈希冲突是可以用链表法或者红黑树来解决的,但是在多线程中,可能就直接给覆盖了。中,当同一个值上元素的链表节点数不小于时,将不再以单链表的形式存储了,会被调整成一颗红黑树。 showImg(https://segmentfault.com/img/bVbsVLk?w=288&h=226); List 和 Set 的区别 List , Set 都是继承自...

    JessYanCoding 评论0 收藏0
  • HashMap源码分析(一):JDK源码分析系列

    摘要:当一个值中要存储到的时候会根据的值来计算出他的,通过哈希来确认到数组的位置,如果发生哈希碰撞就以链表的形式存储在源码分析中解释过,但是这样如果链表过长来的话,会把这个链表转换成红黑树来存储。 正文开始 注:JDK版本为1.8 HashMap1.8和1.8之前的源码差别很大 目录 简介 数据结构 类结构 属性 构造方法 增加 删除 修改 总结 1.HashMap简介 H...

    wdzgege 评论0 收藏0
  • ConcurrentHashMap源码分析_JDK1.8版本

    ConcurrentHashMap源码分析_JDK1.8版本 声明 文章均为本人技术笔记,转载请注明出处[1] https://segmentfault.com/u/yzwall[2] blog.csdn.net/j_dark/ JDK1.6版本 ConcurrentHashMap结构 showImg(https://segmentfault.com/img/remote/146000000900...

    animabear 评论0 收藏0

发表评论

0条评论

0xE7A38A

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<