Java集合系列(二):ArrayList、LinkedList、Vector的使用方法及区别
本篇博客主要讲解List接口的三个实现类ArrayList、LinkedList、Vector的使用方法以及三者之间的区别。
注意:本文中代码使用的JDK版本为1.8.0_191
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的区别(面试常问)
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进阶(四十六)简述ArrayList、Vector与LinkedList的异同点
原创不易,如果觉得文章能学到东西的话,欢迎点个赞、评个论、关个注,这是我坚持写作的最大动力。
如果有兴趣,欢迎添加我的微信:zwwhnly,等你来聊技术、职场、工作等话题(PS:我是一名奋斗在上海的程序员)。
Java集合系列(二):ArrayList、LinkedList、Vector的使用方法及区别的更多相关文章
- 【Java集合系列二】LinkedList解析
一.简介 1.LinkedList继承关系 2.LinkedList底层实现 LinkedList使用双向链表存储数据,所以没有默认的容量,也不会有扩容一说.只有两个指针,永远指向链表的两端:firs ...
- Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例
java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...
- java集合系列之ArrayList源码分析
java集合系列之ArrayList源码分析(基于jdk1.8) ArrayList简介 ArrayList时List接口的一个非常重要的实现子类,它的底层是通过动态数组实现的,因此它具备查询速度快, ...
- Java 集合系列05之 LinkedList详细介绍(源码解析)和使用示例
概要 前面,我们已经学习了ArrayList,并了解了fail-fast机制.这一章我们接着学习List的实现类——LinkedList.和学习ArrayList一样,接下来呢,我们先对Linked ...
- java集合系列之三(ArrayList)
上一章,我们学习了Collection的架构.这一章开始,我们对Collection的具体实现类进行讲解:首先,讲解List,而List中ArrayList又最为常用.因此,本章我们讲解ArrayLi ...
- 【Java集合系列】---ArrayList
开篇前言--ArrayList中的基本方法 前面的博文中,小编主要简单介绍java集合的总体架构,在接下来的博文中,小编将详细介绍里面的各个类,通过demo.对比,来对java集合类进行更加深入的理解 ...
- 【转】Java 集合系列05之 LinkedList详细介绍(源码解析)和使用示例
概要 前面,我们已经学习了ArrayList,并了解了fail-fast机制.这一章我们接着学习List的实现类——LinkedList.和学习ArrayList一样,接下来呢,我们先对Linked ...
- Java——集合框架之ArrayList,LinkedList,迭代器Iterator
概述--集合框架 Java语言的设计者对常用的数据结构和算法做了一些规范(接口)和实现(具体实现接口的类).所有抽象出来的数据结构和操作(算法)统称为Java集合框架(Java Collection ...
- Java集合系列[1]----ArrayList源码分析
本篇分析ArrayList的源码,在分析之前先跟大家谈一谈数组.数组可能是我们最早接触到的数据结构之一,它是在内存中划分出一块连续的地址空间用来进行元素的存储,由于它直接操作内存,所以数组的性能要比集 ...
随机推荐
- 你所忽略的,覆盖equals时需要注意的事项《effective java》
我们都知道Object的equals的比较其实就是==的比较,其实是内存中的存放地址的比较.正常逻辑上:类的每个实例本质上都是唯一的. 在工作中我们实际的业务逻辑往往有可能出现一些相对特殊的需求需要对 ...
- spring 5.x 系列第16篇 —— 整合dubbo (代码配置方式)
文章目录 一. 项目结构说明 二.项目依赖 三.公共模块(dubbo-ano-common) 四. 服务提供者(dubbo-ano-provider) 4.1 提供方配置 4.2 使用注解@Servi ...
- IDEA的参数配置
配置默认JDK 2.默认Project是没有JDK的,需要手动添加,然后才会有选项 关闭Intellij IDEA自动更新 文件编码设置 改快捷键,模板,注释,自动导包,创建web项目卡顿参数修改,代 ...
- WebService跨域配置、Ajax跨域请求、附开发过程源码
项目开发过程中需要和其他公司的数据对接,当时我们公司提供的是WebService,本地测试,都是好的,Ajax跨域请求,就报错,配置WebService过程中,花了不少功夫,入不少坑,不过最终问题还是 ...
- AbstractQueuedSynchronizer(AQS)源码解析
关于AQS的源码解析,本来是没有打算特意写一篇文章来介绍的.不过在写本学期课程作业中,有一门写了关于AQS的,而且也画了一些相关的图,所以直接拿过来分享一下,如有错误欢迎指正. ...
- charles使用说明(基于mac)
1. Charles简介 1.1 Charles 需要java的运行环境支持,支持Windows.Mac:Fiddler不支持Mac.故Charles是在Mac下常用的网络封包截取工具. 1.2 Ch ...
- 深入理解Java类加载
本文目的: 深入理解Java类加载机制; 理解各个类加载器特别是线程上下文加载器; Java虚拟机类加载机制 虚拟机把描述类的数据从 Class 文件加载到内存,并对数据进行校验.转换解析和初始化,最 ...
- pytest 安装和入门
安装pytest 1.在命令行中运行以下命令: pip install -U pytest 2.检查已经安装的版本: pytest --version This is pytest version 3 ...
- 使用Python脚本伪造指定时间区间的数据库备份
为监管需求,需要保留时间非常长的数据库备份.存储代价太大.所以存在了,临时抱佛脚,伪造备份.. 以下脚本功能,在于根据一个备份,复制出一段时间的备份.并且更改备份的文件时间戳.可以用shell轻松写出 ...
- 并发编程-concurrent指南-ReadWriteLock-ReentrantReadWriteLock(可重入读写锁)
几个线程都申请读锁,都能获取: import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantRea ...