容器深入研究

1、完整的容器分类法

  • 集合类库更加完备的图,包括抽象类和遗留构件(不包括Queue的实现):

    容器深入研究

  • Java SE5新添加:

         Queue接口及其实现PriorityQueue和各种风格的BlockingQueue。

         ConcurrentMap接口及其实现ConcurrentHashMap,它们是用于多线程机制的。

         CopyOnWriteArrayList和CopyOnWriteArraySet,它们也是用于多线程机制的。

         EnumSet和EnumMap,为使用enum而设计的Set和Map的特殊实现。

         在Collections类中的多个便利方法。

  • 虚线框表示abstract类,它们只是部分实现了特定接口的工具。如果你想创建自己的容器,并不用从接口开始并实现其中的全部方法,只需从abstract继承,然后执行一些创建新类必须的工作。事实上容器类库包含足够多的功能,通常可以忽略以Abstract开头的类。

2、Collection的功能方法

  • 下图列出可以通过Collection执行的所有操作(不包括从Object继承而来的方法)。因此,它们也是可通过Set或List执行的所有操作(List还有额外的功能)。Map不是继承自Collection的,另行介绍。

    容器深入研究

3、可选操作

  • 执行各种不同的添加和移除的方法在Collection接口中都是可选操作。

  • 将方法定义为可选的原因:防止在设计中出现接口爆炸的情况。

  • 未获支持的操作(这种方式)是一种特例,可以延迟到需要时再实现。为了让这种方式能够工作:

         第一,UnsupportedOperationException必须是一种罕见的事件。

         第二,一个操作是未获支持的,那么实现接口时可能就会导致UnsupportedOperationException异常。

  • 未获支持的操作只有在运行时才能探测到,因此它们表示动态类型检查。

  • Arrays.asList()返回固定尺寸的List,其中的值还是可以修改的;而Collections.unmodifiableList()产生不可修改列表,任何情况下都是不可修改的。

4、List的功能方法

  • 大多数时候只是调用add()添加对象,使用get()一次取出一个元素,以及调用iterator()获取用于该序列的Iterator。

5、Set和存储顺序

  • 当你创建自己的类型时,要意识到Set需要一种方式来维护存储顺序,而存储顺序维护方式在Set不同实现之间会有所变化。

  • 不同的Set实现不仅具有不同的行为,而且它们对于可在特定的Set中放置的元素的类型也有不同要求:

    容器深入研究

      注:在HashSet上打星号表示,如没有其他限制,这就是默认选择,它对速度进行了优化。

  • SortedSet中的元素可以保证处于排序状态。

         Comparator comparator():返回当前Set使用的Comparator;或者返回null,表示以自然方式排序。

     容器深入研究

6、队列

  • 除了并发应用,Queue在Java SE5中仅有的两个实现是LinkedList和PriorityQueue,它们的差异在于排序行为而不是性能。

  • 优先级队列

  • 双向队列:可以在任何一端添加或移除元素,在LinkedList中包含支持双向队列的方法,但在Java标准类库中没有任何显式的用于双向队列的接口。由于不太可能在两端都放入元素并抽取它们,因此双向队列不如Queue那样常用。

7、理解Map

  • 标准Java类库中包含了Map的几种基本实现,包括:HashMap、TreeMap、LinkedHashMap、WeakHashMap、ConcurrentHashMap、IdentityHashMap。它们都有同样的基本接口Map,但是行为特性各不相同,这表现在效率、键值对的保存及呈现次序、对象的保存周期、映射表如何在多线程程序中工作和判定“键”等价的策略等方面。

  • 性能是映射表中的一个重要问题。HashMap使用特殊的值,称作散列码,来取代对键的缓慢搜索。散列码是“相对唯一”的、用以代表对象的int值,它是通过将对象的某些信息进行转换而生成的。hashCode()是根类Object中的方法,因此所有接Java对象都能产生散列码。HashMap就是使用对象的hashCode()进行快速查询的,此方法能够显著提高性能。下图是基本的Map实现,在HashMap上打星号表示如没有其他限制,它应该成为你的默认选择,因为它对速度进行了优化。其他实现强调了其他的特性,因此都不如HashMap快。

    容器深入研究

  • 任何键都必须具有一个equals()方法;如果键被用于散列Map,那么它必须还具有恰当的hashCode()方法;如果被用于TreeMap,那么它还必须实现Comparable。

  • 使用SortedMap(TreeMap是其现阶段的唯一实现),可以确保键处于排序状态。SortedMap接口中提供的方法:

    容器深入研究

  • 为了提高效率,LinkedHashMap散列化所有的元素,但在遍历键值对时,却又以元素的插入顺序返回键值对。此外,可以在构造器中设定LinkedHashMap,使之采用基于访问的最近最少使用(LRU)算法,于是没有被访问过的元素就会出现在队列的前面。对于需要定期清理元素以节省空间的程序来说,此功能使得程序很容易得以实现。

