面试 集合篇

ArrayList

扩容机制
  • ArrayList() 会使用长度为零的数组【无参构造】
  • ArrayList(int initialCapacity) 会使用指定容量的数组 【有参构造】
  • public ArrayList(Collection<? extends E> c) 会使用c的大小作为数组容量
  • add(Object o) 首次扩容为 10,再次扩容为上次容量的 1.5
  • addAll(Collection c) 没有元素时,扩容为 Math.max(10,实际元素个数),有元素时, Math.max(原容量 1.5 倍,实际元素个数)

fail-fast 与 fail-safe
  • ArrayList 是 fail-fast的典型代表,遍历的同时不能修改,尽快失败
  • CopyOnWriteArrayList 是 fail-safe 的典型代表,遍历的同时可以修改,原理是读写分离

LinkedList

  • 掌握 ArrayList 和 LinkedList 的比较

    ArrayList 和 LinkedList

  • ArrayList

    • 基于数组,需要连续内存
    • 随机访问快 (指根据下标访问)
    • 尾部插入,删除性能可以;其他部分插入,删除都会移动数据,因此性能会低
    • 可以利用 CPU 缓存,局部性原理
  • LinkedList

    • 基于双向链表,无需连续内存
    • 随机访问慢 (指要沿着链表遍历)
    • 头尾插入删除性能高
    • 占用内存多

HashMap

  • 初始容量16,负载因子0.75,每次扩容成之前的两倍

  • 底层数据结构,1.7 和1.8 有何不同?

    • 1.7 数组 + 链表,1.8 数组 + (链表 | 红黑树) 【当链表长度超过一定阈值时,会转化为红黑树,当红黑树数量减小时,会转化为链表】
  • 为何要用红黑树,为何一上来不树化,树化阈值为何是8,为何会树化,何时会退化为链表?

    • 红黑树用来避免 DOS 攻击,防止链表超长时性能下降,树化应当是偶然情况
      • hash表的查找,更新的时间复杂度是O(1),而红黑树的查找,更新的时间复杂度是O(log2 n),TreeNode 占用空间也比普通Node的大,如非必要,尽量还是使用链表 【红黑树是防止攻击用的】
      • hash 值如果足够随机,则在 hash 表内按泊松分布,在加(?负)载因子 0.75 的情况下,长度超过 8 的链表出现概率是0.00000006,选择 8 就是为了让树化几率足够小
    • 树化两个条件:链表长度超过树化阈值 (大于8),并且数组长度必须 >= 64
    • 退化情况1:在扩容时如果拆分树时,树元素个数 <= 6 则会退化链表
    • 退化情况2:在remove树节点前,若树的root、root.left、root.right、root.left.left 有一个为null,也会退化为链表
  • 索引如何计算?hashCode都有了,为何还要提供 hash() 方法?数组容量为何是 2 的 n 次幂?

    • 计算对象的 hashCode(),再进行调用 HashMap() 的 hash() 方法进行二次哈希,最后 &(capacity - 1) 得到索引 【capacity必须是2的n次幂】
    • 二次 hash() 是为了综合高位数据,让哈希分布更为均匀
    • 计算索引时,如果是 2 的 n 次幂可以使用位与运算代替取模,效率更高;扩容时 hash & oldCap(旧容量) == 0 的元素留在原来位置,否则新位置 = 旧位置 + oldCap
    • 但前三点都是为了配合容量为 2 的 n 次方幂时的优化手段,例如 Hashtable 的容量就不是 2 的 n 次方幂,并不能说哪种设计更优,应该是设计者综合了各种因素,最终选择了使用 2 的 n 次幂作为容量
  • 介绍一下 put 方法流程,1.7 和 1.8 有何不同?

  • HashMap 是懒惰创建数组的,首次使用才创建数组

  • 计算索引 (桶下标)

  • 如果桶下标还没人占用,创建 Node 站位返回

  • 如果桶下标已经有人占用

    • 已经是 TreeNode 走红黑树的添加或更新逻辑
    • 是普通 Node,走链表的添加或更新逻辑,如果链表长度超过树化阈值,走树化逻辑
  • 返回前检查容量是否超过阈值,一旦超过进行扩容

  • 不同

    • 链表插入节点时,1.7 是头插法,1.8 是尾插法
    • 1.7 是大于等于阈值且没有空位时才扩容,而 1.8 是大于阈值就扩容
    • 1.8 在扩容计算 Node 索引时,会优化
  • 加载因子为何默认是0.75f?

    • 在空间占用与查询时间之间取得较好的权衡
    • 大于这个值,空间节省了,但链表就会比较长,影响性能
    • 小于这个值,冲突减少了,但扩容就会更频繁,空间占用多
  • 多线程下会有啥问题

    • 扩容死链 (1.7)
    • 数据错乱 (1.7,1.8)
  • key 能否为 null,作为 key 的对象有什么要求?

    • HashMap 的 key 可以为 null,但 Map 的其他实现则不然
    • 作为 key 的对象,必须实现 hashCode 和 equals,并且 key 的内容不能修改 (不可变) 【hashcode相同不一定equals,而两个对象equals,则hashcode一定相同】
  • String 对象的 hashCode() 如何设计的,为啥每次乘的是 31?

    • 目标是达到较为均匀的散列效果,每个字符串的 hashCode 足够独特
      • 字符串中的每个字符都可以表现为一个数字,称为 Si,其中 i 的范围是 0 ~ n - 1
      • 散列公式
      • 31 带入公式有较好的散列特性
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值