JDK中集合包Collection和List的源码解读配合大神的一起看,秒懂。

大神总结的目录:http://www.cnblogs.com/skywang12345/p/3323085.html(转载),仅供个人学习,如有抄袭请包容(我也忘了cry....)

一、    总体架构

1)  简介

Java集合是java提供的工具包,包含了常用的数据结构:集合、链表、队列、栈、数组、映射等。Java集合工具包位置是java.util.*

Java集合主要可以划分为4个部分:List列表、Set集合、Map映射、工具类(Iterator迭代器、Enumeration枚举类、Arrays和Collections)、。

2)  架构图

3)  说明

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

1 Collection是一个接口,是高度抽象出来的集合,它包含了集合的基本操作和属性。

  Collection包含了List和Set两大分支。

  (01) List是一个有序的队列,每一个元素都有它的索引。第一个元素的索引值是0。

          List的实现类有LinkedList, ArrayList, Vector, Stack。

  (02) Set是一个不允许有重复元素的集合。

          Set的实现类有HastSet和TreeSet。HashSet依赖于HashMap,它实际上是通过HashMap实现的;TreeSet依赖于TreeMap,它实际上是通过TreeMap实现的。

2 Map是一个映射接口,即key-value键值对。Map中的每一个元素包含“一个key”和“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架构

1)  概要

Collection是一个接口,它主要的两个分支是:List 和Set。

List和Set都是接口,它们继承于Collection。List是有序的队列,List中可以有重复的元素;而Set是数学概念中的集合,Set中没有重复元素!

List和Set都有它们各自的实现类。

  为了方便,我们抽象出了AbstractCollection抽象类,它实现了Collection中的绝大部分函数;这样,在Collection的实现类中,我们就可以通过继承AbstractCollection省去重复编码。AbstractList和AbstractSet都继承于AbstractCollection,具体的List实现类继承于AbstractList,而Set的实现类则继承于AbstractSet。

  另外,Collection中有一个iterator()函数,它的作用是返回一个Iterator接口。通常,我们通过Iterator迭代器来遍历集合。ListIterator是List接口所特有的,在List接口中,通过ListIterator()返回一个ListIterator对象。

  接下来,我们看看各个接口和抽象类的介绍;然后,再对实现类进行详细的了解。

本章内容包括:

1 Collection简介

2 List简介

3 Set简介

4AbstractCollection

5 AbstractList

6 AbstractSet

7 Iterator

8 ListIterator

 

2)  Collection简介

Collection的定义如下:

public interfaceCollection<E> extends Iterable<E> {}

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

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

// Collection的API

abstractboolean         add(E object)

abstractboolean         addAll(Collection<?extends E> collection)

abstract void            clear()

abstractboolean         contains(Object object)

abstractboolean        containsAll(Collection<?> collection)

abstractboolean         equals(Object object)

abstract int             hashCode()

abstractboolean         isEmpty()

abstractIterator<E>     iterator()

abstractboolean         remove(Object object)

abstractboolean        removeAll(Collection<?> collection)

abstractboolean        retainAll(Collection<?> collection)

abstract int             size()

abstract <T>T[]         toArray(T[] array)

abstractObject[]        toArray()

3)  List简介

List的定义如下:

public interfaceList<E> extends Collection<E> {}

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

List的官方介绍如下:

A List is acollection which maintains an ordering for its elements. Every element in theList has an index. Each element can thus be accessed by its index, with thefirst index being zero. Normally, Lists allow duplicate elements, as comparedto Sets, where elements have to be unique.

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

// List的API

abstractboolean         add(E object)

abstractboolean         addAll(Collection<?extends E> collection)

abstract void            clear()

abstractboolean         contains(Object object)

abstractboolean        containsAll(Collection<?> collection)

abstractboolean         equals(Object object)

abstract int             hashCode()

abstractboolean         isEmpty()

abstractIterator<E>     iterator()

abstractboolean         remove(Object object)

abstractboolean        removeAll(Collection<?> collection)

abstractboolean        retainAll(Collection<?> collection)

abstract int             size()

abstract <T>T[]         toArray(T[] array)

abstractObject[]        toArray()

// 相比与Collection,List新增的API:

abstract void                add(int location, E object)

abstractboolean             addAll(int location,Collection<? extends E> collection)

abstract E                   get(int location)

abstract int                 indexOf(Object object)

abstract int                 lastIndexOf(Object object)

abstractListIterator<E>    listIterator(int location)

abstractListIterator<E>     listIterator()

abstract E                   remove(int location)

abstract E                   set(int location, E object)

abstractList<E>             subList(intstart, int end)

4)  Set简介

Set的定义如下:

public interfaceSet<E> extends Collection<E> {}

Set是一个继承于Collection的接口,即Set也是集合中的一种。Set是没有重复元素的集合。

关于API方面。Set的API和Collection完全一样。

// Set的API

abstractboolean         add(E object)

abstractboolean         addAll(Collection<?extends E> collection)

abstract void             clear()

abstractboolean         contains(Object object)

abstractboolean        containsAll(Collection<?> collection)

abstractboolean         equals(Object object)

abstract int             hashCode()

abstractboolean         isEmpty()

abstractIterator<E>     iterator()

abstract boolean         remove(Object object)

abstractboolean        removeAll(Collection<?> collection)

abstractboolean        retainAll(Collection<?> collection)

abstract int             size()

abstract <T>T[]         toArray(T[] array)

abstractObject[]         toArray()

5)  AbstractCollection简介

AbstractCollection的定义如下:

public abstractclass AbstractCollection<E> implements Collection<E> {}

AbstractCollection是一个抽象类,它实现了Collection中除iterator()和size()之外的函数。

AbstractCollection的主要作用:它实现了Collection接口中的大部分函数。从而方便其它类实现Collection,比如ArrayList、LinkedList等,它们这些类想要实现Collection接口,通过继承AbstractCollection就已经实现了大部分的接口了。

6)  AbstractList简介

AbstractList的定义如下:

public abstractclass AbstractList<E> extends AbstractCollection<E> implementsList<E> {}

AbstractList是一个继承于AbstractCollection,并且实现List接口的抽象类。它实现了List中除size()、get(int location)之外的函数。

AbstractList的主要作用:它实现了List接口中的大部分函数。从而方便其它类继承List。

另外,和AbstractCollection相比,AbstractList抽象类中,实现了iterator()接口。

7)  AbstractSet简介

AbstractSet的定义如下:

public abstractclass AbstractSet<E> extends AbstractCollection<E> implementsSet<E> {}

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

AbstractSet的主要作用:它实现了Set接口中的大部分函数。从而方便其它类实现Set接口。

8)  Iterator简介

Iterator的定义如下:

public interfaceIterator<E> {}

Iterator是一个接口,它是集合的迭代器。集合可以通过Iterator去遍历集合中的元素。Iterator提供的API接口,包括:是否存在下一个元素、获取下一个元素、删除当前元素。

注意:Iterator遍历Collection时,是fail-fast机制的。即,当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。关于fail-fast的详细内容,我们会在后面专门进行说明。

// Iterator的API

abstract booleanhasNext()

abstract E next()

abstract void remove()

9)  ListIterator简介

ListIterator的定义如下:

public interfaceListIterator<E> extends Iterator<E> {}

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

// ListIterator的API

// 继承于Iterator的接口

abstract booleanhasNext()

abstract E next()

abstract voidremove()

