Java中ArrayList和LinkedList区别

网友投稿 239 2022-09-03


Java中ArrayList和LinkedList区别

目录1 前言2 数据结构的区别2.1 ArrayList2.2 LinkedList2.3 使用场景3 源码分析3.1 ArrayList核心源码3.2 LinkedList核心源码4 码农来洞见4.1为什么ArrayList比LinkedList要快4.2 注意ArrayList不同JDK版本源码4.3 高并发下如何保证集合数据的同步4.4 为什么java的Vector类被认为是过时的或者废弃的

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 extends AbstractList

implements List, 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);

}

//省略。。。

}

总结:

Arhttp://rayLis初始化构造器的时候数组为{},在调用add方法以后默认数组才赋值为http://新数组,新数组默认长度为10。通过扩容机制判断原数组是否还有空间,若没有则重新实例化一个空间更大的新数组,把旧数组的数据拷贝到新数组中。在执行查询操作时,先判断下标是否越界,然后在直接通过下标从数组中返回元素。

3.2 LinkedList核心源码

//JDK1.8

public class LinkedList

extends AbstractSequentialList<E>

implements List, Deque, Cloneable, java.io.Serializable

{

// 元素数量

transient int size = 0;

// 链表首节点

transient Node first;

// 链表尾节点

transient Node last;

// 空构造器

public LinkedList() {

}

// Node内部类

private static class Node {

E item;

Node next;

Node prev;

Node(Node prev, E element, Node 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 l = last;

//把元素封装为一个Node对象

final Node 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 succ) {

final Node pred = succ.prev;

final Node 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 node(int index) {

//把链表分为两段,如果index在链表的前段,则从前往后找,否则反之

if (index < (size >> 1)) {

Node x = first;

for (int i = 0; i < index; i++)

x = x.next;

return x;

} else {

Node 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也是有锁的资源开销的。


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Python:五种方法实现“字符串反转”(用python将字符串进行反转)
下一篇:一道Python题引发的,一个知识点的探讨:删除列表中特定元素的几种方法
相关文章

 发表评论

暂时没有评论,来抢沙发吧~