JDK源码分析(3)之 ArrayList 相关

ArrayList的源码其实比较简单,所以我并没有跟着源码对照翻译,文本只是抽取了一些我觉得有意思或一些有疑惑的地方分析的。

一、成员变量 private static final int DEFAULT_CAPACITY = 10; // 默认容量 private static final Object[] EMPTY_ELEMENTDATA = {}; // 空实例的空数组对象 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 也是空数组对象,用于计算添加第一个元素时要膨胀多少 transient Object[] elementData; // 存储内容的数组 private int size; // 存储的数量

其中elementData被声明为了transient,那么ArrayList是如何实现序列化的呢?
查看writeObject和readObject的源码如下:

private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { // Write out element count, and any hidden stuff int expectedModCount = modCount; 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(); } } private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { elementData = EMPTY_ELEMENTDATA; // Read in size, and any hidden stuff 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().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(); } } }

可以看到在序列化的时候是把elementData里面的元素逐个取出来放到ObjectOutputStream里面的;而在反序列化的时候也是把元素逐个拿出来放回到elementData里面的;
这样繁琐的操作,其中最重要的一个好处就是节省空间,因为elementData的大小是大于ArrayList中实际元素个数的。所以没必要将elementData整个序列化。

二、构造函数

ArrayList的构造函数主要就是要初始化elementData和size,但是其中有一个还有点意思

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; } }

可以看到在Collection.toArray()之后又判断了他的Class类型是不是Object[].class,这个也注释了是一个bug,那么在什么情况下会产生这种情况呢?

// test 6260652 private static void test04() { List<String> list = Arrays.asList("111", "222", "333"); System.out.println(list.getClass()); Object[] objects = list.toArray(); System.out.println(objects.getClass()); } 打印: class java.util.Arrays$ArrayList class [Ljava.lang.String;

这里可以看到objects的class居然是[Ljava.lang.String,同时这里的ArrayList是java.util.Arrays.ArrayList

// java.util.Arrays.ArrayList public static <T> List<T> asList(T... a) { return new ArrayList<>(a); } private static class ArrayList<E> extends AbstractList<E> implements RandomAccess, java.io.Serializable { private final E[] a; ArrayList(E[] array) { a = Objects.requireNonNull(array); } ... }

从以上例子可以看到,由于直接将外部数组的引用直接赋值给了List内部的数组,所以List所持有的数组类型是未知的。之前讲过数组是协变的,不支持泛型,所以只有值运行时再知道数组的具体类型,所以导致了以上的bug;难怪《Effective Java》里面讲数组的协变设计,不是那么完美。

三、常用方法

由于ArrayList是基于数组的,所以他的api基本都是基于System.arraycopy()实现的;

public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);

可以看到这是一个native方法,并且JVM有对这个方法做特殊的优化处理,

private static void test05() { int[] s1 = {1, 2, 3, 4, 5, 6, 7, 8, 9}; int[] s2 = {1, 2, 3, 4, 5, 6, 7, 8, 9}; System.arraycopy(s1, 3, s2, 6, 2); System.out.println(Arrays.toString(s1)); System.out.println(Arrays.toString(s2)); } 打印: [1, 2, 3, 4, 5, 6, 7, 8, 9] [1, 2, 3, 4, 5, 6, 4, 5, 9] private static void test06() { int[] s1 = {1, 2, 3, 4, 5, 6, 7, 8, 9}; int[] s2 = {1, 2, 3, 4, 5, 6, 7, 8, 9}; System.arraycopy(s1, 3, s2, 6, 5); System.out.println(Arrays.toString(s1)); System.out.println(Arrays.toString(s2)); } // 抛出异常`java.lang.ArrayIndexOutOfBoundsException` private static void test07() { int[] s1 = {1, 2, 3, 4, 5, 6, 7, 8, 9}; int[] s2 = {1, 2, 3, 4, 5, 6, 7, 8, 9}; System.arraycopy(s1, 8, s2, 5, 3); System.out.println(Arrays.toString(s1)); System.out.println(Arrays.toString(s2)); } // 抛出异常`java.lang.ArrayIndexOutOfBoundsException`

从上面的测试可以了解到:

System.arraycopy()就是将源数组的元素复制到目标数组中,

如果源数组和目标数组是同一个数组,就可以实现数组内元素的移动,

源数组和目标数组的下标越界,都会抛出ArrayIndexOutOfBoundsException。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzzxjs.html