// 新增API接口

abstract voidadd(E object)

abstract booleanhasPrevious()

abstract intnextIndex()

abstract Eprevious()

abstract intpreviousIndex()

abstract voidset(E object)

三、    ArrayList类

1)  ArrayList概要

上一章,我们学习了Collection的架构。这一章开始,我们对Collection的具体实现类进行讲解;首先,讲解List,而List中ArrayList又最为常用。因此,本章我们讲解ArrayList。先对ArrayList有个整体认识,再学习它的源码,最后再通过例子来学习如何使用它。内容包括:

第1部分 ArrayList简介

第2部分 ArrayList数据结构

第3部分 ArrayList源码解析(基于JDK1.6.0_45)

第4部分 ArrayList遍历方式

第5部分 toArray()异常

第6部分 ArrayList示例

2)  ArrayList简介

ArrayList 是一个数组队列,相当于 动态数组。与Java中的数组相比,它的容量能动态增长。它继承于AbstractList,实现了List, RandomAccess,Cloneable, java.io.Serializable这些接口。

ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。

ArrayList 实现了RandmoAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。稍后,我们会比较List的“快速随机访问”和“通过Iterator迭代器访问”的效率。

ArrayList 实现了Cloneable接口,即覆盖了函数clone(),能被克隆。

ArrayList 实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。

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

ArrayList构造函数

// 默认构造函数

ArrayList()

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

ArrayList(intcapacity)

// 创建一个包含collection的ArrayList

ArrayList(Collection<?extends E> collection)

ArrayList的API

// Collection中定义的API

boolean             add(E object)

boolean             addAll(Collection<? extendsE> 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()

//AbstractCollection中定义的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)

// ArrayList新增的API

Object               clone()

void                 ensureCapacity(int minimumCapacity)

void                 trimToSize()

void                 removeRange(int fromIndex, inttoIndex)

3)  ArrayList数据结构

ArrayList的继承关系

java.lang.Object

        java.util.AbstractCollection<E>

              java.util.AbstractList<E>

                   java.util.ArrayList<E>

public classArrayList<E> extends AbstractList<E>

        implements List<E>, RandomAccess,Cloneable, java.io.Serializable {}

ArrayList包含了两个重要的对象:elementData 和 size。

(01) elementData 是"Object[]类型的数组",它保存了添加到ArrayList中的元素。实际上,elementData是个动态数组,我们能通过构造函数 ArrayList(int initialCapacity)来执行它的初始容量为initialCapacity;如果通过不含参数的构造函数ArrayList()来创建ArrayList,则elementData的容量默认是10。elementData数组的大小会根据ArrayList容量的增长而动态的增长,具体的增长方式,请参考源码分析中的ensureCapacity()函数。

(02) size 则是动态数组的实际大小。

4)  ArrayList源码解析

为了更了解ArrayList的原理,对ArrayList源码代码作出分析。ArrayList是通过数组实现的,源码比较容易理解。

总结:

(01) ArrayList 实际上是通过一个数组去保存数据的。当我们构造ArrayList时;若使用默认构造函数,则ArrayList的默认容量大小是10。

(02) 当ArrayList容量不足以容纳全部元素时,ArrayList会重新设置容量:新的容量=“(原始容量x3)/2 + 1”。

(03) ArrayList的克隆函数,即是将全部元素克隆到一个数组中。

(04) ArrayList实现java.io.Serializable的方式。当写入到输出流时,先写入“容量”,再依次写入“每一个元素”;当读出输入流时,先读取“容量”,再依次读取“每一个元素”。

5)  ArrayList遍历方式

ArrayList支持3种遍历方式

(01) 第一种,通过迭代器遍历。即通过Iterator去遍历。

Integer value =null;

Iterator iter =list.iterator();

while(iter.hasNext()) {

    value = (Integer)iter.next();

}

(02) 第二种,随机访问,通过索引值去遍历。

由于ArrayList实现了RandomAccess接口,它支持通过索引值去随机访问元素。

Integer value =null;

int size =list.size();

for (int i=0;i<size; i++) {

    value = (Integer)list.get(i);       

}

(03) 第三种,for循环遍历。如下:

Integer value =null;

for (Integerinteg:list) {

    value = integ;

}

遍历ArrayList时,使用随机访问(即,通过索引序号访问)效率最高,而使用迭代器的效率最低!

System.arraycopy(elementData,0, a, 0, size); // 复制功能

6)  toArray()异常

当我们调用ArrayList中的toArray(),可能遇到过抛出“java.lang.ClassCastException”异常的情况。下面我们说说这是怎么回事。

ArrayList提供了2个toArray()函数:

Object[] toArray()

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

调用 toArray() 函数会抛出“java.lang.ClassCastException”异常,但是调用toArray(T[] contents) 能正常返回 T[]。

toArray() 会抛出异常是因为 toArray() 返回的是Object[] 数组,将 Object[] 转换为其它类型(如如,将Object[]转换为的Integer[])则会抛出“java.lang.ClassCastException”异常,因为Java不支持向下转型。具体的可以参考前面ArrayList.java的源码介绍部分的toArray()。

解决该问题的办法是调用 <T>T[] toArray(T[] contents) , 而不是 Object[] toArray()。

调用 toArray(T[]contents) 返回T[]的可以通过以下几种方式实现。

// toArray(T[]contents)调用方式一

public staticInteger[] vectorToArray1(ArrayList<Integer> v) {

    Integer[] newText = new Integer[v.size()];

    v.toArray(newText);

    return newText;

}

// toArray(T[]contents)调用方式二。最常用!

public static Integer[]vectorToArray2(ArrayList<Integer> v) {

    Integer[] newText =(Integer[])v.toArray(new Integer[0]);

    return newText;

}

// toArray(T[]contents)调用方式三

public staticInteger[] vectorToArray3(ArrayList<Integer> v) {

    Integer[] newText = new Integer[v.size()];

    Integer[] newStrings =(Integer[])v.toArray(newText);

    return newStrings;

}

7)  ArrayList示例

// 创建ArrayList

ArrayList list =new ArrayList();

// 将“”

list.add("1");

list.add("2");

list.add("3");

list.add("4");

// 将下面的元素添加到第1个位置

list.add(0, "5");

// 获取第1个元素

System.out.println("thefirst element is: "+ list.get(0));

// 删除“3”

list.remove("3");

// 获取ArrayList的大小

System.out.println("Arraylistsize=: "+ list.size());

// 判断list中是否包含"3"

System.out.println("ArrayListcontains 3 is: "+ list.contains(3));

// 设置第2个元素为10

list.set(1,"10");

// 通过Iterator遍历ArrayList

for(Iterator iter= list.iterator(); iter.hasNext(); ) {

    System.out.println("next is: "+iter.next());

}

// 将ArrayList转换为数组

String[] arr =(String[])list.toArray(new String[0]);

for (Stringstr:arr)

    System.out.println("str: "+ str);

// 清空ArrayList

list.clear();

// 判断ArrayList是否为空

System.out.println("ArrayListis empty: "+ list.isEmpty());

四、  fail-fast总结(一种遍历机制)

1)  概要

我们已经学习了ArrayList。接下来,我们以ArrayList为例,对Iterator的fail-fast机制进行了解。内容包括::

1 fail-fast简介

2 fail-fast示例

3 fail-fast解决办法

4 fail-fast原理

5 解决fail-fast的原理

2)  fail-fast简介

