一、前言

有了前一篇对集合类的概述,我们知道ArrayList是属于Collection类系中的一个具体实现类,其特点是长度可以动态改变,集合内部使用数组保存元素。下面我们对源码进行分析。

二、ArrayList源代码分析

2.1 类的继承关系

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

说明:可以看出ArrayList类实现了List、RandomAccess、Cloneable和Serializable接口。我们可以看出ArrayList是List类系中的具体类。RandomAccess用于支持快速随机访问,Cloneable用于支持深拷贝。同时还继承了AbstractList抽象类,使Arraylist可以选择性的实现List接口中的方法。在定义的时候使用了泛型来支持类型动态加载。

2.2类的属性

/**

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

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

*/

// Object数组,用于保存ArrayList的元素,此数组的长度为ArrayList的容量

private transient Object[] elementData;

/**

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

*/

// ArrayList的长度,即此ArrayList元素的个数

private int size;

//定义数组最大的size

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

说明:ArrayList中定义了一个Object的数组用于保存元素,故归根结底对于ArrayList的所有操作其实都是对数组的操作,因此在随机访问时效率高,而和改变数组大小相关的操作如插入删除操作效率低。

2.3构造方法

/**

* Constructs an empty list with the specified initial capacity.

*/

,抛出异常信息

public ArrayList(int initialCapacity) {

super();

if (initialCapacity < 0)

throw new IllegalArgumentException("Illegal Capacity: "

+ initialCapacity);

// 定义一个指定大小的Object数组

this.elementData = new Object[initialCapacity];

}

/**

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

*/

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.

*/

// 创建一个ArrayList,其中包含参数集合中的所有元素,顺序为迭代器返回的顺序

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

elementData = c.toArray();

size = elementData.length;

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

// 当调用toArray函数返回的类型不为Object数组使,使用Arrays.copyOf()函数完成拷贝

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

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

}

说明:ArrayList支持三种构造方式:默认大小、指定大小和指定元素。其中使用指定元素或指定大小构造时,所得到的ArrayList对象可以正常使用其所有函数完成增删改等操作。

2.4核心函数分析

、contains(Object o)函数

/**

* Returns the index of the first occurrence of the specified element in

* this list, or -1 if this list does not contain the element.

*/

// 返回特定元素(包括null)第一次出现的index,如果不存在则返回-1,比较相等时用的是equals方法

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 boolean contains(Object o) {

时说明存在

return indexOf(o) >= 0;

}

说明:使用containes或者indexOf方法时需要重写equals方法。

、添加元素的函数

/**

* Appends the specified element to the end of this list.

*/

public boolean add(E e) {

ensureCapacityInternal(size + 1); // Increments modCount!!

//扩充数组后将元素添加到数组中

elementData[size++] = e;

return true;

}

在添加元素时,首先要确保保存元素的数组能够再添加一个元素。在add函数内部首先调用了ensureCapacityInternal函数;函数代码如下:

private void ensureCapacityInternal(int minCapacity) {

modCount++;

// overflow-conscious code

//如果最小需要容量大于数组的长度时,需要对数组长度扩充

if (minCapacity - elementData.length > 0)

grow(minCapacity);

}

此函数比较存放元素所需空间和数组的长度,如果长度不够,则调用grow函数扩充数组容量,参数为元素个数。函数代码如下:

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

}

grow函数按照一定的规则获取新的数组长度,并将元素复制到新的数组中,其中如果新的数组长度大于支持的最大值时,会调用hugeCapacity函数获取获取整数的最大值作为数组长度。其中函数源代码如下:

private static int hugeCapacity(int minCapacity) {

if (minCapacity < 0) // overflow

throw new OutOfMemoryError();

return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE

: MAX_ARRAY_SIZE;

}

添加元素大致的流程是:调用add方法----------->扩充数组-------------->添加元素

将元素添加到指定index中:

public void add(int index, E element) {

或者大于数组长度都将抛出异常

rangeCheckForAdd(index);

// 扩充容量

ensureCapacityInternal(size + 1); // Increments modCount!!

//将index索引后面的元素全部向后移动一位

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;

}

添加多个元素到指定index

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

// 检查index是否合法

rangeCheckForAdd(index);

Object[] a = c.toArray();

int numNew = a.length;

// 扩充数组

ensureCapacityInternal(size + numNew); // Increments modCount

int numMoved = size - index;

// 移动index以后的元素

if (numMoved > 0)

System.arraycopy(elementData, index, elementData, index + numNew,

numMoved);

//将元素添加到到数组中

System.arraycopy(a, 0, elementData, index, numNew);

size += numNew;

return numNew != 0;

}

说明:当在中间插入元素时,要对数组元素进行重排序,因此效率要低。

、删除元素函数

删除指定index的元素

E elementData(int index) {

return (E) elementData[index];

}

public E remove(int index) {

// 检查index的合法性

rangeCheck(index);

modCount++;

// 保存要删除的元素

E oldValue = elementData(index);

int numMoved = size - index - 1;

if (numMoved > 0)

System.arraycopy(elementData, index + 1, elementData, index,

numMoved);

// 将最后一个元素设置为null

elementData[--size] = null; // Let gc do its work

return oldValue;

}

删除操作实质上是用后面的元素覆盖掉前面要删除的元素,最后将末尾元素设置为null的操作

删除指定第一次出现的元素

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; // Let gc do its work

}

public boolean remove(Object o) {

if (o == null) {

//可以remove一个null元素

for (int index = 0; index < size; index++)

if (elementData[index] == null) {

fastRemove(index);

return true;

}

} else {

for (int index = 0; index < size; index++)

//以来equals方法,所以元素需要重写equals方法

if (o.equals(elementData[index])) {

fastRemove(index);

return true;

}

}

return false;

}

