程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> java的hashtable的用法

java的hashtable的用法

編輯:關於JAVA

Vector允許我們用一個數字從一系列對象中作出選擇,所以它實際是將數字同對象關聯起來了。但假如我們想根據其他標准選擇一系列對象呢?堆棧就是這樣的一個例子:它的選擇標准是“最後壓入堆棧的東西”。這種“從一系列對象中選擇”的概念亦可叫作一個“映射”、“字典”或者“關聯數組”。從概念上講,它看起來象一個Vector,但卻不是通過數字來查找對象,而是用另一個對象來查找它們!這通常都屬於一個程序中的重要進程。
在Java中,這個概念具體反映到抽象類Dictionary身上。該類的接口是非常直觀的size()告訴我們其中包含了多少元素;isEmpty()判斷是否包含了元素(是則為true);put(Object key, Object value)添加一個值(我們希望的東西),並將其同一個鍵關聯起來(想用於搜索它的東西);get(Object key)獲得與某個鍵對應的值;而remove(Object Key)用於從列表中刪除“鍵-值”對。還可以使用枚舉技術:keys()產生對鍵的一個枚舉(Enumeration);而elements()產生對所有值的一個枚舉。這便是一個Dictionary(字典)的全部。
Dictionary的實現過程並不麻煩。下面列出一種簡單的方法,它使用了兩個Vector,一個用於容納鍵,另一個用來容納值:
 

//: AssocArray.java
// Simple version of a Dictionary
import java.util.*;

public class AssocArray extends Dictionary {
  private Vector keys = new Vector();
  private Vector values = new Vector();
  public int size() { return keys.size(); }
  public boolean isEmpty() {
    return keys.isEmpty();
  }
  public Object put(Object key, Object value) {
    keys.addElement(key);
    values.addElement(value);
    return key;
  }
  public Object get(Object key) {
    int index = keys.indexOf(key);
    // indexOf() Returns -1 if key not found:
    if(index == -1) return null;
    return values.elementAt(index);
  }
  public Object remove(Object key) {
    int index = keys.indexOf(key);
    if(index == -1) return null;
    keys.removeElementAt(index);
    Object returnval = values.elementAt(index);
    values.removeElementAt(index);
    return returnval;
  }
  public Enumeration keys() {
    return keys.elements();
  }
  public Enumeration elements() {
    return values.elements();
  }
  // Test it:
  public static void main(String[] args) {
    AssocArray aa = new AssocArray();
    for(char c = 'a'; c <= 'z'; c++)
      aa.put(String.valueOf(c),
             String.valueOf(c)
             .toUpperCase());
    char[] ca = { 'a', 'e', 'i', 'o', 'u' };
    for(int i = 0; i < ca.length; i++)
      System.out.println("Uppercase: " +
             aa.get(String.valueOf(ca[i])));
  }
} ///:~


在對AssocArray的定義中,我們注意到的第一個問題是它“擴展”了字典。這意味著AssocArray屬於Dictionary的一種類型,所以可對其發出與Dictionary一樣的請求。如果想生成自己的Dictionary,而且就在這裡進行,那麼要做的全部事情只是填充位於Dictionary內的所有方法(而且必須覆蓋所有方法,因為它們——除構建器外——都是抽象的)。
Vector key和value通過一個標准索引編號鏈接起來。也就是說,如果用“roof”的一個鍵以及“blue”的一個值調用put()——假定我們准備將一個房子的各部分與它們的油漆顏色關聯起來,而且AssocArray裡已有100個元素,那麼“roof”就會有101個鍵元素,而“blue”有101個值元素。而且要注意一下get(),假如我們作為鍵傳遞“roof”,它就會產生與keys.index.Of()的索引編號,然後用那個索引編號生成相關的值矢量內的值。
main()中進行的測試是非常簡單的;它只是將小寫字符轉換成大寫字符,這顯然可用更有效的方式進行。但它向我們揭示出了AssocArray的強大功能。
標准Java庫只包含Dictionary的一個變種,名為Hashtable(散列表,注釋③)。Java的散列表具有與AssocArray相同的接口(因為兩者都是從Dictionary繼承來的)。但有一個方面卻反映出了差別:執行效率。若仔細想想必須為一個get()做的事情,就會發現在一個Vector裡搜索鍵的速度要慢得多。但此時用散列表卻可以加快不少速度。不必用冗長的線性搜索技術來查找一個鍵,而是用一個特殊的值,名為“散列碼”。散列碼可以獲取對象中的信息,然後將其轉換成那個對象“相對唯一”的整數(int)。所有對象都有一個散列碼,而hashCode()是根類Object的一個方法。Hashtable獲取對象的hashCode(),然後用它快速查找鍵。這樣可使性能得到大幅度提升(④)。散列表的具體工作原理已超出了本書的范圍(⑤)——大家只需要知道散列表是一種快速的“字典”(Dictionary)即可,而字典是一種非常有用的工具。

③:如計劃使用RMI(在第15章詳述),應注意將遠程對象置入散列表時會遇到一個問題(參閱《Core Java》,作者Conrell和Horstmann,Prentice-Hall 1997年出版)
④:如這種速度的提升仍然不能滿足你對性能的要求,甚至可以編寫自己的散列表例程,從而進一步加快表格的檢索過程。這樣做可避免在與Object之間進行造型的時間延誤,也可以避開由Java類庫散列表例程內建的同步過程。
⑤:我的知道的最佳參考讀物是《Practical Algorithms for Programmers》,作者為Andrew Binstock和John Rex,Addison-Wesley 1995年出版。

作為應用散列表的一個例子,可考慮用一個程序來檢驗Java的Math.random()方法的隨機性到底如何。在理想情況下,它應該產生一系列完美的隨機分布數字。但為了驗證這一點,我們需要生成數量眾多的隨機數字,然後計算落在不同范圍內的數字多少。散列表可以極大簡化這一工作,因為它能將對象同對象關聯起來(此時是將Math.random()生成的值同那些值出現的次數關聯起來)。如下所示:
 

//: Statistics.java
// Simple demonstration of Hashtable
import java.util.*;

class Counter { 
  int i = 1; 
  public String toString() { 
    return Integer.toString(i); 
  }
}

class Statistics {
  public static void main(String[] args) {
    Hashtable ht = new Hashtable();
    for(int i = 0; i < 10000; i++) {
      // Produce a number between 0 and 20:
      Integer r = 
        new Integer((int)(Math.random() * 20));
      if(ht.containsKey(r))
        ((Counter)ht.get(r)).i++;
      else
        ht.put(r, new Counter());
    }
    System.out.println(ht);
  }
} ///:~


在main()中,每次產生一個隨機數字,它都會封裝到一個Integer對象裡,使句柄能夠隨同散列表一起使用(不可對一個集合使用基本數據類型,只能使用對象句柄)。containKey()方法檢查這個鍵是否已經在集合裡(也就是說,那個數字以前發現過嗎?)若已在集合裡,則get()方法獲得那個鍵關聯的值,此時是一個Counter(計數器)對象。計數器內的值i隨後會增加1,表明這個特定的隨機數字又出現了一次。
假如鍵以前尚未發現過,那麼方法put()仍然會在散列表內置入一個新的“鍵-值”對。在創建之初,Counter會自己的變量i自動初始化為1,它標志著該隨機數字的第一次出現。
為顯示散列表,只需把它簡單地打印出來即可。Hashtable toString()方法能遍歷所有鍵-值對,並為每一對都調用toString()。Integer toString()是事先定義好的,可看到計數器使用的toString。一次運行的結果(添加了一些換行)如下:
 

{19=526, 18=533, 17=460, 16=513, 15=521, 14=495,
 13=512, 12=483, 11=488, 10=487, 9=514, 8=523,
 7=497, 6=487, 5=480, 4=489, 3=509, 2=503, 1=475,
 0=505}

大家或許會對Counter類是否必要感到疑惑,它看起來似乎根本沒有封裝類Integer的功能。為什麼不用int或Integer呢?事實上,由於所有集合能容納的僅有對象句柄,所以根本不可以使用整數。學過集合後,封裝類的概念對大家來說就可能更容易理解了,因為不可以將任何基本數據類型置入集合裡。然而,我們對Java封裝器能做的唯一事情就是將其初始化成一個特定的值,然後讀取那個值。也就是說,一旦封裝器對象已經創建,就沒有辦法改變一個值。這使得Integer封裝器對解決我們的問題毫無意義,所以不得不創建一個新類,用它來滿足自己的要求。

1. 創建“關鍵”類
在前面的例子裡,我們用一個標准庫的類(Integer)作為Hashtable的一個鍵使用。作為一個鍵,它能很好地工作,因為它已經具備正確運行的所有條件。但在使用散列表的時候,一旦我們創建自己的類作為鍵使用,就會遇到一個很常見的問題。例如,假設一套天氣預報系統將Groundhog(土拔鼠)對象匹配成Prediction(預報)。這看起來非常直觀:我們創建兩個類,然後將Groundhog作為鍵使用,而將Prediction作為值使用。如下所示:
 

//: SpringDetector.java
// Looks plausible, but doesn't work right.
import java.util.*;

class Groundhog {
  int ghNumber;
  Groundhog(int n) { ghNumber = n; }
}

class Prediction {
  boolean shadow = Math.random() > 0.5;
  public String toString() {
    if(shadow)
      return "Six more weeks of Winter!";
    else
      return "Early Spring!";
  }
}

public class SpringDetector {
  public static void main(String[] args) {
    Hashtable ht = new Hashtable();
    for(int i = 0; i < 10; i++)
      ht.put(new Groundhog(i), new Prediction());
    System.out.println("ht = " + ht + "\n");
    System.out.println(
      "Looking up prediction for groundhog #3:");
    Groundhog gh = new Groundhog(3);
    if(ht.containsKey(gh))
      System.out.println((Prediction)ht.get(gh));
  }
} ///:~

每個Groundhog都具有一個標識號碼,所以赤了在散列表中查找一個Prediction,只需指示它“告訴我與Groundhog號碼3相關的Prediction”。Prediction類包含了一個布爾值,用Math.random()進行初始化,以及一個toString()為我們解釋結果。在main()中,用Groundhog以及與它們相關的Prediction填充一個散列表。散列表被打印出來,以便我們看到它們確實已被填充。隨後,用標識號碼為3的一個Groundhog查找與Groundhog #3對應的預報。
看起來似乎非常簡單,但實際是不可行的。問題在於Groundhog是從通用的Object根類繼承的(若當初未指定基礎類,則所有類最終都是從Object繼承的)。事實上是用Object的hashCode()方法生成每個對象的散列碼,而且默認情況下只使用它的對象的地址。所以,Groundhog(3)的第一個實例並不會產生與Groundhog(3)第二個實例相等的散列碼,而我們用第二個實例進行檢索。
大家或許認為此時要做的全部事情就是正確地覆蓋hashCode()。但這樣做依然行不能,除非再做另一件事情:覆蓋也屬於Object一部分的equals()。當散列表試圖判斷我們的鍵是否等於表內的某個鍵時,就會用到這個方法。同樣地,默認的Object.equals()只是簡單地比較對象地址,所以一個Groundhog(3)並不等於另一個Groundhog(3)。
因此,為了在散列表中將自己的類作為鍵使用,必須同時覆蓋hashCode()和equals(),就象下面展示的那樣:
 

//: SpringDetector2.java
// If you create a class that's used as a key in
// a Hashtable, you must override hashCode()
// and equals().
import java.util.*;

class Groundhog2 {
  int ghNumber;
  Groundhog2(int n) { ghNumber = n; }
  public int hashCode() { return ghNumber; }
  public boolean equals(Object o) {
    return (o instanceof Groundhog2)
      && (ghNumber == ((Groundhog2)o).ghNumber);
  }
}

public class SpringDetector2 {
  public static void main(String[] args) {
    Hashtable ht = new Hashtable();
    for(int i = 0; i < 10; i++)
      ht.put(new Groundhog2(i),new Prediction());
    System.out.println("ht = " + ht + "\n");
    System.out.println(
      "Looking up prediction for groundhog #3:");
    Groundhog2 gh = new Groundhog2(3);
    if(ht.containsKey(gh))
      System.out.println((Prediction)ht.get(gh));
  }
} ///:~

注意這段代碼使用了來自前一個例子的Prediction,所以SpringDetector.java必須首先編譯,否則就會在試圖編譯SpringDetector2.java時得到一個編譯期錯誤。
Groundhog2.hashCode()將土拔鼠號碼作為一個標識符返回(在這個例子中,程序員需要保證沒有兩個土拔鼠用同樣的ID號碼並存)。為了返回一個獨一無二的標識符,並不需要hashCode(),equals()方法必須能夠嚴格判斷兩個對象是否相等。
equals()方法要進行兩種檢查:檢查對象是否為null;若不為null,則繼續檢查是否為Groundhog2的一個實例(要用到instanceof關鍵字,第11章會詳加論述)。即使為了繼續執行equals(),它也應該是一個Groundhog2。正如大家看到的那樣,這種比較建立在實際ghNumber的基礎上。這一次一旦我們運行程序,就會看到它終於產生了正確的輸出(許多Java庫的類都覆蓋了hashcode()和equals()方法,以便與自己提供的內容適應)。

2. 屬性:Hashtable的一種類型
在本書的第一個例子中,我們使用了一個名為Properties(屬性)的Hashtable類型。在那個例子中,下述程序行:
Properties p = System.getProperties();
p.list(System.out);
調用了一個名為getProperties()的static方法,用於獲得一個特殊的Properties對象,對系統的某些特征進行描述。list()屬於Properties的一個方法,可將內容發給我們選擇的任何流式輸出。也有一個save()方法,可用它將屬性列表寫入一個文件,以便日後用load()方法讀取。
盡管Properties類是從Hashtable繼承的,但它也包含了一個散列表,用於容納“默認”屬性的列表。所以假如沒有在主列表裡找到一個屬性,就會自動搜索默認屬性。
Properties類亦可在我們的程序中使用(第17章的ClassScanner.java便是一例)。在Java庫的用戶文檔中,往往可以找到更多、更詳細的說明。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved