使用ArrayList时代码内部发生了什么(jdk1.7)?

前言

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...

原文地址:https://www.cnblogs.com/fixzd/p/8675577.html

时间: 2024-08-30 05:49:18

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

ArrayList底层代码日记

通过底层代码可以学习到很多东西: public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable 由此可见,ArrayList继承自AbastractList,以及实现了以上四个接口; public abstract class AbstractList<E> extends Abstract

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

java虚拟机jvm启动后java代码层面发生了什么? 0000 我想验证的事情 java代码在被编译后可以被jdk提供的java命令进行加载和运行, 在我们的程序被运行起来的时候,都发生了什么事情, 下面就来探究下这个问题, 这个问题被拆成了两个问题, 第一个问题用来确定发生了哪些事情, 第二个问题用来确定这些事情是如何进行的. java进程里面都发生了哪些活动? 这些活动在java代码(反编译或者是源码)级别有所体现吗? 0001 寻找验证的方式 当我在探究上面两个问题时, 我想了很多方式去

ArrayList和LinkedList 内部结构分析(一)

在了解集合的时候,都会学到不同集合之间的区别,比如ArrayList和LinkedList,其中ArrayList是类似于数组结构的,查询比较快速.而LinkedList则是链表结构,在插入和删除的时候效率较高. 通过研究源码,可以更深入的了解其内部实现,真的是ArrayList所有查询都快么?  真的是 LinkedList所有的插入和删除都更效率么? 废话不多说,上代码! 一.先摘抄一些ArrayList的代码吧,这里只分析一些关键的方法: 1.ArrayList的内部结构是怎么样的? 1p

驱动代码内部相关关键字等

1.     LPVOID -指针 一个没有类型的指针,也就是说你可以将任意类型的指针赋值给LPVOID类型的变量(一般作为参数传递),然后在使用的时候再转换回来.可以将其理解为long型的指针,指向void型,没有函数返回值. 2.     PDWORD -短指针(LPDWORD也基本一样.) windef.h中定义的数据类型,原文如下: typedef DWORD near *PDWORD; 表示指向DWORD的短指针类型,是WINDOWS编程中常用的数据类型之一. 3.BOOL和bool

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

编写的代码最怕出现的情况是运行中有错误出现,但是无法定位错误代码位置.综合<C#4.0图解教程>,总结如下: TryCatchFinally用到的最多的是TryCatch,Catch可以把Try代码块的错误捕捉到,并对错误进行后续处理.这一点比较常见. 现在要讨论的是如果Try和Catch代码块有return时代码的执行顺序.众所周知,return的作用是退出当前函数,不执行return后面的代码.那么问题来了:如果return出现在Try或catch代码块中,并且return后面还有代码,则

用友T6 操作发票凭证时 portal.exe发生未处理的win32异常,弹出vistual studio实时调试器

用友T6 操作发票凭证时 portal.exe发生未处理的win32异常,弹出vistual studio实时调试器 解决方法: 先删除注册表如下两项 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug\Debugger HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\DbgManagedDebugger 然后打开visual studio 2008

VMWare 10 Ubuntu 虚拟机启动时提示“内部错误”的解决办法

使用VMware新建虚拟机,启动时出现“内部错误”提示,放百度搜一下,是由权限问题造成:权限的问题,在桌面上右键单击VMWare的图标,选择“以管理员身份运行”. 在VMWare中,再次点击“启动客户机”,则可以正常...使用VMware新建虚拟机,启动时出现“内部错误”提示,放百度搜一下,是由权限问题造成:权限的问题,在桌面上右键单击VMWare的图标,选择“以管理员身份运行”. 在VMWare中,再次点击“启动客户机”,则可以正常... v.17173.com/playlist_187307

vim设置为indent折叠以后,每次打开文件时代码处于折叠状态,能改变吗?

vim设置为indent折叠以后,每次打开文件时代码处于折叠状态.即使这次编辑的时候把折叠展开,保存关闭文件,重新打开,所有的代码都又折叠起来了. 请问有没有默认不折叠的方法? 是否有可以一次展开所有折叠的操作?(zO不行,只能移到折叠行上,展开该行以及该行下缩进更多的折叠) 2013-09-19 09:08 提问者采纳 设置上下面这个,就基本相当于默认不折叠了. set foldlevel=99 试试看zR

处理git pull时代码冲突问题

今天在服务器上git pull是出现以下错误: error: Your local changes to the following files would be overwritten by merge 不知道什么原因造成的代码冲突,处理方法如下: 如果希望保留生产服务器上所做的改动,仅仅并入新配置项: git stash git pull git stash pop 然后可以使用git diff -w +文件名 来确认代码自动合并的情况. 如果希望用代码库中的文件完全覆盖本地工作版本. 方法