Java ArrayList 实现实例讲解
ArrayList概述:
ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存。
ArrayList不是线程安全的,只能用在单线程环境下,多线程环境下可以考虑用Collections.synchronizedList(Listl)函数返回一个线程安全的ArrayList类,也可以使用concurrent并发包下的CopyOnWriteArrayList类。
ArrayList实现了Serializable接口,因此它支持序列化,能够通过序列化传输,实现了RandomAccess接口,支持快速随机访问,实际上就是通过下标序号进行快速访问,实现了Cloneable接口,能被克隆。
每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组的大小。它总是至少等于列表的大小。随着向ArrayList中不断添加元素,其容量也自动增长。自动增长会带来数据向新数组的重新拷贝,因此,如果可预知数据量的多少,可在构造ArrayList时指定其容量。在添加大量元素前,应用程序也可以使用ensureCapacity操作来增加ArrayList实例的容量,这可以减少递增式再分配的数量。
注意,此实现不是同步的。如果多个线程同时访问一个ArrayList实例,而其中至少一个线程从结构上修改了列表,那么它必须保持外部同步。
下面对javaarraylist做一个记录和总结吧
publicclassarraylist<E>{ /** *存放集合的元素 * */ privatetransientObject[]elementData; /**元素的大小*/ privateintsize;
定义了一个泛型类,一个object的数组和一个私有变量来记录该集合的元素数量,原文多了一个私有变量,我也不知道干嘛用的,作者也没解释也没提及到,我没使用也没事
/** *根据指定大小初始化 *@paraminitialCapacity */ publicarraylist(intinitialCapacity){ super(); if(initialCapacity<=0){ //抛异常 thrownewIllegalArgumentException("初始化参数不能小于0"); }else{ //初始化数组 this.elementData=newObject[initialCapacity]; } } /** *默认初始化 */ publicarraylist(){ this(10); } /** *根据一个集合类初始化 *@paramc一个必须继承了Collection接口的类 */ publicarraylist(Collection<?extendsE>c){ //初始化 elementData=c.toArray(); size=elementData.length; //如果不是任意类型的数组就转换Objec类型 if(elementData.getClass()!=Object[].class){ elementData=Arrays.copyOf(elementData,size,Object[].class); } }
3个初始化方法,根据默认大小进行数组的初始化,给定大小初始化和传递一个继承了Collection集合接口的类进行转换赋值初始化
/** *扩容集合 *@paramminCapacity */ publicvoidensureCapacity(intminCapacity){ /**当前数组的大小*/ intoldCapacity=elementData.length; if(minCapacity>oldCapacity){ /** *oldData虽然没有被使用,但是这是关于内存管理的原因和Arrays.copyOf()方法不是线程安全 *oldData在if的生命周期内引用elementData这个变量,所以不会被GC回收掉 *当Arrays.copyOf()方法在把elementData复制到newCapacity时,就可以防止新的内存或是其他线程分配内存是elementData内存被侵占修改 *当结束是离开if,oldData周期就结束被回收 */ ObjectoldData[]=elementData; intnewCapacity=(oldCapacity*3)/2+1;//增加50%+1 if(newCapacity<minCapacity) newCapacity=minCapacity; //使用Arrays.copyOf把集合的元素复制并生成一个新的数组 elementData=Arrays.copyOf(elementData,newCapacity); } }
这是一个核心的方法,集合的扩容,其实是对数组的扩容,minCapacity集合的大小,进行对比判断是否应该进行扩容,使用了Arrays.copyOf()方法进行扩容,
原文有进行详细的解释,这个方法把第一个参数的内容复制到一个新的数组中,数组的大小是第二个参数,并返回一个新的数组,关于oldData的变量上文有详细的注释
/** *检查索引是否出界 *@paramindex */ privatevoidRangeCheck(intindex){ if(index>size||index<0){ thrownewIndexOutOfBoundsException("下标超出,Index:"+index+",Size:"+size); } }
一个下标的检索是否出1/**
*添加元素 *将指定的元素添加到集合的末尾 *@parame添加的元素 *@return */ publicbooleanadd(Ee){ ensureCapacity(size+1); elementData[size]=e; size++; returntrue; }
添加元素,先进行扩容,在赋值,然后元素加一,注意size+1字段size并没有加一,这里进行的是算术的运算,所以在后面才需要进行自增
/** *添加元素 *将元素添加到指定的位置 *@paramindex指定的索引下标 *@paramelement元素 *@return */ publicbooleanadd(intindex,Eelement){ RangeCheck(index); ensureCapacity(size+1); //将elementData中从Index位置开始、长度为size-index的元素, //拷贝到从下标为index+1位置开始的新的elementData数组中。 //即将当前位于该位置的元素以及所有后续元素右移一个位置。 System.arraycopy(elementData,index,elementData,index+1,size-index); elementData[index]=element; size++;//元素加一 returntrue; }
这里不同的是System.arraycopy(elementData,index,elementData,index+1,size-index);
这是一个c的内部方法,详细的原文有解释,这里就不说了,这个也是整个ArrayList的核心所在,也Arrays.copyOf()的内部实现原理
/** *添加全部元素 *按照指定collection的迭代器所返回的元素顺序,将该collection中的所有元素添加到此列表的尾部。 *@paramc *@return */ publicbooleanaddAll(Collection<?extendsE>c){ Object[]newElement=c.toArray(); intelementLength=newElement.length; ensureCapacity(size+elementLength); //从newElement0的下标开始,elementLength个元素,elementDatasize的下标 System.arraycopy(newElement,0,elementData,size,elementLength); size+=elementLength; returnelementLength!=0; }
基本上其他方法都只是根据不同的情况进行不同的处理,比如通过接口把数据对象传递进来然后获取长度进行扩容,在把数据使用System,arraycopy复制到新的数组中
/** *指定位置,添加全部元素 *@paramindex插入位置的下标 *@paramc插入的元素集合 *@return */ publicbooleanaddAll(intindex,Collection<?extendsE>c){ if(index>size||index<0){ thrownewIndexOutOfBoundsException("Index:"+index+",Size:"+size); } Object[]newElement=c.toArray(); intelementLength=newElement.length; ensureCapacity(size+elementLength); intnumMoved=size-index; //判断插入的位置是否在数组中间 if(numMoved>0){ //把index插入位置的后面的所有元素往后移 //elementDataindex下标开始的numMoved个元素插入到elementData的index+elementLength位置 System.arraycopy(elementData,index,elementData,index+elementLength,numMoved); } //把newElement里从0开始的elementLength个元素添加到elementDataindex开始的位置 System.arraycopy(newElement,0,elementData,index,elementLength); size+=elementLength; returnelementLength!=0; } /** *指定下标赋值 *@paramindex *@paramelement *@return */ publicEset(intindex,Eelement){ RangeCheck(index); EoldElement=(E)elementData[index]; elementData[index]=element; returnoldElement; } /** *根据下标取值 *@paramindex *@return */ publicEget(intindex){ RangeCheck(index); return(E)elementData[index]; } /** *根据下标移除元素 *@paramindex */ publicEremove(intindex){ RangeCheck(index); EoldElement=(E)elementData[index]; /**移除的下标后面的元素数量*/ intnumMoved=size-index-1; //如果在数组范围内就进行移动 if(numMoved>0) System.arraycopy(elementData,index+1,elementData,index,numMoved); //移除 elementData[--size]=null; returnoldElement; } /** *根据元素移除 *@paramobj *@return */ publicbooleanremove(Objectobj){ //Arraylist允许存放null,所以也要进行判断处理 if(obj==null){ for(intindex=0;index<size;index++){ if(elementData[index]==null){ remove(index); returntrue; } } }else{ for(intindex=0;index<size;index++){ if(obj.equals(elementData[index])){ remove(index); returntrue; } } } returnfalse; } /** *根据下标移除指定范围内的元素 *@paramfromIndex开始 *@paramtoIndex结束 */ protectedvoidremoveRange(intfromIndex,inttoIndex){ RangeCheck(fromIndex); RangeCheck(toIndex); //要移动的元素数 intnumMoved=size-toIndex; //把toIndex后面的元素移动到fromIndex System.arraycopy(elementData,toIndex,elementData,fromIndex,numMoved); //要移除的元素数量 intnewSize=size-(toIndex-fromIndex); while(size!=newSize){ elementData[--size]=null; } } /** *把数组容量调整到实际的容量 */ publicvoidtrimToSize(){ intleng=elementData.length; if(size<leng){ Object[]old=elementData; elementData=Arrays.copyOf(elementData,size); } } /** *把集合元素转换成数组 *@return */ publicObject[]toArray(){ returnArrays.copyOf(elementData,size); } public<T>T[]toArray(T[]a){ if(a.length<size){ return(T[])Arrays.copyOf(elementData,size,a.getClass()); } //把集合元素复制到a数组中 System.arraycopy(elementData,0,a,0,size); if(a.length>size){ for(intindex=size;index<a.length;index++){ a[index]=null; } } returna; }
基本上都是对数组进行操作和使用c的方法进行赋值移动等,详细的可以查看原文,原文中除了那个私有变量外也没多少问题,代码可以完美运行,这李要注意的和难点就会是System,arraycopy和Arrayist.copy()这2个方法
和在扩容方法里oldData这个变量的使用,这个变量真的很好,一开始我也不知道为什么要这么使用,在原文的末尾会进行解释。
以上所述是小编给大家介绍的JavaArrayList实现实例讲解,希望对大家有所帮助,如果大家有任何疑问欢迎给我留言,小编会及时回复大家的,在此也非常感谢大家对毛票票网站的支持!