Java - 提高-源码(2) - ArrayList

本文详细解析了ArrayList的工作原理,包括其实现机制、内部结构、主要方法及其性能特点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ArrayList


源码对应JDK1.7

JDK1.7源码下载地址:JDK1.7源码

JDK 源码注释

Resizable-array implementation of the List interface. Implements all optional list operations, and permits all elements, including null. In addition to implementing the List interface, this class provides methods to manipulate the size of the array that is used internally to store the list. (This class is roughly equivalent to Vector, except that it is unsynchronized.)

 

The size, isEmpty, get, set, iterator, and listIterator operations run in constant time. The add operation runs in amortized constant time, that is, adding n elements requires O(n) time. All of the other operations run in linear time (roughly speaking). The constant factor is low compared to that for the LinkedList implementation.

 

Each ArrayList instance has a capacity. The capacity is the size of the array used to store the elements in the list. It is always at least as large as the list size. As elements are added to an ArrayList, its capacity grows automatically. The details of the growth policy are not specified beyond the fact that adding an element has constant amortized time cost.

 

An application can increase the capacity of an ArrayList instance before adding a large number of elements using the ensureCapacity operation. This may reduce the amount of incremental reallocation.

 

Note that this implementation is not synchronized. If multiple threads access an ArrayList instance concurrently, and at least one of the threads modifies the list structurally, it must be synchronized externally. (A structural modification is any operation that adds or deletes one or more elements, or explicitly resizes the backing array; merely setting the value of an element is not a structural modification.) This is typically accomplished by synchronizing on some object that naturally encapsulates the list. If no such object exists, the list should be "wrapped" using the Collections.synchronizedList method. This is best done at creation time, to prevent accidental unsynchronized access to the list:

 

   List list = Collections.synchronizedList(new ArrayList(...));

 

The iterators returned by this class's iterator and listIterator methods are fail-fast: if the list is structurally modified at any time after the iterator is created, in any way except through the iterator's own remove or add methods, the iterator will throw a ConcurrentModificationException. Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future.

 

Note that the fail-fast behavior of an iterator cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast iterators throw ConcurrentModificationException on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: the fail-fast behavior of iterators should be used only to detect bugs.

 

This class is a member of the Java Collections Framework.

 

 

List接口的可调整大小数组实现。实现所有可选的列表操作,并允许所有元素,包括null。除了实现List接口之外,该类还提供了一些方法来控制用于内部存储列表的数组大小。 (这个类大致相当于Vector,除了它是不同步的。)

 

size,isEmpty,get,set,iterator和listIterator操作在恒定时间内运行。 add操作以分摊的恒定时间运行,即添加n个元素需要O(n)个时间。所有其他操作都在线性时间内运行(粗略地说)。与LinkedList实现相比,常数因子较低。

 

每个ArrayList实例都有一个容量。容量是用于存储列表中元素的数组的大小。它总是至少与列表大小一样大。随着元素被添加到ArrayList,其容量会自动增长。增长政策的细节并未超出添加元素具有不变的摊销时间成本的事实。

 

在使用ensureCapacity操作添加大量元素之前,应用程序可以增加ArrayList实例的容量。这可能会减少增量重新分配的数量。

 

请注意,此实现不同步。如果多个线程同时访问ArrayList实例,并且至少有一个线程在结构上修改了列表,则它必须在外部同步。 (结构修改是任何添加或删除一个或多个元素的操作,或明确调整后备数组的大小;仅设置元素的值不是结构修改。)这通常通过同步某些自然封装名单。如果不存在这样的对象,则应使用Collections.synchronizedList方法“列出”列表。这最好在创建时完成,以防止意外的不同步访问列表:

 

   List list = Collections.synchronizedList(new ArrayList(...));

 

