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

Java 集合系列

大致说明:

看上面的框架图,先抓住它的主干,即CollectionMap

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

  Collection包含了ListSet两大分支。
  (01) List是一个有序的队列,每一个元素都有它的索引。第一个元素的索引值是0
          List的实现类有LinkedList, ArrayList, Vector, Stack

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

2 Map是一个映射接口,即key-value键值对。Map中的每一个元素包含一个key”“key对应的value”

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

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

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

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

有了上面的整体框架之后,我们接下来对每个类分别进行分析。

 

 

Java 集合系列02 Collection架构

 

概要

首先,我们对Collection进行说明。下面先看看Collection的一些框架类的关系图:

Java 集合系列

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

ListSet都是接口,它们继承于CollectionList是有序的队列,List中可以有重复的元素;而Set是数学概念中的集合,Set中没有重复元素
ListSet都有它们各自的实现类。

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

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

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

本章内容包括:
1 Collection简介
2 List简介
3 Set简介
4 AbstractCollection
5 AbstractList
6 AbstractSet
7 Iterator
8 ListIterator

转载请注明出处:http://www.cnblogs.com/skywang12345/p/3308513.html

 

1 Collection简介

Collection的定义如下:

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

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


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

Java 集合系列

// Collection的API

abstract boolean         add(E object)

abstract boolean         addAll(Collection<? extends E> collection)

abstract void            clear()

abstract boolean         contains(Object object)

abstract boolean         containsAll(Collection<?> collection)

abstract boolean         equals(Object object)

abstract int             hashCode()

abstract boolean         isEmpty()

abstract Iterator<E>     iterator()

abstract boolean         remove(Object object)

abstract boolean         removeAll(Collection<?> collection)

abstract boolean         retainAll(Collection<?> collection)

abstract int             size()

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

abstract Object[]        toArray()

Java 集合系列

 

2 List简介

List的定义如下:

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

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

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

 

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

Java 集合系列

// Collection的API

abstract boolean         add(E object)

abstract boolean         addAll(Collection<? extends E> collection)

abstract void            clear()

abstract boolean         contains(Object object)

abstract boolean         containsAll(Collection<?> collection)

abstract boolean         equals(Object object)

abstract int             hashCode()

abstract boolean         isEmpty()

abstract Iterator<E>     iterator()

abstract boolean         remove(Object object)

abstract boolean         removeAll(Collection<?> collection)

abstract boolean         retainAll(Collection<?> collection)

abstract int             size()

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

abstract Object[]        toArray()

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

abstract void                add(int location, E object)

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

abstract E                   get(int location)

abstract int                 indexOf(Object object)

abstract int                 lastIndexOf(Object object)

abstract ListIterator<E>     listIterator(int location)

abstract ListIterator<E>     listIterator()

abstract E                   remove(int location)

abstract E                   set(int location, E object)

abstract List<E>             subList(int start, int end)

Java 集合系列

 

3 Set简介

Set的定义如下:

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

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

关于API方面。SetAPICollection完全一样。

 

// Set的API

abstract boolean         add(E object)

abstract boolean         addAll(Collection<? extends E> collection)

abstract void             clear()

abstract boolean         contains(Object object)

abstract boolean         containsAll(Collection<?> collection)

abstract boolean         equals(Object object)

abstract int             hashCode()

abstract boolean         isEmpty()

abstract Iterator<E>     iterator()

abstract boolean         remove(Object object)

abstract boolean         removeAll(Collection<?> collection)

abstract boolean         retainAll(Collection<?> collection)

abstract int             size()

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

abstract Object[]         toArray()

Java 集合系列

 

4 AbstractCollection

AbstractCollection的定义如下:

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

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

 

5 AbstractList

AbstractList的定义如下:

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

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

 

6 AbstractSet

AbstractSet的定义如下: 

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

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

 

7 Iterator

Iterator的定义如下:

public interface Iterator<E> {}

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

// Iterator的API

abstract boolean hasNext()

abstract E next()

abstract void remove()

 

8 ListIterator

ListIterator的定义如下:

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

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

Java 集合系列

// ListIterator的API

// 继承于Iterator的接口

abstract boolean hasNext()

abstract E next()

abstract void remove()

// 新增API接口

abstract void add(E object)

abstract boolean hasPrevious()

abstract int nextIndex()

abstract E previous()

abstract int previousIndex()

abstract void set(E object)

Java 集合系列

 

 

第1部分 ArrayList介绍

ArrayList简介

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

ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
ArrayList 实现了RandmoAccess接口,即提供了随机访问功能。RandmoAccessjava中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。稍后,我们会比较List快速随机访问通过Iterator迭代器访问的效率。

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

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

 

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

 

总结
(01) ArrayList 实际上是通过一个数组去保存数据的。当我们构造ArrayList时;若使用默认构造函数,则ArrayList默认容量大小是10
(02) ArrayList容量不足以容纳全部元素时,ArrayList会重新设置容量:新的容量=“(原始容量x3)/2 + 1”
(03) ArrayList的克隆函数,即是将全部元素克隆到一个数组中。
(04) ArrayList实现java.io.Serializable的方式。当写入到输出流时,先写入容量,再依次写入每一个元素;当读出输入流时,先读取容量,再依次读取每一个元素

 

 

1 fail-fast简介

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

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

 

3 fail-fast解决办法

fail-fast机制,是一种错误检测机制。它只能被用来检测错误,因为JDK并不保证fail-fast机制一定会发生。若在多线程环境下使用fail-fast机制的集合,建议使用“java.util.concurrent包下的类去取代“java.util包下的类
所以,本例中只需要将ArrayList替换成java.util.concurrent包下对应的类即可。
即,将代码

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

替换为

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

则可以解决该办法。

 

第1部分 LinkedList介绍

LinkedList简介

LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。
LinkedList 实现 List 接口,能对它进行队列操作。
LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。
LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。
LinkedList 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。
LinkedList 是非同步的。

AbstractSequentialList简介

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

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

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

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

总结起来如下表格:

 

第1部分 Vector介绍

Vector简介

