对于Collection集合类,网上常见的结构图如下:
上图显示了常用的几个集合工具类的相互关系和结构组成,但实际上远没有那么简单,因此我从ArrayList开始,深入底层源码,慢慢将集合工具类的大体框架和具体实现梳理出来,帮助自己深入理解集合工具类。
首先ArrayList作为List家族的一员,首先来梳理一下List家族的具体结构组成:
所以作为我们最常用的ArrayList类,其直接父类是AbstractList,并且实现了 List接口、RandomAccess接口、Cloneable接口以及java.io.Serializable接口。List接口就不用说了,RandomAccess接口里面并没有什么方法,只是作为一个标志接口,用于区分ArrayList和LinkedList,从而选择合适的遍历方式。具体请参照:ArrayList为什么需要实现RandomAccess接口?
而实现Cloneable接口主要是实现Clone方法,使得ArrayList可以进行克隆操作,获得一个一模一样的ArrayList,但是两个ArrayList的引用不一样。而实现Serializable接口则是使得ArrayList可以序列化。
public class ArrayList<E> extends AbstractList<E>
implements List<E>,RandomAccess,Cloneable,java.io.Serializable
接下来再来看一下ArrayList的成员变量和常量:
private static final long serialVersionUID = 8683452581122892189L;
//验证版本是否一致,用于反序列化的
private static final int default_capacity = 10;
//默认初始容量为10
private static final Object[] empty_elementdata = {};
//默认空数组,当数组初始化大小为0时使用
private static final Object[] defaultcapacity_empty_elementdata = {};
//默认空数组,当数组初始化大小为10的时候使用,当添加第一个元素时,该数组
//便会被初始化为默认大小,从而与empty_elementdata区别
transient Object[] elementData;
//这是ArrayList用于存放元素的具体位置,transient表示该区域不能被反序列化
private int size;
//集合数组大小
此处serialVersionUID有些难以理解,特别是之前没有接触过序列化与反序列的(比如博主...),因此我就将它作为一个标签,主要是在序列化和反序列的时候用到,而另外一个比较难以理解的是empty_elementdata和defaultcapacity_empty_elementdata两个私有静态常量数组(源码这两个数组名字是全大写的,因为是常量,但此处为了方便理解,我暂时改为小写)难以理解就暂且放下,继续往下看。从这些成员变量和常量中就基本可以看出ArrayList是怎么实现的。底层其实也就是普通数组,只是它将数组本身和数组大小分开了。
接下来继续看它的构造器:
public ArrayList(int initialCapacity){
//构造器1,如果传入的参数是数组容量,如何初始化ArrayList
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(){
//构造器2,如果不传入参数,那就初始化为空的数组
this.elementData = defaultcapacity_empty_elementdata;
}
public ArrayList(Collection<? extends E> c){
//构造器3,如果传入的参数是一个集合,如何初始化ArrayList
elementData = c.toArray();
//直接用集合c本身的方法转化为普通数组赋给ArrayList集合中的成员数组
if((size = elementData.length) != 0){
if(elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData,size,Object[].class);
//如果转化过来的数组成员不是Object类,则使用复制的方法全都转化为Object类
//作用就是方便后面进行相关操作
}else{
this.elementData = empty_elementdata;
//如果参数数组为空,则直接用成员变量初始化
}
}
从上面可以看出,ArrayList类总共提供了三个构造器,分别是无参构造器、参数为集合数组初始大小的构造器以及参数是其他集合的构造器。另外还有一个特别之处是其他两个构造器如果接收的参数是空或者零的话都是将empty_elementdata常量数组赋给成员变量数组的,只有无参构造器直接将defaultcapacity_empty_elementdata常量数组赋给成员变量数组,为什么呢?继续往下看。
接下来看一下ArrayList类的核心方法,自动扩容:
public void ensureCapacity(int minCapacity){
//确保容量,此方法是公用方法,也就是说可以从外界直接调用的
//ArrayList的扩容方法就这一个是对外开放的,此方法作用是手动扩容
int minExpand = (elementData != defaultcapacity_empty_elementdata)
? 0
: default_capacity;
//首先判断ArrayList是否初始化,如果成员数组变量是defaultcapacity_empty_elementdata
//说明还没有初始化,而从成员变量的赋值可以知道,数组一旦初始化就有10容量,因此先查看
//此数组有没有初始化,初始化了就不能无条件扩容,最小增长为0,
//没有初始化就说明可以一次性扩10容量
if(minCapacity > minExpand){
//判断手动扩容传进来的值是否大于扩充的最小值,也就是说有没有必要
//按传进来的数值进行相应扩容,如果大于扩容最小值,就开始执行正式扩容
ensureExplicitCapacity(minCapacity);//调用扩容方法
}
}
private static int calculateCapacity(Object[] elementData,int minCapacity){
//计算容量
if(elementData == defaultcapacity_empty_elementdata){//如果集合数组还没有初始化
return Math.max(default_capacity,minCapacity);
//比较传进来的扩容值和初始化时扩容的初始值10,返回最大值
}
return minCapacity;//如果集合数组已经初始化了,那么就返回传入的扩容值
}
private void ensureCapacityInternal(int minCapacity){
//确认内部容量,调用明确容量,并将计算容量方法中获得的最终值当作参数传入
ensureExplicitCapacity(calculateCapacity(elementData,minCapacity));
}
private void ensureExplicitCapacity(int minCapacity){
//确保明确容量,从此处开始正式扩容,首先modCount增加一
//防止扩容时其他方法对成员数组进行修改造成了数据不同步
modCount++;
if(minCapacity - elementData.length > 0)//比较传入值与成员数组的长度,如果大,则扩容
grow(minCapacity);//执行扩容方法
}
private static final int Max_array_size = Integer.MAX_VALUE - 8;
//定义一个常量,即数组容量的限制值,即比Integer最大值小8,我理解为一个缓冲区
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)//如果新容量大到一定程度,接近Integer最大数了
newCapacity = hugeCapacity(minCapacity);//调用最大容量方法,判断是否超出界线
elementData = Array.copyOf(elementData,newCapacity);
//如果没有超,则扩充容量,即创建一个新的数组代替原来的数组
//新数组里面含有原数组的一切元素,只是数组长度是扩容后的长度
}
private static int hugeCapacity(int minCapacity){
//设置最大值限制
if(minCapacity < 0)//此处的传入值小于零指的是最高位是1,说明已经溢出了
throw new OutOfMemoryError();//如果超出最大值,抛出内存溢出错误
return (minCapacity > Max_array_size)//如果没有超出,则比较是否比限制值大
? Integer.MAX_VALUE//大的话直接赋值为最大值
: Max_array_size;//小则赋值为限制值
}
直接这样看比较难理解,先来看一下这些方法是如何执行的吧:
这是我自己的理解,可能也有一些错误,但是总体的步骤应该是这样的,因此一次扩容还是比较复杂的,因此ArrayList提供了一个对外的方法,即图中的ensureCapacity方法,可以直接进行比较大的扩容,如果大体确定一个集合数组会容纳多少元素,可以直接先执行此方法。这样就免去了每一次增加一个元素就要执行一次扩容操作这样繁杂的操作。
当然,数组扩容也不是真的一个一个扩,从源代码中grow方法中可以得知,一次扩容大概是原容量的1.5倍左右,至于为何是1.5倍,我的理解是在这个倍数效率最高,如果小了,那就会导致频繁扩容,如果大了,则造成空间的浪费。而且如果传入的值大于这个1.5倍,则按照传入值进行扩容。
除此之外,还有一个值得注意的是,集合数组扩容其实并不是什么高深的技术,就是用一个新的大的数组代替原来旧的老的数组,再把旧数组中的元素全都复制到新数组中去,这样就形成了一个可以自动扩容的集合数组,这也是集合数组和普通数组最大的不同之处。
看到这里,应该就可以明白了为什么要有defaultcapacity_empty_elementdata这个空数组常量了,这个就是判断该集合数组有没有初始化的标志。
剩下的就是一些常用方法,总源代码在下面:
public class ArrayList<E> extends AbstractList<E>
implements List<E>,RandomAccess,Cloneable,java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
//验证版本是否一致,用于反序列化的
private static final int default_capacity = 10;
//默认初始容量为10
private static final Object[] empty_elementdata = {};
//默认空数组,当数组初始化大小为0时使用
private static final Object[] defaultcapacity_empty_elementdata = {};
//默认空数组,当数组初始化大小为10的时候使用,当添加第一个元素时,该数组
//便会被初始化为默认大小,从而与empty_elementdata区别
transient Object[] elementData;
//这是ArrayList用于存放元素的具体位置,transient表示该区域不能被反序列化
private int size;
//数组大小
public ArrayList(int initialCapacity){
//构造器1,如果传入的参数是数组容量,如何初始化ArrayList
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(){
//构造器2,如果不传入参数,那就初始化为空的数组
this.elementData = defaultcapacity_empty_elementdata;
}
public ArrayList(Collection<? extends E> c){
//构造器3,如果传入的参数是一个集合,如何初始化ArrayList
elementData = c.toArray();
//直接用集合c本身的方法转化为普通数组赋给ArrayList集合中的成员数组
if((size = elementData.length) != 0){
if(elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData,size,Object[].class);
//如果转化过来的数组成员不是Object类,则使用复制的方法全都转化为Object类
//作用就是方便后面进行相关操作
}else{
this.elementData = empty_elementdata;
//如果参数数组为空,则直接用成员变量初始化
}
}
public void trimToSize(){
//类似剪枝吧,把elementData没用的剩余空间清除
modCount++;//不知道这个有什么用....
if(size < elementData.length){
elementData = (size == 0)
? empty_elementdata
: Arrays.copyOf(elementData,size);
}
}
public void ensureCapacity(int minCapacity){
//确保容量,此方法是公用方法,也就是说可以从外界直接调用的
//ArrayList的扩容方法就这一个是对外开放的,此方法作用是手动扩容
int minExpand = (elementData != defaultcapacity_empty_elementdata)
? 0
: default_capacity;
//首先判断ArrayList是否初始化,如果成员数组变量是defaultcapacity_empty_elementdata
//说明还没有初始化,而从成员变量的赋值可以知道,数组一旦初始化就有10容量,因此先查看
//此数组有没有初始化,初始化了就不能无条件扩容,最小增长为0,没有初始化就说明可以一次性扩10容量
if(minCapacity > minExpand){//判断手动扩容传进来的值是否大于扩充的最小值,也就是说有没有必要
//按传进来的数值进行相应扩容,如果大于扩容最小值,就开始执行正式扩容
ensureExplicitCapacity(minCapacity);//调用扩容方法
}
}
private static int calculateCapacity(Object[] elementData,int minCapacity){
//计算容量
if(elementData == defaultcapacity_empty_elementdata){//如果集合数组还没有初始化
return Math.max(default_capacity,minCapacity);//比较传进来的扩容值和初始化时扩容的初始值10,返回最大值
}
return minCapacity;//如果集合数组已经初始化了,那么就返回传入的扩容值
}
private void ensureCapacityInternal(int minCapacity){
//确认内部容量,调用明确容量,并将计算容量方法中获得的最终值当作参数传入
ensureExplicitCapacity(calculateCapacity(elementData,minCapacity));
}
private void ensureExplicitCapacity(int minCapacity){
//确保明确容量,从此处开始正式扩容,首先modCount增加一
//防止扩容时其他方法对成员数组进行修改造成了数据不同步
modCount++;
if(minCapacity - elementData.length > 0)//比较传入值与成员数组的长度,如果大,则扩容
grow(minCapacity);//执行扩容方法
}
private static final int Max_array_size = Integer.MAX_VALUE - 8;
//定义一个常量,即数组容量的限制值,即比Integer最大值小8,我理解为一个缓冲区
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)//如果新容量大到一定程度,接近Integer最大数了
newCapacity = hugeCapacity(minCapacity);//调用最大容量方法,判断是否超出界线
elementData = Array.copyOf(elementData,newCapacity);//如果没有超,则扩充容量,即创建一个新的数组代替原来的数组
//新数组里面含有原数组的一切元素,只是数组长度是扩容后的长度
}
private static int hugeCapacity(int minCapacity){
//设置最大值限制
if(minCapacity < 0)//此处的传入值小于零指的是最高位是1,说明已经溢出了
throw new OutOfMemoryError();//如果超出最大值,抛出内存溢出错误
return (minCapacity > Max_array_size)//如果没有超出,则比较是否比限制值大
? Integer.MAX_VALUE//大的话直接赋值为最大值
: Max_array_size;//小则赋值为限制值
}
public int size(){
//返回数组的size
return size;
}
public boolean isEmpty(){
//判断数组是否为空,即大小是不是零
return size == 0;
}
public boolean contains(Object o){
//查看数组中是否含有某元素,就是调用遍历,查看该元素在哪里
return indexOf(o) >= 0;
}
public int indexOf(Object o){
//查看元素在数组的哪一个位置,不在则返回-1,简单的遍历,不过要考虑null的情况
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;
}
public int lastIndexOf(Object o){
//查看该元素是该数组的倒数第几个,还是遍历,一样的方法
if(o == null){
for(int i = size - 1; i >= 0; i--){
if(elementData[i] == null)
return i;
}
}else{
for(int i = size - 1; i >= 0; i--){
if(o.equals(elementData[i]))
return i;
}
}
return -1;
}
public Object clone(){
//克隆当前数组
try{
ArrayList<?> v = (ArrayList<?>) super.clone();//调用父类的克隆方法
v.elementData = Arrays.copyOf(elementData,size);//将数组属性复制进去
v.modCount = 0;
return v;
}catch(CloneNotSupportedException e){
throw new InternalError(e);//抛出异常,克隆错误
}
}
//将集合转化为数组
public Object[] toArray(){
//将集合数组变为一般数组,就是调用数组方法进行复制并返回
return Arrays.copyOf(elementData,size);
}
@SuppressWarnings("unchecked")//忽略“unchecked”警告
public <T> T[] toArray(T[] a){
//传入一个数组实例,根据数组的类型,将集合转为同类型的数组
if(a.length < size)//如果数组长度小于集合长度,则重新创建一个数组返回
return (T[]) Arrays.copyOf(elementData,size,a.getClass);
System.arraycopy(elementData,0,a,0,size);//否则就将集合元素复制到原数组中
if(a.length > size)//如果数组长度大于集合长度
a[size] = null;//将大于的部分全都赋值为null
return a;
}
@SuppressWarnings("unchecked")//忽略警告
E elementData(int index){
//获得数组中数据,为何会有这个方法存在?而不是直接获得?
return (E) elementData[index];
}
public E get(int index){
//获得某一节点的元素
rangeCheck(index);//检查index是否超出size限制
return elementData(index);//返回元素
}
public E set(int index,E element){
//设置某一节点的元素为新输入的元素
rangeCheck(index);//范围检查
E oldValue = elementData(index);//先获得该节点原元素
elementData[index] = element;//将新元素放入该节点中
return oldValue;//返回原来的元素!!是原来的,不是新的!
}
public boolean add(E e){
//向集合数组末尾加入一个元素
ensureCapacityInternal(size + 1);//确保内部容量,进行适当的容量增长
elementData[size++] = e;//将元素放入集合数组的最后一个位置
return true;//返回正确,确认增加成功
}
public void add(int index,E element){
rangeCheckForAdd(index);
//范围检查
ensureCapacityInternal(size + 1);//适当增加容量
System.arraycopy(elementData,index,elementData,index + 1,size - index);
//此处应用了本地方法,arraycopy是将源数组复制到目标数组的方法,参数依次为
//源数组、复制起始位置、目标数组、复制到的起始位置、需复制的长度
elementData[index] = element;//将元素插入到目标地点
size++;//数组大小增加一
}
public E remove(int index){
rangCheck(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;//将数组的大小减一,并且最后一个位置赋值为null
//赋值为null表明移除引用,方便GC回收数据与内存
return oldValue;//返回删除的元素
}
public boolean remove(Object o){
//移出某个元素,还是采用遍历查询,然后移除
if(o == null){//如果元素是null,则移出null
for(int index = 0; index < size; index++)
if(elementData[index] == null){
fastRemove(index);
return true;
}
}else{//否则移出该元素,注意元素是如何判断相等的
for(int index = 0; index < size; index++)
if(o.equals(elementData[index]){
fastRemove(index);
return true;
}
}
//返回的是是否正确移除,而且需要注意,这里移除不是全部移除,而是移除第一个!
return false;
}
private void fastRemove(int index){
//和remove一模一样,唯一不一样就是不返回元素,直接删除,而且只能内部调用
modCount++;
int numMoved = size - index - 1;
if(numMoved > 0)
System.arraycopy(elementData,index + 1,elementData,index,numMoved);
elementData[--size] = null;
}
public void clear(){
//清空集合数组
modCount++;
for(int i = 0; i < size; i++)//直接遍历,每一个元素赋值null,清除引用就行
elementData[i] = null;
size = 0;//设置数组大小为0
}
public boolean addAll(Collection<? extends E> c){
//给数组后面加入一个集合
Object[] a = c.toArray();//将集合转变为数组
int numNew = a.length;//获得数组长度
ensureCapacityInternal(size + numNew);//适当扩展容量
System.arraycopy(a,0,elementData,size,numNew);//复制进去
size += numNew;//增大大小
return numNew != 0;//返回值有点意思,如果数组为空则说明没有插入成功,否则就成功了
}
public boolean addAll(int index,Collection<? extends E> c){
//从某个点插入一个集合,方法类似,不多赘述
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew);
int numMoved = size - index;
if(numMoved > 0)
System.arraycopy(elementData,index,elementData,index + numNew,numMoved);
System.arraycopy(c,0,elementData,index,numNew);
size += numNew;
return numNew != 0;
}
protected void removeRange(int fromIndex,int toIndex){
//将集合数组中的某一段删除,然后将后面的全部往前移补充
modCount++;
int numMoved = size - toIndex;//计算需要往前移动多少
System.arraycopy(elementData,toIndex,elementData,fromIndex,numMoved);
//开始移动
int newSize = size - (toIndex - fromIndex);//计算删除后的数组长度
for(int i = newSize; i < size; i++)//将后面多余的元素赋值null,删除
elementData[i] = null;
size = newSize;//设定新数组大小
}
private void rangeCheck(int index){
//主要检查是否超出数组大小,不检查是否为负数,因为调用它的方法中肯定会调用
//到array方法,如果为负数,那就会抛出ArrayIndexOutOfBoundsException异常
if(index >= size)//验证输入的节点是否超出数组长度,如果超出就抛出异常
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private void rangeCheckForAdd(int index){
//此处一般是在增加数组元素时用的范围检查,需要检查是否为负,但可以等于数组大小
//因为可能插到最后一个
if(index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private String outOfBoundsMsg(int index){
//显示描述引用下标超出数组长度异常信息
return "Index:" + index + ",Size:" + size;
}
public boolean removeAll(Collection<?> c){
//删除所有在集合c中出现的元素
Objects.requireNonNull(c);//判断c是否为null,如果是则抛出空指针异常
return batchRemove(c,false);//调用删除方法
}
public boolean retainAll(Collection<?> c){
//只保留在c中出现的元素
Objects.requireNonNull(c);
return batchRemove(c,true);
}
private boolean batchRemove(Collection<?> c,boolean complement){
//批量删除
final Object[] elementData = this.elementData;//首先获得当前类的数组
int r = 0,w = 0;//设置两个指针
boolean modified = false;
try{
for(; r < size; r++)
//好,重点来了,这个判断是真的牛逼,满足要求的放到前面去
if(c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
}finally{
//这里说可能会中断循环,可能是contains会发生空指针异常吧,但是不管
//发不发生异常,都要确保数组是完整的,所以检查数组的完整性
if(r != size){
System.arraycopy(elementData,r,elementData,w,size - r);
w += size -r;
}
//查看是否需要删除元素,因为try循环中把选中的都移到前面去了,所以
//直接删除后面的就够了
if(w != size){
for(int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
private void writeObject(java.io.ObjectOutputStream s){
//写入操作,将数组里的信息写入别的地方,这个s应该是写入的地址??
throws java.io.IOException{
int expectedModCount = modCount;
s.defaultWriteObject();//写入操作开始
s.writeInt(size);//写入数组长度
for(int i = 0; i < size; i++){
s.writeObject(elememtData[i]);//写入数组元素
}
if(modCount != expectedModCount){
throw new ConcurrentModificationException();
//此异常是指在写入的时候不能对其进行更改操作,否则就会报异常
}
}
}
private void readObject(java.io.ObjectInputStream s){
//读入操作,将其他地方法信息写入进来
throws java.io.IOException,classNotFoundException{
//抛出IO异常和类型找不到异常
elementData = empty_elementdata;//首先将elementData清空
s.defaultReadObject();//写入长度信息和其他必要默认信息
s.readInt();//可忽略,为什么还要写??
if(size > 0){//如果size>0,说明有内容,可以写进来
int capacity = calculateCapacity(elementData,size);
//计算需要的数组长度
SharedSecrets.getJavaOISAccess().checkArray(s.Object[].class,capacity);
//这是检查数组里是否有类型错误异常吗???
ensureCapacityInternal(size);
//适当的增加容量
Object[] a = elementData;//为什么要有这个操作?而不是直接写入elementData?安全吗?
for(int i = 0; i < size; i++){//正式写入
a[i] = s.readObject();
}
}
}
}
public ListIterator<E> listIterator(int index){
//这是返回一个迭代器的,以传入进来的索引为第一个起始位置
if(index < 0 || index > size)//检查索引是否越界
throw new IndexOutOfBoundsException("Index:" + index);
return new ListItr(index);
}
public ListIterator<E> listIterator(){
//返回一个数组迭代器,从数组元素第一个开始
return new ListItr(0);
}
public Iterator<E> iterator(){
//返回一个原始迭代器,其迭代器只能从前往后迭代
return new Itr();
}
private class Itr implements Iterator<E>{
//实现了Iterator接口,并且将Itr作为私有的内部成员类
int cursor;//定义的下一个将要去遍历的索引,这里有初始化,即0,所以此迭代器只能从第一个开始迭代
int lastRet = -1;//最近的遍历过的索引,就返回-1
int expectedModCount = modCount;//记录modCount,防止更新操作影响遍历操作
Itr(){}//构造器
public boolean hasNext(){
return cursor != size;//判断是否有下一个元素,即判断cursor是否为数组大小
}
@SuppressWarnings("unchecked")
public E next(){
//此方法就是获得下一个元素
checkForComodification();//先检查是否有更新操作
int i = cursor;//将索引赋值给i
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];//返回成员数组的元素
}
public void remove(){
//移除遍历到的元素,没有完全理解..........
if(lastRet < 0)//先判断是否遍历到
throw new IllegalStateException();//没有则抛出异常
checkForComodification();//核查是否有其他操作
try{
ArrayList.this.remove(lastRet);//移除遍历到的元素
cursor = lastRet;//将指针向前移动,因为整个数组都改变了
lastRet = -1;//将最近的指针赋值为-1
expectedModCount = modCount;//已经进行了更新操作,所以更新modCount
}catch(IndexOutOfBoundsException ex){//如果数组越界了,抛出异常
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer){
//java1.8新增方法,不太理解。。。。。。。。
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++]);
}
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification(){
//核查是否有更新操作和读取操作交错
if(modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
private class ListItr extends Itr implements ListIterator<E>{
//ListItr主要是继承了Itr,所以肯定可以使用判断下一个是否存在、
//遍历到下一个以及删除当前遍历结点这些方法。除此之外,
//其新增方法主要是可以往前遍历以及可以增加结点和修改当前结点
ListItr(int index){//构造器,可以从任一一个节点开始遍历或操作
super();
cursor = index;
}
public boolean hasPrevious(){//判断是否有前一个结点
return cursor != 0;
}
public int nextIndex(){//输出后一个结点是第几个
return cursor;
}
public int PreviousIndex(){//输出前一个结点是第几个
return cursor - 1;
}
@SuppressWarnings("unchecked")
public E previous(){//向前遍历
checkForComodification();
int i = cursor - 1;
if(i < 0)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if(i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i;
return (E) elementData[lastRet = i];
}
public void set(E e){//修改当前结点,必须先遍历到才能修改,否则抛出异常
if(lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try{
ArrayList.this.set(lastRet,e);
}catch(IndexOutOfBoundsException ex){
throw new ConcurrentModificationException();
}
}
public void add(E e){
//在当前结点后面增加一个结点,并且将指针向后移动一次
//但是不算遍历到
checkForComodification();
try{
int i = cursor;
ArrayList.this.add(i,e);
cursor = i + 1;
lastRet = -1;
expectedModCount = modCount;
}catch(IndexOutOfBoundsException ex){
throw new ConcurrentModificationException();
}
}
}
public List<E> subList(int fromIndex,int toIndex){
//生成子列表,就是从当前集合数组中截取一部分作为子数组
subListRangeCheck(fromIndex,toIndex,size);//检查是否符合规范
return new SubList(this,0,fromIndex,toIndex);//返回子数组
}
static void subListRangeCheck(int fromIndex,int toIndex,int size){
//判断截取的数值是否符合规范
if(fromIndex < 0)
throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
if(toIndex > size)
throw new IndexOutOfBoundsException("toIndex = " + toIndex);
if(fromIndex > toIndex)
throw new IllegalArgumentException("fromIndex(" + fromIndex +
") > toIndex(" + toIndex + ")");
}
private class SubList extends AbstractList<E> implements RandomAccess{
//内部成员类,本集合数组的子数组,继承的是抽象列表
//生成的子数组类似于数据库中的视图,就是在原数组中分割一部分出来,但是你在其上
//进行操作其实是对原数组进行的操作
private final AbstractList<E> parent;//父类集合数组
private final int parentOffset;//此子数组的起始索引
private final int offset;//此数组的终止索引
int size;//此数组的长度
SubList(AbstractList<E> parent,int offset,int fromIndex,int toIndex){
//构造器,构造子数组
this.parent = parent;
this.parentOffset = fromIndex;
this.offset = offset + fromIndex;
this.size = toIndex - fromIndex;
this.modCount = ArrayList.this.modCount;
}
public E set(int index,E e){//更新方法
rangeCheck(index);
checkForComodification();
E oldValue = ArrayList.this.elementData(offset + index);
ArrayList.this.elementData[offset + index] = e;
return oldValue;
}
public E get(int index){//获得方法
rangeCheck(index);
checkForComodification();
return ArrayList.this.elementData(offset + index);
}
public int size(){//获得子数组长度
checkForComodification();
return this.size;
}
public void add(int index, E e){//增加方法
rangeCheckForAdd(index);
checkForComodification();
parent.add(parentOffset + index, e);
this.modCount = parent.modCount;
this.size++;
}
public E remove(int index){//移除方法
rangeCheckForAdd(index);
checkForComodification();
E result = parent.remove(parentOffset + index);
this.modCount = parent.modCount;
this.size--;
return result;
}
protected void removeRange(int fromIndex,int toIndex){//删除区域
checkForComodification();
parent.removeRange(parentOffset + fromIndex,
parentOffset + toIndex);
this.modCount = parent.modCount;
this.size -= toIndex - fromIndex;
}
public boolean addAll(Collection<? extends E> c){//增加一个集合
return addAll(this.size,c);
}
public boolean addAll(int index,Collection<? extends E> c){//从某处增加一个集合,重载
rangeCheckForAdd(index);
int cSize = c.size();
if(cSize == 0)
return false;
checkForComodification();
parent.addAll(parentOffset + index,c);
this.modCount = parent.modCount;
this.size += cSize;
return true;
}
public Iterator<E> iterator(){//获得此子数组的迭代器
return listIterator();
}
public ListIterator<E> listIterator(final int index){//生成迭代器
//此迭代器是子数组类中的内部类,其方法和子数组的父类迭代器几乎一样
//主要是注意引用索引时需要加上offset,因为迭代也是在父类数组里面进行迭代的
checkForComodification();
rangeCheckForAdd(index);
final int offset = this.offset;
return new ListIterator<E>(){
int cursor = index;
int lastRet = -1;
int expectedModCount = ArrayList.this.modCount;
public boolean hasNext(){
return cursor != SubList.this.size;
}
@SuppressWarnings("unchecked")
public E next(){
checkForComodification();
int i = cursor;
if(i >= SubList.this.size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if(offset + i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[offset + (lastRet = i)];
}
public boolean hasPrevious(){
return cursor != 0;
}
@SupressWarnings("unchecked")
public E previous(){
checkForComodification();
int i = cursor - 1;
if(i < 0)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if(offset + i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i;
return (E) elementData[offset + (lastRet = i)];
}
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer){
Objects.requireNonNull(consumer);
final int size = SubList.this.size;
int i = cursor;
if(i >= size){
return;
}
final Object[] elementData = ArrayList.this.elementData;
if(offset + i >= elementData.length){
throw new ConcurrentModificationException();
}
while(i != size && modCount == expectedModCount){
consumer.accept((E)elementData[offset + (i++)]);
}
lastRet = cursor = i;
checkForComodification();
}
public int nextIndex(){
return cursor;
}
public int previousIndex(){
return cursor - 1;
}
public void remove(){
if(lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try{
SubList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = ArrayList.this.modCount;
}catch(IndexOutOfBoundsException ex){
throw new ConcurrentModificationException();
}
}
public void set(E e){
if(lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try{
ArrayList.this.set(offset + lastRet,e);
}catch(IndexOutOfBoundsException ex){
throw new ConcurrentModificationException();
}
}
public void add(E e){
checkForComodification();
try{
int i = cursor;
SubList.this.add(i,e);
cursor = i + 1;
lastRet = -1;
expectedModCount = ArrayList.this.modCount;
}catch(IndexOutOfBoundsException ex){
throw new ConcurrentModificationException();
}
}
final void checkForComodification(){
if(expectedModCount != ArrayList.this.modCount)
throw new ConcurrentModificationException();
}
};
}
public List<E> subList(int fromIndex,int toIndex){
subListRangeCheck(fromIndex,toIndex,size);
return new SubList(this,offset,fromIndex,toIndex);
}
private void rangeCheck(int index){
if(index < 0 || index >= this.size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private void rangeCheckForAdd(int index){
if(index < 0 || index > this.size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private String outOfBoundsMsg(int index){
return "Index:" + index + ",Size" + this.size;
}
private void checkForComodification(){
if(ArrayList.this.modCount != this.modCount)
throw new ConcurrentModificationException();
}
public spliterator<E> spliterator(){
//java1.8新增的分割器
checkForComodification();
return new ArrayListSpliterator<E>(ArrayList.this,offset,offset + this.size,this.modCount);
}
}
@Override
public void forEach(Consumer<? super E> action){
//这是java1.8新增方法,传入一个lambda表达式,根据表达式进行遍历操作
Objects.requireNonNull(action);//检查lambda左侧的类型是否正确
final int expectedModCount = modCount;//记录modCount
@SupressWarnings("unchecked")
final E[] elementData = (E[]) this.elementData;
//将集合数组的成员数组取出
final int size = this.size;//得到数组大小
for(int i = 0; modCount == expectedModCount && i < size; i++){
action.accept(elementData[i]);//进行遍历并根据lambda进行右侧操作
//每一次操作都要检查modCont,检查是否有读脏的存在
}
if(modCount != expectedModCount){
throw new ConcurrentModificationException();//如果数据变了,抛出异常
}
}
@Override
public Spliterator<E> spliterator(){
//java1.8新特性,分割器,获得一个分割器
return new ArrayListSpliterator<>(this, 0, -1, 0);
}
static final class ArrayListSpliterator<E> implements Spliterator<E>{
//数组分割器,又是成员内部类,但是感觉没怎么看懂这些代码,希望下次能看懂,遗留问题
private final ArrayList<E> list;
private int index;
private int fence;
private int expectedModCount;
ArrayListSpliterator(ArrayList<E> list, int origin, int fence,
int expectedModCount){
this.list = list;
this.index = origin;
this.fence = fence;
this.expectedModCount = expectedModCount;
}
private int getFence(){
int hi;
ArrayList<E> lst;
if((hi = fence) < 0){
hi = fence = 0;
else{
expectedModCount = lst.modCount;
hi = fence = lst.size;
}
}
return hi;
}
public ArrayListSpliterator<E> trySplit(){
int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
return (lo >= mid)
? null
: new ArrayListSpliterator<E>(list,lo,index = mid,expectedModCount)
}
public boolean tryAdvance(Consumer<? super E> action){
if(action == null)
throw new NullPointerException();
int hi = getFence(), i = index;
if(i < hi){
index = i + 1;
@SuppressWarnings("unchecked")
E e = (E)list.elementData[i];
action.accept(e);
if(list.modCount != expectedModCount)
throw new ConcurrentModificationException();
return true;
}
return false;
}
public void forEachRemaining(Consumer<? super E> action){
int i, hi, mc;
ArrayList<E> lst;
Object[] a;
if(action == null)
throw new NullPointerException();
if((lst = list) != null && (a = lst.elementData) != null){
if((hi = fence) < 0){
mc = lst.modCount;
hi = lst.size;
}
else
mc = expectedModCount;
if((i = index) >= 0 && (index = hi) <= a.length){
for(;i < hi; ++i){
@SuppressWarnings("unchecked")
E e = (E) a[i];
action.accept(e);
}
if(lst.modCount == mc)
return;
}
}
throw new ConcurrentModificationException();
}
public long estimateSize(){
return(long)(getFence() - index);
}
public int characteristics(){
return Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED;
}
}
@Override
public boolean removeIf(Predicate<? super E> filter){
//过滤器,不太清楚怎么实现的,好像并没有用到ArrayList的方法
Objects.requireNonNull(filter);
int removeCount;
final BitSet removeSet = new BitSet(size);
final int expectedModCount = modCount;
final int size = this.size;
for(int i = 0; modCount == expectedModCount && i < size; i++){
@SupressWarnings("unchecked")
final E element = (E) elementData[i];
if(filter.test(element)){
removeSet.set(i);
removeCount++;
}
}
if(modCount != expectedModCount){
throw new ConcurrentModificationException();
}
final boolean anyToRemove = removeCount > 0;
if(anyToRemove){
final int newSize = size - removeCount;
for(int i = 0; j = 0; (i < size) && (j < newSize); i++, j++){
i = removeSet.nextClearBit(i);
elementData[j] = elementData[i];
}
for(int k = newSize; k < size; k++){
elementData[k] = null;
}
this.size = newSize;
if(modCount != expectedModCount){
throw new ConcurrentModificationException();
}
modCount;
}
return anyToRemove;
}
@Override
@SuppressWarnings("unchecked")
public void replaceAll(UnaryOperation<E> operator){
//一次更新数组中所有的元素,更新内容是传进来的operator
Objects.requireNonNull(operator);
final int expectedModCount = modCount;
final int size = this.size;
for(int i = 0; modCount == expectedModCount && i < size; i++){
elementData[i] = operator.apply((E) elementData[i]);
}
if(modCount != expectedModCount){
throw new ConcurrentModificationException();
}
modCount++;
}
@Override
@SuppressWarnings("unchecked")
public void sort(Comparator<? super E> c){
//对集合数组内的元素按照一定规则进行排序,排序规则由Comparator制定
final int expectedModCount = modCount;
Arrays.sort((E[]) elementData, 0, size, c);
if(modCount != expectedModCount)
throw new ConcurrentModificationException();
modCount++;
}
}
这里继续分析分析ArrayList的常用方法。
1.trimToSize
public void trimToSize(){
//类似剪枝吧,把elementData没用的剩余空间清除
modCount++;//保持同步
if(size < elementData.length){
elementData = (size == 0)
? empty_elementdata
: Arrays.copyOf(elementData,size);
}
}
我的理解是相当于剪枝,将ArrayList成员数组中没有装载元素的部分全都减掉,避免空间浪费,在这里重点说一下这个modCount。首先想象一个场景,一个程序想比较这个集合数组中第几个元素比局部变量min = 4小,因此来进行遍历,一个一个比较,本来集合数组中第三个元素是2,是第一个比min小的元素,但是当程序遍历到第二个元素的时候,另一个程序执行了删除操作,将第三个元素删除了。当这个程序遍历到第三个元素时,其实是曾经的第四个元素,比min大,因此继续遍历下去,当遍历到第四个元素时,另外一个程序又执行了增加操作,在第三个元素处插入了一个新元素2。emmmm....这就很尴尬了。
所以为了避免上述尴尬的行为发生,ArrayList就设定了一个modCount参数,用于对某些操作进行计数,而在另外一些操作进行时,会比较modCount是否变化,如果变化就会抛出相关异常,这样保证了底层数据的一致性。
而上面的剪枝方法应该不用多说,因为此操作涉及到了底层成员数组结构的变化,因此modCount需要加一,然后再判断集合元素数量是否小于数组长度,如果小,则再判断元素数量是否为0,是则将数组置为空,否则重新创建一个小数组对原数组进行覆盖。
2.size
public int size(){
//返回数组的size
return size;
}
这个方法就比较简单了,经常用于对ArrayList进行遍历时终点的确定。
3.isEmpty
public boolean isEmpty(){
//判断数组是否为空,即大小是不是零
return size == 0;
}
判断集合数组是否为空,即判断数组元素数量是否为0。
4.contains与indexOf
public boolean contains(Object o){
//查看数组中是否含有某元素,就是调用遍历,查看该元素在哪里
return indexOf(o) >= 0;
}
public int indexOf(Object o){
//查看元素在数组的哪一个位置,不在则返回-1,简单的遍历,不过要考虑null的情况
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;
}
public int lastIndexOf(Object o){
//查看该元素是该数组的倒数第几个,还是遍历,一样的方法
if(o == null){
for(int i = size - 1; i >= 0; i--){
if(elementData[i] == null)
return i;
}
}else{
for(int i = size - 1; i >= 0; i--){
if(o.equals(elementData[i]))
return i;
}
}
return -1;
}
contains方法主要是判断集合中是否含有某元素,此方法通过判断indexOf的返回值来确定是否存在。而indexOf方法则是查看某元素在集合中第一次出现时的索引,实现方法是遍历集合,当遇见了某元素时则返回其索引值,如果遍历结束也没有找到就返回-1。而contains接收到indexOf的返回值时,如果返回值小于0,则说明条件为假,返回false,如果返回值大于等于0,则返回true,说明元素存在。lastIndexOf则是从尾部查找,即逆向遍历。
5.toArray
//将集合转化为数组
public Object[] toArray(){
//将集合数组变为一般数组,就是调用数组方法进行复制并返回
return Arrays.copyOf(elementData,size);
}
@SuppressWarnings("unchecked")//忽略“unchecked”警告
public <T> T[] toArray(T[] a){
//传入一个数组实例,根据数组的类型,将集合转为同类型的数组
if(a.length < size)//如果数组长度小于集合长度,则重新创建一个数组返回
return (T[]) Arrays.copyOf(elementData,size,a.getClass);
System.arraycopy(elementData,0,a,0,size);//否则就将集合元素复制到原数组中
if(a.length > size)//如果数组长度大于集合长度
a[size] = null;//将大于的部分全都赋值为null
return a;
}
将集合转变为数组,此方法被重载了一次,分别有两种处理,如果传入的是空参,则直接将集合转变为Object类型的数组,即返回一个复制的数组。如果传入的是一个有类型的数组,则集合按照这个数组的类型返回相应类型的数组。
6.get
public E get(int index){
//获得某一节点的元素
rangeCheck(index);//检查index是否超出size限制
return elementData(index);//返回元素
}
获得集合某处索引的元素,从此方法就可以看出ArrayList在查询时的效率高,只需要先检查索引的合法性,如果合法,那么直接返回数组索引处的元素就可以了。其实只需要一步。
7.set
public E set(int index,E element){
//设置某一节点的元素为新输入的元素
rangeCheck(index);//范围检查
E oldValue = elementData(index);//先获得该节点原元素
elementData[index] = element;//将新元素放入该节点中
return oldValue;//返回原来的元素!!是原来的,不是新的!
}
更新操作,也挺简单的,首先判断索引是否合法,不合法直接抛出异常,合法则将原索引指向的元素取出来,再将新元素赋给数组的索引处,最后返回原元素,注意这里是有返回值的,而且返回的还是被删除的元素。
8.add
public boolean add(E e){
//向集合数组末尾加入一个元素
ensureCapacityInternal(size + 1);//确保内部容量,进行适当的容量增长
elementData[size++] = e;//将元素放入集合数组的最后一个位置
return true;//返回正确,确认增加成功
}
public void add(int index,E element){
rangeCheckForAdd(index);
//范围检查
ensureCapacityInternal(size + 1);//适当增加容量
System.arraycopy(elementData,index,elementData,index + 1,size - index);
//此处应用了本地方法,arraycopy是将源数组复制到目标数组的方法,参数依次为
//源数组、复制起始位置、目标数组、复制到的起始位置、需复制的长度
elementData[index] = element;//将元素插入到目标地点
size++;//数组大小增加一
}
增加元素,此处add重载了一次,如果直接传入一个元素,则是默认插入在ArrayList的尾部,因此首先进行扩容,扩容后元素数目增加并且将元素插入到数组尾部就完成了,顺利完成则返回true。而如果传入的是索引和元素,则先检查索引是否合法,再扩容,然后先将索引(包括索引处)后面的所有元素向后移动一次,再将新元素插入到索引处,集合元素数目增加。由此可以看出,当ArrayList进行增加操作时牵一发而动全身,效率很低。
9.remove
public E remove(int index){
rangCheck(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;//将数组的大小减一,并且最后一个位置赋值为null
//赋值为null表明移除引用,方便GC回收数据与内存
return oldValue;//返回删除的元素
}
public boolean remove(Object o){
//移出某个元素,还是采用遍历查询,然后移除
if(o == null){//如果元素是null,则移出null
for(int index = 0; index < size; index++)
if(elementData[index] == null){
fastRemove(index);
return true;
}
}else{//否则移出该元素,注意元素是如何判断相等的
for(int index = 0; index < size; index++)
if(o.equals(elementData[index]){
fastRemove(index);
return true;
}
}
//返回的是是否正确移除,而且需要注意,这里移除不是全部移除,而是移除第一个!
return false;
}
private void fastRemove(int index){
//和remove一模一样,唯一不一样就是不返回元素,直接删除,而且只能内部调用
modCount++;
int numMoved = size - index - 1;
if(numMoved > 0)
System.arraycopy(elementData,index + 1,elementData,index,numMoved);
elementData[--size] = null;
}
移除操作,此方法也重载了一次,如果传入的是索引值,则先将需要删除的元素取出来,再判断删除该元素需要移动几个元素(有可能该元素在集合数组尾部,则不需要移动其他元素),再将其后面的元素向前移动一次,最后把数组尾部元素赋为null,方便GC回收,然后返回被删除的元素。如果传入的是一个元素,则开始遍历,找到数组中第一次出现该元素的位置,将其删除,注意,并不是把集合中所有的该元素删除,而是删除顺序第一个出现的该元素。如果删除成功则返回true,如果找不到该元素则返回false.
10.clear
public void clear(){
//清空集合数组
modCount++;
for(int i = 0; i < size; i++)//直接遍历,每一个元素赋值null,清除引用就行
elementData[i] = null;
size = 0;//设置数组大小为0
}
清空数组,首先modCount进行增加操作,然后遍历数组,将所有的索引引用都赋值为null,最后将数组大小设置为0,就完成了一个集合数组的清空操作。
由于博主目前的能力限制(菜是原罪....),所以只能分析到这里了,对于java1.8的一些新特性,还是不太理解,等以后有了进一步的理解,再过来收拾烂摊子。
参考博客:
https://blog.youkuaiyun.com/m0_37664906/article/details/80244531
https://www.cnblogs.com/chenssy/p/3495238.html
https://blog.youkuaiyun.com/ymrfzr/article/details/51326516
https://blog.youkuaiyun.com/weixin_39148512/article/details/79234817