程序员老鬼

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-

ok,今天先说到这,老规矩,给大家分享一份不错的副业资料,感兴趣的同学找我领取。

Image

以上,就是今天的分享了,看完文章记得右下角给何老师点赞,也欢迎在评论区写下你的留言