Vector 矢量队列,它是JDK1.0版本添加的类。继承于AbstractList,实现了List, RandomAccess, Cloneable这些接口。
Vector 继承了AbstractList,实现了List;所以,它是一个队列,支持相关的添加、删除、修改、遍历等功能
Vector 实现了RandmoAccess接口,即提供了随机访问功能RandmoAccessjava中用来被List实现,为List提供快速访问功能的。在Vector中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。
Vector 实现了Cloneable接口,即实现clone()函数。它能被克隆。

ArrayList不同,Vector中的操作是线程安全的

 

总结
(01) Vector实际上是通过一个数组去保存数据的。当我们构造Vecotr时;若使用默认构造函数,则Vector默认容量大小是10
(02) Vector容量不足以容纳全部元素时,Vector的容量会增加。若容量增加系数 >0,则将容量的值增加容量增加系数;否则,将容量大小增加一倍。
(03) Vector的克隆函数,即是将全部元素克隆到一个数组中。

 

第1部分 Stack介绍

Stack简介

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

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

总结

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

 

 

第1部分 List概括

先回顾一下List的框架图

Java 集合系列

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

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

 

第2部分 List使用场景

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

如果涉及到队列链表等操作,应该考虑用List,具体的选择哪个List,根据下面的标准来取舍。
(01) 对于需要快速插入,删除元素,应该使用LinkedList
(02) 对于需要快速随机访问元素,应该使用ArrayList
(03) 对于单线程环境或者多线程环境,但List仅仅只会被单个线程操作,此时应该使用非同步的类(ArrayList)
       对于多线程环境,且List可能同时被多个线程操作,此时,应该使用同步的类(Vector)


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

import java.util.*;

import java.lang.Class;

 

/*

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

 *

 * @author skywang

 */

public class ListCompareTest {

 

    private static final int COUNT = 100000;

 

    private static LinkedList linkedList = new LinkedList();

    private static ArrayList arrayList = new ArrayList();

    private static Vector vector = new Vector();

    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(List list) {

        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(List list) {

        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(List list) {

        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");

    }

}运行结果如下

Java 集合系列

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

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

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

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

 

Stack : read 100000 elements by position use time:9 ms

Vector : read 100000 elements by position use time:6 ms

LinkedList : read 100000 elements by position use time:10809 ms

ArrayList : read 100000 elements by position use time:5 ms

 

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

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

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

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

Java 集合系列

从中,我们可以发现
插入10万个元素,LinkedList所花时间最短:29ms
删除10万个元素,LinkedList所花时间最短:15ms
遍历10万个元素,LinkedList所花时间最长:10809 ms;而ArrayListStackVector则相差不多,都只用了几秒。

考虑到Vector是支持同步的,而Stack又是继承于Vector的;因此,得出结论:
(01) 对于需要快速插入,删除元素,应该使用LinkedList
(02) 对于需要快速随机访问元素,应该使用ArrayList
(03) 对于单线程环境或者多线程环境,但List仅仅只会被单个线程操作,此时应该使用非同步的类。

 

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

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

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

Java 集合系列

// 在index前添加节点,且节点的值为element

public void add(int index, E element) {

    addBefore(element, (index==size ? header : entry(index)));

}

 

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

private Entry<E> entry(int index) {

    if (index < 0 || index >= size)

        throw new IndexOutOfBoundsException("Index: "+index+

                                            ", Size: "+size);

    Entry<E> e = header;

    // 获取index处的节点。

    // 若index < 双向链表长度的1/2,则从前向后查找;

    // 否则,从后向前查找。

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

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

            e = e.next;

    } else {

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

            e = e.previous;

    }

    return e;

}

 

// 将节点(节点数据是e)添加到entry节点之前。

private Entry<E> addBefore(E e, Entry<E> entry) {

    // 新建节点newEntry,将newEntry插入到节点e之前;并且设置newEntry的数据是e

    Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);

    // 插入newEntry到链表中

    newEntry.previous.next = newEntry;

    newEntry.next.previous = newEntry;

    size++;

    modCount++;

    return newEntry;

}

Java 集合系列

从中,我们可以看出:通过add(int index, E element)LinkedList插入元素时。先是在双向链表中找到要插入节点的位置index;找到之后,再插入一个新节点
双向链表查找index位置的节点时,有一个加速动作index < 双向链表长度的1/2,则从前向后查找; 否则,从后向前查找

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

Java 集合系列

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

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++;

}

Java 集合系列

ensureCapacity(size+1) 的作用是确认ArrayList的容量,若容量不够,则增加容量。
真正耗时的操作是 System.arraycopy(elementData, index, elementData, index + 1, size - index);

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

public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);

arraycopy()是个JNI函数,它是在JVM中实现的。sunJDK中看不到源码,不过可以在OpenJDK包中看到的源码。网上有对arraycopy()的分析说明,请参考:System.arraycopy源码分析 
实际上,我们只需要了解: System.arraycopy(elementData, index, elementData, index + 1, size - index); 会移动index之后所有元素即可这就意味着,ArrayListadd(int index, E element)函数,会引起index之后所有元素的改变!


通过上面的分析,我们就能理解为什么LinkedList中插入元素很快,而ArrayList中插入元素很慢。
删除元素插入元素的原理类似,这里就不再过多说明。

 

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

先看看LinkedList随机访问的代码

Java 集合系列

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

public E get(int index) {

    return entry(index).element;

}

 

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

private Entry<E> entry(int index) {

    if (index < 0 || index >= size)

        throw new IndexOutOfBoundsException("Index: "+index+

                                            ", Size: "+size);

    Entry<E> e = header;

    // 获取index处的节点。

    // 若index < 双向链表长度的1/2,则从前先后查找;

    // 否则,从后向前查找。

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

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

            e = e.next;

    } else {

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

            e = e.previous;

    }

    return e;

}

Java 集合系列

从中,我们可以看出:通过get(int index)获取LinkedListindex个元素时先是在双向链表中找到要index位置的元素;找到之后再返回。
双向链表查找index位置的节点时,有一个加速动作index < 双向链表长度的1/2,则从前向后查找; 否则,从后向前查找。

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

