本文将从ArrayList类的存储结构、初始化、增删数据、扩容处理以及元素迭代等几个方面,分析该类常用方法的源码。

数据存储设计

该类用一个Object类型的数组存储容器的元素。对于容量为空的情况,提供了两个成员变量来表示。

// 用于存储容器元素的数组,数组长度不小于容器内元素个数
transient Object[] elementData; // 容器的大小,反映的是容器内实时元素个数,不超过数组长度
private int size; // 容器为空的实例
private static final Object[] EMPTY_ELEMENTDATA = {}; // 默认构造的实例,用于展示加入第一个元素后容器膨胀的过程
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

初始化

该类提供了三个初始化方法,如下所示:

// 基本构造方法
public ArrayList() {
// 数组初始化为长度为0的空数组
this.elementData = DEFAULTCAPACITY_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);
}
} // 通过已有集合进行实例化的构造方法
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) { // c.toArray might (incorrectly) not return Object[] (see 6260652)
// 如果集合转换得到的数组长度不为0且数组元素类型不为Object类型,则将其转换为Object类型的数组
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}

增删数据

单元素插入方法,这里没有对插入元素进行非null限制,说明ArrayList容器是可以插入null的。

/* 在已有元素末尾增加元素 */
public boolean add(E e) {
/* 确保数组的大小至少有size+1的长度,如果小于会对原数组进行扩容 */
ensureCapacityInternal(size + 1); // Increments modCount!! /* 在索引为size处添加元素e,同时size+1 */
elementData[size++] = e;
return true;
} /* 在数组索引为index处插入元素 */
public void add(int index, E element) {
/* 检查索引位置,index的范围为[0,size] */
rangeCheckForAdd(index); /* 确保数组的大小至少有size+1的长度,如果小于会对原数组进行扩容 */
ensureCapacityInternal(size + 1); // Increments modCount!! /* 将原来index处及之后的元素全部向后移一位 */
System.arraycopy(elementData, index, elementData, index + 1,size - index); /* 将元素element放入index处 */
elementData[index] = element; /* 容器内元素个数+1 */
size++;
}

集合插入方法

// 在已有元素末尾增加集合中所有元素
public boolean addAll(Collection<? extends E> c) {
// 将集合转化为Object数组
Object[] a = c.toArray();
int numNew = a.length;
// 确保数组的大小至少有size+numNew的长度,如果小于会对原数组进行扩容
ensureCapacityInternal(size + numNew); // Increments modCount
// 将数组a内元素拷贝到数组elementData末尾
System.arraycopy(a, 0, elementData, size, numNew);
// 数组大小增加numNew
size += numNew;
return numNew != 0;
} // 在索引index处插入集合中所有元素
public boolean addAll(int index, Collection<? extends E> c) {
// 检查索引位置,index的范围为[0,size]
rangeCheckForAdd(index); Object[] a = c.toArray();
int numNew = a.length; // 确保数组的大小至少有size+numNew的长度,如果小于会对原数组进行扩容
ensureCapacityInternal(size + numNew); // Increments modCount // 要移动的元素长度
int numMoved = size - index;
// 如果插入位置不在size处,则需要把原数组[index,size-1]范围内元素移动numMoved长度
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved); System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}

修改元素方法,只提供了修改指定索引下标元素的方法

/* 替换指定索引处元素 */
public E set(int index, E element) {
/* 检查索引位置,index的范围只能在[0,size-1]内 */
rangeCheck(index); /* 替换index处元素并返回原值 */
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}

单元素删除元素方法,提供了根据索引删除和查找对象进行删除的方法

// 删除指定索引处元素
public E remove(int index) {
// 检查索引范围,只能在[0,size-1]范围内
rangeCheck(index); modCount++;
// 取出index处待删除元素
E oldValue = elementData(index); // 要移动的元素长度
int numMoved = size - index - 1;
// 如果index≠size-1,将index后的元素全部往前移动一位
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
// size数量先减一
// 因为删除了一个元素,数组最后一个元素的引用置为null,让垃圾收集器去回收元素对象
elementData[--size] = null; // clear to let GC do its work // 返回删除的元素
return oldValue;
} // 查找对象并删除元素
public boolean remove(Object o) {
// 这里从下标0开始遍历数组,只删除遇到的第一个相同元素
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++)
// 因为数组元素可以为null,所以需要用o.equals()以防止NullPointException
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
} // 跳过边界检查删除index索引处元素,确保index处于[0,size-1]范围内,并且不范围删除的元素
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
}

批量删除方法