fail-fast 机制是java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。

例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。

在详细介绍fail-fast机制的原理之前,先通过一个示例来认识fail-fast。

3)  fail-fast示例

示例代码:(FastFailTest.java)

importjava.util.*;

importjava.util.concurrent.*;

/*

 * @desc java集合中Fast-Fail的测试程序。

 *

 *  fast-fail事件产生的条件:当多个线程对Collection进行操作时,若其中某一个线程通过iterator去遍历集合时,该集合的内容被其他线程所改变;则会抛出ConcurrentModificationException异常。

 *  fast-fail解决办法:通过util.concurrent集合包下的相应类去处理,则不会产生fast-fail事件。

 *

 *   本例中,分别测试ArrayList和CopyOnWriteArrayList这两种情况。ArrayList会产生fast-fail事件,而CopyOnWriteArrayList不会产生fast-fail事件。

 *   (01)使用ArrayList时,会产生fast-fail事件,抛出ConcurrentModificationException异常;定义如下:

 *           private static List<String> list = new ArrayList<String>();

 *   (02)使用时CopyOnWriteArrayList,不会产生fast-fail事件;定义如下:

 *           private static List<String> list = newCopyOnWriteArrayList<String>();

 *

 * @author skywang

 */

public classFastFailTest {

    private static List<String> list =new ArrayList<String>();

    //private static List<String> list =new CopyOnWriteArrayList<String>();

    public static void main(String[] args) {

        // 同时启动两个线程对list进行操作!

        new ThreadOne().start();

        new ThreadTwo().start();

    }

    private static void printAll() {

        System.out.println("");

        String value = null;

        Iterator iter = list.iterator();

        while(iter.hasNext()) {

            value = (String)iter.next();

            System.out.print(value+",");

        }

    }

    /**

     * 向list中依次添加0,1,2,3,4,5,每添加一个数之后,就通过printAll()遍历整个list

     */

    private static class ThreadOne extendsThread {

        public void run() {

            int i = 0;

            while (i<6) {

                list.add(String.valueOf(i));

                printAll();

                i++;

            }

        }

    }

    /**

     * 向list中依次添加10,11,12,13,14,15,每添加一个数之后,就通过printAll()遍历整个list

     */

    private static class ThreadTwo extendsThread {

        public void run() {

            int i = 10;

            while (i<16) {

                list.add(String.valueOf(i));

                printAll();

                i++;

            }

        }

    }

}

运行结果:

运行该代码,抛出异常java.util.ConcurrentModificationException!即,产生fail-fast事件!

结果说明:

(01) FastFailTest中通过 new ThreadOne().start() 和 new ThreadTwo().start() 同时启动两个线程去操作list。

    ThreadOne线程:向list中依次添加0,1,2,3,4,5。每添加一个数之后,就通过printAll()遍历整个list。

    ThreadTwo线程:向list中依次添加10,11,12,13,14,15。每添加一个数之后,就通过printAll()遍历整个list。

(02) 当某一个线程遍历list的过程中,list的内容被另外一个线程所改变了;就会抛出ConcurrentModificationException异常,产生fail-fast事件。

4)  fail-fast解决办法

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

所以,本例中只需要将ArrayList替换成java.util.concurrent包下对应的类即可。

即,将代码

private staticList<String> list = new ArrayList<String>();

替换为

private staticList<String> list = new CopyOnWriteArrayList<String>();

则可以解决该办法。

5)  fail-fast原理

产生fail-fast事件,是通过抛出ConcurrentModificationException异常来触发的。

那么,ArrayList是如何抛出ConcurrentModificationException异常的呢?

我们知道,ConcurrentModificationException是在操作Iterator时抛出的异常。我们先看看Iterator的源码。ArrayList的Iterator是在父类AbstractList.java中实现的。代码如下:

从中,我们可以发现在调用 next() 和 remove()时,都会执行 checkForComodification()。若“modCount 不等于 expectedModCount”,则抛出ConcurrentModificationException异常,产生fail-fast事件。

要搞明白 fail-fast机制,我们就要需要理解什么时候“modCount 不等于 expectedModCount”!

从Itr类中,我们知道 expectedModCount 在创建Itr对象时,被赋值为 modCount。通过Itr,我们知道:expectedModCount不可能被修改为不等于 modCount。所以,需要考证的就是modCount何时会被修改。

接下来,我们查看ArrayList的源码,来看看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事件。

至此,我们就完全了解了fail-fast是如何产生的!

即,当多个线程对同一个集合进行操作的时候,某线程访问集合的过程中,该集合的内容被其他线程所改变(即其它线程通过add、remove、clear等方法,改变了modCount的值);这时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。

6)  解决fail-fast的原理

上面,说明了“解决fail-fast机制的办法”,也知道了“fail-fast产生的根本原因”。接下来,我们再进一步谈谈java.util.concurrent包中是如何解决fail-fast事件的。

还是以和ArrayList对应的CopyOnWriteArrayList进行说明。我们先看看CopyOnWriteArrayList的源码:查看JDK

从中,我们可以看出:

(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类

1)  概要

我们已经学习了ArrayList,并了解了fail-fast机制。这一章我们接着学习List的实现类——LinkedList。

和学习ArrayList一样,接下来呢,我们先对LinkedList有个整体认识,然后再学习它的源码;最后再通过实例来学会使用LinkedList。内容包括:

第1部分 LinkedList介绍

第2部分 LinkedList数据结构

第3部分 LinkedList源码解析(基于JDK1.6.0_45)

第4部分 LinkedList遍历方式

第5部分 LinkedList示例

2)  LinkedList介绍

LinkedList简介

LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。

LinkedList 实现 List 接口,能对它进行队列操作。

LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。

LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。

LinkedList 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。

LinkedList 是非同步的。

// 默认构造函数

LinkedList()

// 创建一个LinkedList,保护Collection中的全部元素。

LinkedList(Collection<?extends E> collection)

LinkedList的API

boolean       add(E object)

void          add(int location, E object)

boolean       addAll(Collection<? extends E>collection)

boolean       addAll(int location, Collection<?extends E> collection)

void          addFirst(E object)

void          addLast(E object)

void          clear()

Object        clone()

boolean       contains(Object object)

Iterator<E>   descendingIterator()

E             element()

E             get(int location)

E             getFirst()

E             getLast()

int           indexOf(Object object)

int           lastIndexOf(Object object)

ListIterator<E>     listIterator(int location)

boolean       offer(E o)

boolean       offerFirst(E e)

boolean       offerLast(E e)

E             peek()

E             peekFirst()

E             peekLast()

E             poll()

E             pollFirst()

E             pollLast()

E             pop()

void          push(E e)

E             remove()

E             remove(int location)

boolean       remove(Object object)

E             removeFirst()

boolean       removeFirstOccurrence(Object o)

E             removeLast()

boolean       removeLastOccurrence(Object o)

E             set(int location, E object)

int           size()

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

Object[]     toArray()

AbstractSequentialList简介

在介绍LinkedList的源码之前,先介绍一下AbstractSequentialList。毕竟,LinkedList是AbstractSequentialList的子类。

AbstractSequentialList实现了get(int index)、set(int index, E element)、add(int index, Eelement) 和 remove(int index)这些函数。这些接口都是随机访问List的,LinkedList是双向链表;既然它继承于AbstractSequentialList,就相当于已经实现了“get(intindex)这些接口”。

