一、背景

面试题中经常会被面试官问到ArrayList和LinkedList的区别,下面从源码角度来对他们进行一下简单的阐述,相信会对它们有一个更全面深入的了解。

首先,ArrayList和LinkedList都实现了List接口,ArrayList的底层是通过【动态数组】实现的,LinkedList底层是通过【链表】实现的。

二、ArrayList

1、通过add(e)方法添加元素
java中的数组一旦定义之后长度length就不可变了,是不可变数组;而python是可变数组,这点需要注意这两种语言的不同;ArrayList可以不断的通过add添加元素,它的size也是变化的,数组的长度又是不可变的,而ArrayList的底层是数组,它们不就矛盾了吗?别急,ArrayList是通过判断当前集合中元素的size数与数组的长度比较,如果size>数组length,再对数组扩容,然后将元素赋值给扩容后的数组
下面是截取的ArrayList类中的关于add方法的代码

private static final int DEFAULT_CAPACITY = 10;  //定义一个int常量,值为10
private static final Object[] EMPTY_ELEMENTDATA = {}; //定义一个空数组常量,值为{}
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//定义一个默认数组常量,值为{}
transient Object[] elementData; //定义一个数组
private int size;  //定义size/**
* 无参构造方法,new一个ArrayList对象后,实际上也创建并初始化了一个elementData的数组,且
* 这个数组为空数组{},length长度为0
*/
public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}/**
* 执行add(e)方法添加元素,可以看到先调用ensureCapacityInternal方法,再对
* elementData数组进行赋值。
*/
public boolean add(E e) {ensureCapacityInternal(size + 1);  // Increments modCount!!elementData[size++] = e;return true;
}/**
* ensureCapacityInternal方法传入的minCapacity形参对应的实参值=size+1,它下面再去调用其他
* 方法,我们一层层深入,抽丝剥茧到后面的方法
*/
private void ensureCapacityInternal(int minCapacity) {ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}// calculateCapacity方法根据传入的数组是否为空和minCapacity参数来确认并返回int
private static int calculateCapacity(Object[] elementData, int minCapacity) {// arrayList第一次添加元素e时,size=0,minCapacity=1,调用此代码,return返回10if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {return Math.max(DEFAULT_CAPACITY, minCapacity);}// arrayList 第二次及以后添加元素e,会直接执行此处代码,并return (size+1)>=2return minCapacity;
}/**
* 上面calculateCapacity方法返回值作为实参传递给下面的ensureExplicitCapacity方法的形参并进* 行判断处理。
*/
private void ensureExplicitCapacity(int minCapacity) {modCount++;/*** 第1种情况:第一次添加元素e时,minCapacity=10,数组长度length=0* 第2种情况:第二次及以后添加e,minCapacity=size+1>=2* minCapacity与数组elementData长度进行比较,如果前者大于数组长度,则进行数组扩容,执行* grow方法。* 当属于第1种情况时,数组长度=0,我们无法添加元素到数组中,所以需要执行grow()方法扩容,扩容* 的本质就是执行Arrays.copyOf()方法,得到长度为10的数组,然后再给数组赋值;* 当属于第2种情况时,数组长度已经进行了第一次扩容,length=10,当添加第二个元素e时,* minCapacity=2 < elementData.length=10,也就是说数组容量足够,可以直接添加元素,不必* 扩容*/if (minCapacity - elementData.length > 0)grow(minCapacity);
}// 下面的grow方法就是扩容的核心代码
private void grow(int minCapacity) {// overflow-conscious codeint 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);
}

总结一下:通过add(e)方法在集合尾部添加数据,效率还是比较高的,因为不涉及数组元素的复制移动,但有时涉及到扩容
2、通过get(index)根据索引获取元素对象
下面是ArrayList类中的关于get(index)方法的代码

