本篇文章主要是学习后的知识记录,存在不足,或许不够深入,还请谅解。

ArrayList源码解析

ArrayList中的变量



通过上图可以看到,ArrayList中总共有7个变量,下面看下每个变量的作用:

    /**
* 序列化
*/
private static final long serialVersionUID = 8683452581122892189L;
/**
* 默认的初始容量
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 用于在构造函数中,初始化一个空的数组
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 用于无参构造函数中,给一个空的数组
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 这个是真正存储元素的数组
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* size,是用来记录arrayList中,即elementData里的元素的大小,数组中共有多少个元素
* @serial
*/
private int size;
/**
* 这个参数,是数组所允许的最大长度,要是超出了,可能会报OutOfMemoryError异常
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

ArrayList构造函数

ArrayList中有三个构造函数:

    /**
* 指定一个具有初始容量的空数组,可以自己指定容量的大小
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException 指定的初始容量值若是负值,会抛出这个异常
*/
public ArrayList(int initialCapacity) {
//容量大于0,就把elementData初始化一个具有initialCapacity大小的数组
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
//要是指定的容量值是0,就初始化一个空数组
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
//指定的容量值小于0.抛出不合法参数异常
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
} /**
* 无参构造函数,会指定一个初始容量为10的数组
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
} /**
* 构造一个包含指定元素的列表集合,按照集合返回它们的顺序
*
* @param c 传入的集合,会把集合中的元素放入数组中
* @throws NullPointerException if the specified collection is null
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
//数组中元素的长度不为0,就把c的元素copy到elementData中
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
//要是元素个数是0个,就初始化一个空数组
this.elementData = EMPTY_ELEMENTDATA;
}
}

ArrayList中的add方法

    /**
* add方法,会把一个元素添加到数组的末尾
*
* @param e 传入的元素
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
//size是目前数组存的元素的个数,传入size+1,即需要的数组长度
//需要的数组长度,是可以再去容纳一个元素
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
 //这里传入的是size+1
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//这个方法是判断数组是否是一个空数组,要是使用无参构造函数初始化的arrayList,那么返回值就是10
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//要是elementData是一个空的数组
//判断需要的数组长度和默认容量值哪个大,返回最大的那个
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//要是elementData数组中有元素,直接返回minCapacity
return minCapacity;
}
//这里是,判断数组是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
//记录数组被修改的次数
modCount++; //要是需要的数组长度大于目前数组的长度,就需要扩容了(即数组的长度是否可以存入下一个元素)
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
 /**
* 扩容方法
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
//旧的数组的长度
int oldCapacity = elementData.length;
//需要扩容的数组的长度,即10*1.5=15
int newCapacity = oldCapacity + (oldCapacity >> 1);
//要是扩容后的数组的长度还是小于需要的最小容量,那么就把需要的最小容量给newCapacity
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//要是扩容后的数组长度比最大的数组容量还大,就需要控制了
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//把扩容之后的数组copy到长度为newCapacity的数组中
elementData = Arrays.copyOf(elementData, newCapacity);
} private static int hugeCapacity(int minCapacity) {
//参数小于0.抛异常
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//判断需要的数组容量和数组最大容量哪个大,
//需要的数组容量比数组最大容量还大,就返回int的最大值
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}

图式:

ArrayList中的add(插入指定位置)方法

先看下 System.arraycopy:

    // src 源数组
// srcPos 源数组要复制的起始位置
// dest 要赋值到的目标数组
// destPos 目标数组放置的起始位置
// length 复制的长度
System.arraycopy(Object src, int srcPos, Object dest, int destPos,int length);

继续add方法:

    /**
* 将元素插入数组中指定的位置
* @param index 指定的索引值
* @param element 需要插入的元素
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) {
//参数校验,判断要插入的下标是否越界
rangeCheckForAdd(index);
//这个和add(E e)是一样的,判断数组是否需要扩容等
ensureCapacityInternal(size + 1); // Increments modCount!!
//把插入index位置的原有元素以及该元素后面的元素,向右移动
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//把元素插入到index的位置
elementData[index] = element;
//数组元素的个数加1
size++;
}
    /**
* A version of rangeCheck used by add and addAll.
*/
private void rangeCheckForAdd(int index) {
//要是插入的位置比数组中元素的个数大,或者插入的位置值小于0,就抛出下标越界异常
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
} /**
* Constructs an IndexOutOfBoundsException detail message.
* Of the many possible refactorings of the error handling code,
* this "outlining" performs best with both server and client VMs.
*/
private String outOfBoundsMsg(int index) {
return "Index: "+index+", Size: "+size;
}

