ArrayList的底层实现原理
ArrayList源码分析
1、java.util.ArrayList<E> : List 接口的大小可变数组的实现类
- ArrayList 内部基于 数组 存储 各个元素。
- 所谓大小可变数组,是指当 数组容量不足以存放新的元素时,创建新数组,并将原数组中的内容复制过来。
2、ArrayList底层实现原理
- 构造方法源码分析
//对象数组:ArrayList的底层数据结构,transient表示该字段不进行序列化操作 transient Object[] elementData; //实例化一个空数组 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //实例化一个空数组 private static final Object[] EMPTY_ELEMENTDATA = {}; /** *指定初始容量的有参构造 */ public ArrayList(int initialCapacity) { //如果初始容量大于0就,对象数组就指向到一个新的数组,大小为所指定的大小 if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { //如果大于0就指向一个空数组 this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } /** * 无参构造 */ public ArrayList() { //对象数组就指向到一个空数组 this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
ArrayList基于数组实现,构造方法有有参构造和无参构造如果指定了初始容量且大于0就将对象数组指定到一个新的数组,大小为所指定的大小。如果调用无参构造就将对象数组指定到一个空的数组。
- 添加方法源码分析
//elementData中已存放的元素的个数,注意:不是elementData的容量 private int size; //elementData的默认容量为10 private static final int DEFAULT_CAPACITY = 10; //对象数组:ArrayList的底层数据结构,transient表示该字段不进行序列化操作 transient Object[] elementData; //实例化一个空数组 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //实例化一个空数组 private static final Object[] EMPTY_ELEMENTDATA = {}; protected transient int modCount = 0; @Native public static final int MAX_VALUE = 0x7fffffff; private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; /** * 向elementData末尾中添加元素 */ public boolean add(E e) { //确保对象数组elementData有足够的容量,可以将新加入的元素e加进去 ensureCapacityInternal(size + 1); //加入新元素e,size加1 elementData[size++] = e; return true; } // minCapacity = seize+1,即表示执行完添加操作后,数组中的元素个数 private void ensureCapacityInternal(int minCapacity) { //判断是否是空数组 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { //用最小容量和10进行比较,取最大值赋值给最小容量 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } /** *确保数组的容量足够存放新加入的元素,若不够,要扩容 */ private void ensureExplicitCapacity(int minCapacity) { modCount++; //如果数组个数minCapacity (size+1)大于数组长度就需要进行扩容 if (minCapacity - elementData.length > 0) grow(minCapacity); } 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) { // 如果数组个数小于0抛出OutOfMemoryError异常 if (minCapacity < 0) // overflow throw new OutOfMemoryError(); // 如果最数组个数大于最大容量 就返回最大值,否则返回最大容量 return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; } /** * 向elementData指定位置添加元素 */ public void add(int index, E element) { //指定位置检查 rangeCheckForAdd(index); // 扩容检查 ensureCapacityInternal(size + 1); // Increments modCount!! //通过拷贝使数组内位置为 index 到 (size-1)的元素往后移动一位 System.arraycopy(elementData, index, elementData, index + 1, size - index); // 找到位置添加元素 elementData[index] = element; // 元素个数加一 size++; } // 判断指定位置是否超出数组个数 private void rangeCheckForAdd(int index) { if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } /** * @param src 原数组. * @param srcPos 原数组的起始位置. * @param dest 目标数组. * @param destPos 目标数组的起始位置. * @param length 拷贝长度. */ public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
ArrayList在无参的add方法中,每次插入新的元素时,先判断是否需要扩容,判断数组是否为空,为空则建默认的容量10赋值给minCapacity,判断是否要扩容,第一次添加,数组的size是10,之后添加元素时,在ensureExplicitCapacity方法中判断数组元素个数即size+1(形参minCapacity)是否超过数组长度,超过则需要进行扩容操作,扩容是将旧的容量扩大到1.5倍,然后将数组拷贝到新的数组完成扩容操作。最后将元素添加,并size+1。ArrayList在指定位置添加元素时,是先检查指定位置是否在数组范围内,即数组中元素个数是1,则index得小于或者低于1。然后通过拷贝使数组内位置为 index 到 (size-1)的元素往后移动一位,腾出位置之后放入元素,数组个数加一。
- 删除方法源码分析
public E remove(int index) { //指定位置检查 rangeCheck(index); modCount++; //保留要删除的值 E oldValue = elementData(index); //要移动元素个数 int numMoved = size - index - 1; if (numMoved > 0) //通过拷贝使数组内位置为 index+1到 (size-1)的元素往前移动一位 System.arraycopy(elementData, index+1, elementData, index, numMoved); //清除末尾元素让GC回收 elementData[--size] = null; // clear to let GC do its work //返回删除的值 return oldValue; }
删除指定位置的元素,先对index进行检查,在将要删除的值保留,计算出需要移动元素个数,再通过拷贝使数组内位置为 index+1到 (size-1)的元素往前移动一位,最后将末尾元素清理让GC回收返回删除值。由于List接口继承了Collection,因此ArrayList还有一个来自Collection接口定义的删除对象boolean remove( Object o ) ,而ArrayList自定义的remove方法是T remove(int index)删除的是下标位置的对象并返回值。
参考博客链接
https://my.oschina.net/90888/blog/1625416
转载请于明显处标明出处
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用