ArrayList源码阅读笔记

1. ArrayList继承的抽象类和实现的接口

  • ArrayList类实现的接口

    1. List接口:里面定义了List集合的基本接口,ArrayList进行了实现
    2. RandomAccess接口
    3. Cloneable接口
    4. Serializable:标记该类支持序列化
  • ArrayList继承了AbstractList抽象类

2. ArrayList底层的数据结构

  • ArrayList底层是基于数组实现的

    1. 特点:

      1. 数组在内存中是一个内存地址连续的,固定内存大小的内存空间,并且ArrayList可以存放重复的元素。允许存储 一个null或者多个null。
    2. 优点:

      1. 数组查询元素的效率快,因为数组查询元素时只需要一个元素对应在数组中存储的下标就可以获取到对应位置的元素值。
      2. ArrayList是异步的,也就是说该集合线程不安全,但是执行效率快
    3. 缺点:

      1. 当向ArrayList集合中存储的新的元素时,长度已经不够时,就会触发ArrayList底层的扩容机制。其实底层就是数组扩容。因为数组扩容需要把旧的数组元素复制到新的数组中,所以在数据量特别大的时候,扩容的效率不高。
      2. 在指定位置添加和修改元素效率不高(尾部除外)。
        • 例如:在数组的尾部以外的地方插入一个元素,那么为例保证整个数组中所有元素的连续存储性,就必须向将从该位置到尾部的所有元素进行后移一位,腾出一个空的位置,再把新的元素进行插入。删除也是,删除了指定位置的之后需要把从该元素到尾部的所有元素向前移动一个位置。
      3. ArrayList的所有方法都没有进行使用 synchronized 关键字进行修饰,这就代表该类的所有方法都不是同步的,在多线程情况下存着线程安全问题。

3. ArrayList适用的场景

  1. 对于频繁查询和修改集合中元素的场景非常适合,因为ArrayList的查询效率快。

  2. 单线程环境下,频繁对数据进行查询和修改时也适用。如果该多线程情况下可以使用Vector集合。该集合底层也是基于数组实现的,但是中会出现线程安全问题的方法使用了 synchronized 进行修饰,保证了线程安全。但是该集合目前已经基本废弃了。因为将方法进行同步了,那么效率就会下降。如果在多线程情况下还想使用ArrayList集合,这个时候可以创建一个线程安全的ArrayList集合

    // 创建一个线程安全的ArrayList集合,在多线程情况下能保证线程安全
    List<Object> list=Collections.synchronizedList(new ArrayList<Object>());

4. ArrayList底层源码分析

1. ArrayList的三个构造函数

  1. 无参构造

    /**
    * 在创建对象时,如果不指定构造函数,将调用此构造函数初始化一个长度为0,类型为Object的数组
    */
    public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    • 源码说明:无参构造器中用到的成员变量

      • 在创建对象时,如果不指定构造函数,将调用此构造函数初始化一个长度为0,类型为Object的数组
      // 一个 Object 类型的数组,可以存放任意类型的数据,初始化大小为0
      private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 一个 Object 类型的数组,可以存放任意类型的数据,不参与集合的序列化
      transient Object[] elementData;
  2. 有参构造(传入一个整数,指定初始的集合长度大小)

    /**
    * 创建对象时,如果指定初始大小将掉有该方法,创建一个有指定初始换长度的ArrayList集合
    * 如果提前知道需要存储的数据量有多少,使用该构造函数,可以改善因为频繁扩容导致的性能下降问题
    * @param initialCapacity 创建对象时,指定的初始化长度
    * @throws IllegalArgumentException 如何传入的数字小于0,则抛出异常,非法参数异常
    */
    public ArrayList(int initialCapacity) {
    // 判断传入的参数是否大于0
    if (initialCapacity > 0) {
    // 创建一个Object类型,长度为 initialCapacity 的数组,赋给 elementData 数组
    this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) { // 判断传入的参数是否等于0
    // 一个 Object 类型的数组,可以存放任意类型的数据,初始化大小为 0
    this.elementData = EMPTY_ELEMENTDATA;
    } else { // 以上两个条件都不满足,即传入的参数小于0,抛出非法参数异常
    throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
    }
    }
    • 源码说明:

      // 一个 Object 类型的数组,可以存放任意类型的数据,不参与集合的序列化
      // 使用 writeObject() 来实现数据的序列化 和 readObject() 反序列化
      transient Object[] elementData; // 一个 Object 类型的数组,可以存放任意类型的数据,初始化大小为0
      private static final Object[] EMPTY_ELEMENTDATA = {};
  3. 有参构造(传入一个Collection类型的集合)

    /**
    * 在初始化的时候直接将一个集合传入,可以吧传入集合的元素全部复制到创建的新集合中
    * @param c 传入的集合
    * @throws NullPointerException 当传入的集合为空时,会抛出异常
    */
    public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
    if (elementData.getClass() != Object[].class)
    elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
    this.elementData = EMPTY_ELEMENTDATA;
    }
    }

2. 添加一条数据的完整流程

  1. 在每次添加数据时,如果数据是基本数据类型,会先将基本数据类型进行装箱操作,把基本数据类型转换成对应的包装类型(引用数据类型)
// 例如:集合中存放Integer数据类型,在进行add操作时,会先进行装箱操作

/**
* 将基本数据类转换为引用数据类型
* @param i 传入的参数为一个基本型数据类型
* @return 返回的参数是一个基本数据类型的包装类(引用数据类型)
* @since 1.5
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
  1. 调用add方法进行添加操作
/**
* 添加元素方法,成功返回true,失败会抛出异常
* @param e 需要进行添加的元素值
* @return <tt>true</tt> 添加成功返回true,添加过程中如果失败会抛出异常
*/
public boolean add(E e) {
// 判断是否需要扩容的
ensureCapacityInternal(size + 1);
// 将传递进来的元素添加到数组的末尾
elementData[size++] = e;
// 返回true代表添加成功
return true;
}
  • 变量说明:

    /**
    * 该变量用于记录当前ArrayList中实际存储的元素个数
    */
    private int size;
  1. add方法中调用 ensureCapacityInternal 方法
/**
* 传入一个经过当前数组实际元素个数加 1 过后的参数,这样做主要是为了判断当前数组是否还支持添加新元素
* @param minCapacity 当前数组实际元素个数加 1 过后的参数
*/
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
  1. 先调用 calculateCapacity 方法,在调用 ensureExplicitCapacity
  • calculateCapacity 方法

    /**
    * 用于确定存储元素的数组需要的长度,或者当该数组为空时做长度初始化处理
    * @param elementData 当前用于存放元素的数组对象
    * @param minCapacity 当前数组实际元素个数加 1 过后的参数
    */
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
    // 判断当前存储元素的数组是否为空
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    // 返回 DEFAULT_CAPACITY(10) 和 minCapacity 两个数中大的数,如果初始化是采用无参构造器,
    // 则应当返回10,也就是经过判断之后存储数据的数组需要的容量大小
    return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    // 如果当前存储元素的数组不为空则直接返回当前数组实际元素个数加 1 过后得到的值
    return minCapacity;
    }
  • ensureExplicitCapacity 方法

    /**
    * 按照传递进来的具体值,判断是否需要进行扩容操作
    * @param minCapacity 经过判断之后存储数据的数组需要的容量大小
    */
    private void ensureExplicitCapacity(int minCapacity) {
    // 用于记录数组被修改的次数
    modCount++; // 如果实际需要的数组长度 - 数组当前的实际长度 > 0,说明该数组是要进行扩容
    if (minCapacity - elementData.length > 0)
    // 核心的扩容方法
    grow(minCapacity);
    }
    1. 判断是否需要扩容的核心方法 grow
    /**
    * 具体的扩容方法
    * @param minCapacity 经过判断之后存储数据的数组需要的容量大小
    */
    private void grow(int minCapacity) { // 获取为扩容之前的数组长度
    int oldCapacity = elementData.length;
    // 扩容之后的新数组长度为旧数组长度的 1.5 倍
    int newCapacity = oldCapacity + (oldCapacity >> 1); // 新数组长度 - 旧数组的长度 < 0,这种情况发生在创建ArrayList时使用的构造器是默认的无参构造器
    // 所以在第一次添加元素时 elementData.length == 0,
    if (newCapacity - minCapacity < 0)
    // 此时的 minCapacity 是在执行 calculateCapacity 方法时得到的数值10
    newCapacity = minCapacity; // 当扩容之后的数组长度大于最大的长度时
    if (newCapacity - MAX_ARRAY_SIZE > 0)
    newCapacity = hugeCapacity(minCapacity); // 使用数组的 copyOf 对数组进行扩容
    elementData = Arrays.copyOf(elementData, newCapacity);
    }
    • 变量说明:

      // 整数类型的最大值减8
      private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    1. 当数组要求的扩容的长度过设置的最大值时
    /**
    * 具体的扩容方法
    * @param minCapacity 经过判断之后存储数据的数组需要的容量大小
    */
    private static int hugeCapacity(int minCapacity) {
    // 是否已经内存溢出
    if (minCapacity < 0)
    throw new OutOfMemoryError(); // 判断实际需要的数组长度是否大于设置的最大值,是则返回最大值整数类型的最大值,否则返回设置的最大值
    return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
    }

ArrayList总结及部分源码分析的更多相关文章

  1. ArrayList实现原理及源码分析之JDK8

    转载 ArrayList源码分析 一.ArrayList介绍 Java 集合框架主要包括两种类型的容器: 一种是集合(Collection),存储一个元素集合. 一种是图(Map),存储键/值对映射. ...

  2. ArrayList相关方法介绍及源码分析

    目录 ArrayList简介: ArrayList 相关方法介绍 代码表示 相关方法源码分析 ArrayList简介: java.util.ArrayList 是我们最常用的一个类,ArrayList ...

  3. 4.Java集合-ArrayList实现原理及源码分析

    一.ArrayList概述: ArrayList 是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存 ArrayList不是线程安全的,只能用在单线程的情况 ...

  4. java ArrayList 迭代器快速失败源码分析

    先来看一个例子: @Test void test2() { ArrayList<String> list = new ArrayList<String>(); list.add ...

  5. ArrayList增加扩容问题 源码分析

    public class ArrayList<E>{ private static final int DEFAULT_CAPACITY = 10;//默认的容量是10 private s ...

  6. 大杂烩 -- ArrayList的动态增长 源码分析

    基础大杂烩 -- 目录 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 问题:当ArrayList中放入的元素一直增加会如 ...

  7. ArrayList用法详解与源码分析

    说明 此文章分两部分,1.ArrayList用法.2.源码分析.先用法后分析是为了以后忘了查阅起来方便-- ArrayList 基本用法 1.创建ArrayList对象 //创建默认容量的数组列表(默 ...

  8. JDK源码分析系列02---ArrayList和LinkList

    ArrayList和LinkList的源码分析 概要 ArrayList和LinkList是常用的存储结构,不看源码先分析字面意思,Array意思是数组,可知其底层是用数组实现的,Link意思是链接, ...

  9. 【集合框架】JDK1.8源码分析之ArrayList(六)

    一.前言 分析了Map中主要的类之后,下面我们来分析Collection下面几种常见的类,如ArrayList.LinkedList.HashSet.TreeSet等.下面通过JDK源码来一起分析Ar ...

随机推荐

  1. 不可错过的stm32单片机直流电机驱动与测速详解

    stm32直流电机驱动与测速 说实话就现在的市场应用中stm32已经占到了绝对住到的地位,51已经成为过去式,32的功能更加强大,虽然相应的难度有所增加,但是依然阻止不了大家学习32的脚步,不说大话了 ...

  2. xUtils3的使用教程

    首先在build.gradle下的dependencies下添加引用. implementation 'org.xutils:xutils:3.3.36' 然后创建一个表实体. package com ...

  3. 清除行列 牛客网 程序员面试金典 C++ Python

    清除行列 牛客网 程序员面试金典 C++ Python 题目描述 请编写一个算法,若N阶方阵中某个元素为0,则将其所在的行与列清零. 给定一个N阶方阵int[]mat和矩阵的阶数n,请返回完成操作后的 ...

  4. 前端面试手写代码——JS数组去重

    目录 1 测试用例 2 JS 数组去重4大类型 2.1 元素比较型 2.1.1 双层 for 循环逐一比较(es5常用) 2.1.2 排序相邻比较 2.2 查找元素位置型 2.2.1 indexOf ...

  5. Redis去重方法

    目录 1.基于 set 2.基于 bit 3.基于 HyperLogLog 4. 基于bloomfilter 这篇文章主要介绍了Redis实现唯一计数的3种方法分享,本文讲解了基于SET.基于 bit ...

  6. istio基础详解

    1.Istio介绍? 官方文档:https://istio.io/docs/concepts/what-is-istio/ 中文官方文档:https://istio.io/zh/docs/concep ...

  7. LeetCode刷题 链表专题

    链表专题 链表题目的一般做法 单链表的结构类型 删除节点 方法一 方法二 增加节点 LeedCode实战 LC19.删除链表的倒数第N个结点 解法思路 LC24.两两交换链表中的节点 解法思路 LC6 ...

  8. .net core api 请求实现接口幂等性

    简单实现接口幂等性,根据参数的hascode实现: 参数介绍  WaitMillisecond : 请求等待毫秒数 CacheMillisecond:请求结果缓存毫秒数 参数具体使用场景 WaitMi ...

  9. MAC电脑如何将常规视频中音频提取出来(转换格式并调整采样频率),并利用讯飞语音识别文字

    1.下载好相关视频 2.选中需要提取视频,鼠标右键找到「编码所选视频文件」 3.设置中,下拉选择「仅音频」,点击继续 4.找到已提取成功的音频,鼠标右键或快捷键「command + I」,显示简介.默 ...

  10. 问题 L: Yougth的最大化

    题目描述 Yougth现在有n个物品的重量和价值分别是Wi和Vi,你能帮他从中选出k个物品使得单位重量的价值最大吗? 输入 有多组测试数据 每组测试数据第一行有两个数n和k,接下来一行有n个数Wi和V ...