前言:我相信大家对ArrayList并不陌生,这是我们最经常使用的一个基于数组实现的列表,但我们平常可能也只是局限于使用,并没有深入去了解过它的实现。所以出于应对面试或者是为了学习数据结构,学习一些集合源码对我们来说都是很有帮助的。
类图关系
成员变量
既然如此,那我们话不多说,让我们先从ArrayList开始学习吧。首先,正如前言所说,ArrayList是一个基于数组实现的列表,那么在它的内部肯定会维护一个数组,这个数组最开始时并没有初始化,但它拥有一个默认的初始化长度10。
//默认容量大小
private static final int DEFAULT_CAPACITY = 10;
//对象数组
Object[] elementData;
//元素个数
private int size;
//空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
//默认容量大小的空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
构造函数
我们了解了成员变量后,接下来我们去看下它的主要构造函数和作用吧。ArrayList主要有两个构造函数,分别是无参构造函数和一个可以自定义初始化容量的构造函数。当我们调用无参构造函数时,它会为数组初始化一个默认初始化长度为10的数组。但值得我们注意的是,它只是为我们的赋值一个空数组,只是再后续比较容量函数中将默认的10拿去比较而已。
//无参构造函数,
public ArrayList() {
//赋值一个空数组
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//传入初始化容量
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
//创建一个初始化容量的数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//如果等于0,则将这个空数组赋值给对象数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
//其他为非法
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
成员方法
1.如何添加一个元素 ?
既然这样,那让我们看下如果我们不设初值时,ArrayList是怎么初始化第一个数组的吧。首先让我们一步步的看add(E e)方法。它主要分为两步走,分别是如下的两步。
第一步:确定内部容量的大小
第二步:添加元素
public boolean add(E e) {
//确定内部容量大小
ensureCapacityInternal(size + 1); // Increments modCount!!
//先赋值,再移动指针
elementData[size++] = e;
//插入成功则返回true
return true;
}
紧接着让我们再去看下它是确定内部容量大小的吧。在计算容量capacity时,它会判断调用的是什么构造函数;如果是无参的,它会计算容量。如果是有参的它直接返回传进去的minCapacity,而这个minCapacity是数组中元素的个数。正如下面这个方法所做的事情。
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//如果是默认大小的空数组,即调用无参构造函数时
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
/*
将默认的容量大小与元素个数进行比较,取最大值返回
*/
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//这个是调用有参构造函数。
return minCapacity;
}
在我们看完计算容量大小的方法后,我们可以返回到上一个确定容量的方法了。也就是 ensureExplicitCapacity(int minCapacity) 这个方法。在这个方法中所做的事情主要是判断是否需要扩容。如果需要扩容,则会调用扩容方法。
private void ensureExplicitCapacity(int minCapacity) {
//被修改的次数加一
modCount++;
// overflow-conscious code
/*
如果你传过来的这个最小容量比数组大小大了,他就会进行扩容,
这里的最小容量就是元素个数+1,比如数组大小是10,size+1=11>10就会进行扩容
*/
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
在完成上面那些操作后,就会进行尾插入操作,至此一个完整添加操作就完成了。最后执行插入的方法如下
//进行尾部插入
elementData[size++] = e;
2.如何进行扩容 ?
在介绍如何添加元素时,我们题到如果元数个数大于数组长度时,会进行扩容操作。那么ArrayList是如何进行扩容的呢?接下来让我们了解一下前面提到的 grow(int minCapacity) 扩容方法。在扩容方法中,会按旧容量的1.5倍大小进行一次扩容。如果扩容后的容量还是小于元素个数,则会将元素个数赋值给新的容量。如果容量比规定的最大容量MAX_ARRAY_SIZE还大,则新的容量等于Integer的最大值。在初始化newCapacity后,则会调用Arrays.copyOf()方法将老数组的元素搬到扩容后的新数组返回。
private void grow(int minCapacity) {
// overflow-conscious code
//旧容量为数组大小
int oldCapacity = elementData.length;
//新数组按就数组1.5倍扩容
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果扩容后的数组容量还是小于元素个数,则直接将这个元素个数赋值给新的容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//如果新的容量比规定最大的容量还大,则会调用hugeCapacity的方法,返回的是Integer的最大值
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
//调用Arrays的copyOf方法复制一个新的数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
3.如何按索引添加一个元素?
在我们理解完一个基本的添加方法和扩容方法后,让我们看一下如何指定索引的位置添加一个元素?其实指定索引的位置添加元素主要的思路是,将指定索引后的元素往后搬,空出一个位置再将我们要插入的元素插入到index上。
/*
我们看一下Sysytem类的arraycopy方法,其中的四个参数分别代表是:
@src : 源数组
@srcPos : 源数组的下标
@dest:目标数组
@destPos:要移动的元素下标
@length:要移动元素的个数
*/
arraycopy(Object src,int srcPos,Object dest, int destPos,int length);
思路过程:我们index为1的位置插入5
原始数组:1,2,3,4
搬移元素:1, ,2,3,4
插入元素:1,5,2,3,4
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++;
}
4.如何删除元素 ?
介绍完添加元素的操作,我们来介绍一下删除操作。删除操作remove(index)方法中,它的思路和上面讲得按索引位置添加元素刚好相反,它是将index的旧值返回,然后再将index位置后的元素往前覆盖。其中需要注意的点如numMoved这个变量是指需要搬迁的元素的个数。
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);
//将这个元素设置为null方便 GC
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
文章末尾
至此,我已经简单介绍了ArrayList的一些重要的成员变量,构造函数和成员方法。相信大家在阅读完,会对ArrayList有了基本认识。ArrayList的实现在Java集合中还是相对简单,也是线程不安全的。如果有疑问的或者存在你认为可以改进的地方,可以找我交流或给我建议,让我们一起进步哟。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。