Java 集合 (一) Collection 架构

原文链接: http://www.cnblogs.com/skywang12345/p/3323085.html

 

一、集合总体框架概述

Java集合 是java 提供的工具包(java.util.*),包含了常用的数据结构:集合、链表、队列、栈、数组、映射等。
Java集合主要可以划分为4个部分:List列表、Set集合、Map映射、工具类(Iterator迭代器、Enumeration枚举类、Arrays和Collections)、。
Java集合工具包框架图(如下):

 看上面的框架图,先抓住它的主干,即Collection和Map。

1. Collection是一个接口,是高度抽象出来的集合,它包含了集合的基本操作和属性。AbstractCollection抽象类,它实现了Collection中的绝大部分函数;

  Collection包含了List和Set两大分支。 AbstractList和AbstractSet都继承于AbstractCollection; 具体的List实现类继承于AbstractList,而Set的实现类则继承于AbstractSet。
  (01) List是一个有序的队列,每一个元素都有它的索引。第一个元素的索引值是0。
          List的实现类有LinkedList, ArrayList, Vector, Stack。

  (02) Set是一个不允许有重复元素的集合。
          Set的实现类有HastSet和TreeSet。HashSet依赖于HashMap,它实际上是通过HashMap实现的;TreeSet依赖于TreeMap,它实际上是通过TreeMap实现的。

2. Map是一个映射接口,即key-value键值对。

   AbstractMap是个抽象类,它实现了Map接口中的大部分API。HashMap,TreeMap,WeakHashMap都是继承于AbstractMap。
   Hashtable虽然继承于Dictionary,但它实现了Map接口。

接下来,再看Iterator。它是遍历集合的工具,即我们通常通过Iterator迭代器来遍历集合。我们说Collection依赖于Iterator,是因为Collection的实现类都要实现iterator()函数,返回一个Iterator对象。
ListIterator是专门为遍历List而存在的。

再看Enumeration,它是JDK 1.0引入的抽象类。作用和Iterator一样,也是遍历集合;但是Enumeration的功能要比Iterator少。在上面的框图中,Enumeration只能在Hashtable, Vector, Stack中使用。

最后,看Arrays和Collections。它们是操作数组、集合的两个工具类。

 

二、Collection 框架概述

public interface Iterable<T> {
    Iterator<T> iterator();
    // 其它省略。。。
}

1. Collection 

public interface Collection<E> extends Iterable<E> {...}

Collection  是一个接口,是高度抽象出来的集合,它包含了集合的基本操作:添加、删除、清空、遍历(读取)、是否为空、获取大小、是否保护某元素等等;

Collection 接口的所有子类(直接子类和间接子类)都必须实现2种构造函数:不带参数的构造函数 和 参数为Collection的构造函数。带参数的构造函数,可以用来转换Collection的类型;

public interface Collection<E> extends Iterable<E> { 
    int size(); 
    boolean isEmpty(); 
    boolean contains(Object o); 
    Iterator<E> iterator(); 
    Object[] toArray();

    /** 
     *     String[] y = x.toArray(new String[0]);
     */
    <T> T[] toArray(T[] a); 
    boolean add(E e); 
    boolean remove(Object o); 
    boolean containsAll(Collection<?> c); 
    boolean addAll(Collection<? extends E> c); 
    boolean removeAll(Collection<?> c); 
    boolean retainAll(Collection<?> c); 

    void clear(); 
    boolean equals(Object o); 
    int hashCode(); 
}

2. List

public interface List<E> extends Collection<E> {...}

List 是一个继承于Collection的接口,即List是集合中的一种。List是有序的队列,List中的每一个元素都有一个索引;第一个元素的索引值是0,往后的元素的索引值依次+1。和Set不同,List中允许有重复的元素。

关于API方面。既然List是继承于Collection接口,它自然就包含了Collection中的全部函数接口;由于List是有序队列,它也额外的有自己的API接口。主要有“添加、删除、获取、修改指定位置的元素”、“获取List中的子队列”等。

// Collection的API
boolean         add(E object)
boolean         addAll(Collection<? extends E> collection)
void            clear()
boolean         contains(Object object)
boolean         containsAll(Collection<?> collection)
boolean         equals(Object object)
int             hashCode()
boolean         isEmpty()
Iterator<E>     iterator()
boolean         remove(Object object)
boolean         removeAll(Collection<?> collection)
boolean         retainAll(Collection<?> collection)
int             size()
<T> T[]         toArray(T[] array)
Object[]        toArray()
// 相比与Collection,List新增的API:
void                add(int location, E object)
boolean             addAll(int location, Collection<? extends E> collection)
E                   get(int location)
int                 indexOf(Object object)
int                 lastIndexOf(Object object)
ListIterator<E>     listIterator(int location)
ListIterator<E>     listIterator()
E                   remove(int location)
E                   set(int location, E object)
List<E>             subList(int start, int end)

