ArrayList详解——逐行分析源码版【还差一丢丢,会尽快补上】

ArrayList详解

初识ArrayList

  1. ArrayList的继承和实现的结构为
ArrayList<E> extends AbstractList<E>
	 implements List<E>, RandomAccess, Cloneable, java.io.Serializable
  1. 抽象类AbstractList中定义了modCount属性,这个参数是指当前列表的结构被修改的次数
    • 结构上的修改指的是那些改变了list的长度大小或者使得遍历过程中产生不正确的结果的其它方式
    • 请留心每一个操作ArrayList中改变存储内容或者改变结构的方法,都会对modCount属性进行++操作
		protected transient int modCount = 0;
		/**
         * The modCount value that the iterator believes that the backing
         * List should have.  If this expectation is violated, the iterator
         * has detected concurrent modification.
         */
        int expectedModCount = modCount;
  1. ArrayList在创建对象的时候并不会初始化容量,当在add或者其他方法调用的时候才会初始化容量【参见精解1】
  2. System.arraycopy复制数组为浅克隆【参见精解2】
  3. System.arraycopy是一个native函数,复制数组时并不是传统意义的——通过数组下标从最后一位依次向后移动一位,而是直接对内存中的数据块进行复制的,是一整块一起复制的,这样效率会大大提升【参见精解2】

成员变量

    //序列化ID
    private static final long serialVersionUID = 8683452581122892189L;

    //ArrayList的默认初始容量大小
    private static final int DEFAULT_CAPACITY = 10;

    //空对象数组,用于空实例的共享空数组实例
    private static final Object[] EMPTY_ELEMENTDATA = {};

    //空对象数组,使用无参构造器实例化对象时,将此值赋给elementData
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    //存放当前数据,ArrayList底层使用的是数组来保存元素
    //ArrayList实现Serializable接口就是允许序列化的,那么为什么该变量却用transient修饰呢?【参见精解3】
    transient Object[] elementData;

    //记录list中存放数据的大小,而不是指数组的length
    //比如 elementData = {"gao","shao",null,null,null}
    //elementData.length 的结果是5;而size保存的数值是2
    private int size;

	//集合所允许的最大容量
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

方法列表

按照定义顺序介绍

public ArrayList(int initialCapacity)

—— 自定义初始化容器大小
	//自定义初始化容器大小
    //当传入的值<0时,抛出异常
    //当传入的值=0时,使用默认大小的数组初始化
    //当传入的值>0时,根据自定义大小初始化容器
    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()

—— 无参构造
	//无参构造方法,使用默认大小的数组初始化
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

public ArrayList(Collection<? extends E> c)

—— 通过传入集合来初始化当前集合
	//通过传入集合来初始化当前集合
	//将传入的集合中的元素引用复制到当前生成的ArrayList集合中,并且对原先集合中元素的任何修改都会体现到当前集合上【参见精解4】
    public ArrayList(Collection<? extends E> c) {
    	//将传入的集合转为Object数组赋值给elementData
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            //已经通过上面的c.toArray()将放回的Object[]赋值到elementData上了,为什么还要进行判断呢?
            //因为c.toArray()返回的内容类型有可能不是Object[]数组,这是一个小bug,如果不进行判断处理的话,客户使用时会有隐患【参见精解5】
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            //将空数组赋值给elementData
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

public void trimToSize()

—— 将此ArrayList实例的容量修剪为列表的当前size的大小。
    //将此ArrayList实例的容量修剪为列表的当前size的大小。
    //简言之:去除ArrayList的末尾的空余容量
    public void trimToSize() {
        modCount++;
        //如果ArrayList中存在空余容量进行修剪,从而去掉空余容量
        if (size < elementData.length) {
            elementData = (size == 0)
                    ? EMPTY_ELEMENTDATA
                    : Arrays.copyOf(elementData, size);
        }
    }

public void ensureCapacity(int minCapacity) //手动对集合进行扩容

—— 手动对集合进行扩容
	//手动对集合进行扩容
	//如果参数大于低层数组长度的1.5倍,那么这个数组的容量就会被扩容到这个参数值,如果参数小于低层数组长度的1.5倍,那么这个容量就会被扩容到低层数组长度的1.5倍。原因参见【参见精解6】
    public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                ? 0
                : DEFAULT_CAPACITY;

        if (minCapacity > minExpand) {
            //方法内部判断是否需要扩容,需要扩容则进行扩容
            ensureExplicitCapacity(minCapacity);
        }
    }

