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

[置顶] Java容器学习--ArrayList源码分析

2018-01-12 11:00 543 查看
ArrayList 内部采用数组实现,是一种顺序存储方式,并且支持随机访问。本文分析基于 JDK 1.8.0_151 版本。

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


ArrayList
继承于
AbstractList
并且实现了
List
RandomAccess
Cloneable
Serializable
接口。

属性

// 序列化ID
private static final long serialVersionUID = 8683452581122892189L;

// 默认初始化容量(只有执行 add 操作时才执行初始化)
private static final int DEFAULT_CAPACITY = 10;

// 空数组
private static final Object[] EMPTY_ELEMENTDATA = {};

// 默认的空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

// 以上两个空数组只是为了区别于调用不同的构造方法

// 存放数据的内部数组(不参与序列化)
transient Object[] elementData;

// ArrayList 中有效数据的个数(并不一定就是elementData.length)
private int size;

// 数组最大长度
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;


方法

构造方法

// 构造具有指定初始容量的空列表
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;
}

// 构造一个包含指定 collection 的元素的列表,这些元素是按照该collection 的迭代器返回它们的顺序排列的
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// 该 bug 分析见 http://blog.csdn.net/x_iya/article/details/78313756 // c.toArray might (incorrectly) not return Object[] (see 6260652)
// c.toArray 不一定返回 Object[],也有可能是 String[](List<String> list = new ArrayList<>(Arrays.asList("111", "222"));)
if (elementData.getClass() != Object[].class)
//  转换为Object[]
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}


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

public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1);  // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1, size - index);
elementData[index] = element;
size++;
}

public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew);  // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}

public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);

Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew);  // Increments modCount

int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew, numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}


在执行
add
相关操作时,需要调用
ensureCapacityInternal
方法以确保
elementData
有足够的空间保存元素。

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


改&查

ArrayList
的修改和获取元素的方法相当简单,就是对
elementData
数组进行相应的操作罢了。

public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}

private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}


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


其他

扩容

扩容操作基本上是以数组作为其存储结构的类的核心了(String、ArrayList、StringBuilder,StringBuffer)。其他的所有操作无非就是围绕这个可变长的数组。

调用关系:

ensureCapacity() //可以由用户显式调用
ensureCapacityInternal() // 由 ArrayList 内部隐式调用
--> ensureExplicitCapacity()
--> grow()


grow 方法:

/**
* 增加容量以确保它至少可以容纳最小容量参数 minCapacity 指定的元素数量
* minCapacity: 数组需要的最小容量值
* newCapacity: 就是扩容后实际的容量值
* 每次自动扩容都是扩大原有容量 1.5 倍
* 当自动扩容后的容量值仍然小于所需要的容量时,就直接扩容到所需容量值
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 可以看到首先扩展为原来的 1.5 倍
if (newCapacity - minCapacity < 0) // 防止溢出
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity); // 设置大小为 Integer.MAX_VALUE
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}

private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}


ensureExplicitCapacity 方法:

/**
* 保证扩容值大于原数组长度才执行 grow() 方法
*/
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}


ensureCapacityInternal 方法:

private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}

private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}


ensureCapacityInternal()方法首先判断该数组此时是不是默认初始数组,如果是的话就判断一下minCapacity是否大于10,如果不是就扩容到初始默认数组长度10,如果是就直接扩容到minCapacity值的长度。

用户所能使用的只有一个公有方法,也就是说可以由用户显式执行扩容操作:

/**
* 如有必要,增加此 ArrayList 实例的容量,以确保它至少能够容纳最小容量参数 minCapacity 所指定的元素个数
*/
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) ? 0 : DEFAULT_CAPACITY;

if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}


ensureCapacityInternal
隐式扩容方法在容器添加元素的时候被调用,也就是“懒扩容–>只有在真正添加元素的时候进行扩容操作”。

使用 ArrayList 需要注意的地方:

1.ArrayList 是基于数组的方式实现的,可以通过下标索引直接查找到指定位置的元素,因此查找效率高,但每次插入或删除元素,就要大量地移动元素,插入删除元素的效率低。

2.ArrayList在插入元素时,可能会进行数组的扩容,但是在删除元素时却不会减小数组的容量,如果希望减小数组的容量,可使用 trimToSize 方法,在查找元素要遍历数组时,对非null元素使用equals方法,对null元素使用==。

3.扩充容量的方法 ensureCapacityInternal。ArrayList在每次增加元素(可能是1个,也可能是一组)时,都要调用该方法来确保足够的容量。当 容量不足以容纳当前的元素个数时,就设置新的容量为旧的容量的1.5倍,如果设置后的新容量还不够,则直接把新容量设置为传入的参数(也就是所需的容 量),而后用Arrays.copyof()方法将元素拷贝到新的数组。从中可以看出,当容量不够时,每次增加元素,都要将原来的元素拷贝到一个新的数组中,非常之耗时,也因此建议在事先能确定元素数量的情况下,才使用ArrayList,否则建议使用LinkedList

4.ArrayList不是线程安全的。

参考:

https://zhuanlan.zhihu.com/p/27873515

https://zhuanlan.zhihu.com/p/28545284

https://mjd507.github.io/2017/04/09/Data-Structure-ArrayList-SourceCode/

http://www.cnblogs.com/skywang12345/p/3308556.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: