ArrayList源码解析(二)自动扩容机制与add操作
本篇主要分析ArrayList的自动扩容机制,add和remove的相关方法。
作为一个list,add和remove操作自然是必须的。
前面说过,ArrayList底层是使用Object数组实现的。数组的特性是大小固定,这个特性导致的后果之一就是,当ArrayList中成员个数超过capacity后,就需要重新分配一个大的数组,并将原来的成员拷贝到新的数组之中。
add操作前都需要保证capacity足够,因此扩容机制和add放在一起讲解。
1.ArrayList的自动扩容机制
ArrayList有两个概念,capacity和size。capacity就是底层Object数组的length,表示能容纳的最大成员数;size则表示已经存储的成员数,可以通过size()函数获取。
/**
* Returns the number of elements in this list.
*
* @return the number of elements in this list
*/
public int size() {
return size;
}
有时我们需要保证ArrayList的capacity能满足一个最小值,比如ArrayList的当前capacity为10,且size也为10,这时需要插入一个成员,就要保证ArrayList的capacity至少为11。这时就需要ArrayList的扩容机制发挥作用。
ArrayList的扩容相关方法如下:
/** * Increases the capacity of this <tt>ArrayList</tt> instance, if
* necessary, to ensure that it can hold at least the number of elements
* specified by the minimum capacity argument.
* 如果必须,函数增加ArrayList的容量,以确保至少可以容纳minCapacity指定的成员数量。
* @param minCapacity the desired minimum capacity
*/
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
上面三个方法都比较简单,关键在于ensureExplicitCapacity()方法中调用的grow()方法,正是这个方法实现了扩容:
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
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);
}
当调用grow()方法时,已经确定要进行扩容了,所以grow()中不再进行是否进行扩容的判断。
oldCapacity存储旧容量值,新的容量值newCapacity取(1+0.5)倍的oldCapacity大小,如下:
int newCapacity = oldCapacity + (oldCapacity >> 1);
如果newCapacity仍然小于期望扩充的最小值minCapacity,newCapacity取minCapacity的大小;否则就取(1+0.5)倍的oldCapacity大小。
如果计算得到的newCapacity比MAX_ARRAY_SIZE还大,就需要调用hugeCapacity()方法进行处理。
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
最后,将原来数组中的成员拷贝到新的数组中。
简言之,当minCapacity大于ArrayList的capacity时,就将数组的长度扩充到原来的1.5倍,如果这个值还是小于minCapacity,就取minCapacity作为新的capacity。
ArrayList的扩容机制提高了性能,如果每次只扩充一个,那么频繁的插入会导致频繁的拷贝,降低性能,而ArrayList的扩容机制避免了这种情况。
2.add操作
ArrayList操作都调用了上面的ensureCapacityInternal(),先保证ArrayList的capacity足够大(至少为size+1),所以可能发生了数组的复制,也可能没有发生。
所有的add相关的方法都修改了size的值,因此modCount值也会增加。
ArrayList共有四个add相关的方法,前两个用来添加单个成员,后两个用来将一个Collection的所有成员添加到ArrayList中:
1)在ArrayList末尾添加一个成员
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
这个方法比较容易理解,先保证capacity足够,然后在ArrayList的末尾添加成员,返回类型为boolean。
2)在指定位置插入一个成员
/**
* Inserts the specified element at the specified position in this
* list. Shifts the element currently at that position (if any) and
* any subsequent elements to the right (adds one to their indices).
*
* @param index index at which the specified element is to be inserted
* @param element element to be inserted
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
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++;
}
这个方法在index的位置插入成员element:
先调用 rangeCheckForAdd 对index进行界限检查;
然后调用 ensureCapacityInternal 方法保证capacity足够大;
再将从index开始之后的所有成员后移一个位置;
将element插入index位置;
最后size加1。
3)将一个Collection的所有成员都添加到ArrayList的末尾
/**
* Appends all of the elements in the specified collection to the end of
* this list, in the order that they are returned by the
* specified collection's Iterator. The behavior of this operation is
* undefined if the specified collection is modified while the operation
* is in progress. (This implies that the behavior of this call is
* undefined if the specified collection is this list, and this
* list is nonempty.)
*
* @param c collection containing elements to be added to this list
* @return <tt>true</tt> if this list changed as a result of the call
* @throws NullPointerException if the specified collection is null
*/
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的所有成员都添加到ArrayList的末尾:
先将Collection转换为数组a;
确保ArrayList的capacity足够大(至少size+a.length);
将数组a的所有成员拷贝到ArrayList的末尾;
size增加Collection的成员个数, 并返回Collection成员个数。
4)将一个Collection的所有成员插入到ArrayList的指定位置
/**
* Inserts all of the elements in the specified collection into this
* list, starting at the specified position. Shifts the element
* currently at that position (if any) and any subsequent elements to
* the right (increases their indices). The new elements will appear
* in the list in the order that they are returned by the
* specified collection's iterator.
*
* @param index index at which to insert the first element from the
* specified collection
* @param c collection containing elements to be added to this list
* @return <tt>true</tt> if this list changed as a result of the call
* @throws IndexOutOfBoundsException {@inheritDoc}
* @throws NullPointerException if the specified collection is null
*/
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;
}
这个方法需要注意的是,需要将index位置以及这之后的(size-index)个成员后移numNew位,空出numNew个位置,以便存放Collection中的numNew个成员。
ArrayList源码解析(二)自动扩容机制与add操作的更多相关文章
- Java ArrayList源码分析(含扩容机制等重点问题分析)
写在最前面 这个项目是从20年末就立好的 flag,经过几年的学习,回过头再去看很多知识点又有新的理解.所以趁着找实习的准备,结合以前的学习储备,创建一个主要针对应届生和初学者的 Java 开源知识项 ...
- ArrayList源码解析(二)
欢迎转载,转载烦请注明出处,谢谢. https://www.cnblogs.com/sx-wuyj/p/11177257.html 自己学习ArrayList源码的一些心得记录. 继续上一篇,Arra ...
- 顺序线性表 ---- ArrayList 源码解析及实现原理分析
原创播客,如需转载请注明出处.原文地址:http://www.cnblogs.com/crawl/p/7738888.html ------------------------------------ ...
- Mybatis源码解析(二) —— 加载 Configuration
Mybatis源码解析(二) -- 加载 Configuration 正如上文所看到的 Configuration 对象保存了所有Mybatis的配置信息,也就是说mybatis-config. ...
- 面试必备:ArrayList源码解析(JDK8)
面试必备:ArrayList源码解析(JDK8) https://blog.csdn.net/zxt0601/article/details/77281231 概述很久没有写博客了,准确的说17年以来 ...
- ArrayList源码解析--值得深读
ArrayList源码解析 基于jdk1.8 ArrayList的定义 类注释 允许put null值,会自动扩容: size isEmpty.get.set.add等方法时间复杂度是O(1): 是非 ...
- ArrayList源码解析
ArrayList简介 ArrayList定义 1 public class ArrayList<E> extends AbstractList<E> implements L ...
- Java中的容器(集合)之ArrayList源码解析
1.ArrayList源码解析 源码解析: 如下源码来自JDK8(如需查看ArrayList扩容源码解析请跳转至<Java中的容器(集合)>第十条):. package java.util ...
- Sentinel源码解析二(Slot总览)
写在前面 本文继续来分析Sentinel的源码,上篇文章对Sentinel的调用过程做了深入分析,主要涉及到了两个概念:插槽链和Node节点.那么接下来我们就根据插槽链的调用关系来依次分析每个插槽(s ...
随机推荐
- TableView cell自适应高度-----xib
1.通过xib创建一个cell,将label进行上左下右,进行适配, self.automaticallyAdjustsScrollViewInsets = NO; self.edgesForExte ...
- 06 Theory of Generalization
若H的断点为k,即k个数据点不能被H给shatter,那么k+1个数据点也不能被H给shatter,即k+1也是H的断点. 如果给定的样本数N是大于等于k的,易得mH(N)<2N,且随着N的增大 ...
- APP自识别安卓苹果
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- ReentrantLock 以及 AQS 实现原理
什么是可重入锁? ReentrantLock是可重入锁,什么是可重入锁呢?可重入锁就是当前持有该锁的线程能够多次获取该锁,无需等待.可重入锁是如何实现的呢?这要从ReentrantLock ...
- Python实现二叉树的四种遍历
对于一个没学过数据结构这门课程的编程菜鸟来说,自己能理解数据结构中的相关概念,但是自己动手通过Python,C++来实现它们却总感觉有些吃力.递归,指针,类这些知识点感觉自己应用的不够灵活,这是自己以 ...
- 利用子集构造法实现NFA到DFA的转换
概述 NFA非有穷自动机,即当前状态识别某个转换条件后到达的后继状态不唯一,这种自动机不便机械实现,而DFA是确定有限状态的自动机,它的状态转换的条件是确定的,且状态数目往往少于NFA,所以DFA能够 ...
- c#常用方法和类
1. 数据类型转换函数 Convert.ToXXX(); XXX.Parse(); XXX.TryParse(); 2. 日期相关的类与函数 获取系统当前日期(含时间):DateTime.Now 获 ...
- angular2项目添加ng2-bootstrap
1. 先webstome创建一个空的工程.例如test File--> New -->Project --> Empty Project 2.然后命令行(Alt+F12)下执行以下命 ...
- 从零开始构建一个的asp.net Core 项目(二)
接着上一篇博客继续进行.上一篇博客只是显示了简单的MVC视图页,这篇博客接着进行,连接上数据库,进行简单的CRUD. 首先我在Controllers文件夹点击右键,添加->控制器 弹出的对话框中 ...
- 使用Dotfuscator加密混淆程序以及如何脱壳反编译
混淆演示 首先介绍如何使用Dotfuscator对.net程序加密码混淆/加壳 C#或vb.net编写的应用程序或DLL. 这里随便创建了一个C#的命令行控制台程序.程序很简单,对当前的时间进行了AE ...