Java源码阅读之ArrayList
基于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)
大致思路
- 确保在成功添加元素之后,数组的size不超过阈值。
- 添加元素
- 返回成功标志
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 ? e==null : 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 ? get(i)==null : 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的更多相关文章
- Java源码阅读的真实体会(一种学习思路)
Java源码阅读的真实体会(一种学习思路) 刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈 ...
- Java源码阅读的真实体会(一种学习思路)【转】
Java源码阅读的真实体会(一种学习思路) 刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+ ...
- Java源码系列1——ArrayList
本文简单介绍了 ArrayList,并对扩容,添加,删除操作的源代码做分析.能力有限,欢迎指正. ArrayList是什么? ArrayList 就是数组列表,主要用来装载数据.底层实现是数组 Obj ...
- 如何阅读Java源码 阅读java的真实体会
刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心. 说到技术基础,我打个比 ...
- [收藏] Java源码阅读的真实体会
收藏自http://www.iteye.com/topic/1113732 刚才在论坛不经意间,看到有关源码阅读的帖子.回想自己前几年,阅读源码那种兴奋和成就感(1),不禁又有一种激动. 源码阅读,我 ...
- jdk源码阅读笔记-ArrayList
一.ArrayList概述 首先我们来说一下ArrayList是什么?它解决了什么问题?ArrayList其实是一个数组,但是有区别于一般的数组,它是一个可以动态改变大小的动态数组.ArrayList ...
- java源码阅读Hashtable
1类签名与注释 public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, C ...
- Java源码阅读Stack
Stack(栈)实现了一个后进先出(LIFO)的数据结构.该类继承了Vector类,是通过调用父类Vector的方法实现基本操作的. Stack共有以下五个操作: put:将元素压入栈顶. pop:弹 ...
- 源码阅读之ArrayList(JDK8)
ArrayList概述 ArrayList是一个的可变数组的实现,实现了所有可选列表操作,并允许包括 null 在内的所有元素.每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组 ...
随机推荐
- PHP 浮点型运算相关问题
php 浮点数计算比较及取整不准确.举例: $a = 0.2+0.7; $b = 0.9; var_dump($a == $b); //输出的结果为bool(false) PHP 官方手册说明:显然简 ...
- 绕过WAF、安全狗知识整理
0x01 前言 目前市场上的WAF主要有以下几类 1. 以安全狗为代表的基于软件WAF 2. 百度加速乐.安全宝等部署在云端的WAF 3. 硬件WAF WAF的检测主要有三个阶段,我画了一张图进行说明 ...
- TypeScript封装统一操作Mysql Mongodb Mssql的底层类库demo
/* 功能:定义一个操作数据库的库 支持 Mysql Mssql MongoDb 要求1:Mysql MsSql MongoDb功能一样 都有 add update delete get方法 注意:约 ...
- 使用 evo 工具评测 VI ORB SLAM2 在 EuRoC 上的结果
http://www.liuxiao.org/2017/11/%E4%BD%BF%E7%94%A8-evo-%E5%B7%A5%E5%85%B7%E8%AF%84%E6%B5%8B-vi-orb-sl ...
- php cURL error 60: SSL certificate problem: unable to get local issuer certificate 解决办法
错误例子如下: php5.6以上的版本会出现这种问题 关于“SSL证书问题:无法获取本地颁发者证书”错误.很明显,这适用于发送CURL请求的系统(并且没有服务器接收请求) 1)从https://cur ...
- django 自定义 密码加密方式 及自定义验证方式
在django1.6中,默认的加密方式是pbkdf_sha256,具体算法不表,一直以来用django的自带用户验证都十分顺手,但如果需要修改默认加密方式为md5,具体方法为: 在settings.p ...
- 简单明了的注解,读取CLASS中的注解
/***********注解声明***************/ /** * 水果名称注解 * @author peida * */ @Target(ElementType.FIELD) @Reten ...
- GNU C之__attribute__
__attribute__可以设置函数属性(Function Attribute).变量属性(Variable Attribute)和类型属性(Type Attribute) __attribute_ ...
- C#语法中的select
第一次学着用Linq的盆友们,可以看看哈.... 代码 Code highlighting produced by Actipro CodeHighlighter (freeware)http://w ...
- Java分布式:分布式事务
Java分布式:分布式事务 分布式事务 分布式事务就是指事务的参与者.支持事务的服务器.资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上.简单的说,就是一次大的操作由不同的小操作组成,这 ...