Java容器 ArrayList

ArrayList 数组容器类,实现了List,RandomAccess,Cloneable,Serializable四个接口,继承自AbstractList,具有动态扩容,随机访问,可复制,可序列化四个主要功能。

源码分析

核心

之前文章分析过这个接口所需要实现的方法,ArrayList 实现的核心是两个属性

transient Object[] elementData; // 元素载体
private int size; //容器存放的元素大小

elementData 数组用于存放容器,transient 表明容器内的元素不能被序列化。这时候有些同学就表示疑问了,类声明的时候明明是实现了Serializable接口的,表明ArrayList是能够序列化的,此处为什么又要使用transient关键字呢?ArrayList到底能不能被序列化呢?

这里先说结论 ArrayList 是能被序列化的,有兴趣的同学可以做个实验,后面在回顾基础的时候会专门对序列化进行分析。

扩容

ArrayList 有三个构造函数

ArrayList(int initialCapacity)  //指明容器大小
ArrayList() // 默认容器初始化大小
ArrayList(Collection c) // 通过Collection构建容器

这里讲一下第二个构造函数,这个函数只做了一件事,赋空数组。有很多资料说ArrayList 构造函数如果不指定大小,默认是10,这种说法是不严谨的。默认初始化后的大小其实是0,第一次扩容大小为10。

扩容主要发生在Add操作时,每次add 都会检查容量是否够放入新的元素(ensureCapacityInternal方法),如果不够,比较得出一个逻辑最小预期容量(calculateCapacity方法),然后将容量扩至原来的1.5倍(grow方法),如果还是不能满足需求,则将容量扩充至预期容量。

    //入口方法,每次add时先检查 size+1
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            //如果是默认空数组,返回默认容量和minCapacity中较大的值
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
    private void ensureExplicitCapacity(int minCapacity) {
        //增加修改次数
        modCount++;
        // 如果预期容量比实际容量大,则需要扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //尝试扩容至原来的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            //如果1.5倍还不够,则扩容至预期容量
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            //如果预期容量比最大数组容量还大
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

增加的方法有很多,这里重点讲一下增加方法中带有下标的方法,比如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++;
    }
    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

其中要注意的是rangeCheckForAdd(index)检查,插入的下标不能大于数组已有元素大小,否则会报数组越界异常。

删除有两种主要的删除方式,remove(int index)根据下标删除,remove(Object o) 根据元素删除。

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

删除主要是通过遍历找到元素,然后通过平移覆盖元素,最后将尾部元素置为null,等待垃圾回收。这其中有一点需要注意,如果你的元素是int类型,在删除的时候一定要小心。很多人只知道remove(Object o)一种删除,想当然的以为remove(12) 会删除元素为12的元素,其实真正执行的是删除index为12的元素,这是就会参数数组越界问题。

List提供了get(index)方法让我们更快的查询数组中元素,我们平时用的最多的是这样的语句:

for(String s: arrays){
}

这是1.5之后提供的foreach语法,他其实是一种语法糖,通过idea工具反编译class文件(或者javap -verbose命令)

        List list = Arrays.asList(Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3), Integer.valueOf(4), Integer.valueOf(55));
        Iterator var2 = list.iterator();

        while(var2.hasNext()) {
            Integer a = (Integer)var2.next();
            System.out.println(a);
        }

可以看到它经过编译器翻译后,底层使用的是iterator迭代器原理。既然使用的是迭代器原理,首先想到便是Fast-Fail机制。也就是说你不能在循环中删除元素,否则会报ConcurrentModificationException异常。

iterator

arrayList内部类Itr 实现了Iterator接口,这个接口主要有三个方法

  • boolean hasNext() //是否还有下一个元素
  • E next() // 获取下一个元素
  • E remove() //删除当前元素
private class Itr implements Iterator {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

可以看到Iterator通过modCount 和 expectedModCount 来实现 FastFail机制,每次对arraylist的操作都会修改modCount,如果在迭代的过程中ArrayList被修改就会触发快速失败。如果单线程对ArrayList进行删除,可以使用Iterator.remove() 方法,一般不建议在循环的时候删除元素。

ListIterator

Iterator的拓展接口,提供了双向遍历的能力。一般的Iterator是从前向后遍历,ListIterator功能更加全面一点。

subList

ArrayList中有一个方法

    public List subList(int fromIndex, int toIndex) {
        subListRangeCheck(fromIndex, toIndex, size);
        return new SubList(this, 0, fromIndex, toIndex);
    }

返回的是指定区间的子列表,SubList是一个内部类,举一个简单的内部方法

        public E set(int index, E e) {
            rangeCheck(index);
            checkForComodification();
            E oldValue = ArrayList.this.elementData(offset + index);
            ArrayList.this.elementData[offset + index] = e;
            return oldValue;
        }

虽然他是new出来的对象,但可以看到他操作的是原列表的值,相当于原列表的一个视图,所以使用的时候要慎重。

使用规范

阿里的Java操作手册中有如下几点强制规范(这里引用一下):

  • 【强制】ArrayList的subList结果不可强转成ArrayList。
  • 【强制】在 subList 场景中,高度注意对原集合元素的增加或删除,均会导致子列表的遍历、 增加、删除产生 ConcurrentModificationException 异常。
  • 【强制】使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全 一样的数组,大小就是 list.size()。
  • 【强制】使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法。
  • 【推荐】集合初始化时,指定集合初始值大小。

你可能感兴趣的:(Java容器 ArrayList)