在Arrays源码分析中,我们提到ArrayList是Array的内部类,并不是其子类。所以有的初始化方法是不对的,具体可以看Arrays.md。
线程不安全
实现了当默认大小大于实际元素数时,截断。
1.3.2 ensureCapacity()方法
public void ensureCapacity(int minCapacity) { int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) // any size if not default element table ? 0 // larger than default for default empty table. It's already // supposed to be at default size. : DEFAULT_CAPACITY; if (minCapacity > minExpand) { ensureExplicitCapacity(minCapacity); } }公共可用的方法用于用户扩容,可以指定一个容量,该方法通过调用ensureExplicitCapacity
方法进行扩容。
可以看到,当用户传入需要增加的容量大小时,grow方法将被调用。
记录最初的容量大小 int oldCapacity = elementData.length;
扩容至1.5倍 int newCapacity = oldCapacity + (oldCapacity >> 1);
如果扩容后的大小仍然小于需要分配的大小,则直接指定为用户需要的大小
if (newCapacity - minCapacity < 0) newCapacity = minCapacity;如果大于最大容量,则直接指定最大容量进行分配。
其实涉及到ArrayList的内存空间分配仅包括如下函数:
构造器 ArrayList() 无参构造ArrayList(int initialCapacity) 有参构造ArrayList(Collection<? extends E> c) 从已有的collection中构建ArrayList 显示扩容方法 ensureCapacity ensureExplicitCapacitygrow无参的构造方法执行之后,默认返回
public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};所以elementData返回的是空Object数组。
那么此时容器并没有分配大小,但是当向容器中add元素的时候,容器的内存将会发生改变,可以看下列代码:
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); }以上代码便是add之后引发的所有流程。可以看到内存的分配方法都是private修饰的,那么说明分配不允许用户干预,都是在类的内部操作。调用无参构造之后,size仍然是0,那么调用**ensureCapacityInternal(size + 1),那么在此方法内部,会对容器是否为空进行判断,然后调用ensureExplicitCapacity方法,修改modcount标志,最后调用grow()方法完成分配操作。
有参构造,实际上跟无参构造是一致的,只不过有参构造的时候,显示的指定了当前容器的容量。其余步骤都是一致的。size无论是有参还是无参,他都表示容器中元素的个数,而不是容器的容量。
第三种构造器,也很简单,只是在给定collection的基础上,去创建ArrayList。
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; } }有三个点:
toArray()方法
collection的抽象方法,比如Arraylist中的实现如下:
public Object[] toArray() { return Arrays.copyOf(elementData, size); }Arrays.copyOf()方法
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; }上述方法的思想很简单,首先根据反射获得传入进来的数组类型,从而新建一个与原数组大小相同、类型相同的数组,然后利用System.arraycopy复制来赋值。
Fast-fail机制是java中的错误检测机制,当多个或者单个线程在结构上对集合进行改变的时候,就会抛出==ConcurrentModificationException==异常。这就是fast-fail机制。
分析fast-fail机制:
public Iterator<E> iterator() { return new Itr(); } private class Itr implements Iterator<E> { int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount; public boolean hasNext() { return cursor != size; } @SuppressWarnings("unchecked") public E next() { checkForComodification(); .... } public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }这是涉及到的Iterator的代码,其属于ArrayList内部的方法,不允许外部访问。
ArrayList对象可以调用此方法,完成遍历。示例代码:
ArrayList<Integer> list = new ArrayList<>(); for(int i =0;i<20;i++) { list.add(i); } Iterator<Integer> iterator = list.iterator(); while(iterator.hasNext()) { System.out.println(iterator.next()); }当ArrayList对象调用iterator方法时,则会返回new Itr(),然后继续向下调用。
现在可以分析一下,我们可以看到在Ite这个类中next()和remove方法在执行时都会先调用***checkForComodification*** 方法。而此方法中,则会检查modCount和expectedModCount是否相等,如果不等,则会返回***ConcurrentModificationException*** 异常。这边是fast-fail的检测机制。
下面我们讨论一下modCount的意义。
在上述测试代码中,我们可以看到,当ArrayList对象调用iterator()方法的时候,将会在***class Itr*** 内部对属性***expectedModCount***赋值,即 ***int expectedModCount = modCount;***,那么此时二者的值是相等的。我们说只要二者相等,那么就不会引发fast-fail机制。
引发Fast-Fail机制:
package com.wangye.Test; import java.util.*; public class MyTest { final static int tmp = 10; public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<>(); for(int i =0;i<20;i++) { list.add(i); } Iterator<Integer> iterator = list.iterator(); while(iterator.hasNext()) { list.remove(0); //新加的代码 System.out.println(iterator.next()); } } } Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901) at java.util.ArrayList$Itr.next(ArrayList.java:851) at com.wangye.Test.MyTest.main(MyTest.java:20)当使用ArrayList对象自带的remove方法时,出现异常,引发Fast-Fail机制。
原因:
ArrayList中add、remove、clear方法对容器结构做出了改变,所以当这些方法被调用时,一定会改变modCount,其他操作则不会。但是ArrayList中modCount改变后,这时expectedModCount 仍然等于初始的modCount值。所以引发Fast-Fail。什么时候不会引发Fast-fail:
当且仅当使用Iterator的remove方法时,不会引发Fast-Fail,那是因为,Iterator在执行remove过程中,重新赋值 expectedModCount = modCount在java中,将对象转换成字节序列的形式,进行持久化和传输操作。常用于网络传输。
实现方法:实现Serializable接口即可。
反序列化之后,字节序列可以被转换成java对象。
作用:让某些被修饰的变量不被序列化操作,一旦变量被关键字transient修饰,则当前变量将不参与序列化,也就是说反序列化之后将得到空值。
原来在ArrayList中,序列化操作采用的并不是实现Serializable接口,而是在内部定义私有方法,如下:
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(); } } /** * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is, * deserialize it). */ 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 ensureCapacityInternal(size); Object[] a = elementData; // Read in all elements in the proper order. for (int i=0; i<size; i++) { a[i] = s.readObject(); } } }也就是说,ArrayList在序列化的时候,调用的是writeObject方法,将size和elementData写入了ObjectOutputStream,反序列化只是调用readObject方法,从ObjectInputStream中读取size和element,从而恢复elementData。
我认为,elementData是缓存数组,那么数组的长度在很多时候是大于实际的存储元素数的,如果直接序列化,可能造成空间上的浪费。
) { // be like clone(), allocate array based upon size not 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(); } } } 也就是说,ArrayList在序列化的时候,调用的是writeObject方法,将size和elementData写入了ObjectOutputStream,反序列化只是调用readObject方法,从ObjectInputStream中读取size和element,从而恢复elementData。 #### 我的思考:为什么不直接对elementData缓存数组做序列化? #### 我的答案: 我认为,elementData是缓存数组,那么数组的长度在很多时候是大于实际的存储元素数的,如果直接序列化,可能造成空间上的浪费。