此外,我们若需要通过AbstractSequentialList自己实现一个列表,只需要扩展此类,并提供 listIterator() 和 size() 方法的实现即可。若要实现不可修改的列表,则需要实现列表迭代器的 hasNext、next、hasPrevious、previous 和 index 方法即可。

3)  LinkedList数据结构

LinkedList的继承关系

java.lang.Object

        java.util.AbstractCollection<E>

              java.util.AbstractList<E>

                    java.util.AbstractSequentialList<E>

                         java.util.LinkedList<E>

public classLinkedList<E>

    extends AbstractSequentialList<E>

    implements List<E>, Deque<E>,Cloneable, java.io.Serializable {}

LinkedList的本质是双向链表。

(01) LinkedList继承于AbstractSequentialList,并且实现了Dequeue接口。

(02) LinkedList包含两个重要的成员:header 和 size。

  header是双向链表的表头,它是双向链表节点所对应的类Entry的实例。Entry中包含成员变量: previous, next, element。其中,previous是该节点的上一个节点,next是该节点的下一个节点,element是该节点所包含的值。

  size是双向链表中节点的个数。

4)  LinkedList源码解析

为了更了解LinkedList的原理,下面对LinkedList源码代码作出分析。

在阅读源码之前,我们先对LinkedList的整体实现进行大致说明:

    LinkedList实际上是通过双向链表去实现的。既然是双向链表,那么它的顺序访问会非常高效,而随机访问效率比较低。

    既然LinkedList是通过双向链表的,但是它也实现了List接口{也就是说,它实现了get(int location)、remove(int location)等“根据索引值来获取、删除节点的函数”}。LinkedList是如何实现List的这些接口的,如何将“双向链表和索引值联系起来的”?

    实际原理非常简单,它就是通过一个计数索引值来实现的。例如,当我们调用get(int location)时,首先会比较“location”和“双向链表长度的1/2”;若前者大,则从链表头开始往后查找,直到location位置;否则,从链表末尾开始先前查找,直到location位置。

   这就是“双线链表和索引值联系起来”的方法。

好了,接下来开始阅读源码(只要理解双向链表,那么LinkedList的源码很容易理解的)。

总结:

(01) LinkedList 实际上是通过双向链表去实现的。

        它包含一个非常重要的内部类:Entry。Entry是双向链表节点所对应的数据结构,它包括的属性有:当前节点所包含的值,上一个节点,下一个节点。

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

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

5)  LinkedList遍历方式

LinkedList支持多种遍历方式。建议不要采用随机访问的方式去遍历LinkedList,而采用逐个遍历的方式。

(01) 第一种,通过迭代器遍历。即通过Iterator去遍历。

for(Iteratoriter = list.iterator(); iter.hasNext();) iter.next();

(02) 通过快速随机访问遍历LinkedList

int size =list.size();

for (int i=0;i<size; i++) {

    list.get(i);       

}

(03) 通过另外一种for循环来遍历LinkedList

for (Integerinteg:list);

(04) 通过pollFirst()来遍历LinkedList

while(list.pollFirst()!= null);

(05) 通过pollLast()来遍历LinkedList

while(list.pollLast()!= null);

(06) 通过removeFirst()来遍历LinkedList

try {

    while(list.removeFirst() != null)

        ;

} catch(NoSuchElementException e) {

}

(07) 通过removeLast()来遍历LinkedList

try {

    while(list.removeLast() != null)

        ;

} catch(NoSuchElementException e) {}

测试这些遍历方式效率如下:

执行结果:

iteratorLinkedListThruIterator:8 ms

iteratorLinkedListThruForeach:3724 ms

iteratorThroughFor2:5 ms

iteratorThroughPollFirst:8 ms

iteratorThroughPollLast:6 ms

iteratorThroughRemoveFirst:2 ms

iteratorThroughRemoveLast:2 ms

由此可见,遍历LinkedList时,使用removeFist()或removeLast()效率最高。但用它们遍历时,会删除原始数据;若单纯只读取,而不删除,应该使用第3种遍历方式。

无论如何,千万不要通过随机访问去遍历LinkedList!

6)  LinkedList示例

importjava.util.List;

importjava.util.Iterator;

importjava.util.LinkedList;

importjava.util.NoSuchElementException;

/*

 * @desc LinkedList测试程序。

 *

 * @author skywang

 * @email kuiwu-wang@163.com

 */

public class LinkedListTest{

    public static void main(String[] args) {

        // 测试LinkedList的API

        testLinkedListAPIs() ;

        // 将LinkedList当作 LIFO(后进先出)的堆栈

        useLinkedListAsLIFO();

        // 将LinkedList当作 FIFO(先进先出)的队列

        useLinkedListAsFIFO();

    }

    /*

     * 测试LinkedList中部分API

     */

    private static void testLinkedListAPIs() {

        String val = null;

        //LinkedList llist;

        //llist.offer("10");

        // 新建一个LinkedList

        LinkedList llist = new LinkedList();

        //---- 添加操作 ----

        // 依次添加1,2,3

        llist.add("1");

        llist.add("2");

        llist.add("3");

        // 将“4”添加到第一个位置

        llist.add(1, "4");

        System.out.println("\nTest\"addFirst(), removeFirst(), getFirst()\"");

        // (01) 将“10”添加到第一个位置。 失败的话,抛出异常!

        llist.addFirst("10");

       System.out.println("llist:"+llist);

        // (02) 将第一个元素删除。        失败的话,抛出异常!

       System.out.println("llist.removeFirst():"+llist.removeFirst());

        System.out.println("llist:"+llist);

        // (03) 获取第一个元素。          失败的话,抛出异常!

       System.out.println("llist.getFirst():"+llist.getFirst());

        System.out.println("\nTest\"offerFirst(), pollFirst(), peekFirst()\"");

        // (01) 将“10”添加到第一个位置。 返回true。

        llist.offerFirst("10");

       System.out.println("llist:"+llist);

        // (02) 将第一个元素删除。        失败的话,返回null。

       System.out.println("llist.pollFirst():"+llist.pollFirst());

       System.out.println("llist:"+llist);

        // (03) 获取第一个元素。          失败的话,返回null。

       System.out.println("llist.peekFirst():"+llist.peekFirst());

        System.out.println("\nTest\"addLast(), removeLast(), getLast()\"");

        // (01) 将“20”添加到最后一个位置。  失败的话,抛出异常!

        llist.addLast("20");

        System.out.println("llist:"+llist);

        // (02) 将最后一个元素删除。        失败的话,抛出异常!

       System.out.println("llist.removeLast():"+llist.removeLast());

       System.out.println("llist:"+llist);

        // (03) 获取最后一个元素。          失败的话,抛出异常!

        System.out.println("llist.getLast():"+llist.getLast());

        System.out.println("\nTest\"offerLast(), pollLast(), peekLast()\"");

        // (01) 将“20”添加到第一个位置。 返回true。

        llist.offerLast("20");

       System.out.println("llist:"+llist);

        // (02) 将第一个元素删除。        失败的话,返回null。

       System.out.println("llist.pollLast():"+llist.pollLast());

       System.out.println("llist:"+llist);

        // (03) 获取第一个元素。          失败的话,返回null。

       System.out.println("llist.peekLast():"+llist.peekLast());

        // 将第3个元素设置300。不建议在LinkedList中使用此操作,因为效率低!

        llist.set(2, "300");

        // 获取第3个元素。不建议在LinkedList中使用此操作,因为效率低!

       System.out.println("\nget(3):"+llist.get(2));

        // ---- toArray(T[] a) ----

        // 将LinkedList转行为数组

        String[] arr =(String[])llist.toArray(new String[0]);

        for (String str:arr)

           System.out.println("str:"+str);

        // 输出大小

       System.out.println("size:"+llist.size());

        // 清空LinkedList

        llist.clear();

        // 判断LinkedList是否为空

       System.out.println("isEmpty():"+llist.isEmpty()+"\n");

    }

