List 集合源码剖析

✅ ArrayList

底层是基于数组,(数组在内存中分配连续的内存空间)是对数组的升级,长度是动态的。

数组默认长度是10,当添加数据超越当前数组长度时,就会进行扩容,扩容长度是之前的1.5倍,要对之前的数组对象进行复制,所以只有每次扩容时相对性能开销大一些。

源码(jdk 1.8):

1. 添加元素(非指定位置)


  1. // 1. 添加元素
  2. public boolean add(E e) {
  3. ensureCapacityInternal(size + 1); // 每次添加元素都要对容量评估
  4. elementData[size++] = e;
  5. return true;
  6. }
  7. // 2. 评估容量
  8. private void ensureCapacityInternal(int minCapacity) {
  9. // 若果数组对象还是默认的数组对象
  10. if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
  11. minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
  12. }
  13. ensureExplicitCapacity(minCapacity);
  14. }
  15. // 3. 进一步评估是否要进行扩容
  16. private void ensureExplicitCapacity(int minCapacity) {
  17. modCount++; // 记录ArrayList结构性变化的次数
  18. // overflow-conscious code
  19. if (minCapacity - elementData.length > 0)
  20. grow(minCapacity);
  21. }

步骤3中

if (minCapacity - elementData.length > 0) grow(minCapacity);

minCapacity 大于当前数组对象长度时 才进行扩容操作,也就是执行步骤 4的代码


  1. // 4.复制产生新的数组对象并扩容
  2. private void grow(int minCapacity) {
  3. // overflow-conscious code
  4. int oldCapacity = elementData.length;
  5. int newCapacity = oldCapacity + (oldCapacity >> 1);
  6. if (newCapacity - minCapacity < 0)
  7. newCapacity = minCapacity;
  8. if (newCapacity - MAX_ARRAY_SIZE > 0)
  9. newCapacity = hugeCapacity(minCapacity);
  10. // minCapacity is usually close to size, so this is a win:
  11. elementData = Arrays.copyOf(elementData, newCapacity);
  12. }

2. 指定位置添加元素

  1. public void add(int index, E element) {
  2. // 1
  3. rangeCheckForAdd(index);
  4. // 2
  5. ensureCapacityInternal(size + 1);
  6. // 3
  7. System.arraycopy(elementData, index, elementData, index + 1,size - index);
  8. // 4
  9. elementData[index] = element;
  10. // 5
  11. size++;
  12. }
  1. rangeCheckForAdd(index); 评估插入元素位置是否合理

  2. ensureCapacityInternal(size + 1); 检查数组容量是否有当前元素数量 size +1 个大,因为后续进行数组复制要多出一个元素

  3. 数组复制


  1. System.arraycopy(elementData, index, elementData, index + 1,size - index);
  2. System.arraycopy(src, srcPos, dest, destPos , length);

src:源数组;srcPos:源数组要复制的起始位置;index 是要插入元素的位置,所以要从当前开始复制

dest:目的数组; destPos:目的数组放置的起始位置;复制好的元素要放在插入位置的后面 所以 index+1

length:复制的长度。包括插入位置和后面的元素 = 当前元素数 - 插入位置

  1. 步骤执行元素赋值

  2. 步骤元素长度+1

如果ArrayLisy集合不指定位置添加元素,默认往数组对象的后面追加,所以数组对象的其他元素位置不变,没有什么性能开销,如果元素插入到数组对象的前面,而且是越往前,重新排序的数组元素就会越多性能开销越大,当然通过上述源码介绍中看到,通过数组复制的方式排序对性能影响也不大,

3.查找元素

  1. // 获取指定位置元素的源码
  2. public E get(int index) {
  3. rangeCheck(index);
  4. return elementData(index);
  5. }

直接返回当前查找位置的数组对象对应的下标位置的元素,高性能,速度很快。

4. ArraList 和 Vector

ArraList 和 Vector 都是基于数组实现,它俩底层实现原理几乎相同,只是Vector是线程安全的,ArrayLsit不是线程安全的,它俩的性能也相差无几。

✅ LinkedList

  • LinkedList使用循环双向链表数据结构,它和基于数组的List相比是截然不同的,在内存中分配的空间不是连续的。
  • LinkedList是由一系列表项组成的,包括数据内容、前驱表项、后驱表项,或者说前后两个指针
  • 不管LinkedList集合是否为空,都有一个header表项。(前驱表项指向 最后一个 元素,后驱表项指向 第一个元素)

双向链表

表项

