--- old/src/share/classes/java/util/HashMap.java 2012-05-24 12:15:20.000000000 -0700 +++ new/src/share/classes/java/util/HashMap.java 2012-05-24 12:15:20.000000000 -0700 @@ -146,7 +146,7 @@ /** * The table, resized as necessary. Length MUST Always be a power of two. */ - transient Entry[] table; + transient Entry[] table; /** * The number of key-value mappings contained in this map. @@ -174,6 +174,86 @@ * the HashMap fail-fast. (See ConcurrentModificationException). */ transient int modCount; + + /** + * The default threshold of capacity above which alternate hashing is used. + * Alternative hashing reduces the incidence of collisions due to weak hash + * code calculation. + *

+ * This value may be overridden by defining the system property + * {@code java.util.althashing.threshold}. A property value of {@code 1} + * forces alternative hashing to be used at all times whereas + * {@code 2147483648 } ({@code Integer.MAX_VALUE}) value ensures that + * alternative hashing is never used. + */ + static final int ALTERNATE_HASHING_THRESHOLD_DEFAULT = 0; + + /** + * holds values which can't be initialized until after VM is booted. + */ + private static class Holder { + + // Unsafe mechanics + /** + * + */ + static final sun.misc.Unsafe UNSAFE; + + /** + * Offset of "final" hashmask field we must set in + * readObject() method. + */ + static final long HASHSEED_OFFSET; + + /** + * Table capacity above which to switch to use alternate hashing. + */ + static final int ALTERNATE_HASHING_THRESHOLD; + + static { + String altThreshold = java.security.AccessController.doPrivileged( + new sun.security.action.GetPropertyAction( + "jdk.map.althashing.threshold")); + + int threshold; + try { + threshold = (null != altThreshold) + ? Integer.parseInt(altThreshold) + : ALTERNATE_HASHING_THRESHOLD_DEFAULT; + + if(threshold == -1) { + threshold = Integer.MAX_VALUE; + } + + if(threshold < 0) { + throw new IllegalArgumentException("value must be positive integer."); + } + } catch(IllegalArgumentException failed) { + throw new Error("Illegal value for 'jdk.map.althashing.threshold'", failed); + } + ALTERNATE_HASHING_THRESHOLD = threshold; + + try { + UNSAFE = sun.misc.Unsafe.getUnsafe(); + HASHSEED_OFFSET = UNSAFE.objectFieldOffset( + HashMap.class.getDeclaredField("hashSeed")); + } catch (NoSuchFieldException | SecurityException e) { + throw new Error("Failed to record hashSeed offset", e); + } + } + } + + /** + * If {@code true} then perform alternate hashing to reduce the incidence of + * collisions due to weak hash code calculation. + */ + transient boolean useAltHashing; + + /** + * A randomizing value associated with this instance that is applied to + * hash code of keys to make hash collisions harder to find. + */ + transient final int hashSeed = sun.misc.Hashing.randomHashSeed(this); /** * Constructs an empty HashMap with the specified initial @@ -200,8 +280,10 @@ capacity <<= 1; this.loadFactor = loadFactor; - threshold = (int)(capacity * loadFactor); + threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1); table = new Entry[capacity]; + useAltHashing = sun.misc.VM.isBooted() && + (capacity >= Holder.ALTERNATE_HASHING_THRESHOLD); init(); } @@ -221,10 +303,7 @@ * (16) and the default load factor (0.75). */ public HashMap() { - this.loadFactor = DEFAULT_LOAD_FACTOR; - threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); - table = new Entry[DEFAULT_INITIAL_CAPACITY]; - init(); + this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); } /** @@ -255,13 +334,23 @@ } /** - * Applies a supplemental hash function to a given hashCode, which - * defends against poor quality hash functions. This is critical - * because HashMap uses power-of-two length hash tables, that + * Retrieve object hash code and applies a supplemental hash function to the + * result hash, which defends against poor quality hash functions. This is + * critical because HashMap uses power-of-two length hash tables, that * otherwise encounter collisions for hashCodes that do not differ * in lower bits. Note: Null keys always map to hash 0, thus index 0. */ - static int hash(int h) { + final int hash(Object k) { + int h = 0; + if (useAltHashing) { + h = hashSeed; + if (k instanceof String) { + return h ^ sun.misc.Hashing.stringHash32((String) k); + } + } + + h ^= k.hashCode(); + // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). @@ -314,15 +403,9 @@ public V get(Object key) { if (key == null) return getForNullKey(); - int hash = hash(key.hashCode()); - for (Entry e = table[indexFor(hash, table.length)]; - e != null; - e = e.next) { - Object k; - if (e.hash == hash && ((k = e.key) == key || key.equals(k))) - return e.value; - } - return null; + Entry entry = getEntry(key); + + return null == entry ? null : entry.getValue(); } /** @@ -358,7 +441,7 @@ * for the key. */ final Entry getEntry(Object key) { - int hash = (key == null) ? 0 : hash(key.hashCode()); + int hash = (key == null) ? 0 : hash(key); for (Entry e = table[indexFor(hash, table.length)]; e != null; e = e.next) { @@ -386,7 +469,7 @@ public V put(K key, V value) { if (key == null) return putForNullKey(value); - int hash = hash(key.hashCode()); + int hash = hash(key); int i = indexFor(hash, table.length); for (Entry e = table[i]; e != null; e = e.next) { Object k; @@ -427,7 +510,7 @@ * addEntry. */ private void putForCreate(K key, V value) { - int hash = (key == null) ? 0 : hash(key.hashCode()); + int hash = null == key ? 0 : hash(key); int i = indexFor(hash, table.length); /** @@ -475,28 +558,30 @@ } Entry[] newTable = new Entry[newCapacity]; - transfer(newTable); + boolean oldAltHashing = useAltHashing; + useAltHashing |= sun.misc.VM.isBooted() && + (newCapacity >= Holder.ALTERNATE_HASHING_THRESHOLD); + boolean rehash = oldAltHashing ^ useAltHashing; + transfer(newTable, rehash); table = newTable; - threshold = (int)(newCapacity * loadFactor); + threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1); } /** * Transfers all entries from current table to newTable. */ - void transfer(Entry[] newTable) { - Entry[] src = table; + void transfer(Entry[] newTable, boolean rehash) { int newCapacity = newTable.length; - for (int j = 0; j < src.length; j++) { - Entry e = src[j]; - if (e != null) { - src[j] = null; - do { - Entry next = e.next; - int i = indexFor(e.hash, newCapacity); - e.next = newTable[i]; - newTable[i] = e; - e = next; - } while (e != null); + for (Entry e : table) { + while(null != e) { + Entry next = e.next; + if(rehash) { + e.hash = null == e.key ? 0 : hash(e.key); + } + int i = indexFor(e.hash, newCapacity); + e.next = newTable[i]; + newTable[i] = e; + e = next; } } } @@ -558,7 +643,7 @@ * for this key. */ final Entry removeEntryForKey(Object key) { - int hash = (key == null) ? 0 : hash(key.hashCode()); + int hash = (key == null) ? 0 : hash(key); int i = indexFor(hash, table.length); Entry prev = table[i]; Entry e = prev; @@ -585,7 +670,8 @@ } /** - * Special version of remove for EntrySet. + * Special version of remove for EntrySet using {@code Map.Entry.equals()} + * for matching. */ final Entry removeMapping(Object o) { if (!(o instanceof Map.Entry)) @@ -688,7 +774,7 @@ final K key; V value; Entry next; - final int hash; + int hash; /** * Creates new entry. @@ -762,10 +848,13 @@ * Subclass overrides this to alter the behavior of put method. */ void addEntry(int hash, K key, V value, int bucketIndex) { - Entry e = table[bucketIndex]; - table[bucketIndex] = new Entry<>(hash, key, value, e); - if (size++ >= threshold) + if ((size >= threshold) && (null != table[bucketIndex])) { resize(2 * table.length); + hash = hash(key); + bucketIndex = indexFor(hash, table.length); + } + + createEntry(hash, key, value, bucketIndex); } /** @@ -827,7 +916,6 @@ HashMap.this.removeEntryForKey(k); expectedModCount = modCount; } - } private final class ValueIterator extends HashIterator { @@ -1007,9 +1095,8 @@ s.writeInt(size); // Write out keys and values (alternating) - if (i != null) { - while (i.hasNext()) { - Map.Entry e = i.next(); + if (size > 0) { + for(Map.Entry e : entrySet0()) { s.writeObject(e.getKey()); s.writeObject(e.getValue()); } @@ -1019,32 +1106,58 @@ private static final long serialVersionUID = 362498820763181265L; /** - * Reconstitute the HashMap instance from a stream (i.e., + * Reconstitute the {@code HashMap} instance from a stream (i.e., * deserialize it). */ private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { - // Read in the threshold, loadfactor, and any hidden stuff + // Read in the threshold (ignored), loadfactor, and any hidden stuff s.defaultReadObject(); - + if (loadFactor <= 0 || Float.isNaN(loadFactor)) + throw new InvalidObjectException("Illegal load factor: " + + loadFactor); + + // set hashSeed (can only happen after VM boot) + Holder.UNSAFE.putIntVolatile(this, Holder.HASHSEED_OFFSET, + sun.misc.Hashing.randomHashSeed(this)); + // Read in number of buckets and allocate the bucket array; - int numBuckets = s.readInt(); - table = new Entry[numBuckets]; + s.readInt(); // ignored + // Read number of mappings + int mappings = s.readInt(); + if (mappings < 0) + throw new InvalidObjectException("Illegal mappings count: " + + mappings); + + int initialCapacity = (int) Math.min( + // capacity chosen by number of mappings + // and desired load (if >= 0.25) + mappings * Math.min(1 / loadFactor, 4.0f), + // we have limits... + HashMap.MAXIMUM_CAPACITY); + int capacity = 1; + // find smallest power of two which holds all mappings + while (capacity < initialCapacity) { + capacity <<= 1; + } + + table = new Entry[capacity]; + threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1); + useAltHashing = sun.misc.VM.isBooted() && + (capacity >= Holder.ALTERNATE_HASHING_THRESHOLD); + init(); // Give subclass a chance to do its thing. - // Read in size (number of Mappings) - int size = s.readInt(); - // Read the keys and values, and put the mappings in the HashMap - for (int i=0; i