ArrayList源码分析

一、ArrayList

1、概述

(1)ArrayList是可以动态增长和缩减的索引序列,基于数组实现的List接口

(2)该类封装了一个动态再分配的Object[]数组,每一个类对象都有一个capacity属性,表示封装的Object[]数组的长度,当向ArrayList中添加元素时,该属性值会自动增加

​ 如果向ArrayList中添加大量元素,可使用ensureCapacity()方法一次性增加capacity,可以减少重分配的次数以提高性能

(3)ArrayList的用法和Vector类似,但ArrayList线程不安全,多线程不推荐使用

(4)继承结构

2、ArrayList的数据结构

ArrayList底层的数据结构就是数组,数组的元素类型为Object,即可以存放所有类型数据

二、源码分析

1、继承结构和层次关系

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

(1)继承

ArrayList extends AbstractList
AbstractList extends AbstractCollection
  • ArrayList继承了AbstractList,AbstractList继承了AbstractCollection

  • (类和接口之间的抽象类)为什么ArrayList不直接实现list类,而是AbstractList实现list接口后,再继承?

    接口中只能有抽象的方法,而抽象类既可以有抽象方法,也可以有具体的实现方法,List为接口,里面全是抽象方法,AbstractList是一个抽象的类,如此就可以让AbstractList实现部分通用的List方法(即底层类中通用的方法),然后由ArrayList具体的类,来实现自己特有的方法,如此,便可以让代码更简洁,减少代码的复写

(2)接口

  • List:这其实是一个mistake,因为他写这代码的时候觉得这个会有用处,但是其实并没什么用,但因为没什么影响,就一直留到了现在(https://www.cnblogs.com/zhangyinhua/p/7687377.html)

  • RandomAccess:这是一个标记性接口,其作用为用来快速随机存取,有关效率的问题,实现了该接口,使用普通的for循环来遍历,性能更高,这个标记性只是为了让我知道用什么方式去获取数据性能更好

  • Cloneable:可以使用Object.Clone方法,该方法可以克隆

  • java.io.Serializable:实现该序列化接口,表明该类可以被序列化

    • 什么是序列化?

      能从类变成字节流传输,还能从字节流变为原来的类

2、类中的属性

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable {
	// 版本号
	private static final long serialVersionUID = 8683452581122892189L;
	// 默认初始的容量
	private static final int DEFAULT_CAPACITY = 10;
	// 空对象数组,如果用户指定容量为0,返回该数组
	private static final Object[] EMPTY_ELEMENTDATA = new Object[0];
	// 默认空对象数组,如果使用默认构造函数创建,则默认对象内容默认是该数组
	private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];
	// 当前数据对象存放地方,当前对象不参与序列化
	transient Object[] elementData;
	// 当前数组长度
	private int size;
	// 数组最大长度
	private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
}

3、构造方法

(1)无参构造器

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    //private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];
}

分析:将默认的空对象数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA赋值给存放数据对象的elementData

(2)参数为int的构造器

public ArrayList(int initialCapacity) {//initialCapacity:初始容量
    if (initialCapacity > 0) {//初始容量大于0
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {//初始容量等于0
        this.elementData = EMPTY_ELEMENTDATA;//将空对象数组的地址传给elementData存放
        //private static final Object[] EMPTY_ELEMENTDATA = new Object[0];
    } else {//初始容量小于0,抛出异常
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

(3)参数为Collection对象的构造器

public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();//将数组的地址传给elementData存放
    if ((size = elementData.length) != 0) {//数组长度不等于0
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)//每个集合的toarray()的实现方法不一样,所以需要判断一下,如果不是Object[].class类型,那么久需要使用ArrayList中的方法去改造一下
            elementData = Arrays.copyOf(elementData, size, Object[].class);//如果size不等于0,执行Arrays.copyOf()方法,将c的内容copy到elementData中
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

分析:每个集合的toarray()的实现方法不一样,所以需要判断一下,如果不是Object[].class类型,那么久需要使用ArrayList中的方法去改造一下

(4)总结

​ arrayList的构造器做了一件事,初始化存储数据的容器,即elementData,elementData本质上是一个数组

4、方法

4.1 add()方法

(1)add(E): boolean

  • 元素方法入口
    • 扩容
    • 存放数据
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //新数组创建成功,最低扩容至size++,记录数组长度的size+1
    elementData[size++] = e;
    return true;
    //add成功
}
  • ensureCapacityInternal()方法——为扩容确定基础条件,使存放数据的数组为非空
    • 判断数组是否为调用无参构造器构造的空数组,则使数组的长度不低于默认的最小长度10
    • 若数组不是无参构造器创建的对象则执行ensureExplicitCapacity(minCapacity);为实际长度+1
    • 调用ensureExplicitCapacity()方法,判断是否需要扩容
private void ensureCapacityInternal(int minCapacity) {//传入最小容量:size+1,即加入一个元素后的实际长度,用于判断数组长度是否够用
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//判断数组是否为默认空对象数组,这里比较的是地址,判断是否为无参构造器初始化的空数组
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);//是默认初始化的,则使最小容量等于默认数组长度10和传入的最小容量minCapacity中的最大值,即使minCapacity大于等于默认初始容量10——确定初始容量10
        //private static final int DEFAULT_CAPACITY = 10;
    }

    ensureExplicitCapacity(minCapacity);//传入最小容量
}
  • ensureExplicitCapacity()方法——判断是否需要扩容
    • 记录修改次数modCount++
    • 数组的实际长度大于用来存放数据的数组的长度,说明数组长度不够用,需要扩容
    • 调用grow()扩容
private void ensureExplicitCapacity(int minCapacity) {//传入最小容量
    modCount++;//修改次数
    //protected transient int modCount = 0;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)//数组的实际长度大于用来存放数据的数组的长度,说明数组长度不够用,需要扩容
        grow(minCapacity);//传入数据实际的最小容量
    //容量增长成功,扩容的新数组创建完成
}
  • grow()方法——扩容
    • 默认是新数组长度=1.5*老数组长度
    • 若还是放不下,则使新数组长度等于数组的实际长度
    • 若数组长度超出了最大容量限制
    • 则给新数组长度能给的最大值
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;//用来存放数据的数组的长度
    int newCapacity = oldCapacity + (oldCapacity >> 1);//算数右移1,则新数组的长度等于1.5被的老数组长度
    if (newCapacity - minCapacity < 0)//如果放不下
        newCapacity = minCapacity;//令新数组的长度直接等于数据的实际长度
    if (newCapacity - MAX_ARRAY_SIZE > 0)//如果新数组长度高出了容量限制
        //	private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

        newCapacity = hugeCapacity(minCapacity);//将能给的最大值给新数组长度
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);//使elementData指向新的从老elementData数组copy来的新长度的数组
    //返回,新数组创建完成
}
  • hugeCapacity()——最大容量限制
private static int hugeCapacity(int minCapacity) {//最大容量限制
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
    MAX_ARRAY_SIZE;
}

(2) add(int, E):void

在下标为int的地方插入元素

