【Java学习002】Java-ArrayList源码解析

概述

ArrayList是一个顺序容器,允许放入null元素,带有自动扩容。ArrayList定义如下:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

继承关系

  • List 是有序集合的基础接口,除了 Collection 的方法,还有支持倒序遍历的 listIterator 方法、子列表 subList 方法,另外重写 spliterator 方法的实现。
  • AbstractList是一个抽象类,重写了 List 的大部分方法,作用跟 AbstractCollection类似。
  • ArrayList实现了RandomAccess接口,该接口是一个标记型接口,实现该接口表示支持快速随机访问,该接口的主要目的是允许通用算法改变其行为,以便在应用于随机或顺序访问列表时提供良好的性能。也就是说使用索引获取元素的速度快于使用迭代器。
  • ArrayList实现了Cloneable接口,Cloneable接口是一个标记型接口,没有任何的字段或者方法。该接口的作用在于只有实现了该接口才能使用Object.clone()方法进行对象拷贝,如果没有实现该接口就调用克隆方法会导致CloneNotSupportedException异常。
  • ArrayList实现了Serializable接口,该接口同样是一个标记型接口,即没有方法或字段,仅用于标识可序列化的语义。不实现此接口的类将不会使任何状态序列化或反序列化。可序列化类的所有子类型都是可序列化的。

1.8Cloneable文档:

A class implements the Cloneable interface to indicate to the Object.clone() method that it is legal for that method to make a field-for-field copy of instances of that class.
Invoking Object’s clone method on an instance that does not implement the Cloneable interface results in the exception CloneNotSupportedException being thrown.
By convention, classes that implement this interface should override Object.clone (which is protected) with a public method. See Object.clone() for details on overriding this method.
Note that this interface does not contain the clone method. Therefore, it is not possible to clone an object merely by virtue of the fact that it implements this interface. Even if the clone method is invoked reflectively, there is no guarantee that it will succeed.

1.8RandomAccess文档

Marker interface used by List implementations to indicate that they support fast (generally constant time) random access. The primary purpose of this interface is to allow generic algorithms to alter their behavior to provide good performance when applied to either random or sequential access lists.
The best algorithms for manipulating random access lists (such as ArrayList) can produce quadratic behavior when applied to sequential access lists (such as LinkedList). Generic list algorithms are encouraged to check whether the given list is an instanceof this interface before applying an algorithm that would provide poor performance if it were applied to a sequential access list, and to alter their behavior if necessary to guarantee acceptable performance.
It is recognized that the distinction between random and sequential access is often fuzzy. For example, some List implementations provide asymptotically linear access times if they get huge, but constant access times in practice. Such a List implementation should generally implement this interface. As a rule of thumb, a List implementation should implement this interface if, for typical instances of the class, this loop:
for (int i=0, n=list.size(); i < n; i++)
list.get(i);
for (Iterator i=list.iterator(); i.hasNext(); )
i.next();

源码解析

重要成员变量

class ArrayList{
    // 默认初始容量10
	private static final int DEFAULT_CAPACITY = 10;

	// 空实例的共享空数组实例,与不写参数是不同的,通过new ArrayList(0)创建时用的是这个空数组。
    private static final Object[] EMPTY_ELEMENTDATA = {};
	
	// 共享空数组实例,用于默认大小的空实例。
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
	
	// 底层存储数据的位置,集合真正存储数据的容器
    transient Object[] elementData; 
	
	// ArrayList的大小
    private int size;	

    // ArrayList的最大容量,2147483647 - 8 = 2147483639
    // 这么做的原因是因为有些虚拟机要存储对象头,避免请求分配的时候导致 OutOfMemoryError
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
	
	// 结构修改计数器,防止并发修改的问题
	protected transient int modCount = 0;
    
    ...
}

elementData是ArrayList的底层实现,可以看到采用的是Object数组,并进行了一个泛型擦除;size变量记录当前容器的大小。

构造方法

ArrayList的构造方法分为三类:

  1. 不传递任何参数;
  2. 传递一个整数;
  3. 传递一个集合。

空参构造

