JDK源码系列之、ArrayList

属性
// 序列化版本UID
private static final long
serialVersionUID = 8683452581122892189L;

/**

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

/**

  • 用于空实例的共享空数组实例
  • new ArrayList(0);
    */
    private static final Object[]
    EMPTY_ELEMENTDATA = {};

/**

  • 用于提供默认大小的实例的共享空数组实例
  • new ArrayList();
    */
    private static final Object[]
    DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

/**

  • 存储ArrayList元素的数组缓冲区
  • ArrayList的容量,是数组的长度
  • non-private to simplify nested class access
    */
    transient Object[] elementData;

/**

  • ArrayList中元素的数量
    */
    private int size;
    小朋友,你四否有很多问号?

为什么空实例默认数组有的时候是EMPTY_ELEMENTDATA,而又有的时候是DEFAULTCAPACITY_EMPTY_ELEMENTDATA
为什么elementData要用transient修饰?
为什么elementData没有被private修饰?难道正如注释所写的non-private to simplify nested class access
带着问题,我们继续往下看。

构造方法
带初始容量的构造方法
/**

  • 带一个初始容量参数的构造方法
  • @param initialCapacity 初始容量
  • @throws 如果初始容量非法就抛出
  •      IllegalArgumentException
    

/
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);
}
}
如果initialCapacity > 0,就创建一个新的长度是initialCapacity的数组
如果initialCapacity == 0,就使用EMPTY_ELEMENTDATA
其他情况,initialCapacity不合法,抛出异常
无参构造方法
/
*

  • 无参构造方法 将elementData 赋值为
  • DEFAULTCAPACITY_EMPTY_ELEMENTDATA
    /
    public ArrayList() {
    this.elementData =
    DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    带一个集合参数的构造方法
    /
    *
  • 带一个集合参数的构造方法
  • @param c 集合,代表集合中的元素会被放到list中
  • @throws 如果集合为空,抛出NullPointerException
    */
    public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    // 如果 size != 0
    if ((size = elementData.length) != 0) {
    // c.toArray 可能不正确的,不返回 Object[]
    // https://bugs.openjdk.java.net/browse/JDK-6260652
    if (elementData.getClass() != Object[].class)
    elementData = Arrays.copyOf(
    elementData, size, Object[].class);
    } else {
    // size == 0
    // 将EMPTY_ELEMENTDATA 赋值给 elementData
    this.elementData = EMPTY_ELEMENTDATA;
    }
    }
    使用将集合转换为数组的方法
    为了防止c.toArray()方法不正确的执行,导致没有返回Object[],特殊做了处理
    如果数组大小等于0,则使用 EMPTY_ELEMENTDATA
    那么问题来了,什么情况下c.toArray()会不返回Object[]呢?

public static void main(String[] args) {
List list = new ArrayList<>(Arrays.asList(“list”));
// class java.util.ArrayList
System.out.println(list.getClass());

Object[] listArray = list.toArray();
// class [Ljava.lang.Object;
System.out.println(listArray.getClass());
listArray[0] = new Object();

System.out.println();

List<String> asList = Arrays.asList("asList");
// class java.util.Arrays$ArrayList
System.out.println(asList.getClass());

Object[] asListArray = asList.toArray();
// class [Ljava.lang.String;
System.out.println(asListArray.getClass());
// java.lang.ArrayStoreException
asListArray[0] = new Object();

}
我们通过这个例子可以看出来,java.util.ArrayList.toArray()方法会返回Object[]没有问题。而java.util.Arrays的私有内部类ArrayList的toArray()方法可能不返回Object[]。

为什么会这样?

我们看ArrayList的toArray()方法源码:

public Object[] toArray() {
// ArrayLisy中 elementData是这样定义的
// transient Object[] elementData;
return Arrays.copyOf(elementData, size);
}
使用了Arrays.copyOf()方法:

public static T[] copyOf(T[] original, int newLength) {
// original.getClass() 是 class [Ljava.lang.Object
return (T[]) copyOf(original, newLength, original.getClass());
}
copyOf()的具体实现:

public static <T,U> T[] copyOf(U[] original,
int newLength, Class<? extends T[]> newType) {
@SuppressWarnings(“unchecked”)
/**
* 如果newType是Object[] copy 数组 类型就是 Object
* 否则就是 newType 类型
*/
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;
}
我们知道ArrayList中elementData就是Object[]类型,所以ArrayList的toArray()方法必然会返回Object[]。

我们再看一下java.util.Arrays的内部ArrayList源码(截取的部分源码):

