arraylist适合随机读取多、增删少的场景,底层为动态数组,get()和尾部add()均摊o(1),但中间增删为o(n);linkedlist仅在频繁头尾增删且几乎不随机访问时适用,get()为o(n)。

ArrayList 适合随机读取多、增删少的场景
当业务中频繁调用 get(int index) 或遍历访问元素,且插入/删除集中在尾部(如日志缓冲、批量结果暂存),ArrayList 是首选。它底层是动态数组,get() 时间复杂度为 O(1),尾部 add() 均摊也是 O(1)。
但要注意:在中间位置 add(int index, E element) 或 remove(int index) 会触发数组复制,最坏 O(n)。如果业务日志写入后还要按时间戳插入到中间位置排序,就别硬扛——改用 LinkedList 或先收集再排序。
常见误用:
- 把 ArrayList 当队列用,反复 remove(0) 模拟出队 → 实际是 O(n) 操作,应换 ArrayDeque
- 初始化容量过小(默认 10),导致高频扩容(Arrays.copyOf())和内存抖动 → 提前预估 size,用 new ArrayList(expectedSize)
LinkedList 不等于“通用链表优化版”
LinkedList 只在**频繁头尾增删 + 几乎不随机访问**时才有意义。它的 addFirst()、removeLast() 等操作是 O(1),但 get(int index) 是 O(n) 遍历——这点常被忽略。
真实业务中容易踩坑的点:
- 用 for (int i = 0; i 遍历 <code>LinkedList → 每次 get() 都从头/尾开始找,整体变成 O(n²)
- 以为它线程安全或内存更省 → 实际每个元素额外持两个引用(prev/next),比 ArrayList 更占内存
- 试图用它替代栈或队列 → 直接用 ArrayDeque(基于数组、无同步开销、性能更好)更合适
典型适用场景:实现 LRU 缓存的双向链表结构(配合 HashMap 维护节点引用),而非直接拿 LinkedList 存业务数据。
立即学习“Java免费学习笔记(深入)”;
并发场景下别直接用 ArrayList 或 LinkedList
Java 的 ArrayList 和 LinkedList 都不是线程安全的。在多线程写入(如定时任务+用户请求同时往同一列表 add)时,可能抛 ConcurrentModificationException,或出现数据丢失、越界等未定义行为。
根据并发强度和一致性要求选:
- 读多写少,允许弱一致性(如统计缓存)→ Collections.synchronizedList(new ArrayList()),但注意迭代仍需手动同步
- 写操作频繁且需强一致性 → CopyOnWriteArrayList,适合监听器列表等“读远大于写”的场景;但每次写都复制整个数组,大数据量写入代价极高
- 高吞吐、低延迟、可接受最终一致 → ConcurrentLinkedQueue(非 List 接口,但常被拿来替代)或分段加锁结构(如自定义 StripedList)
切记:synchronizedList 包裹的 ArrayList 并不能让 list.get(i) == list.get(i+1) 这类复合操作原子化,业务逻辑仍需外层锁或重设计。
业务语义比性能参数更重要
选型时最容易被忽略的是接口契约本身。比如:
- 需要保持插入顺序且去重 → LinkedHashSet 比 “手动检查 ArrayList.contains() 再 add” 更可靠、更清晰
- 要按自然顺序或自定义顺序遍历时保证有序 → TreeSet 或 PriorityQueue(注意后者不是 List)比每次插入后调用 Collections.sort() 更合理
- 接口方法明确要求支持快速随机访问(如某些 SDK 的 callback 参数类型限定为 List)→ 即便内部只读,也得传 ArrayList,不能塞 LinkedList(否则性能雪崩)
一个具体例子:报表导出接口接收 List<reportrow></reportrow>,内部要做分页(subList(from, to))。若传入 LinkedList,subList() 返回的仍是链表子视图,后续 get() 仍慢;而 ArrayList.subList() 返回的是支持 O(1) 访问的 RandomAccessSubList。
所以别只盯着 big-O,先看业务调用模式、接口约束、维护成本——有时候多 2 行代码封装一层,比强行套用某个实现更可持续。