删除操作依赖于equals方法,并且可以删除null;

删除指定集合中的所有元素,该方法依赖containes方法比较元素,因此元素同样需要重新定义equals方法

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之后的元素全部设置为null

if (w != size) {

for (int i = w; i < size; i++)

elementData[i] = null;

modCount += size - w;

size = w;

modified = true;

}

}

return modified;

}

// 删除若干个元素

public boolean removeAll(Collection<?> c) {

// 调用batchRemove函数

return batchRemove(c, false);

}

收获:在对数组进行增删改查等操作时,应该首先检查index的合法性

删除所有不在参数集合中的元素

public boolean retainAll(Collection<?> c) {

return batchRemove(c, true);

}

三、总结

    ArrayList对于元素的操作底层实现全部是基于对数组的操作实现的,因此具有随机访问效率高,但是插入删除效率低的特点。

JDK源码分析之集合02ArrayList的更多相关文章

  1. JDK源码分析之集合03LinkedList

    一.前言 LinkedList是双向列表,实现方式是使用链表的方式保存元素:除了第一个和最后一个元素外,每一个节点都包含一个指向前一个和指向后一个节点的指针和元素的值.其特点是插入删除效率高,而随机访 ...

  2. 【JDK】JDK源码分析-Vector

    概述 上文「JDK源码分析-ArrayList」主要分析了 ArrayList 的实现原理.本文分析 List 接口的另一个实现类:Vector. Vector 的内部实现与 ArrayList 类似 ...

  3. 【JDK】JDK源码分析-ArrayList

    概述 ArrayList 是 List 接口的一个实现类,也是 Java 中最常用的容器实现类之一,可以把它理解为「可变数组」. 我们知道,Java 中的数组初始化时需要指定长度,而且指定后不能改变. ...

  4. 【JDK】JDK源码分析-List, Iterator, ListIterator

    List 是最常用的容器之一.之前提到过,分析源码时,优先分析接口的源码,因此这里先从 List 接口分析.List 方法列表如下: 由于上文「JDK源码分析-Collection」已对 Collec ...

  5. 【JDK】JDK源码分析-HashMap(2)

    前文「JDK源码分析-HashMap(1)」分析了 HashMap 的内部结构和主要方法的实现原理.但是,面试中通常还会问到很多其他的问题,本文简要分析下常见的一些问题. 这里再贴一下 HashMap ...

  6. JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue

    JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue 目的:本文通过分析JDK源码来对比ArrayBlockingQueue 和LinkedBlocki ...

  7. JDK 源码分析(4)—— HashMap/LinkedHashMap/Hashtable

    JDK 源码分析(4)-- HashMap/LinkedHashMap/Hashtable HashMap HashMap采用的是哈希算法+链表冲突解决,table的大小永远为2次幂,因为在初始化的时 ...

  8. JDK源码分析(三)—— LinkedList

    参考文档 JDK源码分析(4)之 LinkedList 相关

  9. JDK源码分析(一)—— String

    dir 参考文档 JDK源码分析(1)之 String 相关

随机推荐

  1. Winform退出程序

    1.this.Close(); 只是关闭当前窗口,若不是主窗体的话,是无法退出程序的,另外若有托管线程(非主线程),也无法干净地退出: 2.Application.Exit(); 强制所有消息中止,退 ...

  2. CentOS下用pyenv 和 virtualenv 搭建单机多版本python 虚拟开发环境

    安装 系统环境:CentOS 6.5 安装依赖 yum -y install gcc gcc-c++ make git patch openssl-devel zlib-devel readline- ...

  3. android学习笔记30——AndroidMainfest.xml

    Manifest.xml文件的职责:指定APP的包名.声明四大组件, 以及启动方式.指定APP运行的进程名称.指定APP权限.指定最小API版本.指定需要连接的库. Manifest.xml的格式:& ...

  4. Mac下关于——你不能拷贝项目“”,因为它的名称太长或包括的字符在目的宗卷上无效。文件的删除

    内容是google的,测试有效,因为用revel打包的东西删除以后有这个循环bug Mac下关于——你不能拷贝项目“”,因为它的名称太长或包括的字符在目的宗卷上无效.文件的删除 关于这个问题我找到的一 ...

  5. Windows 7无线网卡启用wifi共享蓝屏!

    我的笔记本是联想Y460P,装的是Windows 7 Ultiame(x64)系统,通过设置笔记本的无线(Intel WiFi Link 1000 BGN)搭建Wifi环境并共享,使手机能够通过笔记本 ...

  6. ARM地址映射

    转自:http://blog.csdn.net/a3163504123/article/details/10958229 重映射之后,一般原来的地址依然有效.也就是说,可能两个地址,对应一个存储单元. ...

  7. 106. Construct Binary Tree from Inorder and Postorder Traversal

    Given inorder and postorder traversal of a tree, construct the binary tree. Note:You may assume that ...

  8. Ignoring HTTPS certificates

    Our Development setups won't have valid or trusted certificates. When do you want test our webserver ...

  9. python入门,猜数

    #this is a sample guess program import random guesses_made =0 name = raw_input('Hello! whats your na ...

  10. linux 标准输入输出

    文件描述符是一个简单的正整数,用以标明每一个被进程所打开的文件和socket.最前面的三个文件描述符(0,1,2)分别与标准输入(stdin),标准输出(stdout)和标准错误(stderr)对应 ...