面试的时候,经常会有面试官提问Collection接口下面实现类的区别,以及各自适用场景,不少人都能答出来:ArrayList基于数组实现,善于随机访问和遍历集合,做随机位置插入和删除性能较差,而ArrayList基于链表结构实现(双向链表),在做随机位置插入时性能较好于ArrayList,在做遍历和随机访问时性能较差。但很多人不知道到底为什么基于数组实现随机访问就快?任意位置插入性能就差?而基于链表实现的正好相反呢?
要弄清楚这个问题,必须要清楚其实现方式,最好的办法就是去看源码,这是一种好的
学习习惯。
从上面图可以看到List接口下的所有实现类和父接口,其中当然包括本文介绍的主角儿ArrayList,和LinkedList,既然是实现自同一接口,那么他们的规范自然是一样的。
如上图,因为源码注释太多,看着烦躁,这里我用Ctrl+o可以看到List接口下面的所有方法,接下来,我们来看看他们各自的实现的代码吧!
首先我们看ArrayList,在类中有个Object[] elementData,可以想到他是用来存放Arraylist中的数据的。
那么,它是怎样对数据进行增删改查操作的?
1
2
3
4
5
|
public boolean add(E e) {
ensureCapacityInternal(size + 1 ); // Increments modCount!!
elementData[size++] = e;
return true ;
} |
这是add方法,主要是将e赋值给数组,将数组长度加一,ensureCapacityInternal里面主要是做一些逻辑判断,请看grow方法
1
2
3
4
5
6
7
8
9
10
11
|
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);
}
|
Arrays.copyOf(elementData, newCapacity);这行代码非常关键,他是影响ArrayList插入性能的主要因素,当数组下表超过数组长度时会调用这个方法,调用Arrays.copyOf的方法时会重新copy构建一个新的数组,会消耗大量的资源,导致性能变的很差。
下面看看随机插入add(int index, E element)
1
2
3
4
5
6
7
8
9
|
public void add( int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1 ); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1 ,
size - index);
elementData[index] = element;
size++;
} |
此处更是不消多说,就是copy copy copy,大量的数组移动,导致性能底下。remove方法与add方法实现类似.
再来看看随机访问和遍历,get方法非常简单
1
2
3
4
|
public E get( int index) {
rangeCheck(index);
return elementData(index);
} |
直接指向数组下标位置的元素返回,当然是最快的,遍历亦是如此。
再来看看LinkedList的内部实现吧
内部有两个Node对象,一个指向集合第一个,一个指向最后一个,Node内部代码如下
1
2
3
4
5
6
7
8
9
10
11
|
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;
}
} |
每一个Node都有next和prev两个对象,相互关联这就形成了一条链子,所以这个叫做链表结构(双向),用网上的一个图片。
再来看看他们实现add和remove的方法吧!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public boolean add(E e) {
linkLast(e);
return true ;
} void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null );
last = newNode;
if (l == null )
first = newNode;
else
l.next = newNode;
size++;
modCount++;
} |
从上面代码不难发现,普通的add会调用linkLast,linkdLast方法内部会新建一个Node对象,将要存储的值放入node中,同时把当前的node绑定到集合最末端,时间复杂度为O(n).
再看指定位置的插入,add(int index, E element)方法。
1
2
3
4
5
6
7
8
|
public void add( int index, E element) {
checkPositionIndex(index);
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
} |
当需要插入的位置为集合大小时,可以直接插入集合尾部,否则,则需要找到指定位置的下标然后修改他们next、prev的指向,这里会有一个遍历,JDK对其做了优化,不同位置从两端遍历,这样让遍历的查找时间降到最低。代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
Node<E> node( int index) {
// assert isElementIndex(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;
}
}
|
remove与add逻辑类似。
总结一下,ArrayList得其天赋是数组,数组在随机访问时的速度是与生俱来的,而其软肋就是数组是固定的,大小固定,位置固定,所以你做新增(特别是中间段的新增或者是长度已达目前初始大小时),删除会频繁移动数组,导致性能底下。
而在LinkedList里面,由于他使用双向链表结构实现,那么新增和删除,只需要修改相应的指向就行了,在中间位置插入会比在末尾插入性能要差点,在随机访问时,由于需要遍历,所以速度逊于ArrayList,在做遍历的时候也是一样,使用迭代器可以缓解这个问题。
值得一提的时,假设我需要一个长度为固定数量S的集合,默认向里面add数据,给ArrayList赋予初始初始容量S,那么ArrayList插入的性能会优于LinkedList,请看测试代码和运行结果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
public static void main(String[] args) {
int size= 10000000 ;
testArraylistAdd(size);
testLikedlistAdd(size);
}
static void testArraylistAdd( int size) {
long t1 = System.currentTimeMillis();
List<Integer> list = new ArrayList<Integer>(size);
for ( int i = 0 ; i < size; i++) {
list.add(i);
}
long t2 = System.currentTimeMillis();
System.out.println( "测试" + size + "条数据,插入ArrayList所花费时间 为:" + (t2 - t1));
}
static void testLikedlistAdd( int size) {
long t1 = System.currentTimeMillis();
List<Integer> list = new LinkedList<Integer>();
for ( int i = 0 ; i < size; i++) {
list.add(i);
}
long t2 = System.currentTimeMillis();
System.out.println( "测试" + size + "条数据,插入LinkedList所花费时间 为:" + (t2 - t1));
}
|
测试10000000条数据,插入ArrayList所花费时间 为:78
测试10000000条数据,插入LinkedList所花费时间 为:4738
主要是因为初始时指定了数组长度,插入时不需要频繁移动数组,所以速度才会优于LinkedList
相关推荐
总的来说,理解ArrayList和LinkedList的基本特性和应用场景,以及如何处理与之相关的安全性问题,是Java程序员必备的知识。通过深入学习和实践,可以更好地利用这些数据结构提升程序效率和质量。
ArrayList、LinkedList、Vector 是 Java 中常用的数据结构实现类,它们都实现了 List 接口,但它们在存储方式、性能、线程安全性等方面有着不同特点。 首先,ArrayList 和 Vector 都是采用数组方式存储数据的,这种...
在 Java 中,ArrayList 和 LinkedList 是两种常用的集合类,它们各自具有不同的特性和适用场景,主要体现在数据结构、访问效率和操作性能上。 1. 数据结构: - ArrayList 实现了一个动态数组,它内部是一个 Object...
测试ArrayList和LinkedList的add方法
在Java编程语言中,`ArrayList`与`LinkedList`都是`List`接口的具体实现类,用于存储元素集合。虽然它们都实现了同样的接口并且提供了相同的基本功能,但在内部实现机制、性能特点以及适用场景等方面存在显著差异。 ...
【Java面试题】ArrayList和LinkedList区别
在Java的集合框架中,ArrayList和LinkedList是两种常用的列表实现,它们都实现了List接口,但它们在内存管理和操作效率上存在显著差异。了解这些差异并根据具体应用场景选择合适的列表类型,能够有效提升J2EE应用...
总结:ArrayList 和 LinkedList 是 Java 中两个常用的集合类,它们的底层实现方式不同,ArrayList 是基于数组实现的,而 LinkedList 是基于链表实现的。理解它们的底层实现方式可以帮助我们更好地使用它们。
在Java编程语言中,集合框架是处理数据的重要组成部分。ArrayList、LinkedList和Vector是三种常见的动态数组实现,...在深入源码阅读和实践过程中,我们可以更深入地理解这些类的设计思想和实现方式,提升编程技能。
2,随机访问的时候,ArrayList的效率比较高,因为LinkedList要移动指针,而ArrayList是基于 3,索引(index)的数据结构,可以直接映射到。 4,插入、删除数据时,LinkedList的效率比较高,因为ArrayList要移动数据。 ...
- **实现原理**:Vector与ArrayList类似,也是基于动态数组,但它是线程安全的。 - **线程安全**:每个公共方法都在内部进行了同步,这意味着在多线程环境下无需额外的同步代码。 - **效率**:由于线程安全的实现...
在Java编程语言中,ArrayList、LinkedList和Vector是三种常见的动态数组实现,它们都在java.util包中,用于存储和管理对象的集合。这三个类都实现了List接口,提供了多种操作方法,但它们在内部实现和性能特性上有所...
05丨ArrayList还是LinkedList?使用不当性能差千倍.html
ArrayList和LinkedList是Java集合框架中两种重要的动态数组实现,它们都是List接口的实现类,但它们在存储和操作数据方面有着显著的区别。本文件"arraylist-linkedlist-test.zip"主要探讨了在执行添加和删除元素操作...
### ArrayList、Vector、LinkedList 的区别与用法详解 在Java编程中,选择合适的数据结构对于程序的性能至关重要。本文将深入探讨ArrayList、Vector和LinkedList三种集合类的特点与使用场景,帮助开发者更好地理解...
《ArrayList与LinkedList源码解析》 在Java编程中,ArrayList和LinkedList是两种常见的动态数组,它们都是Java集合框架的一部分,提供了对元素存储和操作的功能。本篇将深入探讨ArrayList和LinkedList的内部实现...
在Java编程语言中,ArrayList和LinkedList都是集合框架中两种重要的列表实现,它们分别基于不同的数据结构,具有不同的特性和性能特点。以下是对这两个类的详细分析: 1. 数据结构: - ArrayList是基于动态数组的...
ArrayList与LinkedList性能比较在java中 ArrayList和LinkedList是java中两个常用的实现List接口的类,它们之间的性能比较是一个非常重要的知识点。 首先,让我们来了解ArrayList和LinkedList的实现原理。ArrayList...
在 Java 中,ArrayList 和 LinkedList 是两个常用的集合实现方式,它们都实现了 Collection 接口,但是它们之间存在着一些关键的差异。本文将通过实例对比 Java 中 ArrayList 和 LinkedList 的实现机制、性能差异、...
ArrayList和LinkedList的比较与实现原理 在 Java 中,ArrayList 和 LinkedList 是两个常用的集合类,它们都是 List 接口的实现类,但它们之间有着鲜明的区别。今天,我们将深入探讨这两个集合类的实现原理和比较。 ...