学习笔记——ArrayList总结

ArrayList可以说是一种很常用很常用的数据结构了。

ArrayList底层是通过数组实现的。可以看下源码(基于JDK1.8)

首先从构造函数说起,总共有三种构造函数:

    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() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

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

其中最常用的就是无参构造函数。

private static final int DEFAULT_CAPACITY = 10;

private
static final Object[] EMPTY_ELEMENTDATA = {}; private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

无参构造函数创建的是DEFAULTCAPACITY_EMPTY_ELEMENTDATA,这里之所以要区分出EMPTY_ELEMENTDATA,主要是因为当DEFAULTCAPACITY_EMPTY_ELEMENTDATA扩容时,

会优先按默认大小(DEFAULT_CAPACITY)进行扩容。具体可以看这个方法:

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

        ensureExplicitCapacity(minCapacity);
    }

这样就将原本初始化时就要做的内存分配推迟到了真正使用时,算是一点优化吧。

 

ArrayList的存储

transient Object[] elementData;

在ArrayList中elementData是实际用来存放数据的数组。

默认大小是10,当数组满后,按1.5倍进行扩容。

代码如下:

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

但这样很容易造成内存的浪费,特别是当ArrayList比较大的时候,比如当前数组大小是20000,已填满,当再添加一个新元素时,就需要扩容成30000。但实际真实只使用了20001,9999就是浪费的。

对此ArrayList提供了trimToSize方法。此外,ArrayList之所以将elementData定义成transient,自己实现序列化和反序列化的方法也是为了序列化的时候,只序列化数组中实际使用的部分。减少了序列化的时间,和序列化后的文件大小。

 

可变长

其实数组压根就是不可变的,初始化长度是多少就是多少,ArrayList之所以可变长,都是通过创建一个新数组,将原数组的数据复制过去实现的。这是有性能开销的。ArrayList中的remove,指定位置add方法,甚至trimToSize等很多方法都是依靠这个数组复制的方式实现的。需要注意,当可以预估到需要ArrayList中元素比较多的时候,最好是在初始化的时候直接指定ArrayList的大小。否则会导致频繁的扩容,影响性能。

 

随机访问

ArrayList实现了RandomAccess接口。这是个标记接口。说明当前这个实现类是支持快速随机访问的。接口说明中给出了一个例子,按索引循环会比使用迭代器更快。

 

和Vector区别

 vector可以看成是线程安全的ArrayList,内部代码实现大部分都是一样的,只不过vector的方法都加上了synchronized锁来保证线程安全。

此外vector默认按2倍扩容,也支持初始化的时候自定义每次扩容的大小。

源码如下:

    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
View Code

其中capacityIncrement就是自定义的扩容大小。

posted @ 2018-12-21 17:11  boogieman  阅读(374)  评论(0编辑  收藏  举报