Java 集合系列

// 获取index位置的元素值

public E get(int index) {

    RangeCheck(index);

 

    return (E) elementData[index];

}

 

private void RangeCheck(int index) {

    if (index >= size)

        throw new IndexOutOfBoundsException(

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

}

Java 集合系列

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

 

第4部分 Vector和ArrayList比较

相同之处

1 它们都是List

它们都继承于AbstractList,并且实现List接口。
ArrayListVector的类定义如下:

Java 集合系列

// ArrayList的定义

public class ArrayList<E> extends AbstractList<E>

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

 

// Vector的定义

public class Vector<E> extends AbstractList<E>

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

Java 集合系列

  

2 它们都实现了RandomAccessCloneable接口

   实现RandomAccess接口,意味着它们都支持快速随机访问;
   实现Cloneable接口,意味着它们能克隆自己。

 

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

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

// 保存ArrayList中数据的数组

private transient Object[] elementData;

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

// 保存Vector中数据的数组

protected Object[] elementData;

 

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

   若创建ArrayListVector时,没指定容量大小;则使用默认容量大小10

ArrayList的默认构造函数如下:

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

public ArrayList() {

    this(10);

}

Vector的默认构造函数如下:

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

public Vector() {

    this(10);

 

5 它们都支持IteratorlistIterator遍历

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

 

不同之处

1 线程安全性不一样

   ArrayList是非线程安全;
   Vector是线程安全的,它的函数都是synchronized的,即都是支持同步的。
   ArrayList适用于单线程,Vector适用于多线程。

 

2 对序列化支持不同

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

 

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

ArrayList的构造函数如下

Java 集合系列

// 默认构造函数

ArrayList()

 

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

ArrayList(int capacity)

 

// 创建一个包含collection的ArrayList

ArrayList(Collection<? extends E> collection)

Java 集合系列

Vector的构造函数如下

Java 集合系列

// 默认构造函数

Vector()

 

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

Vector(int capacity)

 

// 创建一个包含collection的Vector

Vector(Collection<? extends E> collection)

 

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

Vector(int capacity, int capacityIncrement)

Java 集合系列

 

4 容量增加方式不同

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

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

Java 集合系列

public void ensureCapacity(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);

    }

}

Java 集合系列

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

Java 集合系列

private void ensureCapacityHelper(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);

    }

}

Java 集合系列

 

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

Vector中实现Enumeration的代码如下:

Java 集合系列

public Enumeration<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 new NoSuchElementException("Vector Enumeration");

        }

    };

}

Java 集合系列

Java 集合系列09 Map架构

 

概要

前面,我们已经系统的对List进行了学习。接下来,我们先学习Map,然后再学习Set;因为Set的实现类都是基于Map来实现的(如,HashSet是通过HashMap实现的,TreeSet是通过TreeMap实现的)

首先,我们看看Map架构。

Java 集合系列

如上图:
(01) Map 映射接口Map中存储的内容是键值对(key-value)
(02) AbstractMap 继承于Map的抽象类,它实现了Map中的大部分API。其它Map的实现类可以通过继承AbstractMap来减少重复编码。
(03) SortedMap 是继承于Map的接口。SortedMap中的内容是排序的键值对,排序的方法是通过比较器(Comparator)
(04) NavigableMap 是继承于SortedMap的接口。相比于SortedMapNavigableMap有一系列的导航方法;如"获取大于/等于某对象的键值对"获取小于/等于某对象的键值对等等。 
(05) TreeMap 继承于AbstractMap,且实现了NavigableMap接口;因此,TreeMap中的内容是有序的键值对
(06) HashMap 继承于AbstractMap,但没实现NavigableMap接口;因此,HashMap的内容是键值对,但不保证次序
(07) Hashtable 虽然不是继承于AbstractMap,但它继承于Dictionary(Dictionary也是键值对的接口),而且也实现Map接口;因此,Hashtable的内容也是键值对,也不保证次序。但和HashMap相比,Hashtable是线程安全的,而且它支持通过Enumeration去遍历。
(08) WeakHashMap 继承于AbstractMap。它和HashMap的键类型不同,WeakHashMap的键是弱键

 

在对各个实现类进行详细之前,先来看看各个接口和抽象类的大致介绍。内容包括:
1 Map
2 Map.Entry
3 AbstractMap
4 SortedMap
5 NavigableMap
6 Dictionary

转载请注明出处:http://www.cnblogs.com/skywang12345/p/3308931.html

 

1 Map

Map的定义如下:

public interface Map<K,V> { }

Map 是一个键值对(key-value)映射接口。Map映射中不能包含重复的键;每个键最多只能映射到一个值
Map 接口提供三种collection 视图,允许以键集值集-值映射关系集的形式查看某个映射的内容。
Map 映射顺序。有些实现类,可以明确保证其顺序,如 TreeMap;另一些映射实现则不保证顺序,如 HashMap 类。
Map 的实现类应该提供2标准的构造方法:第一个,void(无参数)构造方法,用于创建空映射第二个,带有单个 Map 类型参数的构造方法,用于创建一个与其参数具有相同键-值映射关系的新映射。实际上,后一个构造方法允许用户复制任意映射,生成所需类的一个等价映射。尽管无法强制执行此建议(因为接口不能包含构造方法),但是 JDK 中所有通用的映射实现都遵从它。


MapAPI

Java 集合系列

abstract void                 clear()

abstract boolean              containsKey(Object key)

abstract boolean              containsValue(Object value)

abstract Set<Entry<K, V>>     entrySet()

abstract boolean              equals(Object object)

abstract V                    get(Object key)

abstract int                  hashCode()

abstract boolean              isEmpty()

abstract Set<K>               keySet()

abstract V                    put(K key, V value)

abstract void                 putAll(Map<? extends K, ? extends V> map)

abstract V                    remove(Object key)

abstract int                  size()

abstract Collection<V>        values()

Java 集合系列

说明
(01) Map提供接口分别用于返回 键集、值集或键-值映射关系集。
        entrySet()用于返回-值集Set集合
        keySet()用于返回键集Set集合
       values()用户返回值集Collection集合
       因为Map中不能包含重复的键;每个键最多只能映射到一个值。所以,-值集、键集都是Set,值集时Collection

