Java编程思想第4版[中文版](PDF格式)-第70部分
按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
1000 7。4 6。6 9。5
进行add()以及contains()操作时,HashSet 显然要比ArraySet 出色得多,而且性能明显与元素的多寡关系
不大。一般编写程序的时候,几乎永远用不着使用 ArraySet 。
3。 决定使用何种Map
250
…………………………………………………………Page 252……………………………………………………………
选择不同的 Map 实施方案时,注意 Map 的大小对于性能的影响是最大的,下面这个测试程序清楚地阐示了这
一点:
//: MapPerformance。java
// Demonstrates performance differences in Maps
package c08。newcollections;
import java。util。*;
public class MapPerformance {
private static final int REPS = 200;
public static Map fill(Map m; int size) {
for(int i = 0; i 《 size; i++) {
String x = Integer。toString(i);
m。put(x; x);
}
return m;
}
private abstract static class Tester {
String name;
Tester(String name) { this。name = name; }
abstract void test(Map m; int size);
}
private static Tester'' tests = {
new Tester(〃put〃) {
void test(Map m; int size) {
for(int i = 0; i 《 REPS; i++) {
m。clear();
fill(m; size);
}
}
};
new Tester(〃get〃) {
void test(Map m; int size) {
for(int i = 0; i 《 REPS; i++)
for(int j = 0; j 《 size; j++)
m。get(Integer。toString(j));
}
};
new Tester(〃iteration〃) {
void test(Map m; int size) {
for(int i = 0; i 《 REPS * 10; i++) {
Iterator it = m。entries()。iterator();
while(it。hasNext())
it。next();
}
}
};
};
public static void test(Map m; int size) {
// A trick to print out the class name:
System。out。println(〃Testing 〃 +
m。getClass()。getName() + 〃 size 〃 + size);
251
…………………………………………………………Page 253……………………………………………………………
fill(m; size);
for(int i = 0; i 《 tests。length; i++) {
System。out。print(tests'i'。name);
long t1 = System。currentTimeMillis();
tests'i'。test(m; size);
long t2 = System。currentTimeMillis();
System。out。println (〃: 〃 +
((double)(t2 t1)/(double)size));
}
}
public static void main(String'' args) {
// Small:
test(new Hashtable(); 10);
test(new HashMap(); 10);
test(new TreeMap(); 10);
// Medium:
test(new Hashtable(); 100);
test(new HashMap(); 100);
test(new TreeMap(); 100);
// Large:
test(new HashMap(); 1000);
test(new Hashtable(); 1000);
test(new TreeMap(); 1000);
}
} ///:~
由于Map 的大小是最严重的问题,所以程序的计时测试按Map 的大小(或容量)来分割时间,以便得到令人
信服的测试结果。下面列出一系列结果(在你的机器上可能不同):
类型 测试大小 置入 取出 反复
T y p e T e s t Put Get Iteration
s i z e
10 11。0 5。0 44。0
Hashtable 100 7。7 7。7 16。5
1000 8。0 8。0 14。4
10 16。0 11。0 22。0
TreeMap 100 25。8 15。4 13。2
1000 33。8 20。9 13。6
10 11。0 6。0 33。0
HashMap 100 8。2 7。7 13。7
1000 8。0 7。8 11。9
即使大小为 10,ArrayMap 的性能也要比HashMap 差——除反复循环时以外。而在使用Map 时,反复的作用通
常并不重要(get()通常是我们时间花得最多的地方)。TreeMap 提供了出色的 put()以及反复时间,但 get()
的性能并不佳。但是,我们为什么仍然需要使用TreeMap 呢?这样一来,我们可以不把它作为Map 使用,而
作为创建顺序列表的一种途径。树的本质在于它总是顺序排列的,不必特别进行排序(它的排序方式马上就
要讲到)。一旦填充了一个TreeMap,就可以调用keySet()来获得键的一个Set “景象”。然后用toArray()
252
…………………………………………………………Page 254……………………………………………………………
产生包含了那些键的一个数组。随后,可用 static 方法Array。binarySearch()快速查找排好序的数组中的
内容。当然,也许只有在HashMap 的行为不可接受的时候,才需要采用这种做法。因为HashMap 的设计宗旨
就是进行快速的检索操作。最后,当我们使用 Map 时,首要的选择应该是 HashMap。只有在极少数情况下才
需要考虑其他方法。
此外,在上面那张表里,有另一个性能问题没有反映出来。下述程序用于测试不同类型Map 的创建速度:
//: MapCreation。java
// Demonstrates time differences in Map creation
package c08。newcollections;
import java。util。*;
public class MapCreation {
public static void main(String'' args) {
final long REPS = 100000;
long t1 = System。currentTimeMillis();
System。out。print(〃Hashtable〃);
for(long i = 0; i 《 REPS; i++)
new Hashtable();
long t2 = System。currentTimeMillis();
System。out。println(〃: 〃 + (t2 t1));
t1 = System。currentTimeMillis();
System。out。print(〃TreeMap〃);
for(long i = 0; i 《 REPS; i++)
new TreeMap();
t2 = System。currentTimeMillis();
System。out。println(〃: 〃 + (t2 t1));
t1 = System。currentTimeMillis();
System。out。print(〃HashMap〃);
for(long i = 0; i 《 REPS; i++)
new HashMap();
t2 = System。currentTimeMillis();
System。out。println(〃: 〃 + (t2 t1));
}
} ///:~
在写这个程序期间,TreeMap 的创建速度比其他两种类型明显快得多(但你应亲自尝试一下,因为据说新版
本可能会改善ArrayMap 的性能)。考虑到这方面的原因,同时由于前述TreeMap 出色的put()性能,所以如
果需要创建大量Map,而且只有在以后才需要涉及大量检索操作,那么最佳的策略就是:创建和填充
TreeMap;以后检索量增大的时候,再将重要的TreeMap 转换成HashMap——使用HashMap(Map)构建器。同样
地,只有在事实证明确实存在性能瓶颈后,才应关心这些方面的问题——先用起来,再根据需要加快速度。
8。7。6 未支持的操作
利用 static (静态)数组Arrays。toList(),也许能将一个数组转换成List,如下所示:
//: Unsupported。java
// Sometimes methods defined in the Collection
// interfaces don't work!
package c08。newcollections;
import java。util。*;
public class Unsupported {
private static String'' s = {
253
…………………………………………………………Page 255……………………………………………………………
〃one〃; 〃two〃; 〃three〃; 〃four〃; 〃five〃;
〃six〃; 〃seven〃; 〃eight〃; 〃nine〃; 〃ten〃;
};
static List a = Arrays。toList(s);
static List a2 = Arrays。toList(
new String'' { s'3'; s'4'; s'5' });
public static void main(String'' args) {
Collection1。print(a); // Iteration
System。out。println(
〃a。contains(〃 + s'0' + 〃) = 〃 +
a。contains(s'0'));
System。out。println(
〃a。containsAll(a2) = 〃 +
a。containsAll(a2));
System。out。println(〃a。isEmpty() = 〃 +
a。isEmpty());
System。out。println(
〃a。indexOf(〃 + s'5' + 〃) = 〃 +
a。indexOf(s'5'));
// Traverse backwards:
ListIterator lit = a。listIterator(a。size());
while(lit。hasPrevious())
System。out。print(lit。previous());
System。out。println();
// Set the elements to different values:
for(int i = 0; i 《 a。size(); i++)
a。set(i; 〃47〃);
Collection1。print(a);
// piles; but won't run:
lit。add(〃X〃); // Unsupported operation
a。clear(); // Unsupported
a。add(〃eleven〃); // Unsupported
a。addAll(a2); // Unsupported
a。retainAll(a2); // Unsupported
a。remove(s'0'); // Unsupported
a。removeAll(a2); // Unsupported
}
} ///:~
从中可以看出,实际只实现了 Collection 和 List 接口的一部分。剩余的方法导致了不受欢迎的一种情况,
名为UnsupportedOperationException。在下一章里,我们会讲述违例的详细情况,但在这里有必要进行一
下简单说明。这里的关键在于“集合接口”,以及新集合库内的另一些接口,它们都包含了“可选的”方
法。在实现那些接口的集合类中,或者提供、或者没有提供对那些方法的支持。若调用一个未获支持的方
法,就会导致一个UnsupportedOperationException (操作未支持违例),这表明出现了一个编程错误。
大家或许会觉得奇怪,不是说“接口”和基础类最大的“卖点”就是它们许诺这些方法能产生一些有意义的
行为吗?上述违例破坏了那个许诺——它调用的一部分方法不仅不能产生有意义的行为,而且还会中止程序
的运行。在这些情况下,类型的所谓安全保证似乎显得一钱不值!但是,情况并没有想象的那么坏。通过
Collection,List,Set 或者Map,编译器仍然限制我们只能调用那个接口中的方法,所以它和 Smalltalk 还
是存在一些区别的(在 Smalltalk 中,可为任何对象调用任何方法,而且只有在运行程序时才知道这些调用
是否可行)。除此以外,以Collection 作为自变量的大多数方法只能从那个集合中读取数据——Collection
的所有“read ”方法都不是可选的。
这样一来,系统就可避免在设计期间出现接口的冲突。而在集合库的其他设计方案中,最终经常都会得到数
254
…………………………………………………………Page 256……………………………………………………………
量过多的接口,用它们描述基本方案的每一种变化形式,所以学习和掌握显得非常困难。有些时候,甚至难
于捕捉接口中的所有特殊情况,因为人们可能设计出任何新接口。但 Java 的“不支持的操作”方法却达到了
新集合库的一个重要设计目标:易于学习和使用。但是,为了使这一方法真正有效,却需满足下述条件:
(1) UnsupportedOperationException必须属于一种“非常”事件。也就是说,对于大多数类来说,所有操
作都应是可行的。只有在一些特殊情况下,一、两个操作才可能未获支持。新集合库满足了这一条件,因为
绝大多数时候用到的类——ArrayList,LinkedList,HashList 和 HashMap,以及其他集合方案——都提供了
对所有操作的支持。但是,如果想新建一