Arraylist和LinkedList的区别,哪个集合是线程安全的?
嗨,大家好!今天咱们聊聊Java面试中一个经典到不能再经典的问题:ArrayList和LinkedList的区别,以及哪个集合是线程安全的? 这题,堪称面试圈的“送命题”了,答得不好直接GG。但别慌,咱慢慢剖析,不仅要懂,还要懂得秀。
ArrayList vs LinkedList,有啥不同?
聊这个问题,先得搞清楚两者的基本特性。咱先从数据结构角度切入,毕竟编程就像追剧,剧情要搞清楚。
1. ArrayList的底层:动态数组
ArrayList的底层是个动态数组,它的特点是:
查询快:因为数组是连续存储,支持通过索引随机访问,时间复杂度是 (O(1))。 增删慢:增删操作时,数组需要移动元素,比如在中间插入个数据,后面的全得挪窝,时间复杂度是 (O(n))。 扩容性能问题:ArrayList默认初始容量是10,当容量满了会扩容为原来的1.5倍。扩容意味着要新建一个更大的数组,然后把旧数组里的数据复制过去,这过程你懂的,性能压力山大。
看个简化的源码段落(基于JDK 8):
// ArrayList扩容的核心代码
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 扩容1.5倍
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
elementData = Arrays.copyOf(elementData, newCapacity);
}
这段扩容代码就是ArrayList的“灵魂”,但如果你频繁增删元素,这种动态数组就显得有些笨重了。
2. LinkedList的底层:双向链表
LinkedList的底层是个双向链表,它的特点是:
增删快:链表每个节点都存了当前元素的值和前后指针,插入或删除元素只需调整指针即可,时间复杂度是 (O(1))。 查询慢:因为链表是线性存储,查找时需要从头遍历到尾,时间复杂度是 (O(n))。 额外内存消耗:链表需要额外存储指针,内存占用相对较高。
代码里的“节点”长这样:
// LinkedList节点定义
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev; Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
所以,如果你写个LinkedList,记得别随便 get(index),否则性能可能“感人”。
哪个更适合你的场景?
频繁查询(如读取配置数据):ArrayList更优。 频繁增删(如模拟队列或栈):LinkedList更优。
这里有个小段子:
“用ArrayList处理频繁插入和删除就像搬家:每次搬个新家都得把家具从头到尾挪一遍。用LinkedList做频繁查询就像翻书:每次找某页都得从封面一页页翻起。😂
哪个集合是线程安全的?
直接说答案:都不是!
ArrayList和LinkedList是非线程安全的,这在多线程操作时就像“共享单车没人管”,容易翻车。 如果你非要在多线程场景下用,可以选择: 手动加锁:用 synchronized块保护。使用 Collections.synchronizedList包装。或者直接上更高阶的选择,比如 CopyOnWriteArrayList,它是线程安全的动态数组,适合读多写少的场景。
看个简单的线程安全示例:
List<String> syncList = Collections.synchronizedList(new ArrayList<>());syncList.add("线程1");
syncList.add("线程2");
synchronized (syncList) {
for (String s : syncList) {
System.out.println(s); // 遍历时加锁,防止并发修改异常
}
}
当然,synchronizedList是加了全局锁的,性能会打折。如果你更看重性能,推荐试试CopyOnWriteArrayList。
最后补充点面试秀的“加分项”
这道题,答到这里基本过关,但如果你还能提到下面这些点,面试官可能会暗暗点头。
1. ArrayList的“容量管理”策略
面试官可能会问:“ArrayList的扩容是不是线性增长?会不会内存浪费?” 这时你可以说:
ArrayList的扩容是1.5倍增长,扩容大了,可能浪费内存;扩容小了,频繁扩容又影响性能。这是典型的空间换时间。 如果你明确知道会存多少数据,建议用 new ArrayList<>(initialCapacity)直接指定初始容量,减少扩容次数。
2. LinkedList的“节点操作”效率问题
链表的增删虽然是 (O(1)),但前提是你已经有了节点的引用。如果是通过索引找到节点再操作,还是 (O(n)) 的。别被这个细节坑了!
总结一句话
ArrayList和LinkedList没有绝对的好坏,只有场景适配度。你要搞清楚场景需求,再决定用哪个。还有,别忘了默认情况下它们都不是线程安全的,多线程场景请谨慎操作。
-END-
以上,就是今天的分享了,看完文章记得右下角给何老师点赞,也欢迎在评论区写下你的留言