【集合框架】之深入分析ArrayList

在前面的文章【Java集合框架总结】https://blog.csdn.net/moni_mm/article/details/80065576中对集合框架进行概要分析之后,在此结合JDK源码对ArrayList进行深入分析。

提出并解决问题如下:

问题1:elementData非私有化以简化嵌套类的访问,但嵌套类可以透明地访问外围类的所有成员,如何理解这里的简化?
详情见https://blog.csdn.net/moni_mm/article/details/80723880
问题2: transient 表示不可被序列化,为何要这样?

问题3: new ArrayList()和new ArrayList(0)有什么区别,为何要这样?

问题4:最大容量为什么是Integer.MAX_VALUE - 8

问题5:System.arraycopyArrays.copyOf有何区别?


ArrayList

  • 继承自 AbstractList
  • 实现 ListRandomAccessCloneableSerializable 接口
  • 非线程安全
  • 基于数组,默认容量10,扩容为1.5倍

重要对象

默认初始容量:10

private static final int DEFAULT_CAPACITY = 10;

共享的空数组实例,用来初始化空的ArrayList实例

private static final Object[] EMPTY_ELEMENTDATA = {};

当第一个元素被添加时,确定如何扩容

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

存储数组元素的缓冲区,在第一次添加元素时,DEFAULTCAPACITY_EMPTY_ELEMENTDATA会被扩展成10

transient Object[] elementData; // non-private to simplify nested class access

问题1:非私有化以简化嵌套类的访问,如何理解?

内部类:SubListget方法使用了ArrayListelementData
因为它隐式地保存了外部类的引用


public E get(int index) {
            rangeCheck(index);
            checkForComodification();
            return ArrayList.this.elementData(offset + index);
        }

嵌套类:ArrayListSpliterator,静态类,不能直接访问外部的非静态elementData,所以持有了外部类的list实例,可以访问list的任意成员,那为什么不声明为private呢?

编译期间自动会为内部类生成access&XX方法
提高JVM底层访问效率

问题2: transient 表示不可被序列化,为何要这样?

transient告诉JVM自己不可被序列化,并且ArrayList自己重写了readObjectwriteObject方法,只序列化了实际存储的那些元素,而不是整个数组,避免浪费



真实大小

private int size;

最大容量
为整数最大值-8

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

构造方法

无参

  • DEFAULTCAPACITY_EMPTY_ELEMENTDATA初始化
  • 第一次添加元素时 以默认容量10 构造
public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

有参

  • 以给定的容量new一个Object数组初始化elementData
  • 为负抛异常
  • 为0用EMPTY_ELEMENTDATA初始化elementData
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);
        }
    }

问题3: new ArrayList()和new ArrayList(0)有什么区别,为什么要这样?

  • 内部都是以空数组初始化,无本质区别
  • new ArrayList()以DEFAULTCAPACITY_EMPTY_ELEMENTDATA初始化
  • new ArrayList(0)以EMPTY_ELEMENTDATA初始化

重要方法

添加元素add

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

添加元素时,首先要确保内部容量安全,再向末端添加元素,然后size++,注意modCount也要自增,这个是用来标识修改次数。

如何确保容量安全呢?

  • minCapacity表示所需最小容量,值为size+1
  • 计算容量,返回所需容量:如果elementData空数组,返回10
  • 确定明确容量:如果所需容量大于当前数组长度,进行被动扩容
private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

扩容

主动扩容:
ArrayList向外提供了主动扩容方法,可减少扩容次数,提高程序效率

  • 如果是空数组,扩容为10
  • 不为空数组就调用grow方法扩容至所需容量
public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            // any size if not default element table
            ? 0
            // larger than default for default empty table. It's already
            // supposed to be at default size.
            : DEFAULT_CAPACITY;

        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }

被动扩容:

  • 扩容至当前容量的1.5倍,用newCapacity表示
  • 扩容后仍不满足,直接扩容成minCapacity
  • 扩容后newCapacity为负,表示溢出,抛出异常
  • 扩容后newCapacity超过最大容量Integer.MAX_VALUE-8,就扩容成 Integer.MAX_VALUE;否则扩容成Integer.MAX_VALUE-8
  • Arrays.copyOf复制数组
    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);
    }
private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

问题4:最大容量为什么是Integer.MAX_VALUE - 8

第一种解释:有些虚拟机在数组中保留了一些头信息。避免内存溢出

第二种解释:用来存放数组对象的元数据。

https://stackoverflow.com/questions/35756277/why-the-maximum-array-size-of-arraylist-is-integer-max-value-8


另一个add函数
使用System.arraycopy进行复制

public static native void arraycopy(Object src,  int  srcPos, Object dest, int destPos,int length);
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++;
    }

问题5:System.arraycopyArrays.copyOf有何区别?

Arrays.copyOf最终调用了

System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));

猜你喜欢

转载自blog.csdn.net/moni_mm/article/details/80722011