ArrayList
目录
一、基础常量以及结构
// 默认数组长度
private static final int DEFAULT_CAPACITY = 10;
// 数组容器
transient Object[] elementData;
// 长度设置成0的默认数组容器
private static final Object[] EMPTY_ELEMENTDATA = {};
// 默认的数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 容器现有元素数量
private int size;
ArrayList用到的存储结构就是数组,我们看常量有两个空的数组常量,为什么会有两个?(下面再说)
二、构造方法
初始化很简单,没有什么扩容阈值,就初始化一个数组,值得注意的就是默认的初始化和参数为0的初始化不太一样,用了两个不一样的空数组,就是上面说的两个空数组常量,所以这里猜测目的就是为了区分指定初始化容量为0的容器和默认初始化的容器。(目的是什么?有什么用?下面说)
源代码如下:
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
}
}
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
三、add操作
有两个add方法
不带索引的: 1.扩容判断 2.尾部赋值
带索引的:1.下标校验 2.扩容判断 3.数组数据后移 4.指定位置赋值
源代码如下:
// 默认添加 直接数组尾部添加
public boolean add(E e) {
// 扩容判断
ensureCapacityInternal(size + 1); // Increments modCount!!
// 赋值
elementData[size++] = e;
return true;
}
// 指定下标位置添加
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++;
}
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private void ensureCapacityInternal(int minCapacity) {
// 当数组是无参构造方法生成的默认数组的时候,这里会给一个默认的数组大小 10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 容量大于数组长度 就扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
问题一:两个默认的常量空数组有什么作用?
用于区分指定0容量初始化和默认初始化两种情况,无参构造初始化的时候并没有初始化默认的数组长度为10,而是在第一次add操作的时候扩容里面判断赋值的,这里的判断就是通过两个数组的对比来实现的
指定0容量初始化:在第一次add操作后,数组长度为1
ArrayList<Object> objects = new ArrayList<>(0);
objects.add(1);
默认初始化:在第一次add操作后,数组长度为10
ArrayList<Object> objects = new ArrayList<>();
objects.add(1);
问题二:指定下标赋值的时候,数组数据是怎么移位的?
是利用最底层的**System.arraycopy()方法,常见的ArrayList.toArray()**方法也是利用的这个
源代码如下:
/**
* @param src 源数组
* @param srcPos 源数组起始位置
* @param dest 目标数组
* @param destPos 目标数组的起始位置
* @param length 源数组要复制的长度
*/
public static native void arraycopy(Object src, int srcPos,Object dest, int destPos,
int length);
这是个数组复制的方法,简单来说就是把源数组中的一段连续的数据截取出来,复制到目标数组的指定位置中,如下面例子:
Integer[] arr1 = { 1, 2, 3, 4, 5 };
Integer[] arr2 = new Integer[5];
System.arraycopy(arr1, 2, arr2, 3, 2);
System.out.println(Arrays.toString(arr2));
// 结果输出 arr2为:
[null, null, null, 3, 4]
从源数组中找出从下标2开始连续两个数,复制到目标数组的下标3处,ArrayList里面同理从原下标开始往后所有数据都复制,再赋值到数组原下标+1的位置,就完成了整体数据往后移一位的效果了
图示只是为了更好理解,不是原理图:
四、grow()扩容操作
这里需要注意的就是每次扩容都是原来的1.5倍,会创建一个新的数组,然后再利用System.arraycopy() 将原数据复制过去
源代码如下:
private void grow(int minCapacity) {
// 旧数组长度
int oldCapacity = elementData.length;
// 扩容1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 扩容需要将老数据转移到新数组上 原理上面说过了
elementData = Arrays.copyOf(elementData, newCapacity);
}
// 容量饱和策略 就是容量达到最大值的处理
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
五、get 操作
这个不多说了,数组取值 简单的一批
源代码如下:
public E get(int index) {
// 下标校验 不能超过现有容量
rangeCheck(index);
// 根据下标数组取值
return elementData(index);
}
E elementData(int index) {
return (E) elementData[index];
}
六、contains操作
遍历对比元素,以后要有人问怎么优化,二分查找法丢过去
源代码如下:
public boolean contains(Object o) {
// 下标存在即 true
return indexOf(o) >= 0;
}
//遍历取下标
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;
}
七、remove操作
利用System.arraycopy(),最后把尾部元素置null
源代码如下:
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);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}