首页 > 代码库 > Thinking in Java:容器深入研究

Thinking in Java:容器深入研究

技术分享
1.虚线框表示Abstract类,图中大量的类的名字都是以Abstract开头的,它们仅仅是部分实现了特定接口的工具,因此创建时能够选择从Abstract继承。

Collections中的实用方法:挑几个经常使用的:
1. reverse(List):逆转次序
2. rotate(List,int distance)全部元素向后移动distance个位置,将末尾元素循环到前面来(用了三次reverse)
3.sort(List,Comparator) 排序,可依照自己的Comparator
4.copy(List dest,List src) 复制元素(引用)
5.fill(List,T x)同Arrays一样。复制的是同一引用
6.disjoint(Collection。Collection) 两个集合没有不论什么元素时返回ture
7.frequency(Collection, Object x)返回Collection中等于x的元素个数
8.binarySearch()折半查找(要求有序)

Collection的功能方法:
boolean add(T) 可选方法。若没将參数加入进容器,则false
boolean addAll(Collection )可选方法,仅仅要加入了随意元素就true
void clear() 可选方法
boolean contains(T) 若容器中已经持有具有泛型T參数。true
Boolean containsAll(Collection )
boolean isEmpty()
Iterator iterator()
Boolean remove(Object) 可选方法
boolean removeAll(Collection) 可选方法
Boolean retainAll(Collection) 容器交集,可选方法
int size()
Object[] toArray()
T[] toArray(T[] a)返回包括容器中全部元素的数组
PS:这里不包括随机訪问所选择元素的get()方法,由于Collection包括Set。而Set是自己维护内部顺序的(这使得随机訪问变得没有意义)

2上述执行各种不同的加入和移出的方法在Collection接口中都是可选操作,意味着实现类并不须要为这些方法提供功能定义

未获得支持的操作
1.Arrays.asList()会生成一个List,它基于一个固定大小的数组。仅支持哪些不会改变数组大小的操作,不论什么会引起对底层数据结构的尺寸进行改动的方法都会产生一个UnsupportedOperationException异常(这里Arrays.asList返回的是List的代理类)。若想生成循序使用全部的方法的普通容器,能够将其作为參数传入到新的容器中(如addAll,或构造器等)
2Collections类中的“不可改动”方法(unmodified*方法)将容器包装到了一个代理中,该代理类不支持不论什么试图改动容器的操作

Set和存储顺序:
1:Set(interface):加入Set的元素必须定义equals()方法以确保对象的唯一性,Set接口不保证维护元素的次序
2:HashSet:存入HashSet的元素必须定义hashCode()
3:TreeSet:保证有序,元素必须实现Comparable接口
4:LinkedHashSet 具有HashSet的查询速度,且内部使用链表维护元素的顺序,元素必须定义hashCode()
尽管hashCode不是必须实现的。可是对于良好的编程风格而言,应该在覆盖equals方法时,总是同一时候覆盖hashCode()方法。

3.假设一个对象被用于不论什么种类的排序容器中。如SortedSet(TreeSet是其唯一实现),则其必须实现Comparable接口
PS:在接口的实现方法compareTo()中,不应该使用return i-i2这种形式,错误编程。由于这样没有考虑到i-i2数值溢出的问题,应该
return (arg.i < i ?

-1 : (arg.i == i ? 0 : 1))

4若在HashSet容器中元素并没有又一次定义hashCode(),将它们放置到不论什么散列实现中都可能会产生反复值,这甚至不会产生执行时错误。

唯一的可靠方法就是写单測。

5.SortedSet的意思为“依照对象的比較函数对元素排序”,而不是“元素插入的次序”。

6.优先级队列。其元素排序顺序也是通过实现Comparable而进行控制的。

理解Map:
1:HashMap : 基于散列表的实现。