(02) Map提供了-值对根据键获取值删除键获取容量大小等方法。

 

2 Map.Entry

Map.Entry的定义如下:

interface Entry<K,V> { }

Map.EntryMap中内部的一个接口,Map.Entry键值对Map通过 entrySet() 获取Map.Entry的键值对集合,从而通过该集合实现对键值对的操作。

Map.EntryAPI

abstract boolean     equals(Object object)

abstract K             getKey()

abstract V             getValue()

abstract int         hashCode()

abstract V             setValue(V object)

 

3 AbstractMap

AbstractMap的定义如下:

public abstract class AbstractMap<K,V> implements Map<K,V> {}

AbstractMap类提供 Map 接口的骨干实现,以最大限度地减少实现此接口所需的工作。
要实现不可修改的映射,编程人员只需扩展此类并提供 entrySet 方法的实现即可,该方法将返回映射的映射关系 set 视图。通常,返回的 set 将依次在 AbstractSet 上实现。此 set 不支持 add() remove() 方法,其迭代器也不支持 remove() 方法。

要实现可修改的映射,编程人员必须另外重写此类的 put 方法(否则将抛出 UnsupportedOperationException),entrySet().iterator() 返回的迭代器也必须另外实现其 remove 方法。

 

AbstractMapAPI

Java 集合系列

abstract Set<Entry<K, V>>     entrySet()

         void                 clear()

         boolean              containsKey(Object key)

         boolean              containsValue(Object value)

         boolean              equals(Object object)

         V                    get(Object key)

         int                  hashCode()

         boolean              isEmpty()

         Set<K>               keySet()

         V                    put(K key, V value)

         void                 putAll(Map<? extends K, ? extends V> map)

         V                    remove(Object key)

         int                  size()

         String               toString()

         Collection<V>        values()

         Object               clone()

Java 集合系列

 

4 SortedMap

SortedMap的定义如下:

public interface SortedMap<K,V> extends Map<K,V> { }

SortedMap是一个继承于Map接口的接口。它是一个有序的SortedMap键值映射。
SortedMap的排序方式有两种:自然排序 或者 用户指定比较器 插入有序 SortedMap 的所有元素都必须实现 Comparable 接口(或者被指定的比较器所接受)。

另外,所有SortedMap 实现类都应该提供 4 标准构造方法:
(01) void(无参数)构造方法,它创建一个空的有序映射,按照键的自然顺序进行排序。
(02) 带有一个 Comparator 类型参数的构造方法,它创建一个空的有序映射,根据指定的比较器进行排序。
(03) 带有一个 Map 类型参数的构造方法,它创建一个新的有序映射,其键-值映射关系与参数相同,按照键的自然顺序进行排序。
(04) 带有一个 SortedMap 类型参数的构造方法,它创建一个新的有序映射,其键-值映射关系和排序方法与输入的有序映射相同。无法保证强制实施此建议,因为接口不能包含构造方法。

 

SortedMapAPI

Java 集合系列

// 继承于Map的API

abstract void                 clear()

abstract boolean              containsKey(Object key)

abstract boolean              containsValue(Object value)

abstract Set<Entry<K, V>>     entrySet()

abstract boolean              equals(Object object)

abstract V                    get(Object key)

abstract int                  hashCode()

abstract boolean              isEmpty()

abstract Set<K>               keySet()

abstract V                    put(K key, V value)

abstract void                 putAll(Map<? extends K, ? extends V> map)

abstract V                    remove(Object key)

abstract int                  size()

abstract Collection<V>        values()

// SortedMap新增的API

abstract Comparator<? super K>     comparator()

abstract K                         firstKey()

abstract SortedMap<K, V>           headMap(K endKey)

abstract K                         lastKey()

abstract SortedMap<K, V>           subMap(K startKey, K endKey)

abstract SortedMap<K, V>           tailMap(K startKey)

Java 集合系列

 

5 NavigableMap

NavigableMap的定义如下:

public interface NavigableMap<K,V> extends SortedMap<K,V> { }

NavigableMap是继承于SortedMap的接口。它是一个可导航的键-值对集合,具有了为给定搜索目标报告最接近匹配项的导航方法。
NavigableMap分别提供了获取-值对键集-值对集的相关方法。

 

NavigableMapAPI

Java 集合系列

abstract Entry<K, V>             ceilingEntry(K key)

abstract Entry<K, V>             firstEntry()

abstract Entry<K, V>             floorEntry(K key)

abstract Entry<K, V>             higherEntry(K key)

abstract Entry<K, V>             lastEntry()

abstract Entry<K, V>             lowerEntry(K key)

abstract Entry<K, V>             pollFirstEntry()

abstract Entry<K, V>             pollLastEntry()

abstract K                       ceilingKey(K key)

abstract K                       floorKey(K key)

abstract K                       higherKey(K key)

abstract K                       lowerKey(K key)

abstract NavigableSet<K>         descendingKeySet()

abstract NavigableSet<K>         navigableKeySet()

abstract NavigableMap<K, V>      descendingMap()

abstract NavigableMap<K, V>      headMap(K toKey, boolean inclusive)

abstract SortedMap<K, V>         headMap(K toKey)

abstract SortedMap<K, V>         subMap(K fromKey, K toKey)

abstract NavigableMap<K, V>      subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive)

abstract SortedMap<K, V>         tailMap(K fromKey)

abstract NavigableMap<K, V>      tailMap(K fromKey, boolean inclusive)

Java 集合系列

说明

NavigableMap除了继承SortedMap的特性外,它的提供的功能可以分为4类:
1类,提供操作键-值对的方法。
               lowerEntryfloorEntryceilingEntry higherEntry 方法,它们分别返回与小于、小于等于、大于等于、大于给定键的键关联的 Map.Entry 对象。
               firstEntrypollFirstEntrylastEntry pollLastEntry 方法,它们返回和/或移除最小和最大的映射关系(如果存在),否则返回 null
