一、前言

有了前一篇对集合类的概述,我们知道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. Configure custom SSL certificate for RDP on Windows Server 2012 in Remote Administration mode

    Q: So the release of Windows Server 2012 has removed a lot of the old Remote Desktop related configu ...

  2. 战胜忧虑<2>——忙碌可以消除忧虑

    忙碌可以消除忧虑 当你的脑筋空出来时,也会有东西进去补充,是什么呢?通常都是你的感觉.为什么?因为忧虑.恐惧.憎恨.嫉妒.和羡慕等等情绪,都是由我们的思想所控制的,这种情绪都非常猛烈.会把我们思想中所 ...

  3. Java的外部类和内部类+静态变量和非静态变量的组合关系

    看的李刚<疯狂java讲义>,里面讲内部类的地方感觉有点散而且不全,看完之后还是不十分清楚到底怎么用,于是自己写了个程序测试了一下.看如下代码,即可知道外部类和内部类+静态成员和非静态成员 ...

  4. html中select标签获取选中value和text

    $("#id").find("option:selected").text(); //获取Select选择的Text$("#id").val ...

  5. duplicate symbols

    duplicate symbol _mCollecatView in: /Users/Rubert/Library/Developer/Xcode/DerivedData/ChengDuHidengD ...

  6. RMAN_学习实验2_RMAN Duplicate复制数据库过程(案例)

    待整理 对于基于生产环境下的数据库的版本升级或者测试新的应用程序的性能及其影响,备份恢复等等,我们可以采取从生产环境以克隆的方式将其克隆到本地而不影响生产数据库的正常使用.实现这个功能我们可以借助rm ...

  7. 白书 4.1.2 模运算的世界 P291

    1.逆元 这里有个注意事项要说,就是当要求 (a-b)%m 的时候要注意不能直接 (a%m-b%m)%m 原因是得出的值有可能是负数,所以 (a%m-b%m+m)%m 才是正确的. //x,y是引用 ...

  8. andriod_入门一

    [小技巧] 1.背景图片文件名不能以数字开头,必须以字母开头. 2.如果按钮被背景图被遮住,可以在relativelayout里拖动控件,把背景拖到最上方: [异常] 1. 在用LinearLayou ...

  9. 深入Java集合学习系列:HashMap的实现原理

    1.    HashMap概述: HashMap是基于哈希表的Map接口的非同步实现.此实现提供所有可选的映射操作,并允许使用null值和null键.此类不保证映射的顺序,特别是它不保证该顺序恒久不变 ...

  10. ndk android studio万年坑

    先说javah万年坑 javah语法: Usage: javah [options] <classes> where [options] include: -o <file>  ...