// 从本集合中删除与集合C的交集
public boolean removeAll(Collection<?> c) {
// 确保集合C非null,否则抛出NullPointException
Objects.requireNonNull(c);
return batchRemove(c, false);
} // complement为false表示批量删除与C的交集元素,complement为true表示只留下和C的交集元素,其它全部删除
// 以下说明均采用complement为false的情况,即删除交集元素
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData; // 这里采用了双指针的思想,r为当前遍历到的位置,w指向将要写入元素的位置
int r = 0, w = 0; // 是否有元素被删除,有则为true,无则为false
boolean modified = false;
try {
// 遍历elementDate数组,如果不在集合c中,则写入w处,并将w右移一位
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
// 如果没有遍历完,即c.contains()抛出了异常或错误,需要将r处及其之后的元素复制到w之后
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
// w向右移动
w += size - r;
}
// 如果w≠size,说明有交集元素被删除
if (w != size) {
// clear to let GC do its work
// 将w处及之后的引用置为null,让gc自己回收
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w; // 有元素被删除,置为true
modified = true;
}
}
return modified;
}

扩容处理

每次在增加元素时,都需要确保数组的长度要大于容器内元素的个数。比如在add方法中。

public boolean add(E e) {
// 使用ensureCapacityInternal方法确保数组的大小不小于插入元素后的元素个数
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}

我们看一下该方法具体的实现,这里扩容时会判断元素组长度的1.5倍是否满足输入最小长度要求,满足的话会按1.5倍容量定义新数组并复制元素到新数组,不满足则按输入最小长度扩容。

// 确保数组长度不低于minCapacity
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
} private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 如果数组为空集,则取默认大小(10)和传入参数中较大的一个
// 也就是说最小容量为10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
} private void ensureExplicitCapacity(int minCapacity) {
modCount++; // overflow-conscious code
// 如果当前数组长度未达到输入大小,则进行扩容操作
if (minCapacity - elementData.length > 0)
grow(minCapacity);
} // 扩容操作
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// 原长度的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果传入参数超过数组原长1.5倍,则设置新长度为传入参数,否则为原长1.5倍
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 如果新长度超过设定最大长度(Integer.MAX_VALUE-8),则获取更大容量
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) {
// 如果传入参数超过int最大值,表示扩容后的数组长度溢出了,抛出OOM Error
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
// 如果新长度超过设定最大长度(Integer.MAX_VALUE-8)且不超过Integer.MAX_VALUE,则设置为Integer.MAX_VALUE
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}

元素迭代

迭代的使用和其它容器类似,通过iterator()方法返回一个Iterator实例,通过调用该实例的hashNext()方法和next()方法进行迭代。

ArrayList类内部也定义了实现了Iterator接口的内部类,iterator()方法正是返回了该类的实例。实际上该类内部定义了多个Iterator的实现类,用以不同的迭代场景,如下所示:

/**
* An optimized version of AbstractList.Itr
*/
private class Itr implements Iterator<E>{
...
} /**
* An optimized version of AbstractList.ListItr
*/
private class ListItr extends Itr implements ListIterator<E> {
...
}

这里以Itr类为例分析hashNext()方法和next()方法具体实现

private class Itr implements Iterator<E> {
// 下一个元素的索引,默认值为0
int cursor; // index of next element to return
// 上一个元素的索引
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount; Itr() {} // 是否有下一个元素
public boolean hasNext() {
// 索引未到达size处则为true
return cursor != size;
} // 返回下一个元素(索引从-1开始)
@SuppressWarnings("unchecked")
public E next() {
// 检查是否有修改(增加或删除元素),修改了的话会抛出ConcurrentModificationException
// 这里也说明ArrayList类是不支持并发操作的
checkForComodification(); // 如果当前索引>=size,说明越界了,抛出异常
int i = cursor;
if (i >= size)
throw new NoSuchElementException(); // 如果当前索引超过数组长度,说明有其他线程增加元素对数组进行了扩容操作
// 抛出并发修改异常
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException(); // 如果一切正常,索引+1并返回索引位置元素
cursor = i + 1;
return (E) elementData[lastRet = i];
} // 删除当前遍历到的元素,(至少要调用一次next()方法)
public void remove() {
// 如果lastRet值<0,说明索引还在-1,一次next()方法都没调用,抛出异常
if (lastRet < 0)
throw new IllegalStateException(); // 检查是否有并发修改,有修改抛出异常
checkForComodification(); // 从list中删除下标为lastRet的元素,并更新下标索引的值
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
...
}

ArrayList方法源码分析的更多相关文章

  1. Java split方法源码分析