能够通过构造器设置容量和负载因子调整容器性能。
2:LinkedHashMap :与HashMap却别在于迭代遍历时。取得键值对顺序是插入次序。或是LRU次序。除了迭代訪问时其余性能要慢一点
3:TreeMap:基于红黑树的实现。查看“键”或者“键值对”的时候,它们会被排序(依照Comparable方法),其唯一的带有subMap()方法的Map,能够返回一个子树。
4:WeakHashMap:弱键映射。同意释放映射所指向的对象;假设映射之外没有引用指向某个“键”,则此键能够被垃圾收集器回收
5:IdentityHashMap:使用==取代equals()对”键“进行比較的散列映射
PS:不论什么键都必须具有一个equals方法。假设键被用于散列Map。则还必须有一个恰当的hashCode方法。若键被用于TreeMap,则它必须实现Comparable

7.map中能够直接打印values方法的结果,该方法会产生一个包括Map中全部值得Collection。由于这些Collection背后是由Map支持的,所以对Collection的不论什么改动都会反映到与之相关联的Map。

8.LinkedHashMap散列化全部的元素,可是在遍历键值对时,却又以元素的插入顺序返回键值对。此外,能够在构造器中设定LinkedHashMap,使之採用基于訪问的LRU算法,于是没有被訪问过的元素就会出如今队列的前面。对于须要定期清理元素以节省空间的程序来说,此功能使得程序非常easy得以实现。

散列与散列码:
1.Object的hashCode()方法生成散列码,而它默认是使用对象的地址计算散列码。
2.可能你会觉得,仅仅需编写恰当的hashCode()方法的覆盖版本号就可以。可是它仍然无法正常执行,除非你同一时候覆盖equals()方法。他也是Object的一部分。

HashMap使用equals()推断当前的键是否与表中存在的键同样。


PS:正确的equals()方法必须满足下列5个条件:
1)自反性:x.equals(x)一定返回true
2)对称性:x.equals(y)返回true当且仅当y.equals(x)
3)传递性:x.equals(y)且y.equals(z)。则x.equals(z)为true
4)一致性:若x.equals(y)返回true,则不改变x,y时多次调用x.equals(y)都返回true
5)对于随意的非空引用值x。x.equals(null)一定返回false。

为了速度而散列:
1.由于瓶颈位于键的查询速度。因此解决方式之中的一个就是保持键的排序状态,然后使用Collections。binarySearch()进行查询
2.散列则更进一步,它将键保存在某处,以便能够非常快找到。

存储一组元素最快的数据结构是数组。所以使用它来表示键的信息。信息就是散列码。
3.通常。冲突由外部链接处理,数组并直接保存至,而是保存值得list,然后对list中的值使用equals方法进行线性查询。
4.进来,Java的散列函数都使用2的整数次方。

对现代的处理器来说,除法与求余数是最慢的操作。使用2的整数次方长度的散列表,可用掩码取代除法。由于get是使用最多的操作。求余数的%操作是其开销最大的部分。而是用2的整数次方能够消除此开销。

9.设计hashCode()时最重要的因素就是:不管何时。对同一个对象调用hashCode()都应该生成同样的值

若你的hashCode()方法依赖于对象中易变的数据,用户就要当心了。

同一时候,也不应该使hashCode()依赖于具有唯一性的对象信息,尤其是使用this的值,这仅仅能产生非常糟糕的hashCode()由于这样无法生成一个新的键使之与put中原始的键值对中的键同样。

10.要想使hashCode使用。他必须速度快,而且有意义,也就是说,他必须基于对象的内容生成散列码

散列码不必是独一无二的。可是通过hashCode()和equals,必须能够全然确定对象的身份。

性能:
1.应该避免使用Vector(能正常工作的唯一原因,仅仅是为了向前兼容,被适配成了List)
2TreeSet迭代通常比用HashSet要快。另外。对于插入操作,LinkedHashSet比HashSet代价更高,这是由维护链表所带来额外的开销造成的
3.Hashtable的性能大体上与HashMap相当。

由于HashMap是用来替代Hashtable的。

