目录
1.ArrayList
1.1整体结构
- 初始化大小,默认是10;第一次add的时候扩容的值
- size表示当前数组大小
- modCount表示当前版本号
1.2初始化源码解析
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//无参数直接初始化,数组大小为空
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//指定初始数据初始化
public ArrayList(Collection<? extends E> c) {
//elementData 是保存数组的容器,默认为 null
//object类型的数组是可以接收其他类型的数组的!!!
elementData = c.toArray();
//如果给定的集合(c)数据有值
if ((size = elementData.length) != 0) {
// 这一步是基本是不会进入的,因为toArray方法永远返回object类型数组
// 但是,如果使用Arrays.asList返回的List进行初始化返回的是自己内部实现的ArrayList,
// 它的的toArray方法使用的是clone一个真实类型E的数组,object数组是可以接收的,
// 但是必须进行强制转换为object类型的数组
if (elementData.getClass() != Object[].class) {
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
} else {
// 给定集合(c)无值,则默认空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}
//指定大小初始化
public ArrayList(int initialCapacity) {
//首先判断值的有效性
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
//初始化指定大小
this.elementData = new Object[initialCapacity];
}
//Arrays的静态方法
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
//Arrays的静态内部类
private static class ArrayList<E> extends AbstractList<E>
implements RandomAccess, java.io.Serializable
{
// 直接是基本类型的数组.....
private final E[] a;
......
// object类型数组是可以接收的
public Object[] toArray() {
return a.clone();
}
.....
}
初始化主要分为:
指定数据初始化,使用toArray转换为数组并进行赋值,进行判断传入的集合长度是否为0,在判断是否需要转换为object数组类型。
ps:object类型的数组(elementData )真实类型可能不是object的,因为它是可以接收其他类型的数组的,所以需要判断是否进行强制类型转换!
无参数初始化,直接初始化为空的object数组
指定大小初始化,先判断传入的大小值是否合格,然后直接申请指定大小的object数组
ps:Arrays.asList(array)方法返回的不是ArrayList而是Arrays自己实现的一个静态内部类,不能增加删除数据。
1.3新增和扩容源码解析
public boolean add(E e) {
//确保数组大小是否足够,不够则执行扩容,size 为当前数组的大小
ensureCapacityInternal(size + 1); // Increments modCount!!
//直接赋值,线程不安全的
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
//有初始化数据或者初始化大小,不走 if 逻辑
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);
}
//扩容,并把现有数据拷贝到新的数组里面去
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
// 扩容1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果扩容后的值 < 我们的期望值,那么就等于我们的期望值
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 如果扩容后的值 > 所能分配的数组的最大值,那么就用 Integer 的最大值
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 通过Arrays的复制进行扩容
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//最大可分配和Int最大值就差8,没甚么意义,等价于返回Int最大值
private static int hugeCapacity(int minCapacity) {
// 判断溢出,如果溢出就会重新循环到负数
if (minCapacity < 0)
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
新增时,首先判断是否需要扩容,先判断是否是没有初始化大小和数据(没有初始化就是10和期待大小的最大值),
在进行数组版本号++,然后判断当前长度是否小于期待的值,
进行扩容1.5倍,如果扩容之后的值小于所期待的值,就设置为期待值,如果扩容之后的值大于最大可分配数,就扩容到INT的最大值即可
最后,使用Arrays进行实际的拷贝扩容
1.4删除源码分析
删除元素的方式有很多:索引删除,值删除,批量删除......,大致思路都差不多,现在来说明下根据值来进行删除,
public boolean remove(Object o) {
// 如果要删除的值是 null,找到第一个值是 null 的删除
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
// 如果要删除的值不为 null,找到第一个和要删除的值相等的删除
for (int index = 0; index < size; index++)
// 这里是根据 equals 来判断值相等的,相等后再根据索引位置进行删除
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index) {
// 记录数组的结构要发生变动了
modCount++;
// numMoved 表示删除 index 位置的元素后,需要从 index 后移动多少个元素到前面去
int numMoved = size - index - 1;
if (numMoved > 0)
// 从 index +1 位置开始被拷贝,拷贝的起始位置是 index,长度是 numMoved
System.arraycopy(elementData, index+1, elementData, index, numMoved);
//数组最后一个位置赋值 null,帮助 GC
elementData[--size] = null;
}
根据值来进行删除:首先判断该值是否为null,再根据循环确定出删除元素的第一个位置
要修改数组需要进行版本号++,然后计算要移动元素的个数,最后进行实际的拷贝
2.迭代器
2.1总体结构
- int cursor;// 表示下一个元素的索引位置,默认从 0 开始。
- int lastRet = -1; // add时:表示当前元素的索引位置,默认从-1开始。
- int expectedModCount = modCount(数组实际版本号);// expectedModCount 表示迭代过程中,期望的版本号;
一般来说,有三个方法,hasNext判断还有没有值可以迭代,next迭代值的索引,remove删除当前迭代值
2.2三个方法源码解析
//hasNext
public boolean hasNext() {
//cursor 表示下一个元素的位置,size 表示实际大小,
//如果两者相等,说明已经没有元素可以迭代了,如果不等,说明还可以迭代
return cursor != size;
}
//add
public E next() {
//迭代过程中,判断版本号有无被修改,有被修改,抛 ConcurrentModificationException 异常
checkForComodification();
//本次迭代过程中,元素的索引位置
int i = cursor;
//size一定大于等于object数组的length属性,size表示元素的实际个数,length表示数组长度
//就是判断是否可以迭代,否则抛出异常
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
// 为下一次迭代做准备
cursor = i + 1;
// 返回元素值,并进行强制类型转换
return (E) elementData[lastRet = i]; //index返回值为i
}
// 版本号检验
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
//remove
public void remove() {
// 防止重复删除,限定一个当前index只能删除一次
if (lastRet < 0)
throw new IllegalStateException();
//判断版本号有无被修改
checkForComodification();
try {
//进行实际的删除
ArrayList.this.remove(lastRet);
cursor = lastRet;
// -1 表示元素已经被删除,这里也防止重复删除
lastRet = -1;
// 更新版本号
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
hasNext,直接使用下一个元素的索引与size元素个数进行比较
next,先判断版本号,在判断当前索引是否等于size,防止索引越界(判断是否可以迭代),
在将当前索引增加为下一次迭代做准备,最后返回当前索引位置的元素,并设置当前索引元素的索引位置
remove,先判断当前索引位置是否小于0,小于的话就说明已经删除过了无法再次删除,在判断版本号,
在进行实际的删除,在把下一个元素的指针向前移动指向当前索引位置,并且设置当前索引位置为-1,最后要更新版本号