Java源码分析1-ArrayList

2019-07-10  本文已影响0人  那廿

以下内容基于JDK1.8

概述

继承关系图

继承关系图

相关属性

    /**
    * 默认初始容量
    */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 空数组,如果传入的容量为0或传入集合元素个数为0时使用
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * 空数据,当调用new ArrayList()构造函数时使用
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * 存储元素数据,以下简称存储数组
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * 集合中元素个数
     * @serial
     */
    private int size;

  1. 存储数组Object[] elementData

比较特殊的是修饰符transient,这个之前未接触过,有必要了解下

private void writeObject(java.io.ObjectOutputStream s)
    throws java.io.IOException{
    // Write out element count, and any hidden stuff
    int expectedModCount = modCount;
    //写入非transient修饰的成员变量
    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]);
    }
    //判断对象实例是否被篡改
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}

这么做的目的是因为存储数组的长度不是真实的元素个数,size才是.
如果不声明elementDatatransient,那么在存储数组中(elementData.length-size)个空间是未被使用的,
所以需要将其声明为transient并重写writeObject方法,进而节省存储空间

private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
   
   //默认存储数组为空数组
   elementData = EMPTY_ELEMENTDATA;

    // Read in size, and any hidden stuff
    //读取ArrayList对应的大小等其他属性
    s.defaultReadObject();

    // Read in capacity
    //读取容量
    s.readInt(); // ignored
    
    if (size > 0) {
        //与clone()类似,根据大小而不是容量分配数组
        //确保容量最终,肯定会发送扩容
        ensureCapacityInternal(size);
        
        Object[] a = elementData;
        // Read in all elements in the proper order.
        for (int i=0; i<size; i++) {
            //将流中的元素依次写入到存储数组中
            a[i] = s.readObject();
        }
    }
}

还有个问题,writeObjectreadObject方法都是私有的,在ArrayList或者整个rt.jar都没有找到调用记录,那么在序列化的时候是如何被调用到的呢?

public class ArrayListTest4 {

    public static void main(String[] args) throws IOException, ClassNotFoundException {
    
        List<String> list2 = new ArrayList<>();
        list2.add("a");
        list2.add("b");
        list2.add("c");
    
        //将对象序列化到字节数组中
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(list2);
        byte[] byteArray = byteArrayOutputStream.toByteArray();
    
    
        //从字节数组中反序列化对象
        ObjectInputStream inputStream = new ObjectInputStream(new ByteArrayInputStream(byteArray));
        Object o = inputStream.readObject();
        System.out.println(o.toString());
    }
}

通过调试上述代码可知,将对象序列化时的调用顺序为:
oos---ObjectOutputStream
osc---ObjectStreamClass
oos.writeObject--->oos.writeObject0--->osc.lookup--->oos.writeOrdinaryObject--->oos.writeSerialData--->osc.invokeWriteObject->ArrayList.writeObject
其中,ArrayList.writeObject是通过反射的方式调用的
而为什么能找到writeObject这个方法呢?答案就在ObjectOutputStream的私有构造方法中,上代码

private ObjectStreamClass(final Class<?> cl) {
    this.cl = cl;
    name = cl.getName();
    isProxy = Proxy.isProxyClass(cl);
    isEnum = Enum.class.isAssignableFrom(cl);
    serializable = Serializable.class.isAssignableFrom(cl);
    externalizable = Externalizable.class.isAssignableFrom(cl);

    Class<?> superCl = cl.getSuperclass();
    superDesc = (superCl != null) ? lookup(superCl, false) : null;
    localDesc = this;

    if (serializable) {
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                if (isEnum) {
                    suid = Long.valueOf(0);
                    fields = NO_FIELDS;
                    return null;
                }
                if (cl.isArray()) {
                    fields = NO_FIELDS;
                    return null;
                }

                suid = getDeclaredSUID(cl);
                try {
                    fields = getSerialFields(cl);
                    computeFieldOffsets();
                } catch (InvalidClassException e) {
                    serializeEx = deserializeEx =
                        new ExceptionInfo(e.classname, e.getMessage());
                    fields = NO_FIELDS;
                }

                if (externalizable) {
                    cons = getExternalizableConstructor(cl);
                } else {
                    cons = getSerializableConstructor(cl);
                    //通过'writeObject'获取到类重写的序列化方法
                    writeObjectMethod = getPrivateMethod(cl, "writeObject",
                        new Class<?>[] { ObjectOutputStream.class },
                        Void.TYPE);
                    //通过'readObject'获取到类重写的反序列化方法
                    readObjectMethod = getPrivateMethod(cl, "readObject",
                        new Class<?>[] { ObjectInputStream.class },
                        Void.TYPE);
                    readObjectNoDataMethod = getPrivateMethod(
                        cl, "readObjectNoData", null, Void.TYPE);
                    hasWriteObjectData = (writeObjectMethod != null);
                }
                writeReplaceMethod = getInheritableMethod(
                    cl, "writeReplace", null, Object.class);
                readResolveMethod = getInheritableMethod(
                    cl, "readResolve", null, Object.class);
                return null;
            }
        });
    } else {
        suid = Long.valueOf(0);
        fields = NO_FIELDS;
    }

    try {
        fieldRefl = getReflector(fields, this);
    } catch (InvalidClassException ex) {
        // field mismatches impossible when matching local fields vs. self
        throw new InternalError(ex);
    }

    if (deserializeEx == null) {
        if (isEnum) {
            deserializeEx = new ExceptionInfo(name, "enum type");
        } else if (cons == null) {
            deserializeEx = new ExceptionInfo(name, "no valid constructor");
        }
    }
    for (int i = 0; i < fields.length; i++) {
        if (fields[i].getField() == null) {
            defaultSerializeEx = new ExceptionInfo(
                name, "unmatched serializable field(s) declared");
        }
    }
    initialized = true;
}

