自己写的LIst与ArrayList做add方法的性能对比
源码分析
大家都知道,在ArrayList的源码中一开始存储元素的数组长度是0,如果我们要添加元素,添加第一个时,ArrayList存储元素的数组长度会变为10,当我们添加元素超过10的时候,它会将数组长度扩容到15,后面每次我们每次添加元素到超过当前数组长度时,它会进行1.5倍的扩容,以下是源码:
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);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
其中第四行代码newCapacity = oldCapacity + (oldCapacity >> 1)这一句意思就是新的数组容量 = 原来的容量右移一位(也就是除以2),再加上原来的数组容量,也就是1.5倍扩容
两个add方法的对比
下面我们自己写一个List类,来模仿ArrayList
public class MyList <E>{
private int size = 0;
Object[] obj = new Object[0];
public void add(E data){
Object[] newarr = new Object[obj.length+1];
for (int i = 0; i < obj.length; i++) {
newarr[i] = obj[i];
}
newarr[obj.length] = data;
obj = newarr;
size++;
}
}
我的add方法和 ArrayList一样,先设置一个存贮元素的数组,初始长度为0,由于不清楚要输入的是什么引用类型的元素,所以设置的是Object类型数组,用泛型来适应用户任意的输入类型。不同的是,我们的add方法原理是用户每添加一个元素,就响应的将长度扩容1个单位。
下面是性能对比:
import java.util.ArrayList;
public class ListDemo {
public static void main(String[] args) {
MyList<Integer> myList = new MyList<>();
ArrayList<Integer> myArr = new ArrayList<>();
int num = 100000;
long startTime = System.currentTimeMillis();
for (int i = 0; i <num; i++) {
myList.add(i);
}
long endTime = System.currentTimeMillis();
System.out.println("myList添加元素用时:"+(endTime-startTime));
long startTime_ = System.currentTimeMillis();
for (int i = 0; i < num; i++) {
myArr.add(i);
}
long endTime_ = System.currentTimeMillis();
System.out.println("ArrayList添加元素用时:"+(endTime_-startTime_));
}
}
下面是输出结果
com.wc.ListDemo.ListDemo
myList添加元素用时:7508毫秒
ArrayList添加元素用时:3毫秒
总结
可以看出,我们写的add方法性能远不及ArrayList的add方法,这是因为我们每添加一个元素都要重新申请一个新数组,也就是一段新的内存空间,将原来数组的元素拷贝进去,再加入新添加的数据。而ArrayList扩容远没有那么频繁,并且扩招是用的位运算,大家知道位运算是最快的,这也就是ArrayList add方法耗时如此短的原因。
那么,为什么ArrayList是设置的是1.5倍扩容呢,我的理解是:
*1.首先扩容倍率如果大于2,如果用户很多时候只是在原来基础上添了很少的数据,那么过于浪费内存空间。
2.如果倍率过低,极端的情况像我上面写的一样,每添加一个的时候再扩容一次,那么就会过于频繁的申请内存,也会造成过多的时间代价,性能会比较差,就像我上面写的一样,耗时相对大很多。*