· private static int calculateCapacity(Object[] elementData, int minCapacity)

—— 获取需要的最小容量
	//获取需要的最小容量
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        //如果elementData是默认的空数组,则取默认大小【10】与minCapacity两者的最大值
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        //否则返回minCapacity
        return minCapacity;
    }

· private void ensureCapacityInternal(int minCapacity)

—— 判断是否需要扩容,如果需要进行扩容
	//判断是否需要扩容,如果需要进行扩容
    //和方法ensureExplicitCapacity()的区别在于,
    //在数组elementData为默认数组时,当前方法会根据传入的需要的最小容量与初始化大小10做比较
    private void ensureCapacityInternal(int minCapacity) {
        //calculateCapacity(elementData, minCapacity)  获取需要的最小容量
        //有人一定会说,最小容量不就是minCapacity吗?为什么还要计算呢?
        // 一般情况是你想的这样子,不过你忽略了--当ArrayList刚开始创建并且size为0的情况,如果根据传入的最小容量小于集合的初始化大小时,应该以我的初始化大小为准
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

· private void ensureExplicitCapacity(int minCapacity)

—— 判断是否需要扩容,如果需要进行扩容
	//判断是否需要扩容,如果需要进行扩容
    //注意:当需要的容量等于当前容量时,会进行扩容(例如:当请朋友到家里吃饭,做的饭一点没剩下,总觉得怪怪的)
    private void ensureExplicitCapacity(int minCapacity) {
        //暂时不知道干啥用的
        //如果需要扩容modCount自增,这个参数是指当前列表的结构被修改的次数
        modCount++;
        //判断elementData容量是否够用,不够进行扩容
        if (minCapacity - elementData.length > 0)
            //扩容方法
            grow(minCapacity);
    }

· private void grow(int minCapacity)

—— 扩容方法
	//扩容方法
    private void grow(int minCapacity) {
        //记录扩容前的数组长度
        int oldCapacity = elementData.length;

        //默认将原数组的长度扩大1.5倍作为扩容后数组的长度
        int newCapacity = oldCapacity + (oldCapacity >> 1);

        //如果扩容后的容量还不够用
        if (newCapacity - minCapacity < 0)
            //将当前所需容量的大小作为扩容后的大小
            newCapacity = minCapacity;
        //如果扩容后的容量 大于 数组允许的最大大小
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            // 将扩容长度设置为最大可用长度
            newCapacity = hugeCapacity(minCapacity);
        //根据当前需要的容量将原先数组进行扩容
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

· private static int hugeCapacity(int minCapacity)

—— 将扩容长度设置为最大可用长度
	// 将扩容长度设置为最大可用长度
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        //为什么定义了这个属性之后MAX_ARRAY_SIZE,还要赋值int的最大值呢?【一直没有想到合理解释】
        return (minCapacity > MAX_ARRAY_SIZE) ?
                Integer.MAX_VALUE :
                MAX_ARRAY_SIZE;
    }

public int size()

—— 返回当前集合大小
	//返回当前集合大小
    //注:存放数据部分的大小,不是elementData[]的容量
    public int size() {
        return size;
    }

public boolean isEmpty()

—— 判断当前集合是否为空
	//判断当前集合是否为空
    public boolean isEmpty() {
        return size == 0;
    }

public boolean contains(Object o)

—— 判断是否包含某个元素
	//判断是否包含某个元素
    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

public int indexOf(Object o)

—— 查找集合中某个元素的索引【正向查找】
	//【正向】查找集合中某个元素的索引【从0开始】,当有多个重复的元素时,只返回【第一个】元素所在的索引
    //return -1;不存在该元素
    //因为ArrayList允许null值,所以此处也可以查询集合中null值的索引
    public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

public int lastIndexOf(Object o)

—— 查找集合中某个元素的索引【逆向查找】
	//【逆向】查找集合中某个元素的索引【从0开始】,当有多个重复的元素时,只返回【最后一个】元素所在的索引
    //return -1;不存在该元素
    //因为ArrayList允许null值,所以此处也可以查询集合中null值的索引
    public int lastIndexOf(Object o) {
        if (o == null) {
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

public Object clone()

—— 将该ArrayList实例克隆一份进行返回
	//将该ArrayList实例克隆一份进行返回
    //注意:这里克隆是浅克隆
    public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            throw new InternalError(e);
        }
    }

public Object[] toArray()

—— 将集合转为数组返回(尽量不要使用该方法,而使用 toArray(T[] a)方法 )
	//将集合转为数组返回
    //尽量不要使用该方法【参见详解5】
    public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

public T[] toArray(T[] a)

—— 将集合转为数组返回
	//warning警告虽然不是会让项目停止,但是却是不规范,留下隐患,
    // 而@suppresswarnings就是告诉编译器忽略警告。不用在编译完成后出现警告。
    // ("unchecked")告诉编译器忽略 unchecked 警告信息。
    @SuppressWarnings("unchecked")
    //将集合转为数组时推荐使用该方法【参见5】
    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

E elementData(int index)

—— 获取索引处的元素【未检查索引是否越界】
	//获取索引处的元素,未检查索引是否越界
    //注意此处的未检查是未检查如下情况: size < index && index < elementData.length【即为在数组elementData定义范围内而在ArrayList的size范围外的时候】
    E elementData(int index) {
        return (E) elementData[index];
    }

public E get(int index)

—— 获取索引处的元素
	//获取索引处的元素
    public E get(int index) {
        //检查索引是否越界
        rangeCheck(index);

        return elementData(index);
    }

public E set(int index, E element)

—— 将指定位置的元素替换为传入的元素
	//将指定位置的元素替换为传入的元素
    //返回值为被替换的元素
    public E set(int index, E element) {
        //检查索引是否越界
        rangeCheck(index);
        //获取被替换的元素
        E oldValue = elementData(index);
        //存新元素
        elementData[index] = element;
        //返回被替换的元素
        return oldValue;
    }

public boolean add(E e)

—— 添加单个元素
	//添加单个元素,添加元素之前会先检查容量,容量不足进行扩容
    public boolean add(E e) {
        //添加元素之前的容量时size,那么添加完之后的容量最小为:size+1
        //判断是否需要扩容,如果需要进行扩容
        ensureCapacityInternal(size + 1);
        //将元素添加到集合中
        elementData[size++] = e;
        return true;
    }

public void add(int index, E element)

—— 在指定位置添加元素
	//在指定位置添加元素
    public void add(int index, E element) {
        //判断下标是否越界,如果是抛出IndexOutOfBoundsException
        //注意此处不是数组下标越界
        rangeCheckForAdd(index);

        //判断是否需要扩容,如果需要进行扩容
        ensureCapacityInternal(size + 1);

        // 拷贝数组,将下标后面的元素全部向后移动一位
        System.arraycopy(elementData, index, elementData, index + 1,
                size - index);

        // 将元素插入到当前下标的位置
        elementData[index] = element;
        size++;
    }

public E remove(int index)

—— 删除元素,根据索引位置查找
	//移除某个位置的元素,返回被移除的元素
    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);

        //将数组中空出的最后一位的引用置为null,之前数组最后一位引用的对象不再有指向,就可以被GC回收了
        elementData[--size] = null;

        return oldValue;
    }

