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
-
基于数组,需要连续的内存
-
随机访问速度快(根据下标访问)
-
尾部增删快,越靠近头部增删速度越慢(头部增删需要移动后续所有元素)
-
可以利用cpu缓存,局部性原理
如将需要运算的多个元素存入cpu缓存中,cpu从缓存中取数据后进行运算并将结果也存到缓存中,内存每隔一段时间导入cpu缓存中的数据,cpu每次将元素及其相邻的元素(ArrayList内存空间连续)存入缓存
LinkedList
- 基于双向链表,不需要连续的内存
- 随机访问速度慢
- 首尾增删快,中间部分删除速度也慢(定位需要时间,比AyyayList耗时多)
- 占用内存多
- 链表不仅包含元素,还包含前一节点和后一节点,因此占用内存多
- 链表不能很好的配合cpu缓存,因为链表内存不连续,下一元素可能距离上一元素很远,链表一次如果往cpu缓存存入很多数据,那么会删除之前存的数据
ArrayList综合性能高于LinkedList(只有偏头部的位置增删速度比LinkedList慢)
4. HashMap
-
底层数据结构,1.7和1.8有何不同?
- 1.7 数组+链表 ,1.8 数组 + 链表|红黑树
-
为何要用红黑树?
- 为了防止Dos攻击(注入大量拥有同一hash值的元素),防止链表超长时影响HashMap性能,树化是偶然情况
-
为何不一上来就树化?
- Hash表的查找,更新的时间复杂度时O(1),而红黑树的查找,更新的时间复杂度是O(log2n)
- TreeNode占用的空间也比普通Node大,如非必要,尽量使用链表
-
树化的阈值为何是8?
- hash值如果足够随机,则hash表内按泊松分布,在负载因子**0.75(超过会扩容)**的情况下,长度超过8的链表出现的概率是亿分之六,选择8就是为了让树化的几率足够小
-
树化的两个条件?
- 链表长度超过树化的阈值8
- 数组的容量大于64
-
何时会退化为链表?
- 在扩容时如果拆分树,树元素个数<=6则会退化成链表
- remove树节点是,若root,root.left,root.right,root.left.left中有一个为null,会退化成链表
注:是在remove之前判断
-
索引如何计算?
- 计算对象的hashCode(),再调用HashMap的hash()方法进行二次哈希,最后 & (capacity - 1) 得到索引
-
HashCode都有了,为何还要二次Hash?
- 二次hash()是为了综合高位数据,让hash分布更为均匀
-
数组容量为何是2的n次幂?
- 计算索引时,如果是2的n次幂可以使用位与运算取代模运算,效率更高;1.8在扩容时hash&oldcap == 0 的元素留在原位,否则新位置 = 旧位置 + oldcap
- 之前的手段都是为了配合容量为2的n次幂时的优化手段,例如HashTable的容量就不是2的n次幂,并不是说哪种设计更优,应该是设计者综合了各种因素,最终使用2的n次幂作为容量
缺点:hash分布不均匀,容量为质数的话hash分布比偶数更均匀
-
介绍一下put方法流程,1.7和1.8有何不同?
-
HashMap是懒惰创建数组,首次使用才创建数组
-
通过hashCode计算桶下标
-
如果桶下标还没被占用,就创建Node占位返回
-
如果桶下标已经被占用
- 已经是TreeNode的话走红黑树的添加或更新逻辑
- 是普通Node,走链表的添加或更新逻辑,链表长度超过树化阈值,走树化逻辑
- 返回前检查容量是否超过阈值,一旦超过进行扩容
-
不同
- 链表插入节点时,1.7时头插法,1.8是尾插法
- 1.7是大于阈值且桶下标被占用时进行扩容,1.8是大于阈值就扩容
- 1.8在扩容计算Node时,会优化
-
-
负载因子(扩容因子)为何是0.75f?
- 在空间占用与查询时间之间取得较好较好的平衡
- 大于0.75,节省了空间,但链表长度过长会影响性能
- 小于0.75,hash冲突减少了,但会出现更频繁的扩容,占用更多空间