HashMap

1. HashMap 底层数据结构

        HashMap 于 JDK1.2 中首次出现,JDK1.7 及以前底层数据结构为数组+链表「图 1」,JDK1.8 及以后更新为数组+链表+红黑树「图 2」

   图 1

图 2

1. 将元素存储在数组中

2. 随着元素的增加,不再利于增删操作,因此加入了链表来提升效率

        数组中每个元素皆关联一条链表

3. 随着元素的继续增加,不再利于查找操作,因此将链表改进为红黑树 

值得注意的是,只有在链表长度大于 8 且 数组长度大于 64 时才会将链表替换为红黑树


2. 新增操作 - 流程

1)图解【×】

图 3 

2)文字叙述 

第一步,调用 put 方法传入键值对

第二步,使用 Hash 算法计算 Hash 值

第三步,判断是否发生了哈希碰撞

第四步,若未发生,则将元素存储在数组中;反之,则判断当前底层数据结构是链表还是红黑树

第五步,若是红黑树,则将元素存储在红黑树中;反之,则将元素存储在链表中

第六步,判断链表长度是否大于 8,若是,则将链表替换为红黑树

3)代码分析

① put 方法

        调用 put 方法传入键值对

public V put(K key, V value) {
    return this.putVal(hash(key), key, value, false, true);
}

② hash 方法

        使用 Hash 算法计算 Hash 值

static final int hash(Object key) {
    int h;
    return key == null ? 0 : (h = key.hashCode()) ^ h >>> 16;
}

③ putVal 方法 

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
    HashMap.Node[] tab;
    int n;
    if ((tab = this.table) == null || (n = tab.length) == 0) {
        n = (tab = this.resize()).length;
    }
    Object p;
    int i;
    if ((p = tab[i = n - 1 & hash]) == null) {
        tab[i] = this.newNode(hash, key, value, (HashMap.Node)null);
    } else {
        Object e;
        Object k;
        if (((HashMap.Node)p).hash == hash && ((k = ((HashMap.Node)p).key) == key || key != null && key.equals(k))) {
            e = p;
        } else if (p instanceof HashMap.TreeNode) {
            e = ((HashMap.TreeNode)p).putTreeVal(this, tab, hash, key, value);
        } else {
            int binCount = 0;
            while(true) {
                if ((e = ((HashMap.Node)p).next) == null) {
                    ((HashMap.Node)p).next = this.newNode(hash, key, value, (HashMap.Node)null);
                    if (binCount >= 7) {
                        this.treeifyBin(tab, hash);
                    }
                    break;
                }
                if (((HashMap.Node)e).hash == hash && ((k = ((HashMap.Node)e).key) == key || key != null && key.equals(k))) {
                    break;
                }
                p = e;
                ++binCount;
            }
        }
        if (e != null) {
            V oldValue = ((HashMap.Node)e).value;
            if (!onlyIfAbsent || oldValue == null) {
                ((HashMap.Node)e).value = value;
            }
            this.afterNodeAccess((HashMap.Node)e);
            return oldValue;
        }
    }
    ++this.modCount;
    if (++this.size > this.threshold) {
        this.resize();
    }
    this.afterNodeInsertion(evict);
    return null;
}

A. 第 1 部分

        判断数组的地址或内容是否为空,若为空,则创建一个新的数组

        tab - 数组

        判断数组的地址是否为空:(tab = this.table) == null

        判断数组的内容是否为空: (n = tab.length) == 0

if ((tab = this.table) == null || (n = tab.length) == 0) {
    n = (tab = this.resize()).length;
}

B. 第 2 部分

        判断当前元素插入节点是否有值,若为空,则将元素存储在数组中

        p - 插入节点

        由 n - 1 & hash 计算当前元素存储在数组中的位置(i)

         判断是否发生了地址冲突:(p = tab[i]) == null

if ((p = tab[i = n - 1 & hash]) == null) {
    tab[i] = this.newNode(hash, key, value, (HashMap.Node)null);
}

C. 第 3 部分

        发生了地址冲突