2类,提供操作键的方法。这个和第1类比较类似
               lowerKeyfloorKeyceilingKey higherKey 方法,它们分别返回与小于、小于等于、大于等于、大于给定键的键。
3类,获取键集。
              navigableKeySetdescendingKeySet分别获取正序/反序的键集。
4类,获取键-值对的子集。

 

6 Dictionary

Dictionary的定义如下:

public abstract class Dictionary<K,V> {}

NavigableMapJDK 1.0定义的键值对的接口,它也包括了操作键值对的基本函数。


DictionaryAPI

Java 集合系列

abstract Enumeration<V>     elements()

abstract V                  get(Object key)

abstract boolean            isEmpty()

abstract Enumeration<K>     keys()

abstract V                  put(K key, V value)

abstract V                  remove(Object key)

abstract int                size()

 

 

第1部分 HashMap介绍

HashMap简介

HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。
HashMap 继承于AbstractMap,实现了MapCloneablejava.io.Serializable接口。
HashMap 的实现不是同步的,这意味着它不是线程安全的。它的keyvalue都可以为null。此外,HashMap中的映射不是有序的。

HashMap 的实例有两个参数影响其性能:初始容量加载因子。容量 是哈希表中桶的数量,初始容量 只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。
通常,默认加载因子是 0.75, 这是在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查询成本(在大多数 HashMap 类的操作中,包括 get put 操作,都反映了这一点)。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少 rehash 操作次数。如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作。

说明:

在详细介绍HashMap的代码之前,我们需要了解:HashMap就是一个散列表,它是通过拉链法解决哈希冲突的
还需要再补充说明的一点是影响HashMap性能的有两个参数:初始容量(initialCapacity) 加载因子(loadFactor)。容量 是哈希表中桶的数量,初始容量只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。

第1部分 Hashtable介绍

Hashtable 简介

HashMap一样,Hashtable 也是一个散列表,它存储的内容是键值对(key-value)映射
Hashtable 继承于Dictionary,实现了MapCloneablejava.io.Serializable接口。
Hashtable 的函数都是同步的,这意味着它是线程安全的。它的keyvalue都不可以为null。此外,Hashtable中的映射不是有序的。

Hashtable 的实例有两个参数影响其性能:初始容量  加载因子。容量 是哈希表中桶 的数量,初始容量 就是哈希表创建时的容量。注意,哈希表的状态为 open:在发生哈希冲突的情况下,单个桶会存储多个条目,这些条目必须按顺序搜索。加载因子 是对哈希表在其容量自动增加之前可以达到多满的一个尺度。初始容量和加载因子这两个参数只是对该实现的提示。关于何时以及是否调用 rehash 方法的具体细节则依赖于该实现。
通常,默认加载因子是 0.75, 这是在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查找某个条目的时间(在大多数 Hashtable 操作中,包括 get put 操作,都反映了这一点)。

说明在详细介绍Hashtable的代码之前,我们需要了解:和Hashmap一样,Hashtable也是一个散列表,它也是通过拉链法解决哈希冲突的。

 

第1部分 TreeMap介绍

TreeMap 简介

TreeMap 是一个有序的key-value集合,它是通过红黑树实现的。
TreeMap 继承于AbstractMap,所以它是一个Map,即一个key-value集合。
TreeMap 实现了NavigableMap接口,意味着它支持一系列的导航方法。比如返回有序的key集合。
TreeMap 实现了Cloneable接口,意味着它能被克隆
TreeMap 实现了java.io.Serializable接口,意味着它支持序列化

TreeMap基于红黑树(Red-Black tree)实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。
TreeMap的基本操作 containsKeygetput remove 的时间复杂度是 log(n)
另外,TreeMap非同步的。 它的iterator 方法返回的迭代器是fail-fastl的。

说明:

在详细介绍TreeMap的代码之前,我们先建立一个整体概念。
TreeMap是通过红黑树实现的,TreeMap存储的是key-value键值对,TreeMap的排序是基于对key的排序。
TreeMap提供了操作“key”“key-value”“value”等方法,也提供了对TreeMap这颗树进行整体操作的方法,如获取子树、反向树。
后面的解说内容分为几部分,
首先,介绍TreeMap的核心,即红黑树相关部分
然后,介绍TreeMap的主要函数
再次,介绍TreeMap实现的几个接口
最后,补充介绍TreeMap的其它内容

TreeMap本质上是一颗红黑树。要彻底理解TreeMap,建议读者先理解红黑树。关于红黑树的原理,可以参考:红黑树() 原理和算法详细介绍

 

第1部分 WeakHashMap介绍

WeakHashMap简介

    WeakHashMap 继承于AbstractMap,实现了Map接口。
    HashMap一样,WeakHashMap 也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以是null
   不过WeakHashMap键是弱键。在 WeakHashMap 中,当某个键不再正常使用时,会被从WeakHashMap中被自动移除。更精确地说,对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。某个键被终止时,它对应的键值对也就从映射中有效地移除了。
    这个弱键的原理呢?大致上就是,通过WeakReferenceReferenceQueue实现的 WeakHashMapkey弱键,即是WeakReference类型的;ReferenceQueue是一个队列,它会保存被GC回收的弱键。实现步骤是:
    (01) 新建WeakHashMap,将键值对添加到WeakHashMap中。
           实际上,WeakHashMap是通过数组table保存Entry(键值对);每一个Entry实际上是一个单向链表,即Entry是键值对链表。
   (02) 弱键不再被其它对象引用,并GC回收时。在GC回收该弱键时,这个弱键也同时会被添加到ReferenceQueue(queue)队列中。
   (03) 当下一次我们需要操作WeakHashMap时,会先同步tablequeuetable中保存了全部的键值对,而queue中保存被GC回收的键值对;同步它们,就是删除table中被GC回收的键值对
   这就是弱键如何被自动从WeakHashMap中删除的步骤了。

HashMap一样,WeakHashMap是不同步的。可以使用 Collections.synchronizedMap 方法来构造同步的 WeakHashMap

 