构造函数

  1. 无参构造函数
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

此时的操作有:

  1. 指定数组容量构造函数
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);
     }
}

此时的操作有:

  1. 传入参数为Collection子类的构造函数
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;
    }
}

此时的操作有:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class ArrayListTest1 {

    public static void main(String[] args) {
        List<String> list1 = Arrays.asList("test", "s");
        System.out.println(list1.getClass() 
            +" toArray.getClass="+ list1.toArray().getClass());

        List<String> list2 = new ArrayList<>();
        list2.add("test");
        System.out.println(list2.getClass() 
            +" toArray.getClass="+ list2.toArray().getClass());
    }
}

执行结果

class java.util.Arrays$ArrayList toArray.getClass=class [Ljava.lang.String;
class java.util.ArrayList toArray.getClass=class [Ljava.lang.Object;

通过Arrays.asList生成的List虽然也叫ArrayList但却是Arrays.java里面的一个内部类,且通过其toArray()方法返回的数组对应的Class类为[Ljava.lang.String

2).为什么要调用Arrays.copyOf方法覆盖原存储数组呢,查了下旁边的注释c.toArray might (incorrectly) not return Object[] (see 6260652),正是由于ArrayList是通过数组存储数据,当数组中元素在进行向下转型时由于不安全而抛出异常的bug
如下所示

public class ArrayListTest2 {

    public static void main(String[] args) {

        Object[] objects = new Object[2];
        System.out.println(objects.getClass());

        SubObject[] subObjects = new SubObject[2];
        System.out.println(subObjects.getClass());

        objects = subObjects;
        objects[0] = new Object();
    }
}
class SubObject{}

执行结果

class [Ljava.lang.Object;
class [Lcom.learn.list.SubObject;
Exception in thread "main" java.lang.ArrayStoreException: java.lang.Object
at com.learn.list.ArrayListTest2.main(ArrayListTest2.java:21)

综上可知,代码

if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);

是为了避免在通过传入Collection子类构造出的ArrayList实例在新增元素时,由于传入元素是存储数组类型父类而发生向下转型而抛出异常

常用方法

  1. 在末尾添加单个元素add(E e)方法
public boolean add(E e) {
    //在元素个数+1的情况下确保容量足够
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //将元素添加存储数组索引为size+1出,且数组元素个数+1
    elementData[size++] = e;
    return true;
}

重点都在ensureCapacityInternal方法中,那么就重点分析下吧

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        //当存储数组为空数组时,从默认初试容量DEFAULT_CAPACITY和传入容量minCapacity中取最大值
        //赋给传入容量
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    //确保明确的容量
    ensureExplicitCapacity(minCapacity);
}

继续分析ensureExplicitCapacity方法

private void ensureExplicitCapacity(int minCapacity) {
    //定义于ArrayList的父类AbstractList,用于存储结构修改次数
    modCount++;

    //判断是否需要扩容
    if (minCapacity - elementData.length > 0){
        //如果传入容量大于存储数组长度,则扩容
        grow(minCapacity);
    }   
}

接着分析grow扩容方法

private void grow(int minCapacity) {
    //存储数组长度
    int oldCapacity = elementData.length;
    //新容量=存储数组长度+存储数组长度由移一位(/2),及新容量=1.5倍存储数组长度
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0){
        //如果传入容量大于新容量,则新容量=传入容量
        newCapacity = minCapacity;
    }
    if (newCapacity - MAX_ARRAY_SIZE > 0){
        //如果新容量大于Integer.MAX_VALUE - 8,则获取最大容量
        newCapacity = hugeCapacity(minCapacity);
    }
        
    //将原存储数组中的数据复制到新数组(长度=新容量)中后赋给存储数组
    elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
  if (minCapacity < 0){
      //此时传入容量小于0说明超过Integer的最大值,即内存溢出
      throw new OutOfMemoryError();
  }
  //你懂的
  return (minCapacity > MAX_ARRAY_SIZE) ?
      Integer.MAX_VALUE :
      MAX_ARRAY_SIZE;
}

通过分析完add(E e),可以得到的结论是


  1. 在指定位置添加单个元素add(int index, E element)方法
public void add(int index, E element) {
      //检查传入索引是否越界
      rangeCheckForAdd(index);

      //在元素个数+1的情况下确保容量足够,见1.1
      ensureCapacityInternal(size + 1);  // Increments modCount!!
     //数组复制方法,即将存储元素中的元素从index开始后移一位,给新增元素腾位置
      System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
      
      //将传入元素的值放入数组中的指定位置
      elementData[index] = element;
      size++;
  }

先来看看rangeCheckForAdd方法

private void rangeCheckForAdd(int index) {
    if (index > size || index < 0){
        //若传入索引是否大于当前元素个数或者小于0,则抛出数组越界异常
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
}
/***
* src-源数组
* srcPos-原数组复制起始位置
* dest-目标数据
* destPos-目标数组存放起始位置
* length-复制元素个数
*/
public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

通过分析完add(int index, E element),可以得到的结论是


  1. 在末尾添加Collection子类 addAll(Collection<? extends E> c)方法
public boolean addAll(Collection<? extends E> c) {
    //将参数转为新数组
     Object[] a = c.toArray();
     int numNew = a.length;
    //确保在插入数组对应个数的元素后存储数组的容量足够
     ensureCapacityInternal(size + numNew);  // Increments modCount
     //将新数组中的所有元素复制到存储数组末尾
     System.arraycopy(a, 0, elementData, size, numNew);
     size += numNew;
     return numNew != 0;
}

由此可见,
该方法也是非线程安全的,并发操作存在数据缺失的问题


  1. 删除指定元素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; // clear to let GC do its work

     return oldValue;
}

同理


  1. 通过对象引用删除数据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 {
        //如果对象引用不为空,则通过其equals比较是否,若为true,则删除
      for (int index = 0; index < size; index++)
        if (o.equals(elementData[index])) {
            fastRemove(index);
            return true;
        }
    }
    return false;
}

5.1 快速删除fastRemove(int index)方法

private void fastRemove(int index) {
     modCount++;
     //计算需要移动格式
     int numMoved = size - index - 1;
     if (numMoved > 0){
     //将制定位置后的元素向前挪动一位
       System.arraycopy(elementData, index+1, elementData, index,
                            numMoved);
     }
     //将存储数据末尾元素置为null,尽快让其被GC回收释放空间    
     elementData[--size] = null; // clear to let GC do its work
}

综上可知


  1. 通过Collection子类删除元素removeAll(Collection<?> c)方法
public boolean removeAll(Collection<?> c) {
    //判断请求是否为空
    Objects.requireNonNull(c);
    //匹配删除方法
    return batchRemove(c, false);
}

6.1 匹配删除batchRemove(Collection<?> c, boolean complement)方法

private boolean batchRemove(Collection<?> c, boolean complement) {
    final Object[] elementData = this.elementData;
    int r = 0, w = 0;
    boolean modified = false;
    try {
        //迭代存储数据
        for (; r < size; r++){
            //判断传入Collection子类对象实例中是否未包含存储元素对应的值,
            //由于complement=false,即从存储数组中找出与Collection子类对象实例中不一致的值
            //那么当迭代完后,索引小于w的数据是不匹配的,不需要删除,而>=w的则需要删除,好巧妙啊
            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()发生异常,否则不可能出现r != size
        if (r != size) {
            //将未迭代的元素向前挪动r-w个位置,与之前不匹配的合成一个完成的存储数组
            System.arraycopy(elementData, r,
                             elementData, w,
                             size - r);
            w += size - r;
        }
        if (w != size) {
            //说明传入实例中的元素与存储数组中的元素不全匹配
            // clear to let GC do its work
            for (int i = w; i < size; i++){
                //将大于等会w之后的元素清空,好让GC尽快回收
                elementData[i] = null;
            }
            
            modCount += size - w;
            size = w;
            modified = true;
        }
    }
    return modified;
}
  1. 删除指定区间元素removeRange(int fromIndex, int toIndex)方法
    其策略是现将索引为toIndex之后的元素移到fromIndex之后,然后再将索引为size - (toIndex-fromIndex)及之后的元素删除
protected void removeRange(int fromIndex, int toIndex) {
    modCount++;
    //计算需要移动元素个数
    int numMoved = size - toIndex;
    //将索引为toIndex及之后的元素移动到fromIndex之后
    System.arraycopy(elementData, toIndex, elementData, fromIndex,
                     numMoved);

    // clear to let GC do its work
    //计算删除后的存储元素个数newSize
    int newSize = size - (toIndex-fromIndex);
    for (int i = newSize; i < size; i++) {
        //将newSize-size之间的元素删除
        elementData[i] = null;
    }
    size = newSize;
}
上一篇下一篇

猜你喜欢

热点阅读