【Java集合】试读ArrayList源码
ArrayList简介
ArrayList 是一个数组队列,相当于 动态数组。与Java中的数组相比,它的容量能动态增长。它继承于AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口。
ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
ArrayList 实现了RandmoAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。稍后,我们会比较List的“快速随机访问”和“通过Iterator迭代器访问”的效率。
ArrayList 实现了Cloneable接口,即覆盖了函数clone(),能被克隆。
ArrayList 实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。
(前面都是复制粘贴的图和文字,大家大概理解一下,下面进入正题)
首先我们要明白一点,ArrayList的本质就是数组。
所以我们的源码,首先从ArrayList中维护的两个数组变量开始,它们是ArrayList的核心:
//该数组缓存者集合中的元素,集合的容量就是该数组的长度,elementData用transient修饰,说明在序列化时,数组elementData不在序列化范围内。
private transient Object[] elementData; //集合的大小 (集合中元素的实际数量)
private int size;
接下来看一下ArrayList的构造器:
//ArrayList带容量大小的构造函数,容量大于0,则生成对应容量的数组,容量等于0,则把空数组EMPTY_ELEMENTDATA赋给它,容量小于0则报错
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无参构造函数,则把空数组DEFAULTCAPACIT_EMPTY_ELEMENTDATA赋给它,但是这个空数组拥有一个默认的数组容量
public ArrayList() {
this.elementData = DEFAULTCAPACIT_EMPTY_ELEMENTDATA;
} // 创建一个包含传入collection的ArrayList,若这个Collection为空,则把空数组EMPTY_ELEMENTDATA赋给它;若不为空,通过Collection的toArray()方法生成一个数组
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
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 {
// replace with empty array.7
this.elementData = EMPTY_ELEMENTDATA;
}
}
这时候问题出现了,里面的EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA是什么鬼?都是空数组有什么区别呢?
//默认数组容量10
private static final int DEFAULT_CAPACITY = 10; //空的数组,数组大小为0
private static final Object[] EMPTY_ELEMENTDATA = {}; //空的数组,但是数组大小为默认数组大小10
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
这个所谓的数组容量有什么用呢?
我们接着看下常用的get() 、set()和add()这几个方法:
//封装的获取index位置的元素值方法,避免强转
@SuppressWarnings("unchecked")
E elementData(int index) {
return (E) elementData[index];
} // 获取index位置的元素值
public E get(int index) {
rangeCheck(index); return elementData(index);
} // 设置index位置的值为element
public E set(int index, E element) {
rangeCheck(index); E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
} // 将e添加到ArrayList中
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
} // 将e添加到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++;
}
get()方法和set()方法比较简单,都是判断一下是否越界,然后根据数组索引来找值。判断越界(包含add方法的越界判断)的代码如下:
//判断是否index是否过界
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
} //判断是否index是否过界,用于add和addAll方法
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
add()方法中有一个System.arraycopy(),主要用于数组内的数的复制。由于源代码不是Java写的,我这边只稍微写一下用法:
System提供了一个静态方法arraycopy(),我们可以使用它来实现数组之间的复制。其函数原型是: public static void arraycopy(Object src,int srcPos,Object dest,int destPos,int length) src:源数组;
srcPos:源数组要复制的起始位置;
dest:目的数组;
destPos:目的数组放置的起始位置;
length:复制的长度。
注意:src and dest都必须是同类型或者可以进行转换类型的数组.
有趣的是这个函数可以实现自己到自己复制,比如:
int[] fun ={0,1,2,3,4,5,6};
System.arraycopy(fun,0,fun,3,3);
则结果为:{0,1,2,0,1,2,6};
而之前构造器中的Arrays.copyOf()方法,也有调用这个system.arraycopy方法,从而实现数组的扩容(当然还有Arrays.copyOf()方法还有其他功能,此处姑且不谈):
System.arraycopy(original, 0, copy, 0,Math.min(original.length, newLength));
我们同时发现发现add方法中有一个ensureCapacityInternal()方法,这个就涉及到了我们刚刚说到的容量,我们接下来看一下源码: // 确定ArrarList的容量。
//确定ArrarList的容量。
private void ensureCapacityInternal(int minCapacity) {
//若数组为无参构造形成的数组,则minCapacity为传入值和默认值中的最大值
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
} ensureExplicitCapacity(minCapacity);
} private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//若传入值比当前的数组长度大,则要增加数组长度
if (minCapacity - elementData.length > 0)
grow(minCapacity);
} //不出现OutOfMemory的最大数组容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; //增加数组容量
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
//新的容量为原来的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//把数组容量变大
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;
}
ensureCapacityInternal()方法就是确认ArrayList的容量是否满足要求。
若当前数组是DEFAULTCAPACITY_EMPTY_ELEMENTDATA,则取传入的容量和默认容量中的较大值,来进行后面的增加容量操作。
也就时说,默认容量较大时,把默认容量认为是当前数组的容量。
同样,有另外一个对外的公共方法ensureCapacity(),也是确认ArrayList容量的功能,在是否为默认空间的时候都进行了一次判断,然后才调用ensureCapacity()方法:
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
//数组不是默认的容量
? 0
//数组容量比默认的大
: DEFAULT_CAPACITY; if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
最后增加数据的部分看一下和addAll方法的源码:
// 将集合c追加到ArrayList中,把容量新增一个collection的长度,然后在后面新增collection中的元素
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的长度,从index位置开始,将集合c添加到ArrayList
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;
}
我们再看删除的方法:
// 删除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;
} // 删除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;
} // 快速删除第index个元素
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,将全部的元素设为null
public void clear() {
modCount++; // clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null; size = 0;
}
可以看到,每在中间删除或者增加一个元素,旁边的元素都要跟着移动一遍, 非常麻烦。
然后看元素的搜索:
// 获取index位置的元素值
public E get(int index) {
rangeCheck(index); return elementData(index);
} // 返回ArrayList是否包含Object(o)
public boolean contains(Object o) {
return indexOf(o) >= 0;
} // 正向查找,返回元素的索引值
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;
} // 反向查找,返回元素的索引值
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
接下来看一些里面剩下来的简单常用的方法:
// 返回ArrayList的实际大小
public int size() {
return size;
} // 返回ArrayList是否为空
public boolean isEmpty() {
return size == 0;
} // 将当前容量值设为实际元素个数
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
} // 克隆函数
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);
}
}
最后稍微看一下里面的迭代器方法,代码如下:
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount; public boolean hasNext() {
return cursor != size;
} @SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
} public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification(); try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
} @Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
} final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
里面的具体方法不具体展开讲,我们重点关注一下checkForComodification()方法。
眼尖的朋友可能已经发现了,在前面的add(),addAll(),clear等改变数组值的方法中,都有一个操作
modCount++;
这个modCount是什么?这个操作又代表着什么呢?
事实上,这个数是定义在ArrayList的父类AbstractList中的:
protected transient int modCount = 0;
这个数的用处我们结合checkForComodification()方法来看,:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
这个方法就是判断,当modCount不等于expectedModCount,则抛出异常。
从Itr类中,我们知道 expectedModCount 在创建Itr对象时,被赋值为 modCount。通过Itr,我们知道:expectedModCount不可能被修改为不等于 modCount。所以,当且仅当modCount被修改时,会抛出异常。
这种异常的出现,源自于java集合(Collection)中的一种错误机制:
fail-fast 机制
当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。
例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。
具体到代码中:
当多个线程对同一个集合进行操作的时候,某线程访问集合的过程中,该集合的内容被其他线程所改变(即其它线程通过add、remove、clear等方法,改变了modCount的值);这时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。
所以这个modCount就是用来判断集合的内容是否在iterator遍历过程中被改变的一个参数。
总结一下:
1.ArrayList是用数组实现的,所以查找元素时直接通过Index查找,比较快;可是新增或者删除元素就慢了,要大量的移动数组里的数。
2.ArrayList新增数据的时候,可能会引起容量的扩充,但是删除数据却不会造成容量的减小,只能用trimToSize()方法来把数组容量减小为实际元素个数。
3.扩充容量的方法ensureExplicitCapacity。新增操作或者是客户端主动调用ensureCapacity()方法时,如果容量不足了,就设置新的容量为旧的容量的1.5倍,如果设置后的新容量还不够,则直接新容量设置为传入的参数,而后用Arrays.copyof()方法将元素拷贝到新的数组。容量经常变化,导致元素多次拷贝的话,非常耗时
4.modCount就是用来判断集合的内容是否在iterator遍历过程中被改变,若被改变了会产生fail-fast事件。
【Java集合】试读ArrayList源码的更多相关文章
- java集合系列之ArrayList源码分析
java集合系列之ArrayList源码分析(基于jdk1.8) ArrayList简介 ArrayList时List接口的一个非常重要的实现子类,它的底层是通过动态数组实现的,因此它具备查询速度快, ...
- 【java集合总结】-- ArrayList源码解析
一.前言 要想深入的了解集合就必须要通过分析源码来了解它,那如何来看源码,要看什么东西呢?主要从三个方面: 1.看继承结构 看这个类的继承结构,处于一个什么位置,不需要背记,有个大概的感觉就可以,我自 ...
- Java集合系列[1]----ArrayList源码分析
本篇分析ArrayList的源码,在分析之前先跟大家谈一谈数组.数组可能是我们最早接触到的数据结构之一,它是在内存中划分出一块连续的地址空间用来进行元素的存储,由于它直接操作内存,所以数组的性能要比集 ...
- java集合系列之LinkedList源码分析
java集合系列之LinkedList源码分析 LinkedList数据结构简介 LinkedList底层是通过双端双向链表实现的,其基本数据结构如下,每一个节点类为Node对象,每个Node节点包含 ...
- 数据结构与算法系列2 线性表 使用java实现动态数组+ArrayList源码详解
数据结构与算法系列2 线性表 使用java实现动态数组+ArrayList源码详解 对数组有不了解的可以先看看我的另一篇文章,那篇文章对数组有很多详细的解析,而本篇文章则着重讲动态数组,另一篇文章链接 ...
- Java集合系列[4]----LinkedHashMap源码分析
这篇文章我们开始分析LinkedHashMap的源码,LinkedHashMap继承了HashMap,也就是说LinkedHashMap是在HashMap的基础上扩展而来的,因此在看LinkedHas ...
- java集合系列之HashMap源码
java集合系列之HashMap源码 HashMap的源码可真不好消化!!! 首先简单介绍一下HashMap集合的特点.HashMap存放键值对,键值对封装在Node(代码如下,比较简单,不再介绍)节 ...
- Java中的容器(集合)之ArrayList源码解析
1.ArrayList源码解析 源码解析: 如下源码来自JDK8(如需查看ArrayList扩容源码解析请跳转至<Java中的容器(集合)>第十条):. package java.util ...
- Java集合 - List介绍及源码解析
(源码版本为 JDK 8) 集合类在java.util包中,类型大体可以分为3种:Set.List.Map. JAVA 集合关系(简图) (图片来源网络) List集合和Set集合都是继承Collec ...
随机推荐
- 关于阿里云的远程连接和轻型桌面(xfce4)安装
这里用的阿里云服务器是轻量应用服务器 先通过网页端的远程连接进入服务器,然后 安装xfce4 (1)先安装更新:apt-get update. (2)安装xrdp:输入apt-get install ...
- tensorflow 读取训练集文件 from Hadoop
1.代码配置 filename_queue = tf.train.string_input_producer([ "hdfs://namenode:8020/path/to/file1.cs ...
- [极客大挑战 2019]Http
0x00知识点 了解HTTP协议,使用bp伪造. 0x01 解题 首先查看源代码,找到Secret.php 访问 使用bp查看 提示我们需要来自该网址,直接改header头信息即可,我们可以通过使用r ...
- Docker MongoDB 集群搭建
简单地在Docker环境上搭建一个无认证的MongoDB集群.1.本文使用的容器集群角色 ContainerName IP:portConfig Server cfg_1 10.1.1.2:27 ...
- js 输出语句document.write()及动态改变元素中内容innerHTML的使用
操作 HTML 元素 如需从 JavaScript 访问某个 HTML 元素,您可以使用 document.getElementById(id) 方法. 请使用 "id" 属性来标 ...
- Python中的encode和decode
原文地址:http://www.cnblogs.com/tingyugetc/p/5727383.html 1.Python3中对文本和二进制数据进行了比较清晰的区分,文本总是 unicode ,由 ...
- bzoj3218 a+b Problem(最小割+主席树优化建边)
由于6.22博主要学测,大半时间学文化课,近期刷题量&写题解的数量会急剧下降. 这题出得挺经典的,首先一眼最小割,考虑朴素的做法:与S联通表示白色,与T联通表示黑色,S向i连流量为w[i]的边 ...
- feign声明式客户端
参考地址: https://blog.csdn.net/qq_30643885/article/details/85341275 Feign是一个声明式的Web服务客户端,使得编写Web服务客户端变得 ...
- jenkins pipeline 之 deploy k8s 环境并发送邮件通知
项目中有更新代码之后触发jenkins任务,部署好之后并发送邮件给发开人员 #!/usr/bin/env groovy Date date = new Date()def time = date.fo ...
- Python 处理图片 -- pillow库
pip install pillow 基本使用 from PIL import Image # new 创建一张图片 im1 = Image.new('RGB', (500, 300), (50, 1 ...