java ArrayList的一些理解

1.ArrayList的扩容机制

在了解ArrayList的各种操作之前我们先来看一下ArrayList类中定义的一些成员变量。

private static final int DEFAULT_CAPACITY = 10; //ArrayList默认的容量大小

private static final Object[] EMPTY_ELEMENTDATA = {};//用于空实例的共享空数组实例

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

transient Object[] elementData; //实际存储数据列表元素的Object数组缓冲区,该数据的长度就是ArrayList的
大小,如果该数据是空的或者elementData==DEFAULTCAPACITY_EMPTY_ELEMENTDATA
当添加第一个元素的时候该Object数组会被分配DEFAULT_CAPACITY大小的空间

private int size; //ArrayList实际的大小(包含元素的个数)

我们先来看一下ArrayList中的构造方法

//无参的构造方法
public ArrayList() {
	//ArrayList会默认给elementData赋值一个空的Object数组
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

//指定初始容量大小的构造方法
public ArrayList(int initialCapacity) {
	//如果指定了一个初始的数组大小ArrayList会直接分配长度与该值一样的的elementData对象数组
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        //如果initialCapacity赋值为0则ArrayList会初始化elementData为EMPTY_ELEMENTDATA的空对象数组
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

//指定一个集合对象的构造方法
public ArrayList(Collection<? extends E> c) {
	//将集合参数转成数组(c.toArray()可能得到其他如String,Integer...类型的数组)
        elementData = c.toArray();
        /如果参数不是一个空的集合(这里重新定义list实际的大小)
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            //判断这里elementData的类型是否是Object[],不是则需要重新拷贝一个新的Object数据赋值给elementData
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // 赋值一个空的对象数据
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

接下来我们先看看最常用的add操作

public boolean add(E e) {
	//ArrayList容量的初始大小不是在构造方法中确定的而是在第一次add的操作中确定初始容量的值
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
//接下来我们进入ensureCapacityInternal方法
private void ensureCapacityInternal(int minCapacity) {
	//如果elementData数组是DEFAULTCAPACITY_EMPTY_ELEMENTDATA空对象数组,数组的初始大小会给默认的DEFAULT_CAPACITY也就是10;
        //注:如果是new ArrayList(0)分配的elementData = EMPTY_ELEMENTDATA则不会进入这个方法会继续往下走
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
	//这个方法则是判断是否需要扩容
        ensureExplicitCapacity(minCapacity);
    }

private void ensureExplicitCapacity(int minCapacity) {
	//数组元素的修改次数(增加、删除、修改的次数)
        modCount++;

        // overflow-conscious code
        //判断elementData对象数据是否还有足够容量新增一个元素,如果不够需要进行扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

 //ArrayList维护的对象数组扩容的方法
 private void grow(int minCapacity) {
        // overflow-conscious code
        //1.先获取扩容前的对象数组的大小
        int oldCapacity = elementData.length;
        //2.计算新的对象数组容量大小,这里是oldCapacity的1.5倍(oldCapacity >> 1 即oldCapacity/2),因此ArrayList的每次扩容都是旧的容量的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //3. 如果扩容后的大小仍然不满足当前所需要的容量那么会定义扩容后的大小为当前所需要的容量大小
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //4.如果新的容量大小大于Integer.MAX_VALUE的最大值-8(这里为什么是减去8注释的解释是一些虚拟机会在数组中存储一些 header words),会触发数组最大容量方法;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        //5.复制旧的数组内容到新的长度的数组同时赋值给elementData来达到扩容的目的
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    
private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        //如果实际需要的大小大于Integer.MAX_VALUE的最大值-8会给定一个最大的数组容量(即Integer.MAX_VALUE)不然就分配Integer.MAX_VALUE的最大值-8的大小
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }    

ArrayList在指定位置插入一个元素

 public void add(int index, E element) {
 		//检查index是否合法
        rangeCheckForAdd(index);
		//检查是否需要扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //这个方法即起到移动数组位置的作用(解释见下面)
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        //覆盖数组中该索引的值来达到插入的效果,由此可见ArrayList中在指定位置插入元素需要移动大量元素,效率相对比较低。                 
        elementData[index] = element;
        size++;
    }

/**  * @param      src      原数组 
     * @param      srcPos   原数组开始copy的起始位置
     * @param      dest     目的数组
     * @param      destPos  目的数组插入数据的起始位置
     * @param      length   原数组被copy的数组元素个数
**/     
public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

举个例子
Object [] a = {11,12,13,14,15};
Object [] b = {11,12,13,14,15,null};
System.arraycopy(a, 2, b, 3,3);
for (Object i : b) {
  System.out.print(i);
}

输出结果为:
11
12
13
13
14
15
即原始数组a后的三个元素都被copy到了目标数组第三个元素的后面,目标数组第三个元素的后面的数据都会被覆盖。

ArrayList中大量使用了arraycopy这个方法用来起到移动数组元素的效果(实际为数组拷贝)看一下删除操作

 //删除list中指定位置的元素同时返回该元素值
 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;

        return oldValue;
    }
posted @ 2021-04-09 15:20  zzzYi丶  阅读(73)  评论(0)    收藏  举报