public boolean remove(Object o)

—— 删除元素,根据元素内容查找
	//删除元素
    //当有多个相同元素时删除索引最低的一个元素
    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;
    }

private void fastRemove(int index)

—— 删除元素,跳过边界检查且不返回已删除值的私有删除方法
	//跳过边界检查且不返回已删除值的私有删除方法
    //用户ArrayList自己使用,减少了必要的检查从而提升效率
    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
    }

public void clear()

—— 清空集合
	//清空集合
    public void clear() {
        modCount++;
        //数组全部置为null,数组中的元素没有了引用,就可以被GC回收
        //这里没有对数组容量进行处理,也就是说清空之后的容量与清空之前的容量相同,只是将elementData[]中的所有元素置为null
        for (int i = 0; i < size; i++)
            elementData[i] = null;
        size = 0;
    }

public boolean addAll(Collection<? extends E> c)

—— 在集合末尾添加多个元素
	//在集合末尾添加多个元素
    public boolean addAll(Collection<? extends E> c) {
        //将集合转为Object[]
        Object[] a = c.toArray();
        //记录数组长度
        int numNew = a.length;
        //判断是否需要扩容,如果需要进行扩容
        ensureCapacityInternal(size + numNew);
        //拷贝数组:System.arraycopy是一个native函数,复制数组时并不是传统意义的——通过数组下标从最后一位依次向后移动一位
        //而是直接对内存中的数据块进行复制的,是一整块一起复制的,这样效率会大大提升
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

public boolean addAll(int index, Collection<? extends E> c)

—— 在集合的index位置处添加多个元素
	///在集合的某个位置【index】添加多个元素
    public boolean addAll(int index, Collection<? extends E> c) {
        //判断是否索引下标越界,检查完之后index的范围为: 0 <= index && index <= size
        rangeCheckForAdd(index);
        Object[] a = c.toArray();
        int numNew = a.length;
        //判断是否需要扩容,如果需要进行扩容
        ensureCapacityInternal(size + numNew);

        int numMoved = size - index;

        //当index索引位置在集合中,即为 0 <= index && index < size
        //将 (index,size) 范围内的数据向后复制,腾出传入的集合c所需的位置
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                    numMoved);
        //如果:0 <= index && index < size ,则将传入的集合放入前两行代码腾出的位置
        //index = size,则会在ArrayList的末尾放入集合c【也就是数组a】
        System.arraycopy(a, 0, elementData, index, numNew);

        size += numNew;
        return numNew != 0;
    }

protected void removeRange(int fromIndex, int toIndex)

—— 移除索引范围内的元素
	//移除索引范围内的元素
    protected void removeRange(int fromIndex, int toIndex) {
        modCount++;

        //直接将toIndex之后位置的元素移动到fromIndex处即可
        int numMoved = size - toIndex;
        System.arraycopy(elementData, toIndex, elementData, fromIndex,
                numMoved);

        //将多余的末尾元素制null,没有引用的对象就可以被GC回收了
        int newSize = size - (toIndex-fromIndex);
        for (int i = newSize; i < size; i++) {
            elementData[i] = null;
        }

        size = newSize;
    }

private void rangeCheck(int index)

—— 检查索引是否越界,适用于get/set/remove方法
	//检查索引是否越界,适用于get/set/remove方法
    //方法内没有进行负数校验,而是交给了Array来完成,因为get/set/remove方法操作数组时,都会对数组下标越界进行检查
    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

private void rangeCheckForAdd(int index)

—— 检查索引是否越界,适用于add方法
	//检查索引是否越界,适用于add方法
    //同样的检查为什么要和get/set/remove分开呢?
    //因为向集合中添加元素前可能会对其进行扩容,如果把负数的检查像get/set/remove一样交由Array来做的话,可能造成不必要的扩容
    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

private String outOfBoundsMsg(int index)

—— 定义“索引越界异常”打印格式
	//定义“索引越界异常”打印格式
    private String outOfBoundsMsg(int index) {
        return "Index: "+index+", Size: "+size;
    }

public boolean removeAll(Collection<?> c)

—— 从当前集合中【移除】传入的集合中的所有元素
	//从当前集合中  【移除】传入的集合中的  所有元素
    public boolean removeAll(Collection<?> c) {
        //检查 c 是否为null,如果为null,抛出NullPointerException
        Objects.requireNonNull(c);
        //批处理移除
        return batchRemove(c, false);
    }

public boolean retainAll(Collection<?> c)

