zoukankan      html  css  js  c++  java
  • Java ArrayList 详解

    只记录目前为止关注的。JDK1.8

    一、基础属性

    1.1 内部参数

        //空存储实例。直接new ArrayList()便是以该空数组作为实例
        private static final Object[] EMPTY_ELEMENTDATA = {};
    
        //默认容量大小,在由空实例进行首次扩容时,扩到到该长度。
        //实际使用中,并未实际存在Capacity这个参数,需要扩容时直接根据旧数组的length进行扩容
        private static final int DEFAULT_CAPACITY = 10;
    
        //实际存储数组
        transient Object[] elementData;
    
        //存储元素个数
        private int size;
    
        //该字段用于迭代器的fail-fast机制【参考另一篇文章】
        protected transient int modCount = 0;
    

    1.2 三个重载构造方法

        //构建一个空实例,elementData指向容量为0的空数组
        public ArrayList() {
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }
    
        //以给定容量初始化存储数组
        public ArrayList(int initialCapacity) {
            if (initialCapacity > 0) {
                this.elementData = new Object[initialCapacity]; 
            } else if (initialCapacity == 0) {
                this.elementData = EMPTY_ELEMENTDATA;
            } else {
                throw new IllegalArgumentException("Illegal Capacity: "+
                                                   initialCapacity);
            }
        }
    
        //以集合初始化创建列表
        //步骤:1. 调用toArray(),将给定集合转成数组(以ArrayList为例,toArray()返回的是实际存在元素的那部分数组,即[0,size))
        //2. 让size直接等于给定集合的数组长度
        //3. 判断size如果为0则直接创建空存储实例,否则使用Arrays.copyOf将给定集合数组复制一份(浅拷贝),作为存储数组
        public ArrayList(Collection<? extends E> c) {
            elementData = c.toArray();
            if ((size = elementData.length) != 0) {
                // c.toArray might (incorrectly) not return Object[] (see 6260652)
                if (elementData.getClass() != Object[].class)
                    elementData = Arrays.copyOf(elementData, size, Object[].class);
            } else {
                // replace with empty array.
                this.elementData = EMPTY_ELEMENTDATA;
            }
        }
    
        // ArrayList 的 toArray()源码,复制[0,size)部分返回
        public Object[] toArray() {
            return Arrays.copyOf(elementData, size);
        }
    
    

    二、操作及策略

    2.1 动态扩容

    扩容策略:当数组全满了才扩容,新长度=旧长度*1.5

    动态扩容有两个入口:供用户调用的显式扩容ensureCapacity()和添加元素时的隐式扩容ensureCapacityInternal(),不过均是调用ensureExplicitCapacity()来根据传入所需容量值决定是否扩容,最终实际扩容操作在grow()方法中。

        //显式扩容入口方法
        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);
            }
        }
    
        //隐式扩容入口方法
        //其中参数 minCapacity 值为 size+1,由add方法调用传入
        private void ensureCapacityInternal(int minCapacity) {
            ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
        }
    
        //添加方法
        public boolean add(E e) {
            // 传入最小所需容量为size+1
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
        }
    
    
        //计算所需容量,实则不需要计算
        //只是单纯判断当前是否是空实例,为空就话返回 "默认容量"与minCapacity之间的较大值,不为空直接返回minCapacity
        //参数 minCapacity 只有两种情况:
        // 1. 隐式扩容时,如add()传入,其值为 size+1
        // 2. 显式扩容,用户指定
        private static int calculateCapacity(Object[] elementData, int minCapacity) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            }
            return minCapacity;
        }
        
        //在此处,根据最小所需容量来判断是否实际进行扩容
        private void ensureExplicitCapacity(int minCapacity) {
            modCount++;
    
            // overflow-conscious code
            if (minCapacity - elementData.length > 0) //若已经占满,才进行扩容
                grow(minCapacity);
        }
    
        //实际扩容方法
        //可以看到扩容策略为:length + length*0.5(右移1位缩小1倍)
        //然后调用Arrays.copyOf浅复制
        private void grow(int minCapacity) {
            // overflow-conscious code
            int oldCapacity = elementData.length;
            int newCapacity = oldCapacity + (oldCapacity >> 1); //旧长度+旧长度*2
            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);
        }
    
    

    2.2 删除操作

    ArrayList中有两个remove方法public E remove(int index)public boolean remove(Object o)

    源码实现得其实挺简单,本来还在犹豫有没有必要把remove方法记录下来,但发现有个点值得注意以下:
    注意点:JDK对于数组内元素统一移动操作,偏好使用System.arraycopy(ary, srcPos, ary, destPos, len)来移动

    
        //通过给定下标来删除元素,并返回删除元素
        public E remove(int index) {
            rangeCheck(index); // 检查下标范围
    
            modCount++;
            E oldValue = elementData(index); // 合理的话,先获取该元素(等会儿用于返回)
    
            int numMoved = size - index - 1; // 计算待移动元素个数(删除元素后,后面的元素要统一往前挪补上)
            if (numMoved > 0)
                System.arraycopy(elementData, index+1, elementData, index,
                                 numMoved); // 使用System.arraycopyl来移动数组元素
            elementData[--size] = null; // clear to let GC do its work  // 删除后size-1,并将先前最后一个元素置null
    
            return oldValue; //返回删除元素
        }
    
        // 判断下标是否合理(<size),否则抛出异常
        private void rangeCheck(int index) {
            if (index >= size)
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }
    
        // 删除首个与给定元素相等的元素,元素判等使用equal方法
        // 成功返回true,失败返回false
        public boolean remove(Object o) {
            if (o == null) { // 可以删除null节点
                for (int index = 0; index < size; index++)
                    if (elementData[index] == null) {
                        fastRemove(index); // 参考下面方法注释
                        return true;
                    }
            } else {
                for (int index = 0; index < size; index++)
                    if (o.equals(elementData[index])) { // 从头开始一个个检查,使用equal判等
                        fastRemove(index);
                        return true;
                    }
            }
            return false;
        }
    
        /*
         * 官方注释说得很清楚了:删除元素,但跳过边界检查且不返回删除元素。(就是remove(index)的缩略版
         * Private remove method that skips bounds checking and does not
         * return the value removed.
         */
        private void fastRemove(int index) {
            modCount++;
            int numMoved = size - index - 1;
            if (numMoved > 0)
                System.arraycopy(elementData, index+1, elementData, index,
                                 numMoved);
            elementData[--size] = null; // clear to let GC do its work
        }
    
  • 相关阅读:
    022、如何运行容器(2019-01-15 周二)
    ssh 跳板机部署
    021、镜像小结(2019-01-14 周一)
    020、搭建本地Registry(2019-01-11 周五)
    019、使用公共Registry (2019-01-10 周四)
    018、容器命名最佳实践 (2019-01-09 周三)
    017、RUN、CMD、ENTRYPOINT (2019-01-08 周二)
    016、Dockerfile 常用命令(2019-01-07 周一)
    015、调试Dockerfile(2019-01-04 周五)
    014、镜像的缓存特性(2019-01-03 周四)
  • 原文地址:https://www.cnblogs.com/simpleito/p/10926385.html
Copyright © 2011-2022 走看看