public ArrayList() {
    // 进行赋值操作
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

指定初始容量

class ArrayList{
    
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            // 参数大于0创建一个指定长度的Object数组
            // 如果指定了容量大小就不会进行懒初始化了
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            // 如果参数为0则创建一个EMPTY_ELEMENTDATA共享数组
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            // 参数小于0则抛出异常
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
    ...
}

给定初始的集合

class ArrayList{
    public ArrayList(Collection<? extends E> c) {
    	// 将集合转换成Object数组
        // 详解见sub1
        Object[] a = c.toArray();
    	// 如果原本集合不为空
        if ((size = a.length) != 0) {
            if (c.getClass() == ArrayList.class) {
                // 如果两者的类型一致都是java.util.ArrayList,则直接赋值为底层存储数据的数组
                elementData = a;
            } else {
                // 如果不一致就使用copyof方式进行数组拷贝
                elementData = Arrays.copyOf(a, size, Object[].class);
            }
        } else {
            // 参数集合为空则和指定长度为0的构造函数是一致的,创建一个EMPTY_ELEMENTDATA共享数组。
            elementData = EMPTY_ELEMENTDATA;
        }
    }

    /*
    sub1
    */
    public Object[] toArray() {
        // 调用Arrays工具类的方法将输入的集合拷贝为数组返回
        // 详解见sub1-1
        return Arrays.copyOf(elementData, size);
	}
    
    /*
    sub1-1
    */
    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType){
        /*
    	original – 要拷贝的数组
    	newLength – 拷贝副本的长度
    	newType – 拷贝副本的元素类型
    	*/
        
		...
        
        // 调用本地方法arraycopy进行数组的拷贝工作。
    	System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
     }
}

重要方法

添加方法

add(E e)源码解析:

class ArrayList{
    // 在末尾添加元素
    public boolean add(E e) {
        // 先进行容量检查
        // 详解见sub1
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 检查没问题插入到size++位置
        elementData[size++] = e;
        return true;
    }

    /*
    sub1
    */
    private void ensureCapacityInternal(int minCapacity) {
        // calculateCapacity详解见sub1-1
        // ensureExplicitCapacity详解见sub1-2
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

    /*
    sub1-1
    */
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        // 如果当前底层数组是空参构造得到的Object[]那么返回max(10, minCapacity)
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        // 否则直接返回minCapacity
        return minCapacity;
    }

    /*
    sub1-2
    */
	private void ensureExplicitCapacity(int minCapacity) {
        // 结构计数器+1
        modCount++;

        // 如果所需长度大于当前elementData长度则进行扩容
        if (minCapacity - elementData.length > 0)
            // 详解见sub1-2-1
            grow(minCapacity);
    }

    /*
    sub1-2-1
    */
    private void grow(int minCapacity) {
        // 获取旧的数组长度
        int oldCapacity = elementData.length;
        // 扩容1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 如果新容量还是小于最小的需求容量则将需求容量赋值给新容量
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        // 如果新容量大于最大容量(整数的最大值-8)调用hugeCapacity方法。
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            // 详解见sub1-2-1-1
            newCapacity = hugeCapacity(minCapacity);
        // 扩容完毕之后拷贝数组
        // 详解见sub1-2-1-2
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    /*
    sub1-2-1-1
    */
    private static int hugeCapacity(int minCapacity) {
        // 如果溢出直接抛异常
        if (minCapacity < 0) 
            throw new OutOfMemoryError();
        /*
        这里的逻辑是扩容按1.5倍进行扩容,如果所需长度很大又刚好比当前长度大触发扩容
        那么这个newCapacity有很多的浪费其实并没有在MAX_ARRAY_SIZE~Integer.MAX_VALUE这个区间
        因此这里需要根据实际需求容量minCapacity计算扩容长度
        */
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }
}

class Arrays{
    /*
    sub1-2-1-2
    */
    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        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;
    }
}

class System{
    /*
    将src中从srcPos~src.length-1的数据复制到dest的destPos~des.length-1位置
    */
    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);
}

总结一下

  1. 检查是否需要扩容(minCapacity - elementData.length > 0),如果数组没有初始化先进行初始化;
  2. 如果需要扩容,则扩容1.5倍,对于新容量会有一些边界值判断和处理;
  3. 扩容完毕,调用本地方法将旧数组的数据复制到新数组,更改elementData的指向;
  4. 在新数组最后添加要添加的数据并更改size变量。

add(int index, E element)源码解析:

class ArrayList{
    public void add(int index, E element) {
        // 进行范围检查
        // 详解见sub1
        rangeCheckForAdd(index);

        // 先进行容量检查
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        // 拷贝数组,从elementData的index~length-1复制到index+1~length-1
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        
        // 插入元素
        elementData[index] = element;
        // 容量+1
        size++;
    }

    /*
    sub1
    */
    private void rangeCheckForAdd(int index) {
        // 1. 插入位置大于当前的大小;2. 插入位置小于0都会抛出异常;
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
}

总结一下

