基于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. OpenGL ES3使用MSAA(多重采样抗锯齿)的方法

    昨晚花费了我2个多小时的时间终于把OpenGL ES3.0中的MSAA给搞定了.在OpenGL ES2.0中,Khronos官方没有引入标准的MSAA全屏抗锯齿的方法,而Apple则采用了自己的GL_ ...

  2. 泡泡一分钟:Robust and Fast 3D Scan Alignment Using Mutual Information

    Robust and Fast 3D Scan Alignment Using Mutual Information 使用互信息进行稳健快速的三维扫描对准 https://arxiv.org/pdf/ ...

  3. 戒邪淫利器:《楞严经 四种清净明诲》实例及原经文 (转自学佛网:http://www.xuefo.net/nr/article55/545256.html)

    今日得持首楞严,不读天下糟粕书! 当你读完楞严经后就会很真实的升起这个感觉!根本觉得其他书都是多余的!所以<楞严经>还是要多读,多看!并且<楞严经>戒淫的效果特别好!好好的珍惜 ...

  4. 修改ecshop的70种技巧

    1.如何修改网站”欢迎惠临本店”答复(dafu):languages\zh_cn\common.php文件中,$_LANG['welcome']=’欢迎惠临本店’:将他修改成你需要的字样. 2.如何修 ...

  5. nodejs应用转换png,jpg,gif为webp图片格式

    本博客列表缩略图在支持webp格式的浏览器下,使用的是webp格式图片,不支持webp图片下使用的是原图片(如png,gif,jpg等) webp使用指南,请参考 https://www.imqian ...

  6. Data - 【转】数据分析的道与术

    简要说明 本文来自网络流传的"百度内部培训PPT - 数据分析的道与术",版权属于"百度",如有冒犯,即刻删除. PDF下载 - 数据分析的道与术 什么是数据分 ...

  7. Centos7网络连接

    参考链接:http://baijiahao.baidu.com/s?id=1597809303775176940(上面有些没说清楚,所以简化总结一丢丢) Step1 编辑 — 虚拟网络编辑器 — (管 ...

  8. js 判断字符串是否为JSON格式

    function isJSON(str) { if (typeof str == 'string') { try { var obj=JSON.parse(str); if(typeof obj == ...

  9. Python机器学习基础教程-第1章-鸢尾花的例子KNN

    前言 本系列教程基本就是摘抄<Python机器学习基础教程>中的例子内容. 为了便于跟踪和学习,本系列教程在Github上提供了jupyter notebook 版本: Github仓库: ...

  10. LeetCode 189. 旋转数组(Rotate Array)

    189. 旋转数组 LeetCode189. Rotate Array 题目描述 给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数. 示例 1: 输入: [1,2,3,4,5,6, ...