这个类的迭代器和listIterator方法返回的迭代器是快速失败的:如果在迭代器创建后的任何时候,结构上都会修改列表,除了通过迭代器自己的remove或add方法以外,迭代器将抛出ConcurrentModificationException。因此,面对并发修改,迭代器快速而干净地失败,而不是在将来未定的时间冒着任意的,非确定性的行为冒险。

 

请注意,迭代器的故障快速行为无法得到保证,因为一般来说,在存在非同步并发修改的情况下不可能做出任何硬性保证。失败快速迭代器尽最大努力抛出ConcurrentModificationException。因此,编写一个依赖于此异常的程序是正确的:迭代器的快速失败行为应仅用于检测错误。

 

该类是Java集合框架的成员。

 

ArrayList是实现List接口的动态数组,所谓动态就是它的大小是可变的。

实现了所有可选列表操作,并允许包括null在内的所有元素。

出了实现List接口外,此类还提供了一些方法用来操作内部用来存储列表的数组大小。

 

每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组大小。默认初始容量为10

随着ArrayList中元素的增加,它的容量也会不断的自动增长。

在每次添加新的元素时,ArrayList都会检查是否需要进行扩容操作,扩容操作带来数据向新数组的重新拷贝,

所以如果我们知道具体业务数据量,在构造ArrayList时可以给ArrayList指定一个初始容量,这样就会减少扩容时数据的拷贝问题。

 

当然在添加大量元素前,应用程序也可以使用ensureCapacity操作来增加ArrayList实例的容量,这可以减少递增式再分配的数量。

 

注意,ArrayList实现不是同步的。如果多个线程同时访问一个ArrayList实例,而其中至少一个线程从结构上修改了列表,那么它必须保持外部同步。

所以为了保证同步,最好的办法是在创建时完成,以防止意外对列表进行不同步的访问:

List list =Collections.synchronizedList(new ArrayList(...));


首先记住结论:

a. ArrayList 是一个动态数组列表

b. ArrayList 允许存储数据为空(null)

c. ArrayList 是非线程安全的,在多线程中使用:Collections.synchronizedList获取线程安全的对象。

d. ArrayList 是有序的(读取数据的顺序与存放数据的顺序一致) 

e. ArrayList 允许存储数据重复

f. ArrayList 默认长度10


ArrayList底层数组

/**
 * The array buffer into which the elements of the ArrayList are stored. 
 * The capacity of the ArrayList is the length of this array buffer.<br>
 * 阵列缓冲区,其中存储ArrayList的元素。 ArrayList的容量是此数组缓冲区的长度。
 * 
 */
private transient Object[] elementData;

/**
 * The size of the ArrayList (the number of elements it contains).<br>
 * ArrayList的大小(包含的元素数)。
 * 
 * @serial
 */
private int size;

elementData 就是ArrayList的容器


transient是Java关键字,为变量修饰符;

如果用transient声明一个实例变量,当对象存储时,它的值不需要维持,什么意思呢?

Java的serialization提供了一种持久化对象实例的机制。

当持久化对象时,可能有一个特殊的对象数据成员,我们不想用serialization机制来保存它。

transient型变量的值不包括在序列化的表示中,然而非transient型的变量是被包括进去的。

简单地说,增加transient 修饰符,就是取消该对象序列化。


为什么要取消序列化呢?

在序列化ArrayList的时候,ArrayList中的elementData未必是满的,比如说elementData长度为10,但是存放的元素只有3个,

那么是否有必要序列化整个elementData呢?显然是没有必要的,因此ArrayList中重写了writeObject()方法

/**
* Save the state of the <tt>ArrayList</tt> instance to a stream (that is,
* serialize it).<br>
* 将 ArrayList 实例的状态保存到流中(即序列化它)。
* 
*
* @serialData The length of the array backing the <tt>ArrayList</tt>
*             instance is emitted (int), followed by all of its elements
*             (each an <tt>Object</tt>) in the proper order.
*/
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException {
	// Write out element count, and any hidden stuff
	int expectedModCount = modCount;
	s.defaultWriteObject();

	// Write out array length
	s.writeInt(elementData.length);

	// Write out all elements in the proper order.
	for (int i = 0; i < size; i++)
			s.writeObject(elementData[i]);

	if (modCount != expectedModCount) {
			throw new ConcurrentModificationException();
	}

}