因此它们使用了同样的底层存储和查找机制。


4.IdentityHashMap则具有全然不同的性能,由于它使用==而不是equals来比較元素。

HashMap的性能因子:
容量:表中桶位数(slot)
初始容量:表在创建时所拥有的桶位数。HashMap和HashSet都具有同意你仅仅能初始容量的构造器。
尺寸:表中当前存储的项数。
负载因子:尺寸/容量。

HashMap和HashSet都具有同意你指定负载因子的构造器。表示负载情况达到该负载因子的水平时,容器将自己主动加入其容量(桶位数)。实现方式是使容量大致加倍,并又一次将现有对象分布到新的桶位数集中(这被称为再散列)

Collection或Map的同步控制:
1.Collections类有办法能够自己主动同步整个容器。其语法与“unmodified*”类似。


List a = Collections.synchronizedList(new ArrayList(data));
最好如上所看到的,是直接将新生成的容器传递给了适当的“同步”方法。这样做就不会有不论什么机会会暴漏出不同步的版本号。

高速报错:
Java容器有一种保护机制,能够防止多个进程同一时候改动同一个容器的内容。Java容器类类库採用高速报错(fail fast)机制。

它会探查容器上的不论什么除了你的进程所进行的操作以外的全部变化,一旦发现其它进程改变了容器,就会立马抛出ConcurrentModificationException异常。如:在容器取得迭代器之后,又有东西呗放入到了该容器中
ConcurrentHashMap,CopyOnWriteArrayList、CopyOnWriteArraySet都使用了能够避免ConcurrentModificationException的技术

持有引用:
1.java.lang.ref类库包括一组类。当存在可能会耗尽内存的大对象的时候,这些类显得特别实用。有SoftReference、WeakReference、PhantomReference。

当垃圾回收期正在考察的对象仅仅能通过某个Reference对象才“可获得时”,上述这些不同的派生类为垃圾回收器提供了不同级别的间接性指示
2.假设想继续持有对某个对象的引用。希望以后还能够訪问到该对象。可是也希望能够同意垃圾回收器释放它,这时就应该使用Reference对象。这样,你能够继续使用对象,而在内存消耗殆尽的时候又同意释放该对象。
3.以Reference对象作为你和普通引用之间的媒介(代理),另外一定不能有普通的引用指向那个对象。这样就能达到上述目的
4.对于SoftReference、WeakReference、PhantomReference三个引用的差别和功能,详见JVM.
5.使用SoftReference、WeakReference时,能够选择是否要将它们放入ReferenceQueue(用作“回收前清理工作”的工具,见JVM)。而PhantomReference仅仅能依赖于ReferenceQueue。
6.WeakHashMap:见JVM。它被用来保存WeakReference。这该映射中,每一个值仅仅保存一份实例以节省存储空间。且WeakHashMap同意垃圾回收器自己主动清理键和值。所以它显得十分便利。

Java 1.0/1.1容器(了解):
1.Enmuberation:老版本号的迭代器。其接口比Iterator小,可是不管怎样代码中应该尽量使用Iterator。能够通过使用Collections.enumeration()方法来从Collection生成一个Enumeration
2.Hashtable:如前所说,主要的Hashtable与HashMap非常类似。

尽量使用HashMap(多线程同步时仍然有非常多其它的选择)
3.BisSet:假设想要高效率地存储大量“开/关”信息,BitSet是非常好的选择。只是它的效率仅是对空间而言。假设须要高效訪问时间,BitSet比本地数组稍慢一些,BitSet最小容量是Long:64位,若存储的内容比較小,如8位。则BitSet浪费了一些空间。BitSet也会想普通容器一样随着元素的加入而扩充其容量。总的来说。若拥有一个能够命名的标志集合。那么EnumSet一般是一种更好的选择,由于EnumSet同意你依照名字而不是数字位的位置进行操作,因此能够降低错误。

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

Thinking in Java:容器深入研究