    /**

     * 将LinkedList当作 LIFO(后进先出)的堆栈

     */

    private static void useLinkedListAsLIFO() {

       System.out.println("\nuseLinkedListAsLIFO");

        // 新建一个LinkedList

        LinkedList stack = new LinkedList();

        // 将1,2,3,4添加到堆栈中

        stack.push("1");

        stack.push("2");

        stack.push("3");

        stack.push("4");

        // 打印“栈”

       System.out.println("stack:"+stack);

        // 删除“栈顶元素”

       System.out.println("stack.pop():"+stack.pop());

        // 取出“栈顶元素”

       System.out.println("stack.peek():"+stack.peek());

        // 打印“栈”

       System.out.println("stack:"+stack);

    }

    /**

     * 将LinkedList当作 FIFO(先进先出)的队列

     */

    private static void useLinkedListAsFIFO() {

       System.out.println("\nuseLinkedListAsFIFO");

        // 新建一个LinkedList

        LinkedList queue = new LinkedList();

        // 将10,20,30,40添加到队列。每次都是插入到末尾

        queue.add("10");

        queue.add("20");

        queue.add("30");

        queue.add("40");

        // 打印“队列”

       System.out.println("queue:"+queue);

        // 删除(队列的第一个元素)

       System.out.println("queue.remove():"+queue.remove());

        // 读取(队列的第一个元素)

       System.out.println("queue.element():"+queue.element());

        // 打印“队列”

       System.out.println("queue:"+queue);

    }

}

六、    Vector类

1)  概要

学完ArrayList和LinkedList之后,我们接着学习Vector。学习方式还是和之前一样,先对Vector有个整体认识,然后再学习它的源码;最后再通过实例来学会使用它。

第1部分 Vector介绍

第2部分 Vector数据结构

第3部分 Vector源码解析(基于JDK1.6.0_45)

第4部分 Vector遍历方式

第5部分 Vector示例

2)  Vector介绍

Vector共有4个构造函数

// 默认构造函数

Vector()

// capacity是Vector的默认容量大小。当由于增加数据导致容量增加时,每次容量会增加一倍。

Vector(intcapacity)

// capacity是Vector的默认容量大小,capacityIncrement是每次Vector容量增加时的增量值。

Vector(intcapacity, int capacityIncrement)

// 创建一个包含collection的Vector

Vector(Collection<?extends E> collection)

 

Vector的API

synchronizedboolean        add(E object)

             void           add(int location, E object)

synchronizedboolean        addAll(Collection<?extends E> collection)

synchronizedboolean        addAll(int location,Collection<? extends E> collection)

synchronizedvoid           addElement(E object)

synchronizedint            capacity()

             void           clear()

synchronizedObject         clone()

             boolean        contains(Object object)

synchronizedboolean       containsAll(Collection<?> collection)

synchronizedvoid           copyInto(Object[] elements)

synchronizedE              elementAt(int location)

             Enumeration<E> elements()

synchronizedvoid           ensureCapacity(intminimumCapacity)

synchronizedboolean        equals(Object object)

synchronizedE              firstElement()

             E              get(int location)

synchronizedint            hashCode()

synchronizedint            indexOf(Object object, intlocation)

             int            indexOf(Object object)

synchronizedvoid           insertElementAt(E object,int location)

synchronizedboolean        isEmpty()

synchronizedE              lastElement()

synchronizedint            lastIndexOf(Object object,int location)

synchronizedint            lastIndexOf(Object object)

synchronized E              remove(int location)

             boolean        remove(Object object)

synchronizedboolean       removeAll(Collection<?> collection)

synchronizedvoid           removeAllElements()

synchronizedboolean        removeElement(Objectobject)

synchronizedvoid           removeElementAt(intlocation)

synchronizedboolean       retainAll(Collection<?> collection)

synchronizedE              set(int location, Eobject)

synchronizedvoid           setElementAt(E object, intlocation)

synchronized void           setSize(int length)

synchronizedint            size()

synchronizedList<E>        subList(int start,int end)

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

synchronizedObject[]       toArray()

synchronizedString         toString()

synchronizedvoid           trimToSize()

3)  Vector数据结构

Vector的继承关系

java.lang.Object

        java.util.AbstractCollection<E>

              java.util.AbstractList<E>

                    java.util.Vector<E>

public classVector<E>

    extends AbstractList<E>

    implements List<E>, RandomAccess,Cloneable, java.io.Serializable {}

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

(01) elementData 是"Object[]类型的数组",它保存了添加到Vector中的元素。elementData是个动态数组,如果初始化Vector时,没指定动态数组的>大小,则使用默认大小10。随着Vector中元素的增加,Vector的容量也会动态增长,capacityIncrement是与容量增长相关的增长系数,具体的增长方式,请参考源码分析中的ensureCapacity()函数。

(02) elementCount 是动态数组的实际大小。

(03)capacityIncrement 是动态数组的增长系数。如果在创建Vector时,指定了capacityIncrement的大小;则,每次当Vector中动态数组容量增加时>,增加的大小都是capacityIncrement。

4)  Vector源码解析

为了更了解Vector的原理,下面对Vector源码代码作出分析。

总结:

(01) Vector实际上是通过一个数组去保存数据的。当我们构造Vecotr时;若使用默认构造函数,则Vector的默认容量大小是10。

(02) 当Vector容量不足以容纳全部元素时,Vector的容量会增加。若容量增加系数 >0,则将容量的值增加“容量增加系数”;否则,将容量大小增加一倍。

(03) Vector的克隆函数,即是将全部元素克隆到一个数组中。

5)  Vector遍历方式

Vector支持4种遍历方式。建议使用下面的第二种去遍历Vector,因为效率问题。

(01) 第一种,通过迭代器遍历。即通过Iterator去遍历。

Integer value =null;

for(Iterator iter= vec.iterator(); vec.hasNext();) value = (Integer)iter.next();

(02) 第二种,随机访问,通过索引值去遍历。

由于Vector实现了RandomAccess接口,它支持通过索引值去随机访问元素。

Integer value =null;

int size =vec.size();

for (int i=0;i<size; i++) {

    value = (Integer)vec.get(i);       

}

(03) 第三种,另一种for循环。如下:

Integer value =null;

for (Integerinteg:vec) {

    value = integ;

}

(04) 第四种,Enumeration遍历。如下:

Integer value =null;

Enumeration enu = vec.elements();

while(enu.hasMoreElements()) {

    value = (Integer)enu.nextElement();

}

测试这些遍历方式效率的如下:

运行结果:

iteratorThroughRandomAccess:6 ms

iteratorThroughIterator:9 ms

iteratorThroughFor2:8 ms

iteratorThroughEnumeration:7 ms

总结:遍历Vector,使用索引的随机访问方式最快,使用迭代器最慢。

6)  Vector示例

importjava.util.Vector;

importjava.util.List;

importjava.util.Iterator;