3. Set

public interface Set<E> extends Collection<E> {...}

Set 是一个继承于Collection的接口,即Set也是集合中的一种。Set是没有重复元素的集合。
关于API方面。Set的API和Collection完全一样。

// Set的API
boolean         add(E object)
boolean         addAll(Collection<? extends E> collection)
void             clear()
boolean         contains(Object object)
boolean         containsAll(Collection<?> collection)
boolean         equals(Object object)
int             hashCode()
boolean         isEmpty()
Iterator<E>     iterator()
boolean         remove(Object object)
boolean         removeAll(Collection<?> collection)
boolean         retainAll(Collection<?> collection)
int             size()
<T> T[]         toArray(T[] array)
Object[]         toArray()

4. AbstractCollection

public abstract class AbstractCollection<E> implements Collection<E> {...}

AbstractCollection 是一个抽象类,它实现了Collection中除iterator()和size()之外的函数。从而方便其它类实现Collection,比如ArrayList、LinkedList等,它们这些类想要实现Collection接口,通过继承AbstractCollection就已经实现了大部分的接口了。

5. AbstractList

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {...}

AbstractList 是一个继承于AbstractCollection,并且实现List接口的抽象类。它实现了List中除size()、get(int location)之外的函数, 从而方便其它类继承List。 另外,和AbstractCollection相比,AbstractList抽象类中,实现了iterator()接口。

6. AbstractSet

public abstract class AbstractSet<E> extends AbstractCollection<E> implements Set<E> {...}

AbstractSet 是一个继承于AbstractCollection,并且实现Set接口的抽象类。由于Set接口和Collection接口中的API完全一样,Set也就没有自己单独的API。和AbstractCollection一样,它实现了List中除iterator()和size()之外的函数。 作用:它实现了Set接口中的大部分函数。从而方便其它类实现Set接口。

7. Iterator

public interface Iterator<E> {...}

Iterator 是一个接口,它是集合的迭代器。集合可以通过Iterator去遍历集合中的元素。Iterator提供的API接口,包括:是否存在下一个元素、获取下一个元素、删除当前元素。
注意:Iterator遍历Collection时,是 fail-fast 机制的。即,当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。

// Iterator的API
boolean hasNext()
E next()
void remove()

8. ListIterator

public interface ListIterator<E> extends Iterator<E> {...}

ListIterator 是一个继承于Iterator的接口,它是队列迭代器。专门用于便利List,能提供向前/向后遍历。相比于Iterator,它新增了添加、是否存在上一个元素、获取上一个元素等等API接口。

// ListIterator的API
// 继承于Iterator的接口
boolean hasNext()
E next()
void remove()
// 新增API接口
void add(E object)
boolean hasPrevious()
int nextIndex()
E previous()
int previousIndex()
void set(E object)

三、ArrayList  (线程不安全)

ArrayList 继承了AbstractList,实现了List,是一个数组队列,相当于 动态数组。与Java中的数组相比,它的容量能动态增长。它实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口。
RandmoAccess是java中用来被List实现,为List提供快速随机访问功能的。ArrayList 实现了RandmoAccess接口,在ArrayList中,可以通过元素的序号快速获取元素对象,这就是快速随机访问。

和Vector不同,ArrayList中的操作不是线程安全的建议在单线程中才使用ArrayList,而在多线程中可以选择 Vector 或者 CopyOnWriteArrayList。

// 默认构造函数
ArrayList()

// capacity是ArrayList的默认容量大小。当由于增加数据导致容量不足时,容量会添加上一次容量大小的一半。
ArrayList(int capacity)

// 创建一个包含collection的ArrayList
ArrayList(Collection<? extends E> collection)

 ArrayList包含了两个重要的对象:elementData 和 size。
  (01) ArrayList 通过Object[]类型的动态数组elementData[]去保存数据的。默认容量大小是10。
  (02) 当ArrayList容量不足以容纳全部元素时,ArrayList会扩容:新的容量=“(原始容量x3)/2 + 1”。
  (03) ArrayList的克隆函数,即是将全部元素克隆到一个数组中。
  (04) ArrayList实现Serializable的方式:当写入到输出流时,先写入“容量,再依次写入“每一个元素”;
当读出输入流时,先读取“容量”,再依次读取“每一个元素”。 size 则是动态数组的实际大小。

ArrayList支持3种遍历方式:
  1. 迭代器遍历 (效率最低)

Integer value = null;
Iterator iter = list.iterator();
while (iter.hasNext()) {
    value = (Integer)iter.next();
}

  2. 随机访问,通过索引值去遍历。(效率最高)(由于ArrayList实现了RandomAccess接口,它支持通过索引值去随机访问元素)

Integer value = null;
int size = list.size();
for (int i=0; i<size; i++) {
    value = (Integer)list.get(i);        
}

  3. for 循环遍历

Integer value = null;
for (Integer integ:list) {
    value = integ;
}

 toArray() 异常
  当我们调用ArrayList中的 toArray(),可能遇到过抛出“java.lang.ClassCastException”异常的情况。ArrayList提供了2toArray()函数:

Object[] toArray()
<T> T[] toArray(T[] contents)

调用 toArray() 函数会抛出“java.lang.ClassCastException”异常,但是调用 toArray(T[] contents) 能正常返回 T[]。
toArray() 返回的是 Object[] 数组,将 Object[] 转换为其它类型(如,将Object[]转换为Integer[])则会抛出“java.lang.ClassCastException”异常,Java向下转型可能会发生异常。
  解决该问题的办法是调用 <T> T[] toArray(T[] contents) , 而不是 Object[] toArray()。

 // toArray(T[] contents)调用方式二。最常用!
    public static Integer[] vectorToArray2(ArrayList<Integer> v) {
        Integer[] newText = (Integer[])v.toArray(new Integer[0]);
        return newText;
    }

    fail-fast 机制

fail-fast 机制是java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。
例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;
那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。

fail-fast机制,是一种错误检测机制。它只能被用来检测错误,因为JDK并不保证fail-fast机制一定会发生。
若在多线程环境下使用fail-fast机制的集合,建议使用“java.util.concurrent包下的类”去取代“java.util包下的类”。 

fail-fast机制
   在调用 next() 和 remove()时,都会执行 checkForComodification()。若 “modCount 不等于 expectedModCount”,则抛出ConcurrentModificationException异常,产生fail-fast事件。
   从Itr类中,我们知道 expectedModCount 在创建Itr对象时,被赋值为 modCount。通过Itr,我们知道:expectedModCount不可能被修改为不等于 modCount。所以,需要考证的就是modCount何时会被修改。无论是add()、remove(),还是clear(),只要涉及到修改集合中的元素数时,都会改变modCount的值。  我们再系统的梳理一下fail-fast是怎么产生的。步骤如下:
    (01) 新建了一个ArrayList,名称为arrayList。
    (02) 向arrayList中添加内容。
    (03) 新建一个“线程a”,并在“线程a”中通过Iterator反复的读取arrayList的值。
    (04) 新建一个“线程b”,在“线程b”中删除arrayList中的一个“节点A”。
    (05) 这时,就会产生有趣的事件了。
  在某一时刻,“线程a”创建了arrayList的Iterator。此时“节点A”仍然存在于arrayList中,创建arrayList时,expectedModCount = modCount(假设它们此时的值为N)。在“线程a”在遍历arrayList过程中的某一时刻,“线程b”执行了,并且“线程b”删除了arrayList中的“节点A”。“线程b”执行remove()进行删除操作时,在remove()中执行了“modCount++”,此时modCount变成了N+1!“线程a” 接着遍历,当它执行到next()函数时,调用checkForComodification()比较“expectedModCount”和“modCount”的大小;而“expectedModCount=N”,“modCount=N+1”,这样,便抛出ConcurrentModificationException异常,产生fail-fast事件。  即: 当多个线程对同一个集合进行操作的时候,某线程访问集合的过程中,该集合的内容被其他线程所改变(即其它线程通过add、remove、clear等方法,改变了modCount的值);这时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。

java.util.concurrent包中是如何解决fail-fast事件的 

   (01) 和ArrayList继承于AbstractList不同,CopyOnWriteArrayList 没有继承于AbstractList,它仅仅只是实现了List接口。
   (02) ArrayList的iterator()函数返回的Iterator是在AbstractList中实现的;而CopyOnWriteArrayList是自己实现Iterator。
   (03) ArrayList的Iterator实现类中调用next()时,会“调用checkForComodification()比较‘expectedModCount’和‘modCount’的大小”;
但是,CopyOnWriteArrayList的Iterator实现类中,没有所谓的checkForComodification(),更不会抛出ConcurrentModificationException异常!

四、LinkedList  (线程不安全)

java.lang.Object
   ↳     java.util.AbstractCollection<E>
         ↳     java.util.AbstractList<E>
               ↳     java.util.AbstractSequentialList<E>
                     ↳     java.util.LinkedList<E>

public class LinkedList<E> extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable {...}

LinkedList 是继承于AbstractSequentialList的双向链表,也可以被当作堆栈、队列或双端队列进行操作。
LinkedList 实现 List 接口,能对它进行队列操作。
LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。 

// 默认构造函数
LinkedList()

// 创建一个LinkedList,保护Collection中的全部元素。
LinkedList(Collection<? extends E> collection)

 LinkedList是AbstractSequentialList的子类。
AbstractSequentialList 实现了get(int index)、set(int index, E element)、add(int index, E element) 和
remove(int index)这些函数。这些接口都是随机访问List的;

   LinkedList的本质是双向链表。
    (01) LinkedList继承于AbstractSequentialList,并且实现了Deque接口。
    (02) LinkedList包含两个重要的成员:header 和 size。
      header是双向链表的表头,它是双向链表节点所对应的类Entry的实例。
Entry中包含成员变量: previous, next, element。 size是双向链表中节点的个数。

LinkedList实际上是通过双向链表去实现的,它的 顺序访问非常高效,而随机访问效率比较低
LinkedList也实现了List接口{即它实现了get(int location)、remove(int location)等“根据索引值来获取、删除节点的函数”}。LinkedList如何实现List的这些接口的,如何将“双向链表和索引值联系起来的"?
    实际原理非常简单,它就是通过一个计数索引值来实现的。例如,当我们调用get(int location)时,首先会比较“location”和“双向链表长度的1/2”;若前者大,则从链表头开始往后查找,直到location位置;否则,从链表末尾开始先前查找,直到location位置。

总结:
(01) LinkedList 实际上是通过双向链表去实现的。
        它包含一个非常重要的内部类:Entry。Entry是双向链表节点所对应的数据结构,它包括的属性有:previous, next, element
(02) 从LinkedList的实现方式中可以发现,它不存在LinkedList容量不足的问题。
(03) LinkedList的克隆函数,即是将全部元素克隆到一个新的LinkedList对象中。
(04) LinkedList实现java.io.Serializable。当写入到输出流时,先写入“容量”,再依次写入“每一个节点保护的值”;当读出输入流时,先读取“容量”,再依次读取“每一个元素”。
(05) 由于LinkedList实现了Deque,而Deque接口定义了在双端队列两端访问元素的方法。提供插入、移除和检查元素的方法。每种方法都存在两种形式:一种形式在操作失败时抛出异常,另一种形式返回一个特殊值(null 或 false,具体取决于操作)。  

    第一个元素(头部)                 最后一个元素(尾部)
        抛出异常        特殊值            抛出异常        特殊值
插入    addFirst(e)    offerFirst(e)    addLast(e)        offerLast(e)
移除    removeFirst()  pollFirst()      removeLast()    pollLast()
检查    getFirst()     peekFirst()      getLast()        peekLast()

// 返回第一个节点
// 若LinkedList的大小为0,则返回null
public E peekFirst() {
    if (size==0)
        return null;
    return getFirst();
}

// 获取LinkedList的第一个元素
public E getFirst() {
    if (size==0)
        throw new NoSuchElementException();

     // 链表的表头header中不包含数据。
     // 这里返回header所指下一个节点所包含的数据。
     return header.next.element;
}

(06) LinkedList可以作为FIFO(先进先出)的队列,作为FIFO的队列时,下表的方法等价:

队列方法       等效方法
add(e)        addLast(e)
offer(e)      offerLast(e)
remove()      removeFirst()
poll()        pollFirst()
element()     getFirst()
peek()        peekFirst()

(07) LinkedList可以作为LIFO(后进先出)的栈,作为LIFO的栈时,下表的方法等价:

栈方法        等效方法
push(e)      addFirst(e)
pop()        removeFirst()
peek()       peekFirst()

LinkedList 遍历方式
LinkedList支持多种遍历方式(1.通过Iterator去遍历 2. 快速随机访问遍历 3. foreach循环 4.其它 pollFirst() 之类)。千万不要采用随机访问的方式去遍历LinkedList,而采用逐个遍历的方式。

# 遍历 ArrayList (实现了RandomAccess接口):
for (int i=0; i<list.size(); i++) {
     list.get(i);
}
# 遍历 LinkedList :
for (Integer integ:list)
            ;

五、 Vector (线程安全)

Vector 是矢量队列,继承于AbstractList,实现了List, RandomAccess, Cloneable这些接口。Vector 继承了AbstractList,实现了List;是一个队列,支持相关的添加、删除、修改、遍历等功能。Vector 实现了RandmoAccess接口,即提供了随机访问功能。

和ArrayList不同,Vector中的操作是线程安全的。 方法使用  synchronized 关键字修饰;

Vector的数据结构和ArrayList差不多,有3个成员变量:elementData , elementCount, capacityIncrement。

   (01) elementData 是"Object[]型的数组",可动态扩展,默认为10,它保存了添加到Vector中的元素。
   (02) elementCount 是动态数组的实际大小。
   (03) capacityIncrement 是动态数组的增长系数。
  int newCapacity = (capacityIncrement > 0) ? (oldCapacity + capacityIncrement) : (oldCapacity * 2);
   (04) Vector的克隆函数,即是将全部元素克隆到一个数组中。

# Vector 遍历方式 (推荐:序号随机访问)
    Integer value = null;
    int size = vec.size();
    for (int i=0; i<size; i++) {
       value = (Integer)vec.get(i);        
    }

Vector ArrayList 的比较
1. Vector是多线程安全的,而ArrayList不是,Vector类中的方法很多有synchronized进行修饰,导致了Vector效率无法与ArrayList相比;
2. 两个都是采用的线性连续空间(数组)存储元素,但是当空间不足的时候,两个类的增加方式是不同的。
     ArrayList会扩容:新的容量=“(原始容量x3)/2 + 1”。
     Vector扩容: int newCapacity = (capacityIncrement > 0) ? (oldCapacity + capacityIncrement) : (oldCapacity * 2);
3. Vector可以设置增长因子(Vector中的 capacityIncrement),而ArrayList不可以;

六、Stack

public class Stack<E> extends Vector<E> {...}

java工具包中的Stack是继承于Vector(矢量队列)的,由于Vector是通过数组实现的,因此,Stack也是通过数组实现的,而非链表。当然,我们也可以将 LinkedList 当作栈来使用!

// Stack 的 API 
boolean       empty()
synchronized E             peek()
synchronized E             pop()
             E             push(E object)
synchronized int           search(Object o)

   (01) Stack实际上也是通过数组去实现的。
          执行push时(即,将元素推入栈中),是通过将元素追加的数组的末尾中。
          执行peek时(即,取出栈顶元素,不执行删除),是返回数组末尾的元素。
          执行pop时(即,取出栈顶元素,并将该元素从栈中删除),是取出数组末尾的元素,然后将该元素从数组中删除。
   (02) Stack继承于Vector,意味着Vector拥有的属性和功能,Stack都拥有。

List总结(LinkedList, ArrayList等使用场景和性能分析):

26195410_jchQ.jpg

(01) List 是一个接口,它继承于Collection的接口。它代表着有序的队列。
(02) AbstractList 是一个抽象类,它继承于AbstractCollection。AbstractList实现List接口中除size()、get(int location)之外的函数。
(03) AbstractSequentialList 是一个抽象类,它继承于AbstractList。AbstractSequentialList 实现了“链表中,根据index索引值操作链表的全部函数”。

(04) ArrayList, LinkedList, Vector, Stack是List的4个实现类。
  ArrayList 是一个数组队列,相当于动态数组,随机访问效率高,随机插入、随机删除效率低。
  LinkedList 是一个双向链表。它也可以被当作堆栈、队列或双端队列进行操作。LinkedList随机访问效率低,但随机插入、随机删除效率低。
  Vector 是矢量队列,和ArrayList一样,它也是一个动态数组。但是ArrayList是非线程安全的,而Vector是线程安全的。
  Stack 是栈,它继承于Vector。它的特性是:先进后出(FILO, First In Last Out)。

涉及到“栈”、“队列”、“链表”等操作,应该考虑用List
  (01) 对于需要快速插入,删除元素,应该使用LinkedList。
  (02) 对于需要快速随机访问元素,应该使用ArrayList。
  (03) 对于“单线程环境” 或者 “多线程环境,但List仅仅只会被单个线程操作”,此时应该使用非同步的类(如ArrayList)。
       对于“多线程环境,且List可能同时被多个线程操作”,此时,应该使用同步的类(如Vector)。

 

 

 

 

 

 

转载于:https://my.oschina.net/zfscofield/blog/1594577

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值