Android开发

HashMap 源码解析二、put 相关函数,三面美团Android岗

本文主要是介绍HashMap 源码解析二、put 相关函数,三面美团Android岗,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

可以看出这是一个单链表结构,存放着 hash、key和value

  1. resize() 函数,作用:初始化或者扩容表为原大小的2倍。源码后面再分析。
  2. 知道以上的信息我们在看 putVal() 函数的代码,注释1
if ((tab = table) == null || (n = tab.length) == 0)//注释1
    n = (tab = resize()).length; 

我们知道DEFAULT_INITIAL_CAPACITY = 1 << 4 // aka 16 因此可以知道
tab = (Node<K,V>[])new Node[16]n = 16

  1. 接着往下看,注释2
if ((p = tab[i = (n - 1) & hash]) == null) //注释2
    tab[i] = newNode(hash, key, value, null); 

我们是第一次调用,p = tab[i = (n - 1) & hash]肯定是null ,于是我们这次就成功的把key,value 存到了tab[i] 中。

  1. 我们走进了if,else 的代码就不用看了,直接到了//注释3 的位置 ++modCount 用于记录修改的次数,接着往下看:
if (++size > threshold)
    resize(); 

threshold为扩容阈值,初始化时为 DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR //aka 16*0.75 = 12, size 为HashMap 中保存 Node的数量,当等于 扩容阈值 时就需要对 tab 进行扩容。
接着往下是 afterNodeInsertion(evict);这是一个空方法,什么都没做。

void afterNodeInsertion(boolean evict) { } 

好了,我们第一次调用就结束了。成功的把数据存储到了HashMap 中,再回头看下我们之前没有看的 else 中的情况

else 中的情况

tab[i = (n - 1) & hash]中已经有值的情况就会走到 else 中,看代码:

 static final int TREEIFY_THRESHOLD = 8;
        
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)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;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { //existing mapping for key //注释4
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        } 
  1. TreeNode 为红黑树,有兴趣的同学可以自行了解 红黑树深入剖析及Java实现

  2. 我们看接下来的判断,可以分为3中情况

    • p.key 与 参数key 相同

    直接将p 赋值给 Node e

    • p 为 TreeNode

    将需要保存的内容 添加到红黑树p 中,返回值赋给e

    • else

    遍里链表p,且结果赋值给e; 也可分为3 中情况
    1). if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) 当链表中有Node 的key 与参数key 相同时,结束遍历。
    2). if ((e = p.next) == null) 链表遍历完时
    将需要保存的内容 添加到链表末尾。如果链表长度小于8,结束遍历
    3). 链表遍历完,且添加新增内容。链表长度大于等于8 时。
    执行 treeifyBin(tab, hash) 函数,然后结束遍历。

    static final int MIN_TREEIFY_CAPACITY = 64;
    final void treeifyBin(Node<K,V>[] tab, int hash) {
        int n, index; Node<K,V> e;
        if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize();
        else if ((e = tab[index = (n - 1) & hash]) != null) {
            TreeNode<K,V> hd = null, tl = null;
            do {
                TreeNode<K,V> p = replacementTreeNode(e, null);
                if (tl == null)
                    hd = p;
                else {
                    p.prev = tl;
                    tl.next = p;
                }
                tl = p;
            } while ((e = e.next) != null);
            if ((tab[index] = hd) != null)
                hd.treeify(tab);
        }
    } 
    

    treeifyBin(tab, hash) 函数的逻辑是当tab 长度小于64 时就执行resize() 扩容,否则将链表转为红黑树

  3. 我们会过来看注释4 处代码

if (e != null) { //当参数key 在tab 中有映射时 
    V oldValue = e.value;
    if (!onlyIfAbsent || oldValue == null)
        e.value = value;
    afterNodeAccess(e);
    return oldValue;
} 

onlyIfAbsent 为true 时不修改现有值
当参数key 在tab 中有映射时,根据条件覆盖现有值,并返回旧值。

int hash(Object key) 函数

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
} 
  1. ^ 如果相对应位值相同,则结果为0,否则为1, 如果相对应位都是1,则结果为1,否则为0
a     = 1010
b     = 0011
------------
a ^ b = 1001
a & b = 0010 
  1. (h = key.hashCode()) ^ (h >>> 16) 这个方法最重要的一句代码。为什么要做 ^ (h >>> 16)这个操作呢?

是因为在putVal 函数中,是这样是使用的 tab[index = (n - 1) & hash],n 是表的长度,表的长度永远都是2的幂次方,那么n-1的高位应该全是0,做 & 操作时会导致hash 的高位无法参与运算,从而会带来哈希冲突的风险。所以在计算key的哈希值的时候,做(h = key.hashCode()) ^ (h >>> 16)操作。这也就让高位参与到tab[index = (n - 1) & hash]的计算中来了,即降低了哈希冲突的风险又不会带来太大的性能问题。

resize() 函数

 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) {//table中已经有数据
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            // newCap = oldCap * 2
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; //newThr = oldThr * 2 
        }
        else if (oldThr > 0) //初始化是设置了容量和阈值 使用非空构造函数初始化
            //回顾一下构造函数中 threshold = tableSizeFor(initialCapacity) 值为2的幂次方
            //原来这个值其实是给newCap 所以需要为2的幂次方
            // initial capacity was placed in threshold
            newCap = oldThr;
        else { //初始化是没有设置容量和阈值, 使用的是空的构造函数初始化 zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {// 上面判断进入 (oldThr > 0) 的情况,没有给newThr 赋值,
            // 所以在这里给newThr 赋值
            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) {//注释5
                                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;//注释6
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;//注释7
                        }
                    }
                }
            }
### 尾声

面试成功其实都是必然发生的事情,因为在此之前我做足了充分的准备工作,不单单是纯粹的刷题,更多的还会去刷一些Android核心架构进阶知识点,**比如:JVM、高并发、多线程、缓存、热修复设计、插件化框架解读、组件化框架设计、图片加载框架、网络、设计模式、设计思想与代码质量优化、程序性能优化、开发效率优化、设计模式、负载均衡、算法、数据结构、高级UI晋升、Framework内核解析、Android组件内核等。**
![](https://www.www.zyiz.net/i/ll/?i=img_convert/ddf0127aff0b55ddad58796d97d1f71e.png)

不仅有学习文档,视频+笔记提高学习效率,还能稳固你的知识,形成良好的系统的知识体系。这里,笔者分享一份从架构哲学的层面来剖析的视频及资料分享给大家梳理了多年的架构经验,筹备近6个月最新录制的,相信这份视频能给你带来不一样的启发、收获。

![](https://www.www.zyiz.net/i/ll/?i=img_convert/cffb132bb31a00f5e7618cb6cbd9eaff.png)

##### Android进阶学习资料库

一共十个专题,包括了Android进阶所有学习资料,Android进阶视频,Flutter,java基础,kotlin,NDK模块,计算机网络,数据结构与算法,微信小程序,面试题解析,framework源码!

![image](https://www.www.zyiz.net/i/ll/?i=img_convert/05018f9b86caf912affa35033c59c83f.png)

##### 大厂面试真题

PS:之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)

![](https://www.www.zyiz.net/i/ll/?i=img_convert/9e77312db7ee761f4c3c13b5327b540e.png)

**《2017-2021字节跳动Android面试历年真题解析》**

![](https://www.www.zyiz.net/i/ll/?i=img_convert/bc01a3914e1494d5fc7d8f58690b35f3.png)

* ##### **[CodeChina开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》](https://codechina.csdn.net/m0_60958482/android_p7)**

总、Gradle知识点、常见算法题汇总。)

[外链图片转存中...(img-KMs9NWTC-1630931014787)]

**《2017-2021字节跳动Android面试历年真题解析》**

[外链图片转存中...(img-D5ckAR2c-1630931014788)]

* ##### **[CodeChina开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》](https://codechina.csdn.net/m0_60958482/android_p7)**

这篇关于HashMap 源码解析二、put 相关函数,三面美团Android岗的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!