• ArrayList源码学习


    ArrayList类

    ArrayList
    继承自:AbstractList
    实现接口: List, RandomAccess, Cloneable, java.io.Serializable

    属性

    //实现Serializable接口,生成的序列版本号
    private static final long serialVersionUID = 8683452581122892189L;

    //默认初始化容量
    private static final int DEFAULT_CAPACITY = 10;

    两个空数组对象
    private static final Object[] EMPTY_ELEMENTDATA = {};
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    你一定很好奇为什么弄两个??除了名字其他完全一样啊,有必要吗?
    先看官方解释:

    We distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when first element is added.

    用来区分当第一次添加元素时,需要扩容的大小。之前自己没注意到这个细节,后边在 add() 操作中判断是否扩容时会有分析。

    //ArrayList中实际存储元素的数组,看到这里是不是感觉我写错了?我也搞不懂为什么 elementData 属性设置默认访问权限,莫非是因为有 transient 不序列化的原因?
    transient Object[] elementData;

    //最大数组容量
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    //记录当前ArrayList的长度,而不是 elementData 数组的长度,elementData数组会有值为 null 的位置,而且可能会很多。下面也会说到
    private int size;

    构造方法

    当使用无参构造方法时,给 **elementData **数组设置的是 DEFAULTCAPACITY_EMPTY_ELEMENTDATA
    使用有参构造器且传入的值是0时,为 elementData数组设为 EMPTY_ELEMENTDATA
    这里也没有本质区别,都是空嘛,下边的扩容操作才是亮点!

    //注意没有一个构造方法设置 size 值
    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);
            }
    }
    
    public ArrayList() {//使用默认初始化容量 10
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    

    add(E e)

    ArrayList 在每次新增元素时,都会首先进行判断是否需要动态扩容。

    注意上边的两个构造方法,分两类

    1. 当使用无参构造器时,把 elementData 数组设置为 DEFAULTCAPACITY_EMPTY_ELEMENTDATA终于派上用场了!在这种情况下进行「首次」 add() 操作时,会一次性把 elementData 数组大小设为 **10 ,到这里扩容完毕。之后的第 2~9 次 add() 操作不会扩容,因为在判断是否需要扩容即执行ensureExplicitCapacity(minCapacity)**函数时,传入的值是3-10,都小于等于执行完第一次 add() 操作后的数组大小10。只有在第十次 add() 操作时才会扩容,此时会扩容为原数组长度的 1.5倍。再之后再进行 add() 操作,会把当前 ArrayList 中的元素数量加一(size+1)和 elementData数组大小比较,若前者大,则继续扩容为当前数组长度的 1.5倍……
    2. 使用有参构造器时,当进行 add() 时,只是把 size+1和 **elementData.length()**进行比较,若前置大就扩容至 1.5倍。

    最后把 **elementData[size]**设置为 传入的值 e,并执行 **size++**操作。

    public boolean add(E e) {
    		//每次添加都会先进行判断
            ensureCapacityInternal(size + 1);  
            elementData[size++] = e;
            return true;
    }
    
    private void ensureCapacityInternal(int minCapacity) {
    //使用无参构造器且首次添加元素时,设置最小扩容大小为10
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    						    //默认初始化容量10
    		minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
    
    	//判断是否需要扩容
        ensureExplicitCapacity(minCapacity);
    }
    
    private void ensureExplicitCapacity(int minCapacity) {
        //操作数+1
        modCount++;//(**继承自AbstractList)
        //判断 最小扩容容量>数组大小 :
        if (minCapacity - elementData.length > 0)
            //扩容:
            grow(minCapacity);
    }
    //扩大至原来的1.5倍或者当前 size+1 ,谁大要谁
    private void grow(int minCapacity) {
         
            int oldCapacity = elementData.length;
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
    
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    

    使用无参构造器和传入值为0的有参构造器扩容的**「区别」**
    前者是在首次 add() 时,会一下子把 elementData 数组的长度扩大到10,之后在第10次操作时才会扩容。
    而后者在执行 add()时,第一次时会扩容为 1,elementData[0]=e;
    第二次仍然会扩容至2,elementData[1]=e;
    第三次仍然会扩容至3,elementData[2]=e;
    第四次仍然会扩容至4,elementData[3]=e;
    第五次仍然会扩容至6,elementData[4]=e;
    ……
    扩容次数在前面几次会明显增加

    remove(int index)

    public E remove(int index) {
        //检查角标是否合法:不合法抛异常
        rangeCheck(index);
        //操作数+1:
        modCount++;
        //获取当前角标的value:
        E oldValue = elementData(index);
        //获取需要删除元素 到最后一个元素的长度,也就是删除元素后,后续元素移动的个数;
        int numMoved = size - index - 1;
        //如果移动元素个数大于0 ,也就是说删除的不是最后一个元素:
        if (numMoved > 0)
            // 将elementData数组index+1位置开始拷贝到elementData从index开始的空间
            System.arraycopy(elementData, index+1, elementData, index, numMoved);
        //size减1,并将最后一个元素置为null
        elementData[--size] = null;
        //返回被删除的元素:
        return oldValue;
    }
    
  • 相关阅读:
    Razor里写函数
    Tuple
    javascript下将字符类型转换成布尔值
    如何成为一名快枪手
    easyUI datagrid 前端假分页
    操作JSON对象
    服务器端将复合json对象传回前端
    将JSON对象转换成IList,好用linq
    操作系统学习笔记三 进程
    如何遍历newtonsoft.json的JObject里的JSON数据
  • 原文地址:https://www.cnblogs.com/Zhoust/p/14994606.html
Copyright © 2020-2023  润新知