基于jdk1.8的ArrayList源码分析。


实现List接口最常见的大概就四种,ArrayList, LinkedList, Vector, Stack实现,今天就着重看一下ArrayList的源码实现。ArrayList的底层结构就是最简单的数组,数据结构导致了它查询快,但是增删慢。另外官方也说了,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. (This class is roughly equivalent to Vector, except that it is unsynchronized.)


开始阅读

声明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* Default initial capacity.//初始化容量=10
*/
private static final int DEFAULT_CAPACITY = 10;
 
/**
* Shared empty array instance used for empty instances.//空数组
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
 
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
* 也是空数组,但是第一次add的时候会自动调整容量为DEFAULT_CAPACITY
*/
private static final Object[] DEFAULTCAPACITY_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 == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
* 数组声明,不参与序列化。
*/
transient Object[] elementData; // non-private to simplify nested class access
 
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
* 包含元素的个数,不一定等于elementData.length。。
*/
private int size;

看完了类的属性声明,我们趁热打铁接着就去看一下构造方法!一探初始化ArrayList的究竟。

构造方法
空参数构造方法

默认的构造方法创建的elementData中的长度是1, size=0第一次add的时候会自动调整elementData.length=10。补充一点,我们要知道elementData.length != size。

1
2
3
4
5
6
/**
* Constructs an empty list with an initial capacity of ten
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
初始化指定容量的构造函数

这个构造函数没什么说的,判断传入的参数如果>0就初始化指定参数的数组,如果等于0,就是一个空数组咯,<0,对不起,抛出异常!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 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) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
创建包含指定Collection的构造函数

这个构造函数也没什么好说的,开始吧Collection转换成数组并且elementData引用数组地址(浅拷贝),然后判断数组的长度(并修改size的值),如果不为0就深拷贝数组的内容到elementData中去,如果size还等于0的话,就创建一个空数组就行了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 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
* 创建一个包含指定Collection的数组
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
//如果传入的Collection不为空
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652), c.toArray()错误时候不返回Object[]
if (elementData.getClass() != Object[].class)
//拷贝
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {//如果为空创建一个空数组
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
add*()
add(E e)

大致思路

  1. 确保在成功添加元素之后,数组的size不超过阈值。
  2. 添加元素
  3. 返回成功标志
1
2
3
4
5
6
7
8
9
10
11
/**
* Appends the specified element to the end of this list.
*
* @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;
}

我们继续看一下ensureCapacityInternal的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
 
ensureExplicitCapacity(minCapacity);
}
 
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
 
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

我想我应该补充一下add()方法思路的描述的第一条。确保成功添加元素之后的size不超过阈值。首先在ensureCapacityInternal方法中先判断elementData的类型,如果是DEFAULTCAPACITY_EMPTY_ELEMENTDATA(这个就和我们上面所述的DEFAULTCAPACITY_EMPTY_ELEMENTDATA初始化elementData.length=1, size=0, 第一次add会自动扩容成10,添加成功size变成1)。那么此时取DEFAULT_CAPACITY(10)和当前假设添加元素成功之后的size(其实就是未添加之前的size+1)的最大值。然后进行ensureExplicitCapacity的判断。在ensureExplicitCapacity中,先自增修改次数标识(我们无需关注这个属性)。然后开始判断if, if的主体翻译过来就是: 数组已使用的数组长度+1大于当前数组长度(也就是比较size+1和element.length的大小,我们之前不是说过size != element.length吗?size+1和element.length的比较说明了什么呢?其实很简单,因为size代表的始终是已添加的元素的个数,而element.length就是数组的最大长度,如果添加成功的元素的个数(size+1) > 数组最大长度(element.length)了,那岂不jj了,所以此时要扩容了,那调用grow方法吧),我们接着看一下grow方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
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);
}
 
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}

代码中,我们很容易看出扩容是按照1.5被的oldCapacity进行扩容的,如果1.5oldCapacity 还小于我们需要的最小的minCapacity(也就是size+1),那直接按照minCapacity进行扩容算了。如果newCapacity大于数组允许的最大值(Integer.MAX_VALUE-8)了,那就试着缩小一下扩容的范围。判断内存是否溢出,不然返回最大整型值,继续扩容。如果这一切没问题,那就进行数组的深拷贝,此时完成扩容并拷贝了数据。

add(int index, E element)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* 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).
*
* @param index index at which the specified element is to be inserted
* @param element element to be inserted
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) {
rangeCheckForAdd(index);
 
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
 
/**
* A version of rangeCheck used by add and addAll.
*/
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

