• ArrayList详解


    一. 引言

    ArrayList是Java集合框架中比较常用的数据结构了.继承自AbstractList,实现了List接口.底层基于数组实现容量大小动态变化.允许null的存在.同时还实现了RandomAccess,Cloneable,Serializable接口,所以ArrayList是支持快速访问,赋值,序列化的

    二. 成员变量

    ArrayList底层是基于数组来实现容量大小动态变化的

    private int size; // 实际元素个数
    
    transient Object[] elementData; // 存放的元素集合
    
    private  static  final  int DEFAULT_CAPACITY = 10; // 默认初始大小10
    
    protected  transient  int modCount = 0; //记录对List操作的次数,主要使用在Iterator中,防止迭代过程中集合被修改
    
    // 下面两个变量用在构造函数中,判断第一次添加元素时知道从空的构造函数还是有参构造函数被初始化的
    private static final Object[] EMPTY_ELEMENTDATA = {};
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    

    注意:上面size是指elementData中实际有多少个元素,而elementData.lenth为集合容量,表示最多可容纳多少个元素

    三. 构造函数

    无参构造

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

    构造一个初始容量为10的空的list集合,但构造函数只是给elementData赋值了一个空的数组,其实是在第一次添加元素时扩大至10的

    有参构造

    构造一个初始容量大小为initialCapacity的ArrayList

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

    由以上代码可以看出,使用无参构造函数时是吧DEFAULTCAPACITY_EMPTY_ELEMENTDATA赋值给elementData.当initalCapacity为零时则是把_EMPTY-ELEMENTDATA赋值给elementData.当initialCapacity大于零时初始化一个大小为initalCapacity的object数组赋值给elementData

    指定Collection构造ArrayList的构造函数

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

    将Collection转化为数组并赋值给elementData,把elementData中元素的个数赋值给size.如果size不为零,则判断elementData的class类型是否为Object[],不是的话则做一次转换.如果size为零,则吧EMPTY_ELEMENTDATA赋值给elementData,相当于new ArrayList(0);

    四. 主要方法

    add

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        ensureExplicitCapacity(minCapacity);
    }
    
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    

    以上解释一下,第一个方法调第二个方法,第二个再调第三个方法.每次添加元素到集合中时都会先确认下集合容量大小.然后将size自增1.ensureCapacityInternal函数中判断结果elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA就取DEFAULT_CAPACITYminCapacity的最大值也就是10.这就是EMPTY_ELEMENTDATADEFAULTCAPACITY_EMPTY_ELEMENTDATA的区别所在.下面是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);
    }
    

    很简单明了的一个函数,默认将扩容至原来容量的 1.5 倍。但是扩容之后也不一定适用,有可能太小,有可能太大。所以才会有下面两个 if 判断。如果1.5倍太小的话,则将我们所需的容量大小赋值给newCapacity,如果1.5倍太大或者我们需要的容量太大,那就直接拿 newCapacity = (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE 来扩容。然后将原数组中的数据复制到大小为 newCapacity 的新数组中,并将新数组赋值给 elementData。

    remove

    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;
    }
    
    public boolean remove(Object o) {
        if (o == 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 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
    }
    

    当我们调用 remove(int index) 时,首先会检查 index 是否合法,然后再判断要删除的元素是否位于数组的最后一个位置。如果 index 不是最后一个,就再次调用 System.arraycopy() 方法拷贝数组。说白了就是将从 index + 1 开始向后所有的元素都向前挪一个位置。然后将数组的最后一个位置空,size - 1。如果 index 是最后一个元素那么就直接将数组的最后一个位置空,size - 1即可。 当我们调用 remove(Object o) 时,会把 o 分为是否为空来分别处理。然后对数组做遍历,找到第一个与 o 对应的下标 index,然后调用 fastRemove 方法,删除下标为 index 的元素。其实仔细观察 fastRemove(int index) 方法和 remove(int index) 方法基本全部相同。

    get

    public E get(int index) {
        rangeCheck(index);
        return elementData(index);
    }
    

    由于ArrayList底层是基于数组实现的,所以获取元素就相当简单了,直接调用数组随机访问即可

    我会写代码+代码改变世界=我会改变世界! 为什么要改变世界? 如果无法改变世界,那么,世界就会改变我......
  • 相关阅读:
    弱省胡策 Magic
    CF917D Stranger Trees
    【弱省胡策】Round #5 Count
    【BZOJ2117】 [2010国家集训队]Crash的旅游计划
    「2017 山东一轮集训 Day5」苹果树
    【SDOI2017】天才黑客
    【JXOI2018】守卫
    小程序两种图片加载方式
    小程序之底部栏设计
    小程序之全局变量的设置及使用
  • 原文地址:https://www.cnblogs.com/chougoushi/p/14503545.html
Copyright © 2020-2023  润新知