  1. 插入的逻辑和添加一个元素基本类似,进行插入位置范围检查;
  2. 步骤和add(E e)方法一致;

addAll(Collection<? extends E> c)源码解析:

class ArrayList{
    public boolean addAll(Collection<? extends E> c) {
        // 将参数集合转化Object数组
        Object[] a = c.toArray();
        // 获取参数集合的长度
        int numNew = a.length;
        // 按两个集合的总长度进行扩容传递
        ensureCapacityInternal(size + numNew);  // Increments modCount
        // 拷贝集合中的数据,从a的0~a.length-1到elementData的size,共复制numNew个
        System.arraycopy(a, 0, elementData, size, numNew);
        // 更改当前容量
        size += numNew;
        // 返回原集合是否为空
        return numNew != 0;
    }
}

总结一下

  1. 将集合转换为数组并获取长度;
  2. 按照长度和进行验证和扩容;
  3. 复制集合转换成的数组中的全部数据添加到末尾;
  4. 更改个成员变量;

addAll(int index, Collection<? extends E> c)源码解析:

class ArrayList{
    public boolean addAll(int index, Collection<? extends E> c) {
        // 范围校验
        rangeCheckForAdd(index);
    	// 转换为数组
        Object[] a = c.toArray();
        int numNew = a.length;
        // 按着两个集合长度之和作为最小需求容量进行传递
        ensureCapacityInternal(size + numNew);  // Increments modCount

        // 计算要移动元素的个数
        int numMoved = size - index;
        // 如果要添加的索引位置在size之前,类似于在中间插入参数集合所有元素
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);

        // 拷贝集合中的数据,从a的0~a.length-1到elementData的index,共复制numNew个
        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }
}

总结一下:该方法的逻辑和add(int index, E c)是一致的。

获取方法

get(int index)源码解析:

class ArrayList{
    public E get(int index) {
        // 范围查询
        rangeCheck(index);
		// 直接返回数组中的索引元素
        return elementData(index);
    }
    
    private void rangeCheck(int index) {
        // 大于size会抛出IndexOutOfBoundsException
        // 小于0会抛出ArrayIndexOutOfBoundsException
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
}

总结一下:和正常的数组索引获取数据的方式一样。

设置方法

set(int index, E element)源码解析:

class ArrayList{
    public E set(int index, E element) {
        // 范围检查
        rangeCheck(index);

        // 获取原本索引的旧值,会进行类型转换Object -> E
        E oldValue = elementData(index);
        // 设置元素
        elementData[index] = element;
        // 返回旧值
        return oldValue;
    }
}

总结一下:和正常的数组赋值没有区别,多一个索引位置检查以及返回旧值。

删除方法

remove(int index)源码解析:

class ArrayList{
    public E remove(int index) {
        // 范围检查
        rangeCheck(index);

        // 修改计数器+1
        modCount++;
        // 获取原本的索引处元素,会进行类型准换
        // 见sub1
        E oldValue = elementData(index);

        // 计算要移动的元素个数
        int numMoved = size - index - 1;
        // 数据前移
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        // 最后一个元素置为null,因为前移只移动numMoved个元素,相当于最后一个元素空了
        elementData[--size] = null; // clear to let GC do its work

        // 返回旧值
        return oldValue;
    }

    /*
    sub1
    */
    E elementData(int index) {
        return (E) elementData[index];
    }
}

总结一下

