聊一聊jdk1.8中的ArrayList 底层数组是如何扩容的
一、结论先行
ArrayList在JDK1.8与JDK1.7底层区别
JDK1.7:ArrayList像饿汉式,直接创建一个初始容量为10的数组,当数组的长度不能容下所添加的内容时候,数组会扩容至原大小的1.5倍
JDK1.8:ArrayList像懒汉式,一开始创建一个长度为0的数组,当添加第一个元素时再创建一个始容量为10的数组,当数组的长度不能容下所添加的内容时候,数组会扩容至原大小的1.5倍
二、JDK1.8ArrayList源码分析
1、ArrayList属性
/**
*默认容量的大小
*/
privatestaticfinalintDEFAULT_CAPACITY=10;
/**
*空数组常量
*/
privatestaticfinalObject[]EMPTY_ELEMENTDATA={};
/**
*默认的空数组常量,我们将其与EMPTY_ELEMENTDATA区分开来,
*以便知道添加第一个元素时需要膨胀多少
*/
privatestaticfinalObject[]DEFAULTCAPACITY_EMPTY_ELEMENTDATA={};
/**
*存放元素的数组,从这可以发现ArrayList的底层实现就是一个Object数组
*/
transientObject[]elementData;
/**
*数组中包含元素的个数
*/
privateintsize;
/**
*数组的最大上限,超过上限可能导致OutOfMemoryError错误
*/
privatestaticfinalintMAX_ARRAY_SIZE=Integer.MAX_VALUE-8;
2、构造方法
/**
*指定大小的时候,elementData就变成了我们所指定的初始化大小了
*/
publicArrayList(intinitialCapacity){
if(initialCapacity>0){
this.elementData=newObject[initialCapacity];
}elseif(initialCapacity==0){
this.elementData=EMPTY_ELEMENTDATA;
}else{
thrownewIllegalArgumentException("IllegalCapacity:"+
initialCapacity);
}
}
/**
*根据上面的属性可知,DEFAULTCAPACITY_EMPTY_ELEMENTDATA={},所以默认创建的是一个大小为0的空数组
*/
publicArrayList(){
this.elementData=DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
从上面我可以知道,jkd1.8中默认的是大小为0空数组,这个和jdk1.7之前都是不一样的,这和设计模式的懒汉式很有相似之处
3、add方法,底层扩容机制
/**
*在指定的位置插入指定的元素
*/
publicvoidadd(intindex,Eelement){
rangeCheckForAdd(index);
ensureCapacityInternal(size+1);//IncrementsmodCount!!
System.arraycopy(elementData,index,elementData,index+1,
size-index);
elementData[index]=element;
size++;
}
/**
*将指定的参数添加到列表的末尾,其中size是数组中包含元素的个数
*@parame以附加到此列表中
*/
publicbooleanadd(Ee){
ensureCapacityInternal(size+1);
//数组的下标从0开始,所以size++保证elementData每次添加完一个元素,元素个数也随之加1
elementData[size++]=e;
returntrue;
}
//参数minCapacity表达的意思是所需数组的最小容量,也就是size+1,上面传的值
privatevoidensureCapacityInternal(intminCapacity){
ensureExplicitCapacity(calculateCapacity(elementData,minCapacity));
}
/**
*计算容量的方法,
*/
privatestaticintcalculateCapacity(Object[]elementData,intminCapacity){
//如果是一个空数组,
if(elementData==DEFAULTCAPACITY_EMPTY_ELEMENTDATA){
//DEFAULT_CAPACITY从属性可知默认是10,minCapactity为数组的大小
//由此可以看出他是在添加第一个元素的时候,才创建了长度为10的数组
returnMath.max(DEFAULT_CAPACITY,minCapacity);
}
returnminCapacity;
}
privatevoidensureExplicitCapacity(intminCapacity){
modCount++;
//如果此时所需要的最小的长度大于原数组的长度,则需要进行扩容
if(minCapacity-elementData.length>0)
grow(minCapacity);
}
/**
*增加容量,以确保它至少可以容纳minCapacity指定的元素数量。
*@paramminCapacity表示所需要的扩容的量
*/
privatevoidgrow(intminCapacity){
intoldCapacity=elementData.length;//原数组的长度
//原数组的长度+原数组的长度/2,表示扩容了原来大小的1.5倍,newCapacity:表示需要扩容的量
intnewCapacity=oldCapacity+(oldCapacity>>1);
if(newCapacity-minCapacity<0)
newCapacity=minCapacity;
//如果需要的扩容量大于了本类中定义的最大扩容限制,则扩容到int类型最大长度
if(newCapacity-MAX_ARRAY_SIZE>0)
newCapacity=hugeCapacity(minCapacity);
//调用的是数组的复制方法,实现扩容
elementData=Arrays.copyOf(elementData,newCapacity);
}
privatestaticinthugeCapacity(intminCapacity){
if(minCapacity<0)//overflow
thrownewOutOfMemoryError();
//如若需要扩容的量大于最大限制,则扩容量改为int最大限制量:2147483647,否则为本类中所限制长度:2147483647-8
return(minCapacity>MAX_ARRAY_SIZE)?Integer.MAX_VALUE:MAX_ARRAY_SIZE;
}
解释:intnewCapacity=oldCapacity+(oldCapacity>>1);
这个>>1表示的是右移一位,转为二进制我们应该一下就明白了:比如16,转为2进制为
10000,右移一位则成为01000,换为十进制就是8,扩容的大小也就相当于oldCapacity+oldCapacity/2了
4、总结
jdk1.8中:
add方法总结起来就是在插入数据之前,会先检查是否需要扩容,通过无参构造方法来创建ArrayList时,它的大小其实是为0的,只有在使用到的时候,才会通过grow方法去创建一个大小为10的数组。
publicbooleanadd(Ee)方法的复杂度为O(1),涉及到扩容的操作是非常少的,可以忽略不计,它的本质是添加元素到数组中最后一个元素的后面。
publicvoidadd(intindex,Eelement)这个是带指定下标的add方法,复杂度为O(n),因为涉及到数组中元素的移动,这一操作非常耗时,由此可见ArrayList不适合插入和删除操作。
三、ArrayList与Vector的区别
现在Vector已经很少有人用了,这里只是简单的记录下二者区别:
1、ArrayList线程不安全,Vector是线程安全的
通过Vector源码我们可以知道很多方法都是加了synchronized关键字,所以Vector是线程安全的。
2、ArrayList创建的默认大小为0,Vector创建时的默认大小是10。
3、ArrayList每次扩容都以当前数组大小的1.5倍去扩容,Vector每次扩容都以当前数组大小的2倍去扩容。当指定了capacityIncrement之后,每次扩容仅在原先基础上增加capacityIncrement个单位空间。
补充知识:ArrayList详解,底层是数组,实现Serializable接口
一、对于ArrayList需要掌握的七点内容
ArrayList的创建:即构造器
往ArrayList中添加对象:即add(E)方法
获取ArrayList中的单个对象:即get(intindex)方法
删除ArrayList中的对象:即remove(E)方法
遍历ArrayList中的对象:即iterator,在实际中更常用的是增强型的for循环去做遍历
判断对象是否存在于ArrayList中:contain(E)
ArrayList中对象的排序:主要取决于所采取的排序算法(以后讲)
二、源码分析
2.1、ArrayList的创建(常见的两种方式)
List
strList=newArrayList (); List
strList2=newArrayList (2);
ArrayList源代码:
基本属性:
//对象数组:ArrayList的底层数据结构 privatetransientObject[]elementData; //elementData中已存放的元素的个数,注意:不是elementData的容量 privateintsize;
注意:
transient关键字的作用:在采用Java默认的序列化机制的时候,被该关键字修饰的属性不会被序列化。
ArrayList类实现了java.io.Serializable接口,即采用了Java默认的序列化机制
上面的elementData属性采用了transient来修饰,表明其不使用Java默认的序列化机制来实例化,但是该属性是ArrayList的底层数据结构,在网络传输中一定需要将其序列化,之后使用的时候还需要反序列化,那不采用Java默认的序列化机制,那采用什么呢?直接翻到源码的最下边有两个方法,发现ArrayList自己实现了序列化和反序列化的方法
ViewCode
构造器:
/**
*创建一个容量为initialCapacity的空的(size==0)对象数组
*/
publicArrayList(intinitialCapacity){
super();//即父类protectedAbstractList(){}
if(initialCapacity<0)
thrownewIllegalArgumentException("IllegalCapacity:"+initialCapacity);
this.elementData=newObject[initialCapacity];
}
/**
*默认初始化一个容量为10的对象数组
*/
publicArrayList(){
this(10);//即上边的publicArrayList(intinitialCapacity){}构造器
}
在我们执行newArrayList
在我们执行newArrayList
注意:
上边有参构造器的super()方法是ArrayList父类AbstractList的构造方法,这个构造方法如下,是一个空构造方法:
protectedAbstractList(){
}
在实际使用中,如果我们能对所需的ArrayList的大小进行判断,有两个好处:
节省内存空间(eg.我们只需要放置两个元素到数组,newArrayList
避免数组扩容(下边会讲)引起的效率下降(eg.我们只需要放置大约37个元素到数组,newArrayList
2.2、往ArrayList中添加对象(常见的两个方法add(E)和addAll(Collectionc))
以上这篇聊一聊jdk1.8中的ArrayList底层数组是如何扩容的就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。