8、散列与散列码

  • Object的hashCode()方法生成散列码,它默认是使用对象的地址计算散列码。

  • 正确的equals()方法必须满足下列5个条件:

    容器深入研究

    注:默认的Object.equals()只是比较对象的地址。

  • 如果要使用自己的类作为HashMap的键,必须同时重载hashCode()和equals()。

  • 理解hashCode(),使用散列的目的在于:想要使用一个对象来查找另一个对象。

  • 为速度而散列。散列的价值在于速度:散列使得查询得以快速进行。由于瓶颈位于键的查询速度,因此解决方案之一就是保持键的排序状态,然后使用Collections.binarySearch()进行查询。散列则更进一步,它将键保存在某处,以便能够很快找到。存储一组元素最快的数据结构是数组,所以使用它来表示键的信息。数组不能调整容量,因此数组并不保存键本身。而是通过键对象生成一个数字,将其作为数组的下标,这个数字就是键对象的散列码,由定义在Object中的且可能由你的类覆盖的hashCode()方法生成。不同的键可以产生相同的下标,可能会有冲突。查询一个值的过程首先就是计算散列码,然后使用散列码查询数组。如果能保证没有冲突(值的数量是固定的),那可就有了一个完美的散列函数,但这种情况是特例。通常,冲突由外部链接处理:数组并不直接保存值,而是保存值的List。然后对List中的值使用equals()方法进行线性的查询,这部分查询会比较慢,但如果散列函数好的话,数组每个位置就只有较少的值。因此,不是查询整个List,而是快速地跳到数组的某个位置,只对很少的元素进行比较。这便是HashMap会如此快的原因。

  • 覆盖hashCode()。你无法控制数组的下标值的产生,这个值依赖于具体的HashMap对象的容量,而容量的改变与容器的充满程度和负载因子有关。设计hashCode()时最重要的因素是:无论何时,对同一个对象调用hashCode()都应该生成同样的值。此外,也不应该使hashCode()依赖于具有唯一性的对象信息。

  • 下图是Joshua Bloch给出的一份像样的hashCode()基本指导:

    容器深入研究

9、选择接口的不同实现

  • 尽管实际上只有四种容器:Map、List、Set和Queue,但是每种接口都有不止一个实现版本,每种不同的实现都有各自的特征、优点和缺点。

  • 容器之间的区别通常归结由什么在背后“支持”它们,也就是所使用的接口是由什么样的数据结构实现的。ArrayList底层由数组支持;而LinkedList是由双向链表实现的,其中的每个对象包含数据的同时还包含指向链表中前一个与后一个元素的引用。因此,经常在表中插入或删除元素,LinkedList就比较合适;否则,应该使用速度更快的ArrayList。

  • Set可被实现为TreeSet、HashSet或LinkedHashSet。每一种都有不同的行为:HashSet最常用,查询速度最快;LinkedHashSet保持元素插入的次序;TreeSet基于TreeMap,生成一个总是处于排序状态的Set。

  • 对List的选择:

    容器深入研究

    注:避免使用Vector,最佳做法是将ArrayList作为默认选择,需要额外功能时酌情选择。另外CopyOnWriteArrayList是List的一个特殊实现,专门用于并发编程。

  • 对Set的选择

    容器深入研究

    注:对于插入操作,LinkedHashSet比HashSet代价更高,这是由维护链表所带来的额外开销。

  • 对Map的选择

    容器深入研究

    IdentityHashMap具有完全不同的性能,因为它是使用==而不是equals()来比较元素的。

  • HashMap的性能因子:

         容量:表中的桶位数。

         初始容量:表在创建时所拥有的桶位数。HashMap和HashSet都具有允许指定初始容量的构造器。

         尺寸:表中当前存储的项数。

         负载因子:尺寸/容量。空表的负载因子是0,半满表的负载因子是0.5,依次类推。负载轻的表产生冲突的可能性小,因此对于插入和查找都是最理想的。HashMap和HashSet都具有允许指定负载因子的构造器,表示当负载情况达到该负载因子的水平时,容器将自动增加器容量(桶位数),实现方式是使容量大致加倍,并重新将现有对象分布到新的桶位数中(这被称为再散列)。HashMap默认的负载因子是0.75,这个因子在时间和空间代价之间达到了平衡。如果你知道将要在HashMap中存储多少项,那么创建一个具有恰当大小的初始容量将可以避免自动再散列的开销。