else {
    Object e;
    Object k;
    if (((HashMap.Node)p).hash == hash && ((k = ((HashMap.Node)p).key) == key || key != null && key.equals(k))) {
        e = p;
    } else if (p instanceof HashMap.TreeNode) {
        e = ((HashMap.TreeNode)p).putTreeVal(this, tab, hash, key, value);
    } else {
        int binCount = 0;
        while(true) {
            if ((e = ((HashMap.Node)p).next) == null) {
                ((HashMap.Node)p).next = this.newNode(hash, key, value, (HashMap.Node)null);
                if (binCount >= 7) {
                    this.treeifyBin(tab, hash);
                }
                break;
            }
            if (((HashMap.Node)e).hash == hash && ((k = ((HashMap.Node)e).key) == key || key != null && key.equals(k))) {
                break;
            }
            p = e;
            ++binCount;
        }
    }
    if (e != null) {
        V oldValue = ((HashMap.Node)e).value;
        if (!onlyIfAbsent || oldValue == null) {
            ((HashMap.Node)e).value = value;
        }
        this.afterNodeAccess((HashMap.Node)e);
        return oldValue;
    }
}

a. 第 3 部分第 1 小节 

        判断 tab[i] 的键与当前元素的键是否相同,若相同,则将节点 p 赋值给 节点 e

        节点 p 等同于 tab[i]

if (((HashMap.Node)p).hash == hash && ((k = ((HashMap.Node)p).key) == key || key != null && key.equals(k))) {
    e = p;
}

b. 第 3 部分第 2 小节

        判断当前底层数据结构是否为红黑树,若是,则将元素存储在红黑树中

        instanceof:判断其左边对象是否为其右边类的实例,返回 boolean 类型的数据

else if (p instanceof HashMap.TreeNode) {
    e = ((HashMap.TreeNode)p).putTreeVal(this, tab, hash, key, value);
}

c. 第 3 部分第 3 小结

        从节点 p 的后继结点开始遍历链表,若遍历到链表表尾仍未发生哈希碰撞或键相同的情况,则将元素存储在链表尾结点中,而后判断链表长度是否大于 8,若大于 8,将链表替换为红黑树;若遍历链表时发生了哈希碰撞或键相同的情况,则结束循环。

        哈希碰撞:p != null 和 p.hash == hash

else {
    int binCount = 0;
    while(true) {
        if ((e = ((HashMap.Node)p).next) == null) {
            ((HashMap.Node)p).next = this.newNode(hash, key, value, (HashMap.Node)null);
            if (binCount >= 7) {
                this.treeifyBin(tab, hash);
            }
            break;
        }
        if (((HashMap.Node)e).hash == hash && ((k = ((HashMap.Node)e).key) == key || key != null && key.equals(k))) {
            break;
        }
        p = e;
        ++binCount;
    }
}

D. 第 4 部分 

        【续】第 3 部分第 1 小结或第 3 部分第 3 小结

         判断当前元素的插入节点 e 是否有值,若有值,则表明节点 e 的键与当前元素的键相同,将节点 e 的值替换为当前元素的值

if (e != null) {
    V oldValue = ((HashMap.Node)e).value;
    if (!onlyIfAbsent || oldValue == null) {
        ((HashMap.Node)e).value = value;
    }
    this.afterNodeAccess((HashMap.Node)e);
    return oldValue;
}

F. 第 5 部分 

        判断是否需要扩容

++this.modCount;
if (++this.size > this.threshold) {
    this.resize();
}

3. 扩容操作 - 流程【未掌握】

图解

图 4

4. 解决地址冲突问题 - 链地址法

        因为由 n - 1 & hash 计算当前元素存储在数组中的位置(i),所以地址冲突的前提是 hash 相同

        底层数据结构:数组 + 链表

        链地址法:取数据元素的关键字 key,计算其散列函数值 i(地址 - 存储位置)。若 tab[i] 关联的链表为空,则将该数据元素插入该链表;反之,则将该数据元素插入该链表头节点的后继结点或尾结点中


5. 非线程安全

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值