美文网首页
Java容器 ArrayList

Java容器 ArrayList

作者: zeody | 来源:发表于2019-05-15 22:36 被阅读0次

    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<? extends E> 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<Integer> 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<E> {
            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<E> 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

          本文链接:https://www.haomeiwen.com/subject/eudvaqtx.html