Java中ArrayList和LinkedList区别

2021/5/19 22:26:07

本文主要是介绍Java中ArrayList和LinkedList区别,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

一、简述

ArrayList和Vector内部是使用可増长数组实现的,可以认为ArrayList或者Vector封装了对内部数组的操作,比如向数组中添加、删除、插入新的元素或者数据的扩展和重定向。所以使用get和set方法是花费常数时间的,但是如果插入或者删除元素,除非插入或者删除的位置都在表末尾,否则代码开销会很大,因为里面需要数组的移动。
LinkedList使用了循环双向链表数据结构,所以get会非常消耗资源,除非位置离头部很近。但是插入或者删除元素花费常数时间。与基于数组ArrayList相比,这是两种截然不同的实现技术,这也决定了它们将适用于完全不同的工作场景。

二、ArrayList和LinkedList的大致区别

  1. ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
  2. 对于随机访问get和set,ArrayList绝对优于LinkedList,因为LinkedList要移动指针。
  3. 对于新增和删除操作add和remove,LinkedList比较占优势,因为ArrayList要移动数据。
  4. ArrayList和linkedList都不是线程安全的。

三、数组和链表是数据结构中的概念,各有什么特点,在内存里是怎么分布的

1️⃣数组
定义一个数组,只需指定一个长度即可。然后就可以通过变量名+索引(或者说下标)的形式访问数组元素了,下标不能超过数组长度,否则就会发生索引越界异常。比如数组a,长度是10,那么第一个元素就是a[0],最后一个就是a[9]。想访问哪个元素只要指定下标就可以了。像这种可以随意访问任何元素的,有个专用名词叫做随机访问。

它在内存中是如何分布,才支持随机访问的?
其实数组在内存中是一段连续的空间,可以将它想象成一个梯子,一个格子紧挨着一个格子。数组名,也就是这个a,指向了这个空间的起始处地址,也就是数组的第一个元素的地址,所以其实和a[0]指向的是同一个地方。但a和a[0]的含义不一样,a表示内存地址,a[0]表示这个地址上存的元素。这里的下标0其实指的是相对于起始处地址的偏移量。0表示没有偏移,所以就是起始处地址的那个元素,也即第一个元素。

a[1]表示相对于起始处地址偏移量为1的那个元素,实际可以认为底层执行的是*(a + 1)。a+1表示从起始地址开始向后偏移1个之后的地址,那么*(星号)的意思就是取出那个地址上存储的元素。因为向后偏移了1个,其实就是第二个,所以a[1]叫取出数组的第二个元素。

因数组在内存中是一段连续的空间,所以不管访问哪个元素都是这两步,加上偏移量,然后取数据。这就是它支持随机访问的原因。说白了就是所有元素按顺序挨在了一起。也可以看出来,不管数组的长度是多长,访问元素的方式都是这两步,都在常量的时间内完成。所以按索引访问数组元素的时间复杂度就是O(1)。

ArrayList只不过是对数组的包装,因为数组在内存中分配时必须指定长度,且一旦分配好后便无法再增加长度,即不可能在原数组后面再接上一段的。ArrayList之所以可以一直往里添加,是因为它内部做了处理。当底层数组填满后,它会再分配一个更大的新的数组,把原数组里的元素拷贝过来,然后把原数组抛弃掉。使用新的数组作为底层数组来继续存储。

2️⃣链表
LinkedList也实现了List接口,也可以按索引访问元素,表面上用起来感觉差不多,但是其底层却有天壤之别。

与数组一下子分配好指定长度的空间备用不同,链表不会预先分配空间。而是在每次添加一个元素时临时专门为它自己分配一个空间。

因为内存空间的分配是由操作系统完成的,可以说每次分配的位置都是随机的,并没有确定的规律。所以说链表的每个元素都在完全不同的内存地址上,那我们该如何找到它们呢?

唯一的做法就是把每个元素的内存地址都要保存起来。怎么保存呢?那就让上一个元素除了存储具体的数据之外,也存储一份下一个元素在内存中的地址。整个就像前后按顺序依次相连的一条链,我们只要保存第一个元素的内存地址,就可以顺藤摸瓜找到所有的元素。

这其实就像一个挖宝藏游戏,假设共10步,告诉你第一步去哪里挖。然后挖出一个字条,上面写着第二步去哪里挖。依次这样挖下去。第九步挖出字条后才知道宝藏的位置,然后第十步就把它挖出来了。可见为了得到宝藏必须这样一步一步挖下去。中间的任何一步都不能跳过,因为第十步宝藏的位置在第九步里放着呢,第九步的位置在第八步里放着呢,依次倒着下来就到了第一步的位置,而第一步的位置已经告诉你了。

数组是康庄大道、四平八稳,仿佛回家,轻车熟路。链表是曲径通幽、人迹罕至,犹如探险,步步为营。

可见按索引访问链表元素时,必须从头一个个遍历,而且链表越长,位置越靠后,所需花费的时间就越长。所以按索引访问链表元素的时间复杂度就是O(n),n为链表的长度。也说明了链表不支持随机访问。所以ArrayList就实现了RandomAccess(随机访问)接口,而LinkedList就没有。

#四、new ArrayList(20)扩容几次

public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
    }
}

从 JDK8 源码来看,这种指定数组大小的创建,创建时直接分配大小,没有扩充。

总结:
1️⃣在 JDK7 中,如果通过无参构造的话,初始数组容量为0,当真正对数组进行添加时,才真正分配容量。每次按照1.5倍(位运算)的比率通过copeOf的方式扩容。

举例说明:添加 20 个元素到 ArrayList 中。当第一次插入元素时才分配 10(默认)个对象空间,之后扩容会按照1.5倍增长。也就是当添加第 11 个数据的时候,Arraylist继续扩容变为 10*1.5=15;当添加第 16 个数据时,继续扩容变为 15 * 1.5 =22个。

2️⃣在 JKD6 中,如果通过无参构造的话,初始数组容量为 10。每次通过 copeOf 的方式扩容后容量为原来的 1.5 倍。



这篇关于Java中ArrayList和LinkedList区别的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程