美文网首页
ArrayList源码分析

ArrayList源码分析

作者: ClawHub的技术分享 | 来源:发表于2019-01-07 15:58 被阅读0次
    ArrayList简介 image.png
    image.png

    ArrayList 是一个数组队列,相当于 动态数组。与Java中的数组相比,它的容量能动态增长。它继承于AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable接口。
    这里需要注意:
    ①实现Cloneable接口,即覆盖了函数clone(),能被克隆。
    ②实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。
    ③和Vector不同,ArrayList中的操作不是线程安全的!所以,建议在单线程中才使用ArrayList,而在多线程中可以选择Vector或者CopyOnWriteArrayList。[图片上传中...(image.png-bee6d1-1547022393428-0)]

    ArrayList数据结构
    image.png

    ArrayList包含了两个重要的对象:elementData 和 size。
    ① elementData 是"Object[]类型的数组",它保存了添加到ArrayList中的元素。实际上,elementData是个动态数组,我们能通过构造函数 ArrayList(int initialCapacity)来执行它的初始容量为initialCapacity;如果通过不含参数的构造函数ArrayList()来创建ArrayList,则elementData的容量默认是10。elementData数组的大小会根据ArrayList容量的增长而动态的增长,具体的增长方式,请参考源码分析中的ensureCapacity()函数。
    ② size 则是动态数组的实际大小。

    部分源码解读

    ①构造方法

        //创建一个指定了大小的空列表
        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);
            }
        }
    
        // 构造没有指定大小的空列表 ,这里没有初始化,会在第一次add元素时初始化
        public ArrayList() {
            this.elementData = DEFAULTCAPACITY_EMPTY_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;
            }
        }
    

    ②数组扩容

        // 判断array是不是为空,但是这个方法并没有被调用,不知道写了干啥
        public void ensureCapacity(int minCapacity) {
            //缓冲区和默认列表比较,如果相同minExpand为10,不同minExpand为0
            int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                ? 0: DEFAULT_CAPACITY;
            if (minCapacity > minExpand) {
                ensureExplicitCapacity(minCapacity);
            }
        }
    
        // 扩容检查 
        private void ensureCapacityInternal(int minCapacity) {
            // 第一次add操作初始化,如果为空ArrayList,那么初始化容量为10
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
            }
            // 调用这个方法,来判断是不是要扩容了
            ensureExplicitCapacity(minCapacity);
        }
    
        // 判断是否需要扩容
        private void ensureExplicitCapacity(int minCapacity) {
            modCount++;
    
            // 这个时候表示列表现有空间已经被元素填满了,那就要扩容了
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }
    
        // 要分配数组的最大容量
        private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    
        // 扩容
        private void grow(int minCapacity) {
            // overflow-conscious code
            int oldCapacity = elementData.length;
            // (oldCapacity >> 1)就是oldCapacity/2 的意思,也就是总体扩大1.5倍
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
             //判断容量是否到达long int 最大临界值
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
            // 数组浅拷贝,把原来的复制到新的里面
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    
        // 检查是否超过最大容量 0x7fffffff ,看看是否抛出异常
        private static int hugeCapacity(int minCapacity) {
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return (minCapacity > MAX_ARRAY_SIZE) ?
                Integer.MAX_VALUE :
                MAX_ARRAY_SIZE;
        }
    

    由此我们可知:
    当ArrayList容量不足以容纳全部元素时,ArrayList会重新设置容量:新的容量是原来的1.5倍。
    ③增加和删除

        // 在末尾添加元素
        public boolean add(E e) {
            // 扩容检查
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
        }
    
        // 在指定位置添加元素
        public void add(int index, E element) {
            // 越界了没
            rangeCheckForAdd(index);
            // 扩容检查
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            //复制数组,空出index用于插入新元素,其他元素后移
            System.arraycopy(elementData, index, elementData, index + 1,
                             size - index);
            elementData[index] = element;
            size++;
        }
    
        // 删除指定位置的元素
        public E remove(int index) {
            // 越界了没
            rangeCheck(index);
    
            modCount++;
            E oldValue = elementData(index);
    
            // 确定要复制的数组个数
            int numMoved = size - index - 1;
            // index后的数据前移
            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;
        }
    
        // 删除指定位置的元素,这个就是被上面的remove调用的
        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
        }
    
    

    由此我们可知:
    为什么ArrayList 不适合频繁插入和删除操作?就是因为在ArrayList中我们一直会调用 System.arraycopy 这个效率很低的操作来复制数组,所以导致ArrayList在插入和删除操作中效率不高。
    ④缩容

        // 将当前容量值设为实际元素个数
       // 当我们存完元素,要根据元素个数,适当调整array的大小,避免内存浪费
        public void trimToSize() {
            modCount++;
            if (size < elementData.length) {
                elementData = (size == 0)
                  ? EMPTY_ELEMENTDATA
                  : Arrays.copyOf(elementData, size);
            }
        }
    

    ⑤说一下copyof
    我们看到不管是扩容,还是增加删除元素都频频用到copyof方法,那么他是怎么实现的呢?

    首先我们看到array类中copyof方法有这么多 image.png
    我们挑一个看看
    /**
     * @Description 复制指定的数组, 如有必要用 null 截取或填充,以使副本具有指定的长度
     * 对于所有在原数组和副本中都有效的索引,这两个数组相同索引处将包含相同的值
     * 对于在副本中有效而在原数组无效的所有索引,副本将填充 null,当且仅当指定长度大于原数组的长度时,这些索引存在
     * 返回的数组属于 newType 类
     
     * @param original 要复制的数组
     * @param newLength 副本的长度
     * @param newType 副本的类
     * 
     * @return T 原数组的副本,截取或用 null 填充以获得指定的长度
     * @throws NegativeArraySizeException 如果 newLength 为负
     * @throws NullPointerException 如果 original 为 null
     * @throws ArrayStoreException 如果从 original 中复制的元素不属于存储在 newType 类数组中的运行时类型
    
     * @since 1.6
     */
    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }
    

    从代码可知,数组拷贝时调用的是本地方法 System.arraycopy() ;
    Arrays.copyOf()方法返回的数组是新的数组对象,原数组对象仍是原数组对象,不变,该拷贝不会影响原来的数组。

        public static native void arraycopy(Object src,  int  srcPos,
                                            Object dest, int destPos,
                                            int length);
    

    额,System.arraycopy源码就这么点0.0

    ArrayList的遍历方式
    // 迭代器遍历
    Integer value = null;
    Iterator iter = list.iterator();
    while (iter.hasNext()) {
        value = (Integer)iter.next();
    }
    
    // 由于ArrayList实现了RandomAccess接口,它支持通过索引值去随机访问元素。
    Integer value = null;
    int size = list.size();
    for (int i=0; i<size; i++) {
        value = (Integer)list.get(i);        
    }
    
    // for循环
    Integer value = null;
    for (Integer integ:list) {
        value = integ;
    }
    

    使用随机访问(即,通过索引序号访问)效率最高,而使用迭代器的效率最低!

    相关文章

      网友评论

          本文标题:ArrayList源码分析

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