java面试 集合中知识点 ArrayList源码+扩容机制分析 整理.docx
《java面试 集合中知识点 ArrayList源码+扩容机制分析 整理.docx》由会员分享,可在线阅读,更多相关《java面试 集合中知识点 ArrayList源码+扩容机制分析 整理.docx(27页珍藏版)》请在冰豆网上搜索。
![java面试 集合中知识点 ArrayList源码+扩容机制分析 整理.docx](https://file1.bdocx.com/fileroot1/2023-1/3/d3dc24be-4e16-4601-bfb3-6c0dc644ff24/d3dc24be-4e16-4601-bfb3-6c0dc644ff241.gif)
java面试集合中知识点ArrayList源码+扩容机制分析整理
1.ArrayList简介
ArrayList的底层是数组队列,相当于动态数组。
与Java中的数组相比,它的容量能动态增长。
在添加大量元素前,应用程序可以使用ensureCapacity操作来增加ArrayList实例的容量。
这可以减少递增式再分配的数量。
ArrayList继承于AbstractList,实现了List,RandomAccess,Cloneable,java.io.Serializable这些接口。
publicclassArrayListextendsAbstractList
implementsList,RandomAccess,Cloneable,java.io.Serializable{
}
•RandomAccess是一个标志接口,表明实现这个这个接口的List集合是支持快速随机访问的。
在ArrayList中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。
•ArrayList实现了Cloneable接口,即覆盖了函数clone(),能被克隆。
•ArrayList实现了java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。
1.1.Arraylist和Vector的区别?
1.ArrayList是List的主要实现类,底层使用Object[]存储,适用于频繁的查找工作,线程不安全;
2.Vector是List的古老实现类,底层使用Object[]存储,线程安全的。
1.2.Arraylist与LinkedList区别?
3.是否保证线程安全:
ArrayList和LinkedList都是不同步的,也就是不保证线程安全;
4.底层数据结构:
Arraylist底层使用的是Object数组;LinkedList底层使用的是双向链表数据结构(JDK1.6之前为循环链表,JDK1.7取消了循环。
注意双向链表和双向循环链表的区别,下面有介绍到!
)
5.插入和删除是否受元素位置的影响:
①ArrayList采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。
比如:
执行add(Ee)方法的时候,ArrayList会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是O
(1)。
但是如果要在指定位置i插入和删除元素的话(add(intindex,Eelement))时间复杂度就为O(n-i)。
因为在进行上述操作的时候集合中第i和第i个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。
②LinkedList采用链表存储,所以对于add(Ee)方法的插入,删除元素时间复杂度不受元素位置的影响,近似O
(1),如果是要在指定位置i插入和删除元素的话((add(intindex,Eelement))时间复杂度近似为o(n))因为需要先移动到指定位置再插入。
6.是否支持快速随机访问:
LinkedList不支持高效的随机元素访问,而ArrayList支持。
快速随机访问就是通过元素的序号快速获取元素对象(对应于get(intindex)方法)。
7.内存空间占用:
ArrayList的空间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。
2.ArrayList核心源码解读
packagejava.util;
importjava.util.function.Consumer;
importjava.util.function.Predicate;
importjava.util.function.UnaryOperator;
publicclassArrayListextendsAbstractList
implementsList,RandomAccess,Cloneable,java.io.Serializable
{
privatestaticfinallongserialVersionUID=8683452581122892189L;
/**
*默认初始容量大小
*/
privatestaticfinalintDEFAULT_CAPACITY=10;
/**
*空数组(用于空实例)。
*/
privatestaticfinalObject[]EMPTY_ELEMENTDATA={};
//用于默认大小空实例的共享空数组实例。
//我们把它从EMPTY_ELEMENTDATA数组中区分出来,以知道在添加第一个元素时容量需要增加多少。
privatestaticfinalObject[]DEFAULTCAPACITY_EMPTY_ELEMENTDATA={};
/**
*保存ArrayList数据的数组
*/
transientObject[]elementData;//non-privatetosimplifynestedclassaccess
/**
*ArrayList所包含的元素个数
*/
privateintsize;
/**
*带初始容量参数的构造函数(用户可以在创建ArrayList对象时自己指定集合的初始大小)
*/
publicArrayList(intinitialCapacity){
if(initialCapacity>0){
//如果传入的参数大于0,创建initialCapacity大小的数组
this.elementData=newObject[initialCapacity];
}elseif(initialCapacity==0){
//如果传入的参数等于0,创建空数组
this.elementData=EMPTY_ELEMENTDATA;
}else{
//其他情况,抛出异常
thrownewIllegalArgumentException("IllegalCapacity:
"+
initialCapacity);
}
}
/**
*默认无参构造函数
*DEFAULTCAPACITY_EMPTY_ELEMENTDATA为0.初始化为10,也就是说初始其实是空数组当添加第一个元素的时候数组容量才变成10
*/
publicArrayList(){
this.elementData=DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
*构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。
*/
publicArrayList(Collection
extendsE>c){
//将指定集合转换为数组
elementData=c.toArray();
//如果elementData数组的长度不为0
if((size=elementData.length)!
=0){
//如果elementData不是Object类型数据(c.toArray可能返回的不是Object类型的数组所以加上下面的语句用于判断)
if(elementData.getClass()!
=Object[].class)
//将原来不是Object类型的elementData数组的内容,赋值给新的Object类型的elementData数组
elementData=Arrays.copyOf(elementData,size,Object[].class);
}else{
//其他情况,用空数组代替
this.elementData=EMPTY_ELEMENTDATA;
}
}
/**
*修改这个ArrayList实例的容量是列表的当前大小。
应用程序可以使用此操作来最小化ArrayList实例的存储。
*/
publicvoidtrimToSize(){
modCount++;
if(sizeelementData=(size==0)
?
EMPTY_ELEMENTDATA
:
Arrays.copyOf(elementData,size);
}
}
//下面是ArrayList的扩容机制
//ArrayList的扩容机制提高了性能,如果每次只扩充一个,
//那么频繁的插入会导致频繁的拷贝,降低性能,而ArrayList的扩容机制避免了这种情况。
/**
*如有必要,增加此ArrayList实例的容量,以确保它至少能容纳元素的数量
*@paramminCapacity所需的最小容量
*/
publicvoidensureCapacity(intminCapacity){
//如果是true,minExpand的值为0,如果是false,minExpand的值为10
intminExpand=(elementData!
=DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
//anysizeifnotdefaultelementtable
?
0
//largerthandefaultfordefaultemptytable.It'salready
//supposedtobeatdefaultsize.
:
DEFAULT_CAPACITY;
//如果最小容量大于已有的最大容量
if(minCapacity>minExpand){
ensureExplicitCapacity(minCapacity);
}
}
//得到最小扩容量
privatevoidensureCapacityInternal(intminCapacity){
if(elementData==DEFAULTCAPACITY_EMPTY_ELEMENTDATA){
//获取“默认的容量”和“传入参数”两者之间的最大值
minCapacity=Math.max(DEFAULT_CAPACITY,minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
//判断是否需要扩容
privatevoidensureExplicitCapacity(intminCapacity){
modCount++;
//overflow-consciouscode
if(minCapacity-elementData.length>0)
//调用grow方法进行扩容,调用此方法代表已经开始扩容了
grow(minCapacity);
}
/**
*要分配的最大数组大小
*/
privatestaticfinalintMAX_ARRAY_SIZE=Integer.MAX_VALUE-8;
/**
*ArrayList扩容的核心方法。
*/
privatevoidgrow(intminCapacity){
//oldCapacity为旧容量,newCapacity为新容量
intoldCapacity=elementData.length;
//将oldCapacity右移一位,其效果相当于oldCapacity/2,
//我们知道位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5倍,
intnewCapacity=oldCapacity+(oldCapacity>>1);
//然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量,
if(newCapacity-minCapacity<0)
newCapacity=minCapacity;
//再检查新容量是否超出了ArrayList所定义的最大容量,
//若超出了,则调用hugeCapacity()来比较minCapacity和MAX_ARRAY_SIZE,
//如果minCapacity大于MAX_ARRAY_SIZE,则新容量则为Interger.MAX_VALUE,否则,新容量大小则为MAX_ARRAY_SIZE。
if(newCapacity-MAX_ARRAY_SIZE>0)
newCapacity=hugeCapacity(minCapacity);
//minCapacityisusuallyclosetosize,sothisisawin:
elementData=Arrays.copyOf(elementData,newCapacity);
}
//比较minCapacity和MAX_ARRAY_SIZE
privatestaticinthugeCapacity(intminCapacity){
if(minCapacity<0)//overflow
thrownewOutOfMemoryError();
return(minCapacity>MAX_ARRAY_SIZE)?
Integer.MAX_VALUE:
MAX_ARRAY_SIZE;
}
/**
*返回此列表中的元素数。
*/
publicintsize(){
returnsize;
}
/**
*如果此列表不包含元素,则返回true。
*/
publicbooleanisEmpty(){
//注意=和==的区别
returnsize==0;
}
/**
*如果此列表包含指定的元素,则返回true。
*/
publicbooleancontains(Objecto){
//indexOf()方法:
返回此列表中指定元素的首次出现的索引,如果此列表不包含此元素,则为-1
returnindexOf(o)>=0;
}
/**
*返回此列表中指定元素的首次出现的索引,如果此列表不包含此元素,则为-1
*/
publicintindexOf(Objecto){
if(o==null){
for(inti=0;iif(elementData[i]==null)
returni;
}else{
for(inti=0;i//equals()方法比较
if(o.equals(elementData[i]))
returni;
}
return-1;
}
/**
*返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。
.
*/
publicintlastIndexOf(Objecto){
if(o==null){
for(inti=size-1;i>=0;i--)
if(elementData[i]==null)
returni;
}else{
for(inti=size-1;i>=0;i--)
if(o.equals(elementData[i]))
returni;
}
return-1;
}
/**
*返回此ArrayList实例的浅拷贝。
(元素本身不被复制。
)
*/
publicObjectclone(){
try{
ArrayList
>v=(ArrayList
>)super.clone();
//Arrays.copyOf功能是实现数组的复制,返回复制后的数组。
参数是被复制的数组和复制的长度
v.elementData=Arrays.copyOf(elementData,size);
v.modCount=0;
returnv;
}catch(CloneNotSupportedExceptione){
//这不应该发生,因为我们是可以克隆的
thrownewInternalError(e);
}
}
/**
*以正确的顺序(从第一个到最后一个元素)返回一个包含此列表中所有元素的数组。
*返回的数组将是“安全的”,因为该列表不保留对它的引用。
(换句话说,这个方法必须分配一个新的数组)。
*因此,调用者可以自由地修改返回的数组。
此方法充当基于阵列和基于集合的API之间的桥梁。
*/
publicObject[]toArray(){
returnArrays.copyOf(elementData,size);
}
/**
*以正确的顺序返回一个包含此列表中所有元素的数组(从第一个到最后一个元素);
*返回的数组的运行时类型是指定数组的运行时类型。
如果列表适合指定的数组,则返回其中。
*否则,将为指定数组的运行时类型和此列表的大小分配一个新数组。
*如果列表适用于指定的数组,其余空间(即数组的列表数量多于此元素),则紧跟在集合结束后的数组中的元素设置为null。
*(这仅在调用者知道列表不包含任何空元素的情况下才能确定列表的长度。
)
*/
@SuppressWarnings("unchecked")
publicT[]toArray(T[]a){
if(a.length//新建一个运行时类型的数组,但是ArrayList数组的内容
return(T[])Arrays.copyOf(elementData,size,a.getClass());
//调用System提供的arraycopy()方法实现数组之间的复制
System.arraycopy(elementData,0,a,0,size);
if(a.length>size)
a[size]=null;
returna;
}
//PositionalAccessOperations
@SuppressWarnings("unchecked")
EelementData(intindex){
return(E)elementData[index];
}
/**
*返回此列表中指定位置的元素。
*/
publicEget(intindex){
rangeCheck(index);
returnelementData(index);
}
/**
*用指定的元素替换此列表中指定位置的元素。
*/
publicEset(intindex,Eelement){
//对index进行界限检查
rangeCheck(index);
EoldValue=elementData(index);
elementData[index]=element;
//返回原来在这个位置的元素
returnoldValue;
}
/**
*将指定的元素追加到此列表的末尾。
*/
publicbooleanadd(Ee){
ensureCapacityInternal(size+1);//IncrementsmodCount!
!
//这里看到ArrayList添加元素的实质就相当于为数组赋值
elementData[size++]=e;
returntrue;
}
/**
*在此列表中的指定位置插入指定的元素。
*先调用rangeCheckForAdd对index进行界限检查;然后调用ensureCapacityInternal方法保证capacity足够大;
*再将从index开始之后的所有成员后移一个位置;将element插入index位置;最后size加1。
*/
publicvoidadd(intindex,Eelement){
rangeCheckForAdd(index);
ensureCapacityInternal(size+1);//IncrementsmodCount!
!
//arraycopy()这个实现数组之间复制的方法一定要看一下,下面就用到了arraycopy()方法实现数组自己复制自己
System.arraycopy(elementData,index,elementData,index+1,
size-index);
elementData[index]=element;
size++;
}
/**
*删除该列表中指定位置的元素。
将任何后续元素移动到左侧(从其索引中减去一个元素)。
*/
publicEremove(intindex){
rangeCheck(index);
modCount++;
EoldValue=elementData(index);
intnumMoved=size-index-1;
if(numMoved>0)
System.arraycopy(elementData,index+1,elementData,index,
numMoved);
elementData[--size]=null;//cleartoletGCdoitswork
//从列表中删除的元素
returnoldValue;
}
/**
*从列表中删除指定元素的第一个出现(如果存在)。
如果列表不包含该元素,则它不会更改。
*返回true,如果此列表包含指定的元素
*/
publicbooleanremove(Objecto){
if(o==null){
for(intindex=0;indexif(elementData[index]==null){
fastRemove(index);
returntrue;
}
}else{
for(intindex=0;indexif(o.equals(el