src/share/classes/java/util/IdentityHashMap.java

Print this page

        

@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 only, as
  * published by the Free Software Foundation.  Oracle designates this

@@ -23,11 +23,10 @@
  * questions.
  */
 
 package java.util;
 
-import java.io.*;
 import java.lang.reflect.Array;
 import java.util.function.BiConsumer;
 import java.util.function.BiFunction;
 import java.util.function.Consumer;
 

@@ -72,11 +71,11 @@
  * this parameter is used to determine the number of buckets initially
  * comprising the hash table.  The precise relationship between the expected
  * maximum size and the number of buckets is unspecified.
  *
  * <p>If the size of the map (the number of key-value mappings) sufficiently
- * exceeds the expected maximum size, the number of buckets is increased
+ * exceeds the expected maximum size, the number of buckets is increased.
  * Increasing the number of buckets ("rehashing") may be fairly expensive, so
  * it pays to create identity hash maps with a sufficiently large expected
  * maximum size.  On the other hand, iteration over collection views requires
  * time proportional to the number of buckets in the hash table, so it
  * pays not to set the expected maximum size too high if you are especially

@@ -155,18 +154,19 @@
      * MUST be a power of two.
      */
     private static final int MINIMUM_CAPACITY = 4;
 
     /**
-     * The maximum capacity, used if a higher value is implicitly specified
-     * by either of the constructors with arguments.
-     * MUST be a power of two <= 1<<29.
+     * The maximum capacity (3 << 28), used if a higher value
+     * is implicitly specified by either of the constructors with arguments.
+     * MAXIMUM_CAPACITY / 3 must be an integer and power of two.
      */