1. 添加元素


  1. //1. 添加元素,不指定位置会添加到最后一个
  2. public boolean add(E e) {
  3. linkLast(e);
  4. return true;
  5. }
  6. //2. 添加到最后一位(每次添加插入元素都会创建一个Node对象)
  7. void linkLast(E e) {
  8. final Node<E> l = last;
  9. final Node<E> newNode = new Node<>(l, e, null); // 双向链表的最后一个表项没有 后驱指针
  10. last = newNode;
  11. if (l == null)
  12. first = newNode;
  13. else
  14. l.next = newNode;
  15. size++;
  16. modCount++;
  17. }
  18. //3. 创建表项
  19. private static class Node<E> {
  20. E item;
  21. Node<E> next;
  22. Node<E> prev;
  23. Node(Node<E> prev, E element, Node<E> next) {
  24. this.item = element;
  25. this.next = next;
  26. this.prev = prev;
  27. }
  28. }

2. 添加指定位置元素


  1. public void add(int index, E element) {
  2. checkPositionIndex(index); // 检查位置是否合理
  3. if (index == size)
  4. linkLast(element); // 如果插入位置等于集合元素长度就往后追加
  5. else
  6. linkBefore(element, node(index)); // 否则在当前位置元素前面创建新节点
  7. }
  8. void linkBefore(E e, Node<E> succ) {
  9. // assert succ != null;
  10. // 步骤一
  11. final Node<E> pred = succ.prev;
  12. // 步骤二
  13. final Node<E> newNode = new Node<>(pred, e, succ);
  14. // 步骤三
  15. succ.prev = newNode;
  16. // 步骤四
  17. if (pred == null)
  18. first = newNode;
  19. else
  20. pred.next = newNode;
  21. size++;
  22. modCount++;
  23. }

步骤一:获取当前元素(未插入目前要插入的元素前)前指针指向的节点

步骤二:创建新节点,一个表项,前驱表项是前面的节点 步骤一得到,后驱表项指向的时当前位置的节点(未插入目前要插入的元素前)

步骤三:重新设置当前位置的节点(未插入目前要插入的元素前)的前驱指针指向的节点,也就是刚插入的创建的新节点

步骤四:是对创建新节点的前置节点的后驱表项进行修改设置

总结:对应LinkedList插入任意位置元素 我们只需创建一个新元素节点和移动前后两个表项的指针,其他表项无需任何操作,性能高;

3. LinkedList集合中的第一个和最后一个元素位置是确定的

最后一个元素和第一个元素的位置不需要遍历整个链表获取,每个LinkedList集合无论是否为空,都会有一个Header表项,Header表项的前驱指针始终指向最后一个元素,后驱指针指向第一个元素,所以可以说LinkedList集合中的第一个和最后一个元素位置是确定的。

4. 查找删除元素

循环双向链表结构中节点位置的确定需要根据前面的元素往后遍历查找或者后面的元素往前遍历查找


  1. // 1. 获取指定位置元素
  2. public E get(int index) {
  3. checkElementIndex(index); // 首先判断位置是否有效
  4. return node(index).item;
  5. }
  6. // 2. 根据位置对节点遍历查找
  7. Node<E> node(int index) {
  8. // assert isElementIndex(index);
  9. if (index < (size >> 1)) { // 如果下标位置在总长度中间之前,则从前往后遍历查找
  10. Node<E> x = first;
  11. for (int i = 0; i < index; i++)
  12. x = x.next;
  13. return x;
  14. } else { // 如果下标位置在总长度中间之后,则从后往前遍历查找
  15. Node<E> x = last;
  16. for (int i = size - 1; i > index; i--)
  17. x = x.prev;
  18. return x;
  19. }
  20. }

从上面的源码中可以看出如果LinkedList集合的长度很大,则每次查找元素都会遍历很多次,性能影响也会更大

删除任意位置的元素,是先要找到该元素,所以需要上一步提到的查找操作


  1. public E remove(int index) {
  2. checkElementIndex(index);
  3. return unlink(node(index)); // node(index) 是查找元素
  4. }

ArrayList和LinkedLisy对比

