数组将数字与对象联系起来。它保存类型明确的对象,查询对象时,不需要对结果做类型转换。它可以是多维的,可以保存基本类型的数据。但是数组一旦生成,其容量就不能改变。

4.[Think in Java笔记]容器深入研究_第1张图片

1.Set和存储顺序

Set(Interface)

存入Set的每个元素都必须是为唯一的,因为Set不保存重复元素。加入Set的元素必须定义equals()方法以确保对象的唯一性。Set与Collection有完全一样的接口。Set接口不保证维护元素的次序

HashSet(非线程安全)

为快速查找而设计的Set。存入HashSet的元素必须定义hashCode()

TreeSet(非线程安全)

保持次序的Set,底层为树结构。使用它可以从Set中提取有序的序列。元素必须实现Comparable接口

LinkedHashSet(非线程安全)

具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入顺序),于是在使用迭代器遍历Set时,结果会按元素插入的次序显示。元素也必须定义hashCode()方法

SortedSet

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

2.队列

Queue在J2SM中仅有连个实现:LinkedList和PriorityQueue,它们的差异在于排序行为而不是性能。

优先级队列

该列表中每个对象都包含一个字符串和一个主要的以及次要的优先级值。该列表的排序顺序也是通过实现Comparable而进行控制的。

双向队列

双向队列可以在任何一端添加或移除元素。在LinkedList中包含支持双向队列的方法,但Java标准库中没有任何显式的用于双向队列的接口。

3.Map

映射表(也称关联数组)的基本思想是它维护的是键-值(对)关联,因此可以使用键值来查找值。

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

性能是映射表中的一个重要问题。HashMap使用了特殊的值,称作散列码,来取代对键的缓慢搜索。散列码是“相对唯一”的、用以代表对象的int值,它是通过将该对象的某些信息进行转换而生成的。hashCode()是根类Object中的方法,因此所有Java对象都能产生散列码。HashMap是使用对象的hashCode()进行快速查询的。

HashMap(非线程安全)

Map基于散列表的实现(它取代了HashTable),插入和查询“键值对”的开销是固定的。可以通过构造器设置容器和负载因子,已调整容器的性能

LinkedHashMap(非线程安全)

类似HashMap,但是迭代器遍历它时,取得“键值对”的顺序是插入次序,或者是最近最少使用的次序,执笔HashMap慢一点;而在迭代访问时反而更快,因为它使用链表维护内部次序。

TreeMap(非线程安全)

基于红黑树的实现。查看“键”或“键值对”时,它们会被排序(次序由Comparable或Comparator决定),TreeMap的特点在于所得到的结果是经过排序的。TreeMap视为一个带有subMap()方法的Map,它可以返回一个子树。

WeakHashMap

弱键(weak key)映射,允许释放映射所指向的对象,这是为解决某些类特殊问题而设计的。如果映射之外没有引用指向某个“键”,则此“键”可以被垃圾收集器收集。

ConcurrentHashMap(线程安全)

一种线程安全的Map,他不涉及同步枷锁。

IdentityHashMap

使用==代替equals()对“键”进行比较的散列映射。专为解决特殊问题而设计的。

SortedMap

使用SortedMap(TreeMap是其现阶段的唯一实现)可以确保键处于排序状态。

4.散列与散列码

HashMap使用equals()方法判断检索键是否与表中存在的键相同。而默认的Object.equals()只是比较对象的地址,因此,要使用自己的类作为HashMap的键,必须同时重载hashCode()和equals()方法。

理解hashCode()

如果不为你的键覆盖hashCode()和equals(),那么使用散列数据结构就无法正确处理你的键。

首先,使用散列的目的在于:想要使用一个对象来查找另一个对象。

其次,散列的价值在于速度:散列使得查询得以快速进行。

存储一组元素最快的数据结构是数组,所以使用它来标示键信息。但由于数组不能调整容量,所以数组并不保存键本身,而是通过键对象生成一个数字,将其作为数组的下标。这个数字就是散列码,由定义在Object中,或可能由你的类覆盖的hashCode()方法(散列函数)生成。

为了解决数组容量被固定的问题,不同的键可以产生相同的下标,即会有冲突。于是查询一个值的过程首先就是计算散列码,然后使用散列码查询数组。

散列表中的“槽位”(slot)通常称为桶位(bucket),因此散列表数组命名为bucket。

为了使散列分布均匀,桶的数量通常使用质数。

覆盖hashCode()

首先,你无法控制bucket数组的下标值的产生。这个值依赖于具体的HashMap对象的容量,而容量的改变/与容量的充满程度和负载因子有关。

hashCode()生成的结果,经过处理后成为桶位的下标。

