Java | ArrayList

Java8中ArrayList是基于数组保存数据,相当于一个数组队列,但该数组是动态数组,即数组大小可以被扩容。

接下来看下ArrayList类继承关系(IDEA中快捷键Ctrl + Alt + U查看),如下:

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

如上图所示:

ArrayList 继承于AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable四类接口。

其中AbstractList实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
ArrayList 实现了RandmoAccess接口,为List提供随机快速访问功能,即可以通过元素的序号快速获取元素对象;
ArrayList 实现了Cloneable接口,表示能被克隆;
ArrayList 实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输;

ArrayList总结:
ArrayList实际上是通过一个数组来保存数据。若使用默认构造函数,则ArrayList的默认容量大小是10。
ArrayList容量不足以容纳全部元素时,ArrayList会重新设置容量:新的容量="(原始容量x3)/2 + 1"。
ArrayList的克隆函数,即是将全部元素克隆到一个数组中。
ArrayList实现java.io.Serializable的方式。当写入到输出流时,先写入"容量",再依次写入"每一个元素";当读出输入流时,先读取"容量",再依次读取"每一个元素"。


接下来看下Java8 中ArrayList的具体实现:

先看下ArrayList内定义的全局变量有什么作用

// 默认初始化容量
private static final int DEFAULT_CAPACITY = 10;

// 用于空实例的共享空数组实例
private static final Object[] EMPTY_ELEMENTDATA = {};

// 用于默认大小的空实例的共享空数组实例
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

// 存储ArrayList元素的数组缓冲区。ArrayList的容量是此数组缓冲区的长度。
// 任何elementData==DEFAULTCAPACITY的空ArrayList 将在添加第一个元素时扩展到默认容量。
transient Object[] elementData; // non-private to simplify nested class access

// ArrayList包含的元素数量大小
private int size;

 // 最大数组容量大小
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

再来看下ArrayList的构造方法:

 // 构造一个空list且容量大小为指定的initialCapacity值。
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);
	}
}

 // 使用默认容量的空数组常亮完成初始化 即构造一个空list且初始化容量大小为10。
public ArrayList() {
	this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

// 构造包含指定的集合,按集合返回它们的顺序迭代器。
public ArrayList(Collection<? extends E> c) {
	elementData = c.toArray();
	if ((size = elementData.length) != 0) {
		// c.toArray might (incorrectly) not return Object[] (see 6260652)
		if (elementData.getClass() != Object[].class)
			elementData = Arrays.copyOf(elementData, size, Object[].class);
	} else {
		// replace with empty array.
		this.elementData = EMPTY_ELEMENTDATA;
	}
}

基于空构造方法即ArrayList()再来看看ArrayList内添加元素的具体过程:

 // 追加一个指定的元素到list尾部
public boolean add(E e) {
	ensureCapacityInternal(size + 1);  // Increments modCount!!
	elementData[size++] = e;
	return true;
}

 // 在list中指定位置插入指定元素,并将当前位于该位置的元素和任何后续元素右移一位。
public void add(int index, E element) {
	// 判断index索引值是否满足条件,即 0 <= index <= size
	rangeCheckForAdd(index);
	// 数组容量自增 
	ensureCapacityInternal(size + 1);  // Increments modCount!!
	// 将elementData中index后续元素往后挪一位
	System.arraycopy(elementData, index, elementData, index + 1, size - index);
	elementData[index] = element;
	size++;
}

 // 将指定的元素集合内所有元素追加到list尾部
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;
}

// 将指定的元素集合内所有元素追加到指定的index索引位置,并将该index及后续元素右移。
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)
		//将原数组中需要移动的元素往后移动numNew位
		System.arraycopy(elementData, index, elementData, index + numNew,
						 numMoved);
	// 如果不需要移动原数组元素,则直接将目标数组a中的元素复制追加到源数组尾部
	System.arraycopy(a, 0, elementData, index, numNew);
	size += numNew;
	return numNew != 0;
}

当ArrayList列表中添加元素时,需要先对当前数组容量进行扩容,然后在添加元素,回过头再来看下数组扩容的具体过程:

// 明确容量初始值并确认是否需要扩容
private void ensureCapacityInternal(int minCapacity) {
	ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

// 计算最小容量
private static int calculateCapacity(Object[] elementData, int minCapacity) {
	if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
		return Math.max(DEFAULT_CAPACITY, minCapacity);
	}
	return minCapacity;
}

// 明确容量大小
private void ensureExplicitCapacity(int minCapacity) {
	modCount++;

	// 扩容长度大于当前数组长度大小则扩容
	// overflow-conscious code
	if (minCapacity - elementData.length > 0)
		grow(minCapacity);
}

 // 增加容量以确保它至少可以容纳最小容量参数指定的元素数。
