ArrayList源码分析

目录

ArrayList简介

 初始化

构造函数

扩容机制

其他方法

ArrayList简介

ArrayList 的底层是数组队列,相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用ensureCapacity操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。

ArrayList 继承于 AbstractList ,实现了 List, RandomAccess, Cloneable, java.io.Serializable 这些接口。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    private static final long serialVersionUID = 8683452581122892189L;

 初始化

 /**
     * 默认初始容量大小
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 空数组(用于空实例)。
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    //用于默认大小空实例的共享空数组实例。
    //我们把它从EMPTY_ELEMENTDATA数组中区分出来,以知道在添加第一个元素时容量需要增加多少。
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * 保存ArrayList数据的数组
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * ArrayList 所包含的元素个数
     */
    private int size;

这里提前说明几个变量有助于后面理解:

 1. EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA都是空数组,不过前者容量为0,后者初始容量为0,当有元素进来时候按照DEFAULTCAPACITY增加,也就是容量直接由1->10(假设开始往空数组只添加一个元素)

2.transient关键字出现的时候不参与类的序列化,用来保留传进来的数组数据

3.size为实际有的元素个数,而后面会出现的数组.length,是数组的长度,后者大于等于size。比如刚开始空集合加入一个元素,但是扩容逻辑要求先一下子扩到10,所以size为1,.length为10.

构造函数

注意点:

1. 无参构造用的是DEFAULTCAPACITY_EMPTY_ELEMENTDATA,像上面所说的,当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为 10。 在下面的add方法中会详细的讲。

2.带容量/集合作为参数的有参构造,参数为0是默认给的是另一个空数组EMPTY_ELEMENTDATA

3.最后一种有参构造,.toArray()方法出来的不是object对象情况下,要进行copyOf方法进行拷贝

注意:toArray是浅拷贝,只是让elementData指向c.toArray的地址,所以当不是object对象时要进行深拷贝copyOf,生成一个新的数组。

/**
 * 默认初始容量大小
 */
private static final int DEFAULT_CAPACITY = 10;

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

/**
 * 默认构造函数,使用初始容量10构造一个空列表(无参数构造)
 */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

/**
 * 带初始容量参数的构造函数。(用户自己指定容量)
 */
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {//初始容量大于0
        //创建initialCapacity大小的数组
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {//初始容量等于0
        //创建空数组
        this.elementData = EMPTY_ELEMENTDATA;
    } else {//初始容量小于0,抛出异常
        throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
    }
}


/**
 *构造包含指定collection元素的列表,这些元素利用该集合的迭代器按顺序返回
 *如果指定的集合为null,throws NullPointerException。
 */
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

扩容机制

add,一个参数

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

这里可以看到,minCapacity取得是10和传进来的size+1的最大值,就在这个时候出现了minCapacity和size+1的不一致的可能。

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

        如果此时minCapacity满足不了数组的长度,开始扩容

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
 
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

传进来的minCapacity就是arg0,args2就是数组长度+数组长度二进制数右移1位(*1/2),也就是1.5倍,如果还不够,就让 最后的长度为需要的arg0,如果已经大到大于2^31 - 1,那就执行hugeCapacity;如果都没有这些意外情况,就将数组按照1.5倍原长度拷贝成一个新数组

	private void grow(int arg0) {
		int arg1 = this.elementData.length;
		int arg2 = arg1 + (arg1 >> 1);
		if (arg2 - arg0 < 0) {
			arg2 = arg0;
		}
 
		if (arg2 - 2147483639 > 0) {
			arg2 = hugeCapacity(arg0);
		}
 
		this.elementData = Arrays.copyOf(this.elementData, arg2);
	}

在下面的代码中,当 `minCapacity` 小于0时会抛出 `OutOfMemoryError` 异常,而不是说 `minCapacity` 小于0本身会导致内存溢出。这是因为在 Java 中,通常情况下我们不希望数组的容量为负数,因为数组的长度应该是一个非负整数。如果 `minCapacity` 为负数,通常表示发生了某种错误,比如传入了非法的参数,所以抛出 `OutOfMemoryError` 异常来提醒开发者程序出现了问题。 

//比较minCapacity和 MAX_ARRAY_SIZE
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
    throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}

 

 

add,两个参数 

涉及到index就要检查是否在合法范围内

arrayCopy会在后面写,参数为(原数组,原数组起始位置,目标数组,目标数组起始位置,想复制的长度)

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

其他方法

remove

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

注意是将元素置空,而不是把对应的位置也删掉了,等着系统 GC。

clear 

同样是全员置空                        

    public void clear() {
        modCount++;
 
        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;
 
        size = 0;
    }

  subList
我们看到代码中是创建了一个ArrayList 类里面的一个内部类SubList对象,传入的值中第一个参数时this参数,其实可以理解为返回当前list的部分视图,真实指向的存放数据内容的地方还是同一个地方,如果修改了sublist返回的内容的话,那么原来的list也会变动。

所以阿里巴巴开发规约中有一条如下,底层是根据modCount一不一致去判断有没有并发修改异常,所以在ArrayList中,modCount很重要,记得++。

	public List<E> subList(int arg0, int arg1) {
		subListRangeCheck(arg0, arg1, this.size);
		return new ArrayList.SubList(this, 0, arg0, arg1);
	}

Arrays.copyOf

    public static int[] copyOf(int[] original, int newLength) {
      // 申请一个新的数组
        int[] copy = new int[newLength];
  // 调用System.arraycopy,将源数组中的数据进行拷贝,并返回新的数组
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

还有一个常见的相关方法:System.arraycopy() 方法

    // 我们发现 arraycopy 是一个 native 方法,接下来我们解释一下各个参数的具体意义
    /**
    *   复制数组
    * @param src 源数组
    * @param srcPos 源数组中的起始位置
    * @param dest 目标数组
    * @param destPos 目标数组中的起始位置
    * @param length 要复制的数组元素的数量
    */
    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

 

可以发现 copyOf()内部实际调用了 System.arraycopy() 方法

区别:

arraycopy() 需要目标数组,将原数组拷贝到你自己定义的数组里或者原数组,而且可以选择拷贝的起点和长度以及放入新数组中的位置 copyOf() 是系统自动在内部新建一个数组,并返回该数组

  • 19
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值