  1. 索引位置范围检查;获取旧值(转换成原始类型);计算移动数据个数;
  2. 数据前移;
  3. 最后一个数据置为空,返回旧值;

remove(Object o)源码解析:

class ArrayList{
    public boolean remove(Object o) {
        // 如果为空则使用【==】进行的比较,null可以使用==进行比较
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    // 删除元素,详见sub1
                    fastRemove(index);
                    return true;
                }
        } else {
            // 非空则调用equals方法进行比较
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

    /*
    sub1
    因为这个索引位置是遍历搜索出来的,因此该方法就是简化版的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
    }
}

总结一下:

  1. 判断给定的要删除的对象是否为空;
  2. 为空则使用【==】进行比较;不为空则调用equals方法进行比较;
  3. 使用简化版remove方法移除元素;
  4. 返回移除结果;

removeAll(Collection<?> c)解析:

class ArrayList{
    public boolean removeAll(Collection<?> c) {
        // c不能为空
        Objects.requireNonNull(c);
        // 批删除,详情见下文
        return batchRemove(c, false);
    }
}

清除方法

clear()方法解析:

class ArrayList{
    public void clear() {
        // 修改计数器+1
        modCount++;

        // 遍历数组清除元素置null
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        // 集合长度置0
        size = 0;
    }
}

toString方法

调用的是AbstractCollection类的toString方法:

class AbstractCollection{
    public String toString() {
        // 先获取迭代器
        Iterator<E> it = iterator();
        // 迭代器无任何元素直接返回[]
        if (! it.hasNext())
            return "[]";

        StringBuilder sb = new StringBuilder();
        // 拼接开头
        sb.append('[');
        for (;;) {
            E e = it.next();
            // 拼接元素
            sb.append(e == this ? "(this Collection)" : e);
            // 检查是否还有元
            if (! it.hasNext())
                return sb.append(']').toString();
            // 拼接元素之间分隔符
            sb.append(',').append(' ');
        }
    }
}

总结一下:

  1. 采用迭代器进行遍历拼接;
  2. 没有元素返回"[]";
  3. 开头是"[“;结尾是”]“;中间元素分割使用”, ";
  4. 元素拼接顺序是先拼接元素->检查是否是集合末尾->拼接分隔符;

包含方法

contains(Object o)方法解析:

class ArrayList{
    
    public boolean contains(Object o) {
        // 详见sub1
        return indexOf(o) >= 0;
    }

    /*
    sub1
    */
    public int indexOf(Object o) {
        // 要找的元素为空则直接找到空值返回
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            // 要找的元素不为空则调用equals方法进行比较
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        // 没找到返回-1
        return -1;
    }
}

总结一下:这方法跟remove(Object o)逻辑基本一致;

判空方法

isEmpty()详解:

class ArrayList{
    public boolean isEmpty() {
        // 返回size==0?
        return size == 0;
    }
}

克隆方法

clone()解析:

class ArrayList{
    public Object clone() {
        try {
            // 调用Object的克隆方法
            ArrayList<?> v = (ArrayList<?>) super.clone();
            // 调用copyof方法对原数组进行拷贝
            v.elementData = Arrays.copyOf(elementData, size);
            // 修改指示器置0
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // 如果没有实现Cloneable接口就会抛出该异常
            throw new InternalError(e);
        }
    }
}

总结一下ArrayList重写了Object类的clone()方法,采用的是浅拷贝的方式。

迭代器方法

iterator()解析:

class ArrayList{
    public Iterator<E> iterator() {
        return new Itr();
    }

    // Itr是私有内部类
    private class Itr implements Iterator<E> {
        // 当前索引位置
        int cursor;    
        // 返回元素指针,-1代表没有返回元素
        int lastRet = -1; 
        // 记录修改计数器
        int expectedModCount = modCount;

        // 空参构造器
        Itr() {}

        public boolean hasNext() {
            // 判断逻辑是cursor是否等于长度
            return cursor != size;
        }

        
        public E next() {
            // 检查是否有并发修改
            // 详见sub1
            checkForComodification();
            // 临时变量i判断是否还有可遍历的元素
            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];
        }

        /*
        sub1
        */
        final void checkForComodification() {
            // 如果进行了结构上的更改比方说删除、新增,那么迭代器对象记录的expectedModCount
            // 和modCount必然不相等,抛出ConcurrentModificationException异常
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
        
    }
}

总结一下

  1. ArrayList的迭代器是一个私有内部类,实现了Iterator接口;
  2. 通过记录modCount并对比防止并发修改;
  3. 迭代过程中先改指针再返回元素;

求交集

retainAll方法解析:

class ArrayList{
    public boolean retainAll(Collection<?> c) {
        // 检查c是否为空
        Objects.requireNonNull(c);
        // 调用批量删除方法,这时complement传入true,表示删除不包含在c中的元素
        return batchRemove(c, true);
    }

	/**
    * 批量删除元素
    * complement为true表示删除c中不包含的元素
    * complement为false表示删除c中包含的元素
    */
    private boolean batchRemove(Collection<?> c, boolean complement) {
        // 使用读写两个指针同时遍历数组
    	// 读指针每次自增1,写指针放入元素的时候才加1
    	// 这样不需要额外的空间,只需要在原有的数组上操作就可以了
        final Object[] elementData = this.elementData;
        int r = 0, w = 0;
        boolean modified = false;
        try {
            // 遍历整个数组,如果c中包含该元素,则把该元素放到写指针的位置(以complement为准)
            for (; r < size; r++)
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
            // 正常来说r最后是等于size的,除非c.contains()抛出了异常
            // 如果c.contains()抛出了异常,则把未读的元素都拷贝到写指针之后
            if (r != size) {
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            if (w != size) {
                // 将写指针之后的元素置为空,帮助GC
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;
                // 新大小等于写指针的位置(因为每写一次写指针就加1,所以新大小正好等于写指针的位置)
                size = w;
                modified = true;
            }
        }
        // 如果w == size代表根本没有修改,也就是没有交集,因此返回false
        // 本质上是在更改原始对象并返回更改结果
        return modified;
    }
}

总结一下

