Java集合之List

来源:互联网 发布:ubuntu 共享打印机 编辑:程序博客网 时间:2024/05/23 16:55

Java中最常用的三种集合类型: List,Set, Map, Queue

List最常用的两种实现:

  • ArrayList
  • LinkedList

Iterable && Iterator

Iterable接口表示可通过迭代器进行访问,通过迭代器访问的好处是将容器本身的行为和元素访问分开,将对所有容器的访问抽象出来,形成共同的方法,既方便理解,也方便使用。Iterator仅能单向访问容器,提供了获取可用元素、删除刚获取元素以及判断容器中是否还有可用元素的方法。

//获取容器iteratorpublic interface Iterable<T> {    Iterator<T> iterator();}public interface Iterator<E> {    boolean hasNext();    E next();    void remove();}

Collection

Collection接口继承自Iterable, 表示容器可通过iterator进行访问,Collection指明集合提供哪些功能,一般无外乎元素的增删改查

public interface Collection<E> extends Iterable<E> {    // Query Attribute    int size();    boolean isEmpty();    //query     boolean contains(Object o);    //visit    Iterator<E> iterator();    Object[] toArray();    <T> T[] toArray(T[] a);    // Modification Operations    boolean add(E e);    boolean remove(Object o);    void clear();    // Bulk Operations    boolean containsAll(Collection<?> c);    boolean addAll(Collection<? extends E> c);    boolean removeAll(Collection<?> c);    boolean retainAll(Collection<?> c);    //inherit form Object    boolean equals(Object o);    int hashCode();}

增删指增删某个元素及删除所有,查包括查询自身属性,是否包含某个元素,更改一般是set方法,Collection中无更改方法,此外,还包括集合与集合之间操作,判断是否是子集,求并集,求差集,求交集。为方便使用数组的特性和方法,还提供了转化为数组的操作。

AbstractCollection

Collection有一个抽象实现AbstractCollection,借助迭代器实现了一些简单的方法,继承类根据自身数据存储访问特点,如果有更高效的实现会重写这些方法。一般Collection接口的子类都继承自AbstractCollection.

//Collection提供的通过toString方法,将集合中元素以逗号分隔连接,[]括起来public String toString() {    Iterator<E> it = iterator();    if (! it.hasNext())        return "[]";    StringBuilder sb = new StringBuilder();    sb.append('[');    for (;;) {        E e = it.next();        sb.append(e == this ? "(this Collection)" : e);        if (! it.hasNext())            return sb.append(']').toString();        sb.append(',').append(' ');    }}

List

List继承关系图:

List Interface继承自Collection,由于List内部是按照插入顺序有序的,List接口除了继承自Collection的方法外,还增加了按索引index的增删改查,相比Collection,提供了修改方法。

public interface List<E> extends Collection<E> {    // Query Operations    int size();    boolean isEmpty();    boolean contains(Object o);    Iterator<E> iterator();    Object[] toArray();    <T> T[] toArray(T[] a);    // Modification Operations    boolean add(E e);    boolean remove(Object o);    // Bulk Modification Operations    boolean containsAll(Collection<?> c);    boolean addAll(Collection<? extends E> c);      boolean removeAll(Collection<?> c);    boolean retainAll(Collection<?> c);    void clear();    // Comparison and hashing    boolean equals(Object o);    int hashCode();    //List自有    // Positional Access Operations    E get(int index);    E set(int index, E element);    void add(int index, E element);    boolean addAll(int index, Collection<? extends E> c);    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);}

ListIterator

Java中List是双向List, 通用Iterator仅能单向访问容器,因此List接口提供了获取ListIterator的方法,ListIterator扩展自通用Iterator,通过ListIterator可双向访问List,可添加,删除,更改容器,以及获取Iterator的Index.

public interface ListIterator<E> extends Iterator<E> {          // Query Operations,双向迭代            boolean hasNext();    E next();       boolean hasPrevious();    E previous();    //迭代器index    int nextIndex();    // 获取刚刚通过previous()元素的index    int previousIndex(); //获取刚刚通过next()访问元素的index    // Modification Operations,修改List    void remove();    void set(E e);    void add(E e);}

此外,List接口的subList方法返回List本身子集的一个视图,返回的view的底层数据依然是List的数据,只是可以更直观的访问操作子集。

AbstactList

List接口有一个抽象实现AbstactList, 实现了List接口中的一些通用功能,因为List接口继承自Collection,因此,AbstactList直接利用AbtractCollection的一些实现,继承自AbtractCollection, 同时优化了AbtractCollection的一些方法,这样实现List接口的容器就可以直接继承自AbstactList。

ArrayList

故名思议,ArrayList就是用数组实现的List,组数是内存连续元素集合,可通过索引快速随机访问。

通过上图可看出ArrayList extend AbstractList, implements List、RandomAccess, Cloneable和Serializable四个接口, 其中RandomAccess, Cloneable、Serializable三个接口都是标记接口,没有任何方法。

既然是用数组实现,数组大小是固定的,就面临一个问题,当List中元素个数超过固定大小时,数组需要动态扩容,并将原内容copy至扩容后的组数中。JDK实现中默认初始大小是10,以1.5倍速率动态扩容。

//内部数组private transient Object[] elementData;//初始容量大小private static final int DEFAULT_CAPACITY = 10; private void grow(int minCapacity) {    // overflow-conscious code    int oldCapacity = elementData.length;    //以1.5倍速率扩容    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);}

ArrayList内部通过一个Object[]数组保存对象,这样通过index可以直接定位元素,批量操作移动也可借助System.arraycopy native方法高效实现。

LinkedList

LinkedList内部元素之间通过链表的形式组织,只能顺序访问List,此外链表可以方便的在头尾部添加删除获取元素,因此JDK实现中为LinkedList实现了Deque接口,添加了双向队列的功能。

//指向头结点transient Node<E> first;//指向尾结点transient Node<E> last;//LinkedList中一个元素的结构private static class Node<E> {    E item;    Node<E> next;    Node<E> prev;    Node(Node<E> prev, E element, Node<E> next) {        this.item = element;        this.next = next;        this.prev = prev;    }}

Iterator FailFast

当对某个容器进行迭代时,不允许除当前迭代器以外的方式改变容器的内部结构,例如其他的线程改变容器结构,这样可以快速告诉访问者底层发生了改变,而不是让访问者继续进行当前的操作,以致后续发生更严重或莫名奇妙的错误。

Java中迭代器快速失败是通过抛出ConcurrentModificationException体现的,但是此
行为无法得到保证,即使底层结构真正发生改变,也不一定每次都会抛出此异常,因为很难探测到每一次结构更改,因此不能依赖于是否抛出ConcurrentModificationException来进行错误纠正,仅可用于检测bug,即如果有ConcurrentModificationException,则可认为程序有bug。java.util包下的集合类都是Fail Fast.

ArrayList中是如何实现Iterator FailFast的呢?通过源码发下是在每次获取Iterator时在
迭代器内部保存了当前List的修改次数,每次调用next获取元素是都会检测Iterator私有的expectedModCount是否与List内部保存的modCount相等,若不相等则抛出ConcurrentModificationException。详情参考:

https://my.oschina.net/hosee/blog/612718

Vector

从ArrayList/LinkedList实现可以看出,它们并非线程安全的,没有任何同步操作,当有多个线程同时add一个List时,可能会发生数据丢失。实现List接口的Vector是线程安全的,从源码可以看出是在主要的实现方法上添加synchronized关键字,直接在this上加锁,get时也加锁,这样效率较低。

//vector addpublic synchronized boolean add(E e) {    modCount++;    ensureCapacityHelper(elementCount + 1);    elementData[elementCount++] = e;    return true;}//vector getpublic synchronized E get(int index) {    if (index >= elementCount)        throw new ArrayIndexOutOfBoundsException(index);    return elementData(index);}

CopyOnWriteArrayList

java.util.concurrent.CopyOnWriteArrayList也线程安全的实现了的List,其直接implements List,没有继承自AbstractList.

CopyOnWriteArrayLis主要思想就是在有修改内部数组操作时copy出一个新数组,对新数组进行修改操作,修改完成后再将新数组的引用赋值给List内部数组变量,这样原来的迭代还是基于原始内存数组,不受影响。

//CopyOnWriteArrayList.addpublic boolean add(E e) {    final ReentrantLock lock = this.lock;    lock.lock();    try {        Object[] elements = getArray();        int len = elements.length;        //copy出一个新数组        Object[] newElements = Arrays.copyOf(elements, len + 1);         newElements[len] = e;        //保存新数组        setArray(newElements);        return true;    } finally {        lock.unlock();    }}

注意每次add时都会加锁,这样才能保证所有线程add的数据不会丢失。

相对Fail-Fast, CopyOnWriteArrayLis的迭代器COWIterator是Fail-Safe的,迭代时并不会抛出ConcurrentModificationException,其对原结构的修改是在其copy上进行的,不会被原结构的迭代器感知,这样带来的代价就是每次更改时copy需要额外的空间和时间上的开销,可能产生大量的对象,此外,更改前COWIterator获取的是旧数据,不能保证遍历的是最新的内容。

SynchronizedList

如果更新频率较高,或数组较大时,用Collections.SynchronizedList(List),对所有操作用同一把锁来保证线程安全也许是更好的选择。

static class SynchronizedList<E>    extends SynchronizedCollection<E>    implements List<E> {    private static final long serialVersionUID = -7754090372962971524L;    final List<E> list;    SynchronizedList(List<E> list) {        super(list);        this.list = list;    }    SynchronizedList(List<E> list, Object mutex) {        super(list, mutex);        this.list = list;    }    public boolean equals(Object o) {        if (this == o)            return true;        synchronized (mutex) {return list.equals(o);}    }    public int hashCode() {        synchronized (mutex) {return list.hashCode();}    }    public E get(int index) {        synchronized (mutex) {return list.get(index);}    }    public E set(int index, E element) {        synchronized (mutex) {return list.set(index, element);}    }    public void add(int index, E element) {        synchronized (mutex) {list.add(index, element);}    }    public E remove(int index) {        synchronized (mutex) {return list.remove(index);}    }    public int indexOf(Object o) {        synchronized (mutex) {return list.indexOf(o);}    }    public int lastIndexOf(Object o) {        synchronized (mutex) {return list.lastIndexOf(o);}    }    public boolean addAll(int index, Collection<? extends E> c) {        synchronized (mutex) {return list.addAll(index, c);}    }    public ListIterator<E> listIterator() {        return list.listIterator(); // Must be manually synched by user    }    public ListIterator<E> listIterator(int index) {        return list.listIterator(index); // Must be manually synched by user    }    public List<E> subList(int fromIndex, int toIndex) {        synchronized (mutex) {            return new SynchronizedList<>(list.subList(fromIndex, toIndex),                                        mutex);        }    }    private Object readResolve() {        return (list instanceof RandomAccess                ? new SynchronizedRandomAccessList<>(list)                : this);    }}

Stack

我们知道stack是后进先出的结构,主要提供push、pop、peek方法,直接上源码简单直接,其继承自Vector,因此也是线程安全的List.

publi cclass Stack<E> extends Vector<E> {    public Stack() {    }    public E push(E item) {        addElement(item);        return item;    }    public synchronized E pop() {        E       obj;        int     len = size();        obj = peek();        removeElementAt(len - 1);        return obj;    }    public synchronized E peek() {        int     len = size();        if (len == 0)            throw new EmptyStackException();        return elementAt(len - 1);    }    public boolean empty() {        return size() == 0;    }    public synchronized int search(Object o) {        int i = lastIndexOf(o);        if (i >= 0) {            return size() - i;        }        return -1;    }    /** use serialVersionUID from JDK 1.0.2 for interoperability */    private static final long serialVersionUID = 1224463164541339165L;}
原创粉丝点击