ArrayList
ArrayList
以数组存储数据的集合。默认数组长度为10
,是一个Object[] elementData
的数组。
以构建不带构造参数的ArrayList
来看看运行逻辑。
这里的默认就是一个空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}
;
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
size
是数组的大小默认为0。modCount
表示数组新增,删除
次数,默认为0,当使用方法为数组增加元素,删除元素时modCount会递增。
public boolean add(E e) {
modCount++;//操作次数递增
add(e, elementData, size);
return true;
}
/**
注意:该方法不是静态方法,且是私有方法。
e : 要添加到数组的元素
elementData:被添加元素的数组
s:被添加元素的数组大小
*/
private void add(E e, Object[] elementData, int s) {
//判断了传递的大小 == 数组的长度,这样不能避免空指针。
//这样设计有个好处。此处elementData是持有对象数组的引用,s是对象数组大小,实际值副本引用,因此当数组被更改了,就不会往下执行,能规避一定的多线程下问题。
//假设线程A,B同时操作该add方法,那么elementData与s都会是正确的。因此并不能完全的线程安全
if (s == elementData.length)
//grow:增长。增长并返回一个数组,再次修改elementData的引用
//完成操作,此时的elementData = new Object[10];
elementData = grow();
//将e添加到当前数组大小的位置。elementData[0] = e;
elementData[s] = e;
//由于size是从0开始,因此下个元素位置等于0+1 = 1。所以ArrayList的数组下标是从0开始往上+1
size = s + 1;
//注意:除了首个元素,其他元素为null。那么elementData的数组就只会有1个元素,该元素下标为0,elementData.length = 1,elementData[0] = object。不再是10个元素的数组
}
//数组增长1,并返回该数组的引用
private Object[] grow() {
return grow(size + 1);//当前数组大小+1
}
/**
minCapacity:最小容量
这里是上面方法的引用,首次为0+1 = 1,minCapacity = 1
*/
private Object[] grow(int minCapacity) {
//获取当前数组的长度
int oldCapacity = elementData.length;
if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
/**
若果当前数组内有元素了,或者不是默认数组就进来。
假设当前数组大小为1
oldCapacity = 1
minCapacity - oldCapacity = 2 - 1 = 1
oldCapacity >> 1 = 0。10的二进制=0b1, 右移 1位 = 0b0 = 0
好,现在这个方法就是这样的了
ArraysSupport.newLength(1,1,0);看Arrays.Support的代码块,其实就是判断了下大小,确定数组不会特别巨大。
逻辑:newCapacity = ((1 >=0)?1 : 0 )+1。newCapacity 的大小就是 2
*/
int newCapacity =
ArraysSupport.newLength(
oldCapacity,
minCapacity - oldCapacity,
oldCapacity >> 1
)
];
/**
数组赋值,将elementData,赋值给一个长度为2的数组,并返回引用给elementData
其中elementData的元素会被copy到新数组中,再返回该数组,看回add方法
*/
return elementData = Arrays.copyOf(elementData, newCapacity);
} else {
/**
当前是数组长度为 <= 0 || 当前数组是默认的空数组就会进来。
Math.max(DEFAULT_CAPACITY, minCapacity)这个max方法就是 a >= b ? a : b。
DEFAULT_CAPACITY = 10。结合前面的传递过来的值,也就是10>1?10:1。因此会创建一个长度为10的Object数组并返回,看回add方法
*/
return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
}
}
/**
get方法从指定的元素下标返回元素
*/
public E get(int index) {
//检查,如果超出长度就会抛出异常:IndexOutOfBoundsException
Objects.checkIndex(index, size);
return elementData(index);
}
E elementData(int index) {
//返回数组指定下标的元素,由于是通过下标访问元素,因此访问速度会很快,这样体现了数组的优势
//添加元素新增指定数组,再复制元素到新数组,再返回新数组的引用给当前对象数组。因此新增效率不高,访问效率很高
return (E) elementData[index];
}
//根据数组中的某个元素
public E remove(int index) {
//长度的检查
Objects.checkIndex(index, size);
//获取一个引用
final Object[] es = elementData;
@SuppressWarnings("unchecked")
E oldValue = (E) es[index];//旧值,用于抛出
fastRemove(es, index);
return oldValue;
}
private void fastRemove(Object[] es, int i) {
//操作数递增
modCount++;
final int newSize;
//为了更直观,我添加了「{}」,在代码中{}是被省略了的
//这里的逻辑来看一下,((newSize = size - 1) = 10-1=9)。9 > 0 == true
if ((newSize = size - 1) > i){
//数组复制,从es,从(0+1=)1开始,复制到es,从 0 开始,(9 - 1 =)8
//复制数组,从1开始,复制8个元素,到当前数组的下标0的元素开始。完成删除
System.arraycopy(es, i + 1, es, i, newSize - i);
}
//当前数组第9个为null。
//假设i =9,且当前数组长度为10那么不走上面逻辑,直接设置为null,这是很快的
es[size = newSize] = null;
}
ArraysSupport
/**
获取长度:
oldLength:1
minGrowth:1
prefGrowth:0
*/
public static int newLength(int oldLength, int minGrowth, int prefGrowth) {
//newLength = ((1 >= 0)? 1 : 0 )+1。newLength = 2
int newLength = Math.max(minGrowth, prefGrowth) + oldLength;
if (newLength - MAX_ARRAY_LENGTH <= 0) {
return newLength;//抛出2
}
return hugeLength(oldLength, minGrowth);
}
Arrays
/**
原数组,新长度为
*/
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
/**
original原数组
newLength新长度,
newType原数组的类型
*/
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
//只需要掌握一处,这里是一个new Object[2]
T[] copy = ((Object)newType == (Object)Object[].class)
?(T[]) new Object[newLength]
: (T[])Array.newInstance(newType.getComponentType(), newLength);
//使用系统的复制,从原数组,0开始,到copy数组,从0开始,((1 <= 2)?1 : 2)=1
//这里的复制也可以这么形容。把原数组从0开始复制到一个从0开始的新数组中,复制个数为1。
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
//抛出copy数组
return copy;
}
来源:oschina
链接:https://my.oschina.net/yongyuanliu/blog/3179364