java集合框架-List集合ArrayList和LinkedList详解的更多相关文章

  1. Java集合源代码剖析(一)【集合框架概述、ArrayList、LinkedList、Vector】

    Java集合框架概述 Java集合工具包位于Java.util包下.包括了非常多经常使用的数据结构,如数组.链表.栈.队列.集合.哈希表等.学习Java集合框架下大致能够分为例如以下五个部分:List ...

  2. 一、集合框架(关于ArrayList,LinkedList,HashSet,LinkedHashSet,TreeSet)

    一.ArrayList 解决了数组的局限性,最常见的容器类,ArrayList容器的容量capacity会随着对象的增加,自动增长.不会出现数组边界的问题. package collection;   ...

  3. Java容器解析系列(4) ArrayList Vector Stack 详解

    ArrayList 这里关于ArrayList本来都读了一遍源码,并且写了一些了,突然在原来的笔记里面发现了收藏的有相关博客,大致看了一下,这些就是我要写的(╹▽╹),而且估计我还写不到博主的水平,这 ...

  4. Java自动化测试框架-12 - TestNG之xml文件详解篇 (详细教程)

    1.简介 现在这篇,我们来学习TestNG.xml文件,前面我们已经知道,TestNG就是运行这个文件来执行测试用例的.通过本篇,你可以进一步了解到:这个文件是配置测试用例,测试套件.简单来说,利用这 ...

  5. java集合框架collection(2)ArrayList和LinkedList

    ArrayList是基于动态数组实现的list,而LinkedList是基于链表实现的list.所以,ArrayList拥有着数组的特性,LinkedList拥有着链表的特性. 优缺点 ArrayLi ...

  6. 集合框架学习之Collection和Map详解

    线性表,链表,哈希表是常用的数据结构,在进行Java开发时,JDK已经为我们提供了一系列相应的类来实现基本的数据结构.这些类均在java.util包中.本文试图通过简单的描述,向读者阐述各个类的作用以 ...

  7. 【Java集合】LinkedList详解前篇

    [Java集合]LinkedList详解前篇 一.背景 最近在看一本<Redis深度历险>的书籍,书中第二节讲了Redis的5种数据结构,其中看到redis的list结构时,作者提到red ...

  8. JAVASE02-Unit04: 集合框架 、 集合操作 —— 线性表

    Unit04: 集合框架 . 集合操作 -- 线性表 操作集合元素相关方法 package day04; import java.util.ArrayList; import java.util.Co ...

  9. java基础解析系列(十)---ArrayList和LinkedList源码及使用分析

    java基础解析系列(十)---ArrayList和LinkedList源码及使用分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder jav ...

随机推荐

  1. DICOM医学图像处理:WEB PACS初谈四,PHP DICOM Class

    背景: 预告了好久的几篇专栏博文一直没有整理好,主要原因是早前希望搭建的WML服务器计划遇到了问题.起初以为参照DCMTK的官方文档wwwapp.txt结合前两天搭建的WAMP服务器可以顺利的实现WM ...

  2. 【腾讯Bugly干货分享】经典随机Crash之二:Android消息机制

    本文作者:鲁可--腾讯SNG专项测试组 测试工程师 背景 承上经典随机Crash之一:线程安全 问题的模型 好几次灰度top1.top2 Crash发生场景:在很平常.频繁的使用页面,打开一个界面,马 ...

  3. App设计:消息推送和界面路由跳转

    概要 app消息推送.显示通知栏,点击跳转页面是很一般的功能了,下面以个推为例演示push集成,消息处理模块及app内部路由模块的简单设计. 推送 推送sdk集成 集成sdk步骤根据文档一步步做就行了 ...

  4. 【高速接口-RapidIO】2、RapidIO串行物理层的包与控制符号

    一.RapidIO串行物理层背景介绍 上篇博文提到RapidIO的物理层支持串行物理层与并行物理层两种,由于Xilinx 部分FPGA内部已经集成了串行高速收发器,所以用FPGA实现RapidIO大多 ...

  5. BCrypt实现密码的加密

    这里设计到一个新的知识点,下来准备找找资料学习一下:Spring Security 我们都知道,密码这种东西存到数据库是不能以明文直接存入的,而是要经过加密,而且加密还颇多讲究 比如以前的 MD5加密 ...

  6. shell脚本执行错误 $'\r':command not found

    shell脚本执行错误 $'\r':command not found Linux下有命令dos2unix 可以用一下命令测试 vi -b filename 我们只要输入dos2unix *.sh就可 ...

  7. Git使用详细教程(1):工作区、暂存区、本地仓库、远程仓库

    之前的写过一篇如何在服务器上搭建Git服务Git服务器搭建,接下来的一段时间,我将详细的讲解Git的使用.看如下一张图片,本篇主要理解一些基本概念. 图中几个名词的意思如下: workspace: 工 ...

  8. 字体图标三种格式区别(Unicode / Font class / Symbol)

    在实际项目中,或多或少会用到字体图标,优点是即减少了体积,又减少了http请求,可谓一举两得 我一般是在阿里巴巴矢量图库下载字体图标:http://www.iconfont.cn/   下面以阿里巴巴 ...

  9. OAuth2简易实战(四)-Github社交联合登录

    1. OAuth2简易实战(四)-Github社交联合登录 1.1. 用到的第三方插件 https://github.com/spring-projects/spring-social-github ...

  10. RocketMQ入门手册

    前言 继我上一篇博客后 分布式消息队列RocketMQ学习教程① 上一篇博客最主要介绍了几种常用的MQ,所以本博客再简单介绍一下RocketMQ的原理和简单的例子,基于Java实现,希望可以帮助学习者 ...