每次序列化时调用writeObject()方法,先调用defaultWriteObject()方法,序列化ArrayList中非transient的元素(也就是需要序列化)。

elementData不去序列化,接着遍历elementData[],只序列化数组中有的元素,这样的好处:

a. 加快了序列化的速度

b. 减小了序列化之后文件的大小


ArrayList构造函数

public ArrayList(int initialCapacity):
	构造具有指定初始容量的空列表。
	
public ArrayList():
	构造一个初始容量为10的空列表。
	
public ArrayList(Collection<? extends E> c):
	构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。

new ArrayList();之后可以看出,底层创建了一个长度为10的数组。elementData数组就是存放的容器。

/**

 * Constructs an empty list with an initial capacity of ten.

 */

public ArrayList() {

this(10);

}

 

/**

 * Constructs an empty list with the specified initial capacity.

 *

 * @param initialCapacity

 *            the initial capacity of the list

 * @throws IllegalArgumentException

 *             if the specified initial capacity is negative

 */

public ArrayList(int initialCapacity) {

super();

if (initialCapacity < 0)

throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);

this.elementData = new Object[initialCapacity];

}

 

/**

 * Constructs a list containing the elements of the specified collection, in

 * the order they are returned by the collection's iterator.

 *

 * @param c

 *            the collection whose elements are to be placed into this list

 * @throws NullPointerException

 *             if the specified collection is null

 */

public ArrayList(Collection<? extends E> c) {

elementData = c.toArray();

size = elementData.length;

// c.toArray might (incorrectly) not return Object[] (see 6260652)

if (elementData.getClass() != Object[].class)

elementData = Arrays.copyOf(elementData, size, Object[].class);

}

 


ArrayList成员变量

我们先看下,ArrayList中几个重要的成员变量

/**

 * Default initial capacity.<br>

 * 默认初始容量。

 */

private static final int DEFAULT_CAPACITY = 10;

 

/**

 * Shared empty array instance used for empty instances. <br>

 * 用于空实例的共享空数组实例。

 */

private static final Object[] EMPTY_ELEMENTDATA = {};

 

/**

 * The array buffer into which the elements of the ArrayList are stored. The

 * capacity of the ArrayList is the length of this array buffer. Any empty

 * ArrayList with elementData == EMPTY_ELEMENTDATA will be expanded to

 * DEFAULT_CAPACITY when the first element is added. <br>

 * ArrayList的元素存储在其中的数组缓冲区。<br>

 * ArrayList的容量是这个数组缓冲区的长度。<br>

 * 当添加第一个元素时,任何具有elementData ==

 * EMPTY_ELEMENTDATA的空ArrayList将展开为DEFAULT_CAPACITY。<br>

 *

 */

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

// access

 

/**

 * The size of the ArrayList (the number of elements it contains).<br>

 * ArrayList的大小(它包含的元素的数量)。

 *

 * @serial

 */

private int size;



ArrayList常用方法

在看下面的方法之前,我们先来看一个方法,System类中的 arraycopy()方法:
public static void arraycopy(Object src,int srcPos,Object dest,int destPos,int length)

作用:
	从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。
	从 src 引用的源数组到 dest 引用的目标数组,数组组件的一个子序列被复制下来。

参数说明:
	src - 源数组。
	srcPos - 源数组中的起始位置。
	dest - 目标数组。
	destPos - 目标数据中的起始位置。
	length - 要复制的数组元素的数量。 
