ArrayList和HashMap一样,在大家的平时编程中,经常被使用,算是使用率最高的容器了,ArrayList其实内在就是一个数组,现在我来简要介绍一下ArrayList其内部的原理和核心技术。
首先ArrayList内部使用的是一个数组来存储数据,然后还有一个size成员变量来记录该List中已经存放的数据个数。
我们知道,数组是固定长度的,在定义之初就定义好的,那为什么ArrayList在使用的时候,不用考虑这个数组越界等问题呢?其实在ArrayList里面有一个方法,就是
ensureCapacity,具体实现定义如下
public void ensureCapacity(int minCapacity) {
modCount++;
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity) {
Object oldData[] = elementData;
int newCapacity = (oldCapacity * 3)/2 + 1;
if (newCapacity < minCapacity)
newCapacity = minCapacity;
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
这个方法是用于在队列长度不够的时候,自动扩充队列长度的,每次扩充的比例是老长度*3/2 + 1,也就是说将老的长度扩大1.5倍,并且再加1,这个为什么以这个比例来扩展,背景和原因不太清楚。
接着,我们来看下里面的具体方法的实现
trimToSize
public void trimToSize() {
modCount++;
int oldCapacity = elementData.length;
if (size < oldCapacity) {
elementData = Arrays.copyOf(elementData, size);
}
}
这个方法的目的就是将这个List内部的数组后面空的对象直接移除掉,类似String的trim方法,可以节省空间。比如这个List内部的数组已经定义为了32的长度,但是实际内部只放了12个数据,那么这个时候,调用了这个方法之后,会重新申请一个12长度的数组,将原来数组中的12个数据拷贝过来,然后将原来的32长度的数组destroy。
接着,我们看下indexOf方法的实现
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
这个方法,其实就是遍历数组,然后找到内容是一样的对象出来,不过这里需要注意的一点就是,ArrayList的元素是可以重复的,所以这里找到的也是第一个匹配的记录。
既然有indexOf,而且元素又是可以重复的,所以必然也就有一个lastIndexOf
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
这个的实现思路和上面的方法类似,只是从数组的最后面查询起。
接下来需要关注的是几个toArray的方法,这里,通过看内部的实现可以知道,其实返回的是一个新的数组,并且是通过内存拷贝的方式,具体的方法有
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
后面的add方法和set方法如下
public E set(int index, E element) {
RangeCheck(index);
E oldValue = (E) elementData[index];
elementData[index] = element;
return oldValue;
}
public boolean add(E e) {
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
这两个方法也没啥技术含量,对于set方法来说,就是先检测一下这个index是否合法,如果不合法,就抛出异常,否则就将这个位置的数据替换掉,同时返回老数据。
对于add方法,则是先对长度+1做一次ensureCapacity,可能有概率会扩充这个数组长度,然后将数据追加到数组的有数据的最后一个位置的后面。
后面的remove等方法就不继续介绍了,只要知道了ArrayList其实内部就是一数组,并且会自动扩展长度,最后对外呈现就是一个不需要关心其长度的List,其他的一些方法就逗比较好理解了。
最后有一个问题需要特别说明,就是如果在new的时候不指定初始化长度,那么默认给的长度就是10,所以一般推荐大家在new一个ArrayList的时候,一定要指定一下初始化长度,减少ArrayList自己去扩充长度照成的内存、性能的浪费。