0. 说明

今天无意间看到网上在讨论ArrayList和LinkedList的区别,本文准备从源码中分析下这两个List的底层实现和应用选择。

1. ArrayList

从名称上可以看出来是数组的形式的List,其实代表的是底层实现是数组。所以可以实现随机读取。看下源码

/** * The array buffer into which the elements of the ArrayList are stored. * The capacity of the ArrayList is the length of this array buffer. Any * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA * will be expanded to DEFAULT_CAPACITY when the first element is added. */transient Object[] elementData;```可以大胆猜测内部是以数组的形式实现的,为了验证我们再来看看获得设置某一个元素的方法源码:```javaE elementData(int index) { return (E) elementData[index];}/** * Returns the element at the specified position in this list. * * @param index index of the element to return * @return the element at the specified position in this list * @throws IndexOutOfBoundsException {@inheritDoc} */public E get(int index) { rangeCheck(index); return elementData(index);}/** * Replaces the element at the specified position in this list with * the specified element. * * @param index index of the element to replace * @param element element to be stored at the specified position * @return the element previously at the specified position * @throws IndexOutOfBoundsException {@inheritDoc} */public E set(int index, E element) { rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue;}

可见获得元素或者设置元素的方法就是设置数组的方法,而且也是设置elementData属性。

ArrayList有一个比较特别的就是扩容, 看下扩容的源码:

private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));}private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity);}/** * The maximum size of array to allocate. * Some VMs reserve some header words in an array. * Attempts to allocate larger arrays may result in * OutOfMemoryError: Requested array size exceeds VM limit */private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;/** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */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);}

主要是在grow方法中,int newCapacity = oldCapacity + (oldCapacity >> 1);代码可以得到每次扩容后的容量是上一次容量的1.5倍,先开辟1.5倍的空间,然后再将原来数组中的元素copy到新的数组中,并将list的elementData引用指向新的地址。

图示的办法看下扩容的样子:

ArrayList扩容

红色的位置已经有数据了。

2. LinkedList

看名字也知道,是以链表的形式实现的。内部的数据存储的数据结构是:

/** * Pointer to first node. * Invariant: (first == null && last == null) || * (first.prev == null && first.item != null) */transient Node first;/** * Pointer to last node. * Invariant: (first == null && last == null) || * (last.next == null && last.item != null) */transient Node last;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; }}

从源码可知是以双向链表的形式实现的。从get,set方法中再验证下:

/** * Returns the element at the specified position in this list. * * @param index index of the element to return * @return the element at the specified position in this list * @throws IndexOutOfBoundsException {@inheritDoc} */public E get(int index) { checkElementIndex(index); return node(index).item;}/** * Replaces the element at the specified position in this list with the * specified element. * * @param index index of the element to replace * @param element element to be stored at the specified position * @return the element previously at the specified position * @throws IndexOutOfBoundsException {@inheritDoc} */public E set(int index, E element) { checkElementIndex(index); Node x = node(index); E oldVal = x.item; x.item = element; return oldVal;}/** * Returns the (non-null) Node at the specified element index. */Node node(int index) { // assert isElementIndex(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; }}

定位查找的方法主要是node方法,明显是双向链表,当所以是后半部分的时候,从结尾开始遍历,如果是前半部分,则从头部开始遍历。而链表形式,理论上链表可以无限长,所以,在LinkedList中不存在扩容的问题。

我们看下存储的图示:

LinkedList

3. 比较

其实性能问题主要就是数组和链表的性能区别。

  1. - arraylist
  2. - 优点
  3. - 随机访问,get(i)的时间复杂度为O(1)
  4. - 缺点
  5. - 需要扩容,复制数组
  6. - 内部插入数据需要移动数据,插入删除的性能差
  7. - LinkedList
  8. - 优点
  9. - 容量理论无限,不存在扩容
  10. - 可以很方便的插入和删除数据(性能损失在查找)
  11. - 缺点
  12. - 不能随机访问,get(i)需要遍历

可见,两个的优缺点正好相反,从而在实际使用中要根据自己的也许决定使用那个list。

注意,这里面有几个方法,就是contain(Object o), indexOf(Object O),这些方法,也是必须的一个一个遍历的,复杂度是O(n)。在LinkedList中的get(i)方法内,使用双向链表的方法,可以将平均复杂度为n/2降低为n/4.

这里还有一个思想就是,List元素的数量专门用一个字段size来存储,非常方便,但是有可能出现数据不一致(在多线程编程中),这种为了方便或者说提高性能而数据冗余的方法,在实际的开发中也会用到,这个字段也为上面的get(i), add()方法提供了很大遍历。

@Testpublic void test_list_get() { List list = new ArrayList<>(); for (int i = 0; i < 1000; i++) { list.add("String" + ++i); } long start = System.currentTimeMillis(); for (int i = 0; i < 10000000; i++) { String str = list.get(30);// System.out.println(str); } long end = System.currentTimeMillis(); System.out.println("耗时:" + (end - start)); list = new LinkedList<>(); for (int i = 0; i < 1000; i++) { list.add("String" + ++i); } start = System.currentTimeMillis(); for (int i = 0; i < 10000000; i++) { String str = list.get(30);// System.out.println(str); } end = System.currentTimeMillis(); System.out.println("耗时:" + (end - start));}@Testpublic void test_list_set() { List list = new ArrayList<>(); for (int i = 0; i < 1000; i++) { list.add("String" + i); } long start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { list.add(100, "String" + (100+i)); } long end = System.currentTimeMillis(); System.out.println("耗时:" + (end - start)); list = new LinkedList<>(); for (int i = 0; i < 1000; i++) { list.add("String" + i); } start = System.currentTimeMillis(); for (int i = 0; i < 100000; i++) { list.add(100, "String" + (100+i)); } end = System.currentTimeMillis(); System.out.println("耗时:" + (end - start));}

两个方法的运行结果是:

耗时:19耗时:209耗时:597耗时:30

可以验证我们上面讲的。

当然,List的不同实现使用了不同的原理,本文只是简单的介绍了ArrayList和LinkedList的部分原理,具体各位读者可以自己研究,也欢迎评论区交流。

求**评论、点赞、关注+转发**

限于笔者知识有限,如果不足之处请帮忙指正,不喜勿喷!

您的支持是我不懈努力的动力,请读者多支持下!

更多文章,请关注微信公众号 CS_Toper之路,或者头条号 CSToper

arraylist 后往前遍历_ArrayList和LinkedList的深入浅出相关推荐

  1. arraylist 后往前遍历_面试官:谈谈常用的Arraylist和Linkedlist的区别

    Arraylist:底层是基于动态数组,根据下表随机访问数组元素的效率高,向数组尾部添加元素的效率高:但是,删除数组中的数据以及向数组中间添加数据效率低,因为需要移动数组. 例如最坏的情况是删除第一个 ...

  2. arraylist 后往前遍历_面试官:请说出线程安全的 ArrayList 有哪些,除了Vector

    以下环境是 JDK 1.8 ArrayList 的初始容量 面试官:你看过 ArrayList 的源码? Python 小星:看过 面试官:那你说下ArrayList 的初始容量是多少? Python ...

  3. arraylist 后往前遍历_Java集合框架之ArrayList

    ArrayList介绍 ArrayList是一个数组列表.与Java数组相比,ArrayList相当于一个动态数组.它继承于AbstractList,实现了List, RandomAccess, Cl ...

  4. 记直接插入排序,为什么必须从后往前遍历

    从前往后遍历找到插入位置 @Slf4j public class InsertSort {@Testpublic void test() {int[] arr = new int[]{5, 3, 7, ...

  5. AQS 从后往前遍历寻找继任者

    在阅读AQS源码的过程中,也许会存在这样的困惑,为什么当next指针对应的节点为null 或者取消时,从tail 向前遍历寻找最近的一个非取消的节点: 当前任释放时,需要获取继任者:AQS的实现方式是 ...

  6. 转载:二叉树的前中后和层序遍历详细图解(递归和非递归写法)

    二叉树的前中后和层序遍历详细图解(递归和非递归写法) Monster_ii 2018-08-27 17:01:53 50530 收藏 403 分类专栏: 数据结构拾遗 文章标签: 二叉树 前序 中序 ...

  7. 【二叉树前/先序DLR中序LDR后序LRD遍历及镜像翻转,so esay~】

    二叉树前/先序DLR中序LDR后序LRD遍历及镜像翻转 一.名词释义 二叉树的遍历方式,根据遍历根节点的顺序不同,分为三种:前序(先序)遍历(DLR).中序遍历(LDR).后序遍历(LRD). 1.前 ...

  8. 二叉树层次遍历算法 python_二叉树的遍历详解:前、中、后、层次遍历(Python实现)...

    二叉树的遍历详解:前.中.后.层次遍历(Python实现) 二叉树是一种常见的数据结构,而它的常见遍历方法有前序遍历.中序遍历.后续遍历.层次遍历--掌握这几种遍历方法是很有必要的. 假设我们二叉树节 ...

  9. 数组-合并两个有序数组(双指针,从后往前)

    题意: 给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组. 说明: 初始化 nums1 和 nums2 的元素数量分别为 ...

最新文章

  1. HDU1392(凸包求周长模版)
  2. 关于信噪比的确切含义——Eb/No和SNR的区别
  3. enscape助手_SDT Enscape助手
  4. zcmu1710(dp)
  5. 小学奥数 7827 质数的和与积 python
  6. 物体检测方法总结(下)
  7. 微服务架构的核心要点和实现原理解析
  8. 服务器怎么控制忽略样式_看问题要看到本质:从Web服务器说起
  9. maven 基本常识以及命令
  10. vs插件VS10x CodeMap 注册码
  11. 安卓问题-第三方相关
  12. java中json获取key值_如何获取JsonObject中key的值
  13. Unity格子类三消游戏【物体下落】小细节(Unity萌新的备忘录)
  14. 介绍一个使用go写的TUI性能监测工具gotop
  15. 机顶盒抓包(无法连接WIFI的设备)
  16. r7c刷机android6.0,OPPO R7电信版R7c官方固件刷机包rom线刷包(Root权限+驱动)
  17. FreeBSD开发手册(一)
  18. gitbook安装中installing gitbook xxx 时间过长的问题
  19. 用几何画板验证三角形重心定理,很方便!
  20. python竖着展示诗_竖着写诗的格式

热门文章

  1. Python获取文件后缀名
  2. svn打分支与分支合并回主干
  3. 搭建hadoop分布式集群
  4. response.sendRedirect(quot;http://www.baidu.comquot;);重定向
  5. java 执行 awk_3.1 biostar lesson3 linux学习日记;java版本;awk
  6. java中math的方法_Java Math所有方法
  7. FastDfs之TrackerServer的详细配置介绍
  8. Eureka 配置参数说明
  9. Android开发笔记(八十七)几个修饰关键字
  10. 文本居于图片左侧html,CSS实现图片与文本的居中对齐的常见方式