这个方法,和add(E e)没什么差别,唯一多了的就是越界检查。没什么好分析的。

addAll(Collection<? extends E> c)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 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.)
*
* @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;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}

因为我们之前详细分析了add(E e)这个方法,然后看addAll这个方法就很简单(其实本来也不难哈(玩笑脸))。要添加的Collection的长度加上size判断容量,然后进行深拷贝,修改size,返回添加的结果,完了,就这么简单!另外还有一个addAll(int index, Collection<? extends E> c)我就不贴了,脑补一下,其实实现都很简单,无非就是检查容量,拷贝,修改size….

get()
E get(int index)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Returns the element at the specified position in this list.
*
* @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);
}
 
/**
* Checks if the given index is in range. If not, throws an appropriate
* runtime exception. This method does *not* check if the index is
* negative: It is always used immediately prior to an array access,
* which throws an ArrayIndexOutOfBoundsException if index is negative.
*/
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

没啥好说的吧。

1
2
3
4
if index 越界:
throw exception
else:
return elementData[index]
set
E set(int index, E element)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Replaces the element at the specified position in this list with
* the specified element.
*
* @param index index of the element to replace
* @param element element to be stored at the specified position
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E set(int index, E element) {
rangeCheck(index);
 
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
 
// Positional Access Operations
 
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
}
contains()
contains(Object o)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* Returns <tt>true</tt> if this list contains the specified element.
* More formally, returns <tt>true</tt> if and only if this list contains
* at least one element <tt>e</tt> such that
* <tt>(o==null&nbsp;?&nbsp;e==null&nbsp;:&nbsp;o.equals(e))</tt>.
*
* @param o element whose presence in this list is to be tested
* @return <tt>true</tt> if this list contains the specified element
*/
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.
* More formally, returns the lowest index <tt>i</tt> such that
* <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
* or -1 if there is no such 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;
}

这个也不作分析了,很简单的代码。

remove()
E remove(int index)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left (subtracts one from their
* indices).
*
* @param index the index of the element to be removed
* @return the element that was removed from the list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
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;
}

删除我就稍作一下分析吧,因为我们都知道数组的特点就是查询快,增删慢的数据结构。 remove先检查index,然后计算要移动的元素的个数,然后调用System.arraycopy方法

1
2
3
4
5
public static void arraycopy(Object src,
int srcPos,
Object dest,
int destPos,
int length)

src:源数组; srcPos:源数组要复制的起始位置;
dest:目标数组; destPos:目的数组放置的起始位置; length:复制的长度。
注意:src and dest都必须是同类型或者可以进行转换类型的数组

很明显,System.copyarray跳过了要删除的元素,达到了删除的目的。

clear()
clear()
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Removes all of the elements from this list. The list will
* be empty after this call returns.
*/
public void clear() {
modCount++;
 
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
 
size = 0;
}

结束

