• 使用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...


    作者: 云枭zd
    Github: Github地址
    出处: https://www.cnblogs.com/fixzd/
    版权声明:本文欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则视为侵权。
  • 相关阅读:
    Integer.highestOneBit(int i)方法的作用与底层实现
    一文搞明白位运算、补码、反码、原码
    Zookeeper如何解决脑裂问题
    Zookeeper请求处理原理分析
    Linux 设备驱动之 UIO 机制
    virtio guest side implementation: PCI, virtio device, virtio net and virtqueue
    DPDK之(八)——vhost库
    Red Hat OpenStack 10的新特性
    探秘DPDK Virtio的不同路径
    2017版:KVM 性能优化之内存优化
  • 原文地址:https://www.cnblogs.com/fixzd/p/8675577.html
Copyright © 2020-2023  润新知