ArrayList源码分析

一、创建一个 ArrayList 的对象
ArrayList list = new ArrsyList();

当实例化 ArrayList 时,我们看一下构造器内部都做了什么。

// transient Object[] elementData;
// private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

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

构造器内部对 elementData 进行了初始化,把它初始化为一个空数组,ArrayList 的元素存放在这个数组里。

二、添加一个元素
ArrayList list = new ArrayList();
... // 可能添加很多个元素了
list.add(new Object());

我们还是看源码

// 将指定的元素追加到集合的末尾
public boolean add(E e) {
	// 确保容量是够用的,不够用的话就扩容
    ensureCapacityInternal(size + 1); // size: elementData 数组中元素个数,而不是数组的长度
    elementData[size++] = e;
    return true;
}

接下来我们来看这个扩容的方法

private void ensureCapacityInternal(int minCapacity) {
	/* 
	如果 elementData 和 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 地址相同,这个最小容量
	minCapacity 取 DEFAULT_CAPACITY(默认容量 10) 和 minCapacity 的最大值。
	
	那么什么时候地址值相同呢?
	只有第一次添加元素的时候,地址值才会相同,因为当我们实例化 ArrayList 的时候,
	this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
	之后在扩容的时候这个 elementData 地址值就改变了,我在下面有说在哪里改变的
	 */ 
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

	// 确定到底需不需要扩容
    ensureExplicitCapacity(minCapacity);
}

接下来看这个方法

private void ensureExplicitCapacity(int minCapacity) {
    modCount++; // 修改次数 + 1

	// 如果所需要的最小容量 minCapacity 大于 数组的长度,那就需要扩容了
    if (minCapacity - elementData.length > 0)
        grow(minCapacity); // 扩容
}

只有走到这里才能进行扩容

private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    // 扩容为原来的 1.5 倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 如果新的容量还不够用,那就你需要多少我就给你多少
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // 如果新的容量比 MAX_ARRAY_SIZE 还大,我们就拿最小容量 minCapacity 和 MAX_ARRAY_SIZE作比较,看下面 hugeCapacity() 这个方法
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // 进行数组的复制,扩容这里就完事了。接着分析一下?还是?
 	// 注意:在 copyOf() 这个方法里,elementData 地址值改变了!!!
    elementData = Arrays.copyOf(elementData, newCapacity);
}

这是 hugeCapacity() 这个方法,其实里面就进行了判断

// private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

private static int hugeCapacity(int minCapacity) {
	if (minCapacity < 0)
       throw new OutOfMemoryError();
   // 如果最小容量 minCapacity 大于这个 MAX_ARRAY_SIZE,就直接把 Integer.MAX_VALUE 给返回了,否则就返回 MAX_ARRAY_SIZE
   return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}

还是继续分析一下吧,是怎么进行扩容的

// 刚才分析到这里
// elementData = Arrays.copyOf(elementData, newCapacity);

public static <T> T[] copyOf(T[] original, int newLength) {
	return (T[]) copyOf(original, newLength, original.getClass());
}

接着往下来

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
	// 如果这个数组是 Object[],就根据新的容量直接创建一个 Object[] 返回了,
	// 如果不是,就根据新的容量创建那个类型的数组,具体怎么实现的,简单点说,就是 Java 调用了 非 Java 的接口,往下看都调用了哪些方法
	// 注意:在这里 elementData 地址值改变了!!!
    @SuppressWarnings("unchecked")
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        
    // 这个在熟悉不过了吧,就是一个数组的复制
    System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
    return copy;
}

分析一下这个Array.newInstance(newType.getComponentType(), newLength);

public static Object newInstance(Class<?> componentType, int length)
    throws NegativeArraySizeException {
    return newArray(componentType, length);
}

// 这下到底了吧,都看到 native 了
private static native Object newArray(Class<?> componentType, int length)
    throws NegativeArraySizeException;

终于把扩容这里说的差不多了,我们终于可以说说添加元素这个事了

public boolean add(E e) {
    ensureCapacityInternal(size + 1);
    // 就直接把元素放里面就可以了呗,你还在那里想什么想
    elementData[size++] = e;
    return true;
}
三、移除一个元素
ArrayList list = new ArrayList();
... // 可能添加很多个元素了
list.add(new Object());
list.remove(int index);

继续带你体验看源码的乐趣!!!

public E remove(int index) {
	// 检查这个索引是否是有效的,无效就抛出异常,代码在下面,里面就抛了个异常
    rangeCheck(index);

    modCount++; // 修改次数 + 1
    // 返回 elementData[index] 返回给 oldValue,代码在下面
    E oldValue = elementData(index);

	// 要向前移动元素的个数
    int numMoved = size - index - 1;
    if (numMoved > 0)
		// 把你要移除元素的后面的元素往前移动一位
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    // 把最后一个元素干掉,同时 size--
    elementData[--size] = null;

	// 把移除元素的那个值返回,这个是不是比那个 add() 舒服多了 ^_^
    return oldValue;
}

private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

@SuppressWarnings("unchecked")
E elementData(int index) {
    return (E) elementData[index];
}
四、插入一个元素
public void add(int index, E element) {
	// 检查你插入的索引是否在 [0, size] 的范围里,不在的话会抛出异常的,rangeCheckForAdd() 代码在下面
    rangeCheckForAdd(index);

	// 这个操作是不是很似曾相识,在 add() 里面就调用了这个方法,保证容量是够用的,不够用的话会扩容
    ensureCapacityInternal(size + 1);
    // 将index 后面的元素向后移动一位
    System.arraycopy(elementData, index, elementData, index + 1,  size - index);
	// 现在可以把你要插入的元素放进来了
    elementData[index] = element;
    size++; // 元素个数 + 1
}

private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值