来个Demo跑一下...
@Test
public void test() {

	// 创建一个长度为10的数组,并塞入对应值.
	String[] strArr = new String[10];
	for (int i = 0; i < strArr.length; i++) {
		strArr[i] = String.valueOf(i);
	}
	System.out.println(Arrays.toString(strArr));

	int size = strArr.length;

	// 模拟将index为2的数据删除
	int index = 2;

	// 向左移动的位数
	int numMoved = size - index - 1;

	/**
	 * 
	 * 从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束<br>
	 * 
	 * strArr:源数组<br>
	 * index+1:源数组中的起始位置---3<br>
	 * strArr:目标数组<br>
	 * index:目标数据中的起始位置---2<br>
	 * numMoved:要复制的数组元素的数量---7<br>
	 */
	System.arraycopy(strArr, index + 1, strArr, index, numMoved);

	System.out.println(Arrays.toString(strArr));

	strArr[--size] = null;

	System.out.println(Arrays.toString(strArr));
}

输出:
	[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
	[0, 1, 3, 4, 5, 6, 7, 8, 9, 9]
	[0, 1, 3, 4, 5, 6, 7, 8, 9, null]

add(E e)

源码:

/**
* Appends the specified element to the end of this list.<br>
* 将指定的元素追加到此列表的末尾。
*
* @param e
*            element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
	// 内部容量检查(判断是否需要扩容)
	ensureCapacityInternal(size + 1); // Increments modCount!!

	// 将数据,放在数组最后一位
	elementData[size++] = e;

	return true;
}

我们顺便深入看下ArrayList是怎么扩容的..

顺着ensureCapacityInternal()方法往下看

// 内部容量校验
private void ensureCapacityInternal(int minCapacity) {
	// list被修改次数
	modCount++;

	// overflow-conscious code
	// minCapacity为当前list中保存元素的数量+1
	// (当前容器保存数+1) - 容器(数组总长) 大于 0
	if (minCapacity - elementData.length > 0)
		// 扩容
		grow(minCapacity);
}

看下grow()方法

/**
* Increases the capacity to ensure that it can hold at least the number of
* elements specified by the minimum capacity argument.<br>
* 增加容量,以确保它至少容纳最小容量参数指定的元素数量。
* 
* @param minCapacity
*            the desired minimum capacity所需的最小容量
*/
private void grow(int minCapacity) {
	// overflow-conscious code
	// 旧数组的长度
	int oldCapacity = elementData.length;

	// 位移计算新新数组的长度
	int newCapacity = oldCapacity + (oldCapacity >> 1);

	// (新数组长度 - 所需最小长度) 小于 0
	if (newCapacity - minCapacity < 0)
			// 将最小长度赋值给新数组长度,说明最小长度够用
			newCapacity = minCapacity;

	// (新数组长度 - 数组最大长度) 大于 0,说明,新数组长度超出最大容量
	if (newCapacity - MAX_ARRAY_SIZE > 0)
			// 将所需最小长度数,传入进行计算
			newCapacity = hugeCapacity(minCapacity);

	// minCapacity is usually close to size, so this is a win:
	// Arrays.copyOf()方法,传入需要拷贝的数组和数组长度,返回新数组,完成数组的扩容
	elementData = Arrays.copyOf(elementData, newCapacity);
}

private static int hugeCapacity(int minCapacity) {
	// 最小长度数校验
	if (minCapacity < 0) // overflow
			throw new OutOfMemoryError();
	// 最小长度数 大于 最大长度限制,true:返回Integer最大数,false:返回数组最大限制数
	return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}
看到了嘛,底层扩容通过 Arrays.copyOf()方法

add(int index, E element)
源码:
/**
* Inserts the specified element at the specified position in this list.
* Shifts the element currently at that position (if any) and any subsequent
* elements to the right (adds one to their indices).
*
* 在该列表中的指定位置插入指定的元素。将当前位于该位置的元素(如果有)<br>
* 和任何后续元素(向其索引添加一个)移动。
*
* @param index
*            index at which the specified element is to be inserted<br>
*            指定元素要插入的索引
* @param element
*            element to be inserted<br>
*            要插入的元素
* 
* @throws IndexOutOfBoundsException
*             {@inheritDoc}
*/
public void add(int index, E element) {

	// index参数校验
	rangeCheckForAdd(index);

	// 内部容量检查(判断是否需要扩容)
	ensureCapacityInternal(size + 1); // Increments modCount!!

	// 使用arraycopy()方法,进行数组后移。
	// 目的是空出index的位置,插入element,并将index后的元素后移一个位置。
	System.arraycopy(elementData, index, elementData, index + 1, size - index);

	// 将元素插入index处
	elementData[index] = element;

	// ArrayList容量+1
	size++;
}

注释很详细了


addAll(Collection<? extends E> c)
源码:

/**
* Appends all of the elements in the specified collection to the end of
* this list, in the order that they are returned by the specified
* collection's Iterator. The behavior of this operation is undefined if the
* specified collection is modified while the operation is in progress.
* (This implies that the behavior of this call is undefined if the
* specified collection is this list, and this list is nonempty.)<br>
* 将指定集合中的所有元素追加到此列表的末尾,<br>
* 按照它们由指定集合的Iterator返回的顺序。<br>
* 如果在操作进行中修改了指定的集合,则此操作的行为是未定义的。<br>
* (这意味着如果指定的集合是此列表,并且此列表是非空的,则此调用的行为是未定义的。)<br>
*
* @param c
*            collection containing elements to be added to this list
* @return <tt>true</tt> if this list changed as a result of the call
* @throws NullPointerException
*             if the specified collection is null
*/
public boolean addAll(Collection<? extends E> c) {
	// 将集合转换为数组
	Object[] a = c.toArray();

	// 获取集合数组的长度
	int numNew = a.length;

	// ArrayList内部容量校验
	ensureCapacityInternal(size + numNew); // Increments modCount

	// 通过arraycopy()方法,将集合数组,复制到elementData中。
	System.arraycopy(a, 0, elementData, size, numNew);

	// 更新当前容量大小
	size += numNew;

	return numNew != 0;
}
注释写的都挺清楚的了..


get(int index)
源码:

@SuppressWarnings("unchecked")
E elementData(int index) {
	return (E) elementData[index];
}

/**
* Returns the element at the specified position in this list.<br>
* 返回此列表中指定位置的元素。
* 
* @param index
*            index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException
*             {@inheritDoc}
*/
public E get(int index) {

	// 检查索引是否在集合容器的范围内
	rangeCheck(index);

	//从数组中取出下标对应的数据
	return elementData(index);
}
这应该很好理解吧...


remove(Object o)
源码:

/**
* Removes the first occurrence of the specified element from this list, if
* it is present. If the list does not contain the element, it is unchanged.
* More formally, removes the element with the lowest index <tt>i</tt> such
* that
* <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>
* (if such an element exists). Returns <tt>true</tt> if this list contained
* the specified element (or equivalently, if this list changed as a result
* of the call).<br>
*
* 从列表中删除指定元素的第一个出现(如果存在)。<br>
* 如果列表中不包含该元素,它将保持不变。<br>
*
* @param o
*            element to be removed from this list, if present
* @return <tt>true</tt> if this list contained the specified element
*/
public boolean remove(Object o) {

	if (o == null) {

	// 删除对象为null
	for (int index = 0; index < size; index++)
			// 循环取出集合中的数据,与null进行比较
			if (elementData[index] == null) {
					// 如果存在,删除对应下标的数据
					fastRemove(index);
					return true;
			}
	} else {

	// 删除对象不为null
	for (int index = 0; index < size; index++)
			// 循环取出集合中的数据与o进行比较
			if (o.equals(elementData[index])) {
					// 命中的话,删除对应下标的元素
					fastRemove(index);
					return true;
			}
	}
	return false;
}

/**
* Private remove method that skips bounds checking and does not return the
* value removed.<br>
* 专用remove方法跳过边界检查,并且不返回删除的值。
*/
private void fastRemove(int index) {
	modCount++;

	// 当前集合中存在元素的数量 - 下标位置数 - 1
	int numMoved = size - index - 1;
	if (numMoved > 0)
			// 通过arraycopy()方法,对数组进行复制,将index后的元素向前移动一个位置
			System.arraycopy(elementData, index + 1, elementData, index, numMoved);

	// 置空最后一个元素
	elementData[--size] = null; // Let gc do its work
}

注意:

通过上面几个通用的方法,我们可以看出,不论是add还是remove,ArrayList底层都是对数组进行操作。

要知道,操作数组进行扩容、复制十分浪费性能和效率。

所以,在开发中,要是能预先估算出ArrayList集合的大小,就通过ArrayList(int initialCapacity)设置初始容量。


举个栗子:

我们通过默认的ArrayList()构造方法创建对象,默认长度为10。

程序运行时,不断的add,这样子,数组的容量会一直自动扩容,一直操作数组拷贝,对性能和效率,是很大的浪费


ArrayList的优缺点

优点:

a. ArrayList 底层是数组实现,是一种随机访问模式,查询起来很快

b. ArrayList 顺序添加一个元素很方便

缺点:

a. 删除元素,会涉及到底层数组的复制,如果元素很多,会十分损耗性能

b. 插入指定位置元素,也会涉及到底层数组的复制,会十分损耗性能。

so,ArrayList比较适合顺序添加,随机访问的场景


ArrayList toArray()方法异常

我们先来看个Demo

public static void main(String[] args) {

	List<Integer> list = new ArrayList<Integer>();

	list.add(1);
	list.add(2);
	list.add(3);

	Integer[] integerArr = (Integer[]) list.toArray();

	System.out.println(Arrays.toString(integerArr));
}

输出结果:
Exception in thread "main" java.lang.ClassCastException: 
	[Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
	at com.demo.list.list_1.TestList.main(TestList.java:17)

上面这段代码,看上去似乎没什么毛病,将ArrayList集合转换为数组并强转为Integer数组,然后输出数组

博主原文的意思是:出现错误是因为Java不支持向下转型。但是Java是支持向下转型的。感觉原博理解的有偏差

仔细思考一下,普通ArrayList的toArray()方法,返回的是Object[]数组类型,然后我们强转为Integer类型肯定会报错。

就是类型转换错误。

http://blog.youkuaiyun.com/lemon89/article/details/44727013

大致意思:list.toArray()返回的是Object数组,JVM不知如何盲目的?将object数组转换为Integer数组


那该如何解决呢?我们来看下面这个Demo,只是修改了两行代码:

public class TestList {

	public static void main(String[] args) {

	List<Integer> list = new ArrayList<Integer>();

	list.add(1);
	list.add(2);
	list.add(3);

	Integer[] integerA = new Integer[0]; 
	
	Integer[] integerArr = (Integer[]) list.toArray(integerA);

	System.out.println(Arrays.toString(integerArr));
	}

}
输出结果:
	[1, 2, 3]

大部分代码和上面是一样的,在调用toArray()方法的时候,我new了一个空的Integer[]数组,并传入了toArray()方法。

带参数的toArray()方法完整是这样的:

public <T> T[] toArray(T[] a) {
	if (a.length < size)
			// Make a new array of a's runtime type, but my contents:
			return (T[]) Arrays.copyOf(elementData, size, a.getClass());
	System.arraycopy(elementData, 0, a, 0, size);
	if (a.length > size)
			a[size] = null;
	return a;
}

可以看出,传参和返回参数,都是自定义泛型。

它会根据你传入的数组类型,进行转换,然后将转换完的数组返回给你。


参考资料:

http://www.cnblogs.com/skywang12345/p/3308556.html

http://blog.youkuaiyun.com/lemon89/article/details/44727013
http://www.cnblogs.com/xrq730/p/4989451.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值