Java岗大厂面试百日冲刺 - 日积月累,每日三题【Day12】—— 集合框架2

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
img

正文

5、链表红黑树如何互相转换?阈值多少?

7、头插法改成尾插法为了解决什么问题?

而我们,当然是提前准备好如何回答好这些问题!当你的回答超过面试同学的认知范围时,主动权就到我们手里了。

在这里插入图片描述

深入追问:


追问1:如何实现HashMap的有序?

使用LinkedHashMap 或 TreeMap。

LinkedHashMap内部维护了一个单链表,有头尾节点,同时LinkedHashMap节点Entry内部除了继承HashMap的Node属性,还有before 和 after用于标识前置节点和后置节点。可以实现按插入的顺序或访问顺序排序。

/**

  • The head (eldest) of the doubly linked list.

*/

transient LinkedHashMap.Entry<K,V> head;

/**

  • The tail (youngest) of the doubly linked list.

*/

transient LinkedHashMap.Entry<K,V> tail;

//将加入的p节点添加到链表末尾

private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {

LinkedHashMap.Entry<K,V> last = tail;

tail = p;

if (last == null)

head = p;

else {

p.before = last;

last.after = p;

}

}

//LinkedHashMap的节点类

static class Entry<K,V> extends HashMap.Node<K,V> {

Entry<K,V> before, after;

Entry(int hash, K key, V value, Node<K,V> next) {

super(hash, key, value, next);

}

}

示例代码:

public static void main(String[] args) {

Map<String, String> linkedMap = new LinkedHashMap<String, String>();

linkedMap.put(“1”, “占便宜”);

linkedMap.put(“2”, “没够儿”);

linkedMap.put(“3”, “吃亏”);

linkedMap.put(“4”, “难受”);

for(linkedMap.Entry<String,String> item: linkedMap.entrySet()){

System.out.println(item.getKey() + “:” + item.getValue());

}

}

输出结果:

1:占便宜

2:没够儿

3:吃亏

4:难受

追问2:那TreeMap怎么实现有序的?

TreeMap是按照Key的自然顺序或者Comprator的顺序进行排序,内部是通过红黑树来实现。

  1. TreeMap实现了SortedMap接口,它是一个key有序的Map类。

  2. 要么key所属的类实现Comparable接口,或者自定义一个实现了Comparator接口的比较器,传给TreeMap用于key的比较。

TreeMap<String, String> map = new TreeMap<String, String>(new Comparator() {

@Override

public int compare(String o1, String o2) {

return o2.compareTo(o1);

}

});


追问3:put方法原理是怎么实现的?

该条问答摘自 安琪拉的博客(https://blog.csdn.net/zhengwangzw/article/details/104889549)

在这里插入图片描述

  1. 判断数组是否为空,为空进行初始化;

  2. 不为空,计算 k 的 hash 值,通过(n - 1) & hash计算应当存放在数组中的下标 index;

  3. 查看 table[index] 是否存在数据,没有数据就构造一个Node节点存放在 table[index] 中;

  4. 存在数据,说明发生了hash冲突(存在二个节点key的hash值一样), 继续判断key是否相等,相等,用新的value替换原数据(onlyIfAbsent为false)

  5. 如果不相等,判断当前节点类型是不是树型节点,如果是树型节点,创造树型节点插入红黑树中;(如果当前节点是树型节点证明当前已经是红黑树了)

  6. 如果不是树型节点,创建普通Node加入链表中;判断链表长度是否大于 8并且数组长度大于64, 大于的话链表转换为红黑树;

  7. 插入完成之后判断当前节点数是否大于阈值,如果大于开始扩容为原数组的二倍。


下面我们看看源码中的内容:

/**

  • 将指定参数key和指定参数value插入map中,如果key已经存在,那就替换key对应的value

  • @param key 指定key

  • @param value 指定value

  • @return 如果value被替换,则返回旧的value,否则返回null。当然,可能key对应的value就是null。

*/

public V put(K key, V value) {

//putVal方法的实现就在下面

return putVal(hash(key), key, value, false, true);

}

从源码中可以看到,put(K key, V value)可以分为三个步骤:

  1. 通过hash(Object key)方法计算key的哈希值。

  2. 通过putVal(hash(key), key, value, false, true)方法实现功能。

  3. 返回putVal方法返回的结果。

那么看看putVal方法的源码是如何实现的?

/**

  • Map.put和其他相关方法的实现需要的方法

  • @param hash 指定参数key的哈希值

  • @param key 指定参数key

  • @param value 指定参数value

  • @param onlyIfAbsent 如果为true,即使指定参数key在map中已经存在,也不会替换value

  • @param evict 如果为false,数组table在创建模式中

  • @return 如果value被替换,则返回旧的value,否则返回null。当然,可能key对应的value就是null。

*/

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {

Node<K,V>[] tab; Node<K,V> p; int n, i;

//如果哈希表为空,调用resize()创建一个哈希表,并用变量n记录哈希表长度

if ((tab = table) == null || (n = tab.length) == 0)

n = (tab = resize()).length;

//如果指定参数hash在表中没有对应的桶,即为没有碰撞

if ((p = tab[i = (n - 1) & hash]) == null)

//直接将键值对插入到map中即可

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);

//如果链的长度大于TREEIFY_THRESHOLD这个临界值,则把链变为红黑树

if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st

treeifyBin(tab, hash);

//跳出循环

break;

}

//如果找到了重复的key,判断链表中结点的key值与插入的元素的key值是否相等,如果相等,跳出循环

if (e.hash == hash &&

((k = e.key) == key || (key != null && key.equals(k))))

break;

//用于遍历桶中的链表,与前面的e = p.next组合,可以遍历链表

p = e;

}

}

//如果key映射的节点不为null

if (e != null) { // existing mapping for key

//记录节点的vlaue

V oldValue = e.value;

//如果onlyIfAbsent为false,或者oldValue为null

if (!onlyIfAbsent || oldValue == null)

//替换value

e.value = value;

//访问后回调

afterNodeAccess(e);

//返回节点的旧值

return oldValue;

}

}

//结构型修改次数+1

++modCount;

//判断是否需要扩容

if (++size > threshold)

resize();

//插入后回调

afterNodeInsertion(evict);

return null;

}

追问4:HashMap扩容机制原理

  • capacity 即容量,默认16。
  • loadFactor 加载因子,默认是0.75
  • threshold 阈值。阈值=容量*加载因子。默认12。当元素数量超过阈值时便会触发扩容。
  • 一般情况下,当元素数量超过阈值时便会触发扩容(调用resize()方法)。

  • 每次扩容的容量都是之前容量的2倍。

  • 扩展后Node对象的位置要么在原位置,要么移动到原偏移量两倍的位置。

这里我们以JDK1.8的扩容为例:

HashMap的容量变化通常存在以下几种情况:

  1. 空参数的构造函数:实例化的HashMap默认内部数组是null,即没有实例化。第一次调用put方法时,则会开始第一次初始化扩容,长度为16

  2. 有参构造函数:用于指定容量。会根据指定的正整数找到不小于指定容量的2的幂数,将这个数设置赋值给阈值(threshold)。第一次调用put方法时,会将阈值赋值给容量,然后让 阈值 = 容量 x 加载因子 。(因此并不是我们手动指定了容量就一定不会触发扩容,超过阈值后一样会扩容!!)

  3. 如果不是第一次扩容,则容量变为原来的2倍,阈值也变为原来的2倍。(容量和阈值都变为原来的2倍时,加载因子0.75不变)

此外还有几个点需要注意:

  • 首次put时,先会触发扩容(算是初始化),然后存入数据,然后判断是否需要扩容;可见首次扩容可能会调用两次resize()方法

  • 不是首次put,则不再初始化,直接存入数据,然后判断是否需要扩容;

扩容时,要扩大空间,为了使hash散列均匀分布,原有部分元素的位置会发生移位。

JDK7的元素迁移

JDK7中,HashMap的内部数据保存的都是链表。因此逻辑相对简单:在准备好新的数组后,map会遍历数组的每个“桶”,然后遍历桶中的每个Entity,重新计算其hash值(也有可能不计算),找到新数组中的对应位置,以头插法插入新的链表。

  • 这里有几个注意点:

是否要重新计算hash值的条件这里不深入讨论,读者可自行查阅源码。

因为是头插法,因此新旧链表的元素位置会发生转置现象。

元素迁移的过程中在多线程情境下有可能会触发死循环(无限进行链表反转)。

JDK1.8的元素迁移

Ending

Tip:由于文章篇幅有限制,下面还有20个关于MySQL的问题,我都复盘整理成一份pdf文档了,后面的内容我就把剩下的问题的目录展示给大家看一下

如果觉得有帮助不妨【转发+点赞+关注】支持我,后续会为大家带来更多的技术类文章以及学习类文章!(阿里对MySQL底层实现以及索引实现问的很多)

吃透后这份pdf,你同样可以跟面试官侃侃而谈MySQL。其实像阿里p7岗位的需求也没那么难(但也不简单),扎实的Java基础+无短板知识面+对某几个开源技术有深度学习+阅读过源码+算法刷题,这一套下来p7岗差不多没什么问题,还是希望大家都能拿到高薪offer吧。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
关注】支持我,后续会为大家带来更多的技术类文章以及学习类文章!**(阿里对MySQL底层实现以及索引实现问的很多)

[外链图片转存中…(img-uCOtMLni-1713255512398)]

[外链图片转存中…(img-dtmmmE1y-1713255512399)]

吃透后这份pdf,你同样可以跟面试官侃侃而谈MySQL。其实像阿里p7岗位的需求也没那么难(但也不简单),扎实的Java基础+无短板知识面+对某几个开源技术有深度学习+阅读过源码+算法刷题,这一套下来p7岗差不多没什么问题,还是希望大家都能拿到高薪offer吧。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-4sCYJlym-1713255512399)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值