-    private static final int MAXIMUM_CAPACITY = 1 << 29;
+    private static final int MAXIMUM_CAPACITY = 3 << 28;
 
     /**
-     * The table, resized as necessary. Length MUST always be a power of two.
+     * The table, resized as necessary. Length MUST always be a power of two
+     * or 2 * MAXIMUM_CAPACITY
      */
     transient Object[] table; // non-private to simplify nested class access
 
     /**
      * The number of key-value mappings contained in this identity hash map.

@@ -179,15 +179,10 @@
      * The number of modifications, to support fast-fail iterators
      */
     transient int modCount;
 
     /**
-     * The next size value at which to resize (capacity * load factor).
-     */
-    private transient int threshold;
-
-    /**
      * Value representing null keys inside tables.
      */
     static final Object NULL_KEY = new Object();
 
     /**

@@ -227,44 +222,40 @@
                                                + expectedMaxSize);
         init(capacity(expectedMaxSize));
     }
 
     /**
-     * Returns the appropriate capacity for the specified expected maximum
-     * size.  Returns the smallest power of two between MINIMUM_CAPACITY
-     * and MAXIMUM_CAPACITY, inclusive, that is greater than
-     * (3 * expectedMaxSize)/2, if such a number exists.  Otherwise
-     * returns MAXIMUM_CAPACITY.  If (3 * expectedMaxSize)/2 is negative, it
-     * is assumed that overflow has occurred, and MAXIMUM_CAPACITY is returned.
-     */
-    private int capacity(int expectedMaxSize) {
-        // Compute min capacity for expectedMaxSize given a load factor of 2/3
-        int minCapacity = (3 * expectedMaxSize)/2;
-
-        // Compute the appropriate capacity
-        int result;
-        if (minCapacity > MAXIMUM_CAPACITY || minCapacity < 0) {
-            result = MAXIMUM_CAPACITY;
-        } else {
-            result = MINIMUM_CAPACITY;
-            while (result < minCapacity)
-                result <<= 1;
-        }
-        return result;
+     * Returns the appropriate capacity for the given expected maximum size.
+     * Returns the smallest power of two between MINIMUM_CAPACITY and
+     * MAXIMUM_CAPACITY, inclusive, that is greater than (3 *
+     * expectedMaxSize)/2, if such a number exists.  Otherwise returns
+     * MAXIMUM_CAPACITY.
+     */
+    private static int capacity(int expectedMaxSize) {
+        // assert expectedMaxSize >= 0;
+        return Math.min(
+            MAXIMUM_CAPACITY,
+            Math.max(
+                MINIMUM_CAPACITY,
+                expectedMaxSize > Integer.MAX_VALUE / 3
+                    ? MAXIMUM_CAPACITY // 3 * expectedMaxSize would overflow
+                    : Integer.highestOneBit(expectedMaxSize + (expectedMaxSize << 1))
+            )
+        );
     }
 
     /**
      * Initializes object to be an empty map with the specified initial
      * capacity, which is assumed to be a power of two between
      * MINIMUM_CAPACITY and MAXIMUM_CAPACITY inclusive.
      */
     private void init(int initCapacity) {
-        // assert (initCapacity & -initCapacity) == initCapacity; // power of 2
+        // assert initCapacity == MAXIMUM_CAPACITY ||
+        //        (initCapacity & -initCapacity) == initCapacity; // power of 2
         // assert initCapacity >= MINIMUM_CAPACITY;
         // assert initCapacity <= MAXIMUM_CAPACITY;
 
-        threshold = (initCapacity * 2)/3;
         table = new Object[2 * initCapacity];
     }
 
     /**
      * Constructs a new identity hash map containing the keys-value mappings

@@ -302,19 +293,29 @@
     /**
      * Returns index for Object x.
      */
     private static int hash(Object x, int length) {
         int h = System.identityHashCode(x);
-        // Multiply by -127, and left-shift to use least bit as part of hash
-        return ((h << 1) - (h << 8)) & (length - 1);
+        // multiply by -127
+        h -= h << 7;
+        if (length < MAXIMUM_CAPACITY * 2) { // length is 2^n
+            // left-shift to use least bit as part of hash
+            return (h << 1) & (length - 1);
+        }
+        // assert length == MAXIMUM_CAPACITY * 2
+        // clamp
+        h &= (MAXIMUM_CAPACITY / 3 * 4 - 1);
+        // Multiply by 3/2 and reset 0th bit
+        return (h + (h >> 1)) & ~1;
     }
 
     /**
      * Circularly traverses table of size len.
      */
     private static int nextKeyIndex(int i, int len) {
-        return (i + 2 < len ? i + 2 : 0);
+        int i2 = i + 2;
+        return (i2 < len ? i2 : 0);
     }
 
     /**
      * Returns the value to which the specified key is mapped,
      * or {@code null} if this map contains no mapping for the key.

@@ -427,56 +428,70 @@
      * @see     Object#equals(Object)
      * @see     #get(Object)
      * @see     #containsKey(Object)
      */
     public V put(K key, V value) {
-        Object k = maskNull(key);
-        Object[] tab = table;
-        int len = tab.length;
+        final Object k = maskNull(key);
+
+        while (true) {
+            final Object[] tab = table;
+            final int len = tab.length;
         int i = hash(k, len);
 
-        Object item;
-        while ( (item = tab[i]) != null) {
+            for (Object item; (item = tab[i]) != null;
+                 i = nextKeyIndex(i, len)) {
             if (item == k) {
                 @SuppressWarnings("unchecked")
                     V oldValue = (V) tab[i + 1];
                 tab[i + 1] = value;
                 return oldValue;
             }
-            i = nextKeyIndex(i, len);
         }
 
+            final int s = size + 1;
+            // Use optimized form of 3 * s.
+            if (s + (s << 1) < len) {
         modCount++;
         tab[i] = k;
         tab[i + 1] = value;
-        if (++size >= threshold)
-            resize(len); // len == 2 * current capacity.
+                size = s;
         return null;
     }
+            // Next capacity is len, 2 * current capacity.
+            resize(len);
+        }
+    }
 
     /**
-     * Resize the table to hold given capacity.
+     * Resizes the table if necessary to hold given capacity.
      *
      * @param newCapacity the new capacity, must be a power of two.
      */
     private void resize(int newCapacity) {
-        // assert (newCapacity & -newCapacity) == newCapacity; // power of 2
-        int newLength = newCapacity * 2;
+        // assert newCapacity == MAXIMUM_CAPACITY ||
+        //        newCapacity == MAXIMUM_CAPACITY * 2 || // when adding past 66% of MAXIMUM_CAPACITY
+        //        (newCapacity & -newCapacity) == newCapacity; // power of 2
+
+        // when resize is triggered from put() for 2 * MAXIMUM_CAPACITY it means
+        // that we are about to insert new element past 66% of MAXIMUM_CAPACITY
+        // (past max. size of 2^29 - 1)
+        if (newCapacity == 2 * MAXIMUM_CAPACITY) {
+            throw new OutOfMemoryError("Max. size exceeded.");
+        }
+
+        int newLength = Math.min(
+            2 * MAXIMUM_CAPACITY,
+            newCapacity > Integer.MAX_VALUE / 2
+                      ? 2 * MAXIMUM_CAPACITY // newCapacity << 1 would overflow
+                      : newCapacity << 1
+        );
 
         Object[] oldTable = table;
         int oldLength = oldTable.length;
-        if (oldLength == 2*MAXIMUM_CAPACITY) { // can't expand any further
-            if (threshold == MAXIMUM_CAPACITY-1)
-                throw new IllegalStateException("Capacity exhausted.");
-            threshold = MAXIMUM_CAPACITY-1;  // Gigantic map!
-            return;
-        }
-        if (oldLength >= newLength)
-            return;
+        // assert newLength > oldLength
 
         Object[] newTable = new Object[newLength];
-        threshold = newLength / 3;
 
         for (int j = 0; j < oldLength; j += 2) {
             Object key = oldTable[j];
             if (key != null) {
                 Object value = oldTable[j+1];

@@ -502,12 +517,13 @@
      */
     public void putAll(Map<? extends K, ? extends V> m) {
         int n = m.size();
         if (n == 0)
             return;
-        if (n > threshold) // conservatively pre-expand
-            resize(capacity(n));
+        int c = capacity(n);
+        if (c << 1 > table.length)
+            resize(c); // conservatively pre-expand
 
         for (Entry<? extends K, ? extends V> e : m.entrySet())
             put(e.getKey(), e.getValue());
     }
 

@@ -540,11 +556,10 @@
             }
             if (item == null)
                 return null;
             i = nextKeyIndex(i, len);
         }
-
     }
 
     /**
      * Removes the specified key-value mapping from the map if it is present.
      *

@@ -1264,12 +1279,12 @@
 
 
     private static final long serialVersionUID = 8188218128353913216L;
 
     /**
-     * Save the state of the <tt>IdentityHashMap</tt> instance to a stream
-     * (i.e., serialize it).
+     * Saves the state of the <tt>IdentityHashMap</tt> instance to a stream
+     * (i.e., serializes it).
      *
      * @serialData The <i>size</i> of the HashMap (the number of key-value
      *          mappings) (<tt>int</tt>), followed by the key (Object) and
      *          value (Object) for each key-value mapping represented by the
      *          IdentityHashMap.  The key-value mappings are emitted in no

@@ -1293,23 +1308,24 @@
             }
         }
     }
 
     /**
-     * Reconstitute the <tt>IdentityHashMap</tt> instance from a stream (i.e.,
-     * deserialize it).
+     * Reconstitutes the <tt>IdentityHashMap</tt> instance from a stream (i.e.,
+     * deserializes it).
      */
     private void readObject(java.io.ObjectInputStream s)
         throws java.io.IOException, ClassNotFoundException  {
         // Read in any hidden stuff
         s.defaultReadObject();
 
         // Read in size (number of Mappings)
         int size = s.readInt();
-
-        // Allow for 33% growth (i.e., capacity is >= 2* size()).
-        init(capacity((size*4)/3));
+        if (size < 0)
+            throw new java.io.StreamCorruptedException
+                ("Illegal mappings count: " + size);
+        init(capacity(size));
 
         // Read the keys and values, and put the mappings in the table
         for (int i=0; i<size; i++) {
             @SuppressWarnings("unchecked")
                 K key = (K) s.readObject();

@@ -1322,11 +1338,11 @@
     /**
      * The put method for readObject.  It does not resize the table,
      * update modCount, etc.
      */
     private void putForCreate(K key, V value)
-        throws IOException
+        throws java.io.StreamCorruptedException
     {
         Object k = maskNull(key);
         Object[] tab = table;
         int len = tab.length;
         int i = hash(k, len);