ArrayList源码解析(jdk1.8)——思路篇(如何看源码)

jdk1.8集合框架源码解析(四)ArrayList源码解析 ArrayList是我们日常开发中最常用的集合类甚至没有之一,了解其底层源码非常重要,以下源码基于 jdk1.8.0_144版本 一、ArrayList的数据结构 集合框架数据结构是其灵魂,理解数据结构才能更好理解ArrayList的实现思路。 ArrayList的数据结构如下: ArrayList其底层是一个动态数组,存储的元素是所有对象即Object,下面看ArrayList是... 阅读详情

前言:关于ArrayList相信大家都不陌生。而且大多数人应该都点开过它的源码浏览过。不过看到了什么,记住了什么,这是个值得深思的问题。博主今天写这篇文章的重点也不是解析其源码。更多的是想和大家分享一下看源码的一种方法以及一种思路。

源码解析第一步:简单了解其特性

ArrayList特点:

  1. ArrayList是有序的(放入顺序)
  2. ArrayList元素是不唯一的(可存放重复元素)

ArrayList简介:

  1. ArrayList底层是由数组实现的
  2. 他的容量是动态增长的
  3. 操作元素时查询效率高,增删效率低

源码解析第二步:提出自己的疑问,带着问题去读源码

  1. ArrayList是如何实现有序的
  2. ArrayList是如何扩容的
  3. ArrayList为何查询效率高,增删效率低
  4. ArrayList如何转换为数组

源码解析第三步:解读其继承与实现

现在让我们打开其源码,简单的读了读类注释,大体意思就是:它是一个集合框架,它是由数组实现的,它的容量可以自动扩充,它是线程不安全的以及迭代相关的一些说明;还有建议我们去看的一些类@see

/**
 *
 * @author  Josh Bloch
 * @author  Neal Gafter
 * @see     Collection
 * @see     List
 * @see     LinkedList
 * @see     Vector
 * @since   1.2
 */
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{

我们可以看到他继承了AbstractList抽象类,实现了 List, RandomAccess, Cloneable, java.io.Serializable 这些接口。

  • 继承了AbstractList,实现了List:AbstractList是个抽象类,抽象类一般用来提取一些共有的属性。简单看下他有add(),get(),set(),remove()等方法。
  • 实现了RandomAccess接口:点进去看了眼啥也没有!查了下RandomAccess 是一个标志接口,表明实现这个接口的 List 集合是支持快速随机访问的。
  • 实现了Cloneable 接口:点进去看了眼还是啥也没有!查了下实现这个接口需要覆盖clone()函数,表示能被克隆。源码如下:
public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }
  •  实现java.io.Serializable 接口:ArrayList支持序列化,能通过序列化去传输。

源码解析第四步:了解其成员变量

 private static final long serialVersionUID = 8683452581122892189L;

    /**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * Shared empty array instance used for empty instances.
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * 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; // non-private to simplify nested class access

    /**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     */
    private int size;
  • DEFAULT_CAPACITY:默认初始容量,容量大小为10。
  • EMPTY_ELEMENTDATA:用于空实例的共享空数组实例(来源有道翻译)。
  • DEFAULTCAPACITY_EMPTY_ELEMENTDATA:用于默认大小的空实例的共享空数组实例。我们将其与EMPTY_ELEMENTDATA区分开来,以知道在添加第一个元素时容量需要增加多少。
  • elementData:保存数据的数组。
  • size:ArrayList 所包含的元素个数

源码解析第五步:阅读源码,寻找答案

  1. ArrayList是如何实现有序的
        public boolean add(E e) {
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
        }

    解答:由add()方法可以看出新加元素时是将新元素追加到数组末尾,实际是对数组的赋值。

  2. ArrayList是如何扩容的

    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);
        }

    解答:这段代码是ArrayList的核心扩容方法。由第二行可以看出(扩容后=扩容前+(扩容前>>1)),扩容1.5倍(位移一位除以2)。因为位运算的运行速度是极快的,我们可以得出结论之所以扩容1.5倍是为了快。

  3. ArrayList为何查询效率高,增删效率低

        public E get(int index) {
            rangeCheck(index);
    
            return elementData(index);
        }
    
        private void rangeCheck(int index) {
            if (index >= size)
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }

    解答:上面是查询方法,不从算法的角度看应该也能看出它快吧。下面看看其增删的方法:

        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++;
        }
    
        public E remove(int index) {
            rangeCheck(index);
    
            modCount++;
            E oldValue = elementData(index);
    
            int numMoved = size - index - 1;
            if (numMoved > 0)
                System.arraycopy(elementData, index+1, elementData, index,
                                 numMoved);
            elementData[--size] = null; // clear to let GC do its work
    
            return oldValue;
        }

    解答:以上是增加和删除的方法,都调用了System.arraycopy()方法。这个方法是实现数组自己复制自己的。

       
        
     //src:原数组;srcPos:元数组起始位置;
     //dest:目标数组;destPos:目标数组的起始位置;length:要复制的元素数量
     public static native void arraycopy(Object src,  int  srcPos,
                                            Object dest, int destPos,
                                            int length);

    简单点说就是把一节数组拷贝到目标数组;而这里的目标数组也是其自身;我们来模拟一下添加操作,看一下效果;

     public static void main(String[] args) {
            //初始数组
            Integer[] elementData = new Integer[]{1,3,5,7,9,11,13,15,17,19};
            //在下标为1的位置
            int index = 1;
            //添加元素2
            int element = 2;
            int size = elementData.length;
            System.out.println("初始容量:"+size);
            //模拟扩容1.5倍
            elementData = new Integer[]{1,3,5,7,9,11,13,15,17,19,null,null,null,null,null};
            System.arraycopy(elementData, index, elementData, index + 1,
                    size - index);
            elementData[index] = element;
            size++;
            System.out.println("添加完后的数组:"+Arrays.toString(elementData));
            System.out.println("添加完后容量:"+size);
        }
    
    输出结果:
    初始容量:10
    添加完后的数组:[1, 2, 3, 5, 7, 9, 11, 13, 15, 17, 19, null, null, null, null]
    添加完后容量:11

    综上:添加移除时都需对数组进行复制,因此得出结论增删效率低(有兴趣的可以结合算法算一下)。

  4. ArrayList如何转换为数组

        public Object[] toArray() {
            return Arrays.copyOf(elementData, size);
        }

    这个无参的转换是使用了Arrays里的一个方法,具体实现如下:

        public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
            @SuppressWarnings("unchecked")
            T[] copy = ((Object)newType == (Object)Object[].class)
                ? (T[]) new Object[newLength]
                : (T[]) Array.newInstance(newType.getComponentType(), newLength);
            System.arraycopy(original, 0, copy, 0,
                             Math.min(original.length, newLength));
            return copy;
        }

    可以看出也是调用了System.arraycopy()方法,这里所用的目标数组copy就是一个新数组。

 源码解析第六步:总结与思考

  1. ArrayList创建时实际是一个容量为10的数组;
  2. 容量不够时扩容1.5倍,1.5倍扩充速度最快;
  3. 添加元素时都是追加在数组末尾,所以实现了有序,可以重复;
  4. 访问时直接通过下标,效率较高;
  5. 增删时需要将数组复制到新数组(尽管是自己复制自己);
  6. 转数组时其实是将现数组复制到新数组返回;

思考一:另外俩个没提到的成员变量参与了什么;

思考二:关于克隆的思考;

思考三:扩容的具体实现ensureCapacityInternal();

jdk 1.8源码包,包含sun源码,绝对真实,自己看大小就懂了,靠谱 jdk源码包的完整版本,可以查看sun的源码了,包括sun包下的反射,nio等等源码,现在统统不是问题。当我要找jdk源码包的时候,上面好多都是不全,或者假的,我也是醉了。 立即下载

相关推荐

