面试篇:基础-集合

1. ArrayList扩容机制

  • ArrayList() 会使用长度为0的数组
  • 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倍,实际元素个数)

2. Iterator

fail-fast与fail-safe

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

fail-safe的读写分离即一个数组用与存储,在添加元素时通过copyof方法另外创建一个长度+1的数组,再将元素添加到新数组的末尾

3. ArrayList和LinkedList

ArrayList

  1. 基于数组,需要连续的内存

  2. 随机访问速度快(根据下标访问)

  3. 尾部增删快,越靠近头部增删速度越慢(头部增删需要移动后续所有元素)

  4. 可以利用cpu缓存,局部性原理

    如将需要运算的多个元素存入cpu缓存中,cpu从缓存中取数据后进行运算并将结果也存到缓存中,内存每隔一段时间导入cpu缓存中的数据,cpu每次将元素及其相邻的元素(ArrayList内存空间连续)存入缓存

LinkedList

  1. 基于双向链表,不需要连续的内存
  2. 随机访问速度慢
  3. 首尾增删快,中间部分删除速度也慢(定位需要时间,比AyyayList耗时多)
  4. 占用内存多
    • 链表不仅包含元素,还包含前一节点和后一节点,因此占用内存多
    • 链表不能很好的配合cpu缓存,因为链表内存不连续,下一元素可能距离上一元素很远,链表一次如果往cpu缓存存入很多数据,那么会删除之前存的数据

ArrayList综合性能高于LinkedList(只有偏头部的位置增删速度比LinkedList慢)

4. HashMap

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

    1. 1.7 数组+链表 ,1.8 数组 + 链表|红黑树
  • 为何要用红黑树?

    1. 为了防止Dos攻击(注入大量拥有同一hash值的元素),防止链表超长时影响HashMap性能,树化是偶然情况
  • 为何不一上来就树化?

    1. Hash表的查找,更新的时间复杂度时O(1),而红黑树的查找,更新的时间复杂度是O(log2n)
    2. TreeNode占用的空间也比普通Node大,如非必要,尽量使用链表
  • 树化的阈值为何是8?

    1. hash值如果足够随机,则hash表内按泊松分布,在负载因子**0.75(超过会扩容)**的情况下,长度超过8的链表出现的概率是亿分之六,选择8就是为了让树化的几率足够小
  • 树化的两个条件?

    1. 链表长度超过树化的阈值8
    2. 数组的容量大于64
  • 何时会退化为链表?

    1. 在扩容时如果拆分树,树元素个数<=6则会退化成链表
    2. remove树节点是,若root,root.left,root.right,root.left.left中有一个为null,会退化成链表
      注:是在remove之前判断
  • 索引如何计算?

    1. 计算对象的hashCode(),再调用HashMap的hash()方法进行二次哈希,最后 & (capacity - 1) 得到索引
  • HashCode都有了,为何还要二次Hash?

    1. 二次hash()是为了综合高位数据,让hash分布更为均匀
  • 数组容量为何是2的n次幂?

    1. 计算索引时,如果是2的n次幂可以使用位与运算取代模运算,效率更高;1.8在扩容时hash&oldcap == 0 的元素留在原位,否则新位置 = 旧位置 + oldcap
    2. 之前的手段都是为了配合容量为2的n次幂时的优化手段,例如HashTable的容量就不是2的n次幂,并不是说哪种设计更优,应该是设计者综合了各种因素,最终使用2的n次幂作为容量

缺点:hash分布不均匀,容量为质数的话hash分布比偶数更均匀

  • 介绍一下put方法流程,1.7和1.8有何不同?

    1. HashMap是懒惰创建数组,首次使用才创建数组

    2. 通过hashCode计算桶下标

    3. 如果桶下标还没被占用,就创建Node占位返回

    4. 如果桶下标已经被占用

      1. 已经是TreeNode的话走红黑树的添加或更新逻辑
      2. 是普通Node,走链表的添加或更新逻辑,链表长度超过树化阈值,走树化逻辑
      3. 返回前检查容量是否超过阈值,一旦超过进行扩容
    5. 不同

      1. 链表插入节点时,1.7时头插法,1.8是尾插法
      2. 1.7是大于阈值且桶下标被占用时进行扩容,1.8是大于阈值就扩容
      3. 1.8在扩容计算Node时,会优化
  • 负载因子(扩容因子)为何是0.75f?

    1. 在空间占用与查询时间之间取得较好较好的平衡
    2. 大于0.75,节省了空间,但链表长度过长会影响性能
    3. 小于0.75,hash冲突减少了,但会出现更频繁的扩容,占用更多空间
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值