您的位置:首页 > 编程语言 > Java开发

ArrayList源码分析(基于Java1.8)

2017-10-31 15:00 615 查看
ArrayList 基于数组实现,也就是类对变量 Object[]系列操作,封装为常用的add,remove,indexOf, contains本质是通过 size 计数器对数组进行系列的操作实现

add 方法和 toArray 也是借助 Arrays 工具类完成

还有很有意思的地方就是add的扩容方法 ensureCapacityInternal() 就会增加50%容量,

因为底层数组是不可变的,所以add实际上是通过Arrays工具再拷贝扩容后长度的数组,然后覆盖当前数组

预设集合长度是最优的操作,今天看源码才明白原理,ArrayList默认构造器为内部数组定义的长度为10,当list超出指定集合的时候,ArrayList会操作自动扩容,

扩容的方法是使用Arrays.copyOf方法重新构造新的数组,然后将旧数组的数据全部拷贝到新的数组里面,重新生成,再拷贝数组,消耗是巨大的,这也导致了 ArrayList 天生的缺点,插入删除元素慢

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {

private static final long serialVersionUID = 8683452581122892189L;

private static final int DEFAULT_CAPACITY = 10;

private static final Object[] EMPTY_ELEMENTDATA = {};

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

transient Object[] elementData; // non-private to simplify nested class access

private int size;


源码中可以看到,ArrayList继承抽象类AbstractList,实现List接口,RandomAccess, Cloneable, Serializable等接口主要用于标识,并没有很大作用

DEFAULT_CAPACITY = 10 是集合的默认初始化长度,我们常用new ArrayList<>()构造,其实就是创建一个长度为10的Object[]数组

size 为计数器,记录当前数组的长度

public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
}
}

public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}


构造器的代码中,看下最常用的两个构造器,带参数initialCapacity的构造器,直接就让变量创建对应长度的数组,如果initialCapacity == 0 就直接赋值为空数组 Object[] elementData = {};

下面无参构造器,就直接初始化容量为10的空列表

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);
}


上面的自动扩展数组容量的方法,每次添加元素都会调用该方法,确保集合的长度足够用,上面代码中最有趣的是2行

newCapacity = oldCapacity + (oldCapacity >> 1), 既每次扩大容量为当前容量的50%,这里的扩容是固定的,和HashMap有负载因子不同

然后调用 Arrays.copyOf() 将旧数组内容和新长度作为参数,创建新的Object[]数组,并且将原数组的元素,拷贝到新的数组里面

因为数组长度是不可变的,所以这里的做法是,创建新的数组,再拷贝元素,这里也是ArrayList性能最低的地方

public int size() {
return size;
}

public boolean isEmpty() {
return size == 0;
}

public boolean contains(Object o) {
return indexOf(o) >= 0;
}

public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}

public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}

E elementData(int index) {
return (E) elementData[index];
}


常用的size,isEmpty,contains,indexOf 也只是对Object[] 进行系列的循环判断操作

public E get(int index) {
rangeCheck(index);
return elementData(index);
}

public boolean add(E e) {
ensureCapacityInternal(size + 1);  // Increments modCount!!
elementData[size++] = e;
return true;
}

public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}


get方法,也是指通过下标,从Object[]数组中获取对象,然后转型 Object -> E

add末尾添加元素方法步骤,先判断容量是否够用,然后在数组末尾elementData[size++] == e,加入新的元素,size为数组长度

remove 方法,也是通过循环equals匹配,找到下标,然后通过下标删除,—size,减去计数器长度,并没有实际减少数组长度

clear方法也相同,把数组的值都设为null,然后 size = 0;

 

总结:

通过以上对ArrayList关键部分源码分析,通晓ArrayList原理,我们可以得出结论:
ArrayList 底层来自数组,通过下标随机访问元素速度非常快,但是插入和删除要频繁的移动数组元素,改变数组长度,效率比较低
ArrayList 主要性能问题在于当数组元素达到容量上限,扩容的成本很大,所以能够预先知道元素,然后使用 new ArrayList(int initialCapacity) 手动设置容量是最好的
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: