ArrayList源码的简单分析

引言

ArrayList是 java.util 包下的一个类,是List接口的实现类。下面我们会从ArrayList的底层数据结构构造方法元素添加三个方面对ArrayList的源码做一个简单的分析。在分析之前,我们先思考3个问题:

  1. ArrayList的初始容量是多少?
  2. ArrayList的容量是什么时候被初始化的,是在创建ArrayList对象的时候吗?
  3. ArrayList是如何扩容的?

底层数据结构

ArrayList类中有一个属性,如下:

transient Object[] elementData;

可以看到该属性是一个Object类型的数组,我们通过add方法向ArrayList中添加元素时,元素都是保存在该数组中的。值得我们注意的一点是,目前还没有指定这个数组的大小。那么是什么时候指定的呢?后面会说到。

tips:被transient关键字修饰的变量不会被序列化。

构造方法

ArrayList中有三个构造方法,如下:

// 无参的构造方法
public ArrayList() {
	this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

// 有参的,参数是指定初始容量的构造方法
public ArrayList(int initialCapacity) {
	if (initialCapacity > 0) {
		this.elementData = new Object[initialCapacity];
	} else if (initialCapacity == 0) {
		this.elementData = EMPTY_ELEMENTDATA;
	} else {
		throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
	}
}

// 有参的,参数是Collectionl类型的构造方法
public ArrayList(Collection<? extends E> c) {
	Object[] a = c.toArray();
	if ((size = a.length) != 0) {
	if (c.getClass() == ArrayList.class) {
	    elementData = a;
	} else {
	    elementData = Arrays.copyOf(a, size, Object[].class);
	}
	} else {
	// replace with empty array.
	elementData = EMPTY_ELEMENTDATA;
}
}

三个构造方法中都出现了一个常量,EMPTY_ELEMENTDATA,它其实是一个空的对象数组,在ArrayList类中的定义如下:

private static final Object[] EMPTY_ELEMENTDATA = {};

上面列举了三个构造方法,我们着重看一下第一个,不带参数的,也是我们经常使用的。
它的方法体很简单,只有简单的一个语句:this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;,将一个常量 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 赋值给 elementDate 属性,前面我们说过,elementData 是一个 Object 数组。那么常量 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 是什么呢?其实它是一个空的对象数组,它的声明如下:

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

分析到此处,我们知道了在创建ArrayList对象时,存储元素的 elementData 数组被初始化为一个空的对象数组。

添加元素

我们常用的向ArrayList中添加元素的方法是add(),它的定义如下:

public boolean add(E e) {
	ensureCapacityInternal(size + 1);  // size表示元素个数。第一次调用add方法,size的大小肯定为0.
	elementData[size++] = e;
	return true;
 }

ensureCapacityInternal 方法的定义:

 private void ensureCapacityInternal(int minCapacity) { 
  	ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
 }
ensureExplicitCapacity 方法的定义:
private void ensureExplicitCapacity(int minCapacity) {
  modCount++;
  
  if (minCapacity - elementData.length > 0)
       grow(minCapacity);
}

calculateCapacity 方法的定义:

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {   // 如果我们是通过无参的构造方法创建的ArrayList对象,这一步一定为true.
        return Math.max(DEFAULT_CAPACITY, minCapacity);  // 返回 DEFAULT_CAPACITY 和 minCapcatiy 中的较大值
    }
    return minCapacity;
}

常量 DEFAULT_CAPCITY 的声明如下:

// 默认的初始化容量
private static final int DEFAULT_CAPACITY = 10;
/**
 * 数组扩容的方法
*/
private void grow(int minCapacity) {
    int oldCapacity = elementData.length;   // 旧的容量
    int newCapacity = oldCapacity + (oldCapacity >> 1);  // 新的容量等于旧的容量的1.5倍。(>>:右移运算,“>> 1” 相当于除以2。)
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity); // 数组拷贝,这一步数组扩容的本质
}

调用有点多,但是并不难,我们简单梳理一下,从上到下调用的方法依次是:add --》ensureCapacityInternal --》ensureExplicitCapacity --》grow。图示如下:
add方法的向下调用过程
我们需要着重关注的是 grow 方法,从上述代码的注释可以得出以下结论:

  • ArrayList的默认初始化容量是 10;
  • 通过无参的构造方法创建ArrayList对象,并没有指定ArrayList的初始化容量10,只是给了一个空的对象数组,真正给初始化容量是在第一次调用add方法时指定的;
  • 数组扩容的本质数组拷贝,调用的Arrays.copyOf()方法

以上就是ArrayList源码简单的分析,感谢阅读和指正,欢迎评论区交流!
Respect表情包

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值