数组介绍数组对于我们来说并不陌生。它们是内存中一个连续的内存空间。数组元素可以通过下标进行随机访问,如图1所示,而JDK中提供了更强大的ArrayList,其底层是基于数据的。为什么JDK已经提供数据并使用ArrayList?下面通过比较两者的区别来谈谈ArrayList的优点。如何获取数组的实际元素?面试中经常会问到java中数组的长度和ArrayList中的大小有什么区别?通过下面的代码,我们其实已经看到,数组的长度永远是声明时定义的长度,即使数组中只有一个元素。通过下面的输出,有人会问arr[1]输出的是0,这是因为int类型是java中的基本类型,所以在初始化数组的时候,默认会用0填充。ArrayList中的length为list数组,实际元素个数与数组明显不同。具体如何实现,后面会根据源码分析,对数组进行删除和添加元素。删除数组中的一个元素后,需要移动该元素以保证数组的连续性。如果数组被删除最后一个元素不需要移动。如果删除下标为3的元素使数组连续,则元素3之后的元素需要依次向前移动。删除元素后,下标为7的元素不再包含有效元素。如果我们在使用的数组中删除数据后,需要编写相关代码来完成这些移动操作。使用ArrayList删除和移动数据将自动完成,无需我们手动向数组添加元素。如果插入元素后的元素个数超过了数组的长度,就会报错数组越界。这是因为数组的容量是固定的,不能动态增加。指定位置处的元素,插入9。正因为如此,数组不是动态的,所以在使用数据的时候,需要考虑很多数组本身的问题,从而不能专注于编写核心代码。ArrayList的出现,正式解决了数组中的以下问题。动态扩展有效数据长度增加,删除元素后数据移动。数据遍历结合ArrayList的一些面试题来看具体源码。ArrayList默认初始化容量什么时候扩容?一个重要的属性elementData是存储数据的数组size数组中元素的实际个数ArrayList的构造函数分为带参数和不带参数两种方式。构造函数为ArrayList(),初始化后不分配内存空间,而是在第一次add操作时分配参数。构造函数ArrayList(intinitialCapacity)初始化并分配指定大小的内存。参数构造函数ArrayList(Collectionc)初始化内存分配问题1:无参初始化默认容量是多少,什么时候扩容,扩容多少?具体分析可以看下方评论。//默认初始化elementData为空数组privatestaticfinalObject[]DEFAULTCAPACITY_EMPTY_ELEMENTDATA={};publicArrayList(){this.elementData=DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}//接下来执行添加操作publicbooleanadd(Ee){//检查当前数据是否capacityissufficientensureCapacityInternal(size+1);//IncrementsmodCount!!//size可以自动增加保证有效元素个数elementData[size++]=e;returntrue;}//计算出的capacity主要用于默认第一次添加操作的初始化privatestaticintcalculateCapacity(Object[]elementData,intminCapacity){//在默认初始化elementData=DEFAULTCAPACITY_EMPTY_ELEMENTDATA//这里返回默认容量DEFAULT_CAPACITY,这个值的大小为10if(elementData==DEFAULTCAPACITY_EMPTY_ELEMENTDATA){returnMath.最大(默认容量TY,minCapacity);}returnminCapacity;}privatevoidensureCapacityInternal(intminCapacity){ensureExplicitCapacity(calculateCapacity(elementData,minCapacity));}privatevoidensureExplicitCapacity(intminCapacity){modCount++;//检查是否扩展,如果当前容量大于array,expandit//overflow-consciouscodeif(minCapacity-elementData.length>0)grow(minCapacity);}//按原数据长度扩容//oldCapacity>>1位运算相当于oldCapacity=oldCapacity/2//新容量为newCapacity=oldCapacity+(oldCapacity>>1);即原来的1.5倍//将旧数组中的数据移动到新数组中//这里解释一下为什么是1.5倍,这是扩展次数和空间大小之间的折衷如果太多了扩大倍数,效率会降低。如果空间太大,就会浪费空间。minCapacity;if(newCapacity-MAX_ARRAY_SIZE>0)newCapacity=hugeCapacity(minCapacity);//minCapacity通常接近size,所以这个isawin:elementData=Arrays.copyOf(elementData,newCapacity);}问题2:数据遍历,fast-fail,为什么不是迭代器删除Outofbounds会在下面的代码注释中找到答案ArrayList的遍历支持、foreach、迭代器遍历fail-fast机制是java集合(Collection)中的一种出错机制。当多个线程对同一集合的内容进行操作时,可能会生成fail-fast事件。例如:线程A通过迭代器遍历一个集合时,如果集合的内容被其他线程改变了;然后当线程A访问集合时,将抛出ConcurrentModificationException并生成fail-fast事件。//ArrayList迭代器是通过两个游标数组的下标进行的如果cursor不等于size,说明还有一个元素这里判断当前游标超出数组有效元素个数if(i>=size)thrownewNoSuchElementException();Object[]elementData=ArrayList.this.elementData;//判断数组是否越界if(i>=elementData.length)thrownewConcurrentModificationException();cursor=i+1;//更新lastRet游标为当前数组下标return(E)elementData[lastRet=i];}publicvoidremove(){//检查是否删除了元素下标合法if(lastRet<0)thrownewIllegalStateException();checkForComodification();try{//删除原数组中的下标元素ArrayList.this.remove(lastRet);//更新游标cursor=lastRet;lastRet=-1;expectedModCount=modCount;}catch(IndexOutOfBoundsExceptionex){thrownewConcurrentModificationException();}}}