—— 从当前集合中【保留】传入的集合中的所有元素,并将其余的所有元素移除
	//从当前集合中  【保留】传入的集合中的  所有元素  将其余的所有元素移除
    public boolean retainAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, true);
    }

private boolean batchRemove(Collection<?> c, boolean complement)

—— 批处理从ArrayList移除【或保留】集合c中的元素
	//批处理从ArrayList移除【或保留】集合c中的元素
    //complement == true-->【保留】集合c【存在】内容,其余部分全部【移除】
    //complement == false-->【保留】集合c【不存在】内容,其余部分全部【移除】
    //注意:该方法是【重新整理】需要保留的数据
    private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData;
        // r--> 当前元素遍历的索引
        // w--> 需要保留的数据的索引
        int r = 0, w = 0;
        //是否有改变的标识
        boolean modified = false;
        try {
            for (; r < size; r++)
                if (c.contains(elementData[r]) == complement)
                    //保存符合条件的数据
                    elementData[w++] = elementData[r];
        } finally {
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            //正常情况下应该是 r 等于 size 的,但是c.contains()有可能抛出异常,当抛出异常时循环终止,所以就不相等了
            //那么未循环的元素如何处理呢?【删除还是保留?亦或是选择性保留?】
            //jdk的处理方式为:将未处理的元素保留下来,不进行处理
            if (r != size) {
                System.arraycopy(elementData, r,
                        elementData, w,
                        size - r);
                //w 索引移动相应位置
                w += size - r;
            }
            //w记录的索引之前是需要保存的元素,之后则置为null
            if (w != size) {
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;
                size = w;
                modified = true;
            }
        }
        return modified;
    }

private void writeObject(ObjectOutputStream s) throws IOException

—— 将ArrayList写入到流中
	//将ArrayList写入到流中【详见精解9】
    private void writeObject(ObjectOutputStream s)
            throws IOException{
        // 记录当前的modCount
        int expectedModCount = modCount;
        //序列化非静态和非transient字段【个人感觉该方法包括序列化size,目前不知道为啥后面又定义了s.writeInt(size);】
        s.defaultWriteObject();

        // Write out size as capacity for behavioural compatibility with clone()
        //把数组大小序列化
        s.writeInt(size);

        // Write out all elements in the proper order.
        //按正确顺序序列化数组中所有的元素
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }

        //如果在写入流期间modCount发生变化,则抛出并发修改异常
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException

—— 从流中读出并还原ArrayList
	//反序列化,过程类似于序列化
    private void readObject(java.io.ObjectInputStream s)
            throws IOException, ClassNotFoundException {
        //先初始化空数组
        elementData = EMPTY_ELEMENTDATA;

        // Read in size, and any hidden stuff
        //反序列化非静态和非transient字段
        s.defaultReadObject();

        // Read in capacity
        s.readInt(); // ignored

        if (size > 0) {
            // be like clone(), allocate array based upon size not capacity
            //获得所需要的最小的容量大小
            int capacity = calculateCapacity(elementData, size);
            //SharedSecrets.getJavaOISAccess()返回类型为【sun.misc.JavaOISAccess】
            //但是个空方法,没有return,只有如下注释,之后待我细细研究再进行补充【 /* compiled code */】
            SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
            //判断是否需要扩容,如果需要进行扩容
            ensureCapacityInternal(size);

            Object[] a = elementData;
            // Read in all elements in the proper order.
            //将数据依次读出
            for (int i=0; i<size; i++) {
                a[i] = s.readObject();
            }
        }
    }

精解列表