importjava.util.Enumeration;

/**

 * @desc Vector测试函数:遍历Vector和常用API

 *

 * @author skywang

 */

public classVectorTest {

    public static void main(String[] args) {

        // 新建Vector

        Vector vec = new Vector();

        // 添加元素

        vec.add("1");

        vec.add("2");

        vec.add("3");

        vec.add("4");

        vec.add("5");

        // 设置第一个元素为100

        vec.set(0, "100");

        // 将“500”插入到第3个位置

        vec.add(2, "300");

       System.out.println("vec:"+vec);

        // (顺序查找)获取100的索引

       System.out.println("vec.indexOf(100):"+vec.indexOf("100"));

        // (倒序查找)获取100的索引

        System.out.println("vec.lastIndexOf(100):"+vec.lastIndexOf("100"));

        // 获取第一个元素

       System.out.println("vec.firstElement():"+vec.firstElement());

        // 获取第3个元素

       System.out.println("vec.elementAt(2):"+vec.elementAt(2));

        // 获取最后一个元素

        System.out.println("vec.lastElement():"+vec.lastElement());

        // 获取Vector的大小

       System.out.println("size:"+vec.size());

        // 获取Vector的总的容量

       System.out.println("capacity:"+vec.capacity());

        // 获取vector的“第2”到“第4”个元素

        System.out.println("vec 2 to4:"+vec.subList(1, 4));

        // 通过Enumeration遍历Vector

        Enumeration enu = vec.elements();

        while(enu.hasMoreElements())

           System.out.println("nextElement():"+enu.nextElement());

        Vector retainVec = new Vector();

        retainVec.add("100");

        retainVec.add("300");

        // 获取“vec”中包含在“retainVec中的元素”的集合

       System.out.println("vec.retain():"+vec.retainAll(retainVec));

       System.out.println("vec:"+vec);

        // 获取vec对应的String数组

        String[] arr = (String[])vec.toArray(new String[0]);

        for (String str:arr)

           System.out.println("str:"+str);

        // 清空Vector。clear()和removeAllElements()一样!

        vec.clear();

//        vec.removeAllElements();

        // 判断Vector是否为空

       System.out.println("vec.isEmpty():"+vec.isEmpty());

    }  

}

七、    stack类

1)  概要

学完Vector了之后,接下来我们开始学习Stack。Stack很简单,它继承于Vector。学习方式还是和之前一样,先对Stack有个整体认识,然后再学习它的源码;最后再通过实例来学会使用它。内容包括:

第1部分 Stack介绍

第2部分 Stack源码解析(基于JDK1.6.0_45)

第3部分 Stack示例

2)  Stack介绍

Stack简介

Stack是栈。它的特性是:先进后出(FILO, First In Last Out)。

java工具包中的Stack是继承于Vector(矢量队列)的,由于Vector是通过数组实现的,这就意味着,Stack也是通过数组实现的,而非链表。当然,我们也可以将LinkedList当作栈来使用!在“Java 集合系列06之 Vector详细介绍(源码解析)和使用示例”中,已经详细介绍过Vector的数据结构,这里就不再对Stack的数据结构进行说明了。

Stack的继承关系

java.lang.Object

     java.util.AbstractCollection<E>

        java.util.AbstractList<E>

            java.util.Vector<E>

                java.util.Stack<E>

public classStack<E> extends Vector<E> {}

Stack的构造函数

Stack只有一个默认构造函数,如下:

Stack()

Stack的API

Stack是栈,它常用的API如下:

             boolean       empty()

synchronizedE             peek()

synchronizedE             pop()

             E             push(E object)

synchronizedint           search(Object o)

由于Stack和继承于Vector,因此它也包含Vector中的全部API。

3)  Stack源码解析

Stack的源码非常简单,下面我们对它进行学习。

总结:

(01) Stack实际上也是通过数组去实现的。

       执行push时(即,将元素推入栈中),是通过将元素追加的数组的末尾中。

       执行peek时(即,取出栈顶元素,不执行删除),是返回数组末尾的元素。

       执行pop时(即,取出栈顶元素,并将该元素从栈中删除),是取出数组末尾的元素,然后将该元素从数组中删除。

(02) Stack继承于Vector,意味着Vector拥有的属性和功能,Stack都拥有。

4)  Vector示例

importjava.util.Stack;

importjava.util.Iterator;

importjava.util.List;

/**

 * @desc Stack的测试程序。测试常用API的用法

 *

 * @author skywang

 */

public classStackTest {

    public static void main(String[] args) {

        Stack stack = new Stack();

        // 将1,2,3,4,5添加到栈中

        for(int i=1; i<6; i++) {

            stack.push(String.valueOf(i));

        }

        // 遍历并打印出该栈

        iteratorThroughRandomAccess(stack) ;

        // 查找“2”在栈中的位置,并输出

        int pos = stack.search("2");

        System.out.println("the postion of2 is:"+pos);

        // pop栈顶元素之后,遍历栈

        stack.pop();

        iteratorThroughRandomAccess(stack) ;

        // peek栈顶元素之后,遍历栈

        String val = (String)stack.peek();

       System.out.println("peek:"+val);

        iteratorThroughRandomAccess(stack) ;

        // 通过Iterator去遍历Stack

        iteratorThroughIterator(stack) ;

    }

    /**

     * 通过快速访问遍历Stack

     */

    public static voiditeratorThroughRandomAccess(List list) {

        String val = null;

        for (int i=0; i<list.size(); i++) {

            val = (String)list.get(i);

            System.out.print(val+"");

        }

        System.out.println();

    }

    /**

     * 通过迭代器遍历Stack

     */

    public static voiditeratorThroughIterator(List list) {

        String val = null;

        for(Iterator iter = list.iterator();iter.hasNext(); ) {

            val = (String)iter.next();

            System.out.print(val+"");

        }

        System.out.println();

    }

}

八、    List总结

1)  概要

前面,我们学完了List的全部内容(ArrayList, LinkedList, Vector, Stack)。

Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例

Java 集合系列04之 fail-fast总结(通过ArrayList来说明fail-fast的原理、解决办法)

Java 集合系列05之 LinkedList详细介绍(源码解析)和使用示例

Java 集合系列06之 Vector详细介绍(源码解析)和使用示例

Java 集合系列07之 Stack详细介绍(源码解析)和使用示例

现在,我们再回头看看总结一下List。内容包括:

第1部分 List概括

第2部分 List使用场景

第3部分 LinkedList和ArrayList性能差异分析

第4部分Vector和ArrayList比较

2)  List概括

先回顾一下List的框架图说明

(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)。

3)  List使用场景

学东西的最终目的是为了能够理解、使用它。下面先概括的说明一下各个List的使用场景,后面再分析原因。

如果涉及到“栈”、“队列”、“链表”等操作,应该考虑用List,具体的选择哪个List,根据下面的标准来取舍。

(01) 对于需要快速插入,删除元素,应该使用LinkedList。

(02) 对于需要快速随机访问元素,应该使用ArrayList。

(03) 对于“单线程环境” 或者 “多线程环境,但List仅仅只会被单个线程操作”,此时应该使用非同步的类(如ArrayList)。

       对于“多线程环境,且List可能同时被多个线程操作”,此时,应该使用同步的类(如Vector)。

通过下面的测试程序,我们来验证上面的(01)和(02)结论。参考代码如下:

importjava.util.*;

importjava.lang.Class;