private static class ArrayList extends AbstractList
implements RandomAccess, java.io.Serializable {

// 存储元素的数组
private final E[] a;

ArrayList(E[] array) {
    // 直接把接收的数组 赋值 给 a
    a = Objects.requireNonNull(array);
}

/**
 * obj 为空抛出异常
 * 不为空 返回 obj
 */
public static <T> T requireNonNull(T obj) {
    if (obj == null)
        throw new NullPointerException();
    return obj;
}

@Override
public Object[] toArray() {
    // 返回 a 的克隆对象
    return a.clone();
}

}
这是Arrays.asList()方法源码

public static List asList(T… a) {
return new ArrayList<>(a);
}
不难看出来java.util.Arrays的内部ArrayList的toArray()方法,是构造方法接收什么类型的数组,就返回什么类型的数组。

所以,在我们上面的例子中,实际上返回的是String类型的数组,再将其中的元素赋值成Object类型的,自然报错。

我们还是继续看ArrayList吧…

插入方法
在列表最后添加指定元素
/**

  • 在列表最后添加指定元素

  • @param e 要添加的指定元素

  • @return true
    /
    public boolean add(E e) {
    // 增加 modCount !!
    ensureCapacityInternal(size + 1);
    elementData[size++] = e;
    return true;
    }
    在父类AbstractList上,定义了modCount 属性,用于记录数组修改的次数。
    在指定位置添加指定元素
    /
    *

  • 在指定位置添加指定元素

  • 如果指定位置已经有元素,就将该元素和随后的元素移动到右面一位

  • @param index 待插入元素的下标

  • @param element 待插入的元素

  • @throws 可能抛出 IndexOutOfBoundsException
    */
    public void add(int index, E element) {
    rangeCheckForAdd(index);

    // 增加 modCount !!
    ensureCapacityInternal(size + 1);
    System.arraycopy(elementData, index, elementData, index + 1,
    size - index);
    elementData[index] = element;
    size++;
    }
    插入方法调用的其他私有方法
    /**

  • 计算容量
    */
    private static int calculateCapacity(
    Object[] elementData, int minCapacity) {

    if (elementData ==
    DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
    }

private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(
calculateCapacity(elementData, minCapacity)
);
}

private void ensureExplicitCapacity(int minCapacity) {
modCount++;

// overflow-conscious code
if (minCapacity - elementData.length > 0)
    grow(minCapacity);

}
扩容方法
/**

  • 数组可以分配的最大size
  • 一些虚拟机在数组中预留一些header words
  • 如果尝试分配更大的size,可能导致OutOfMemoryError
    */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

/**

  • 增加容量,至少保证比minCapacity大
  • @param minCapacity 期望的最小容量
    */
    private void grow(int minCapacity) {
    // 有可能溢出的代码
    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);
    }

/**

  • 最大容量返回 Integer.MAX_VALUE
    */
    private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
    throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
    Integer.MAX_VALUE :
    MAX_ARRAY_SIZE;
    }
    通常情况新容量是原来容量的1.5倍
    如果原容量的1.5倍比minCapacity小,那么就扩容到minCapacity
    特殊情况扩容到Integer.MAX_VALUE
    看完构造方法、添加方法、扩容方法之后,上文第1个问题终于有了答案。原来,new ArrayList()会将elementData 赋值为 DEFAULTCAPACITY_EMPTY_ELEMENTDATA,new ArrayList(0)会将elementData 赋值为 EMPTY_ELEMENTDATA,EMPTY_ELEMENTDATA添加元素会扩容到容量为1,而DEFAULTCAPACITY_EMPTY_ELEMENTDATA扩容之后容量为10。

通过反射我们可以验证这一想法。如下:

public static void main(String[] args) {
printDefaultCapacityList();
printEmptyCapacityList();
}

public static void printDefaultCapacityList() {
ArrayList defaultCapacity = new ArrayList();
System.out.println(
“default 初始化长度:” + getCapacity(defaultCapacity));

defaultCapacity.add(1);
System.out.println(
        "default add 之后 长度:" + getCapacity(defaultCapacity));

}

public static void printEmptyCapacityList() {
ArrayList emptyCapacity = new ArrayList(0);
System.out.println(
“empty 初始化长度:” + getCapacity(emptyCapacity));

emptyCapacity.add(1);
System.out.println(
        "empty add 之后 长度:" + getCapacity(emptyCapacity));

}