1-ArrayList什么时候将存放数据的数组elementData初始化到初始大小10?
  • 在容器进行扩容时才可能会将容器初始化到默认大小10【可以是用户调用扩容方法ensureCapacity(int minCapacity),也可能是新增数据时】
  • 有三个构造方法均没有将容器容量初始化到10
 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) {
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
  • 全局搜索该属性DEFAULT_CAPACITY,只有这两个方法中存在 ensureCapacity(int minCapacity)calculateCapacity(Object[] elementData, int minCapacity),这两个方法均为扩容时调用
2-为什么说System.arraycopy复制数组为浅复制(浅克隆)?

3-ArrayList实现序列化接口就是为了在网络上传输,那为什么存放数据的elementData,要用transient修饰呢?writeObject()/readObject()又是做什么的呢?

  • ArrayList实际上是动态数组,如果数组大小设为100,而实际只放了一个元素,那就会序列化99个null元素。为了保证不会序列化一堆null,ArrayList把元素数组elementData用transient修饰,并自己定义writeObject()/readObject()进行序列化和反序列化。
  • 如果没有定义writeObject()/readObject(),默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法
  • writeObject()/readObject()是如何进行调用的呢?为了节省篇幅该处只举出序列化的例子,并且只给出调用栈和关键代码,可自行跟jdk源码
    • 序列化的调用栈writeObject【ObjectOutputStream】—> writeObject0【ObjectOutputStream】 —>writeOrdinaryObject【ObjectOutputStream】—>writeSerialData【ObjectOutputStream】—>invokeWriteObject【ObjectStreamClass】
    • 其中invokeWriteObject方法的这行代码是调用的关键【writeObjectMethod.invoke(obj, new Object[]{ out });】
void invokeWriteObject(Object obj, ObjectOutputStream out)
            throws IOException, UnsupportedOperationException
    {
        requireInitialized();
        if (writeObjectMethod != null) {
            try {
                writeObjectMethod.invoke(obj, new Object[]{ out });
            } catch (InvocationTargetException ex) {
                Throwable th = ex.getTargetException();
                if (th instanceof IOException) {
                    throw (IOException) th;
                } else {
                    throwMiscException(th);
                }
            } catch (IllegalAccessException ex) {
                // should not occur, as access checks have been suppressed
                throw new InternalError(ex);
            }
        } else {
            throw new UnsupportedOperationException();
        }
    }
  • 那么为什么还要实现序列化接口呢?我都自己定义了writeObject()/readObject()方法?ObjectOutputStream类中方法writeObject0()有如下代码。从其中可以看出如果不是列出的几种类型,就会抛出异常,所以必须实现序列化接口
	if (obj instanceof String) {
	    writeString((String) obj, unshared);
	} else if (cl.isArray()) {
	    writeArray(obj, desc, unshared);
	} else if (obj instanceof Enum) {
	    writeEnum((Enum<?>) obj, desc, unshared);
	} else if (obj instanceof Serializable) {
	    writeOrdinaryObject(obj, desc, unshared);
	} else {
	    if (extendedDebugInfo) {
	        throw new NotSerializableException(
	                cl.getName() + "\n" + debugInfoStack.toString());
	    } else {
	        throw new NotSerializableException(cl.getName());
    
  • 稍稍拓展一下:请脑海中请有一个概念,序列化只是序列化了对象此刻的状态,而非结构。
    • 如何解释呢?例如ArrayList序列化时,并不会完整序列化整个容器,而支持序列化容器内的值和一些状态属性数据
    • 例如:A、B两个系统进行数据通信时,如果传输的是ArrayList,那么AB两个系统都会存在ArrayList.class,我只要知道ArrayList此刻存放的数据和此刻状态的属性数据,我就可以在另外一个系统内还原
    • 为了节约带宽,所以序列化时数据越少越好,则只序列化状态数据而不序列化结构数据

4-当通过构造方法ArrayList(Collection<? extends E> c)创建得到ArrayList对象后,为什么对集合c元素属性的的所有修改都体现在生成的ArrayList上?

5-通过toArray()将集合转为Object[] 可能会有哪些问题?为什么《阿里巴巴编程规范》中强制要求使用toArray(T array)方法来将集合转为数组?

6-用户自己调用容器的扩容方法,为什么不能全凭用户想法扩容到指定大小呢?而是要与默认1.5倍扩容大小进行比较,如果1.5倍扩容后的大小比用户定义的大,那么就按照1.5倍进行扩容,而不按照用户想要的大小进行扩容?

▄█▀█●各位同仁,如果我的代码对你有帮助,请给我一个赞吧,为了下次方便找到,也可关注加收藏呀
如果有什么意见或建议,也可留言区讨论

  • 6
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值