Java容器深入研究

容器深入研究

1、完整的容器分类法

                                             

 

2、可选操作

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

  Arrays.asList()会生成一个固定尺寸的List,仅支持那些不会改变数组大小的操作,如set操作。若执行会改变大小的操作,会抛出UnsupportedOperationException异常,例如retainAll()、addAll()等。

  Collections.unmodifiableList()会生成一个不可修改的List,只要你执行任何试图修改容器的操作,都会抛出UnsupportedOperationException异常,例如set()、add()等。

 

3、Set和存储顺序

(1)Set类型

类型说明
Set(interface)

存入Set的每个元素必须是唯一的,因为Set不保存重复元素。

加入Set的元素必须定义equals()方法以确保对象的唯一性。

Set与Collection有完全一样的接口。Set接口不保证维护元素的次序。

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

保持次序的Set,底层为树结构。使用它可以从Set中提取有序的序列。

元素必须实现Comparable接口。

LinkedHashSet

具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序)。

于是在使用迭代器遍历Set时,结果会按元素插入的次序显示。

元素也必须定义hashCode()方法。

 

(2)SortedSet

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

方法说明
Object first() 返回容器中的第一个元素。
Object last() 返回容器中的最末一个元素。
SortedSet subSet(fromElement,toElement) 生成此Set的子集,范围从fromElement(包含)到toElement(不包含)。
SortedSet headSet(toElement) 生成此Set的子集,由小于toElement的元素组成。
SortedSet tailSet(fromElement) 生成此Set的子集,由大于或等于fromElement的元素组成。

 

4、队列

  队列可以将元素从队列的一段插入,并于另一端将它们抽取出来。

(1)优先级队列(PriorityQueue)

  优先级队列会对元素进行排序,排序顺序可以通过实现Comparable而进行控制。

(2)双向队列

  双向队列可以在任何一端添加或移除元素。

 1 public class Deque<T>{
 2     private LinkedList<T> deque = new LinkedList<T>();
 3     public void addFirst(T e){ deque.addFirst(e); }
 4     public void addLast(T e){ deque.addLast(e); }
 5     public T getFirst(){ return deque.getFirst(); }
 6     public T getLast(){ return deque.getLast(); }
 7     public T removeFirst(){ return deque.removeFirst(); }
 8     public T removeLast(){ return deque.removeLast(); }
 9     public int size(){ return deque.size(); }
10 11     @Override
12     public String toString() { return deque.toString(); }
13 }

 

5、Map

  Map保存的是映射表,即它维护的是键-值对,因此可以使用键来查找值。基本实现类型有:HashMap、TreeMap,LinkedHashMap、WeakHashMap、ConcurrentHashMap、IdentityHashMap。

(1)性能

  HashMap使用散列码来取代对键的缓慢搜索,能显著提高在搜索方面的性能。

类型说明
HashMap

Map基于散列表的实现(它取代了HashTable)。插入和查询“键值对”的开销是固定的。

可以通过构造器设置容量和负载因子,以调整容器性能。

LinkedHashMap

类似于HashMap。

迭代遍历它时,取得“键值对”的顺序是其插入次序,或者是最近最少使用(LRU)的次序。

只比HashMap慢一点,而在迭代访问时反而更快,因为它使用链表维护内部次序。

TreeMap

基于红黑树的实现。查看“键”或“键值对”时,它们会被排序(次序由Comparable或Comparator决定)。

TreeMap的特点在于,所得到结果是经过排序的。

TreeMap是唯一的带有subMap()方法的Map,它可以返回一个子树。

WeakHashMap

弱键(weak key)映射,允许释放映射所指向的对象;这是为解决某类特殊问题而设计的。

如果映射之外没有引用指向某个“键”,则此“键”可以被垃圾收集器回收。

ConcurrentHashMap 一种线程安全的Map,它不涉及同步加锁。
IdentityHashMap 使用==代替equals()对“键”进行比较的散列映射。专为解决特殊问题而设计的。

 

(2)SortedMap

  使用SortedMap可以确保键处于排序状态。

方法说明
T firstKey() 返回Map中的第一个键。
T lastKey() 返回Map中的最后一个键。
SortedMap subMap(fromKey,toKey) 生成此Map的子集,范围从fromKey(包含)到toKey(不包含)。
SortedMap headMap(toKey) 生成此Map的子集,由键小于toKey的所有键值对组成。
SortedMap tailMap(fromKey) 生成此Map的子集,由键大于或等于fromKey的所有键值对组成。

 

(3)LinkedHashMap

  LinkedHashMap散列化所有的元素,但是在遍历键值对时,却又以元素的插入顺序返回键值对。可以在构造器中设定LinkedHashMap,使之采用基于访问的最近最少使用(LRU)算法,于是没有被访问过的元素就会出现在队列最前面。

 

6、散列与散列码

  Object的hashCode()方法生成散列码,而它默认是使用对象的地址计算散列码。因此,如果要使用自己的类作为HashMap的键,必须同时重载hashCode()和equals()。HashMap使用equals()判断当前的键是否与表中存在的键相同。

(1)为速度而散列

  散列的价值在于速度:散列使得查询得以快速进行。通过数组保存键的信息。通过键对象生成散列码,作为数组的下标。将键本身保存在一个数组的list中。为了解决数组容量被固定的问题,不同的键可以产生相同的下标,也就是会产生冲突。

  查询一个值的过程首先计算散列码,然后使用散列码查询数组。在数组中取得保存值的list,然后对list中的值使用equals()方法进行线性的查询。

(2)覆盖hashCode()

  设计hashCode()时最重要的因素就是:无论何时,对同一个对象调用hashCode()都应该产生同样的值。hashCode()方法不能依赖于对象中易变的数据,也不应该依赖于具有唯一性的对象信息,尤其是使用this的值,因为这样做无法生成一个新的键,使之与put()中原始的键值对中的键相同。

 

7、选择接口的不同实现

  尽管实际上只有四种容器:Map、List、Set和Queue,但是每种接口都有不止一个实现版本。选择哪一个实现可以基于使用某个特定操作的频率,以及需要的执行速度来在它们中间进行选择。

(1)对List的选择

  在List中通常ArrayList作为默认首选,只有你需要额外的功能,或者当程序的性能因为经常从表中间进行插入和删除而变差的时候,才会选择LinkedList。

(2)对Set的选择

  HashSet的性能基本上总是比TreeSet好,特别是在添加和查询元素时。只有当需要一个排好序的Set时,才应该使用TreeSet。对于插入操作,LinkedHashSet比HashSet的代价更高,这是由维护链表所带来的额外开销造成的。

(3)对Map的选择

  除了IdentityHashMap,所有的Map实现的插入操作都会随着Map尺寸的变大而明显变慢。

  HashTable的性能大体上与HashMap相当,因为它们使用了相同的底层存储和查找机制。

  TreeMap通常比HashMap要慢。第一选择应该是HashMap,只有在你要求Map始终保持有序时,才需要使用TreeMap。

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

 

8、实用方法

(1)List的排序和查询

  排序通过sort()方法实现(sort(List <T>),sort(List<T>,Comparator<? Super T> c))。通过Collections.binarySearch()方法实现查询。

  List如果使用了Comparator进行排序,那么binarySearch()必须使用相同的Comparator。

1 List<String> list =
2                 new ArrayList<String>(Arrays.asList("C","B","a"));
3 Collections.sort(list,String.CASE_INSENSITIVE_ORDER);
4  int index = Collections.binarySearch(
5                 list,"B",String.CASE_INSENSITIVE_ORDER);

 

(2)设定Collection或Map为不可修改

1 Collection<String> c = 
2                 Collections.unmodifiableCollection(new ArrayList<String>());
3 List<String> a = 
4                 Collections.unmodifiableList(new ArrayList<String>());
5 Set<String> s = 
6                 Collections.unmodifiableSet(new HashSet<String>());
7 Map<String,String> m = 
8                 Collections.unmodifiableMap(new HashMap<String,String>());

 

(3)Collection或Map的同步控制

  快速报错机制能够防止多个进程同时修改同一个容器的内容。它会检查容器上的任何除了你的进程所进行的操作以外的所有变化,一旦它发现其他进程修改了容器,就会立刻抛出ConcurrentModificationException异常。

1 Collection<String> c =
2                 Collections.synchronizedCollection(new ArrayList<String>());
3 List<String> a =
4                 Collections.synchronizedList(new ArrayList<String>());
5 Set<String> s =
6                 Collections.synchronizedSet(new HashSet<String>());
7 Map<String,String> m =
8                 Collections.synchronizedMap(new HashMap<String,String>());

 

9、持有引用

  如果想继续持有对某个对象的引用,希望以后还能够访问到该对象,但是也希望能够允许垃圾回收器释放它,这时就应该使用Reference对象。以Reference对象作为你和普通引用之间的媒介(代理),另外,一定不能有普通引用指向那个对象,这样就能达到上述的目的。有三个继承自抽象类Reference的类:SoftReference、WeakReference和PhantomReference。

(1)WeakHashMap

  WeakHashMap用来保存WeakReference。它允许垃圾回收器自动清理键和值,这是一种节约存储空间的技术。允许清理元素的触发条件:不再需要此键了。

 

 

 参考于《Java编程思想》,第459~524页

posted @ 2021-07-09 14:01  sumAll  阅读(57)  评论(0编辑  收藏  举报