实战STM32G0驱动DRV8870/AT8870:PWM+DMA精准调速与H桥衰减模式详解

本文详细介绍了如何使用STM32G0单片机结合DRV8870/AT8870电机驱动芯片实现直流有刷电机的精准控制。重点讲解了PWM与DMA结合实现高效调速的硬件配置与软件驱动方法,并深入剖析了H桥快衰减与慢衰减模式的工作原理及应用场景,为嵌入式电机控制项目提供了完整的实战解决方案。

c7d8e9的博客 598

HashMap源码分析(jdk1.8,保证你能看懂)

红黑树是一个自平衡的二叉查找树,也就是说红黑树的查找效率是非常的高,查找效率会从链表的o(n)降低为o(logn)。如果之前没有了解过红黑树的话,也没关系,你就记住红黑树的查找效率很高就OK了。

好好学java 551

2.1 SRv6 TE Policy

SRv6 TE Policy是在SRv6技术基础上发展的一种新的隧道引流技术。SRv6 TE Policy路径表示为指定路径的段列表(Segment List),称为SID列表(Segment ID List)。每个SID列表是从源到目的地的端到端路径,并指示网络中的设备遵循指定的路径,而不是遵循IGP计算的最短路径。如果数据包被导入SRv6 TE Policy中,SID列表由头端添加到数据包上,网络的其余设备执行SID列表中嵌入的指令。

m0_70976024的博客 1237

jdk1.8集合源码解读

java集合框架 jdk1.8

sbxxd的博客 665

ALTERA Triple-Speed Ethernet IP核在UDP通信中的实战配置指南

本文详细介绍了在Intel FPGA(原ALTERA)平台上配置Triple-Speed Ethernet IP核以实现UDP通信的实战指南。内容涵盖从IP核选型、QSYS系统搭建、关键参数配置,到UDP数据包组装、NIOS C程序调试及上板验证的全流程,重点解析了如何利用该IP核快速构建稳定的千兆网通信基础,为FPGA网络应用开发提供清晰路径。

m3n5b7v8c9x的博客 515

KUKA机器人修改机器人名称和IP地址的具体方法示例

KUKA机器人修改机器人名称和IP地址的具体方法示例

Robot_PLC_HMI_Automation 5260

JAVA面试题整理 || ArrayList源码分析

ArrayList创建和add等各种api使用原理ArrayList允许空值和重复元素,当往 ArrayList 中添加的元素数量大于其底层数组容量时,其会通过扩容机制重新生成一...

JAVA高级架构 428

【局部规划模块轨迹优化算法】Teb_planner规划算法

认知有限,望大家多多包涵,有什么问题也希望能够与大家多交流,共同成长!本文先对****做个简单的介绍,具体内容后续再更,其他模块可以参考去我其他文章提示:以下是本篇文章正文内容由于ROS中的局部路径规划器比较老了,所以我推荐大家使用比较新的局部路径规划器teb_local_planner。teb_local_planner是基于弹性时间带碰撞约束的算法,算法将动态障碍物、运行时效、路径平滑性等约束做综合考虑,在复杂环境下有更优秀的表现。

qq_35635374的博客 952

(一)ArrayList无参构造的初始容器大小

