java基础面试题001—ArrayList底层机制和LinkedList比较
总结List、Set实际应用
- 面试官说:如果希望元素可以重复、有索引、索引查询要快 用哪个呢?
ArrayList,基于数组,所以索引查询快 - 面试官说:如果希望元素可以重复、有索引、首尾增添速度要快 用哪个呢?
LinkedList,基于链表,所以首尾增添速度快 - 面试官说:如果希望元素增删改查速度快、元素不重复、无序、无索引 用哪个呢?
Hashset,基于哈希表 - 面试官说:如果希望元素增删改查速度快、元素不重复、有序、无索引 用哪个呢?
LinkedHashSet,基于双向链表和哈希表 - 面试官说:如果对对象排序 用哪个呢?
TreeSet,基于红黑树
一、ArrayList对比LinkedList
通常在面试时,不会单独问LinkedList而是把ArrayList一起来问,比如说两者进行对比。
那么,你认为这样的说法是正确的吗?
- ArrayList 增删慢、查询快
- LinkedList 增删快、查询慢
其实这是错误的理解。我们一起来看看真正ArrayList和LinkedList吧
1、ArrayList的介绍:
- 基于数组,需要连续内存。
- 随机访问快(指根据下标访问)
由于内存地址是连续的。如果已经知道了第一个内存地址,那么就可以通过计算得到对应的元素。 首地址+(索引*每个类型大小)
- 在尾部进行插入和删除,相比LinkedList并不差。但是其他部分插入和删除都会进行移动数据,因此性能低
- 可以利用CPU缓存,局部性原理【优点】
当没有CPU缓存时。CPU从内存读写一次大概需要几百纳秒。而CPU执行一次计算是少于纳秒级别的。
2、LinkedList的介绍:
- 基于双向链表,无需连续内存
- 随机访问慢,因为要沿着链表进行遍历。
- 头部和尾部进行插入和删除性能高
- 占用内存多【缺点】
ArrayList本身大小(不包含元素),数组长度(1000各元素,扩容后是1234大小),element 元素的大小
linkedList本身占用内存非常大。因为其中包括元素、上下指针。
3、自我总结
第一点:是对于数据结构来对比:
ArrayList底层是数组,所以在内存分配上需要一块连续的内存。
linkedList底层是双向链表,所以在内存分配上不需要连续的内存
第二点:对于随机访问能力:
ArrayList的随机访问快。原码中是实现了一个叫【RandomAccess接口】。通过下标索引进行过去元素。如果没有实现该接口,就会通过迭代器的next方法。
LinkedList随机访问慢。因为要沿着链表进行遍历。
第三点:对于增删查的速度:
ArrayList的尾部增删性能还可以。其它部分进行增删就比较慢了,是因为涉及增删的后面的元素,会进行复制移动数据。也就是arrayCopy操作。所以性能会受影响。
ArrayList除了头部增删性能不如LinkedList,其他部位都不逊于Linkedlist
LinedList的头尾增删性能高。中间的性能不高,甚至不如ArrayList 因为移动指针比较慢。指针沿着链表进行遍历,从而找到要操作的元素。
根据元素的内容去查找的话,时间复杂度都是0(n)。所以都不适合用来用查询。 而HashMap、TreeMap更适合用来做查询,底层是哈希表和红黑树,更适合做查询。
第四点:ArrayList底层是数组,可以充分配合CPU缓存,利用局部性原理,提升相邻元素被访问的机会。LinkedList并不可以。
第五点:LinkedList底层是双向链表,所以占用内存比较多。
二、ArrayList的扩容机制
1、初始容量
调用的是ArrayList()的无参构造,初始容量是0
调用的是ArrayList(int initialCapacity)的有参构造,会使用指定容量的数组。
调用的是addAll(Collection<? extends E> c) 带有集合的有参构造,会使用集合的大小来作为数组的初始容量。
2、扩容机制
-
add(Object o)首次扩容为10,再次扩容为上次扩容的1.5倍。【1.5倍:扩容的数 + 右移一位】例如15+7=22
依次:0 10 15 22 33 49 74 109 163 244,366,549,823,123,2776 -
addAll(Collection c) 没有元素时,扩容为Math.max(10,实际元素个数),有元素时为Math.max(原容量1.5倍,实际元素个数)。 下次扩容的容量跟实际元素的个数,取最大值最为真正扩容的容量。
比如从0插入11个元素,那么容量就是11。 比如插入11插入2两个元素,那么容量是从10的基础1.5倍,就是15,实际只有13个元素。
来无参构造来说:
初始容量为0,第一次扩容是10. 第二次扩容是,上一次容量的1.5倍。
扩容是创建一个新数组,把就数组的元素拷贝到新数组中,再把新元素追加到新数组中的最后一个位置。
旧数组没有人引用,会作为垃圾,然后垃圾回收掉。
三、fail-fast 与 fail-safe
ArrayList是fail-fast的典型代表,遍历的同时不能修改,尽快失败
CopyOnWriteArrayList是fail-safe的典型代表,遍历的同时可以修改,原理是读写分离。
四、Set
1、Set系列集合特点
- 无序:存取顺序不一致。指的是只无序一次,后面的都是相同的顺序
- 不重复:可以去除重复
- 无索引:没有带索引的方法,所以不能使用普通的for循环遍历,也不能通过索引获取元素
2、set集合实现类的特点
- HashSet:无序、不重复、无索引
- LinkedHashSet:有序、不重复、无索引
- TreeSet:排序、不重复、无索引。