本篇博客主要讲解 List 接口的三个实现类 ArrayList,LinkedList,Vector 的使用方法以及三者之间的区别.
1. ArrayList 使用
ArrayList 是 List 接口最常用的实现类, 内部通过数组来实现, 因此它的优点是适合随机查找和遍历, 缺点是不适合插入和删除.
ArrayList 类的代码声明如下所示:
- public class ArrayList<E> extends AbstractList<E>
- implements List<E>, RandomAccess, Cloneable, java.io.Serializable
- {
- ......
- }
1.1 添加元素
使用 ArrayList 添加元素有以下两个重载:
- boolean add(E e);
- void add(int index, E element);
boolean add(E e); 是将元素添加到集合的末尾,
void add(int index, E element); 是将元素添加到指定的索引位置(索引是从 0 开始的).
使用方法如下所示:
- List<String> platformList = new ArrayList<>();
- // 添加元素
- platformList.add("博客园");
- platformList.add("掘金");
- platformList.add("微信公众号");
- // 添加重复元素, 会添加成功, 因为 List 支持添加重复元素
- platformList.add("博客园");
- platformList.add("掘金");
- platformList.add(3, "个人博客");
1.2 获取元素
获取 ArrayList 中指定索引处的元素的使用方法如下所示:
System.out.println("索引为 3 的元素为:" + platformList.get(3));
如果指定的索引超出了集合的最大索引, 比如 platformList.get(6)就会抛出异常 java.lang.IndexOutOfBoundsException:
1.3 获取集合元素个数
获取 ArrayList 元素个数的使用方法如下所示:
System.out.println("platformList 的元素个数为:" + platformList.size());
1.4 删除元素
使用 ArrayList 删除元素有以下两个重载:
- E remove(int index);
- boolean remove(Object o);
E remove(int index); 是删除集合中指定索引处的元素, boolean remove(Object o); 是删除集合中的指定元素.
使用方法如下所示:
- // 指定索引删除重复的元素 "博客园" "掘金"
- platformList.remove(4);
- platformList.remove(4);
- // 删除指定元素 "个人博客"
- platformList.remove("个人博客");
1.5 修改元素
修改 ArrayList 中指定索引处的元素值的使用方法如下所示:
- platformList.set(0, "博客园: https://www.cnblogs.com/zwwhnly/");
- platformList.set(1, "掘金: https://juejin.im/user/5c7ce730f265da2dca388167");
- platformList.set(2, "微信公众号: 申城异乡人");
1.6 判断集合是否为空
判断 ArrayList 是否为空的使用方法如下所示:
System.out.println("isEmpty:" + platformList.isEmpty());
1.7 遍历元素(面试常问)
遍历 ArrayList 的元素主要有以下 3 种方式:
迭代器遍历
for 循环
foreach 循环
使用方法如下所示:
- System.out.println("使用 Iterator 遍历:");
- Iterator<String> platformIterator = platformList.iterator();
- while (platformIterator.hasNext()) {
- System.out.println(platformIterator.next());
- }
- System.out.println();
- System.out.println("使用 for 循环遍历:");
- for (int i = 0; i <platformList.size(); i++) {
- System.out.println(platformList.get(i));
- }
- System.out.println();
- System.out.println("使用 foreach 遍历:");
- for (String platform : platformList) {
- System.out.println(platform);
- }
1.8 清空集合
清空 ArrayList 中所有元素的使用方法如下所示:
platformList.clear();
1.9 完整示例代码
上面讲解的几点, 完整代码如下所示:
- public static void main(String[] args) {
- List<String> platformList = new ArrayList<>();
- // 添加元素
- platformList.add("博客园");
- platformList.add("掘金");
- platformList.add("微信公众号");
- // 添加重复元素, 会添加成功, 因为 List 支持添加重复元素
- platformList.add("博客园");
- platformList.add("掘金");
- platformList.add(3, "个人博客");
- System.out.println("索引为 3 的元素为:" + platformList.get(3));
- System.out.println("platformList 的元素个数为:" + platformList.size());
- // 指定索引删除重复的元素 "博客园" "掘金"
- platformList.remove(4);
- platformList.remove(4);
- // 删除指定元素 "个人博客"
- platformList.remove("个人博客");
- System.out.println("platformList 的元素个数为:" + platformList.size());
- platformList.set(0, "博客园: https://www.cnblogs.com/zwwhnly/");
- platformList.set(1, "掘金: https://juejin.im/user/5c7ce730f265da2dca388167");
- platformList.set(2, "微信公众号: 申城异乡人");
- System.out.println("isEmpty:" + platformList.isEmpty());
- System.out.println("使用 Iterator 遍历:");
- Iterator<String> platformIterator = platformList.iterator();
- while (platformIterator.hasNext()) {
- System.out.println(platformIterator.next());
- }
- System.out.println();
- System.out.println("使用 for 循环遍历:");
- for (int i = 0; i <platformList.size(); i++) {
- System.out.println(platformList.get(i));
- }
- System.out.println();
- System.out.println("使用 foreach 遍历:");
- for (String platform : platformList) {
- System.out.println(platform);
- }
- System.out.println();
- // 清空集合
- platformList.clear();
- System.out.println("isEmpty:" + platformList.isEmpty());
- }
输出结果为:
索引为 3 的元素为: 个人博客
platformList 的元素个数为: 6
platformList 的元素个数为: 3
isEmpty:false
使用 Iterator 遍历:
博客园: https://www.cnblogs.com/zwwhnly/
掘金: https://juejin.im/user/5c7ce730f265da2dca388167
微信公众号: 申城异乡人
使用 for 循环遍历:
博客园: https://www.cnblogs.com/zwwhnly/
掘金: https://juejin.im/user/5c7ce730f265da2dca388167
微信公众号: 申城异乡人
使用 foreach 遍历:
博客园: https://www.cnblogs.com/zwwhnly/
掘金: https://juejin.im/user/5c7ce730f265da2dca388167
微信公众号: 申城异乡人
isEmpty:true
2. LinkedList 使用
LinkedList 也是 List 接口的实现类, 内部使用链表结构存储数据, 因此它的优点是适合动态插入和删除元素, 缺点是随机查找和遍历速度比较慢.
LinkedList 类的代码声明如下所示:
- public class LinkedList<E>
- extends AbstractSequentialList<E>
- implements List<E>, Deque<E>, Cloneable, java.io.Serializable
- {
- }
LinkedList 类的使用方法和 ArrayList 基本一样, 只需修改下声明处的代码即可:
List<String> platformList = new LinkedList<>();
3. Vector 使用
Vector 也是 List 接口的实现类, 内部也是通过数组来实现.
Vector 类的代码声明如下所示:
- public class Vector<E>
- extends AbstractList<E>
- implements List<E>, RandomAccess, Cloneable, java.io.Serializable
- {
- }
与 ArrayList 不同的是, Vector 是线程安全的, 即某一时刻只有一个线程能够写 Vector, 避免多线程同时写而引起的不一致性. 不过这也造成 Vector 的缺点: 实现线程的同步需要额外的花费, 因此它的访问速度会比 ArrayList 慢一些.
可以认为 Vector 是 ArrayList 在多线程环境下的实现版本.
所以 Vector 类的使用方法和 ArrayList 基本一样, 只需修改下声明处的代码即可:
List<String> platformList = new Vector<>();
由于要支持线程同步, 因此 Vector 类的很多方法都有 synchronized 关键字, 如下所示:
- public synchronized boolean isEmpty() {
- return elementCount == 0;
- }
- public synchronized int size() {
- return elementCount;
- }
- public synchronized void addElement(E obj) {
- modCount++;
- ensureCapacityHelper(elementCount + 1);
- elementData[elementCount++] = obj;
- }
4. ArrayList,LinkedList,Vector 的区别(面试常问)
注意: 以下代码使用的 JDK 版本为 1.8.0_191
4.1 相同点
ArrayList,LinkedList,Vector 都实现了 List 接口, 所以使用方式很类似, 通过上面的示例也能发现这一点.
4.2 不同点
但是 ArrayList,LinkedList,Vector 的内部实现方式不同, 也就导致了它们之间是有区别的.
4.2.1 存储结构
ArrayList 和 Vector 是基于数组实现的, LinkedList 是基于双向链表实现的.
这也就导致 ArrayList 适合随机查找和遍历, 而 LinkedList 适合动态插入和删除元素.
关于数组和双向链表, 这里不做详解, 后续会单独写篇文章总结.
4.2.2 线程安全性
ArrayList 和 LinkedList 是线程不安全的, Vector 是线程安全的.
Vector 可以看做是 ArrayList 在多线程环境下的另一种实现方式, 这也导致了 Vector 的效率没有 ArraykList 和 LinkedList 高.
如果要在并发环境下使用 ArrayList 或者 LinkedList, 可以调用 Collections 类的 synchronizedList()方法:
Collections.synchronizedList(platformList);
4.2.3 扩容机制
ArrayList 和 Vector 都是使用 Object 类型的数组来存储数据的, ArrayList 的默认容量是 0,Vector 的默认容量是 10.
空说无凭, 我们先看下 ArrayList 的使用示例:
- List<String> strArrayList = new ArrayList<>();
- for (int i = 0; i <20; i++) {
- strArrayList.add(String.valueOf(i));
- }
执行的 ArrayList 构造函数的源码为:
- transient Object[] elementData;
- public ArrayList() {
- this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
- }
- private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
再看下 Vector 的使用示例:
- List<String> strVector = new Vector<>();
- for (int i = 0; i <30; i++) {
- strVector.add(String.valueOf(i));
- }
执行的 Vector 构造函数的源码为:
- protected Object[] elementData;
- protected int capacityIncrement;
- public Vector() {
- this(10);
- }
- public Vector(int initialCapacity) {
- this(initialCapacity, 0);
- }
- public Vector(int initialCapacity, int capacityIncrement) {
- super();
- if (initialCapacity < 0)
- throw new IllegalArgumentException("Illegal Capacity:"+
- initialCapacity);
- this.elementData = new Object[initialCapacity];
- this.capacityIncrement = capacityIncrement;
- }
当向这两种类型中添加元素时, 若容量不够, 就会进行扩容, 扩容的本质是产生一个新数组, 将原数组的数据复制到新数组, 再将新的元素添加到新数组中, 使用的方法是 Arrays.copyOf(), 其中 ArrayList 扩容后的容量是之前的 1.5 倍, Vector 默认情况下扩容后的容量是之前的 2 倍.
仍然使用上面的 ArrayList 的例子:
- List<String> strArrayList = new ArrayList<>();
- for (int i = 0; i <20; i++) {
- strArrayList.add(String.valueOf(i));
- }
在执行完 List<String> strArrayList = new ArrayList<>(); 后, 此时 strArrayList 的容量是 0,
然后在添加第 1 个元素时, strArrayList 的容量会扩容为容量 10,
当添加第 11 个元素时, strArrayList 的容量会扩容为容量 15,
当添加第 16 个元素时, strArrayList 的容量会扩容为容量 22,
如果还需要扩容, 依次会扩容到 33-->49.
看下 ArrayList 的源码, 就明白为什么会这样扩容:
- private static final int DEFAULT_CAPACITY = 10;
- public boolean add(E e) {
- ensureCapacityInternal(size + 1); // Increments modCount!!
- elementData[size++] = e;
- return true;
- }
- private void ensureCapacityInternal(int minCapacity) {
- ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
- }
- private static int calculateCapacity(Object[] elementData, int minCapacity) {
- if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
- return Math.max(DEFAULT_CAPACITY, minCapacity);
- }
- return minCapacity;
- }
- private void ensureExplicitCapacity(int minCapacity) {
- modCount++;
- // overflow-conscious code
- if (minCapacity - elementData.length> 0)
- grow(minCapacity);
- }
- private void grow(int minCapacity) {
- // overflow-conscious code
- int oldCapacity = elementData.length;
- int newCapacity = oldCapacity + (oldCapacity>> 1);
- if (newCapacity - minCapacity <0)
- newCapacity = minCapacity;
- if (newCapacity - MAX_ARRAY_SIZE> 0)
- newCapacity = hugeCapacity(minCapacity);
- // minCapacity is usually close to size, so this is a win:
- elementData = Arrays.copyOf(elementData, newCapacity);
- }
最核心的代码就是 int newCapacity = oldCapacity + (oldCapacity>> 1);, 所以 ArrayList 扩容后的容量是之前的 1.5 倍.
再看下上面的 Vector 例子:
- List<String> strVector = new Vector<>();
- for (int i = 0; i <30; i++) {
- strVector.add(String.valueOf(i));
- }
在执行完 List<String> strVector = new Vector<>(); 后, 此时 strVector 的容量是 10,
当添加第 11 个元素时, strVector 的容量会扩容为容量 20,
当添加第 21 个元素时, strVector 的容量会扩容为容量 40,
如果还需要扩容, 依次会扩容到 80-->160.
看下 Vector 的源码, 就明白为什么会这样扩容:
- public synchronized void addElement(E obj) {
- modCount++;
- ensureCapacityHelper(elementCount + 1);
- elementData[elementCount++] = obj;
- }
- private void ensureCapacityHelper(int minCapacity) {
- // overflow-conscious code
- if (minCapacity - elementData.length> 0)
- grow(minCapacity);
- }
- private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
- private void grow(int minCapacity) {
- // overflow-conscious code
- int oldCapacity = elementData.length;
- int newCapacity = oldCapacity + ((capacityIncrement> 0) ?
- capacityIncrement : oldCapacity);
- if (newCapacity - minCapacity <0)
- newCapacity = minCapacity;
- if (newCapacity - MAX_ARRAY_SIZE> 0)
- newCapacity = hugeCapacity(minCapacity);
- elementData = Arrays.copyOf(elementData, newCapacity);
- }
最核心的代码就是 int newCapacity = oldCapacity + ((capacityIncrement> 0) ?capacityIncrement : oldCapacity);, 所以 Vector 默认情况下扩容后的容量是之前的 2 倍.
4.2.4 效率
ArrayList 随机查找和遍历的效率会高一些, 但动态插入和删除元素的效率会低一些.
LinkedList 动态插入和删除元素的效率会高一些, 但随机查找和遍历的效率会低一些.
如果需要在多线程下操作集合元素, 建议使用 Vector, 否则的话, 建议使用 ArrayList.
5. 源码及参考
ArrayList,LinkedList,Vector 的区别和实现原理
Java 深入 - 深入理解 Java 集合 https://segmentfault.com/a/1190000008934023
Java 进阶 (四十六) 简述 ArrayList,Vector 与 LinkedList 的异同点
6. 最后
来源: https://www.cnblogs.com/zwwhnly/p/11265599.html