ArrayList中的get方法

get方法相对而言就比较简单些:

 /**
* 根据下标获取指定位置的元素
*
* @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) {
//参数校验,若index比数组中元素的size还大,就抛异常
rangeCheck(index);
//返回对应的元素
return elementData(index);
}
 /**
* 在获取数组元素之前,需要进行数据校验
* 若传入的参数不在指定的数组索引范围内,就抛异常
*/
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

ArrayList中的remove(int index)方法

/**
* 移除列表中指定位置的元素,然后把移除元素后面的元素向左移动
*
* @param index 需要移除元素的索引值
* @return 移除的元素
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
//参数校验,校验index的值是否在索引所允许的范围内
rangeCheck(index);
//列表的修改次数加一
modCount++;
//先查出对应index位置出的元素,赋值给oldValue
E oldValue = elementData(index);
//把移除的元素后面的所有元素向左移动
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//把数组最后一个索引位置数据设置为null
elementData[--size] = null; // clear to let GC do its work
//返回移除的元素数据
return oldValue;
}

ArrayList中的remove(Object o)方法

    /**
*
* 从列表中删除指定元素的第一个匹配项
*
* @param o 需要移除的元素
* @return <tt>true</tt> if this list contained the specified element
*/
public boolean remove(Object o) {
//若元素是null,移除第一个匹配为null的元素
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
//若元素不为null,就移除第一个匹配到的元素
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
    /*
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
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; // clear to let GC do its work
}

注意:由于ArrayList是线程不安全的,所以不要在遍历中去对ArrayList做修改,否则会出现错误

ArrayList中的clear方法

/**
* 清除列表中的所有元素
*/
public void clear() {
//列表修改次数加一
modCount++;
// clear to let GC do its work 清除所有元素,垃圾回收
//通过遍历把所有元素设置为Null
for (int i = 0; i < size; i++)
elementData[i] = null; size = 0;
}

CopyOnWriteArrayList源码解析



通过上图可以看到copyOnWrite的实现方式,这种方式适用于读极多,写极少的情况,而且如果数据量巨大,在copy之后的一瞬间,内存占用增加,也会引发问题。CopyOnWriteArrayList是线程安全的。

CopyOnWriteArrayList变量

    /** 可重入锁 */
final transient ReentrantLock lock = new ReentrantLock(); /**
* 数组,只能通过getArray和setArray操作
*/
private transient volatile Object[] array; /**
* Gets the array. Non-private so as to also be accessible
* from CopyOnWriteArraySet class.
*/
final Object[] getArray() {
return array;
} /**
* Sets the array.
*/
final void setArray(Object[] a) {
array = a;
}

CopyOnWriteArrayList的构造函数

 /**
* 创建一个空的列表
*/
public CopyOnWriteArrayList() {
setArray(new Object[0]);
} /**
* 构造一个包含指定元素的列表集合,按照集合返回它们的顺序
*
* @param c 传入的集合
* @throws NullPointerException if the specified collection is null
*/
public CopyOnWriteArrayList(Collection<? extends E> c) {
Object[] elements;
if (c.getClass() == CopyOnWriteArrayList.class)
elements = ((CopyOnWriteArrayList<?>)c).getArray();
else {
elements = c.toArray();
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elements.getClass() != Object[].class)
elements = Arrays.copyOf(elements, elements.length, Object[].class);
}
setArray(elements);
} /**
* 创建包含给定数组副本的列表
*
* @param toCopyIn the array (a copy of this array is used as the
* internal array)
* @throws NullPointerException if the specified array is null
*/
public CopyOnWriteArrayList(E[] toCopyIn) {
setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}