public E get(int index) {// 校验索引index是否越界rangeCheck(index);// 调用elementData方法return elementData(index);}// 返回index下标对应的数组元素,不需要遍历
E elementData(int index) {return (E) elementData[index];}

总结一下:get(index)不需要遍历,直接取出相应下标的数组元素,效率较高
3、通过add(index,e)指定位置添加元素
下面是ArrayList类中的关于add(index,e)方法的源代码

public void add(int index, E element) {// 检验index有效性rangeCheckForAdd(index);// 根据传参size+1,判断是否扩容ensureCapacityInternal(size + 1);  // Increments modCount!!// 数组复制,指定index对应的位置空出System.arraycopy(elementData, index, elementData, index + 1,size - index);// 数组index下标赋值插入的数据elementData[index] = element;size++;}

原理图如下:

总结一下:add(index,e)方法涉及到元素的整体复制向后移动,元素下标也会发生变化,此种方式添加元素效率较低,数组容量不足,也会进行扩容

4、remove(index)删除元素源码

public E remove(int index) {// 检查下标的有效性rangeCheck(index);modCount++;// 获取被删除元素value值E oldValue = elementData(index);// 被删元素后面需要被移动的元素个数int numMoved = size - index - 1;if (numMoved > 0)// 数组复制并向前移动1位System.arraycopy(elementData, index+1, elementData, index,numMoved);elementData[--size] = null; // clear to let GC do its workreturn oldValue;}

总结一下:remove(index)删除元素,会导致剩下的元素整体复制移动,元素下标会发生变化,因此该方式删除元素数据效率较低

二、LinkedList

1、通过add(e)方法添加元素
以下是LinkedList类中部分源码。

 ......省略......// 声明容量sizetransient int size = 0;// 声明首节点transient Node<E> first;// 声明尾节点transient Node<E> last;// 无参构造方法,执行后size=0;first=null;last=nullpublic LinkedList() {}// add方法添加元素public boolean add(E e) {// 调用的核心方法linkLast(e);return true;}void linkLast(E e) {// 添加元素之前,先将当前LinkedList对象的尾部节点赋给l对象final Node<E> l = last;// 创建一个新Node对象:上一个节点指向l,下一个节点指向null,元素E对象为efinal Node<E> newNode = new Node<>(l, e, null);// 将newNode新节点赋给last对象last = newNode;// 如果当前LinkedList对象没有尾节点,即l==null,说明LinkedList对象中没有节点元素。if (l == null)// 这种情况下,将新Node节点赋给首节点first = newNode;else// 如果LinkedList对象中已经存在尾节点,则将该尾节点的下一个节点指向新添加的节点l.next = newNode;// size数加1size++;modCount++;}// 定义私有内部静态Node类:Node对象有三个属性->元素E、指向上一个节点prev、指向下一个节点nextprivate 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;}}

LinkedList对象结构示意图:

总结一下:LinkedList对象中通过add()方法添加元素时,对已经存在的元素没有影响,没有对其他元素的复制移动等操作,效率高

2、通过add(index,e)添加元素到指定位置
以下是相关的源码,关键代码做了注释。

 public void add(int index, E element) {// 校验index是否越界checkPositionIndex(index);// index == size时,直接在最后添加,方法同上add(e)if (index == size)linkLast(element);else// 在集合首尾之间指定index处添加元素场景下的核心方法linkBefore(element, node(index));}// add(index,e)的底层核心方法void linkBefore(E e, Node<E> succ) {// 获取指定index处Node对象的上一个节点对象predfinal Node<E> pred = succ.prev;// new一个新节点对象,指定它的上一个节点对象是pred,下一个节点对象是succfinal Node<E> newNode = new Node<>(pred, e, succ);// 重新设置指定index索引Node对象的上一个节点对象为newNodesucc.prev = newNode;// 判断指定index处Node对象的上一个节点对象pred是否为空if (pred == null)// 如果为空,则指定newNode节点为首节点first = newNode;else// 如果不为空,则将指定indexNode对象的上一个节点对象的pred的下一个节点对象设置为newNodepred.next = newNode;size++;modCount++;}// 该方法返回指定index处的Node节点对象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;}}

总结一下:add(index,e)方法添加元素到集合中的指定位置,只是改变了上一个节点和下一个节点的指向位置,其他元素不受影响,所以比ArrayList的add(index,e)的效率要高,但需注意在查找index节点时,进行了遍历,如果size比较大的话,遍历会比较耗时

3、通过remove(index)的方法删除元素。
以下是LinkedList中相关源码,关键代码做了注释

 public E remove(int index) {// 检查index索引是否合法checkElementIndex(index);// 调用node和unlink方法return unlink(node(index));}/*** Returns the (non-null) Node at the specified element index.* 返回指定index索引处的Node<E>对象*/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;}}E unlink(Node<E> x) {// 获取index处Node对象的元素efinal E element = x.item;// 获取index处Node对象的下一个节点Node对象nextfinal Node<E> next = x.next;// 获取index处Node对象的上一个节点Node对象prevfinal Node<E> prev = x.prev;// 若index节点指向的上一个节点为null,说明index是首节点,删除之后,将next节点赋值给首节点if (prev == null) {first = next;} else {// 若index节点指向的上一个节点prev不为null,说明被删节点不是首节点,此时将prev的下一个节点指向next节点prev.next = next;// index节点指向的上一个节点赋null值,代表取消它的指向关系。x.prev = null;}// 若index节点指向的下一个节点next为null,说明被删节点是尾节点,将prev节点赋值给尾节点if (next == null) {last = prev;} else {// 若next不为null,说明被删节点不是尾节点,此时将next节点的上一个节点指向prev节点next.prev = prev;// index节点指向的下一个节点赋值为null值,代表取消它的指向关系x.next = null;}// index节点对应的元素Element赋值为null,结合上面的x.prev=null/x.next=null,代表该节点已经被删除了。x.item = null;size--;modCount++;return element;}

总结一下:remove(index)方法删除集合中的元素只是改变了上一个节点和下一个节点的指向位置,对其他元素没有造成影响,效率比较高,但需注意在查找index节点时,进行了遍历,如果size比较大的话,遍历会比较耗时

4、通过get(index)获取集合中的元素
以下是LinkedList中的部分源码,关键部分做了注释。

 public E get(int index) {// 检查index索引合法性,是否越界。checkElementIndex(index);// 调用node方法return node(index).item;}/*** Returns the (non-null) Node at the specified element index.* 此方法根据传入的index索引,进行遍历查询得到相应的元素*/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;}}

总结一下:LinkedList的get(index)方法通过遍历查询元素,效率比较低;而ArrayList中的get(index)直接通过下标获取数组元素,不用遍历,效率更高。

java中ArrayList与LinkedList的区别相关推荐

  1. java中ArrayList和LinkedList的区别

    首先来看ArrayList和LinkedList的集成类和接口的区别.// lang java public class ArrayList<E> extends AbstractList ...

  2. Java中ArrayList和LinkedList区别

    一般大家都知道ArrayList和LinkedList的大致区别:       1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构.       2.对于随机 ...

  3. Java中ArrayList和LinkedList区别 时间复杂度 与空间复杂度

    一般大家都知道ArrayList和LinkedList的大致区别:       1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构.       2.对于随机 ...

  4. Java基础 ArrayList和LinkedList的区别和实现原理

    ArrayList 和 LinkedList都是List的实现类,List集合主要有两个特点:1.有序:2.可重复.所以他们两个肯定也有其特征. 下面分别介绍下二者:  1.ArrayList---  ...

  5. Java中ArrayList和Vector的区别

    首先想说的是: Vector是在Collections API之前就已经产生了的, 而ArrayList是在JDK1.2的时候才作为Collection framework的一部分引入的. 它们都是在 ...

  6. java c 性能比较_java 中ArrayList与LinkedList性能比较

    java 中ArrayList与LinkedList性能比较 今天看一框架的代码,看到有些 可以使用ArrayList的地方 使用的是 LinkedList,用到的情景是在一个循环里面进行顺序的插入操 ...

  7. java的list和数组谁高效_java 中ArrayList与LinkedList性能比较

    java 中ArrayList与LinkedList性能比较 今天看一框架的代码,看到有些 可以使用ArrayList的地方 使用的是 LinkedList,用到的情景是在一个循环里面进行顺序的插入操 ...

  8. java集合框架05——ArrayList和LinkedList的区别

    前面已经学习完了List部分的源码,主要是ArrayList和LinkedList两部分内容,这一节主要总结下List部分的内容. List概括 先来回顾一下List在Collection中的的框架图 ...

  9. Java集合系列---List源码解析(ArrayList和LinkedList的区别)

    List源码主要讲ArrayList,LinkedList,Vector三个类 1 ArrayList ArrayList是一个底层基于数组的集合, 首先来看一下它的继承关系, public clas ...

最新文章

  1. 网易云信全面技术支持,让“子弹短信”飞得更快
  2. 挂在windows2003下的硬盘分区文件系统被系统识别为RAW,如何恢复至NTFS
  3. CobaltStrike的使用
  4. 【dp 贪心】bzoj4391: [Usaco2015 dec]High Card Low Card
  5. 广东计算机考试1级时间安排,1级计算机考试时间
  6. sql server中截取字符串的常用函数(自己经常到用的时候想不起来所以拿到这里)...
  7. 在eclipse中创建web项目
  8. 【前端学习】HTML入门
  9. Qt开发的超轻量http server
  10. Wet-Ra: Monitoring Diapers Wetness with Wireless Signals
  11. PHP工程师是个让我很心疼的职业
  12. 聊聊directory traversal attack
  13. layui下载图片到本地
  14. 深信服 VDS设备烤机
  15. mac 壁纸 android,可以用于任何设备的Android 12壁纸现在已可下载
  16. html多行注释如何实现,javascript多行注释如何实现
  17. 中国卫生材料及医药用品行业发展前景与投资战略规划分析报告2022-2028年
  18. CleanMyMac闪退怎么办?解决CleanMyMac X闪退
  19. 做本让客户念念不忘的产品手册
  20. 阿里php开发规范,阿里巴巴java开发手册学习记录,php版

热门文章

  1. AIX系统CPU性能评估-1
  2. 为什么添加Web引用后,客户端就能远程调用WebService了?
  3. laravel CURD ORM
  4. SDWAN动态路径选择是什么?SDWAN成本降低的意义是什么?
  5. 实例讲解sed的9种常见用法
  6. S/4HANA生产订单增强WORKORDER_UPDATE方法BEFORE_UPDATE参数分析
  7. 牛客网NOIP赛前集训营-提高组(第六场)B-选择题
  8. 课堂练习---统计空格流程图、Jackson图
  9. 第八周课上额外项目:pwd的实现
  10. 初识WebSocket