为什么就连JDK作者都从不使用 LinkedList?一分钟告诉你答案!

145 阅读2分钟

不受待见的 LinkedList

LinkedList 的作者 Joshua Bloch 曾在Twitter提到:

“Does anyone actually use LinkedList? I wrote it, and I never use it.”

可见,就连作者自己都不愿意用它

LinkedList 怎么了?

一句话总结:LinkedList 基于对象构建(而非数组),这极大地影响了性能,而且受限于线性结构,它没有充分利用对象存储的灵活性

与紧密存储的数组相比,链表的存储是离散的,分布在更大的内存范围内

为什么链表的离散存储是个问题?

  • 指针开销大:每个节点都需要存储 nextprev 指针,带来额外的内存消耗。
  • CPU 缓存不友好:数组的元素是连续存储的,CPU 访问时可以利用缓存预取(Cache Prefetch),而链表的离散存储会导致频繁的缓存未命中(Cache Miss)。
  • GC 负担重:LinkedList 需要频繁分配和回收对象,容易加重 GC 负担,影响性能。
  • JVM 没有特殊优化:JVM 不会对 LinkedList 进行特殊优化,导致链表的存储空间可能远大于实际数据的大小,而数组可以利用对象数组存储,提高空间利用率。

如何弥补 LinkedList 的缺憾?

链表的悲剧:线性结构 + 离散存储的无奈

  • 线性结构 限制了链表的访问模式,使得查找变慢。
  • 离散存储 导致大量的指针跳转,降低 CPU 缓存命中率,影响访问速度。

改进方向:利用更高效的数据结构,弥补链表的劣势

如果链表无法避免离散存储的缺陷,我们可以通过更高效的数据结构来优化它,例如:

  • 块链表(Block List) :将多个元素存储在一个块中,减少指针开销,提高缓存友好性。
  • 分层链表(Skip List) :增加索引层,降低查找复杂度(接近 O(log n))。
  • B+ 树:利用树形结构,提高查询和修改效率,同时保持缓存友好性。

现实应用:业界的优化方案

在实际应用中,单纯的 LinkedList 已经很少被使用,业界通常采用:

  • 跳表(Skip List) —— Redis 使用的底层数据结构之一,比链表更高效。
  • 块存储(Block Storage) —— 例如 RoaringBitmap,通过块结构减少内存碎片。
  • B+ 树 —— 数据库索引的核心结构,优化了存储和查询效率。

总结

LinkedList 的问题在于:离散存储带来的额外开销,使其性能远不如基于数组的数据结构JVM 并不会对 LinkedList 进行优化,导致它在现代计算环境下显得低效。

优化方向:如果必须使用链表,应该考虑 跳表、块链表、B+树 等更高效的数据结构,而不是简单的双向链表。

现实应用业界已经广泛采用更优的存储结构,单纯的 LinkedList 已经很少被使用。