10、实用方法

  • 大量用于容器的方法被表示为java.util.Collections类内部的静态方法。之前看过例如addAll()、reverseOrder()和binarySearch()之类。下面还将介绍另外一部分(synchronized和unmodifiable的实用方法)

    容器深入研究

    注:min()和max()只能作用于Collection对象,而不能作用于List。

  • 设定Collection或Map为不可修改。unmodifiableXXX(),此方法有大量变种,对应于Collection、List、Set和Map。

  • Collection或Map的同步控制。synchronizedXXX(),此方法有大量变种,对应于Collection、List、Set和Map。使用此类方法可能产生ConcurrentModificationException异常(多个进程同时修改同一个容器内容),ConcurrentHashMap、CopyOnWriteArrayList和CopyOnWriteArraySet都使用了可避免ConcurrentModificationException的技术。

11、持有引用

  • java.lang.ref类库包含了一组类,这些类为垃圾回收提供了更大灵活性。当存在可能会耗尽内存的大对象的时候,这些类显得特别有用。有三个继承自抽象类Reference的类:SoftReference、WeakReference和PhantomReference。

  • 对象是可获得的是指此对象可在程序中某处找到。如果一个对象不是“可获得的”,那么你的程序将无法使用到它,所以回收是安全的。

  • 如果想继续持有对某个对象的引用,希望以后还能够访问到该对象,但也希望能够允许垃圾回收器释放该对象,这时就应该使用Reference对象。这样,你可以继续使用该对象,而在内存消耗殆尽时又允许释放该对象。

  • SoftReference、WeakReference和PhantomReference由强到弱排列,对应不同级别的“可获得性”。SoftReference用以实现内存敏感的高速缓存。WeakReference是为实现“规范映射”而设计的,它不妨碍垃圾回收器回收映射的“键”(或“值”),“规范映射”中的对象实例可以在程序的多处被同时使用,以节省存储空间。PhantomReference用以调度回收前的清理工作,它比Java终止机制更灵活。

  • 使用SoftReference和WeakReference时,可以选择是否要将它们放入ReferenceQueue(用作“回收前清理工作”的工具),而PhantomReference只能依赖于ReferenceQueue。

  • WeakHashMap是容器类中一种特殊的Map,它用来保存WeakReference,使得规范映射更易于使用。在这种映射中,每个值只保存一份实例以节省空间。WeakHashMap允许垃圾回收器自动清理键和值,允许清理元素的触发条件是:不再需要此键了。

12、Java 1.0/1.1的容器

  • 本人只讨论BitSet,如果想要高效率地存储大量“开/关”信息,BitSet是很好的选择。不过它的效率仅是对空间而言;如果需要高效的访问时间,BitSet比本地数组稍慢一些。BitSet的最小容量是long:64位,如果存储的内容小,那么BitSet就浪费了很多空间。BitSet在必要时会进行扩充容量。由于EnumSet允许你按照名字而不是数字位的位置进行操作,可以减少错误,因此EnumSet与BitSet相比,通常是一种更好的选择。使用BitSet而不是EnumSet的理由只包括:只有运行时才知道需要多少个标志;对标志命名不合理;需要BitSet中的某种特殊操作。

你可能感兴趣的:(容器深入研究)