/*

 * @desc 对比ArrayList和LinkedList的插入、随机读取效率、删除的效率

 *

 * @author skywang

 */

public classListCompareTest {

    private static final int COUNT = 100000;

    private static LinkedList linkedList = newLinkedList();

    private static ArrayList arrayList = newArrayList();

    private static Vector vector = newVector();

    private static Stack stack = new Stack();

    public static void main(String[] args) {

        // 换行符

        System.out.println();

        // 插入

        insertByPosition(stack) ;

        insertByPosition(vector) ;

        insertByPosition(linkedList) ;

       insertByPosition(arrayList) ;

        // 换行符

        System.out.println();

        // 随机读取

        readByPosition(stack);

        readByPosition(vector);

        readByPosition(linkedList);

        readByPosition(arrayList);

        // 换行符

        System.out.println();

        // 删除

        deleteByPosition(stack);

        deleteByPosition(vector);

        deleteByPosition(linkedList);

        deleteByPosition(arrayList);

    }

    // 获取list的名称

    private static String getListName(List list){

        if (list instanceof LinkedList) {

            return "LinkedList";

        } else if (list instanceof ArrayList) {

            return "ArrayList";

        } else if (list instanceof Stack) {

            return "Stack";

        } else if (list instanceof Vector) {

            return "Vector";

        } else {

            return "List";

        }

    }

    // 向list的指定位置插入COUNT个元素,并统计时间

    private static void insertByPosition(Listlist) {

        long startTime =System.currentTimeMillis();

        // 向list的位置0插入COUNT个数

        for (int i=0; i<COUNT; i++)

            list.add(0, i);

        long endTime =System.currentTimeMillis();

        long interval = endTime - startTime;

        System.out.println(getListName(list) +" : insert "+COUNT+" elements into the 1st position use time:" + interval+" ms");

    }

    // 从list的指定位置删除COUNT个元素,并统计时间

    private static void deleteByPosition(Listlist) {

        long startTime =System.currentTimeMillis();

        // 删除list第一个位置元素

        for (int i=0; i<COUNT; i++)

            list.remove(0);

        long endTime =System.currentTimeMillis();

        long interval = endTime - startTime;

        System.out.println(getListName(list) +" : delete "+COUNT+" elements from the 1st position use time:" + interval+" ms");

    }

    // 根据position,不断从list中读取元素,并统计时间

    private static void readByPosition(Listlist) {

        long startTime =System.currentTimeMillis();

        // 读取list元素

        for (int i=0; i<COUNT; i++)

            list.get(i);

        long endTime =System.currentTimeMillis();

        long interval = endTime - startTime;

        System.out.println(getListName(list) +" : read "+COUNT+" elements by position use time:" + interval+" ms");

    }

}

运行结果如下:

Stack : insert100000 elements into the 1st position use time:1640 ms

Vector : insert100000 elements into the 1st position use time:1607 ms

LinkedList :insert 100000 elements into the 1st position use time:29 ms

ArrayList : insert100000 elements into the 1st position use time:1617 ms

 

Stack : read100000 elements by position use time:9 ms

Vector : read100000 elements by position use time:6 ms

LinkedList : read100000 elements by position use time:10809 ms

ArrayList : read100000 elements by position use time:5 ms

 

Stack : delete100000 elements from the 1st position use time:1916 ms

Vector : delete100000 elements from the 1st position use time:1910 ms

LinkedList :delete 100000 elements from the 1st position use time:15 ms

ArrayList : delete100000 elements from the 1st position use time:1909 ms

从中,我们可以发现:

插入10万个元素,LinkedList所花时间最短:29ms。

删除10万个元素,LinkedList所花时间最短:15ms。

遍历10万个元素,LinkedList所花时间最长:10809 ms;而ArrayList、Stack和Vector则相差不多,都只用了几秒。

考虑到Vector是支持同步的,而Stack又是继承于Vector的;因此,得出结论:

(01) 对于需要快速插入,删除元素,应该使用LinkedList。

(02) 对于需要快速随机访问元素,应该使用ArrayList。

(03) 对于“单线程环境” 或者 “多线程环境,但List仅仅只会被单个线程操作”,此时应该使用非同步的类。

4)  LinkedList和ArrayList性能差异分析

下面我们看看为什么LinkedList中插入元素很快,而ArrayList中插入元素很慢!

LinkedList.java中向指定位置插入元素的代码如下:

public voidadd(int index, E element) {

    checkPositionIndex(index);

    if (index == size)

        linkLast(element);

    else

        linkBefore(element, node(index));

}

private voidcheckPositionIndex(int index) {

    if (!isPositionIndex(index))

        throw newIndexOutOfBoundsException(outOfBoundsMsg(index));

}

void linkLast(E e){

    final Node<E> l = last;

    final Node<E> newNode = newNode<>(l, e, null);

    last = newNode;

    if (l == null)

        first = newNode;

    else

        l.next = newNode;

    size++;

    modCount++;

}

void linkBefore(Ee, Node<E> succ) {

    // assert succ != null;

    final Node<E> pred = succ.prev;

    final Node<E> newNode = newNode<>(pred, e, succ);

    succ.prev = newNode;

    if (pred == null)

        first = newNode;

    else

        pred.next = newNode;

    size++;

    modCount++;

}

从中,我们可以看出:通过add(intindex, E element)向LinkedList插入元素时。先是在双向链表中找到要插入节点的位置index;找到之后,再插入一个新节点。

双向链表查找index位置的节点时,有一个加速动作:若index < 双向链表长度的1/2,则从前向后查找; 否则,从后向前查找。

接着,我们看看ArrayList.java中向指定位置插入元素的代码。如下:

// 将e添加到ArrayList的指定位置

public void add(intindex, 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++;

}

ensureCapacity(size+1)的作用是“确认ArrayList的容量,若容量不够,则增加容量。”

真正耗时的操作是System.arraycopy(elementData, index, elementData, index + 1, size - index);

Sun JDK包的java/lang/System.java中的arraycopy()声明如下:

public staticnative void arraycopy(Object src, int srcPos, Object dest, int destPos, intlength);

arraycopy()是个JNI函数,它是在JVM中实现的。sunJDK中看不到源码,不过可以在OpenJDK包中看到的源码。网上有对arraycopy()的分析说明,请参考:System.arraycopy源码分析

实际上,我们只需要了解:System.arraycopy(elementData, index, elementData, index + 1, size - index); 会移动index之后所有元素即可。这就意味着,ArrayList的add(int index, E element)函数,会引起index之后所有元素的改变!

通过上面的分析,我们就能理解为什么LinkedList中插入元素很快,而ArrayList中插入元素很慢。

“删除元素”与“插入元素”的原理类似,这里就不再过多说明。

接下来,我们看看 “为什么LinkedList中随机访问很慢,而ArrayList中随机访问很快”。

先看看LinkedList随机访问的代码

// 返回LinkedList指定位置的元素

public E get(intindex) {

    checkElementIndex(index);

    return node(index).item;

}

// 获取双向链表中指定位置的节点

Node<E>node(int index) {

  // assert isElementIndex(index);

  if (index < (size >> 1)) {

      Node<E> x = first;

      for (int i = 0; i < index; i++)

          x = x.next;

      return x;

  } else {

      Node<E> x = last;

      for (int i = size - 1; i > index; i--)

          x = x.prev;

      return x;

  }

}