文章目录8版本的jdk初始容量:使用无参构造的初始大小到底是多少? 8版本的jdk初始容量:使用无参构造的初始大小到底是多少? 1.无参初始化并不是在无参构造方法的位置执行的,而是在第一次执行add方法的时候执行了容器大小的设置 /** * Shared empty array instance used for default sized empty instances. We * dist...

dancer 944

Android CCodec (十一) Codec2Buffer BufferPool分析

1、序言 2、C2BlockPool分析 3、SimpleC2Component关于BlockingBlockPool的实现 4、总结

给大佬递杯卡布奇诺的博客 4024

路径跟踪 | 图解纯追踪算法Pure Pursuit(附ROS C++/Python/Matlab仿真)

纯追踪算法(Pure Pursuit)参考了人类驾驶行为,其基本思想是在待跟踪路径上设置预瞄点(goal-ahead),通过简单的几何方法驱动机器人跟踪预瞄点直至抵达目标位置.本文详细推导纯追踪算法的理论原理及其变种——自适应纯追踪算法(APP)和规范化纯追踪算法(RPP),并提供ROS C++/Python/Matlab三种仿真实现加深理解

FRIGIDWINTER的博客 8413

ArrayList源码解析

前言 集合中的collection接口下的常用类的关系 ArrayList中的成员变量 /** * Shared empty array instance used for empty instances. */ private static final Object[] EMPTY_ELEMENTDATA = {};/** * Shared empty array instance used for default sized empty instances. We * di...

a219219219219的博客 314

JAVA:Spring Boot3 集成 ShardingSphere 实现数据分库分表

本文介绍了基于Spring Boot和ShardingSphere-JDBC实现分库分表与读写分离的解决方案。针对高并发、大数据量场景下单库单表的性能瓶颈,通过配置ShardingSphere-JDBC实现:1)订单表按user_id和order_id分片到两个库和两张表;2)读写分离,写操作路由至主库,读操作在从库间负载均衡。方案使用Druid连接池优化,支持业务透明,无需修改SQL。

木头 663

ArrayList源码深度解析(JDK8)【重点】

ArrayList源码深度解析概述构造方法ArrayLIst 常用API新增boolean add(E e)方法void add(int index, E element)方法结语   同学们,今天我们一起来了解最常用集合ArrayList的源码,通过本文的学习,你将能够了解到看源码的思路,以及掌握ArrayList的源码知识。 概述   简要的说,ArrayList是一个动态数组,他不是线程安全的,允许存储null元素,它的底层数据结构依然是数组,ArrayList是List接口的大小可变数组的实现;Ar

ᶻᶻᶻ 枕星河的博客 1230

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

JDK 1.8 java集合源码分析一(ArrayList)

工作有几年了,说来惭愧,从来没认真仔细的分析过JDK里面的源码,从今天开始分析下JDK中集合部分源码,学习下大神的思路,如有错误,大家尽管指出。 JDK版本 JDK_1.8.0_201 编辑器:idea 2019.3 首先我们看一下Collection接口 看一下Collection接口的各种关系,idea中ctrl + h 我们只看其中的Set与List 用一个UML图画一...

zzx125的专栏 567

《Java 100 天进阶之路》第45篇:ArrayList源码深度解析(2026版):从懒加载到1.5倍扩容奥秘

摘要:从JDK 7到JDK 8+,从懒加载到1.5倍扩容,从transient序列化到fail-fast机制,本文深入剖析ArrayList底层源码与设计思想。涵盖构造方法版本差异、扩容边界、迭代器对比、业务场景踩坑,附完整代码验证与面试真题。一篇吃透Java最常用集合框架,面试不再怕“ArrayList原理”。全文无废话,直接上干货。

折哥|智能物流与Java实战,副高数据库工程师。深耕出版社物流WMS/OMS(线体/立库/VGA),也分享Java进阶与OMS电商对接。Oracle→PG实战派。相信沉淀可迁移能力,分享通用技术,不藏不虚。 601

源码解析(1)ArrayList 源码解析(JDK8):扩容、增删原理与 fail‑fast 机制深度剖析

ArrayList的核心并不复杂:它本质上就是一个动态数组。默认构造采用延迟分配,第一次添加元素时容量通常扩展到 10。数组不足时,容量一般扩展为原来的 1.5 倍。查询和修改速度快,但中间位置的插入、删除需要移动元素。modCount与迭代器共同实现 fail-fast 机制。序列化只保存有效元素,避免无意义地保存数组空位。subList()是原列表的视图,不是独立副本。Java 8 为forEachremoveIfreplaceAllsort等方法提供了针对数组结构的优化实现。

er112121的博客 398

ArrayList 源码解析和设计思路

它会根据旧的容量大小计算出新的容量大小,并根据需要调整为满足最小容量要求的合适大小。然后将原来的元素复制到新的更大的数组中,完成数组的扩容操作。: 原来的容量大小计算出新的容量大小。这个方法会比较当前数组的容量和需要的最小容量,如果当前容量不足,则会扩大内部数组的容量以满足需求。方法会根据需要的最小容量来确定新的容量大小,并将原来的元素复制到新的更大的数组中。,接着移动需要向前移动的元素,将列表中最后一个元素置空,并返回被删除的元素的值。的容量,它会创建一个新的更大的数组,并将原来的元素复制到新数组中。

记录点点滴滴 1546

ArrayList源码解析和设计思路

系列文章目录 提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加 例如:第一章 Python 机器学习入门之pandas的使用 提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 系列文章目录 前言 一、pandas是什么? 二、使用步骤 1.引入库 2.读入数据 总结 前言 提示:这里可以添加本文要记录的大概内容: 例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习..

weixin_46003987的博客 341

JDK集合源码解析——ArrayList

导语 首先说下为什么要写集合类的博文,毕竟这类的文章网上也是数不胜数,可是并不能因为这个就成了自己不写的理由,写博文其实主要是为了锻炼自己思考问题的思路,很多时候一篇文章写的好坏与否,主要就看写作者的思路如何。当你的思路很清晰的时候,同时也说明了你对它的熟悉度如何。 虽然集合源码的解析又很多人写过,看上去也不是很复杂,可是如果你去细看的话,其实还是可以看出很多东西的。笔者之所以要写,更多的是要...

寒山道杳 249

ArrayList 源码深度剖析(第 1 篇)

"为什么扩容是 1.5 倍而不是 2 倍?"是 ArrayList 面试中被问得最多的问题之一。回答这个问题的关键不是背结论,而是把权衡的三个维度扩容次数、内存浪费、均摊成本。我们先把扩容序列实际推演一遍。1.5 倍序列2 倍序列添加 1000 个元素,1.5 倍扩容 12 次,2 倍扩容 7 次。看起来 2 倍更好?但要把三个因素放在一起看。第一,扩容次数 vs 单次成本。扩容次数越少越好,但每次扩容的成本是 O(n)——要拷贝全部已有元素。扩容倍数越大,后期单次扩容的绝对成本越高。

2401_85340832的博客 718

【源码深度 第2篇】ArrayList:隐藏在便捷操作下的动态数组之谜​

深扒ArrayList源码,探索扩容的奥秘。扩容真的是1.5倍?初始化容量一定是10?

浩泽学编程的博客 1533

JDK1.8源码分析之ArrayList

分析Collection下面几种常见的类,如ArrayList、LinkedList、HashSet、TreeSet等。通过JDK源码分析ArrayList底层是如何实现的。

qq_40564929的博客 178

深入理解ArrayList源码(Ⅰ)

本文以JDK8下的 java.util.ArrayList为基础,分析并简单说明ArrayList常用方法的实现思路。以备自用。 这里给出JavaSE8 在线API的网址: https://docs.oracle.com/javase/8/docs/api/ 目录 概述 ArrayList的底层结构 类层次关系 源码详析 ArrayList属性源码 ArrayList构造方...

Return_head的博客 828

Java ArrayList new出来,默认的容量到底是0还是10 ?

前文 最近也快到了金三银四, 想该篇文章这种问题,貌似又有了热度 : 这种问题存在疑惑吗? 如果你存在? 看完这篇你就没疑惑了 。 这一篇结合源码还有小代码例子, 还有我的唠叨,我们还是一贯作风,学知识,跟着我,只学一遍,忘不掉! 正文 不多说,开整: JDK 1.8 第一行代码,new一个ArrayList出来: List<Integer> testList = new ArrayList<>(); 然后点进去看源码, 跟着我...

默默不代表沉默 1万+
上一篇: Java8新特性——接口的默认方法(或扩展方法)
下一篇: 分布式学习(一)——小白入门篇
评论 20
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值