public static int getCapacity(ArrayList<?> arrayList) {
Class arrayListClass = ArrayList.class;
try {
// 获取 elementData 字段
Field field = arrayListClass.getDeclaredField(“elementData”);
// 开启访问权限
field.setAccessible(true);
// 把示例传入get,获取实例字段elementData的值
Object[] objects = (Object[]) field.get(arrayList);
//返回当前ArrayList实例的容量值
return objects.length;
} catch (Exception e) {
e.printStackTrace();
return -1;
}
}
移除方法
移除指定下标元素方法
/**

  • 移除列表中指定下标位置的元素

  • 将所有的后续元素,向左移动

  • @param 要移除的指定下标

  • @return 返回被移除的元素

  • @throws 下标越界会抛出IndexOutOfBoundsException
    */
    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);
    // 将引用置空,让GC回收
    elementData[–size] = null;

    return oldValue;
    }
    移除指定元素方法
    /**

  • 移除第一个在列表中出现的指定元素

  • 如果存在,移除返回true

  • 否则,返回false

  • @param 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) {
    modCount++;
    int numMoved = size - index - 1;
    if (numMoved > 0)
    System.arraycopy(elementData, index+1, elementData, index,
    numMoved);
    // 将引用置空,让GC回收
    elementData[–size] = null;
    }
    查找方法
    查找指定元素的所在位置
    /
    *

  • 返回指定元素第一次出现的下标

  • 如果不存在该元素,返回 -1

  • 如果 o ==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;
    }
    查找指定位置的元素
    /
    *

  • 返回指定位置的元素

  • @param index 指定元素的位置

  • @throws index越界会抛出IndexOutOfBoundsException
    */
    public E get(int index) {
    rangeCheck(index);

    return elementData(index);
    }
    该方法直接返回elementData数组指定下标的元素,效率还是很高的。所以ArrayList,for循环遍历效率也是很高的。

序列化方法
/**

  • 将ArrayLisy实例的状态保存到一个流里面
    */
    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);

    // 按照顺序写入所有的元素
    for (int i=0; i<size; i++) {
    s.writeObject(elementData[i]);
    }

    if (modCount != expectedModCount) {
    throw new ConcurrentModificationException();
    }
    }
    反序列化方法
    /**

  • 根据一个流(参数)重新生成一个ArrayList
    */
    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();

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

    }
    }
    看完序列化,反序列化方法,我们终于又能回答开篇的第二个问题了。elementData之所以用transient修饰,是因为JDK不想将整个elementData都序列化或者反序列化,而只是将size和实际存储的元素序列化或反序列化,从而节省空间和时间。

创建子数组
public List subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
我们看一下简短版的SubList:

private class SubList extends AbstractList implements RandomAccess {
private final AbstractList parent;
private final int parentOffset;
private final int offset;
int size;

SubList(AbstractList<E> parent,
        int offset, int fromIndex, int toIndex) {
    this.parent = parent;
    this.parentOffset = fromIndex;
    this.offset = offset + fromIndex;
    this.size = toIndex - fromIndex;
    this.modCount = ArrayList.this.modCount;
}

public E set(int index, E e) {
    rangeCheck(index);
    checkForComodification();
    E oldValue = ArrayList.this.elementData(offset + index);
    ArrayList.this.elementData[offset + index] = e;
    return oldValue;
}

// 省略代码...

}
SubList的set()方法,是直接修改ArrayList中elementData数组的,使用中应该注意
SubList是没有实现Serializable接口的,是不能序列化的
迭代器
创建迭代器方法
public Iterator iterator() {
return new Itr();
}
Itr属性
// 下一个要返回的元素的下标
int cursor;
// 最后一个要返回元素的下标 没有元素返回 -1
int lastRet = -1;
// 期望的 modCount
int expectedModCount = modCount;
Itr的hasNext() 方法
public boolean hasNext() {
return cursor != size;
}
Itr的next()方法
public E next() {
checkForComodification();
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];
}

final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
在迭代的时候,会校验modCount是否等于expectedModCount,不等于就会抛出著名的ConcurrentModificationException异常。什么时候会抛出ConcurrentModificationException?

public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
for (int i = 0; i < 10; i++) {
arrayList.add(i);
}
remove(arrayList);
System.out.println(arrayList);
}

public static void remove(ArrayList list) {
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
Integer number = iterator.next();
if (number % 2 == 0) {
// 抛出ConcurrentModificationException异常
list.remove(number);
}
}
}
那怎么写才能不抛出ConcurrentModificationException?很简单,将list.remove(number);换成iterator.remove();即可。why?请看Itr的remove()源码…

Itr的remove()方法
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();

try {
    ArrayList.this.remove(lastRet);
    cursor = lastRet;
    lastRet = -1;
    // 移除之后将modCount 重新赋值给 expectedModCount
    expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
    throw new ConcurrentModificationException();
}

}
Absorbing material: www.goodsmaterial.com

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值