谈谈java中ArrayList

1. ArrayList结构图

ArrayList是java集合框架中比较常用的数据结构了。继承自AbstractList,实现了List接口。底层是基于数组实现容量大小动态变化,允许null的存在。同时还实现了RandomAccess、Cloneable、Serializable接口,所以ArrayList是支持快速访问、复制、系列化的。


image.png

2. ArrayList类简介

    1. ArrayList内部是以动态数组的形式来存储数据的,知道数组可能会疑惑:数组不是定长的吗?这里的动态数组不是意味着去改变原有内部生成的数组的长度,而是保留原有的数组引用,将其指向新生成的数组对象,这样会造成数组的长度可变的假象。
    1. ArrayList具有数组所具有的特性,通过索引支持随机访问,所以通过随机访问ArrayList中的元素效率非常高,但是执行插入,删除时效率比较低下。
    1. ArrayList实现了AbstractList抽象类、List接口、所以其更具有了AbstractList和List的功能,前面我们知道AbstractList内部已经实现了获取Iterator和ListIterator的方法,所以ArrayList只需关心对数组操作的方法和实现。
    1. ArrayList实现了RandomAccess接口、此接口只有声明、没有方法体、表示ArrayList支持随机访问。
    1. ArrayList实现了Cloneable接口、此接口只有声明、没有方法体、表示ArrayList支持克隆。
    1. ArrayList实现了Serialicable接口、此接口只有声明、没有方法体、表示ArrayList支持序列化、既可以将ArrayList以流的形式通过ObjectInputStream/ObjectOutputStream来写/读。

3. 基础属性

ArrayList部分源码:

public class ArrayList extends AbstractList
        implements List, RandomAccess, Cloneable, java.io.Serializable
{
    private static final int DEFAULT_CAPACITY = 10;

    private static final Object[] EMPTY_ELEMENTDATA = {};

    private transient Object[] elementData;
    
    private int size;

    //...省略部分代码
}

如上代码中为ArrayList的主要属性:

  • DEFAULT_CAPACITY:默认容量,即为初始值大小
  • EMPTY_ELEMENTDATA:共享的空数组,用于初始化空实例
  • elementData:ArrayList内部结构,是一个Object[]类型的数组
  • size:数组长度大小

4. 构造方法

public ArrayList(int initialCapacity)

public ArrayList()

public ArrayList(Collection c){
    elementData = c.toArray();
    size = elementData.length;
    // c.toArray might (incorrectly) not return Object[] (see 6260652)
    if (elementData.getClass() != Object[].class)
        elementData = Arrays.copyOf(elementData, size, Object[].class);
}

  • 1.构造方法1,表示接受指定地容量值,初始化创建数组,建议在可估算数组大小时,创建ArrayList可指定
    1. 构造方法2,是默认的构造方法,它将创建一个空数组
    1. 构造方法3,接收一个Collection的实体,将该Collection实体转换为ArrayList对象

5.主干流程

添加指定元素代码如下

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

1.扩容

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

//最大数组容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

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

    1. 第一个方法的逻辑为:判断是不是第一次添加元素,若为第一次,则设置初始化大小为默认的值10,否则使用传入的参数
    1. 第二个方法的逻辑为:若长度大于数组长度,则扩容
    1. 第三个方法的逻辑为:
        1. 扩容的大小为3/2倍原数组长度
        1. 若值newCapacity比传入值minCapacity还要小,则使用传入minCapacity,若newCapacity比设定的最大数组容量大,则使用最大整数值
        1. 实际扩容,使用了Arrays.copyof(elementData, newCapacity) (此处有两个问题 1.为啥扩容是原来的3/2倍原数组的长度? 2.调用Arrays.copyOf(elementData, newCapacity)方法具体做了什么操作? )

2.赋值:将添加的值放置到size++的位置上

3. 返回:返回true

添加指定元素到指定的位置上代码如下:

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

其流程为:

  • 1.校验下标:调用rangeCheckForAdd方法进行下标校验,不正确则会抛出IndexOutOfBoundsException异常
  • 2.扩容:详见上部分中做的介绍
  • 3.移动数据:将数据index后面的数据,都向后移动
  • 4.赋值:将加入的值放置到index位置中
  • 5.长度增加:长度增加

6.总结

  • 1.ArrayList是基于数组实现的,它的内存储元素的数组为 elementData;elementData的声明为:transient Object[] elementData;
  • 2.ArrayList中EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA的使用;这两个常量,使用场景不同。前者是用在用户通过ArrayList(int initialCapacity)该构造方法直接指定初始容量为0时,后者是用户直接使用无参构造创建ArrayList时。
  • 3.ArrayList默认容量为10。调用无参构造新建一个ArrayList时,它的elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA, 当第一次使用 add() 添加元素时,ArrayList的容量会为 10。
  • 4.ArrayList的扩容计算为 newCapacity = oldCapacity + (oldCapacity » 1);且扩容并非是无限制的,有内存限制,虚拟机限制。
  • 5.ArrayList的toArray()方法和subList()方法,在源数据和子数据之间的区别;
  • 6.注意扩容方法ensureCapacityInternal()。ArrayList在每次增加元素(可能是1个,也可能是一组)时,都要调用该方法来确保足够的容量。当容量不足以容纳当前的元素个数时,就设置新的容量为旧的容量的1.5倍加1,如果设置后的新容量还不够,则直接新容量设置为传入的参数(也就是所需的容量),而后用Arrays.copyof()方法将元素拷贝到新的数组。从中可以看出,当容量不够时,每次增加元素,都要将原来的元素拷贝到一个新的数组中,非常之耗时,也因此建议在事先能确定元素数量的情况下,才使用ArrayList,否则不建议使用。

你可能感兴趣的:(谈谈java中ArrayList)