前言

ArrayList(这里的ArrayList是基于jdk1.7)是在项目中经常使用的集合类,例如我们从数据库中查询出一组数据。这篇文章不去剖析它的继承和实现,只是让我们知道实例化及增删改查时它的内部代码是怎么实现的。

public class TestList {
@Test
public void testArrayList(){
List<Integer> list = new ArrayList<>(); for (int i = 0; i < 12; i++) {
list.add(i);
}
}
}

  

实例化

List<Integer> list = new ArrayList<>();

  

我们先来看看上面这段实例化ArrayList时,内部发生了什么。

这调用的是ArrayList的无参构造函数,如下

   
public ArrayList() {
super();
this.elementData = EMPTY_ELEMENTDATA;
}

  

步骤:

  1. 调用父类的构造函数(AbstractList的构造函数)
  2. 赋值elementData(一个空数组{})

这里把ArrayList内部的属性说明下,一共有DEFAULT_CAPACITY、EMPTY_ELEMENTDATA、elementData、size。其中DEFAULT_CAPACITY和EMPTY_ELEMENTDATA是被定义为static和final的。而elementData是我们添加元素时存储的数组,size就是这个数组的大小,DEFAULT_CAPACITY就是数组默认的大小,EMPTY_ELEMENTDATA是一个空数组。

    /**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10; /**
* Shared empty array instance used for empty instances.
*/
private static final Object[] EMPTY_ELEMENTDATA = {}; /**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == EMPTY_ELEMENTDATA will be expanded to
* DEFAULT_CAPACITY when the first element is added.
*/
private transient Object[] elementData; /**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;

  

实例化部分就到这里了,其他有参的这里就不做介绍,需要的可以自己去看看,接下来看看添加元素

添加元素

for (int i = 0; i < 12; i++) {
list.add(i);
}

  

这里循环添加了12个元素,是为了查看ArrayList的第一次扩容,接下来看看ArrayList里面是怎么实现的

add方法

    public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}

  

add方法中的步骤如下

  1. 调用ensureCapacityInternal,传入参数(size+1)
  2. elementData数组对象赋值
  3. 返回true

ensureCapacityInternal方法

    private void ensureCapacityInternal(int minCapacity) {
if (elementData == EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
} ensureExplicitCapacity(minCapacity);
}

  

这个方法中传入的参数名称为minCapacity,翻译为中文就是最小容量。

这个方法首先判断当前数组对象elementData是不是等于空对象EMPTY_ELEMENTDATA。大家可以看下实例化时elementData对象的赋值(如下)。

    public ArrayList() {
super();
this.elementData = EMPTY_ELEMENTDATA;
}

  

如果是,则最小容量minCapacity重新赋值为DEFAULT_CAPACITY和minCapacity中最大的一个数。

然后调用ensureExplicitCapacity方法。断点内容如下

minCapacity被赋值为DEFAULT_CAPACITY

接下来是ensureExplicitCapacity方法

ensureExplicitCapacity方法

    private void ensureExplicitCapacity(int minCapacity) {
modCount++; // overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

  

modCount是ArrayList的父类AbstractList的一个属性,记录被修改的次数,也是通过这个触发fail-fast机制的,这里不过多说明。

下面就是比较传入的最小容量minCapacity减去elementData数组的长度是否大于0(也就是最小容量minCapacity是否大于elementData数组的长度),是的话调用grow方法。

grow方法

这个方法就是ArrayList的扩容方法,接下来看看方法内部代码

    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);
}

  

由于是第一次调用add方法,所以minCapacity值为ArrayList的默认容量10,如下

  1. 接下来定义一个旧容量oldCapacity,值为elementData数组长度
  2. 然后定义一个新容量newCapacity,值为旧容量oldCapacity加上(旧容量oldCapacity右移1位,也就是除以2,不四舍五入)
  3. 接着比较新容量newCapacity和传入的最小容量minCapacity,谁大就赋值给新容量newCapacity
  4. 如果新容量newCapacity比定义的数组最大容量MAX_ARRAY_SIZE还大的话,就调用hugeCapacity方法,看是否抛出异常还是赋最大值
  5. 调用Arrays.copyOf进行数组扩容

获取元素

获取元素有三种方式

  1. 迭代器Iterator遍历
  2. 通过索引
  3. foreach循环获取

这里就介绍下通过迭代器Iterator遍历吧。

    public Iterator<E> iterator() {
return new Itr();
}

  

Itr是ArrayList一个实现Iterator的内部类,通过这个对象来获取ArrayList中存储的元素。代码如下

    /**
* An optimized version of AbstractList.Itr
*/
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount; public boolean hasNext() {
return cursor != size;
} @SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
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);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
} final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}

  

总结

当我们实例化ArrayList时(调用无参构造函数),这个对象里面存储元素的数组还只是一个空数组。

在第一次调用add方法时(也就是第一次添加元素时),对存储元素的数组elementData进行第一次扩容,扩容的数组长度为ArrayList内部定义的默认容量10,。

当插入第11个数组元素时,进行第二次扩容,扩容的长度为原先的容量加上(原先容量/2),即原先容量的1.5倍。扩容的长度规则是10>>15>>22>>33...

使用ArrayList时代码内部发生了什么(jdk1.7)?的更多相关文章

  1. 当你创建了一个 Deployment 时,Kubernetes 内部发生了什么?

    我们通常使用 kubectl 来管理我们的 Kubernetes 集群. 当我们需要一个 Nginx 服务时,可以使用以下命令来创建: kubectl create deployment nginx ...

  2. ArrayList底层代码解析笔记

    通过底层代码可以学习到很多东西: public class ArrayList<E> extends AbstractList<E> implements List<E& ...

  3. java虚拟机jvm启动后java代码层面发生了什么?

    java虚拟机jvm启动后java代码层面发生了什么? 0000 我想验证的事情 java代码在被编译后可以被jdk提供的java命令进行加载和运行, 在我们的程序被运行起来的时候,都发生了什么事情, ...

  4. 【ITOO 2】使用ArrayList时的注意事项:去除多余的null值

    问题描述:在课表导入的时候,将数据从excel表里读出,然后将list批量插入到对应的课程表的数据表单中去,出现结果:当我们导入3条数据时,list.size()为3,但是实际上,list里面存在10 ...

  5. 换个视角来看git命令与代码库发生网络交互报错事件

    git的一系列命令中像 clone.pull.push等与代码库发生网络交互时,可能报下面的错误信息 fatal: remote error: CAPTCHA required Your Stash ...

  6. 实习日记)select option 选择不同的option时, 页面发生不同的变化

    怎么在下拉框的选择不同的option时, 页面发生响应的变化 因为option是没有点击事件什么的,  只有select才有, 所以不能通过option的点击事件来完成, 所以开始的尝试都失败了(之前 ...

  7. 当C#中带有return的TryCatch代码遇到Finally时代码执行顺序

    编写的代码最怕出现的情况是运行中有错误出现,但是无法定位错误代码位置.综合<C#4.0图解教程>,总结如下: TryCatchFinally用到的最多的是TryCatch,Catch可以把 ...

  8. 如何将oc代码转换成运行时代码

    // 运行时 其实就是oc的底层  平时写的代码 最终都是转成底层的运行时代码以下面程序为例子: 如果我们想要看我们的main.m文件底层转换成了怎样的运行时代码 ,我们可以这样做. 1.打开终端  ...

  9. 《Secrets of the JavaScript Ninja》:JavaScript 之运行时代码

    最近,在阅读 jQuery 之父 John Resig 力作:Secrets of the JavaScript Ninja(JavaScript忍者秘籍).关于第九章提及的 JavaScript 之 ...

随机推荐

  1. 【BZOJ3669】【Noi2014】魔法森林(Link-Cut Tree)

    [BZOJ3669][Noi2014]魔法森林(Link-Cut Tree) 题面 题目描述 为了得到书法大家的真传,小 E 同学下定决心去拜访住在魔法森林中的隐 士.魔法森林可以被看成一个包含 n ...

  2. [Luogu3527][POI2011]MET-Meteors

    BZOJ权限题!提供洛谷链接! sol 昨晚突发奇想去学了一下整体二分. 这道题应该算是整体二分的板子题吧. 整体二分 首先要求可离线,不然还是安心码数据结构吧. 整体二分简单的来讲,就是时间复杂度可 ...

  3. [CQOI2007]余数求和

    大于k的部分直接加k 对于小于等于k的cnt个数 ans=cnt*k - Σ(k/i * i) 然后k/i在一段区间内不变,这段区间直接可以数列求和 # include <bits/stdc++ ...

  4. [BZOJ2296] [POJ Challenge] 随机种子

    Description 1tthinking除了随机算法,其他什么都不会.但是他还是可以ac很多题目,他用的是什么呢?他会选择一个好的随机种子,然后输出答案.往往他选择的一个好的种子可以有99%的概率 ...

  5. Adaboost的意义

    Adaboost是广义上的提升方法(boosting method)的一个特例.广泛应用于人脸识别等领域. 它的基本思想是,“三个臭皮匠赛过诸葛亮”,即用多个弱分类器的线性加权,来得到一个强的分类器. ...

  6. 关系型数据库工作原理-事务管理(一)(翻译自Coding-Geek文章)

    本文翻译自Coding-Geek文章:< How does a relational database work>. 原文链接:http://coding-geek.com/how-dat ...

  7. 了解wireshark

    Wireshark是很流行的网络分析工具.这个强大的工具可以捕捉网络中的数据,并为用户提供关于网络和上层协议的各种信息.与很多其他网络工具一样,Wireshark也使用pcap network lib ...

  8. AngularJS中实现服务端分页

    这个教程将介绍在AngularJS应用中的服务端分页处理.在任何涉及到列表或表格数据的应用中都可能会用到分页. 概念 当我们处理异步分页时,每次只从服务器上获取一页数据.也就是说当用户点击第二页,就只 ...

  9. Activiti就是这么简单

    Activiti介绍 什么是Activiti? Activiti5是由Alfresco软件在2010年5月17日发布的业务流程管理(BPM)框架,它是覆盖了业务流程管理.工作流.服务协作等领域的一个开 ...

  10. 聊聊Linux用户态驱动设计

    序言 设备驱动可以运行在内核态,也可以运行在用户态,用户态驱动的利弊网上有很多的讨论,而且有些还上升到政治性上,这里不再多做讨论.不管用户态驱动还是内核态驱动,他们都有各自的缺点.内核态驱动的问题是: ...