您的位置:首页 > 其它

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

2020-02-04 22:29 302 查看

ArrayList详解

  • public ArrayList()
  • public ArrayList(Collection<? extends E> c)
  • public void trimToSize()
  • public void ensureCapacity(int minCapacity) //手动对集合进行扩容
  • · private static int calculateCapacity(Object[] elementData, int minCapacity)
  • · private void ensureCapacityInternal(int minCapacity)
  • · private void ensureExplicitCapacity(int minCapacity)
  • · private void grow(int minCapacity)
  • · private static int hugeCapacity(int minCapacity)
  • public int size()
  • public boolean isEmpty()
  • public boolean contains(Object o)
  • public int indexOf(Object o)
  • public int lastIndexOf(Object o)
  • public Object clone()
  • public Object[] toArray()
  • public T[] toArray(T[] a)
  • E elementData(int index)
  • public E get(int index)
  • public E set(int index, E element)
  • public boolean add(E e)
  • public void add(int index, E element)
  • public E remove(int index)
  • public boolean remove(Object o)
  • private void fastRemove(int index)
  • public void clear()
  • public boolean addAll(Collection<? extends E> c)
  • public boolean addAll(int index, Collection<? extends E> c)
  • protected void removeRange(int fromIndex, int toIndex)
  • private void rangeCheck(int index)
  • private void rangeCheckForAdd(int index)
  • private String outOfBoundsMsg(int index)
  • public boolean removeAll(Collection<?> c)
  • public boolean retainAll(Collection<?> c)
  • private boolean batchRemove(Collection<?> c, boolean complement)
  • private void writeObject(ObjectOutputStream s) throws IOException
  • private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException
  • 精解列表
  • 3-ArrayList实现序列化接口就是为了在网络上传输,那为什么存放数据的elementData,要用transient修饰呢?writeObject()/readObject()又是做什么的呢?
  • 4-当通过构造方法ArrayList(Collection<? extends E> c)创建得到ArrayList对象后,为什么对集合c元素属性的的所有修改都体现在生成的ArrayList上?
  • 5-通过toArray()将集合转为Object[] 可能会有哪些问题?为什么《阿里巴巴编程规范》中强制要求使用toArray(T array)方法来将集合转为数组?
  • 6-用户自己调用容器的扩容方法,为什么不能全凭用户想法扩容到指定大小呢?而是要与默认1.5倍扩容大小进行比较,如果1.5倍扩容后的大小比用户定义的大,那么就按照1.5倍进行扩容,而不按照用户想要的大小进行扩容?
  • 初识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倍进行扩容,而不按照用户想要的大小进行扩容?

    • 点赞 4
    • 收藏
    • 分享
    • 文章举报
    程序人生_高少猛 发布了12 篇原创文章 · 获赞 13 · 访问量 2807 私信 关注
    内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
    标签: