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