• Java API学习(一) ArrayList源码学习


    ArrayList在平常用的还挺多的,用起来十分舒服,顺手。这里来学习一下它的源码。

    类定义

    下面是类的定义:

    1 public class ArrayList<E> extends AbstractList<E>
    2         implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    3 
    4

    实现的接口还挺多的。

    类属性

    首先来看下一ArrayList的几个属性:

    1. DEFAULT_CAPACITY:初始容量

    1 private static final int DEFAULT_CAPACITY = 10;

    DEFAULT_CAPACITY,是ArrayList的初始容量,有意思是它使用的时机。

    2. EMPTY_ELEMENTDATA:共享的空数组

    1 private static final Object[] EMPTY_ELEMENTDATA = {};

    英文注释是:"Shared empty array instance used for empty instances."。这个看上去还是很好理解的,但是下面还有一个属性也是空数组。

    3. DEFAULTCAPACITY_EMPTY_ELEMENTDATA:默认容量的空数组

    好奇怪,用一个EMPTY_ELEMENTDATA不就可以了么,为什么还要用一个DEFAULTCAPACITY_EMPTY_ELEMENTDATA呢?当我们使用new ArrayList()的时候就会发现这个空数组实例排上用场了:

    1 public ArrayList() {
    2     this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    3 }

    这个方法的英文注释是:"Constructs an empty list with an initial capacity of ten."。但是这里其实就把这个空的数组对象DEFAULTCAPACITY_EMPTY_ELEMENTDATA给了elementData,并没有实例化额外的对象数组。

    4. elementData:实际元素所在的数组

    1 transient Object[] elementData; // non-private to simplify nested class access

    elementData才是元素真正存放的地方,这里不明白的是为什么要用transient关键字。

    这个数组缓冲区是ArrayList存储元素的地方。ArrayList的容量正是这个数组缓冲区的长度。任何elementData为DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空ArrayList会在添加第一个元素的时候,将容量扩充到DEFAULT_CAPACITY(目前是10)。 

    5. size:数组中元素的长度

    1 private int size;

    这里的size并不是elementData数组的长度,而是这个数组真正包含的元素的个数(这个个数其实有待商榷,后面会讲到)。

    构造方法

    在ArrayList中一共有3个构造方法:

    1 // 给定初始容量
    2 public ArrayList(int initialCapacity);
    3 
    4 // 空参构造方法
    5 public ArrayList();
    6 
    7 // 从其他集合类中构造
    8 public ArrayList(Collection<? extends E> c);

     1. 给定初始容量的constructor

     1 public ArrayList(int initialCapacity) {
     2     if (initialCapacity > 0) {
     3         this.elementData = new Object[initialCapacity];
     4     } else if (initialCapacity == 0) {
     5         this.elementData = EMPTY_ELEMENTDATA;
     6     } else {
     7         throw new IllegalArgumentException("Illegal Capacity: "+
     8                                            initialCapacity);
     9     }
    10 }

    逻辑如下:

    • initialCapacity > 0为真时,创建一个大小为initialCapacity的空数组,并将引用赋给elementData
    • initialCapacity == 0为真时,把之前的空数组EMPTY_ELEMENTDATAelementData
    • initialCapacity < 0为真时,直接抛出IllegalArgumentException异常。

    唯一一点是为啥这个时候要用EMPTY_ELEMENTDATA了,而不是继续用原来的DEFAULTCAPACITY_EMPTY_ELEMENTDATA了。

    2. 空参构造方法

    1 public ArrayList() {
    2     this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    3 }

    这个就比较简单了,和上面对比的是,这个时候为啥要用DEFAULTCAPACITY_EMPTY_ELEMENTDATA

    问题来了:new ArrayList(0)和new ArrayList()有啥不同呢?

    唯一的不同就在于内部数组用的不是同一个,虽然它们都是空的。

    3. 从集合类中构造

     1 public ArrayList(Collection<? extends E> c) {
     2     elementData = c.toArray();   // 注意, 这个时候ArrayList中元素的顺序取决于toArray
     3     if ((size = elementData.length) != 0) {
     4         // c.toArray might (incorrectly) not return Object[] (see 6260652)
     5         if (elementData.getClass() != Object[].class)
     6             elementData = Arrays.copyOf(elementData, size, Object[].class);
     7     } else {
     8         // replace with empty array.
     9         this.elementData = EMPTY_ELEMENTDATA;
    10     }
    11 }

    这里有两个地方需要注意下:

    • 集合转ArrayList之后元素的顺序是由集合的toArray方法所决定的;
    • toArray可能会不正确地返回数组对象Object[].class, see 6260652。 

     Collection接口的toArray方法依赖于实现类中的具体实现,有时候ArrayList中的顺序对我们来说还是挺重要的。然后在来看一下,这个官方bug,我在网上查了一下,发现这篇写的不错:https://blog.csdn.net/gulu_gulu_jp/article/details/51457492

    新增元素的方法

    新增元素的方法是ArrayList中最值得关注的,它提供了两种add方法:

    • 没有index的add方法,即在内部数组的末尾添加一个元素;
    • 在给定的index处插入元素,index右边的元素会后移。

    没有index的add方法

    1 public boolean add(E e) {
    2     ensureCapacityInternal(size + 1);  // Increments modCount!!
    3     elementData[size++] = e;
    4     return true;
    5 }

    ensureCapacityInternal方法在ArrayList中很多地方都用到了,非常关键的方法。

    当插入新元素所需的最小capacity要比内部数组的长度大的时候,就要扩容了,现在原来的基础之上扩容50%,如果新的capacity比最小的capacity还要小的话,直接用所需最小的capacity作为capacity。

    有index的方法

    在这种情况下是在ArrayList的某个位置上插入一个元素,然后把index后边的元素集体往右移一位。需要注意的问题是,index的范围是有限制的,必须在[0, size)之间,那么这中检查是为了不让ArrayList之间出现空洞。

    1 public void add(int index, E element) {
    2     rangeCheckForAdd(index);           // 返回检查
    3     ensureCapacityInternal(size + 1);  // 扩容
    4     System.arraycopy(elementData, index, elementData, index + 1,
    5                  size - index);        // 右移元素
    6     elementData[index] = element;      // 插入新元素
    7     size++;
    8 }

    ArrayList中的内部模型 

    理解了ArrayList的内部模型的话,其实看不看源码感觉关系不大。

    其他方法

    其他方法我觉得还挺常规的,没有太多好说的,就是System.arraycopy用得还挺多的,专门用于复制数组。


    以上~

  • 相关阅读:
    【WIN32API&DAPI】窗口相关函数
    第十四章_安全性
    android实现gif图与文字混排
    Extjs 4.2 设置buttontext为中文
    HDU 5384 Danganronpa (AC自己主动机模板题)
    bzoj2938【Poi2000】病毒
    [Java开发之路](9)对象序列化与反序列化
    atitit.jndi的架构与原理以及资源配置and单元測试实践
    QueryError:Incorrect result size: expected 1, actual 0
    LightOJ 1070 Algebraic Problem (推导+矩阵高速幂)
  • 原文地址:https://www.cnblogs.com/tuhooo/p/9265780.html
Copyright © 2020-2023  润新知