设计hashCode()时,无论何时,对同一个对象调用hashCode()都应该生成同样的值。

5.选择接口

实际上只有四中容器:Map、List、Set和Queue,但每种接口都不止一种实现版本。

Hashtable、Vector和Stack的特性:它们是过去遗留下来的类,目的是为了支持老的程序,最好不要在新程序中使用它们。

容器之间的区别:所使用的接口是有什么样的数据结构实现的。如ArrayList和LinkedList都实现了List接口,但无论选择哪一个,基本的List操作都是相同的。然而,ArrayList是由数组实现的,LinkedList是由双向链表实现的。

对List的选择:ArrayList和LinkedList

对于ArrayList,无论列表大小如何,访问都很快速和一致;对于LinkedList,访问时间对于较大的列表将明显增加。显然,如果需要执行大量的访问,链表不是一个好的选择。

在LinkedList中插入或移除代价相当低廉,并且不随列表尺寸发生变化,但对于ArrayList,插入操作代价特别高昂,并且其代价将随着列表尺寸的增加而增加。这是因为ArrayList在插入时,必须创建空间并将它的所有引用向前移动,这会随着ArrayList尺寸的增加而产生高昂代价。

通常默认选择是ArrayList,只有需要使用额外的功能,或者当程序的性能应为经常从表中进行插入或删除而变差的时候,采取选择LinkedList。如果使用固定的数量的元素,那么既可以选择ArrayList,也可以选择真正的数组。

对Set的选择:TreeSet、HashSet、LinkedHashSet

HashSet的性能基本上总比TreeSet好,特别是在添加和查询元素时。TreeSet存在的唯一原因是它可以维持元素的排序状态;所以,只有当需要一个排好序的Set时,才应该使用TreeSet,而且用TreeSet迭代通常比HashSet要快。

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

对Map的选择

除了IdentityHashMap,所有的Map实现的插入操作都会随着Map的尺寸变大而显著变慢。但是,查找的代价通常比要入要小得多。

Hashtable的性能大体上与HashMap相当,因为HashMap是用来替代Hashtable的,他们使用了相同的底层存储和查找机制。

TreeMap通常比HashMap要慢,与TreeSet一样,TreeMap是一种创建有序列表的方式。当使用Map时,首选是HashMap,只有在你要求Map始终保持有序时,才需要使用TreeMap。

LinkedHashMap在插入时比HashMap慢一点,因为它维护散列数据结构的同时瑶瑶维护链表(以保持插入顺序),但正是由于这个列表,其迭代速度更快。

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

6.实用方法

List的排序和查询

List排序与查询所使用的方法与对象数组所使用的方法有相同的名字与语法,只是使用了Collections的static方法代替Arrays的方法。

Collection或Map的同步控制

关键字Synchronized是多线程的重要部分。

Java容器有一种保护机制,能够防止多个进程同时修改同一个容器的内容。Java容器类类库采用快速报错(fail-fast)机制,它会探查容器上的任何除了你的进程所进行的操作以外的所有变化,一旦它发现其他进程修改了容器,就会立刻抛出ConcurrentModificationException异常。当程序的不同部分修改了同一个容器时,就可能导致容器的状态不一致,所以,此异常提醒你。

CocurrentHashMap、CopyOnWriteArrayList和CopyOnWriteArraySet都使用了可以避免ConcurrentModificationException的技术。

7.持有对象

WeakHashMap

容器类有一种特殊的Map,即WeakHashMap,它被用来保存WeakReference。在这种映射中,每个值只保存一份实例以节约存储空间。当程序需要那个“值“的时候,便在映射中查找现有的对象,然后使用它(而不是重新再创建),这是一种节约存储空间的技术,因为WeakHashMap允许垃圾回收器自动清理键和值。

同样Key类必须有hashCode()和equals(),因为在散列数据结构中,他被用作键。

WeakReference

java.lang.ref类中包含一组类,这些类在/当存在可能会耗尽内存的大对象的时候/特别有用,有三个继承自抽象类Reference的类:SoftReference、WeakReference、PhantomReferece。当垃圾回收器正在考察的对象只能通过某个Reference对象才“可获得“时,上述这个不同的派生类为垃圾回收器提供了不同级别的间接性指示。

如果一个对象是“可获得的“,垃圾回收器就不能释放它,因为它仍然为你的程序所用。如果一个对象不是“可获得的”,那么你的程序将无法使用它,所以其回收是安全的。

如果想继续持有/对某个对象的引用,希望以后还能访问到该对象,但是也希望能够允许垃圾回收器释放它,这时使用Reference对象。

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



说明:笔记内容摘自《SCJP考试指南》和《Think in Java》

关联:整理了一些Java软件工程师的基础知识点