jdk源码阅读一:ArrayList
2017-01-14 22:59
831 查看
ArrayList介绍
ArrayList是我们使用的最多的集合类了。特点主要如下:
基于数组实现底层存储,容量可以自动扩展。
实现List全部接口。
允许null对象
提供操作数组容量大小的方法。
粗略来看和Vector类相同,但是 Vector在 方法上加了同步,所以Vector是线程安全的。
size,isEmpty,get,set,iterator和listIterator运行时间为常量。
add方法 如果添加n个元素 时间复杂度O(n)
ArrayList非 thradsafe,所以自己wrap或者用Collections#synchronizedList
返回的iterator是fail-fast,如果iterator创建后,修改了list结构则会抛出异常ConcurrentModificationException,
注意这里异常表明是一个bug,需要避免,而不能处理业务逻辑。
size 变量
size 表示当前list中存储了元素的个数,初始化为0。add一个元素加一,删除一个元素减一。capacity(容量)增长策略
每个ArrayList示例都有一个capacity。表示数组(list中用来储存元素)的大小。capacity至少和size相等,不会小于它。capacity大小是自动增长的。你也可以调用ArrayList#ensureCapacity方法改变容量。
不过如果你将容量改小 是不起作用的,ensureCapacity方法不允许。
增长容量的方法有三个。
ensureCapacity
` /** * Increases the capacity of this <tt>ArrayList</tt> instance, if * necessary, to ensure that it can hold at least the number of elements * specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ 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); } }`
说明:
public的,说明是提供给用户操作的,因为其他两个方法是private的。这个方法Arraylist内部是没有调用的。内部扩容调用的是ensureCapacityInternal方法。
增加容量如果有必要:原则就是判断minCapacity 和当前的capacty大小,如果大则增加。
minExpand(最小扩展容量)可能是0 ,也可能是DEFAULT_CAPACITY=10。
一般ensureCapacity类型的三个方法只是确保最小容量满足,但是真正的增长策略可能不同,比如add一个元素,minCapacity=size+1。
这时候如果需要扩容(minCapacity>size)那么数组只需要增大1,即可,但是这样后面每次都要复制,所以后面真正讲到的grow方法策略不是+1,而是然后大小增加150%。
源码分析:
minExpand表示最小拓展容量
首先我们看下ArrayList创建的两种创建方法和他的两个常量
ArrayList arr = new ArrayList<>();
ArrayList arr = new ArrayList<>(0);
/** * Shared empty array instance used for empty instances. */ private static final Object[] EMPTY_ELEMENTDATA = {}; /** * Shared empty array instance used for default sized empty instances. We * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when * first element is added. */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
如果使用第一种创建方法那么this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
如果使用第二种创建方法 则this.elementData = EMPTY_ELEMENTDATA;
什么意思?就是说如果你没穿参数,那么Arraylist 设置 默认空数组。如果你传了个0,则认为是你故意设置容量为0的。它用上面两个常量来区分这种行为,区分的目的在于对待这两种不同的空数组, 第一次的容量增长策略不同。
下面我们接着说ensureCapacity方法,就好解释多了,如果elementData!=DEFAULTCAPACITY_EMPTY_ELEMENTDATA 那么minExpand最小扩展容量=0,如果否则minExpand = DEFAULT_CAPACITY=10。
委托给ensureExplicitCapacity方法,扩容
然后比较传入参数和minExpand的大小。取其中大者。然后委托给ensureExplicitCapacity方法。
ensureExplicitCapacity
`private void ensureExplicitCapacity(int minCapacity) { //修改次数 计数器 在iterator迭代出现并发修改快速失败就是利用到该字段 modCount++; // overflow-conscious code // 如果minCapacity大于数组长度则扩容 if (minCapacity - elementData.length > 0) grow(minCapacity); }`
grow方法是真正的增长策略方法:
/** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ 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); }
方法说明:
1. newCapacity先扩展到为原来的1.5倍。
2. 比较newCapacity和minCapacity选择大的。
3. newCapacity如果超过MAX_ARRAY_SIZE(= Integer.MAX_VALUE - 8),则调用hugeCapacity,增大到Integer.MAX_VALUE。
4. 调用Arrays.copyOf调整elementData到新容量。
ensureCapacityInternal
private void ensureCapacityInternal(int minCapacity) { //如果满足elementData是默认空数组,则比较 minCapacity和DEFAULT_CAPACITY 取大者 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); }
到此容量增长策略已经分析完成。
扩容方法总结:
ensureCapacity是public给开发人员调用的api,内部是没有调用的。
ensureCapacityInternal才是内部判断是否需要扩容的方法。
上面两个方法都是调用ensureExplicitCapacity实现。
ensureCapacityInternal和ensureExplicitCapacity是私有的。
这三个方法是判断是否需要扩容,但是不一定真正执行扩容动作,真正的方法是grow。
如果我们能够提前预知list容量 直接赋值容量,可以减少后面自动扩容导致的数组复制的开销,调用ensureCapacity直接到一个比较大的容量也是可行的。
List常用方法分析
插入元素
顺序插入add(E e)
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
确保容量加1,然后直接数组赋值。时间复杂度O(1)。
随机插入
public void add(int index, E element) { //检查index是否在 0-size范围内 rangeCheckForAdd(index); ensureCapacityInternal(size + 1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }
因为是随机插入,所以要把插入位置index以及以后的元素向后移动一位。所以速度会慢些。
插入集合addAll
这里不再细讲,大致思路是将Collection转成数组然后插入数组。不过如果是随机插入容量足够 还是复制了两次(第一次向后移动n位,第二次赋值Collection 到指定位置)。clear清空
很简单 循环将数组设置null检索操作
主要包括contains,containsAll,indexOf,lastIndexOf。containsAll循环调用的contaions,contaios调用的indexOf。
indexOf和lastIndexOf差不多,一个从数组顺序查找,一个从数组逆序查找。我们主要看下lastIndexOf。
public int lastIndexOf(Object o) { if (o == null) { for (int i = size-1; i >= 0; i--) if (elementData[i]==null) return i; } else { for (int i = size-1; i >= 0; i--) if (o.equals(elementData[i])) return i; } return -1; }
方法也很简单,但是时间复杂度还是非常高的,平均O(n*n)。
get 随机获取元素
速度很快O(1),会先检查index是否合法[0,size)。remove方法
有四个,根据元素删除,需要遍历数组,找到位置,然后移动数组。根据index删除,直接移动数组。效率都不高。removeIf是1.8新加的,根据传入lambda 表达式判断是否删除元素,会应用于每个元素,内部通过迭代器遍历元素,判断是否需要该元素。
迭代器
有iterator,listiterator 和Spliterator。iterator主要通过内部类实现,存储了一个cursor,默认无参的=0。
listiterator继承了iterator,实现了可以向前遍历。
Spliterator(splitable iterator可分割迭代器)接口是Java为了并行遍历数据源中的元素而设计的迭代器,这个可以类比最早Java提供的顺序遍历迭代器Iterator,但一个是顺序遍历,一个是并行遍历。
可以参考:https://segmentfault.com/q/1010000007087438
iterator分析
public Iterator<E> iterator() { return new Itr(); }
是ArrayLIst一个内部类,截取部分代码:
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() { //每次都会检查modCount是否改变,改变则抛出异常 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]; }
listiterator分析
private class ListItr extends Itr implements ListIterator<E> { ListItr(int index) { super(); cursor = index; } public boolean hasPrevious() { return cursor != 0; } public int nextIndex() { return cursor; } public int previousIndex() { return cursor - 1; } @SuppressWarnings("unchecked") public E previous() { checkForComodification(); int i = cursor - 1; if (i < 0) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i; return (E) elementData[lastRet = i]; }
部分代码如上,继承了Itr,添加了逆序遍历功能。
子列表
通过一个内部类实现,部分代码如下:private class SubList extends AbstractList<E> implements RandomAccess { private final AbstractList<E> parent; private final int parentOffset; private final int offset; int size;
看出来 主要通过一个offset变量来记录开始位置在数组中的起始位置,还是共享父类的数组。
序列化、反序列化
ArrayList实现了Serializable接口,所以它支持序列化,但是我们看到底层存储:transient Object[] elementData; // non-private to simplify nested class access
又是transient,表示忽略该字段的序列化,其实他是通过writeObject(java.io.ObjectOutputStream s),readObject(java.io.ObjectInputStream s)两个方法重写实现的。注意这两个方法是固定写法。以write为例:
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]); } //如果序列化过程中发现有修改list结构 则抛出异常 if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } }
上面代码中写了两次size,网上说是兼容老板jdk原因。还提到反序列化老版本会恢复容量 新版本只恢复size大小的容量。
具体参考:
http://mushanshitiancai.github.io/2016/06/17/java/language/JDK%E6%BA%90%E7%A0%81%E9%98%85%E8%AF%BB-ArrayList/#序列化-反序列化
克隆
克隆只是浅拷贝。元素对象还是共享的。其他总结
用的最多的移动数组方法是Arrays.copyOf和 System.arraycopy。pdf下载
这里写链接内容相关文章推荐
- [Jdk1.8源码阅读]ArrayList
- 【JDK1.8】JDK1.8集合源码阅读——ArrayList
- jdk源码阅读——ArrayList
- JDK源码阅读——ArrayList(1)
- JDK源码阅读——ArrayList(2)
- JDK1.8源码阅读之——VECTOR,ARRAYLIST, LINKEDLIST
- JDK源码阅读(一) ArrayList
- [JDK1.7源码阅读]ArrayList
- jdk源码阅读之ArrayList
- JDK 1.7源码阅读笔记(二)集合类之ArrayList
- JDK源码阅读——ArrayList\LinkedList
- 【JDK源码阅读3-util】ArrayList
- JDK源码阅读-ArrayList
- [Jdk源码阅读]ArrayList实现
- JDK源码阅读ArrayList
- jdk源码阅读之——arraylist
- JDK源码阅读之ArrayList
- 通过jdk源码理解ArrayList和LinkedList区别
- JDK 1.7源码阅读笔记(七)集合类之HashMap
- jdk集合源码之ArrayList