Java中ArrayList和LinkedList区别
作者:码农洞见? 发布时间:2023-09-06 20:43:09
1 前言
许多语言,例如 Perl ,Python 和 Ruby ,都有集合的本地支持。有些语言(例如Python)甚至将基本集合组件(列表,映射和集合)作为内置函数包含在其中。
通常,程序总是根据运行时才知道的某些条件去创建新的对象。在此之前,无法知道所需对象的数量甚至确切类型。为了解决这个普遍的编程问题,需要在任意时刻和任意位置创建任意数量的对象。java.util
库提供了一套相当完整的集合类(collection classes)来解决这个问题,其中基本的类型有 List 、 Set 、 Queue 和 Map。这些类型也被称作容器类。
2 数据结构的区别
2.1 ArrayList
ArrayList
是基于数组实现的,数组是典型的紧密结构,所以它使用索引在数组中搜索和读取数据快,可以直接返回数组中index位置的元素,因此在随机访问集合元素上有较好的性能。
2.2 LinkedList
LinkedList是基于双链表实现的,链表是典型的跳转结构,插入和删除只是指针指向的修改,所以它插入、删除中间元素快,因此在操作数据方面性能较好。
2.3 使用场景
如果应用程序对数据有较多的随机访问,ArrayList
对象要优于LinkedList
对象;
如果应用程序有更多的插入或者删除操作,较少的随机访问,LinkedList对象要优于ArrayList对象;
3 源码分析
下面我们通过JDK1.8源码源码分析一下核心功能。
3.1 ArrayList核心源码
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
//默认大小
private static final int DEFAULT_CAPACITY = 10;
//省略。。。
//动态数组
transient Object[] elementData;
//数组大小
private int size;
//空构造器
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 查询元素
public E get(int index) {
// 检查索引是否在范围内
rangeCheck(index);
return elementData(index);
}
//顺序添加元素
public boolean add(E e) {
//扩容
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
//从数组中间添加元素
public void add(int index, E element) {
// 检查索引是否在范围内
rangeCheckForAdd(index);
// 扩容
ensureCapacityInternal(size + 1);
// 复制数组
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
// 替换元素
elementData[index] = element;
size++;
}
//是否要扩容
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//确定容量
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//计算数组容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
//动态数组扩容放法
private void grow(int minCapacity) {
//
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 数组复制
elementData = Arrays.copyOf(elementData, newCapacity);
}
//省略。。。
}
总结:
ArrayLis
初始化构造器的时候数组为{},在调用add方法以后默认数组才赋值为新数组,新数组默认长度为10。
通过扩容机制判断原数组是否还有空间,若没有则重新实例化一个空间更大的新数组,把旧数组的数据拷贝到新数组中。
在执行查询操作时,先判断下标是否越界,然后在直接通过下标从数组中返回元素。
3.2 LinkedList核心源码
//JDK1.8
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
// 元素数量
transient int size = 0;
// 链表首节点
transient Node<E> first;
// 链表尾节点
transient Node<E> last;
// 空构造器
public LinkedList() {
}
// Node内部类
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
// 中间元素
this.item = element;
// 下一个元素地址
this.next = next;
// 上一个元素地址
this.prev = prev;
}
}
// 顺序添加元素
public boolean add(E e) {
linkLast(e);
return true;
}
// 指定位置添加元素
public void add(int index, E element) {
// 检查是否越界
checkPositionIndex(index);
// 在链表末尾添加,否则在之前插入元素
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
}
// 添加元素e
void linkLast(E e) {
//把链表中last元素赋给l ,如果是第一个元素则为null
final Node<E> l = last;
//把元素封装为一个Node对象
final Node<E> newNode = new Node<>(l, e, null);
//把链表的last元素指向新的元素
last = newNode;
//如果添加的是第一个元素
if (l == null){
//把链表的first元素指向新的元素
first = newNode;
}
//如果添加的不是第一个元素
else{
//把l的下一个元素指向新的元素
l.next = newNode;
}
//集合中元素数量加1
size++;
modCount++;
}
void linkBefore(E e, Node<E> succ) {
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
//获取元素
public E get(int index) {
//检测元素索引
checkElementIndex(index);
return node(index).item;
}
//返回指定元素索引处的(非空)节点
Node<E> node(int index) {
//把链表分为两段,如果index在链表的前段,则从前往后找,否则反之
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
//省略。。。
}
总结:
LinkedList
的get首先判断需要获取的数据在该链表的前半段还是后半段,这样可以减少需要遍历的数据,由于需要遍历数据,所以获取数据的速度会比ArrayList
慢。
由于LinkedList底层是用双向链表实现的,没有初始化大小,也没有扩容的机制。
4 码农来洞见
4.1为什么ArrayList比LinkedList要快
ArrayList
和LinkedList
本质上每次插入和删除元素都会进行复制数组的操作,如果ArrayList插入操作没有触发数组扩容操作,并且在List靠近末尾的地方插入,那么ArrayList
只需要移动较少的数据,而LinkedList则需要一直查找到列表尾部,反而耗费较多时间,这时ArrayList就比LinkedList要快。
4.2 注意ArrayList不同JDK版本源码
JDK1.7中在调用构造器的时候数组长度就初始化为了10,JDK1.8则是在调用add方法后才创建数组长度为10的新数组替换默认空数组。
4.3 高并发下如何保证集合数据的同步
ArrayList
和LinkedList
都不是线程安全的。然而Vector类被认为是过时的废弃的了。
方案一: Collections.synchronizedList(); 得到一个线程安全的 ArrayList。
方案二: concurrent 并发包下的 CopyOnWriteArrayList 类。
CopyOnWriteArrayList和Collections.synchronizedList是实现线程安全的列表的两种方式。两种实现方式分别针对不同情况有不同的性能表现,其中CopyOnWriteArrayList的写操作性能较差,而多线程的读操作性能较好。而Collections.synchronizedList的写操作性能比CopyOnWriteArrayList在多线程操作的情况下要好很多,而读操作因为是采用了synchronized关键字的方式,其读操作性能并不如CopyOnWriteArrayList。因此在不同的应用场景下,应该选择不同的多线程安全实现类。
4.4 为什么Java的Vector类被认为是过时的或者废弃的
Vector中对每一个独立操作都实现了同步,这通常不是我们想要的做法。对单一操作实现同步通常不是线程安全的(举个例子,比如你想遍历一个Vector实例。你仍然需要申明一个锁来防止其他线程在同一时刻修改这个Vector实例。如果不添加锁的话
通常会在遍历实例的这个线程中导致一个ConcurrentModificationException)同时这个操作也是十分慢的(在创建了一个锁就已经足够的前提下,为什么还需要重复的创建锁)
当然,即使你不需要同步,Vector也是有锁的资源开销的。
来源:https://blog.csdn.net/pangpengshuai/article/details/121276121


猜你喜欢
- 线程可以划分优先级,优先级高的线程得到的CPU资源比较多,也就是CPU优先执行优先级高的线程对象中的任务。设置线程优先级有助于帮助线程规划器
- 哈希 Hash 算法介绍哈希算法也叫散列算法, 不过英文单词都是 Hash, 简单一句话概括, 就是可以把任意长度的输入信息通过算法变换成固
- 本文实例讲述了Java实现文件和base64流的相互转换功能。分享给大家供大家参考,具体如下:import java.io.FileInpu
- 1.SpringCloud是什么以前的服务器就像是一个医院只有一个医生,什么病人都要让这个医生看,如果医生觉得太累,自我暴毙了,那整个医院都
- 定义:/** * @author Administrator * @project: TestOne * @package: PACKAGE
- 问题:1.线程 wait()方法使用有什么前提?2. 多线程之间如何进行通信?3. Java 中 notify 和 notifyAll 有什
- 1.将本地jar包放入本地仓库。只需执行如下命令即可:mvn install:install-file -Dfile=D:/demo/fib
- 接着上一篇进行学习java文件上传下载1。五、断点续传 对于熟用QQ的程序员,QQ的断点续传功能应该是印象很深刻的。因为它很实用也
- 本文实例为大家分享了Android微信摇一摇功能的实现方法,供大家参考,具体内容如下import java.util.ArrayList;
- 本文实例讲述了Android使用selector修改TextView中字体颜色和背景色的方法。分享给大家供大家参考,具体如下:android
- 先来看看效果图跳动的小球做这个动画,需掌握: 1、属性动画  
- 下面以launch方法为例进行分析。一.协程的创建launch方法的代码如下:// CoroutineScope的扩展方法public fu
- 在 Windows 窗体应用程序中显示图片时要使用图片控件 ( PictureBox ),图片的设置方式与背景图片的设置方式相似。图片控件中
- C# 中有三种定时器,System.Windows.Forms 中的定时器和 System.Timers.Timer 的工作方式是完全一样的
- 最近有很多小伙伴给我留言,分布式系统时代,线程并发,资源抢占,"锁" 慢慢变得很重要。那么常见的锁都有哪些?今天Tom哥
- 本文实例为大家分享了javaweb多文件上传及zip打包下载的具体代码,供大家参考,具体内容如下项目中经常会使用到文件上传及下载的功能。本篇
- TCP/IP、UDP、Socket对TCP/IP、UDP、Socket编程这些词你不会很陌生吧?随着网络技术的发展,这些词充斥着我们的耳朵。
- 本文实例讲述了C#使用Ado.Net更新和添加数据到Excel表格的方法。分享给大家供大家参考。具体分析如下:微软NET提供了一个交互的方法
- /* String name = "adsbsadgsadgtewterfsdf"
- @Valid:@Valid注解用于校验,所属包为:javax.validation.Valid。① 首先需要在实体类的相应字段上添加用于充当