友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
富士康小说网 返回本书目录 加入书签 我的书架 我的书签 TXT全本下载 『收藏到我的浏览器』

Java编程思想第4版[中文版](PDF格式)-第47部分

快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 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,以及其他集合方案——都提供了 

对所有操作的支持。但是,如果想新建一个集合,同时不想为集合接口中的所有方法都提供有意义的定义, 

同时令其仍与现有库配合,这种设计方法也确实提供了一个“后门”可以利用。  

(2) 若一个操作未获支持,那么UnsupportedOperationException (未支持的操作违例)极有可能在实现期 

间出现,则不是在产品已交付给客户以后才会出现。它毕竟指出的是一个编程错误——不正确地使用了一个 

类。这一点不能十分确定,通过也可以看出这种方案的“试验”特征——只有经过多次试验,才能找出最理 

想的工作方式。  

  

在上面的例子中,Arrays。toList()产生了一个 List (列表),该列表是由一个固定长度的数组后推出来 

的。因此唯一能够支持的就是那些不改变数组长度的操作。在另一方面,若请求一个新接口表达不同种类的 

行为(可能叫作“FixedSizeList ”——固定长度列表),就有遭遇更大的复杂程度的危险。这样一来,以后 

试图使用库的时候,很快就会发现自己不知从何处下手。  

对那些采用 Collection,List,Set 或者Map 作为参数的方法,它们的文档应当指出哪些可选的方法是必须 

实现的。举个例子来说,排序要求实现set()和 Iterator。set()方法,但不包括add()和 remove() 。  



8。7。7  排序和搜索  



Java 1。2 添加了自己的一套实用工具,可用来对数组或列表进行排列和搜索。这些工具都属于两个新类的 

 “静态”方法。这两个类分别是用于排序和搜索数组的Arrays,以及用于排序和搜索列表的Collections。  

  

1。 数组  

Arrays 类为所有基本数据类型的数组提供了一个过载的 sort()和 binarySearch(),它们亦可用于String 和 

Object。下面这个例子显示出如何排序和搜索一个字节数组(其他所有基本数据类型都是类似的)以及一个 

String 数组:  

  

//: Array1。java  

// Testing the sorting & searching in Arrays  

package c08。newcollections;  

import java。util。*;  

  

public class Array1 {  

  static Random r = new Random();  

  static String ssource =   

    〃ABCDEFGHIJKLMNOPQRSTUVWXYZ〃 +  

    〃abcdefghijklmnopqrstuvwxyz〃;  

  static char'' src = ssource。toCharArray();  

  // Create a random String  

  public static String randString(int length) {  

    char'' buf = new char'length';  

    int rnd;  

    for(int i = 0; i 《 length; i++) {  

      rnd = Math。abs(r。nextInt()) % src。length;  

      buf'i' = src'rnd';  

    }  

    return new String(buf);  

  }  

  // Create a random array of Strings:  

  public static   



                                                                                    255 


…………………………………………………………Page 257……………………………………………………………

  String'' randStrings(int length; int size) {  

    String'' s = new String'size';  

    for(int i = 0; i 《 size; i++)  

      s'i' = randString(length);  

    return s;  

  }  

  public static void print(byte'' b) {  

    for(int i = 0; i 《 b。length; i++)  

      System。out。print(b'i' + 〃 〃);  

    System。out。println();  

  }  

  public static void print(String'' s) {  

    for(int i = 0; i 《 s。length; i++)  

      System。out。print(s'i' + 〃 〃);  

    System。out。println();  

  }  

  public static void main(String'' args) {  

    byte'' b = new byte'15';  

    r。nextBytes(b); // Fill with random bytes  

    print(b);  

    Arrays。sort(b);  

    print(b);  

    int loc = Arrays。binarySearch(b; b'10');  

    System。out。println(〃Location of 〃 + b'10' +  

      〃 = 〃 + loc);  

    // Test String sort & search:  

    String'' s = randStrings(4; 10);  

    print(s);  

    Arrays。sort(s);  

    print(s);  

    loc = Arrays。binarySearch(s; s'4');  

    System。out。println(〃Location of 〃 + s'4' +  

      〃 = 〃 + loc);  

  }  

} ///:~  

  

类的第一部分包含了用于产生随机字串对象的实用工具,可供选择的随机字母保存在一个字符数组中。 

randString()返回一个任意长度的字串;而 readStrings()创建随机字串的一个数组,同时给定每个字串的 

长度以及希望的数组大小。两个print()方法简化了对示范数组的显示。在 main()中,Random。nextBytes() 

用随机选择的字节填充数组自变量(没有对应的Random 方法用于创建其他基本数据类型的数组)。获得一个 

数组后,便可发现为了执行sort()或者 binarySearch(),只需发出一次方法调用即可。与binarySearch() 

有关的还有一个重要的警告:若在执行一次binarySearch()之前不调用 sort(),便会发生不可预测的行为, 

其中甚至包括无限循环。  

对String 的排序以及搜索是相似的,但在运行程序的时候,我们会注意到一个有趣的现象:排序遵守的是字 

典顺序,亦即大写字母在字符集中位于小写字母的前面。因此,所有大写字母都位于列表的最前面,后面再 

跟上小写字母——Z 居然位于a 的前面。似乎连电话簿也是这样排序的。  

  

2。 可比较与比较器  

但假若我们不满足这一排序方式,又该如何处理呢?例如本书后面的索引,如果必须对以A 或a 开头的词条 

分别到两处地方查看,那么肯定会使读者颇不耐烦。  

若想对一个 Object 数组进行排序,那么必须解决一个问题。根据什么来判定两个 Object 的顺序呢?不幸的 

是,最初的 Java 设计者并不认为这是一个重要的问题,否则就已经在根类 Object 里定义它了。这样造成的 



                                                                                          256 


…………………………………………………………Page 258……………………………………………………………

一个后果便是:必须从外部进行Object 的排序,而且新的集合库提供了实现这一操作的标准方式(最理想的 

是在Object 里定义它)。  

针对Object 数组(
返回目录 上一页 下一页 回到顶部 10 9
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!