数组与ArrayList
首先我们要知道ArrayList的底层是一个数组,也可以说本质上ArrayList就是一个普通的类,对数组进行的封装,扩展其功能。
对于数组,我们知道数组一旦确定就不能再被改变,而ArrayList可以实现自动扩容,ArrayList所谓的自动扩容其实也是新创建一个数组而已,所以我们首先了解一下数组拷贝,再来看看ArrayList是怎么扩容的。
数组拷贝
1. Arrays.copyof
我们先看看代码,看如何使用它
public static void main(String[] args) {
int[] arr = new int[]{1, 2};
for (int a : arr) {
System.out.println(a);
}
System.out.println("-------------copyOf------------");
int[] arr1 = Arrays.copyOf(arr, 10);
for (int b : arr1) {
System.out.println(b);
}
}
运行结果
1
2
-------------copyOf------------
1
2
0
0
0
0
0
0
0
0
应该不难理解,将原数组arr扩大成长度为10的数组,int[] arr1 = Arrays.copyOf(arr, 10);
2. System.arraycopy()
public static void main(String[] args) {
int[] src = new int[]{1, 2, 3, 4, 5};
int[] dest = new int[10];
System.arraycopy(src, 1, dest, 0, 3);
for (int num :
dest) {
System.out.println(num);
}
}
运行结果:
2
3
4
0
0
0
0
0
0
0
我们去看看System.arraycopy()是啥
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
src:要拷贝的数组
srcPos:要拷贝的数组的起始位置
dest:目标数组
destPos:目标数组的起始位置
length:你要拷贝多少个数据
以上两个方法都是进行数组拷贝的,这个对理解数组扩容技术很重要,而且在ArrayList中也有应用,我们等会会详细说。
现在主角ArrayList正式登场!
我们先来看看它的无参构造函数
ArrayList arrayList = new ArrayList();
我们点进去康康:
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
这里出现了两个不认识的东西:
1. elementData
2. DEFAULTCAPACITY_EMPTY_ELEMENTDATA
我们分别来看看
elementData
transient Object[] elementData; // non-private to simplify nested class access
原来就是一个Object数组的声明呀~ 前面有个transient,它修饰序列化的时候会被忽略掉,这里不展开讲
DEFAULTCAPACITY_EMPTY_ELEMENTDATA
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
这不也是个Object数组嘛?这是一个空数组,所以
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
也就是将一个空的Object数组赋给elementData。
我们之前也提到了,ArrayList其实底层就是数组,所以这里new AarrayList()后就给我们整了一个空数组出来。
但是我们要往里面塞数据呀,所以空数组肯定不行,那我们再去看看它的add()方法看看如何进行数据的添加
ArrayList的add()
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
怎么又有我们不认识的东西?!
1. ensureCapacityInternal()
2. size
ensureCapacityInternal()
翻译一下这个方法名:确保内部容量?????
啥玩意儿,我们不知道,先看看size是什么
size
private int size;
原来就是一个变量
再来看看这一句
elementData[size++] = e;
elementData我们上面有提到过,就是new一个ArrayList时候创建的一个空数组,那这一句的意思就是在这个数组中某一个元素进行赋值呗,但是空数组怎么赋值?所以我们知道上面那一句【ensureCapacityInternal(size + 1);】是干嘛的了吧~
这一步应该就是把数组的容量给确定下来的,赶紧进去看看
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
OMG,怎么又多了一堆不认识的东西?!没关系,看源码就得耐心,一步一步慢慢得剖开它
1. calculateCapacity()
2. minCapacity
3. ensureExplicitCapacity()
我们先来看看calculateCapacity()
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
我们来看这个if语句
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
这个语句其实就是先判断下现在这个ArrayList的底层数组elementData 是不是刚创建的的空数组,现在这个情况当然是啦对吧。
我们进了这个if语句后,看这一句
Math.max(DEFAULT_CAPACITY, minCapacity);
首先我们要清楚minCapacity是啥?在这个方法【calculateCapacity】里面是一个形参吧?然后返回看
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
那它也是【ensureCapacityInternal】的形参呀,所以我们要看调用【ensureCapacityInternal】传进来的参数是什么,往上一找发现是:
ensureCapacityInternal(size + 1);
所以这个minCapacity就是size + 1啦。那现在这个情况也就是minCapacity=1,也就是说将要向底层数组添加第一个元素
所以这个语句
return Math.max(DEFAULT_CAPACITY, minCapacity);
我们就要来看看DEFAULT_CAPACITY是多大啦
private static final int DEFAULT_CAPACITY = 10;
这就是ArrayList的底层数组elementData初始化容量啊,是10。
我们再看看这个if语句
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
不难发现,这个语句必定只执行一次,为什么呢?这个elementData为空的时候,不就是刚创建ArrayList的时候吗?只有第一次添加数据才会执行这步,这一步就是为了指定默认数组长度(10)的,指定一次就ok了
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
这里我们已经分析好了calculateCapacity()方法咯,现在返回的就是默认数组长度10,所以现在来探究一下ensureExplicitCapacity
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
这里形参minCapacity = 10
此时elementData还是一个空数组呀,所以肯定会进去执行grow
grow方法
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);
}
我们来康康第一句
int oldCapacity = elementData.length;
这句得到了原来数组的长度
int newCapacity = oldCapacity + (oldCapacity >> 1);
得到新容量的,后面的这oldCapacity >> 1就相当于oldCapacity /2,是移位运算,也就是扩容为原来的1.5倍
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
目前数组长度为0,所以这个新的容量newCapacity也是0,而minCapacity 则是10,所以会执行方法体内的赋值操作,也就是现在的新容量成了10。
elementData = Arrays.copyOf(elementData, newCapacity);
我们继续往下瞅瞅发现了Arrays.copyOf,上面特意讲过的数组拷贝呀,那这个elementData就是原来的数组,newCapacity就是新数组的容量。
这里饶了一大圈,就是为了创建一个默认长度为10的底层数组,苍天啊。。。
简单来说grow方法就是拿来扩容滴
ArrayList的有参构造
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);
}
}
这不就是直接创建嘛?!
我们再来康康这个
else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
}
EMPTY_ELEMENTDATA
private static final Object[] EMPTY_ELEMENTDATA = {};
那么EMPTY_ELEMENTDATA和 DEFAULTCAPACITY_EMPTY_ELEMENTDATA有什么区别呢?
这两个都是空的Object数组
但是DEFAULTCAPACITY_EMPTY_ELEMENTDATA
是在无参构造的时候elementData被赋值为DEFAULTCAPACITY_EMPTY_ELEMENTDATA。而有参构造的时候,elementData被赋值为EMPTY_ELEMENTDATA。虽然可能会认为两个都是空数组,没什么区别,但是我们前面分析了add方法,就应该知道这两个的区别是什么。
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
当我们添加第一个元素的时候,minCapacity肯定都是等于1的,但是如果我们用的是无参构造,return的就会是DEFAULT_CAPACITY,也就是默认数组容量10,而我们用有参构造的话,return的就是minCapacity,是1
所以我们扩容的时候也会有区别,无参构造扩容是基于默认初始化大小10进行扩容,依次是10,15,22,33这种1.5倍扩容
而如果用
ArrayList arrayList = new ArrayList(0);
就是基于我们自己设置的大小0开始扩容,一次是0,1,2,3,4,6这种1.5倍扩容
参考:
https://blog.csdn.net/sinat_33921105/article/details/103773539