CopyOnWriteArrayList的两个add方法

    /**
* 添加一个元素到列表的最后面
*
* @param e 需要添加到列表中的元素
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
//初始化锁
final ReentrantLock lock = this.lock;
//加锁
lock.lock();
try {
//获取数组中的元素
Object[] elements = getArray();
//获取数组的长度
int len = elements.length;
//把elements数组copy到长度为len + 1的newElements数组中,即新的数组长度增加1
Object[] newElements = Arrays.copyOf(elements, len + 1);
//然后把元素加到数组的末尾
newElements[len] = e;
//set数组的元素为添加之后的数组
setArray(newElements);
return true;
} finally {
//解锁
lock.unlock();
}
} /**
* 向指定的索引位置加入元素,加入位置后面的元素需要向右移位
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element) {
//初始化一个锁
final ReentrantLock lock = this.lock;
//加锁
lock.lock();
try {
//获取数组中的元素
Object[] elements = getArray();
//获取数组的长度
int len = elements.length;
//如果传入的索引值不在数组所允许的范围内,就抛异常
if (index > len || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+len);
Object[] newElements;
int numMoved = len - index;
//如果插入的索引大小和数组长度一样,那么直接插入到数组末尾
if (numMoved == 0)
newElements = Arrays.copyOf(elements, len + 1);
else {
//设置新数组的长度比之前的数组长度大一位,即可以插入一个元素
newElements = new Object[len + 1];
//先copy elements中index索引之前的元素到newElements
System.arraycopy(elements, 0, newElements, 0, index);
//再把elements中index之后的元素已经index中的元素copy到index右边,即右移
System.arraycopy(elements, index, newElements, index + 1,
numMoved);
}
//把元素放入到指定的索引处
newElements[index] = element;
//设置数组的引用为新的数组
setArray(newElements);
} finally {
//解锁
lock.unlock();
}
}

向指定索引处插入元素图解:

CopyOnWriteArrayList的两个get方法

get方法比较简单,不做赘述。

private E get(Object[] a, int index) {
return (E) a[index];
} /**
* {@inheritDoc}
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
return get(getArray(), index);
}

CopyOnWriteArrayList的remove方法

    /**
* 移除列表中指定索引位置的元素,并把后续的元素向左移动
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
//初始化锁
final ReentrantLock lock = this.lock;
//加锁
lock.lock();
try {
//获取数组中的元素
Object[] elements = getArray();
//获取数组的长度
int len = elements.length;
//获取索引处原先的旧值
E oldValue = get(elements, index);
int numMoved = len - index - 1;
//如果要移除的是最后一位直接移除
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
//新的数组,长度比旧的数组少一位
Object[] newElements = new Object[len - 1];
//同样的,//先copy elements中index索引之前的元素到newElements
System.arraycopy(elements, 0, newElements, 0, index);
//再把elements中index+1之后的元素向左移位
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
//返回移除后的元素
return oldValue;
} finally {
//解锁
lock.unlock();
}
}

在CopyOnWriteArrayList,移除的方法还有另外两个,实现的方法也都大同小异,都是先copy一份列表,然后加锁去操作,移除掉元素,然后再把数组的引用指向移除后的数组即可。

到此,arrayList和CopyOnWriteArrayList源码就结束了,上面的解释以及注释可能有错误或者不足的地方,希望指正,共同进步,多谢!

ArrayList、CopyOnWriteArrayList源码解析(JDK1.8)的更多相关文章

  1. Java集合-ArrayList源码解析-JDK1.8

    ◆ ArrayList简介 ◆ ArrayList 是一个数组队列,相当于 动态数组.与Java中的数组相比,它的容量能动态增长.它继承于AbstractList,实现了List, RandomAcc ...

  2. CopyOnWriteArrayList源码解析(1)

    此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 注:在看这篇文章之前,如果对ArrayList底层不清楚的话,建议先去看看ArrayList源码解析. ht ...

  3. 第三章 CopyOnWriteArrayList源码解析

    注:在看这篇文章之前,如果对ArrayList底层不清楚的话,建议先去看看ArrayList源码解析. http://www.cnblogs.com/java-zhao/p/5102342.html ...

  4. CopyOnWriteArrayList源码解析

    Java并发包提供了很多线程安全的集合,有了他们的存在,使得我们在多线程开发下,可以和单线程一样去编写代码,大大简化了多线程开发的难度,但是如果不知道其中的原理,可能会引发意想不到的问题,所以知道其中 ...

  5. (一)ArrayList集合源码解析

    一.ArrayList的集合特点 问题 结      论 ArrayList是否允许空 允许 ArrayList是否允许重复数据 允许 ArrayList是否有序 有序 ArrayList是否线程安全 ...

  6. 源码解析JDK1.8-HashMap链表成环的问题解决方案

    前言 上篇文章详解介绍了HashMap在JDK1.7版本中链表成环的原因,今天介绍下JDK1.8针对HashMap线程安全问题的解决方案. jdk1.8 扩容源码解析 public class Has ...

  7. ArrayList LinkedList源码解析

    在java中,集合这一数据结构应用广泛,应用最多的莫过于List接口下面的ArrayList和LinkedList; 我们先说List, public interface List<E> ...

  8. ArrayList类源码解析——ArrayList动态数组的实现细节(基于JDK8)

    一.基本概念 ArrayList是一个可以添加对象元素,并进行元素的修改查询删除等操作的容器类.ArrayList底层是由数组实现的,所以和数组一样可以根据索引对容器对象所包含的元素进行快速随机的查询 ...

  9. CopyOnWriteArrayList源码解析(2)

    此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 5.删除元素 public boolean remove(Object o) 使用方法: list.remo ...

随机推荐

  1. hive复杂数据类型的用法

    目录 1.简单描述 2.测试 1.简单描述 arrays: ARRAY<data_type> maps: MAP<primitive_type, data_type> stru ...

  2. Python2和Python3编码的区别

    Python2 python2中有两种储存变量的形式,第一种:Unicode:第二种:按照coding头来的. 假设python2用utf8存储x='中文',当你print(x)的时候,终端接收gbk ...

  3. ubuntu系统共享桌面的使用和配置

    内容转载自我的博客 目录 1. ubuntu共享桌面 2. 局域网登录远程桌面 2.1 ubuntu使用remmina登录远程桌面 2.2 在windows登录远程桌面 2.3 Android使用RD ...

  4. COM技术中的VARIANT and VARIANTARG

    VARIANT and VARIANTARG Use VARIANTARG to describe arguments passed within DISPPARAMS, and VARIANT to ...

  5. Cassandra数据操作管理工具tableplus

    一.概述 Cassandra是一个NoSQL数据库,具有类SQL CQL入口,基本语法与SQL保持一致.其实笔者认为 Cassandra的自带的cqlsh已经满足本的需求:如: 但是用习惯了数据库操作 ...

  6. 后端程序员之路 49、SSDB

    正如Redis似乎是为替换memcached一样,SSSB是一个国人开发的旨在替换Redis的kv数据库. SSDB - 高性能的支持丰富数据结构的 NoSQL 数据库, 替代 Redishttp:/ ...

  7. 001-深度学习Pytorch环境搭建(Anaconda , PyCharm导入)

    001-深度学习Pytorch环境搭建(Anaconda , PyCharm导入) 在开始搭建之前我们先说一下本次主要安装的东西有哪些. anaconda 3:第三方包管理软件. 这个玩意可以看作是一 ...

  8. Hi3559AV100 SDK的详细安装过程及问题解决方法

    下面给出Hi3559AV100 SDK的安装的详细步骤(一些注意事项可以参照我之前写的随笔-<Hi3519 SDK搭建.问题总结及yolov3 RFCN的运行结果与测试 >): 1.开发环 ...

  9. Vue.js 可排序列表 (Sortable & Searchable Tables) 组件

    可排序表格 (Sortable & Searchable Tables) 在网页和表单设计中非常常用.用户可以通过点击表头对将表格以该列做顺序或降序排列,也可以利用 Search Box 对表 ...

  10. mpvue 开发微信小程序搭建项目

    首先 mpvue 是一款基于vue的框架,mpvue 修改了 Vue.js 的 runtime 和 compile 实现,可以运行在小程序的环境中. 第一步:安装 vue-cli vue-cli是vu ...