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

ArrayList工作原理

2016-08-26 00:00 239 查看
摘要: List集合中我们最常用到的是ArrayList和LinkedList。前天有朋友问我,ArrayList是怎么工作的?一时没答上来,很尴尬。特地研究了一下Collection接口架构,这里先说一下ArrayList的工作机制

1.前言

List接口中,重要的两个实现是ArrayList和LinkedList,其中ArrayList又比LinkedList常用。这是因为ArrayList的读取性能远远高于LinkedList。本篇博文将介绍ArrayList,稍后介绍LinkedList及其两者的区别。

2.ArrayList简介

2.1 ArrayList是什么

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.

ArrayList是实现了List接口的、大小可变的数组队列。能够实现所有List接口的可选操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来存储列表的数组的大小。(此类大致上等同于 Vector 类,除了此类是不同步的。)--JDK API翻译(可能不是很精确,请谅解)

从API的定义中可以看出,ArrayList是一个数组队列,相当于动态数组。与数组相比,能够动态增长。

2.2 ArrayList的数据结构



java.util
类 ArrayList<E>
java.lang.Object
- java.util.AbstractCollection<E>
- java.util.AbstractList<E>
- java.util.ArrayList<E>
所有已实现的接口:
Serializable, Cloneable, Iterable<E>, Collection<E>, List<E>, RandomAccess

ArrayList继承了AbstractList,实现了List,它是一个数组队列,提供了相关的增、改、删

查等功能。

ArrayList实现了RandomAccess接口,提供了随机访问功能。RandomAccess是List 实现所使用的标记接口,用来表明其支持快速(通常是固定时间)随机访问。此接口的主要目的是允许一般的算法更改其行为,从而在将其应用到随机或连续访问列表时能提供良好的性能。(来自JDk API翻译)

ArrayList实现了Cloneable接口,提供了克隆功能

ArrayList实现了java.io.Serializable借口,这意味着ArrayList支持序列化。

注意:ArrayList是非线程安全的!所以,建议在多线程程序中避免使用ArrayList,可以使用Vector或者CopyOnWriteArrayList。(这两个类的用法及其实现,在稍后的博文中将被介绍)

3.源码解析ArrayList

3.1 ArrayList的重要属性

package java.util;

public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
//序列版本号
private static final long serialVersionUID = 8683452581122892189L;

/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer.
* 数组缓冲区中的数组的元素存储,即保存ArrayList中数据的数组
*/
private transient Object[] elementData;

/**
* The size of the ArrayList (the number of elements it contains).
* ArrayList中实际数据的数量
* @serial
*/
private int size;
}

从源码中看到ArrayList有2个重要对象

elementData是“Object[]类型的数组”,它保存了添加到ArrayList中的元素。实际上,elementData是个动态数组,我们通过带初始容量的构造器来执行构建数组。如果不含初始化容量的构造器,elementData的容量是10.elementData数组的大小会根据ArrayList容量的增长而动态增长。但是建议在添加大量元素前,应用程序可以使用 ensureCapacity 操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。构造器的内容,详见3.2 ArrayList的构造函数。具体的增长方式,请参照3.3 ArrayList的重要方法中的ensureCapacity()函数解析。

size 是动态数组的实际大小

3.2 ArrayList的构造函数

public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
/**
* Constructs an empty list with the specified initial capacity.
*
* 构造一个具有指定初始容量的空列表。
*/
public ArrayList(int initialCapacity) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
//新建一个数组,大小为入参值
this.elementData = new Object[initialCapacity];
}

/**
* Constructs an empty list with an initial capacity of ten.
* ArrayList的无参构造器, 构造一个初始容量为 10 的空列表
*/
public ArrayList() {
this(10);
}

/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* 构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。     */
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)
//Arrays.copyOf方法:数组复制
elementData = Arrays.copyOf(elementData, size, Object[].class);
}
}


3.3 ArrayList的重要方法

3.3.1 增加ArrayList容量的方法ensureCapacity()

当ArrayList容量不足以容纳全部元素时,ArrayList会重新设置容量:新的容量 = (原始容量 X 3) / 2 + 1。再次说明在添加大量元素前,应用程序应该使用 ensureCapacity 操作来增加 ArrayList 实例的容量。以减少递增式再分配的数量。