private void grow(int minCapacity) {
	// overflow-conscious code
	int oldCapacity = elementData.length;
	// 默认扩容当前数组大小的50%,即1.5倍
	int newCapacity = oldCapacity + (oldCapacity >> 1);
	// 扩容后大小与minCapacity比较,小于minCapacity则以minCapacity为准
	if (newCapacity - minCapacity < 0)
		newCapacity = minCapacity;
	// 若扩容后大小大于MAX_ARRAY_SIZE
	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);
}

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

接下来再来看下ArrayList中获取元素的过程,如下:

 // 返回列表中指定索引位置的元素
public E get(int index) {
	rangeCheck(index);

	return elementData(index);
}

 // 顺序遍历数组查找指定对象所在列表中的顺序出现的第一个索引位置
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 int lastIndexOf(Object o) {
	if (o == null) {
		for (int i = size-1; i >= 0; i--)
			if (elementData[i]==null)
				return i;
	} else {
		for (int i = size-1; i >= 0; i--)
			if (o.equals(elementData[i]))
				return i;
	}
	return -1;
}

接下来再来看看ArrayList中删除元素的过程:

 // 移除列表中指定索引位置的元素,同时被删除元素的后续元素分别需要往前移动一位
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;
}

 // 先获取该列表中指定元素的索引位置,然后在执行删除操作
public boolean remove(Object o) {
	if (o == null) {
		for (int index = 0; index < size; index++)
			if (elementData[index] == null) {
				fastRemove(index);
				return true;
			}
	} else {
		for (int index = 0; index < size; index++)
			if (o.equals(elementData[index])) {
				fastRemove(index);
				return true;
			}
	}
	return false;
}

 // 同remove()一样,但区别在于不需要验证索引是否有效
private void fastRemove(int index) {
	modCount++;
	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
}

 // 清空整个列表
public void clear() {
	modCount++;

	// clear to let GC do its work
	for (int i = 0; i < size; i++)
		elementData[i] = null;

	size = 0;
}

// 保留列表中与集合c中相同的元素
public boolean retainAll(Collection<?> c) {
	Objects.requireNonNull(c);
	return batchRemove(c, true);
}
// 批量移除操作,移除列表中与集合c中不相同的元素
private boolean batchRemove(Collection<?> c, boolean complement) {
	final Object[] elementData = this.elementData;
	int r = 0, w = 0;
	boolean modified = false;
	try {
		for (; r < size; r++)
			if (c.contains(elementData[r]) == complement)
			// 保留相同的元素	
			elementData[w++] = elementData[r]; 
	} finally {
		// Preserve behavioral compatibility with AbstractCollection,
		// even if c.contains() throws.
		// 异常兼容
		if (r != size) {
			System.arraycopy(elementData, r, elementData, w, size - r);
			w += size - r;
		}
		// 若 w==size 则表示数组将要清空
		if (w != size) {
			// clear to let GC do its work
			for (int i = w; i < size; i++)
				elementData[i] = null;
			modCount += size - w;
			size = w;
			modified = true;
		}
	}
	return modified;
}

 

最后关于arraycopy()的说明和对批量删除操作时finally模块中方法进行的测试如下:

/***
* @param      src      the source array. 源数组 
* @param      srcPos   starting position in the source array. 源数组要复制的起始位置
* @param      dest     the destination array. 目标数组
* @param      destPos  starting position in the destination data. 目标数组复制的起始位置 
* @param      length   the number of array elements to be copied. 复制的长度
*/
public static native void arraycopy(Object src,  int  srcPos, Object dest, int destPos, int length);
@Test
public void arrayCopyTest(){
	Collection c =  Arrays.asList(new Integer[]{3,6,7,9});
	Integer[] arrayInteger = new Integer[]{1,2,3,4,5,6,7,8,9,10};  // 源数组

	int r = 0,w=0, size = arrayInteger.length;

	for (; r < size; r++)
		if (c.contains(arrayInteger[r]) == true)
			arrayInteger[w++] = arrayInteger[r];

	System.out.println("size=="+size +", r=="+r+", w=="+w + ", length=="+(size-r));
	for (Integer e : arrayInteger){
		System.out.print(e+",");
	}
	System.out.println("\n");
	r = 9;
	if (r != size) {
		System.arraycopy(arrayInteger, r, arrayInteger, w, size - r);
		w += size - r;
	}
	for (Integer e : arrayInteger){
		System.out.print(e+",");
	}

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章