// containers/MapPerformance36.java // TIJ4 Chapter Containers, Exercise 36, page 878 /* Modify SlowMap so that instead of two ArrayLists, it holds a single * ArrayList of MapEntry objects. Verify that the modified version works * correctly. Using MapPerformance.java, test the speed of your new Map. * Now change the put() method so that if performs a sort() after each * pair is entered, and modify get() to use Collections.binarySearch() * to look up the key. Compare the performance of the new version with * the old ones. */ /* My solution to one of the exercises in * Thinking in Java 4th Edition (by Bruce Eckel) * compiles and runs correctly using JDK 1.6.0 * @author Greg Gordon * @author www.greggordon.org * December, 2007 */ import java.util.*; import net.mindview.util.*; import static net.mindview.util.Print.*; // SlowMap with one ArrayList> and // original put() and get() methods: class SlowMap36a extends AbstractMap { protected List> entryList = new ArrayList>(); private EntrySet entries = new EntrySet(); public Set> entrySet() { return entries; } private class EntrySet extends AbstractSet> { public int size() { return entryList.size(); } public Iterator> iterator() { return new Iterator>() { private int index = -1; public boolean hasNext() { return index < entryList.size() - 1; } @SuppressWarnings("unchecked") public Map.Entry next() { int i = ++index; return entryList.get(i); } public void remove() { entryList.remove(index--); } }; } } public V put(K key, V value) { V oldValue = get(key); // The old value or null Iterator> it = entryList.iterator(); while(it.hasNext()) { MapEntry me = it.next(); if(me.getKey().equals(key)) me.setValue(value); } entryList.add(new MapEntry(key, value)); return oldValue; } public V get(Object key) { // key is type Object, not K Iterator> it = entryList.iterator(); while(it.hasNext()) { MapEntry me = it.next(); if(me.getKey().equals(key)) return me.getValue(); } return null; } } // SlowMap with modified put() and get() methods: class SlowMap36b extends SlowMap36a { // To allow search for key via Collections.binarySearch(List,T,Comparator): private List> hashEntryList = new ArrayList>(); public List> hashEntryList() { return hashEntryList; } // To allow sorting: private final MapEntryKeyComp comp1 = new MapEntryKeyComp(); private final MapEntryKeyComp comp2 = new MapEntryKeyComp(); // Very slow put() with 2 sorts: @Override public V put(K key, V value) { V oldValue = get(key); // The old value or null Iterator> it = entryList.iterator(); while(it.hasNext()) { MapEntry me = it.next(); if(me.getKey().equals(key)) { me.setValue(value); } } entryList.add(new MapEntry(key, value)); hashEntryList.add(new MapEntry(key.hashCode(), value)); // Sort (uses hashCode()) after adding: Collections.sort(entryList, comp1); Collections.sort(hashEntryList, comp2); return oldValue; } // Faster get() with sorting done in put: @Override public V get(Object key) { // key is type Object, not K // Same key, same hashCode via MapEntryKeyComp: MapEntry sub = new MapEntry(key.hashCode(), null); int i = Collections.binarySearch(hashEntryList, sub, comp2); if(i >= 0) return hashEntryList.get(i).getValue(); return null; } } // Sort by key hashCode(): class MapEntryKeyComp implements Comparator> { public int compare(MapEntry me1, MapEntry me2) { return me1.getKey().hashCode() - me2.getKey().hashCode(); } } public class MapPerformance36 { static List>> tests = new ArrayList>>(); static { tests.add(new Test>("put") { int test(Map map, TestParam tp) { int loops = tp.loops; int size = tp.size; for(int i = 0; i < loops; i++) { map.clear(); for(int j = 0; j < size; j++) map.put(j, j); } return loops * size; } }); tests.add(new Test>("get") { int test(Map map, TestParam tp) { int loops = tp.loops; int span = tp.size * 2; for(int i = 0; i < loops; i++) for(int j = 0; j < span; j++) map.get(j); return loops * span; } }); tests.add(new Test>("iterate") { int test(Map map, TestParam tp) { int loops = tp.loops * 10; for(int i = 0; i < loops; i++) { Iterator it = map.entrySet().iterator(); while(it.hasNext()) it.next(); } return loops * map.size(); } }); } public static void main(String[] args) { if(args.length > 0) Tester.defaultParams = TestParam.array(args); else Tester.defaultParams = TestParam.array(10, 100, 50, 50, 100, 20); print("Tesing SlowMap36a:"); SlowMap36a m = new SlowMap36a(); m.putAll(Countries.capitals(15)); print("m: " + m); print("m.get(\"BURUNDI\"): " + m.get("BURUNDI")); print("m.entrySet(): " + m.entrySet()); print("m.keySet(): " + m.keySet()); print("m.values() = " + m.values()); print("Two different maps: "); SlowMap36a m2 = new SlowMap36a(); print("m.equals(m2): " + m.equals(m2)); m2.putAll(Countries.capitals(15)); print("Maps with same entries: "); print("m.equals(m2): " + m.equals(m2)); m.clear(); print("After m.clear(), m.isEmpty(): " + m.isEmpty() + ", m = " + m); m2.keySet().clear(); print("After m2.keySet().clear(), m2.isEmpty(): " + m2.isEmpty() + ", m2 = " + m2); print(); print("Testing SlowMap36b:"); SlowMap36b m3 = new SlowMap36b(); m3.putAll(Countries.capitals(15)); print("m3: " + m3); print("m3.get(\"BURUNDI\"): " + m3.get("BURUNDI")); print("m3.entrySet(): " + m3.entrySet()); print("m3.hashEntryList(): " + m3.hashEntryList()); m3.clear(); print("After m3.clear(), m3.isEmpty(): " + m3.isEmpty() + ", m3 = " + m3); m3.keySet().clear(); print("After m3.keySet().clear(), m3.isEmpty(): " + m3.isEmpty() + ", m3 = " + m3); print(); print("Comparative time tests:"); Tester.run(new SlowMap(), tests); Tester.run(new SlowMap17(), tests); Tester.run(new SlowMap36a(), tests); Tester.run(new SlowMap36b(), tests); Tester.run(new HashMap(), tests); Tester.run(new TreeMap(), tests); Tester.run(new LinkedHashMap(), tests); Tester.run(new IdentityHashMap(), tests); Tester.run(new WeakHashMap(), tests); Tester.run(new Hashtable(), tests); } }