ArrayList为List的一个实现类,List的实现类有很多,我们该选择在什么时候使用什么集合?需要对他们有一个深入的了解.
1.构造方法,这里我们介绍两个常用的,第一个当属我们的空参构造方法
public ArrayList() {
super();
this.elementData = EMPTY_ELEMENTDATA;
}
这里,ArrayList(后续我会称之为本类)中管理了一个常量EMPTY_ELEMENTDATA,这个常量声明是这样的:
private static final Object[] EMPTY_ELEMENTDATA = {};
这是他内部管理的一个空的对象数组,而elementData的声明同样也是一个对象数组,具体如下:
private transient Object[] elementData;
这里的elementData是类中真实存放数据的一个对象数组,即后续存入数据真正是存放在此对象数组中,由于其类型为Object,所以可以存入所有类型的对象.基于此,其实我们就可以大胆猜测其实ArrayList内部就是基于对象数组实现的,(还有一个构造方法我们后边再给大家引入)那么它就具备了数组的一些特性:存取有序,查询方便,插入删除不便.那么为什么这么说,接下来我们深入剖析一下他的增删改查方法;
增加:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
如上添加时传入了一个参数,此处为泛型,这里就表示可以传入所有类型的参数,内部的实现如上,可能大家都比较想知道的是数组不是必须在初始化的时候就给定长度么?数组不是固定长度么?我们使用集合存入数据时是可以一直存入的昂,具体是如何实现的?此处我们需要关注一下上面的ensureCapacityInternal(size + 1)这个方法,就是他实现了数组的动态扩充,也就是数组会随着元素的添加而不断的扩充,我们去看下他的具体实现:
private void ensureCapacityInternal(int minCapacity) {
if (elementData == EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}ensureExplicitCapacity(minCapacity);
}
上面的方法开始就做了一个简单的验证,判断真实存放数据的数组是否等于空的对象数组,如果为空,则真实存放数据的elementData长度为10.
private static final int DEFAULT_CAPACITY = 10;
//下面是Math.max(a,b)方法的实现,目的是比较二者获取一个大值
public static int max(int a, int b) {
return (a >= b) ? a : b;
}
接下来执行了一个
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
根据存入数据的下标+1来表示当前数据实际存放的数量,从而来判断数据是否已经存满,需要扩充,如果存入数据数量+1减去已有数据数组长度大于零,即执行下面的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);
}
如上,先获取到现在对象数组的长度,即oldCapacity,再通过算法得到新的数组长度:oldCapacity + (oldCapacity >> 1),这里算法实现是原数组长度+原数组向右位移一位,即原数组长度乘以2,因为位移运算效率高,所以此处这么写,相当于oldCapacity +oldCapacity *2.后续两个判断是逻辑性的验证,最后利用Arrays.copyOf方法,根据原数组和传入新数组的长度来得到新的数组,并赋值给了elementData .这里我们要追进去Arrays类中去看下具体是如何实现的:
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,Math.min(original.length, newLength));
return copy;
}
有些新手看到这可能已经被一系列的T[],U[]给整蒙了, 这里具体实现是判断利用反射对存入类型做了最后一步校验,如果不是同一类型,则以传入的新长度为长度,直接构建一个新的Object类型对象数组并返回,(这里对二者的一个区分判断其实并不是很了解,由于System的copyOf方法传入的参数都是Object方法,所以应该是传入的什么参数都可以,而如果说创建数组时,声明存入本数组时一个存放什么元素的数组,那同样可以直接使用original.getClass去获取类型,希望有看懂的能留言探讨),如果是则调用系统的arraycopy方法,将旧的数组中元素依次有序的存入新数组中,并将新数组返回.至于System.arraycopy方法,就是循环将旧数组中的元素依次存入新数组中,由于System.arraycopy方法已经封装为native方法,无法查看.
public static native void arraycopy(Object src, int srcPos,Object dest, int destPos,int length);
到这里,已经可以看出来添加方法,其实核心是一个数组的动态扩充,即丢弃旧的数组,创建新的数组,所以如果频繁的扩充对象数组,就会制造大量垃圾,浪费内存,影响性能,此时,我们如果已知存放数据大致数量的情况下,我们即可以使用第二种构造方法:指定初始化容量,减少内存的大量消耗
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
}
后续明天再撸...