最近突然想对学过的知识进行总结概述,看过很多写Java集合框架的资料,现在自己打算根据自己的水平写一下ArrayList的源码详解,写得可能不是太好,望海涵。
目录
(一)ArrayList概述
(二)ArrayList细节讲解
(三)ArrayList源码中的成员变量
(四)ArrayList中的构造函数
1.带初始化容量参数的构造器
2.无参构造器
3.传递一个Collection集合的构造器
(五)ArrayList中重要的方法详解
1、容量方法
2.add方法
3.移除元素操作
4.clear清空ArrayList的元素
5.addAll方法
6.ArrayList中检查越界的方法
7.removeAll 批量删除元素操作
8.ArrayList中构建Interatory方法
(六)参考文献
参考文献:
ArrayList底层是一个动态数组,可以根据插入值的长度大小进行扩容操作。ArrayList实现了RandomAccess接口实现此接口的作用是随机访问,实现了Cloneable接口 作用是支持复制,同时也实现了Serialzable接口可以进行序列化和反序列化操作
ArrayList并不是一个线程安全的,所以我们一般建议在单线程环境下。其中ArrayList源码中有一个成员变量modCount是为了保证我们在使用迭代器的时候防止因为多线程环境下的插入删除引起的数据不一致的问题,各位有兴趣的可以去试试在循环的过程中对正在循环的ArrayList集合进行删除,你会发现结果会出乎各位的意料的。
//默认的容量
private static final int DEFAULT_CAPACITY = 10;
//一个空的元素集合
private static final Object[] EMPTY_ELEMENTDATA = {};
//与EMPTYT——ELEMENTDATA做对比 当第一次插入元素时知道自己需要膨胀多少即扩容多大
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//ArrayList真实保存数据的数组 默认采用的是Object来保存值
transient Object[] elementData; // non-private to simplify nested class access
//ArrayList的大小
private int size;
/**
* 初始化容量构造器
* 如果参数>0则直接new一个Object容量的数组
* 如果参数==0 会将上诉的静态EMPTY_ELEMENTDATA属性赋值给elementData,不会产生新的元素数组
* 如果小于0 则抛出一个非法容量的异常
*
* @param initialCapacity 容量大小
*/
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);
}
}
/**
* 无参构造求 默认将DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空数组赋值给elementData
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* 传递一个集合给ArrayList
*
* @param c
*/
public ArrayList(Collection extends E> c) {
//将集合转换成数组赋值给elementData
elementData = c.toArray();
//如果elementData的长度不为0 说明集合中是有元素的
if ((size = elementData.length) != 0) {
//c.toArray可能会造成一点误差会不返回Object的数组 所以我们需要进行判断一下防止这种错误的发生
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
//为0依旧还是一个静态的空数组引用
this.elementData = EMPTY_ELEMENTDATA;
}
}
//供用户调用的方法 用户可以设置容量大小
public void ensureCapacity(int minCapacity) {
//最小扩容容量 判断是不是空的ArrayList 如果是的话最小扩容为10 否则扩容为0
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
? 0
: DEFAULT_CAPACITY;
//如果用户指定的最小容量>最小扩容容量 则以用户指定标准为标准
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
//私有方法 明确ArrayList的容量 提供给本类使用
private void ensureCapacityInternal(int minCapacity) {
// 若 elementData == {},则取 minCapacity 为 默认容量和参数 minCapacity 之间的最大值
// 注:ensureCapacity() 是提供给用户使用的方法,在 ArrayList 的实现中并没有使用
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
ensureCapacityInternal方法的是ArrayList中私有的明确容量方法,改方法能够使得ArrayList的容量能够接近真实容量的大小,不至于浪费过大的空间。
例如:ArrayList list = new ArrayList<>()现在我们构造了一个ArrayList的集合,ArrayList在构建的时候如果是无参构造器默认采用的空间是10 如果我们向其中插入一个元素那真实的容量就是1 ,如果数据过于庞大则会导致资源的过度浪费,后面我们将ArrayList的扩容。
下面这个方式是ensureCapacityInternal方法里面调用的内部方法 此方法是讲解如何内部优化的方法。
/**
* 私有方法:明确 ArrayList 的容量
* - 用于内部优化,保证空间资源不被浪费:尤其在 add() 方法添加时起效
*
* @param minCapacity 指定的最小容量
*/
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//如果最小的容量比elementData.length的容量大的话则扩容 防止代码溢出
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
如果最小的容量都比ArrayList的容量大的话 那我们就需要对ArrayList进行扩容 防止代码溢出。下面试是ArrayList中的扩容操作。
/**
* 数组缓冲区最大存储容量
* - 一些 VM 会在一个数组中存储某些数据--->为什么要减去 8 的原因
* - 尝试分配这个最大存储容量,可能会导致 OutOfMemoryError(当该值 > VM 的限制时)
*/
/*为什么这里要减8呢?*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
/*
使用方法 数组扩容 用来确保Arraylist至少要存储minCapacity个元素
扩容计算公式 newCapacity = oldCapacity + (oldCapacity>>1) 扩容当前容量的1.5倍
* */
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果新的数组容量-最小数组容量<0 则说明最小的容量大于了新的容量则将新的容量等于最小容量
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);
}
/**
* 私有方法 : 大容量分配 最大分配Integer.MAX_VALUE
*
* @param minCapacity
* @return
*/
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
阅读的时候我们发现数据缓冲区的MAX_ARRAY_SIZE =Integer.MAX_VALUE-8这里我们为什么需要减8呢?
具体可以看下面这个链接:
https://stackoverflow.com/questions/35756277/why-the-maximum-array-size-of-arraylist-is-integer-max-value-8
根据上面我们明白了ArrayList的扩容原理 接下来我们讲解ArrayList的常用具体方法实现原理
/**
* 向ArrayList中添加一个元素
* @param e 元素
* @return 返回添加是否成功
*/
public boolean add(E e) {
//明确Arraylistde容量 保存多少元素就存储多少元素 节省空间
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
/**
* 向目标索引位置插入值
* @param index
* @param element
*/
public void add(int index, E element) {
//检查索引越界
rangeCheckForAdd(index);
//明确ArrayList的容量
ensureCapacityInternal(size + 1); // Increments modCount!!
//重点 第一个参数是复制的数组 ,第二个参数是要复制的数组是从哪里开始的,第三个是参数是复制到哪里
//第四个参数是复制到的数组从第几个开始 最后一个是复制的长度
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
添加一个元素的时候 我们ArrayList底层会代用自己定义的ensureCapacityInternal方法来明确我此时到底需要多少的真正容量,而不是进行一味的扩容操作浪费空间。
/**
* 移除某个索引位置的元素
* @param index
* @return
*/
public E remove(int index) {
//检查索引越界
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
//需要移动的距离 判断是否是最后一个元素
//如果是最后一个元素就直接让最后一个元素的值为null
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;
}
/**
* 移除元素 通过循环匹配来确定
* @param o
* @return
*/
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;
}
上述移除元素中,第二张代码图我们看见循环体中调用了一个fastRemove(index)的方法 我们接着往源码下面走会发现:
/**
* 私有方法 跳过检查 执行快速删除
* @param 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中的所有元素
*/
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
/**
* 添加一个集合元素
* @param c
* @return
*/
public boolean addAll(Collection extends E> c) {
//把集合转换成数组
Object[] a = c.toArray();
//拿到数组的个数
int numNew = a.length;
//明确ArrayList的大小
ensureCapacityInternal(size + numNew); // Increments modCount
//进行复制 从elementDate的末尾开始增加元素
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
//从某个索引位置复制进入集合元素
public boolean addAll(int index, Collection extends E> c) {
//检查索引越界
rangeCheckForAdd(index);
Object[] a = c.toArray();
//新的元素集合长度
int numNew = a.length;
//明确ArrayList大小
ensureCapacityInternal(size + numNew); // Increments modCount
//需要移动的距离
int numMoved = size - index;
//判断是否需要移动索引位置 如果需要移动位置 则先将索引位置以后的元素加载elementDate的末尾 腾出集合大小的空间出来
//如果numMoved=0说明是在ArrayList的末尾处进行插入的
//所以我们无需进行腾出位置操作 直接往通过System.arraycopy方法往集合元素结尾插入元素即可
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 rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
/**
* 添加时检查索引是否越界
* @param index
*/
private void rangeCheckForAdd(int index) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
/**
* 构建数组越界的错误信息
* @param index
* @return
*/
private String outOfBoundsMsg(int index) {
return "Index: " + index + ", Size: " + size;
}
/**
* 移除指定集合的元素
* @param c
* @return
*/
public boolean removeAll(Collection> c) {
Objects.requireNonNull(c);//判断集合是否为空,如果为空报NullPointerException
//批量移除c集合的元素,第二个参数:是否采补集
return batchRemove(c, false);
}
/**
* 批量处理移除操作
* @param c
* @param complement
* @return
*/
private boolean batchRemove(Collection> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
//遍历数组,并检查这个集合是否对应值,移动要保留的值到数组前面,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.
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
/**
* 创建一个Iteratory的迭代器
* @return
*/
public Iterator iterator() {
return new Itr();
}
我们可以看出ArrayList底层并没有创建一个Iterator的迭代器而是创建了一个自己定义的Itr()的迭代器,下面我们看看ArrayList自己定义的内部类。
/**
* Itr是AbstractList.Itr的优化版本
* 1. Iterator 是工作在一个独立的线程中,并且拥有一个 mutex 锁。
* 2. Iterator 被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,
* 这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,
* 3. 所以按照 fail-fast 原则 Iterator 会马上抛出 java.util.ConcurrentModificationException 异常。
* 4. 所以 Iterator 在工作的时候是不允许被迭代的对象被改变的。
* 但你可以使用 Iterator 本身的方法 remove() 来删除对象,
* 5. Iterator.remove() 方法会在删除当前迭代对象的同时维护索引的一致性。
*/
private class Itr implements Iterator {
int cursor; // 下一个元素返回的索引
int lastRet = -1; // 最后一个元素返回的索引 默认为-1
int expectedModCount = modCount;
/**
* 是否有下一个元素
* @return
*/
public boolean hasNext() {
return cursor != size;
}
/**
* 下一个元素
* @return
*/
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
//将ArrayList的集合数组给当前方法数组
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);//删除下表为lastRet的元素
cursor = lastRet;//cursor等于下一个元素 即回退操作
lastRet = -1;//设置为-1 即不能继续连续的删除元素 只能继续往下走
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
/**
* 遍历剩下的元素
* @param consumer
*/
@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++]);
}
cursor = i;
lastRet = i - 1;
checkForComodification();
}
/**
* 如果迭代的过程中 插入或者删除了ArrayList元素 则会抛出这个异常 相当于版本控制一样
*/
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
/**
* AbstractList.ListItr 的优化版本
* ListIterator 与普通的 Iterator 的区别:
* - 它可以进行双向移动,而普通的迭代器只能单向移动
* - 它可以添加元素(有 add() 方法),而后者不行
*/
private class ListItr extends Itr implements ListIterator {
ListItr(int index) {
super();
cursor = index;
}
/**
* 是否有上一个元素
* @return
*/
public boolean hasPrevious() {
return cursor != 0;
}
/**
* 获取下一个元素的索引
* @return
*/
public int nextIndex() {
return cursor;
}
/**
* 获取 cursor 前一个元素的索引
* - 是 cursor 前一个,而不是当前元素前一个的索引。
* - 若调用 next() 后马上调用该方法,则返回的是当前元素的索引。
* - 若调用 next() 后想获取当前元素前一个元素的索引,需要连续调用两次该方法。
*/
public int previousIndex() {
return cursor - 1;
}
/**
* 返回 cursor 前一元素
*/
@SuppressWarnings("unchecked")
public E previous() {
checkForComodification();
int i = cursor - 1;
if (i < 0)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i;
return (E) elementData[lastRet = i];
}
/**
* 将数组的最后一个元素,设置成元素e
*/
public void set(E e) {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.set(lastRet, e);
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
/**
* 添加元素
*/
public void add(E e) {
checkForComodification();
try {
int i = cursor;
ArrayList.this.add(i, e);
cursor = i + 1;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
ArrayList源码分析详超详细讲解https://www.cnblogs.com/gxl1995/p/7534171344218b3784f1beb90d621337.html