一,ArrayList概述:
ArrayList是基于副本实现的,是一个动态数组,其容量能自动增长,串行C语言中的动态申请内存,动态增长内存。
ArrayList不是线程安全的,只能用在单线程环境下,多线程环境下可以考虑用Collections.synchronizedList(List l)函数返回一个线程安全的ArrayList类,也可以使用并发并发包下的CopyOnWriteArrayList类。
ArrayList实现了一个可序列化的接口,因此它支持序列化,能够通过序列化传输,实现了RandomAccess接口,支持快速随机访问,实际上就是通过下标序号进行快速访问,实现了Cloneable接口,能被克隆。
每个ArrayList实例都有一个容量,该容量是指使用存储列表元素的数组的大小。它总是至少等于列表的大小。依次向ArrayList中不断添加元素,其容量也自动增长。带来数据向新副本的重新复制,因此,如果可预知数据量的多少,可在构造ArrayList时指定其容量。在添加大量元素前,应用程序也可以使用确保容量操作来增加ArrayList实例的容量,这可以减少递增式再分配的数量。
注意,此实现不是同步的。如果多个线程同时访问一个ArrayList实例,而其中至少一个线程从结构上修改了列表,那么它必须保持外部同步。
二,ArrayList的实现:
对于ArrayList而言,它实现List接口,并使用交替保存所有元素。其操作基本上是对阵列的操作。下面我们来分析ArrayList的源代码:
1)私有属性:
ArrayList定义只定义类两个私有属性:
/ **
*用于存储ArrayList元素的数组缓冲区。
* ArrayList的容量是此数组缓冲区的长度。
* /
私有瞬时Object [] elementData;
/ **
* ArrayList的大小(它包含的元素数)。
*
* @序列
* /
私有int大小;
很容易理解,elementDataStorageArrayList内部的元素,大小表示它包含的元素的数量。
有个关键字需要解释:transient。
Java的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的对象数据成员,我们不想用serialization机制来保存它。为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient。
2)构造方法:
ArrayList提供了三种方式的构造器,可以构造一个初始容量为10的空列表,构造一个指定初始容量的空列表以及构造一个包含指定集合的元素的列表,这些元素按照该集合的迭代器返回它们的顺序排列的。
// ArrayList带容量大小的构造函数。
公共ArrayList(int initialCapacity){
super();
如果(initialCapacity <0)
抛出新的IllegalArgumentException(“ Illegal Capacity:” +
initialCapacity);
//新建一个
摘要this.elementData = new Object [initialCapacity];
}
// ArrayList无参构造函数。替代容量是10。public
ArrayList(){
this(10);
}
//创建一个包含集合的ArrayList
public ArrayList(Collection <?
extended E> c){ elementData = c.toArray();
大小= elementData.length;
如果(elementData.getClass()!= Object []。class)
elementData = Arrays.copyOf(elementData,size,Object []。class);
}
3)元素存储:
ArrayList提供了set(int index,E element),add(E e),add(int index,E element),addAll(Collection <?extends E> c),addAll(int index,Collection <?extends E> c) )这些添加元素的方法。下面我们一一讲解:
//用指定的元素替代此列表中指定位置上的元素,并返回以前位于该位置上的元素。
公共E set(int index,E element){
RangeCheck(index);
E oldValue =(E)elementData [index];
elementData [index] =元素;
返回oldValue; boolean add(E e){ sureCapacity(size + 1);
}
//将指定的元素添加到此列表的尾部。elementData [size ++] = e; 返回true;}
//将指定的元素插入此列表中的指定位置。
//如果当前位置有元素,则向右移动当前位于该位置的元素以及所有后续元素(将其索引加1)。
public void add(int index,E element){
如果(索引>大小||索引<0)
抛出新的IndexOutOfBoundsException(“ Index:” + index +“,Size:” + size);
//如果长度不足,将进行扩容。
sureCapacity(size + 1); //增加modCount!
//将elementData中从索引位置开始,长度为size-index的元素,
//复制到从下标为索引+1位置开始的新的elementData数组中。
//即将当前位于该位置的元素以及所有后续元素右移一个位置。
System.arraycopy(elementData,index,elementData,index + 1,size-index);
elementData [index] =元素;
大小++;
}
//按照指定集合的继承器所返回的元素顺序,依次集合中的所有元素添加到此列表的尾部。
公共布尔addAll(Collection <?扩展E> c){
Object [] a = c.toArray();
int numNew = a.length;
sureCapacity(size + numNew); //增加modCount
System.arraycopy(a,0,elementData,size,numNew);
大小+ = numNew;
返回numNew!= 0;
}
//从指定的位置开始,将指定集合中的所有元素插入到此列表中。public
boolean addAll(int index,Collection <?extends E> c){
if(index> size || index <0)
throw new IndexOutOfBoundsException(
“ Index:” + index +“,Size:” + size);
Object [] a = c.toArray();
int numNew = a.length;
sureCapacity(size + numNew); //增加modCount
int numMoved = size-index;
如果(numMoved>
System.arraycopy(elementData,index,elementData,index + numNew,numMoved);
System.arraycopy(a,0,elementData,index,numNew);
大小+ = numNew;
返回numNew!= 0;
}
看到add(E e)中先调用了确保容量(size + 1)方法,之后将元素的索引赋值给elementData [size],而后size自增。例如初次添加时,size为0,add将elementData [0 ]赋值e,然后大小设置为1(类似执行以下两条语句elementData [0] = e; size = 1)。将元素的索引赋值给elementData [size]不是会出现多个越界的情况吗?这里关键就在ensureCapacity(size + 1)中了。
4)元素读取:
公共E get(int index){
RangeCheck(index); //返回此列表中指定位置上的元素。
返回(E)elementData [index];
}
)元素删除:
ArrayList提供了根据下标或指定对象两种方式的删除功能。如下:
romove(int索引):
// public E remove(int index){
RangeCheck(index); //删除此列表中指定位置上的元素。
5个modCount ++;
E oldValue =(E)elementData [index];
8 int numMoved =大小-索引-1;
如果(numMoved> 0)
System.arraycopy(elementData,index + 1,elementData,index,numMoved);
elementData [-size] = null; //让gc进行工作12 13 return oldValue;
}
首先是检查范围,修改modCount,保留将要被除去的元素,将可移除位置之后的元素向前挪动一个位置,将列表末尾元素置空(null),返回被移除的元素。
remove(对象o)
首先通过代码可以看到,当可删除成功后返回true,否则返回false。了index,不通过remove(index)来删除元素呢?因为fastRemove跳过了判断边界的处理,因为找到元素就相当于确定了index不会超过边界,而且fastRemove并不返回被删除的元素。下面是fastRemove的代码,基本和remove(index)一致。
私有无效fastRemove(int index){
modCount ++;
int numMoved =大小-索引-1;
如果(numMoved> 0)
System.arraycopy(elementData,index + 1,elementData,index,
numMoved);
elementData [-size] = null; //让gc进行工作8}
removeRange(int fromIndex,int toIndex)
受保护的void removeRange(int fromIndex,int toIndex){
modCount ++;
int numMoved = size-toIndex;
System.arraycopy(elementData,toIndex,elementData,fromIndex,
numMoved);
//让gc进行工作
int newSize = size-(toIndex-fromIndex);
而(size!= newSize)
elementData [-size] = null;
}
执行过程是将elementData从到索引位置开始的元素向前移动到索引,然后将到索引位置之后的元素全部放置空顺便修改大小。
————————————————————————————————————
最后,我自己是一名从事了多年开发的Java程序员,辞职目前在做自己的Java私人定制课程,今年年初我花了一个月整理了一份最适合2019年学习的Java学习干货,可以送给每一位喜欢Java的小伙伴,想要获取的可以申请加入我自建的Java交流学习裙:钱面六三零中间四七三最后711。即可免费获取。