03 java list 源码浅析
三大数据结构链表、树和图,顺序表作为其中的一种,可以说是平时编程中最长使用到的。List接口是顺序表在java中的实现,它有很多子接口和实现类,平时的编程中使用起来非常方便。但是更进一步,我们有必要对其实现和原理进行理解,并和数据结构中所学比较,并应用于平时的编程中,编写出高效率的代码。
首先看下list接口的层次关系,下图由本人根据jdk的类结构简单画的:
从上图可以看出,list接口有Collection接口衍生而出,那么首先了解下Collection接口。
Collection
collection 是集合框架的根,他定义的集合操作的通用行为,如添加元素、批量添加、删除、批量删除、集合大小、包含、迭代器等。它的接口定义这里不再贴,在关系上,Collection是继承于Iterable接口的,其只有一个方法:
- public interface Iterable<T> {
- /**
- * Returns an {@link Iterator} for the elements in this object.
- *
- * @return An {@code Iterator} instance.
- */
- Iterator<T> iterator();
- }
其中Iterator如下:
- public interface Iterator<E> {
- public boolean hasNext();
- public E next();
- public void remove();
- }
AbstractCollection
AbstractCollection 是Collection接口的抽象实现,实现了其中大部分的功能。如下所示,我们实现自己的Collection只需要实现三个方法即可:
- Collection<String> c =
- /**
- *
- *自定义实现示例,在AbstractCollection 中的一些方法的实现中,如Clear,调用了
- *iterator()方法,而在这些方法在子类中,实现这是典型的template模式。
- *
- */
- new AbstractCollection<String>() {
- /* *
- * 迭代器
- */
- @Override
- public Iterator<String> iterator() {
- // TODO Auto-generated method stub
- return null;
- }
- /*
- * 大小
- */
- @Override
- public int size() {
- // TODO Auto-generated method stub
- return 0;
- }
- /*
- * 抽象实现是抛出异常,我们需要自己实现
- */
- @Override
- public boolean add(String e) {
- return true;
- }
- };
如下代码片段摘自AbstractCollection,调用了iterator方法,其中有很多类似代码,如addAll、removeAll、contains、toArray()等,这些实现只是基本的实现,子类中有更有效率的就会覆盖它。典型的可见后面的Arraylist的toArray()。
- public void clear() {
- Iterator<E> e = iterator();
- while (e.hasNext()) {
- e.next();
- e.remove();
- }
- }
List
List接口表示数据结构中的链表,其继承collection接口,又往里面添加了一些链表操作的方法,主要是随机访问、删除、查找、专用的迭代器等,如下所示:
- /**
- 随机获取
- */
- E get(int index);
- /**
- * 随机设置值
- */
- E set(int index, E element);
- /**
- * 随机添加
- */
- void add(int index, E element);
- /**
- *随机移除
- */
- E remove(int index);
- // Search Operations
- /**
- * 查找
- */
- int indexOf(Object o);
- /**
- * 从后查找
- */
- int lastIndexOf(Object o);
- // List Iterators
- /**
- *专用迭代
- */
- ListIterator<E> listIterator();
- /**
- * 从某个位置迭代
- */
- ListIterator<E> listIterator(int index);
- // View
- /**
- * 子列表
- */
- List<E> subList(int fromIndex, int toIndex);
AbstractList
这是List接口的抽象实现,和AbstractCollection类似,实现了基本的功能而把关键的方法延迟到子类中实现。如下所示一个示意子类:
- List<String> l = /**
- *
- *示例实现
- */
- new AbstractList<String>() {
- /*
- * 随机获取
- */
- @Override
- public String get(int index) {
- return null;
- }
- /*
- * 大小
- */
- @Override
- public int size() {
- return 0;
- }
- /*
- * 超类中实现抛出异常,表示不可变list
- *
- * 自己实现后变为可变
- */
- @Override
- public String set(int index, String element) {
- return null;
- }
- /*
- * 默认实现抛出异常
- */
- @Override
- public void add(int index, String element) {
- }
- /**
- * 默认实现抛出异常
- */
- @Override
- public String remove(int index) {
- return null;
- }
- };
ListIterator
List接口添加了新的方法,其中一个方法就是返回ListIterator,其扩展了iterator,增加了向前遍历的方法。主要链表是有序的。
其实现不多说,需要注意的就是迭代器失效问题,在list实现中,维护了一个字段modCount,当此list进行改变操作,如add/remove等的时候,此值会进行改变,当构造迭代器的时候,此值会在迭代器中保留一个副本,使用迭代器中的方法都会检查副本和list中的modCount是否一致,如果不一致,抛出迭代器异常。需要注意的是,java中的for each语法,经过编译后,是使用的迭代器。
如下部分源码示例:
- private class Itr implements Iterator<E> {
- /** 此类是个内部类,直接访问abstractList的字段
- * Index of element to be returned by subsequent call to next.
- */
- int cursor = 0;
- /**
- * Index of element returned by most recent call to next or
- * previous. Reset to -1 if this element is deleted by a call
- * to remove.
- */
- int lastRet = -1;
- /**
- * 注意这点
- */
- int expectedModCount = modCount;
- /**
- *迭代器的next方法
- */
- public E next() {
- /**
- *操作前进行检查
- */
- checkForComodification();
- try {
- E next = get(cursor);
- lastRet = cursor++;
- return next;
- } catch (IndexOutOfBoundsException e) {
- checkForComodification();
- throw new NoSuchElementException();
- }
- }
- /**
- *检查迭代器失效问题。
- */
- final void checkForComodification() {
- if (modCount != expectedModCount)
- throw new ConcurrentModificationException();
- }
- }
ArrayList
这是一般编程中最长使用的数据结构,基于数组实现的顺序表,但是数组可自动扩容。它直接继承于abstractList,故只研究其关键实现和方法。
数组存储
ArrayList是基于数组的存储,默认构造初始大小是10,后面我们会看到这个初始大小会一定程度上影响其性能:
- /**
- * The array buffer into which the elements of the ArrayList are stored.
- * The capacity of the ArrayList is the length of this array buffer.
- */
- private transient Object[] elementData;
- public ArrayList(int initialCapacity) {
- super();
- if (initialCapacity < 0)
- throw new IllegalArgumentException("Illegal Capacity: "+
- initialCapacity);
- this.elementData = new Object[initialCapacity];
- }
- /**
- * Constructs an empty list with an initial capacity of ten.
- */
- public ArrayList() {
- this(10);
- }
Add方法
List接口有两个add的重载方法,第一个是在list的末尾添加元素,第二个是随机位置添加元素,其自动扩展数据容量的方法是ensureCapacity(int),保证大小:
- /**
- * 此方法在父类中已经实现,但是arralylist覆盖了其实现,采用更加有效率的实现
- */
- public boolean add(E e) {
- ensureCapacity(size + 1); // Increments modCount!!
- elementData[size++] = e;
- return true;
- }
- /**
- * 任意位置添加元素,相应的元素后移,保持链表的特性。
- * 其中System.arrayCopy效率较高。
- *
- */
- public void add(int index, E element) {
- if (index > size || index < 0)
- throw new IndexOutOfBoundsException(
- "Index: "+index+", Size: "+size);
- ensureCapacity(size+1); // Increments modCount!!
- System.arraycopy(elementData, index, elementData, index + 1,
- size - index);
- elementData[index] = element;
- size++;
- }
- /**
- * 保证list链表的大小,如果不足则扩容。
- * 这个方法比较消耗性能,因此,如果实现可以知道或者预估AyyayList的大小,那么
- * 可以在构造的时候设定合适的初始容量。
- */
- public void ensureCapacity(int minCapacity) {
- modCount++;
- //获取旧的数组大小
- int oldCapacity = elementData.length;
- //比较,如果不足则扩容
- if (minCapacity > oldCapacity) {
- Object oldData[] = elementData;
- int newCapacity = (oldCapacity * 3)/2 + 1;
- if (newCapacity < minCapacity)
- newCapacity = minCapacity;
- // 调用效率较高的arraycopy
- elementData = Arrays.copyOf(elementData, newCapacity);
- }
- }
Vector
实现和ArraaylIst基本一致,只是在方法上加了同步操作,但需要注意其线程安全是相对的,比如一个线程进行add操作,另外一个线程迭代,肯定会出异常。
另外有个Stack继承Vector,添加了栈的相关方法,如push,和pop。
LinkedList
基于链表的顺序表,和ArrayList相比,其随机插入和删除的效率较高,因为其不需要扩容和移动元素操作,但是随机访问效率较低(随机访问需要从头节点遍历)。
AbstractSequentialList
LinkedList其继承于AbstractSequentialList,而其中AbstractSequentialList实现了abstractlist中的抽象方法,都是基于迭代器实现的,它同时把返回迭代器的方法覆盖为抽象方法。故LinkedList实现的关键在于迭代器,如下所示:
- public abstract class AbstractSequentialList<E> extends AbstractList<E> {
- /**
- *基于迭代器实现的get
- */
- public E get(int index) {
- try {
- return listIterator(index).next();
- } catch (NoSuchElementException exc) {
- throw new IndexOutOfBoundsException("Index: "+index);
- }
- }
- /**
- *基于迭代器实现的set
- */
- public E set(int index, E element) {
- try {
- ListIterator<E> e = listIterator(index);
- E oldVal = e.next();
- e.set(element);
- return oldVal;
- } catch (NoSuchElementException exc) {
- throw new IndexOutOfBoundsException("Index: "+index);
- }
- }
- /*
- 基于迭代器实现的add
- */
- public void add(int index, E element) {
- try {
- listIterator(index).add(element);
- } catch (NoSuchElementException exc) {
- throw new IndexOutOfBoundsException("Index: "+index);
- }
- }
- /**
- * 基于迭代器实现的remove
- *
- */
- public E remove(int index) {
- try {
- ListIterator<E> e = listIterator(index);
- E outCast = e.next();
- e.remove();
- return outCast;
- } catch (NoSuchElementException exc) {
- throw new IndexOutOfBoundsException("Index: "+index);
- }
- }
- public Iterator<E> iterator() {
- return listIterator();
- }
- /**
- * Returns a list iterator over the elements in this list (in proper
- * sequence).
- *
- */
- public abstract ListIterator<E> listIterator(int index);
- }
LinkedList
Linkedlist除了实现了List接口,还实现了队列的相关接口,这里略过不提。由listiterator接口,可知linkedList也是一个可双向遍历的顺序表。
只需研究每个链表节点的结构和迭代器实现。
链表节点如下所示,是一个静态内部类:
- private static class Entry<E> {
- E element;
- Entry<E> next;
- Entry<E> previous;
- Entry(E element, Entry<E> next, Entry<E> previous) {
- this.element = element;
- this.next = next;
- this.previous = previous;
- }
- }
其迭代器实现是一个内部类,接下来以其add操作说明,其他类似:
- private class ListItr implements ListIterator<E> {
- private Entry<E> lastReturned = header;
- private Entry<E> next;
- private int nextIndex;
- private int expectedModCount = modCount;
- /**
- * 构造的时候采用一个优化技巧,根据index决定从前还是从后遍历。
- */
- ListItr(int index) {
- if (index < 0 || index > size)
- throw new IndexOutOfBoundsException("Index: "+index+
- ", Size: "+size);
- if (index < (size >> 1)) {
- next = header.next;
- for (nextIndex=0; nextIndex<index; nextIndex++)
- next = next.next;
- } else {
- next = header;
- for (nextIndex=size; nextIndex>index; nextIndex--)
- next = next.previous;
- }
- }
- /*
- * 链表插入,在构造此迭代器的时候,index就是插入的位置,故直接插入元素即可
- *
- * addbefor是ArrayList的方法,就是链表插入,指针改变的过程,这里不赘述。
- */
- public void add(E e) {
- checkForComodification();
- lastReturned = header;
- addBefore(e, next);
- nextIndex++;
- expectedModCount++;
- }
- final void checkForComodification() {
- if (modCount != expectedModCount)
- throw new ConcurrentModificationException();
- }
- }
- /**
- *AbstractSequentialList的方法,随机插入。首先构造从index开始的迭代器
- */
- public void add(int index, E element) {
- try {
- listIterator(index).add(element);
- } catch (NoSuchElementException exc) {
- throw new IndexOutOfBoundsException("Index: "+index);
- }
- }
CopyOnWriteArrayList
CopyOnWriteArrayList是java并发包中的一个工具类,在jdk1.5的时候被引入,单独一个它的内容足以写一篇文章,故这里不再展开,只是简要对其说明。
其基本思想从名称中可以看出,当进行写操作的时候,先复制复制出一个副本,写操作对副本进行。
读和遍历操作发生在操作发生的那一刻存在的副本上。
写操作的时候枷锁,读操作的时候不加锁。并通过java内存模型的语义,进行优雅的设计。
CopyOnWrite并发容器用于读多写少的并发场景。CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。