jdk源码分析之ArrayList
ArrayList关键属性分析
ArrayList采用Object数组来存储数据
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. 何问起 hovertree.com
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* The size of the ArrayList (the number of elements it contains).
* @serial
*/
private int size;
Object[] elementData是一个buffer数组,用来存储ArrayList的数据,该数组的大小表示ArrayList的容量,而size属性表示的是ArrayList里边存储元素的个数。
/**
* 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.何问起 hovertree.com
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
多个ArrayList实例共享的static属性,一个空数组的实例,使用ArrayList的无参构造函数创建ArrayList实例的时候,直接使用DEFAULTCAPACITY_EMPTY_ELEMENTDATA给底层数组elementData赋值
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
而当创建一个容量为0的ArrayList时,直接将层数组elementData赋值为另外一个static属性
/**
* Shared empty array instance used for empty instances.
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
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);
}
}
从ArrayList获取数据get(int index)
ArrayList可以通过下标对数据进行随机访问,时间复杂度O(1),实现方法为get方法
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
get方法很简单,输入参数合法的话直接返回底层数组elementData对应位置的元素即可。
而rangeCheck主要是检查下标不能越界访问
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
向ArrayList尾部添加一个数据add(E e)
添加数据有两个方法,调用add(E e) 向ArrayList末尾添加数据和调用add(int index, E element)添加数据到指定位置
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
添加数据首先检查是不是需要扩充容量来添加新的数据,调用ensureCapacityInternal(size + 1)确保当前容量足够添加一个新的数据,然后elementData[size++] = e将新数据添加在数组的末尾
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
ensureCapacityInternal确保在ArrayList容量为0的时候添加数据扩容时,至少扩容DEFAULT_CAPACITY大小,而DEFAULT_CAPACITY大小默认为10
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
然后调用 ensureExplicitCapacity(minCapacity)进行扩容
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
modCount++用于记录修改次数,主要用于一个线程使用迭代器迭代数据的时候另一个数据更改ArrayList结构抛出ConcurrentModificationException异常
if (minCapacity - elementData.length > 0)用于判断是否真的需要扩容,elementData.length表示的是容器容量,size表示容器存储数据的数量,而minCapacity =sie+1,因此elementData如果有多于一个位置空闲没有存储数据,就不需要扩容
否则调用grow(minCapacity)进行真正的扩容
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);
}
可以看到扩容时直接将容量大小变为之前的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
这里还需要对newCapacity进行调整,如果扩容后的newCapacity还是比minCapacity小
满足
if (newCapacity - minCapacity < 0)
设置newCapacity为传进来的参数minCapacity
newCapacity = minCapacity;
然后判断现在的newCapacity是否比上限MAX_ARRAY_SIZE大
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
如果newCapacity比MAX_ARRAY_SIZE还大,则需要调用hugeCapacity(minCapacity)判断是否是minCapacity传入负数溢出
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
hugeCapacity(int minCapacity)首先检测是否溢出,没溢出的话就返回Integer.MAX_VALUE 作为最大值(minCapacity > MAX_ARRAY_SIZE条件在调用出就满足)
经过上述一系列步骤,最终满足各种条件的新容量值minCapacity得到满足
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
这里是直接创建一个容量为newCapacity的新数组,把之前elementData保存的数据复制到新数组。数组的复制是比较耗费性能的,因此应该避免连续扩容,尽量调用有参构造函数ArrayList(int initialCapacity)并设置合理容量大小
向ArrayList任意位置添加一个数据add(int index, E element)
调用add(int index, E element)在ArrayList任意位置添加数据
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++;
}
传入参数合法性检查仍然是方法体第一件要做的事情,这里要检测插入数据的位置index是否合法
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
参数合法性通过后调用ensureCapacityInternal(size + 1)进行扩容,上边已经分析过扩容的固定套路。扩容后就是调用System.arraycopy进行数组的复制,由此可见ArrayList添加数据是比较耗费性能的,事件复杂度是O(n),因为插入数据总是伴随着数组的复制(数组元素的移动)。
在ArrayList尾部添加一个Collection集合addAll
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;
}
先通过Collection的toArray方法把Collection转化为Object数组,然后通过ensureCapacityInternal(size + numNew)进行扩容,然后调用
System.arraycopy(a, 0, elementData, size, numNew);
public static native void arraycopy(Object src, int srcPos,Object dest, int destPos,int length);
System.arraycopy进行数组的拼接,System.arraycopy(a, 0, elementData, size, numNew)表示从数组a的下标0出开始复制length个元素导数组elementData的下标size处。
在ArrayList任意位置添加一个集合Collection
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
仍然是先做入口参数的合法性检查
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
然后把集合转化为Object数组
Object[] a = c.toArray();
通过ensureCapacityInternal(size + numNew)进行扩容
计算原数组需要移动的数据的个数int numMoved = size - index
如果有数据需要移动,调用System.arraycopy进行数据整体移动
System.arraycopy(elementData, index, elementData, index + numNew, numMoved);
从elementData数组下标为index出复制numMoved个数据到elementData数组的下标为index + numNew处
这样elementData数组下标为index到index+numNew-1的位置被空出来,用于放置新的数据
然后集合的数据添加到elementData数组留出的位置即可
System.arraycopy(a, 0, elementData, index, numNew);
从数组a的下标为0的地方复制numNew个数据到elementData数组的下标为index处
ArrayList中获取某一数据的下标indexOf
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;
}
获取下标只能通过遍历的方式逐一比较,null数据不能调用方法,所以不能通过equals进行比较,所以分类讨论,
如果传入的参数o是是null的话通过elementData[i]==null进行比较,否则通过o.equals(elementData[i])进行比较。
如果遍历过程中找到该数据,返回该数据下标,遍历结束没有找到返回-1
注意indexOf是找到第一个相等的数据就直接返回下标了,如果想找到该数据在ArrayList中的最后一个位置,使用lastIndexOf即可
清空ArrayList的全部数据clear()
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
首先modCount++,这是如果其他线程正在通过迭代器进行数据的访问,检测到modCount发生了变化将会抛出ConcurrentModificationException
然后设置数组中的每一个元素为null,方便垃圾回收,最后设置size=0;
虽然数据全部置null,size归0,但是elementData的大小没有变化
ArrayList的克隆clone()
/**
* Returns a shallow copy of this <tt>ArrayList</tt> instance. (The
* elements themselves are not copied.)
*
* @return a clone of this <tt>ArrayList</tt> instance
*/
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
可以看到clone方法只是进行了浅克隆,在某些需要深克隆的场景出,需要自己负责每一个元素的深克隆
ArrayList删除指定位置的数据
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;
}
代码流程还是比较清晰
下标检查,modCount++达到迭代器快速失败,数组元素的移动,返回旧值
其中比较重要的一点就是
elementData[–size] = null; // clear to let GC do its work
Effective Java提到了这点,自己申请内存自己要记得管理,否则造成内存泄露
ArrayList删除某一个数据
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
这里同样是根据传入的参数o是否为null进行分类处理,找到元素所在的位置index后直接调用 fastRemove(index)进行数据的删除,fastRemove比remove不需要检查数组下标
ArrayList的排序sort
public void sort(Comparator<? super E> c) {
final int expectedModCount = modCount;
Arrays.sort((E[]) elementData, 0, size, c);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
先保存modCount,如果modCount在排序过程中被改变,抛出ConcurrentModificationException异常
排序是直接调用Arrays.sort方法
ArrayList的缩容trimToSize()
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
先登记modCount++是迭代器可以快速失败
然后通过 Arrays.copyOf进行数组创建和复制,使ArrayList的容量等于保存的数据的数量的大小
推荐:http://www.cnblogs.com/roucheng/p/javatimu.html
jdk源码分析之ArrayList的更多相关文章
- 【jdk源码分析】ArrayList的size()==0和isEmpty()
先看结果 分析源码 [jdk源码解析]jdk8的ArrayList初始化长度为0 java的基本数据类型默认值 无参构造 size()方法 isEmpty()方法
- JDK源码分析(一)——ArrayList
目录 ArrayList分析 ArrayList继承结构 ArrayList字段属性 ArrayList构造函数 重要方法 ArrayList Iterator迭代器 总结 ArrayList分析 ...
- 【JDK】JDK源码分析-ArrayList
概述 ArrayList 是 List 接口的一个实现类,也是 Java 中最常用的容器实现类之一,可以把它理解为「可变数组」. 我们知道,Java 中的数组初始化时需要指定长度,而且指定后不能改变. ...
- 集合源码分析[3]-ArrayList 源码分析
历史文章: Collection 源码分析 AbstractList 源码分析 介绍 ArrayList是一个数组队列,相当于动态数组,与Java的数组对比,他的容量可以动态改变. 继承关系 Arra ...
- JDK源码分析(2)LinkedList
JDK版本 LinkedList简介 LinkedList 是一个继承于AbstractSequentialList的双向链表.它也可以被当作堆栈.队列或双端队列进行操作. LinkedList 实现 ...
- 【JDK】JDK源码分析-Vector
概述 上文「JDK源码分析-ArrayList」主要分析了 ArrayList 的实现原理.本文分析 List 接口的另一个实现类:Vector. Vector 的内部实现与 ArrayList 类似 ...
- 【JDK】JDK源码分析-List, Iterator, ListIterator
List 是最常用的容器之一.之前提到过,分析源码时,优先分析接口的源码,因此这里先从 List 接口分析.List 方法列表如下: 由于上文「JDK源码分析-Collection」已对 Collec ...
- 【JDK】JDK源码分析-CountDownLatch
概述 CountDownLatch 是并发包中的一个工具类,它的典型应用场景为:一个线程等待几个线程执行,待这几个线程结束后,该线程再继续执行. 简单起见,可以把它理解为一个倒数的计数器:初始值为线程 ...
- JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue
JDK源码分析—— ArrayBlockingQueue 和 LinkedBlockingQueue 目的:本文通过分析JDK源码来对比ArrayBlockingQueue 和LinkedBlocki ...
随机推荐
- [php入门] 1、从安装开发环境环境到(庄B)做个炫酷的登陆应用
1.前言 本文适合想了解下web开发入门的小白~ 最近在根据一个网上课程学习php,课程内说道: php环境自己搭比较麻烦,可以用wamp集成开发环境,一键安装即可. 2.安装集成开发环境 下面是wa ...
- 用python语言讲解数据结构与算法
写在前面的话:关于数据结构与算法讲解的书籍很多,但是用python语言去实现的不是很多,最近有幸看到一本这样的书籍,由Brad Miller and David Ranum编写的<Problem ...
- 我心中的核心组件(可插拔的AOP)~第四回 异常拦截器
回到目录 之前说过有关拦截器的文章,第二回 缓存拦截器,事实上,在那讲里说的最多是AOP和缓存组件,对于拦截的概念并没有详细的说明,这一讲,不说AOP,主要说一下拦截器,拦截器Interceptio ...
- 我心中的核心组件(可插拔的AOP)~分布式文件上传组件~基于FastDFS
回到目录 一些概念 在大叔框架里总觉得缺点什么,在最近的项目开发中,终于知道缺什么了,分布式文件存储组件,就是缺它,呵呵,对于分布式文件存储来说,业界比较公认的是FastDFS组件,它自己本身就是集群 ...
- Java程序员的日常—— IOUtils总结
以前写文件的复制很麻烦,需要各种输入流,然后读取line,输出到输出流...其实apache.commons.io里面提供了输入流输出流的常用工具方法,非常方便.下面就结合源码,看看IOUTils都有 ...
- 百度面试题 字符串相似度 算法 similar_text 和页面相似度算法
在百度的面试,简直就是花样求虐. 首先在面试官看简历的期间,除了一个自己定义字符串相似度,并且写出求相似度的算法. ...这个确实没听说过,php的similar_text函数也是闻所未闻的.之前看s ...
- 批处理集锦——(4)2>nul和1>nul是什么意思?
>nul 是屏蔽操作成功显示的信息,但是出错还是会显示(即1>nul) 2>nul 是屏蔽操作失败显示的信息,如果成功依旧显示. >nul 2>nul 就是正确的错误的一 ...
- 《PHP Manual》阅读笔记3 —— 类与对象
1.PHP 中的所有函数和类都具有全局作用域,可以定义在一个函数之内而在之外调用,反之亦然. PHP 不支持函数重载,也不可能取消定义或者重定义已声明的函数. 当一个函数是有条件被定义时,必须在调用函 ...
- 使用替换shader渲染
相关函数: Camera.RenderWithShader(shader: Shader, replacementTag: string) 使用指定shader渲染,只影响一帧 Camera.SetR ...
- 编译原理LL1文法Follow集算法实现
import hjzgg.first.First; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set ...