说明WeakHashMapHashMap都是通过"拉链法"实现的散列表。它们的源码绝大部分内容都一样,这里就只是对它们不同的部分就是说明。

    WeakReference弱键实现的哈希表。它这个弱键的目的就是:实现对键值对的动态回收。当弱键不再被使用到时,GC会回收它,WeakReference也会将弱键对应的键值对删除。
    “弱键是一个弱引用(WeakReference)”,在Java中,WeakReferenceReferenceQueue 是联合使用的。在WeakHashMap中亦是如此:如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。 接着,WeakHashMap会根据引用队列,来删除“WeakHashMap中已被GC回收的弱键对应的键值对
    另外,理解上面思想的重点是通过 expungeStaleEntries() 函数去理解。

 

Java 集合系列14 Map总结(HashMap, Hashtable, TreeMap, WeakHashMap等使用场景)

 

概要

学完了Map的全部内容,我们再回头开开Map的框架图。

Java 集合系列

 

本章内容包括:
1部分 Map概括
2部分 HashMapHashtable异同
3部分 HashMapWeakHashMap异同

转载请注明出处:http://www.cnblogs.com/skywang12345/admin/EditPosts.aspx?postid=3311126

 

第1部分 Map概括

(01) Map 键值对映射的抽象接口。
(02) AbstractMap 实现了Map中的绝大部分函数接口。它减少了“Map的实现类的重复编码。
(03) SortedMap 有序的键值对映射接口。
(04) NavigableMap 是继承于SortedMap的,支持导航函数的接口。
(05) HashMap, Hashtable, TreeMap, WeakHashMap4个类是键值对映射的实现类。它们各有区别!

  HashMap 是基于拉链法实现的散列表。一般用于单线程程序中。
  Hashtable 也是基于拉链法实现的散列表。它一般用于多线程程序中。
  WeakHashMap 也是基于拉链法实现的散列表,它一般也用于单线程程序中。相比HashMapWeakHashMap中的键是弱键,当弱键GC回收时,它对应的键值对也会被从WeakHashMap中删除;而HashMap中的键是强键。
  TreeMap 是有序的散列表,它是通过红黑树实现的。它一般用于单线程中存储有序的映射。

 

第2部分 HashMap和Hashtable异同

2.1部分 HashMapHashtable的相同点

HashMapHashtable都是存储键值对(key-value)”的散列表,而且都是采用拉链法实现的。
存储的思想都是:通过table数组存储,数组的每一个元素都是一个Entry;而一个Entry就是一个单向链表Entry链表中的每一个节点就保存了key-value键值对数据

添加key-value键值对:首先,根据key值计算出哈希值,再计算出数组索引(即,该key-valuetable中的索引)。然后,根据数组索引找到Entry(即,单向链表),再遍历单向链表,将key和链表中的每一个节点的key进行对比。若key已经存在Entry链表中,则用该value值取代旧的value值;若key不存在Entry链表中,则新建一个key-value节点,并将该节点插入Entry链表的表头位置。
删除key-value键值对:删除键值对,相比于添加键值对来说,简单很多。首先,还是根据key计算出哈希值,再计算出数组索引(即,该key-valuetable中的索引)。然后,根据索引找出Entry(即,单向链表)。若节点key-value存在与链表Entry中,则删除链表中的节点即可。


上面介绍了HashMapHashtable的相同点。正是由于它们都是散列表,我们关注更多的是它们的区别,以及它们分别适合在什么情况下使用。那接下来,我们先看看它们的区别。

 

2.2部分 HashMapHashtable的不同点

1 继承和实现方式不同

HashMap 继承于AbstractMap,实现了MapCloneablejava.io.Serializable接口。
Hashtable 继承于Dictionary,实现了MapCloneablejava.io.Serializable接口。

HashMap的定义:

public class HashMap<K,V>

    extends AbstractMap<K,V>

    implements Map<K,V>, Cloneable, Serializable { ... }

Hashtable的定义:

public class Hashtable<K,V>

    extends Dictionary<K,V>

    implements Map<K,V>, Cloneable, java.io.Serializable { ... }

从中,我们可以看出:
1.1 HashMapHashtable都实现了MapCloneablejava.io.Serializable接口。
      实现了Map接口,意味着它们都支持key-value键值对操作。支持添加key-value键值对获取key”获取value”获取map大小清空map”等基本的key-value键值对操作。
      实现了Cloneable接口,意味着它能被克隆。
      实现了java.io.Serializable接口,意味着它们支持序列化,能通过序列化去传输。

1.2 HashMap继承于AbstractMap,而Hashtable继承于Dictionary
      Dictionary是一个抽象类,它直接继承于Object类,没有实现任何接口。Dictionary类是JDK 1.0的引入的。虽然Dictionary也支持添加key-value键值对获取value”获取大小等基本操作,但它的API函数比Map少;而且             Dictionary一般是通过Enumeration(枚举类)去遍历,Map则是通过Iterator(迭代器)去遍历。 然而由于Hashtable也实现了Map接口,所以,它即支持Enumeration遍历,也支持Iterator遍历。关于这点,后面还会进一步说明。
      AbstractMap是一个抽象类,它实现了Map接口的绝大部分API函数;为Map的具体实现类提供了极大的便利。它是JDK 1.2新增的类。

 

2 线程安全不同

Hashtable的几乎所有函数都是同步的,即它是线程安全的,支持多线程。
HashMap的函数则是非同步的,它不是线程安全的。若要在多线程中使用HashMap,需要我们额外的进行同步处理。 HashMap的同步处理可以使用Collections类提供的synchronizedMap静态方法,或者直接使用JDK 5.0之后提供的java.util.concurrent包里的ConcurrentHashMap类。


3 null值的处理不同

HashMapkeyvalue都可以为null
Hashtablekeyvalue都不可以为null

我们先看看HashMapHashtable “添加key-value”的方法

HashMap的添加key-value的方法

Java 集合系列 View Code

Hashtable的添加key-value的方法

Java 集合系列 View Code

根据上面的代码,我们可以看出:

Hashtablekeyvalue,都不能为null否则,会抛出异常NullPointerException
HashMapkeyvalue都可以为null HashMapkeynull时,HashMap会将其固定的插入table[0]位置(HashMap散列表的第一个位置);而且table[0]处只会容纳一个keynull的值,当有多个keynull的值插入的时候,table[0]会保留最后插入的value

 

4 支持的遍历种类不同

HashMap只支持Iterator(迭代器)遍历。
Hashtable支持Iterator(迭代器)Enumeration(枚举器)两种方式遍历。

Enumeration JDK 1.0添加的接口,只有hasMoreElements(), nextElement() 两个API接口,不能通过Enumeration()对元素进行修改
Iterator JDK 1.2才添加的接口,支持hasNext(), next(), remove() 三个API接口。HashMap也是JDK 1.2版本才添加的,所以用Iterator取代EnumerationHashMap只支持Iterator遍历。

 

5 通过Iterator迭代器遍历时,遍历的顺序不同

HashMap从前向后的遍历数组;再对数组具体某一项对应的链表,从表头开始进行遍历。
Hashtabl从后往前的遍历数组;再对数组具体某一项对应的链表,从表头开始进行遍历。

HashMapHashtable都实现Map接口,所以支持获取它们“key的集合“value的集合“key-value的集合,然后通过Iterator对这些集合进行遍历。
由于“key的集合“value的集合“key-value的集合的遍历原理都是一样的;下面,我以遍历“key-value的集合来进行说明。

HashMap Hashtable 遍历"key-value集合"的方式是:(01) 通过entrySet()获取“Map.Entry集合 (02) 通过iterator()获取“Map.Entry集合的迭代器,再进行遍历。

HashMap的实现方式:先从前向后的遍历数组;对数组具体某一项对应的链表,则从表头开始往后遍历。

Java 集合系列 View Code

Hashtable的实现方式:先从后向往前的遍历数组;对数组具体某一项对应的链表,则从表头开始往后遍历。

Java 集合系列 View Code

 

6 容量的初始值 增加方式都不一样

HashMap默认的容量大小是16;增加容量时,每次将容量变为原始容量x2”
Hashtable默认的容量大小是11;增加容量时,每次将容量变为原始容量x2 + 1

HashMap默认的加载因子0.75, 默认的容量大小是16

Java 集合系列 View Code

HashMap实际容量” >= “阈值时,(阈值 = 总的容量 * 加载因子),就将HashMap的容量翻倍。

Java 集合系列 View Code

Hashtable默认的加载因子0.75, 默认的容量大小是11 

Java 集合系列 View Code

Hashtable实际容量” >= “阈值时,(阈值 = 总的容量 x 加载因子),就将变为原始容量x2 + 1”

Java 集合系列 View Code

 

7 添加key-value时的hash值算法不同

HashMap添加元素时,是使用自定义的哈希算法。
Hashtable没有自定义哈希算法,而直接采用的keyhashCode()

HashMap添加元素时,是使用自定义的哈希算法。

Java 集合系列 View Code 

Hashtable没有自定义哈希算法,而直接采用的keyhashCode()

Java 集合系列 View Code

 

8 部分API不同

Hashtable支持contains(Object value)方法,而且重写了toString()方法
HashMap不支持contains(Object value)方法,没有重写toString()方法。


最后,再说说“HashMapHashtable”使用的情景。
其实,若了解它们之间的不同之处后,可以很容易的区分根据情况进行取舍。例如:(01) 若在单线程中,我们往往会选择HashMap;而在多线程中,则会选择Hashtable(02),若不能插入null元素,则选择Hashtable;否则,可以选择HashMap
但这个不是绝对的标准。例如,在多线程中,我们可以自己对HashMap进行同步,也可以选择ConcurrentHashMap。当HashMapHashtable都不能满足自己的需求时,还可以考虑新定义一个类,继承或重新实现散列表;当然,一般情况下是不需要的了。

 

第3部分 HashMap和WeakHashMap异同

3.1 HashMapWeakHashMap的相同点

1 它们都是散列表,存储的是键值对映射。
2 它们都继承于AbstractMap,并且实现Map基础。
3 它们的构造函数都一样。
   它们都包括4个构造函数,而且函数的参数都一样。
4 默认的容量大小是16,默认的加载因子是0.75
5 它们的都允许为null
6 它们都是非同步的

 

3.2 HashMapWeakHashMap的不同点

HashMap实现了CloneableSerializable接口,而WeakHashMap没有。
   HashMap实现Cloneable,意味着它能通过clone()克隆自己。
   HashMap实现Serializable,意味着它支持序列化,能通过序列化去传输。

HashMap强引用(StrongReference)”,而WeakHashMap的键是弱引用(WeakReference)”
   WeakReference弱键能实现WeakReference键值对的动态回收。当弱键不再被使用到时,GC会回收它,WeakReference也会将弱键对应的键值对删除。
   这个弱键实现的动态回收键值对的原理呢?其实,通过WeakReference(弱引用)ReferenceQueue(引用队列)实现的。 首先,我们需要了解WeakHashMap中:
    第一,WeakReference,即key是弱键。
    第二,ReferenceQueue是一个引用队列,它是和WeakHashMap联合使用的。当弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。 WeakHashMap中的ReferenceQueuequeue
   第三,WeakHashMap是通过数组实现的,我们假设这个数组是table
 

接下来,说说动态回收的步骤。

(01) 新建WeakHashMap,将键值对添加到WeakHashMap中。
        键值对添加到WeakHashMap中时,添加的键都是弱键。
        实际上,WeakHashMap是通过数组table保存Entry(键值对);每一个Entry实际上是一个单向链表,即Entry是键值对链表。
(02) 当某弱键不再被其它对象引用,并被GC回收时。在GC回收该弱键时,这个弱键也同时会被添加到queue队列中。
        例如,当我们在将弱键”key添加到WeakHashMap之后;后来将key设为null。这时,便没有外部外部对象再引用该了key
        接着,当Java虚拟机的GC回收内存时,会回收key的相关内存;同时,将key添加到queue队列中。