从中,我们可以看出:通过get(intindex)获取LinkedList第index个元素时。先是在双向链表中找到要index位置的元素;找到之后再返回。

双向链表查找index位置的节点时,有一个加速动作:若index < 双向链表长度的1/2,则从前向后查找; 否则,从后向前查找。

下面看看ArrayList随机访问的代码

// 获取index位置的元素值

public E get(intindex) {

    RangeCheck(index);

    return (E) elementData[index];

}

private void RangeCheck(intindex) {

    if (index >= size)

        throw new IndexOutOfBoundsException(

        "Index: "+index+", Size:"+size);

}

从中,我们可以看出:通过get(intindex)获取ArrayList第index个元素时。直接返回数组中index位置的元素,而不需要像LinkedList一样进行查找。

5)  Vector和ArrayList比较

相同之处

1 它们都是List

它们都继承于AbstractList,并且实现List接口。

ArrayList和Vector的类定义如下:

// ArrayList的定义

public classArrayList<E> extends AbstractList<E>

        implements List<E>, RandomAccess,Cloneable, java.io.Serializable

// Vector的定义

public classVector<E> extends AbstractList<E>

    implements List<E>, RandomAccess,Cloneable, java.io.Serializable {}

2 它们都实现了RandomAccess和Cloneable接口

   实现RandomAccess接口,意味着它们都支持快速随机访问;

   实现Cloneable接口,意味着它们能克隆自己。

3 它们都是通过数组实现的,本质上都是动态数组

ArrayList.java中定义数组elementData用于保存元素

// 保存ArrayList中数据的数组

private transientObject[] elementData;

Vector.java中也定义了数组elementData用于保存元素

// 保存Vector中数据的数组

protected Object[]elementData;

4 它们的默认数组容量是10

   若创建ArrayList或Vector时,没指定容量大小;则使用默认容量大小10。

ArrayList的默认构造函数如下:

// ArrayList构造函数。默认容量是10。

public ArrayList(){

    this(10);

}

Vector的默认构造函数如下:

// Vector构造函数。默认容量是10。

public Vector() {

    this(10);

}

5 它们都支持Iterator和listIterator遍历

   它们都继承于AbstractList,而AbstractList中分别实现了 “iterator()接口返回Iterator迭代器” 和 “listIterator()返回ListIterator迭代器”。

不同之处

1 线程安全性不一样

   ArrayList是非线程安全;

   而Vector是线程安全的,它的函数都是synchronized的,即都是支持同步的。

   ArrayList适用于单线程,Vector适用于多线程。

2 对序列化支持不同

   ArrayList支持序列化,而Vector不支持;即ArrayList有实现java.io.Serializable接口,而Vector没有实现该接口。

3 构造函数个数不同

   ArrayList有3个构造函数,而Vector有4个构造函数。Vector除了包括和ArrayList类似的3个构造函数之外,另外的一个构造函数可以指定容量增加系数。

ArrayList的构造函数如下:

// 默认构造函数

ArrayList()

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

ArrayList(intcapacity)

// 创建一个包含collection的ArrayList

ArrayList(Collection<?extends E> collection)

Vector的构造函数如下:

// 默认构造函数

Vector()

// capacity是Vector的默认容量大小。当由于增加数据导致容量增加时,每次容量会增加一倍。

Vector(intcapacity)

// 创建一个包含collection的Vector

Vector(Collection<?extends E> collection)

// capacity是Vector的默认容量大小,capacityIncrement是每次Vector容量增加时的增量值。

Vector(intcapacity, int capacityIncrement)

4 容量增加方式不同

   逐个添加元素时,若ArrayList容量不足时,“新的容量”=“(原始容量x3)/2 + 1”。

   而Vector的容量增长与“增长系数有关”,若指定了“增长系数”,且“增长系数有效(即,大于0)”;那么,每次容量不足时,“新的容量”=“原始容量+增长系数”。若增长系数无效(即,小于/等于0),则“新的容量”=“原始容量 x 2”。

ArrayList中容量增长的主要函数如下:

public voidensureCapacity(int minCapacity) {

    // 将“修改统计数”+1

    modCount++;

    int oldCapacity = elementData.length;

    // 若当前容量不足以容纳当前的元素个数,设置新的容量=“(原始容量x3)/2+ 1”

    if (minCapacity > oldCapacity) {

        Object oldData[] = elementData;

        int newCapacity = (oldCapacity * 3)/2 +1;

        if (newCapacity < minCapacity)

            newCapacity = minCapacity;

        elementData =Arrays.copyOf(elementData, newCapacity);

    }

}

Vector中容量增长的主要函数如下:

private voidensureCapacityHelper(int minCapacity) {

    int oldCapacity = elementData.length;

    // 当Vector的容量不足以容纳当前的全部元素,增加容量大小。

    // 若 容量增量系数>0(即capacityIncrement>0),则将容量增大当capacityIncrement

    // 否则,将容量增大一倍。

    if (minCapacity > oldCapacity) {

        Object[] oldData = elementData;

        int newCapacity = (capacityIncrement> 0) ?

            (oldCapacity + capacityIncrement) :(oldCapacity * 2);

        if (newCapacity < minCapacity) {

            newCapacity = minCapacity;

        }

        elementData =Arrays.copyOf(elementData, newCapacity);

    }

}

5 对Enumeration的支持不同。Vector支持通过Enumeration去遍历,而List不支持

Vector中实现Enumeration的代码如下:

publicEnumeration<E> elements() {

    // 通过匿名类实现Enumeration

    return new Enumeration<E>() {

        int count = 0;

        // 是否存在下一个元素

        public boolean hasMoreElements() {

            return count < elementCount;

        }

        // 获取下一个元素

        public E nextElement() {

            synchronized (Vector.this) {

                if (count < elementCount) {

                    return(E)elementData[count++];

                }

            }

            throw newNoSuchElementException("Vector Enumeration");

        }

    };

}

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Pattern是Java用于进行正则表达式匹配的类,其源码比较复杂,需要仔细研究才能理解其的实现细节。下面是Pattern类的主要源码解读: 1. Pattern类的构造函数 Pattern类只有一个私有构造函数,因为其实例都是通过静态工厂方法Pattern.compile()创建的。在构造函数,主要进行了一些初始化操作,如设置默认的标志位和编译正则表达式等。 2. 静态工厂方法Pattern.compile() 这是Pattern类的主要工厂方法,用于将正则表达式编译为一个Pattern对象。在该方法,会根据传入的参数,设置Pattern对象的各种属性,如标志位、编译器等。然后,通过调用Matcher类的构造函数,生成一个Matcher对象,并将其返回。 3. Matcher类 Matcher类是用于进行匹配操作的类,其实例可以从Pattern类获取。在Matcher类,主要含了一些用于匹配操作的方法,如find()、start()、end()等。在这些方法,主要进行的就是正则表达式的匹配操作,其涉及到一些复杂的算法,如回溯、贪婪匹配等。 4. PatternSyntaxException类 这是一个异常类,用于表示正则表达式语法错误。在Pattern.compile()方法,如果正则表达式存在语法错误,就会抛出该异常。在PatternSyntaxException类,主要含了错误的位置、错误信息等属性,便于程序员进行调试。 总体来说,Pattern类的源码比较复杂,主要涉及到正则表达式的编译和匹配操作,其涉及到一些复杂的算法和数据结构。如果想深入了解该类的实现细节,需要仔细研究其源码并进行调试。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值