title: ArrayList原理解析
date: 2019-03-02 08:37:25
tags: Java基础
ArrayList的基本原理
ArrayList
是我们平时编码经常用到的动态数组容器类,要想分析它的原理,我们先来看看一个简易的DynamicArray类(摘自Java编程的逻辑)
一个简易的动态数组类
public static class DynamicArray<E>{
private static final int DEFAULT_CAPACITY = 10;
private int size;
private Object[] elementData;
public Dynamicarray() {
this.elementData = new Object[DEFAULT_CAPACITY];
}
public void ensureCapacity(int minCapacity){
int oldCapacity = elementData.length;
if(oldCapacity >= minCapacity){
return;
}
int newCapacity = oldCapacity * 2;
if(newCapacity < minCapacity){
newCapacity = minCapacity;
}
elementData = Arrays.copyOf(elementData,newCapacity);
}
public void add(E e){
ensureCapacity(size + 1);
elementData[size++] = e;
}
public E get(int index){
return (E) elementData[index];
}
public E set(int index,E e){
E e1 = get(index);
elementData[index] = e;
return e1;
}
}
在这个类中,定义了一个内部数组elementData,数组元素个数size,一个静态常量DEFAULT_CAPACITY,它表示数组的默认空间大小。这个动态数组类的操作基本都是基于内部数组element和size。
ensureCapacity
方法,在每次做add操作时,都会被调用,它是检查当前数组容量,并增大容量,然后根据新的容量,复制原来数组的。
ArrayList源码解析:
ArrayList的基本原理与上文中的动态数组类是差不多的,它同样有静态常量默认空间,实例变量内部数组、元素个数。同样,内部方法基本都是操作elementData这个数组,size实时记录着这个数组的大小,首先我们从
add
方法说起(各源码内注释已说明白,就不再叙述)。
添加方法add(E e):
public boolean add(E e) {
//首先,调用ensureCapacityInternal方法,确保数组容量够。将当前元素个数加一,即最小容量minCapacity传入方法。
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
检查数组容量方法ensureCapacityInternal(int minCapacity):
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//如果数组为空,则返回默认值与minCapacity之间的最大值
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//如果minCapacity大于当前数组的长度,就调用grow方法增大容量,
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
modCount++表示内部的修改次数,而这个参数与arrayList的迭代有关,下篇博客再讲解。
增大数组容量grow方法(int minCapacity):
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//定义一个新的容量newCapacity,它的值为当前容量右移一位,即除以2,再加上当前数组容量,即当前数组容量的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果还小于当前元素个数加一,新的容量就等于minCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果newCapacity大于一个静态常量MAX_ARRAY_SIZE,就调用hugeCapacity方法,
将newCapacity设定为Integer包装类的最大值0x7fffffff,其中MAX_ARRAY_SIZE为Integer.MAX_VALUE-8,
减8是因为在一些vm中,在数组中会保留一些头信息,尝试分配更大的数组可能导致OutOfMemoryError:
请求的数组大小超过VM限制
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
//以newCapacity为数组size,创建了一个新的数组,复制原内容,赋值给elementData
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//如果minCapacity还大于MAX_ARRAY_SIZE,就返回Integer的最大值0x7fffffff
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
再来看看remove(int index)方法:
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);
//将size减1,GC会回收未经使用的对象
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
这里modCount依旧加一。
结论
上面,我们介绍了
ArrayList
的add
和remove
方法,其他方法也都是对内部数组elementData和元素个数size的操作,就不再探究了。总之,ArrayList
就是一个动态数组,实现动态的原理,就是对内部的elementData、size和默认空间DEFAULT_CAPACITY
进行操作。创建ArrayList
时,会默认初始化一个DEFAULT_CAPACITY
大小的数组。每次要做增加操作,就进行数组容量检查,若不够,就增加容量,做删除操作,size就减一,保持size实时记录当前元素个数。