    Java split方法源码分析 public String[] split(CharSequence input [, int limit]) { int index = 0; // 指针 bool ...

  2. ArrayList迭代器源码分析

    集合的遍历 Java集合框架中容器有很多种类,如下图中: 对于有索引的List集合可以通过for循环遍历集合: List<String> list = new ArrayList<& ...

  3. ArrayList的源码分析

    在项目中经常会用到list集合来存储数据,而其中ArrayList是用的最多的的一个集合,这篇博文主要简单介绍ArrayList的源码分析,基于JDK1.7: 这里主要介绍 集合 的属性,构造器,和方 ...

  4. invalidate和requestLayout方法源码分析

    invalidate方法源码分析 在之前分析View的绘制流程中,最后都有调用一个叫invalidate的方法,这个方法是啥玩意?我们来看一下View类中invalidate系列方法的源码(ViewG ...

  5. Linq分组操作之GroupBy,GroupJoin扩展方法源码分析

    Linq分组操作之GroupBy,GroupJoin扩展方法源码分析 一. GroupBy 解释: 根据指定的键选择器函数对序列中的元素进行分组,并且从每个组及其键中创建结果值. 查询表达式: var ...

  6. 【Java】NIO中Selector的select方法源码分析

    该篇博客的有些内容和在之前介绍过了,在这里再次涉及到的就不详细说了,如果有不理解请看[Java]NIO中Channel的注册源码分析, [Java]NIO中Selector的创建源码分析 Select ...

  7. jQuery实现DOM加载方法源码分析

    传统的判断dom加载的方法 使用 dom0级 onload事件来进行触发所有浏览器都支持在最初是很流行的写法 我们都熟悉这种写法: window.onload=function(){ ... }  但 ...

  8. jQuery.extend()方法和jQuery.fn.extend()方法源码分析

    这两个方法用的是相同的代码,一个用于给jQuery对象或者普通对象合并属性和方法一个是针对jQuery对象的实例,对于基本用法举几个例子: html代码如下: <!doctype html> ...

  9. jQuery.clean()方法源码分析(一)

    在jQuery 1.7.1中调用jQuery.clean()方法的地方有三处,第一次就是在我之前的随笔分析jQuery.buildFramgment()方法里面的,其实还是构造函数的一部分,在处理诸如 ...

随机推荐

  1. 详解CURL状态码,最全的代码列表

    curl是一个强大的命令,其中我们经常使用的通过curl 加地址和端口号,判断目标链接状态,下面列出了curl 返回的状态码. 举例: [root@k8s-master1 ~]# curl k8s-m ...

  2. html5 video/audio 标签

    一般来说推荐使用,第三方的媒体播放库[例如 videojs 等],这些库的ui界面,js控制都已写好,功能也扩展的比较丰富 ,直接参考文档使用更加的高效! 这里还是 记录一下 html 原生 vide ...

  3. JavaWeb 补充(Json)

    HTML DOM alert() 方法 定义和用法 alert() 方法用于显示带有一条指定消息和一个 OK 按钮的警告框. 参数 描述 message 要在 window 上弹出的对话框中显示的纯文 ...

  4. 【笔记】《Redis设计与实现》chapter13 客户端

    服务器为每个客户端建立相应的redis.h/redisClient结构,这个结构保存了客户端当前的状态信息 客户端的套接字描述符 客户端的名字 客户端的标志值 只想客户端正在使用的数据库的指针,以及该 ...

  5. redhat7.6 更换 centos7 YUM

    使用yum 遇到如下错误. This system is not registered to Red Hat Subscription Management. You can use subscrip ...

  6. 手写Spring DI依赖注入,嘿,你的益达!

    目录 提前实例化单例Bean DI分析 DI的实现 构造参数依赖 一:定义分析 二:定义一个类BeanReference 三:BeanDefinition接口及其实现类 四:DefaultBeanFa ...

  7. 一.文件处理和json模块(容易混淆知识点即用法)

    一.文件处理 注意点1: 打开文件包含两部分资源:操作系统级打开得文件+应用程序的变量. 在操作完毕一个文件时,必须把该文件的两部分资源一个不落的回收 如下: f.close() #回收操作系统级打开 ...

  8. CentOS7 常用基础操作

    系统目录结构了解 CentOS系统中没有磁盘的概念,一切皆文件,/目录下的的一个个文件夹目录就相当于磁盘了,这里简单记录几个常用的目录以及对应的作用: dev:Linux一切皆文件,包括硬件也进行了文 ...

  9. Centos7 安装RabbitMQ及配置(亲测)

    Rabbit MQ Linux 安装 Centos7 如果是重装先把erlang和rabbitmq卸载干净,不然还会重装失败<rabbitmq和erlang卸载> 记得删除/var/lib ...

  10. IE 兼容问题笔记

    IE 兼容问题笔记 解决IE11兼容HTML5 设置 document.body的一些用法以及js中的常见问题 flex布局浏览器兼容处理 ie8, ie9 css3 media媒体查询器用法总结 c ...