/**
* Increases the capacity of this <tt>ArrayList</tt> instance, if
* necessary, to ensure that it can hold at least the number of elements
* specified by the minimum capacity argument.
* 增加此 ArrayList 实例的容量,以确保它至少能够容纳最小容量参数所指定的元素数。
*/
public void ensureCapacity(int minCapacity) {
//将修改统计计数 +1
modCount++;
int oldCapacity = elementData.length;
//如果传入最小容量大于原来数组的长度,就增加arrayList容量
//新的容量 = (原始容量 * 3)/2 + 1
if (minCapacity > oldCapacity) {
Object oldData[] = elementData;
int newCapacity = (oldCapacity * 3)/2 + 1;
if (newCapacity < minCapacity)
newCapacity = minCapacity;
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
}


3.3.2 最小化 ArrayList 实例的存储量方法trimToSize()

将此 ArrayList 实例的容量调整为列表的当前大小。应用程序可以使用此操作来最小化 ArrayList 实例的存储量。

/**
* Trims the capacity of this ArrayList instance to be the list's current size.
* 将此 ArrayList 实例的容量调整为列表的当前大小,也即将当前容量值设置为实际元素个数
*/
public void trimToSize() {
modCount++;
int oldCapacity = elementData.length;
if (size < oldCapacity) {
elementData = Arrays.copyOf(elementData, size);
}
}


3.3.3 添加元素add()

将指定的元素添加到此列表的尾部。在调整数组大小之前要判断是否需要调整arrayList容量

/**
* Appends the specified element to the end of this list.
*
* 添加元素e
*/
public boolean add(E e) {
//确定ArrayList容量大小
ensureCapacity(size + 1);  // Increments modCount!!
//添加e到ArrayList尾部
elementData[size++] = e;
return true;
}


3.3.4 检查是否含有指定元素的方法contains(object o)

/**
* Returns true if this list contains the specified element.
* 返回ArrayList是否包含Object(o)
*/
public boolean contains(Object o) {
return indexOf(o) >= 0;
}

/**
* Returns the index of the first occurrence of the specified element
* in this list, or -1 if this list does not contain the element.
* 正向查找,返回元素的索引值,如果元素不存在集合中,就返回-1
*/
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;
}


3.3.5 其余方法详见源码(带翻译)

package java.util;

public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
// 正向查找,返回元素的索引值
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;
}

// 反向查找(从数组末尾向开始查找),返回元素(o)的索引值
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的Object数组
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}

// 返回ArrayList的模板数组。所谓模板数组,即可以将T设为任意的数据类型
public <T> T[] toArray(T[] a) {
// 若数组a的大小 < ArrayList的元素个数;
// 则新建一个T[]数组,数组大小是“ArrayList的元素个数”,并将“ArrayList”全部拷贝到新数组中
if (a.length < size)
return (T[]) Arrays.copyOf(elementData, size, a.getClass());

// 若数组a的大小 >= ArrayList的元素个数;
// 则将ArrayList的全部元素都拷贝到数组a中。
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}

// 设置index位置的值为element
public E set(int index, E element) {
RangeCheck(index);

E oldValue = (E) elementData[index];
elementData[index] = element;
return oldValue;
}

// 将e添加到ArrayList的指定位置
public void add(int index, E element) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(
"Index: "+index+", Size: "+size);

ensureCapacity(size+1);  // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}

// 删除ArrayList指定位置的元素
public E remove(int index) {
RangeCheck(index);

modCount++;
E oldValue = (E) elementData[index];

int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // Let gc do its work

return oldValue;
}

// 删除ArrayList的指定元素
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;
}

// 快速删除第index个元素
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
// 从"index+1"开始,用后面的元素替换前面的元素。
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// 将最后一个元素设为null
elementData[--size] = null; // Let gc do its work
}

// 删除元素
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
// 便利ArrayList,找到“元素o”,则删除,并返回true。
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}

// 清空ArrayList,将全部的元素设为null
public void clear() {
modCount++;

for (int i = 0; i < size; i++)
elementData[i] = null;

size = 0;
}

// 将集合c追加到ArrayList中
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacity(size + numNew);  // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}

// 从index位置开始,将集合c添加到ArrayList
public boolean addAll(int index, Collection<? extends E> c) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(
"Index: " + index + ", Size: " + size);

Object[] a = c.toArray();
int numNew = a.length;
ensureCapacity(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;
}

// 删除fromIndex到toIndex之间的全部元素。
protected void removeRange(int fromIndex, int toIndex) {
modCount++;
int numMoved = size - toIndex;
System.arraycopy(elementData, toIndex, elementData, fromIndex,
numMoved);

// Let gc do its work
int newSize = size - (toIndex-fromIndex);
while (size != newSize)
elementData[--size] = null;
}

// 克隆函数
public Object clone() {
try {
ArrayList<E> v = (ArrayList<E>) super.clone();
// 将当前ArrayList的全部元素拷贝到v中
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError();
}
}
}


4. ArrayList遍历方式

集合操作中最常见的就是遍历操作了,ArrayList支持3种遍历方式:迭代器遍历、随机访问和for循环|(foreach循环)

4.1 迭代器遍历,即通过Iterator去遍历(效率低下)

Integer value = null;
Iterator iter = list.iterator();
while (iter.hasNext()) {
value = (Integer)iter.next();
}


4.2 随机访问遍历,通过索引值去遍历(效率高)

Integer value = null;
int size = list.size();
for (int i=0; i<size; i++) {
value = (Integer)list.get(i);
}


4.3 for循环遍历,效率介于二者之间

Integer value = null;
for (Integer integ:list) {
value = integ;
}


4.4 三种遍历效率比对

package com.my.mobile;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class TestArrayList {
public static void main(String[] args) {
TestArrayList t = new TestArrayList();
List<Data> initArrayList = t.initArrayList(100000);
testiterator(initArrayList);
testRandomAccess(initArrayList);
testforeach(initArrayList);

}
/**
* 测试foreach循环遍历ArrayList性能
* @param initArrayList
* */
private static void testforeach(List<Data> initArrayList) {
// TODO Auto-generated method stub
long starttime = System.currentTimeMillis();
for (Data data : initArrayList) {
//			System.out.println(data.toString());
}
System.out.println("foreach遍历完毕,耗时["+(System.currentTimeMillis() - starttime)+"]ms");
}

private static void testRandomAccess(List<Data> initArrayList) {
// TODO Auto-generated method stub
long starttime = System.currentTimeMillis();
for(int i = 0;i<initArrayList.size();i++){
Data data = initArrayList.get(i);
//			System.out.println(data.toString());
}
System.out.println("randomAccess遍历完毕,耗时["+(System.currentTimeMillis() - starttime)+"]ms");
}

private static void testiterator(List<Data> initArrayList) {
// TODO Auto-generated method stub
long starttime = System.currentTimeMillis();
for(Iterator<Data> iter = initArrayList.iterator();iter.hasNext();){
Data data = iter.next();
//			System.out.println(data.toString());
}
System.out.println("iterator遍历完毕,耗时["+(System.currentTimeMillis() - starttime)+"]ms");
}

private List<Data> initArrayList(int listSize){
Data data = new Data();
List<Data> arrayList = new ArrayList<Data>();
for(int i = 0;i<listSize;i++){
try {
TestArrayList.Data data_clone = data.clone();
data_clone.setId(i+"");
data_clone.setName("我是第"+i+"个酷酷的酷");
data_clone.setUser("kucs");
arrayList.add(data_clone);
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return arrayList;
}
class Data implements Cloneable{
private String id;
private String name;
private String user;

public Data() {
super();
// TODO Auto-generated constructor stub
}

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getUser() {
return user;
}

public void setUser(String user) {
this.user = user;
}

@Override
public String toString() {
return "Data [id=" + id + ", name=" + name + ", user=" + user + "]";
}

@Override
protected Data clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
return (Data)super.clone();
}

}
}

运行结果:

iterator遍历完毕,耗时[34]ms
randomAccess遍历完毕,耗时[12]ms
foreach遍历完毕,耗时[14]ms

经此实验,遍历ArrayList时,使用随机访问,遍历比较快。

5.ArrayList总结

ArrayList是实现了List接口的、大小可变的数组队列。能够实现所有List可选操作,并允许包括null在内的所有元素。除了实现List接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。(此类大致上等同于 Vector 类,除了此类是不同步的。)

ArrayList的size、isEmpty、get、set、iterator和listIterator操作都以固定时间运行。add操作以分摊的固定时间运行,也就是说,添加n个元素需要O(n)时间。其他所有操作都以线性时间运行。与用于LinkedList实现的常数因子相比,此实现的常数因子较低。

每个ArrayList实例都有一个容量。该容量是指用来存储列表元素的数组的大小。它总是至少等于列表的大小。随着向ArrayList中不断添加元素,其容量也自动增长。并未指定增长策略的细节,因为这不只是添加元素会带来分摊固定时间开销那样简单。在添加大量元素前,应用程序可以使用ensureCapacity操作来增加ArrayList实例的容量。这可以减少递增式再分配的数量。

注意,此实现不是同步的。如果多个线程同时访问一个ArrayList实例,而其中至少一个线程从结构上修改了列表,那么它必须保持外部同步。(结构上的修改是指任何添加或删除一个或多个元素的操作,或者显式调整底层数组的大小;仅仅设置元素的值不是结构上的修改。)这一般通过对自然封装该列表的对象进行同步操作来完成。如果不存在这样的对象,则应该使用Collections.synchronizedList方法将该列表“包装”起来。这最好在创建时完成,以防止意外对列表进行不同步的访问。尽量避免在并发访问中使用ArrayList。可以使用同步的Vector或者CopyOnWriteList集合代替ArrayList
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  List ArrayList Java 原理