长久以来,ArrayList凭借着自生的存储结构优点以及简单好用的操作方法有着很高的曝光使用率。相信很多朋友都对ArrayList的优缺点倒背如流了,比如有序,访问元素速度快,插入和删除元素效率较慢等,本篇文章也会围绕着几点来分析说明,让大家从本质上来理解这一集合类。
开局一张图,剩下就全靠我编了。
step1:创建集合添加元素
大家都清楚ArrayList是基于数组来存储元素的。数组是在内存中开辟一块连续的空间,中间不留空,通过索引就能找到元素。如上图所示,班级在操场上组织开展活动,至少得需要申请告诉学校自己班级在何时占用何地,以及人数规模。在ArrayList中创建一个大小为10的数组,供班级使用。
public class ArrayList {
private Object[] mElementData;
private int mSize;
public ArrayList(int initSize){
mElementData = new Object[10];
}
public void add(T element){
mElementData[mSize] = element;
mSize++;
}
}
再让女同学们集合站好。
public static void main(String[] args) {
ArrayList studentList = new ArrayList<>(10);
studentList.add(new GirlStudent(18,175));
studentList.add(new GirlStudent(18,174));
studentList.add(new GirlStudent(18,172));
studentList.add(new GirlStudent(18,171));
studentList.add(new GirlStudent(18,170));
studentList.add(new GirlStudent(18,169));
studentList.add(new GirlStudent(18,168));
studentList.add(new GirlStudent(18,167));
studentList.add(new GirlStudent(18,166));
}
step2:插入元素和删除元素:
这时候又来了一位迟到的女同学,这位女同学个子也挺高的,有173。得站在第三个位置,所以先得麻烦后面7位同学都往后摞一摞,给迟到的女同学让出位置来,迟到的女同学再入队。有女同学出队也如此,当有女同学身体不适需要休息,出队后为了队形整齐,空出的位置也需要后面的女同学依次向前走一步补齐。ArrayList中通过执行System.arraycopy()方法以复制数组的方式来完成删除,插入,扩容等功能,说明如下:
/**
*
* @param src 原数组
* @param srcPos 从原数组哪个位置开始需要复制
* @param dest 目标数组
* @param destPos 从目标数组哪个位置开始复制
* @param length 原数组需要复制多少个到目标数组
*/
public static native void arraycopy(Object src, int srcPos,Object dest, int destPos,int length);
在ArrayList中如下:
public void add(E element,int index){
System.arraycopy(mElementData, index, mElementData, index+1, mSize-index);
mElementData[index] = element;
mSize++;
}
public void remove(int index){
System.arraycopy(mElementData, index+1, mElementData, index, mSize-index-1);
mElementData[--mSize] = null;
}
public static void main(String[] args) {
//入队
studentList.add(new GirlStudent(18, 173), 2);
}
集合打印如下:
ArrayList扩容,插入删除元素是牺牲时间和空间换来的。虽然System.arraycopy()方法很快。
step3:ArrayList扩容
刚集合完毕,老师突然安排让隔壁班同学站在一起,这个时候就出现了一个问题,地方不够用,装不下这么多的人了。又得老师去申请一个大点的地方了:
GirlStudent[] newStudentArrays = new GirlStudent[30];
新地方找好了之后,在老师的带领下,女同学们又去新的地方一个个站好了队,再添加新的同学到队列后边:
System.arrayCopy(mElementData,0,newStudentArrays,0,newStudentArrays.length());
ArrayList每次在添加元素之前会先判断是不是需要扩容了:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
//大小为10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
//ArrayList操作记录
modCount++;
//是否需要扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
我们在创建ArrayList的时候,如果没有给一个初始大小,在添加元素的时候会为我们创建一个大小为10的数组。如果添加元素后的大小和当前数组长度一样,则需要扩容。即长度为10的数组,在添加第10个元素的时候就先进行扩容。比如上面迟到的女同学会先扩容再入队。
step4:ArrayList扩容规则
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//扩容后大小
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0){
newCapacity = minCapacity;
}
if (newCapacity - MAX_ARRAY_SIZE > 0){
newCapacity = hugeCapacity(minCapacity);
}
elementData = Arrays.copyOf(elementData, newCapacity);
}
ArrayList根据当前数组长度的一半,1.5倍来扩容。
Arrays.copyof()方法追朔下去也是通过System.arrayCopy()做到扩容的目的。System.arrayCopy()是jni方法,底层用C/C++语言实现,直接对内存复制,避免在寻址上浪费时间效率很高>foreach>for循环。在开发中如果对数据量有一个大概的估计,最好在创建ArrayList时就给一个大小,避免数组扩容带来不必要的资源浪费。
总结:
- ArrayList扩容,插入和删除元素,会牺牲时间和空间,如果可以,尽量给一个初始大小。
- JDK1.7起,空参数构造创建ArrayList。只会是一个空数组。
- ArrayList访问元素效率高。
- ArrayList按照顺序添加元素,所以是有序的。
- ArrayList可以存Null值和重复值。
如果时间可以重来,我要studentList.add(Myself(),0);
不足之处,多多包涵