Java源码阅读之ArrayList的更多相关文章

  1. Java源码阅读的真实体会(一种学习思路)

    Java源码阅读的真实体会(一种学习思路) 刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈 ...

  2. Java源码阅读的真实体会(一种学习思路)【转】

    Java源码阅读的真实体会(一种学习思路)   刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+ ...

  3. Java源码系列1——ArrayList

    本文简单介绍了 ArrayList,并对扩容,添加,删除操作的源代码做分析.能力有限,欢迎指正. ArrayList是什么? ArrayList 就是数组列表,主要用来装载数据.底层实现是数组 Obj ...

  4. 如何阅读Java源码 阅读java的真实体会

    刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心.   说到技术基础,我打个比 ...

  5. [收藏] Java源码阅读的真实体会

    收藏自http://www.iteye.com/topic/1113732 刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我 ...

  6. jdk源码阅读笔记-ArrayList

    一.ArrayList概述 首先我们来说一下ArrayList是什么?它解决了什么问题?ArrayList其实是一个数组,但是有区别于一般的数组,它是一个可以动态改变大小的动态数组.ArrayList ...

  7. java源码阅读Hashtable

    1类签名与注释 public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, C ...

  8. Java源码阅读Stack

    Stack(栈)实现了一个后进先出(LIFO)的数据结构.该类继承了Vector类,是通过调用父类Vector的方法实现基本操作的. Stack共有以下五个操作: put:将元素压入栈顶. pop:弹 ...

  9. 源码阅读之ArrayList(JDK8)

    ArrayList概述 ArrayList是一个的可变数组的实现,实现了所有可选列表操作,并允许包括 null 在内的所有元素.每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组 ...

随机推荐

  1. 处理 Bootstrap CSS 冲突

    问题: Bootstrap 是Web上最流行的CSS框架.它使创建漂亮,响应迅速的设计非常容易. 但是,有时候并不希望在整个网站上使用 Bootstrap,您只需要使用Bootstrap CSS的一部 ...

  2. leetcode 576. Out of Boundary Paths 、688. Knight Probability in Chessboard

    576. Out of Boundary Paths 给你一个棋盘,并放一个东西在一个起始位置,上.下.左.右移动,移动n次,一共有多少种可能移出这个棋盘 https://www.cnblogs.co ...

  3. Dart泛型

    /* 通俗理解:泛型就是解决 类 接口 方法的复用性.以及对不特定数据类型的支持(类型校验) */ //只能返回string类型的数据 // String getData(String value){ ...

  4. shell编程系列2--字符串的处理

    shell编程系列2--字符串的处理 字符串的处理 .计算字符串的长度 方法1 ${#string} 方法2 expr length "$string" (如果string中间有空 ...

  5. ES6深入浅出-7 新版的类(上集)-1.介绍原型

    ES6新出的关键class BE受雇与网景开发了JS 当我们在写一个对象的时候,我们实际上内存的形式表示. obj等于一个空对象,可以直接toString.它为什么可以有toString window ...

  6. 【FreeMarker】Spring MVC与FreeMarker整合(二)

    前一篇介绍了FreeMarker的基本使用,本例介绍Spring MVC与FreeMarker整合 不熟悉项目搭建,可参考 [FreeMarker]FreeMarker快速入门(一) 整合 1.新建S ...

  7. 一份 Tomcat 和 JVM 的性能调优经验总结!拿走不谢

    Tomcat性能调优 找到Tomcat根目录下的conf目录,修改server.xml文件的内容.对于这部分的调优,我所了解到的就是无非设置一下Tomcat服务器的最大并发数和Tomcat初始化时创建 ...

  8. jQuery调用WebService返回JSON数据

    相信大家都比较了解JSON格式的数据对于ajax的方便,不了解的可以从网上找一下这方面的资料来看一下,这里就不多说了,不清楚的可以在网上查一下,这里只说一下因为参数设置不当引起的取不到返回值的问题. ...

  9. SpringBoot学习笔记:Swagger实现文档管理

    SpringBoot学习笔记:Swagger实现文档管理 Swagger Swagger是一个规范且完整的框架,用于生成.描述.调用和可视化RESTful风格的Web服务.Swagger的目标是对RE ...

  10. 【GStreamer开发】GStreamer播放教程02——字幕管理

    目标 这篇教程和上一篇非常相似,但不是切换音频流,而是字幕了.这次我们会展示: 如何选择选择字幕流 如何引入外部的字幕 如何客制化字幕使用的字体 介绍 我们都知道一个文件可以有多个音视频流并且可以使用 ...