(03) 当下一次我们需要操作WeakHashMap时,会先同步tablequeuetable中保存了全部的键值对,而queue中保存被GC回收的弱键;同步它们,就是删除table中被GC回收的弱键对应的键值对。
        例如,当我们读取WeakHashMap中的元素或获取WeakReference的大小时,它会先同步tablequeue,目的是删除table中被GC回收的弱键对应的键值对。删除的方法就是逐个比较“table中元素的queue中的’”,若它们相当,则删除“table中的该键值对

 

3.3 HashMapWeakHashMap的比较测试程序

Java 集合系列 View Code

运行结果:

Java 集合系列

 -- HashMap --

map entry : H2 - h2

  map size:1

 

 -- WeakHashMap --

map entry : W2 - w2

  map size:1

 

 -- Self-def HashMap --

map entry : [email protected] - s4

  map size:1

 

 -- Self-def WeakHashMap --

GC Self: id=10 [email protected])

map entry : [email protected] - s2

  map size:1

 

 

Java 集合系列15 Set架构

 

前面,我们已经系统的对ListMap进行了学习。接下来,我们开始可以学习Set。相信经过Map的了解之后,学习Set会容易很多。毕竟,Set的实现类都是基于Map来实现的(HashSet是通过HashMap实现的,TreeSet是通过TreeMap实现的)

首先,我们看看Set架构。

Java 集合系列

 

(01) Set 是继承于Collection的接口。它是一个不允许有重复元素的集合。
(02) AbstractSet 是一个抽象类,它继承于AbstractCollectionAbstractCollection实现了Set中的绝大部分函数,为Set的实现类提供了便利。
(03) HastSet TreeSet Set的两个实现类。
        HashSet依赖于HashMap,它实际上是通过HashMap实现的。HashSet中的元素是无序的。
        TreeSet依赖于TreeMap,它实际上是通过TreeMap实现的。TreeSet中的元素是有序的。

 

第1部分 HashSet介绍

HashSet 简介

HashSet 是一个没有重复元素的集合
它是由HashMap实现的,不保证元素的顺序,而且HashSet允许使用 null 元素
HashSet非同步的。如果多个线程同时访问一个哈希 set,而其中至少一个线程修改了该 set,那么它必须 保持外部同步。这通常是通过对自然封装该 set 的对象执行同步操作来完成的。如果不存在这样的对象,则应该使用 Collections.synchronizedSet 方法来包装” set。最好在创建时完成这一操作,以防止对该 set 进行意外的不同步访问:

Set s = Collections.synchronizedSet(new HashSet(...));

HashSet通过iterator()返回的迭代器是fail-fast的。

 

说明 HashSet的代码实际上非常简单,通过上面的注释应该很能够看懂。它是通过HashMap实现的,若对HashSet的理解有困难,建议先学习以下HashMap;学完HashMap之后,在学习HashSet就非常容易了。

第1部分 TreeSet介绍

TreeSet简介

TreeSet 是一个有序的集合,它的作用是提供有序的Set集合。它继承于AbstractSet抽象类,实现了NavigableSet<E>, Cloneable, java.io.Serializable接口。
TreeSet 继承于AbstractSet,所以它是一个Set集合,具有Set的属性和方法。
TreeSet 实现了NavigableSet接口,意味着它支持一系列的导航方法。比如查找与指定目标最匹配项。
TreeSet 实现了Cloneable接口,意味着它能被克隆。
TreeSet 实现了java.io.Serializable接口,意味着它支持序列化。

TreeSet是基于TreeMap实现的。TreeSet中的元素支持2种排序方式:自然排序 或者 根据创建TreeSet 时提供的 Comparator 进行排序。这取决于使用的构造方法。
TreeSet为基本操作(addremove contains)提供受保证的 log(n) 时间开销。
另外,TreeSet是非同步的。 它的iterator 方法返回的迭代器是fail-fast的。

总结

(01) TreeSet实际上是TreeMap实现的。当我们构造TreeSet时;若使用不带参数的构造函数,则TreeSet的使用自然比较器;若用户需要使用自定义的比较器,则需要使用带比较器的参数。
(02) TreeSet是非线程安全的。
(03) TreeSet实现java.io.Serializable的方式。当写入到输出流时,依次写入比较器、容量、全部元素;当读出输入流时,再依次读取。

 

Java 集合系列18 IteratorEnumeration比较

 

概要

这一章,我们对IteratorEnumeration进行比较学习。内容包括:
1部分 IteratorEnumeration区别
2部分 IteratorEnumeration实例

转载请注明出处:http://www.cnblogs.com/skywang12345/admin/EditPosts.aspx?postid=3311275

 

第1部分 Iterator和Enumeration区别

Java集合中,我们通常都通过 “Iterator(迭代器)” “Enumeration(枚举类)” 去遍历集合。今天,我们就一起学习一下它们之间到底有什么区别。

我们先看看 Enumeration.java Iterator.java的源码,再说它们的区别。

Enumeration是一个接口,它的源码如下:

Java 集合系列

package java.util;

 

public interface Enumeration<E> {

 

    boolean hasMoreElements();

 

    E nextElement();

}

Java 集合系列

Iterator也是一个接口,它的源码如下:

Java 集合系列

package java.util;

 

public interface Iterator<E> {

    boolean hasNext();

 

    E next();

 

    void remove();

}

Java 集合系列

看完代码了,我们再来说说它们之间的区别。

(01) 函数接口不同
        Enumeration只有2个函数接口。通过Enumeration,我们只能读取集合的数据,而不能对数据进行修改。
        Iterator只有3个函数接口。Iterator除了能读取集合的数据之外,也能数据进行删除操作。

(02) Iterator支持fail-fast机制,而Enumeration不支持。
        Enumeration JDK 1.0添加的接口。使用到它的函数包括VectorHashtable等类,这些类都是JDK 1.0中加入的,Enumeration存在的目的就是为它们提供遍历接口。Enumeration本身并没有支持同步,而在VectorHashtable实现Enumeration时,添加了同步。
        Iterator JDK 1.2才添加的接口,它也是为了HashMapArrayList等集合提供遍历接口。Iterator是支持fail-fast机制的:当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。

 

 

转载于:https://my.oschina.net/905430/blog/3052678

相关文章: