题目详细答案
ArrayList
ArrayList 是最常用的 List 实现类,线程不安全,内部是通过数组实现的,继承了AbstractList,实现了List。它允许对元素进行快速随机访问。数组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要将已经有数组的数据复制到新的存储空间中。当从 ArrayList 的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。排列有序,可重复,容量不够的时候,新容量的计算公式为:
newCapacity = oldCapacity + (oldCapacity >> 1),这实际上是将原容量增加50%(即乘以1.5)。
ArrayList实现了RandomAccess接口,即提供了随机访问功能。RandomAccess是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。
ArrayList实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。
LinkedList(链表)
LinkedList 是用链表结构存储数据的,线程不安全。很适合数据的动态插入和删除,随机访问和遍历速度比较慢,增删快。另外,他还提供了 List 接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。底层使用双向链表数据结构。
LinkedList是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。
Vector(数组实现、线程同步)
Vector 与 ArrayList 一样,也是通过数组实现的,Vector和ArrayList用法上几乎相同,但Vector比较古老,一般不用。Vector是线程同步的,效率低。即某一时刻只有一个线程能够写 Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,因此,访问它比访问 ArrayList 慢。默认扩展一倍容量。
ArrayList、LinkedList 与 Vector详解
在 Java 集合框架中,List
接口的实现类是日常开发中最常用的数据结构之一。其中,ArrayList
、LinkedList
和 Vector
是三种经典实现,它们基于不同的数据结构设计,适用于不同的业务场景。本文将从底层实现、核心特性、性能表现等方面详细对比这三种集合,帮助开发者合理选择。
一、ArrayList:基于动态数组的随机访问王者
ArrayList
是 List
接口最常用的实现类,其设计核心是动态数组,兼顾了数组的高效访问和动态扩容能力。
底层实现
- 内部通过Object类型数组存储元素,默认初始容量为10(JDK 8及以上)。
- 当元素数量超过当前容量时,会触发扩容机制,新容量计算公式为:
newCapacity = oldCapacity + (oldCapacity >> 1)
(即原容量的1.5倍)。 - 实现了
RandomAccess
接口(标记接口),表明支持快速随机访问(通过索引直接定位元素)。
核心特性
- 线程不安全:未实现同步机制,多线程并发修改时可能导致数据不一致(如
ConcurrentModificationException
)。 - 有序可重复:元素按插入顺序排列,允许存储重复元素和
null
值。 - 随机访问高效:通过索引访问元素的时间复杂度为 O(1),适合频繁查询的场景。
- 增删效率较低:
-
- 尾部插入/删除:时间复杂度 O(1)(无扩容时)。
- 中间插入/删除:需要复制和移动数组元素,时间复杂度 O(n)(n为元素数量)。
- 支持序列化:实现了
java.io.Serializable
接口,可通过序列化传输。
适用场景
- 频繁通过索引访问元素(如随机读取操作多)。
- 元素数量变化不大,或主要在尾部进行增删操作。
- 单线程环境或已通过外部同步(如
Collections.synchronizedList
)保证线程安全。
二、LinkedList:基于双向链表的灵活操作能手
LinkedList
采用双向链表作为底层结构,更注重元素插入和删除的灵活性。
底层实现
- 内部由节点(Node) 组成,每个节点包含三部分:
prev
(前驱指针)、item
(元素值)、next
(后继指针)。 - 不依赖数组,因此无需扩容,元素存储在离散的内存空间中。
- 实现了
Deque
接口,可作为堆栈(Stack)、队列(Queue) 或双端队列(Deque) 使用(提供addFirst()
、pollLast()
等方法)。
核心特性
- 线程不安全:同
ArrayList
,无同步机制,并发场景需额外处理。 - 有序可重复:元素按插入顺序排列,允许重复元素和
null
值。 - 随机访问低效:通过索引访问元素时,需从表头/表尾遍历,时间复杂度 O(n)。
- 增删效率较高:
-
- 已知节点位置时,插入/删除仅需修改指针,时间复杂度 O(1)。
- 未知位置时,需先遍历找到节点,时间复杂度 O(n)。
- 内存开销较大:每个元素需额外存储前驱和后继指针,内存占用高于
ArrayList
。
适用场景
- 频繁在中间位置插入/删除元素(如实现链表式数据结构)。
- 需要使用队列、堆栈等数据结构(利用
Deque
接口的方法)。 - 元素数量不确定,且增删操作远多于查询操作。
三、Vector:古老的线程安全数组实现
Vector
是 Java 早期的 List
实现类,与 ArrayList
同属数组结构,但强调线程安全。
底层实现
- 内部通过Object类型数组存储元素,默认初始容量为10。
- 扩容机制:默认新容量为原容量的2倍(可通过构造函数指定增量
capacityIncrement
)。 - 实现了
RandomAccess
接口,支持随机访问。
核心特性
- 线程安全:绝大多数方法(如
add()
、get()
)通过synchronized
关键字修饰,保证多线程环境下的原子操作。 - 有序可重复:同
ArrayList
,支持重复元素和null
值。 - 性能较低:同步机制会带来额外开销,单线程环境下效率低于
ArrayList
。 - 功能冗余:部分方法(如
addElement()
、elementAt()
)与List
接口方法重复,设计上较为陈旧。
适用场景
- 多线程环境下需要线程安全的数组(但现代开发中更推荐
CopyOnWriteArrayList
,性能更优)。 - 维护 legacy 代码(旧系统中仍在使用)。
四、三者核心区别对比
特性 |
ArrayList |
LinkedList |
Vector |
底层结构 |
动态数组 |
双向链表 |
动态数组 |
线程安全 |
不安全 |
不安全 |
安全( |
随机访问效率 |
高(O(1)) |
低(O(n)) |
高(O(1)) |
中间增删效率 |
低(O(n)) |
高(已知位置时O(1)) |
低(O(n)) |
扩容机制 |
原容量1.5倍 |
无需扩容 |
原容量2倍(默认) |
内存开销 |
较低(仅存储元素) |
较高(需存储指针) |
较低(同数组) |
适用场景 |
频繁查询、尾部增删 |
频繁中间增删、队列/堆栈 |
多线程同步(不推荐新用) |
五、选择建议
- 优先考虑
ArrayList
:大多数场景下,查询操作远多于增删,且ArrayList
的综合性能更优。 - 中间操作多选
LinkedList
:当需要频繁在列表中间插入/删除元素,或需使用队列/堆栈功能时。 - 线程安全场景:避免使用
Vector
,推荐CopyOnWriteArrayList
(读写分离,适合读多写少)或通过Collections.synchronizedList
包装ArrayList
。 - 容量规划:若已知元素数量,初始化时指定容量(如
new ArrayList<>(100)
),可减少ArrayList
和Vector
的扩容次数。