Java 面试知识点【背诵版 240题 约7w字】 (6)

HashSet 判断元素是否相同时,对于包装类型直接按值比较。对于引用类型先比较 hashCode 是否相同,不同则代表不是同一个对象,相同则继续比较 equals,都相同才是同一个对象。

LinkedHashSet 继承自 HashSet,通过 LinkedHashMap 实现,使用双向链表维护元素插入顺序。

TreeSet 通过 TreeMap 实现的,添加元素到集合时按照比较规则将其插入合适的位置,保证插入后的集合仍然有序。

Q4:TreeMap 有什么特点?

TreeMap 基于红黑树实现,增删改查的平均和最差时间复杂度均为 O(logn) ,最大特点是 Key 有序。Key 必须实现 Comparable 接口或提供的 Comparator 比较器,所以 Key 不允许为 null。

HashMap 依靠 hashCode 和 equals 去重,而 TreeMap 依靠 Comparable 或 Comparator。 TreeMap 排序时,如果比较器不为空就会优先使用比较器的 compare 方法,否则使用 Key 实现的 Comparable 的 compareTo 方法,两者都不满足会抛出异常。

TreeMap 通过 put 和 deleteEntry 实现增加和删除树节点。插入新节点的规则有三个:① 需要调整的新节点总是红色的。② 如果插入新节点的父节点是黑色的,不需要调整。③ 如果插入新节点的父节点是红色的,由于红黑树不能出现相邻红色,进入循环判断,通过重新着色或左右旋转来调整。TreeMap 的插入操作就是按照 Key 的对比往下遍历,大于节点值向右查找,小于向左查找,先按照二叉查找树的特性操作,后续会重新着色和旋转,保持红黑树的特性。

Q5:HashMap 有什么特点?

JDK8 之前底层实现是数组 + 链表,JDK8 改为数组 + 链表/红黑树,节点类型从Entry 变更为 Node。主要成员变量包括存储数据的 table 数组、元素数量 size、加载因子 loadFactor。

table 数组记录 HashMap 的数据,每个下标对应一条链表,所有哈希冲突的数据都会被存放到同一条链表,Node/Entry 节点包含四个成员变量:key、value、next 指针和 hash 值。

HashMap 中数据以键值对的形式存在,键对应的 hash 值用来计算数组下标,如果两个元素 key 的 hash 值一样,就会发生哈希冲突,被放到同一个链表上,为使查询效率尽可能高,键的 hash 值要尽可能分散。

HashMap 默认初始化容量为 16,扩容容量必须是 2 的幂次方、最大容量为 1<< 30 、默认加载因子为 0.75。

Q6:HashMap 相关方法的源码?

JDK8 之前

hash:计算元素 key 的散列值

① 处理 String 类型时,调用 stringHash32 方法获取 hash 值。

② 处理其他类型数据时,提供一个相对于 HashMap 实例唯一不变的随机值 hashSeed 作为计算初始量。

③ 执行异或和无符号右移使 hash 值更加离散,减小哈希冲突概率。

indexFor:计算元素下标

将 hash 值和数组长度-1 进行与操作,保证结果不会超过 table 数组范围。

get:获取元素的 value 值

① 如果 key 为 null,调用 getForNullKey 方法,如果 size 为 0 表示链表为空,返回 null。如果 size 不为 0 说明存在链表,遍历 table[0] 链表,如果找到了 key 为 null 的节点则返回其 value,否则返回 null。

② 如果 key 为 不为 null,调用 getEntry 方法,如果 size 为 0 表示链表为空,返回 null 值。如果 size 不为 0,首先计算 key 的 hash 值,然后遍历该链表的所有节点,如果节点的 key 和 hash 值都和要查找的元素相同则返回其 Entry 节点。

③ 如果找到了对应的 Entry 节点,调用 getValue 方法获取其 value 并返回,否则返回 null。

put:添加元素

① 如果 key 为 null,直接存入 table[0]。

② 如果 key 不为 null,计算 key 的 hash 值。

③ 调用 indexFor 计算元素存放的下标 i。

④ 遍历 table[i] 对应的链表,如果 key 已存在,就更新 value 然后返回旧 value。

⑤ 如果 key 不存在,将 modCount 值加 1,使用 addEntry 方法增加一个节点并返回 null。

resize:扩容数组

① 如果当前容量达到了最大容量,将阈值设置为 Integer 最大值,之后扩容不再触发。

② 否则计算新的容量,将阈值设为 newCapacity x loadFactor 和 最大容量 + 1 的较小值。

③ 创建一个容量为 newCapacity 的 Entry 数组,调用 transfer 方法将旧数组的元素转移到新数组。

transfer:转移元素

① 遍历旧数组的所有元素,调用 rehash 方法判断是否需要哈希重构,如果需要就重新计算元素 key 的 hash 值。

② 调用 indexFor 方法计算元素存放的下标 i,利用头插法将旧数组的元素转移到新数组。

JDK8

hash:计算元素 key 的散列值

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwpgxs.html