集合源码解析:ArrayList的底层实现原理
jdk7.0源码解析ArrayList底层实现原理
一、ArrayList的概述
ArrayList是List的实现类, 存储多个元素,有序、有下标、元素可以重复,允许null作为元素进行存储;线程不安全,因为底层方法为非同步。
二、ArrayList的存储结构
底层用数组实现,所以每个ArrayList实例都有一个容量,代表底层数组的长度,默认长度为10。随着存储的数据不断增加,底层数组需要扩容,扩容时需要将原数组中的数据拷贝到新数组中,所以如果能预计数量的多少,可在创建ArrayList对象时指定容量,如果一次性存储大量的元素时,可以调用ensureCapacity(int minCapacity)方法增加ArrayList的容量,从而减少扩容的次数。
三、ArrayList内部实现原理机制(源码解析)
-
ArrayList的基本元素
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8683452581122892189L; // 默认的容量 为 10 private static final int DEFAULT_CAPACITY = 10; // 用于共享的空实例数组对象 private static final Object[] EMPTY_ELEMENTDATA = {}; // 用于存储ArrayList元素的底层数组 private transient Object[] elementData; // 记录 ArrayList中元素的个数,同时控制存储的下标 private int size;
-
ArrayList中的构造方法:
/* 无参数的构造方法,采用默认的初始容量为10, 如果利用无参数的构造方法创建ArrayList对象,则 并没有完成 底层数组的初始化 */ public ArrayList() { super(); this.elementData = EMPTY_ELEMENTDATA; } /* 用指定容量完成ArrayList对象的创建: 参数代表底层数组的长度,但是如果指定的长度小于0,则抛出异常, 如果基于此构造方法创建ArrayList对象,则创建ArrayList对象 时,完成底层数组的初始化 */ public ArrayList(int initialCapacity) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; } /* 将Collection集合作为参数构造 ArrayList 对象, ArrayList集合用于 存储Collection集合中的数据 */ public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); size = elementData.length; if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); }
-
ArrayList中添加:add方法的实现
/* 往ArrayList集合中添加一个元素,size统计底层数组中有效元素的个数, 同时控制存储的下标 */ public boolean add(E e) { // 在 内部确认容量是否够用 ensureCapacityInternal(size + 1); // Increments modCount!! // 将 添加元素存储在底层数组相对应的位置 elementData[size++] = e; return true; } // 确认容量 private void ensureCapacityInternal(int minCapacity) { if (elementData == EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } // 根据容量 判断 是否需要扩容 private void ensureExplicitCapacity(int minCapacity) { modCount++; // 修改的次数加一 // 如果需要的最小容量,大于底层数组的长度,则扩容 if (minCapacity - elementData.length > 0) grow(minCapacity); } private void grow(int minCapacity) { // 获取底层数组的原始数组的长度 int oldCapacity = elementData.length; /* >> 代表右移,原数据除2取整数部分 新数组长度 = 原数组长度 + 原数组长度/2; 即:新数组长度是原数组长度的 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); }
解析:ArrayList底层数组在扩容时,每次扩容后的容量是原容量的 1.5 倍左右,扩容的
同时需要将原始数组中元素一一拷贝到新数组中,通过 Arrays.copyOf()方法完成
新数组的创建和元素的赋值,所以每一次的扩容,代价是很高的,所以我们在使
用时,尽可能的避免扩容。如果在使用 ArrayList时,能预知存储元素的个数,可
以在创建ArrayList 对象时,利用有参数的构造方法指定容量,或是根据实际容量
的需求调用ensureCapacity(int minCapacity) 方法 指定容量。
-
ArrayList中删除方法:remove
// 根据下标删除对应元素 public E remove(int index) { // 检测指定下标是否超过 最大下标 rangeCheck(index); //对集合的改变次数 + 1 modCount++; // 获取 底层数组对应的下标元素 E oldValue = elementData(index); // 需要移动元素的个数 int numMoved = size - index - 1; // 被删除下标开始,后一个元素覆盖前一个元素 if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); // 最后一个元素置 null,同时元素个数 - 1 elementData[--size] = null; // clear to let GC do its work // 将被删除的元素作为返回值返回 return oldValue; } // 删除指定的元素 public boolean remove(Object o) { // 如果 被删除的元素为null if (o == null) { // 遍历数组,删除第一个为null的元素 for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { /* 如果被删除的元素不为null,则遍历输出,删除第一个 和指定数据相同的元素 */ for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; }
-
**ArrayList中添加:get方法的实现 **
// 根据指定下标获取对应集合中的元素 public E get(int index) { // 检测指定下标是否超过 最大下标 rangeCheck(index); // 调用 方法,获取底层数组对应的下标元素 return elementData(index); }
-
**ArrayList中添加:get方法的实现 **
// 修改指定下标对应的数组元素
public E set(int index, E element) {
// 检测指定下标是否超过 最大下标
rangeCheck(index);
// 获取 底层数组对应的下标元素
E oldValue = elementData(index);
// 将新元素存储在 对应下标位置上
elementData[index] = element;
return oldValue;//被修改的数据作为返回值返回
}
四、Fail-Fast机制(快速失败)
-
在使用迭代器遍历集合过程中,对集合中对象的内容进行了修改(增、删、改)会抛出java.util.ConcurrentModificationException(并发修改异常),这就是所谓的Fail-Fast策略。
-
实现原理:在ArrayList的父类中定义了modCount属性,记录了对ArrayList的修改次数,只要对ArrayList中的内容进行修改,都会让modCount增加;在迭代器初始化时 ,会将modCount的值赋值给 expectedModCount ,每次迭代(nextNode操作)时,都会先判断
modCount和expectedModCount 的值是否相等,如果不相等则代表ArrayList集合被操作,就会抛出java.util.ConcurrentModificationException异常。
注意:modCount声明为volatile,保证线程之间修改的可见性。
上一篇: 2020 Dubbo面试题
推荐阅读
-
Mybaits 源码解析 (五)----- 面试源码系列:Mapper接口底层原理(为什么Mapper不用写实现类就能访问到数据库?)
-
Java中的容器(集合)之ArrayList源码解析
-
[五]类加载机制双亲委派机制 底层代码实现原理 源码分析 java类加载双亲委派机制是如何实现的
-
Mybaits 源码解析 (三)----- Mapper接口底层原理(为什么Mapper不用写实现类就能访问到数据库?)
-
荐 【dubbo源码解析】--- dubbo的服务暴露+服务消费(RPC调用)底层原理深入探析
-
基于数组实现的列表ArrayList源码解析
-
HashMap的源码及底层实现原理介绍
-
Spring的AOP实现原理源码解析
-
互联网架构-Spring5.0源码深度解析:SpringBean声明事务底层实现原理
-
在java中ArrayList集合底层的扩容原理