--- old/src/share/classes/java/util/HashMap.java 2012-05-22 21:27:25.000000000 -0700 +++ new/src/share/classes/java/util/HashMap.java 2012-05-22 21:27:25.000000000 -0700 @@ -174,6 +174,35 @@ * the HashMap fail-fast. (See ConcurrentModificationException). */ transient int modCount; + + private static class Holder { + /** + * + */ + private static final sun.misc.Unsafe UNSAFE; + + /** + * Offset of "final" hashmask field we must set in + * readObject() method. + */ + private static final long HASHMASK_OFFSET; + + static { + try { + UNSAFE = sun.misc.Unsafe.getUnsafe(); + HASHMASK_OFFSET = UNSAFE.objectFieldOffset( + HashMap.class.getDeclaredField("hashMask")); + } catch (NoSuchFieldException | SecurityException e) { + throw new InternalError("Failed to record hashMask offset", e); + } + } + } + + /** + * A random mask value that is used for hashcode values associated with this + * instance to make hash collisions harder to find. + */ + transient final int hashMask = sun.misc.Hashing.makeHashMask(this); /** * Constructs an empty HashMap with the specified initial @@ -200,7 +229,7 @@ capacity <<= 1; this.loadFactor = loadFactor; - threshold = (int)(capacity * loadFactor); + threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1); table = new Entry[capacity]; init(); } @@ -221,10 +250,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,18 +281,27 @@ } /** - * 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. + * in lower bits. */ - static int hash(int h) { + final int hash(Object k) { + int h = hashMask; + if ((0 != h) && (k instanceof String)) { + return h ^ ((String)k).hash32(); + } + + 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). h ^= (h >>> 20) ^ (h >>> 12); - return h ^ (h >>> 7) ^ (h >>> 4); + h ^= (h >>> 7) ^ (h >>> 4); + + return h; } /** @@ -313,17 +348,9 @@ */ @SuppressWarnings("unchecked") public V get(Object key) { - if (key == null) - return (V)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 (V)e.value; - } - return null; + Entry entry = getEntry(key); + + return null == entry ? null : entry.getValue(); } /** @@ -360,7 +387,7 @@ */ @SuppressWarnings("unchecked") 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) { @@ -388,7 +415,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); @SuppressWarnings("unchecked") Entry e = (Entry)table[i]; @@ -433,7 +460,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); /** @@ -484,7 +511,7 @@ Entry[] newTable = new Entry[newCapacity]; transfer(newTable); table = newTable; - threshold = (int)(newCapacity * loadFactor); + threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1); } /** @@ -494,19 +521,17 @@ void transfer(Entry[] newTable) { Entry[] src = table; int newCapacity = newTable.length; - for (int j = 0; j < src.length; j++) { - Entry e = (Entry)src[j]; - if (e != null) { - src[j] = null; - do { - Entry next = e.next; - int i = indexFor(e.hash, newCapacity); - e.next = (Entry)newTable[i]; - newTable[i] = e; - e = next; - } while (e != null); + for (int j = 0; j < src.length; j++ ) { + Entry e = (Entry) src[j]; + while(null != e) { + Entry next = e.next; + int i = indexFor(e.hash, newCapacity); + e.next = (Entry) newTable[i]; + newTable[i] = e; + e = next; } } + Arrays.fill(table, null); } /** @@ -566,7 +591,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); @SuppressWarnings("unchecked") Entry prev = (Entry)table[i]; @@ -594,7 +619,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)) @@ -773,11 +799,13 @@ * Subclass overrides this to alter the behavior of put method. */ void addEntry(int hash, K key, V value, int bucketIndex) { - @SuppressWarnings("unchecked") - Entry e = (Entry)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); } /** @@ -841,7 +869,6 @@ HashMap.this.removeEntryForKey(k); expectedModCount = modCount; } - } private final class ValueIterator extends HashIterator { @@ -1021,9 +1048,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()); } @@ -1033,26 +1059,50 @@ 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 hashMask + Holder.UNSAFE.putIntVolatile(this, Holder.HASHMASK_OFFSET, + sun.misc.Hashing.makeHashMask(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); 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