  1. 遍历elementData数组;
  2. 如果元素在c中,则把这个元素添加到elementData数组的w位置并将w位置往后移一位;
  3. 遍历完之后,w之前的元素都是两者共有的,w之后(包含)的元素不是两者共有的(这个得根据complement参数而定);
  4. 将w以及之后的元素置为null,方便GC回收;

总结

底层

ArrayList 是一个大小可以调整的动态数组,适应于查询为主的场景,ArrayList 不是一个线程安全的集合。并发修改时,可能会抛出 ConcurrentModificationException 或者得到无法预料的结果。ArrayList内部使用数组存储元素,当数组长度不够时进行扩容,每次加一半的空间,ArrayList不会进行缩容;

扩容

每当向数组中添加元素时,都要去检查添加后元素的个数是否会超出当前数组的长度,如果超出,数组将会进行扩容,以满足添加数据的需求。一般情况下会扩容1.5倍,但是有几种特殊情况:

  1. 空参初始化的情况下,会直接扩容到10
  2. 扩容超过容量上限会进行一定的处理;

并发修改

ArrayList类内部包含一个内部私有类ItrItr内部会通过记录modCount,在每次调用next()方法时会进行检查从而判断是否进行了并发修改。

性能分析

  1. ArrayList支持随机访问,通过索引访问元素极快,时间复杂度为 O ( 1 ) O(1) O(1)
  2. ArrayList添加元素到尾部极快,平均时间复杂度为 O ( 1 ) O(1) O(1)
  3. ArrayList添加元素到中间比较慢,因为要搬移元素,平均时间复杂度为 O ( n ) O(n) O(n)
  4. ArrayList从尾部删除元素极快,时间复杂度为 O ( 1 ) O(1) O(1)
  5. ArrayList从中间删除元素比较慢,因为要搬移元素,平均时间复杂度为 O ( n ) O(n) O(n)
  6. ArrayList支持求并集,调用addAll(Collection<? extends E> c)方法即可;
  7. ArrayList支持求交集,调用retainAll(Collection<? extends E> c)方法即可;
  8. ArrayList支持求单向差集,调用removeAll(Collection<? extends E> c)方法即可;

补充说明

ArrayListelementData被定义为transient的,不会被序列化,只要实现了Serializable接口即可自动序列化。但是为了控制序列化、反序列化过程,我们可以自己实现writeObject()readObject(),这两个私有方法会被反射得到。

class ArrayList{
    
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // 记录修改计数器,防止序列化过程中修改
        int expectedModCount = modCount;
        // 写出非transient非static属性
        s.defaultWriteObject();

        // 写出元素个数
        s.writeInt(size);

        // 遍历写出元素
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }

        // 判断是否有并发修改
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

    
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        // 声明空数组
        elementData = EMPTY_ELEMENTDATA;

        // 读入非transient非static属性(会读取size属性)
        s.defaultReadObject();

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

        if (size > 0) {
            // 计算容量
            int capacity = calculateCapacity(elementData, size);
            SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
            // 检查是否需要扩容
            ensureCapacityInternal(size);

            Object[] a = elementData;
            // 依次读取元素
            for (int i=0; i<size; i++) {
                a[i] = s.readObject();
            }
        }
    }
}

总结一下

  1. 序列化时先调用s.defaultWriteObject()方法,然后将非静态、非transient元素写入流中,再把size写入到流中,再把元素一个一个的写入到流中;
  2. 反序列化时先调用了s.defaultReadObject()方法,这个方法是读取非静态、非transient的属性,然后根据size属性依次读取元素;
  3. elementData定义为transient的优势,自己根据size序列化真实的元素,而不是根据数组的长度序列化元素,减少了空间占用。

往期回顾

  1. 【Java学习004】Map集合遍历
  2. 【Java学习003】Java-LinkedList源码解析
  3. 【Java学习001】Java-HashMap源码详解

文中难免会出现一些描述不当之处(尽管我已反复检查多次),欢迎在留言区指正,相关的知识点也可进行分享,希望大家都能有所收获!!如果觉得我的文章写得还行,不妨支持一下。你的每一个转发、关注、点赞、评论都是对我最大的支持!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小猪猪家的大猪猪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值