[ Team LiB ] |
11.3 Hashtables and HashMapsBecause Hashtables and HashMaps are the most commonly used nonlist structures, I will spend a little extra time discussing them. Hashtables and HashMaps are pretty fast and provide adequate performance for most purposes. I rarely find that I have a performance problem using Hashtables or HashMaps, but here are some points that will help you tune them or, if necessary, replace them:
Here is a specialized class to use for keys in a Hashtable. This example assumes that I am using String keys, but all my String objects are nonequal, and I can reference keys by identity. I use a utility class, tuning.dict.Dict, which holds a large array of nonequal words taken from an English dictionary. I compare the access times against all the keys using two different Hashtables, one using the plain String objects as keys, the other using my own StringWrapper objects as keys. The StringWrapper objects cache the hash value of the string and assume that equality comparison is the same as identity comparison. These are the fastest possible equals( ) and hashCode( ) methods. The access speedups are illustrated in the following table of measurements (times normalized to the JDK 1.2 case):
If you create a hash-table implementation specialized for the StringWrapper class, you avoid calling the hashCode( ) and equals( ) methods completely. Instead, the specialized hash table can access the hash-instance variable directly and use identity comparison of the elements. The speedup is considerably larger, and for specialized purposes, this is the route to follow: package tuning.hash; import java.util.Hashtable; import tuning.dict.Dict; public class SpecialKeyClass { public static void main(String[ ] args) { //Initialize the dictionary try{Dict.initialize(true);}catch(Exception e){ } System.out.println("Started Test"); //Build the two hash tables. Keep references to the //StringWrapper objects for later use as accessors. Hashtable h1 = new Hashtable( ); Hashtable h2 = new Hashtable( ); StringWrapper[ ] dict = new StringWrapper[Dict.DICT.length]; for (int i = 0; i < Dict.DICT.length; i++) { h1.put(Dict.DICT[i], Boolean.TRUE); h2.put(dict[i] = new StringWrapper(Dict.DICT[i]), Boolean.TRUE); } System.out.println("Finished building"); Object o; //Time the access for normal String keys long time1 = System.currentTimeMillis( ); for (int i = 0; i < Dict.DICT.length; i++) o = h1.get(Dict.DICT[i]); time1 = System.currentTimeMillis( ) - time1; System.out.println("Time1 = " + time1); //Time the access for StringWrapper keys long time2 = System.currentTimeMillis( ); for (int i = 0; i < Dict.DICT.length; i++) o = h2.get(dict[i]); time2 = System.currentTimeMillis( ) - time2; System.out.println("Time2 = " + time2); } } final class StringWrapper { //cached hash code private int hash; private String string; public StringWrapper(String str) { string = str; hash = str.hashCode( ); } public final int hashCode( ) { return hash; } public final boolean equals(Object o) { //The fastest possible equality check return o = = this; /* //This would be the more generic equality check if we allowed //access of the same String value from different StringWrapper objects. //This is still faster than the plain Strings as keys. if(o instanceof StringWrapper) { StringWrapper s = (StringWrapper) o; return s.hash = = hash && string.equals(s.string); } else return false; */ } } |