• ArrayList简单学习


    类声明:

    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    {

    AbstractList是个抽象类,RandomAccess是个给List用的标记接口,为了指明这个容器支持快速(一般是常量时间复杂度)的随机访问。

    List接口

    public interface List<E> extends Collection<E> {
        boolean add(E e);
        void add(int index, E element);
        boolean remove(Object o);
        E remove(int index);
        E set(int index, E element);
        E get(int index);
        ...
    }

    ArrayList的类变量

    private static final int DEFAULT_CAPACITY = 10;
    private static final Object[] EMPTY_ELEMENTDATA = {};
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    transient Object[] elementData;
    private int size;

    第一个是默认容量;

    第二个是当用户指定ArrayList的容量为0的时候,返回的一个数组。

    第三个是一个空数组实例

      - 当用户没有指定 ArrayList 的容量时(即调用无参构造函数),返回的是该数组==>刚创建一个 ArrayList 时,其内数据量为 0。
      - 当用户第一次添加元素时,该数组将会扩容,变成默认容量为 10(DEFAULT_CAPACITY) 的一个数组===>通过 ensureCapacityInternal() 实现
      -它与 EMPTY_ELEMENTDATA 的区别就是:该数组是默认返回的,而后者是在用户指定容量为 0 时返回

    第四个是真正保存数据data的数组,ArrayList 的容量就是该数组的长度

    第五个显然就是ArrayList里面存了的元素的个数了。

    构造函数

    无参:

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

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

    就根据这个传进来的容量,new一个相应的Object数组。

    如果传进来的是0,就让elementData指向说好的那个代表这个状态的空数组。

    add方法之add(E e)

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

    介个ensureCapacityInternal方法很明显,就是要确保底层数组的capacity能够装得下原来的size + 1后的数据。

    在确保操作完成后,就在末尾添加这个元素。

    然后看这个ensureCapacityInternal方法:

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

    第一个if中,意思是这是不是用户调用默认构造器构造了ArrayList实例后第一次添加元素,是的话,此时底层数组的length是为0的。那么就更新这个minCapacity,如果传进来的minCapacity比10小,就变成10.

    所以这个方法的意思是,确定正确的底层数组的capacity值。

    再调用ensureExplicitCapacity方法:

     

    ensureExplicitCapacity:

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

    这个方法意思是,真正去确保底层数组有这个minCapacity的length。

    如果现在这个底层数组的length还没到这个需要有的最小capacity的值,就要扩容。

    这个modCount++是因为ArrayList的结构要变了,如果扩容的话,底层数组的length变而且添加了一个新值;如果不用扩容,则因为添加了新值,所以结构变化。

    然后看这个grow方法:

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

    由这个

      int newCapacity=oldCapacity+ (oldCapacity >> 1);

    可见,默认的扩容方式应该是原来的数组length + 原来的length/2。

    如果这个默认的加大方法不足以达到minCapacity的要求,就直接newCapacity为minCapacity,如果newCapacity比最大值还大,就用hugeCapacity方法返回一个符合固定的值(如果很大就返回Integer.MAX_VALUE)。

    然后就调用Arrays数组的一个复制数组的方法,elementData指向一个新的容量是newCapacity的新Object数组。(注意还没插入新值的),然后回到add方法里面在已经够容量的数组里面插入新值就搞定。

    add(int index, E element)方法

    public void add(int index, E element) {
        rangeCheckForAdd(index);
    
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

    第一行是越界检查的方法。

    第二行上面介绍了,是保证底层数组的length足够装下原来的size + 1个数据,调用后可能什么都没做,也可能扩容了。

    第三行是调用System的一个static方法,这是个复制数组的方法,把index开始的size - index个元素,移到(同一个数组)的index + 1到index + 1 + (size - index)的位置去,也就是index开始的所有元素往后挪咯。(性能开销就是在这些操作)

    然后再在index的位置赋值咯,size即ArrayList容纳的元素个数加一。

    remove(int index)方法

    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);
        elementData[--size] = null; // clear to let GC do its work
    
        return oldValue;
    }

    前面两行就不解释了,第三行获得要删除的那个元素。

    numMoved——删除这个元素后,index后面的需要往前挪的元素的个数。

    numMoved表示在执行删除操作时数组需要移动的元素个数,将elementData数组中从index后一位开始的所有元素(即numMoved个元素)往前"移动"1位(这样一移动,index位置的元素会被后面的元素覆盖,间接起到了删除元素的作用),然后把最后的那个元素置空,(然后gc就可以处理了)同时将size变量减1。

     还有个remove(Object o)差不多:

    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])) {
                        fastRemove(index);
                        return true;
                    }
            }
            return false;
        }
    
        /*
         * 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
        }

    有个删null对象的操作,然后还有个fastRemove而已,不多讲了。

    get和set方法

    直接用数组的方式get和set,很方便。

    总结

    • ArrayList底层实现是数组。
    • 当使用无参数构造函数创建ArrayList对象时,ArrayList对象中的数组初始长度为0(是一个空数组)。
    • ArrayList的扩容策略是每次都增加当前数组长度的一半(非固定分配)。
    • ArrayList的扩容方式是直接创建一个新的数组,并将数据拷贝到新数组中。


  • 相关阅读:
    Elasticsearch系列(4):基本搜索
    Elasticsearch系列(3):Elasticsearch操作入门
    Elasticsearch系列(2):安装Elasticsearch(Linux环境)
    禅道之需求追踪和多角色协作流程
    Linux下Redis服务器搭建
    Sql Server 2008日志满的解决办法
    sql server2008数据库迁移的两种方案
    Linux下Jenkins服务器搭建
    Linux下RabbitMQ服务器搭建
    Linux下GitLab服务器搭建
  • 原文地址:https://www.cnblogs.com/wangshen31/p/10457158.html
Copyright © 2020-2023  润新知