public void add(int index, E element) {
    rangeCheckForAdd(index);//判断下标合理性

    ensureCapacityInternal(size + 1);  // Increments modCount!!扩容
    //System.arraycopy()方法用来在插入元素之后,要将index之后的元素都往后移一位
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    //在index下标放入元素
    elementData[index] = element;
    size++;//当前数组长度增加1
}
private void rangeCheckForAdd(int index) {//传入下标
    if (index > size || index < 0)//判断下标合理性,不合理抛出异常
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

(3)总结

  • 若调用无参构造器,第一次add后容器能放置的对象长度为10
  • 若调用有参构造器,则会扩容1.5倍
  • 扩容有最大上限

4.2 get(int):E

返回指定位置上的元素

public E get(int index) {
    rangeCheck(index);//检查下标的合理性

    return elementData(index);//返回数组下标相应的元素
}
private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

4.3 set(int, E):E

设置新元素,返回旧元素

public E set(int index, E element) {
        rangeCheck(index);//检查下标合理性

        E oldValue = elementData(index);//将旧元素存放在oldValue中
        elementData[index] = element;//放入新元素
        return oldValue;//返回旧元素
    }

4.4 contains(Object):boolean

public boolean contains(Object o) {
    return indexOf(o) >= 0;//返回是否存在,若存在下标大于等于0,不存在返回-1
}
index(Object):int:遍历查找指定元素
public int indexOf(Object o) {//遍历查询
    if (o == null) {//查询null元素
        for (int i = 0; i < size; i++)
            if (elementData[i]==null)//比较地址是否为null
                return i;//找到返回下标
    } else {
        for (int i = 0; i < size; i++)
            if (o.equals(elementData[i]))//比较元素内容是否相等
                return i;
    }
    return -1;//没找到返回-1
}

4.5 remove()删除方法

(1)remove(int):E

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

(2)remove(Object):boolean

public boolean remove(Object o) {
    if (o == null) {//查找元素为空
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {//找到索引
                fastRemove(index);//删除元素
                return true;//返回
            }
    } else {//非空
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}
private void fastRemove(int index) {//用来删除指定元素,和remove一样
    modCount++;
    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
}

(3)clear():void

将容器elementData中每个元素置空null等待垃圾回收将其回收

public void clear() {
    modCount++;

    // clear to let GC do its work
    for (int i = 0; i < size; i++)//遍历置空
        elementData[i] = null;

    size = 0;
}

(4)removeAll(Collection):boolean

批量删除c

public boolean removeAll(Collection<?> c) {
    Objects.requireNonNull(c);//c为null报错
    return batchRemove(c, false);
}
  • batchRemove:检测两个集合是否有交集
private boolean batchRemove(Collection<?> c, boolean complement) {
    final Object[] elementData = this.elementData;//向上转换
    int r = 0, w = 0;//r控制循环,w记录交集个数
    boolean modified = false;
    try {
        for (; r < size; r++)//循环size次,即遍历elementData是否有c
            if (c.contains(elementData[r]) == complement)//如果没找到
                elementData[w++] = elementData[r];//将没找到的元素放入新数组
    } finally {
        // Preserve behavioral compatibility with AbstractCollection,
        // even if c.contains() throws.
        if (r != size) {//异常,将剩下的元素放入新数组
            System.arraycopy(elementData, r,
                             elementData, w,
                             size - r);
            w += size - r;
        }
        if (w != size) {//数组长度减少
            // clear to let GC do its work
            for (int i = w; i < size; i++)
                elementData[i] = null;//将w下标及之后之后全部置空,因为w在前面是后++所以从w开始
            modCount += size - w;//操作次数更新
            size = w;//数组大小更新
            modified = true;//删除成功
        }
    }
    return modified;
}
public boolean contains(Object o) {//有,返回true,无返回false
    return indexOf(o) >= 0;//indexOf遍历,若有返回下标,无返回-1
}

三、总结

1、arrayList可以存放null

2、arrayList本质上是elementData数组

3、arrayList区别于数组的地方在于能够自动扩展大小,其中关键的方法就是gorw()方法

4、arrayList中removeAll(collection c)和clear()的区别就是removeAll可以删除批量指定的元素,而clear是全是删除集合中的元素

5、arrayList由于本质是数组,所以它在数据的查询方面会很快,而在插入删除这些方面,性能下降很多,有移动很多数据才能达到应有的效果

6、arrayList实现了RandomAccess,所以在遍历它的时候推荐使用for循环

posted @ 2021-12-23 00:18  DarkSki  阅读(44)  评论(0)    收藏  举报