本系列第一篇文章今天正式开始,会按照本人的时间情况进行更新
阅读代码版本是JDK1.8,第一个包就先从util包开始吧
下图是ArrayList的UML图,由于是本系列的第一篇文章,所以我们把其中涉及到的接口全部的介绍一下,对数据结构有整体的认识
1 Iterable:提供了两种迭代器:一种是顺序迭代器Iterator, 一种是可分割迭代器Spliterator,该可分割迭代器主要是通过降低每次迭代的宽度,增加并发,同时还提供相应的线程安全性;最后Iterable还提供了forEach方法;
2 Collection:提供了集合中绝大多数的基础操作方法,同时由于继承Iterable,所以需要实现两种迭代器;Stream与parallelStrream是1.8中的新特性,可以通过parallel()与sequential()方法在并行流和串行流之间进行切换。
3 AbstractCollection 抽象集合类
4 RandomAccess, Cloneable, Serializable为三个标记接口
标记接口的含义:
4.1 一般此标记接口用于 List 实现,以表明它们支持快速(通常是恒定时间)的随机访问。该接口的主要目的是允许通用算法改变其行为,以便在应用于随机或顺序访问列表时提供良好的性能。
比如在工具类 Collections(这个工具类后面会详细讲解)中,应用二分查找方法时判断是否实现了 RandomAccess 接口:
int binarySearch(List<? extends Comparable<? super T>> list, T key) {
if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
return Collections.indexedBinarySearch(list, key);
else
return Collections.iteratorBinarySearch(list, key);
}
4.2 实现 Cloneable 接口
这个类是 java.lang.Cloneable,前面我们讲解深拷贝和浅拷贝的原理时,我们介绍了浅拷贝可以通过调用 Object.clone() 方法来实现,但是调用该方法的对象必须要实现 Cloneable 接口,否则会抛出 CloneNoSupportException异常。
Cloneable 和 RandomAccess 接口一样也是一个标记接口,接口内无任何方法体和常量的声明,也就是说如果想克隆对象,必须要实现 Cloneable 接口,表明该类是可以被克隆的。
4.3 实现 Serializable 接口
这个没什么好说的,也是标记接口,表示能被序列化。
5
5.1 字段属性:
//集合的默认大小
private static final int DEFAULT_CAPACITY = 10;
//空的数组实例
private static final Object[] EMPTY_ELEMENTDATA = {};
//这也是一个空的数组实例,和EMPTY_ELEMENTDATA空数组相比是用于了解添加元素时数组膨胀多少
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//存储 ArrayList集合的元素,集合的长度即这个数组的长度
//1、当 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 时将会清空 ArrayList
//2、当添加第一个元素时,elementData 长度会扩展为 DEFAULT_CAPACITY=10
transient Object[] elementData;
//表示集合的长度
private int size;
5.2 构造函数
带容量参数构造函数,如容量为0,生成默认空数组对象
默认构造函数,生成容量为10的AL
将已有集合复制进去
5.3 add函数
public boolean add(E e) { // 添加元素
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 判断元素数组是否为空数组
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); // 取较大值
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
// 结构性修改加1 该值用于迭代器比较版本使用
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
int oldCapacity = elementData.length; // 旧容量
int newCapacity = oldCapacity + (oldCapacity >> 1); // 新容量为旧容量的1.5倍
if (newCapacity - minCapacity < 0) // 新容量小于参数指定容量,修改新容量
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0) // 新容量大于最大容量
newCapacity = hugeCapacity(minCapacity); // 指定新容量
// 拷贝扩容
elementData = Arrays.copyOf(elementData, newCapacity);
}
总结添加元素:
1、当通过 ArrayList() 构造一个空集合,初始长度是为0的,第 1 次添加元素,会创建一个长度为10的数组,并将该元素赋值到数组的第一个位置。
2、第 2 次添加元素,集合不为空,而且由于集合的长度size+1是小于数组的长度10,所以直接添加元素到数组的第二个位置,不用扩容。
3、第 11 次添加元素,此时 size+1 = 11,而数组长度是10,这时候创建一个长度为10+10*0.5 = 15 的数组(扩容1.5倍),然后将原数组元素引用拷贝到新数组。并将第 11 次添加的元素赋值到新数组下标为10的位置。
4、第 Integer.MAX_VALUE - 7 次添加元素时,创建一个大小为 Integer.MAX_VALUE 的数组,在进行元素添加。
5、第 Integer.MAX_VALUE + 1 次添加元素时,抛出 OutOfMemoryError 异常。
注意:能向集合中添加 null 的,因为数组可以有 null 值存在。
5.4 删除元素
删除元素有两个方法,1根据索引,2直接删除指定元素,本质上这两个方法都是删除
public E remove(int index) {
rangeCheck(index);//判断给定索引的范围,超过集合大小则抛出异常
modCount++;
E oldValue = elementData(index);//得到索引处的删除元素
int numMoved = size - index - 1;
if (numMoved > 0)//size-index-1 > 0 表示 0<= index < (size-1),即索引不是最后一个元素
//通过 System.arraycopy()将数组elementData 的下标index+1之后长度为 numMoved的元素拷贝到从index开始的位置
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; //将数组最后一个元素置为 null,便于垃圾回收
return oldValue;
}
5.5 修改元素 与删除
public E set(int index, E element) {
rangeCheck(index);//判断索引合法性
E oldValue = elementData(index);//获得原数组指定索引的元素
elementData[index] = element;//将指定所引处的元素替换为 element
return oldValue;//返回原数组索引元素
}
删除元素有两种方法,删除指定位置与删除指定元素,核心都为删除指定位置,删除原理将index后的元素整体前移一格,最后一个元素null,让gc进行整理
5.6 访问元素
其他批量删除,批量remove,整体清空,addAll等都可以进行组合使用;
5.7 遍历元素
遍历元素有两种方式1 根据index进行边界内的顺序访问 2根据迭代器的next进行顺序访问
AL由于继承Collection,所以需要实现Iterable,该实现在AL中为内部类
private class Itr implements Iterator<E> {
int cursor; //游标, 下一个要返回的元素的索引
int lastRet = -1; // 返回最后一个元素的索引; 如果没有这样的话返回-1.
int expectedModCount = modCount;
//通过 cursor != size 判断是否还有下一个元素
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();//迭代器进行元素迭代时同时进行增加和删除操作,会抛出异常
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;//游标向后移动一位
return (E) elementData[lastRet = i];//返回索引为i处的元素,并将 lastRet赋值为i
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);//调用ArrayList的remove方法删除元素
cursor = lastRet;//游标指向删除元素的位置,本来是lastRet+1的,这里删除一个元素,然后游标就不变了
lastRet = -1;//lastRet恢复默认值-1
expectedModCount = modCount;//expectedModCount值和modCount同步,因为进行add和remove操作,modCount会加1
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {//便于进行forEach循环
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
//前面在新增元素add() 和 删除元素 remove() 时,我们可以看到 modCount++。修改set() 是没有的
//也就是说不能在迭代器进行元素迭代时进行增加和删除操作,否则抛出异常
//解决办法是不调用 ArrayList.remove() 方法,转而调用 迭代器的 remove() 方法:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
注意:迭代器只能向后遍历,不能向前遍历,能够删除元素,但是不能新增元素。
5.8 优化后迭代器
Iterator迭代器只能向后遍历,而List可以向前遍历,也可以增加元素,实现方式是将游标进行向前移动,注意边界卡位
5.9 内部类 SubList
查看其数据结构发现,该类继承AbstractList,与AL本身行为几乎一致
提供的操作方法也几乎一致
由于子队列并没有生成新的AL,所以返回的仍然是父AL,所以对子AL的操作影响的是父AL
想完全生成一个独立子AL,只需要
List<String> subList = new ArrayList<>(list.subList(0, 1)); new一个出来
5.10 JDK1.8新引入的ArrayListSpliterator
典型的分治法思维
其中核心方法为trySplit