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 ...
随机推荐
- Html5 Canvas笔记(2)-Canvas绘图
用Canvas API绘图,需要画图形的边线并设置内部区域填充,边线英文语法对应stroke,填充对应fill,在后面我们会频繁看到这2个英文单词的出现.Canvas API内置的形状绘图函数比较少, ...
- MySQL学习笔记(四)—存储过程
一.概述 存储过程是数据库定义的一些SQL语句的集合,然后直接调用这些存储过程和函数来执行已经定义好的SQL语句.存储过程可以避免开发人员重复的编写相同的SQL语句,而且存储过程是在MySq ...
- Git基本操作命令
先配置用户和邮箱: Administrator@USER-20150302NL MINGW32 ~$ git config --global user.name "youname" ...
- Centos7部署Zabbix
转载于http://www.cnblogs.com/xqzt/p/5124894.html,更正了部分错误,并增加了个别问题处理办法. 一.Zabbix简介 zabbix是一个基于WEB界面的提供分布 ...
- 《Algorithms Unlocked》读书笔记2——二分查找和排序算法
<Algorithms Unlocked>是 <算法导论>的合著者之一 Thomas H. Cormen 写的一本算法基础,算是啃CLRS前的开胃菜和辅助教材.如果CLRS的厚 ...
- 在eclipse中使用Maven建web工程项目
在eclipse中使用Maven建web工程项目: 第一种方式: 右键新建maven工程,勾选创建一个简单工程 填入信息,注意打包方式要改为war 点击完成,创建完的工程目录如下: 项目中没有WEB- ...
- html 选择器之基础选择器
我把CSS选择器分开成三部分,第一部分是我们常用的部分,我把他叫做基本选择器:第二部分我把他称作是属性选择器,第三部分我把他称作伪类选择器 一.基础选择器 1. 通配符(*):选中所有的元素 2.元素 ...
- angular 自定义filter
用modul.filter .filter("fiilterCity",function(){ return function(obj){ var newObj = []; ang ...
- 感觉不止被Q了一下,还不知道被谁套了一个虚弱
最近身体出现了一些问题,导致博客无法正常更新,只能是看身体状态更新了,相信用不了半个月就会满血复活的,请谅解 Joker在这里提醒大家 身体是革命的本钱,不要透支,不要过度消耗
- JSON 解析工具的封装(Java)
JSON 解析工具的封装(Java) 一直想有一个属于自己的JSON工具,今天终于弄好了..... 1.添加pom依赖包 <!--json解析--> <dependency> ...