You can't avoid using IdentityHashMap for special scenes

brief introduction
What should we do if an entity class overrides the hashCode method, and the hashCode returns a fixed value and needs to use the entity key? HashMap can't be used, because HashMap uses the key hashCode high and low to get the remainder of the array length and locate the position. The result of using HashMap is that no matter how many entity objects are put, only one key value pair is actually stored. In fact, the JDK provides a Map specifically to solve such problems. Yes, it is IdentityHashMap. When looking at the source code of IdentityHashMap, we should first make it clear that its key and value are in the same array. table[i] stores the key, and table[i+1] stores the value corresponding to the key.

IdentityHashMap class

public class IdentityHashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, java.io.Serializable, Cloneable

Inherit AbstractMap like HashMap (confirm again that only HashTable in the Map family is a traitor, so it does not inherit AbstractMap)

IdentityHashMap attribute

// Default length
private static final int DEFAULT_CAPACITY = 32;
// Minimum length
private static final int MINIMUM_CAPACITY = 4;
// Maximum length
private static final int MAXIMUM_CAPACITY = 1 << 29;
// Element array
transient Object[] table;
// Number of elements
int size;
// Modification times
transient int modCount;
// null key
static final Object NULL_KEY = new Object();

IdentityHashMap constructor
 

public IdentityHashMap() {
    init(DEFAULT_CAPACITY);
}
public IdentityHashMap(int expectedMaxSize) {
    // Throw exception when length is less than 0
    if (expectedMaxSize < 0)
        throw new IllegalArgumentException("expectedMaxSize is negative: "
                + expectedMaxSize);
    init(capacity(expectedMaxSize));
}
public IdentityHashMap(Map<? extends K, ? extends V> m) {
    this((int) ((1 + m.size()) * 1.1));
    putAll(m);
}

Constructor delegate to init method initialization

private void init(int initCapacity) {
    table = new Object[2 * initCapacity];
}

The initialization length is twice as long as the original one (direct * 2. What posture is this? I've gained insight)

private static int capacity(int expectedMaxSize) {
    return
            (expectedMaxSize > MAXIMUM_CAPACITY / 3) ? MAXIMUM_CAPACITY :
                    (expectedMaxSize <= 2 * MINIMUM_CAPACITY / 3) ? MINIMUM_CAPACITY :
                            Integer.highestOneBit(expectedMaxSize + (expectedMaxSize << 1));
}

It looks a little windy. expectedMaxSize > maximum_ Capability / 3 if expectedMaxSize is greater than one third of the default maximum value, take the default maximum value. Otherwise, follow the logic later, expectedMaxSize < = 2 * minimum_ Capability / 3 if expectedMaxSize is less than or equal to 4 * 2 / 3 (2), take the default minimum value; otherwise, take integer Highestonebit (expectedMaxSize + (expectedMaxSize < < 1)), interpretation of expectedMaxSize + (expectedMaxSize < < 1) is actually three times of expectedMaxSize, integer The function highestonebit is used to take the highest bit on the leftmost side of the binary form, fill all zeros after the high bit, and finally return the result of int type. It shows that all other bits except the high bit are 0, which means that the number must be the nth power of 2. Have you found that the length of nonparametric constructor array is 32 * 2 and the length of parametric constructor is maximum_ Capability, 3 times the original length, take the high or minimum_ Capability, its length is still the n-th power of 2.

After reading the previous HashMap article, we have become very powerful [covering our faces], so here we will no longer analyze one method by one, but only pick a few important methods...

IdentityHashMap cardinality method
Note: if table[i] stores the key, then table[i+1] stores the value corresponding to the key

public int hashCode() {
    int result = 0;
    Object[] tab = table;
    // Traverse the array (i+2 must all be key s)
    for (int i = 0; i < tab.length; i +=2) {
        Object key = tab[i];
        if (key != null) {
            Object k = unmaskNull(key);
            // All keys tab[i] and values tab[i + 1] identityHashCode take XOR
            result += System.identityHashCode(k) ^
                    System.identityHashCode(tab[i + 1]);
        }
    }
    // Return total value
    return result;
}

hashCode method is to add all key values after XOR

public boolean equals(Object o) {
    // Same memory address
    if (o == this) {
        return true;
    } else if (o instanceof IdentityHashMap) {
        // All are IdentityHashMap instances
        IdentityHashMap<?,?> m = (IdentityHashMap<?,?>) o;
        // Is the length consistent
        if (m.size() != size)
            return false;
        // Traverse whether all key values are consistent
        Object[] tab = m.table;
        for (int i = 0; i < tab.length; i+=2) {
            Object k = tab[i];
            if (k != null && !containsMapping(k, tab[i + 1]))
                return false;
        }
        return true;
    } else if (o instanceof Map) {
        Map<?,?> m = (Map<?,?>)o;
        return entrySet().equals(m.entrySet());
    } else {
        return false;  // o is not a Map
    }
}

IdentityHashMap determines where the key is located

private static int hash(Object x, int length) {
    // Use system Identityhashcode (x) calculates the hash value
    int h = System.identityHashCode(x);
    // &(length - 1) it's no stranger to the remainder taking process. If you don't know, see the derivation in HashMap
    return ((h << 1) - (h << 8)) & (length - 1);
}

There is a question (H < < 1) - (H < < 8) why can't it be negative? Exploring

Location of the next key of IdentityHashMap

private static int nextKeyIndex(int i, int len) {
    return (i + 2 < len ? i + 2 : 0);
}

The current position plus 2 is the next available position, and plus 1 is the value corresponding to the current key

IdentityHashMap add

public V put(K key, V value) {
  // Convert null key to Object(NULL_KEY)
  final Object k = maskNull(key);
  // retryAfterResize: is the tag usage in java
  retryAfterResize: for (;;) {
    final Object[] tab = table;
    final int len = tab.length;
    // Similarly, the array subscript position is calculated first
    int i = hash(k, len);
    // First find out whether there is the same key in the array, and replace it if found
    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;
      }
    }
    // If it is not found, the insertion is executed, and the number of key value pairs stored first is + 1
    final int s = size + 1;
    // If the number of elements * 3 is greater than the length of the array, the capacity will be expanded, and the size after expansion will be twice the original length
    if (s + (s << 1) > len && resize(len))
      continue retryAfterResize;
    // Number of modifications plus 1
    modCount++;
    // tab[i] save key, tab[i+1] save value
    tab[i] = k;
    tab[i + 1] = value;
    size = s;
    return null;
  }
}

IdentityHashMap capacity expansion

private boolean resize(int newCapacity) {
  // The new length is twice as long as the original
  int newLength = newCapacity * 2;
  Object[] oldTable = table;
  int oldLength = oldTable.length;
  // If the maximum capacity has been reached, the expansion fails
  if (oldLength == 2 * MAXIMUM_CAPACITY) {
    // If the maximum capacity has been reached, (leave one for null, otherwise the loop cannot be exited)
    if (size == MAXIMUM_CAPACITY - 1)
      throw new IllegalStateException("Capacity exhausted.");
    return false;
  }
  if (oldLength >= newLength)
    return false;
  //The following is to loop the old table and insert the value into the new one
  Object[] newTable = new Object[newLength];
  for (int j = 0; j < oldLength; j += 2) {
    Object key = oldTable[j];
    if (key != null) {
      Object value = oldTable[j+1];
      oldTable[j] = null;
      oldTable[j+1] = null;
      int i = hash(key, newLength);
      while (newTable[i] != null)
        i = nextKeyIndex(i, newLength);
      newTable[i] = key;
      newTable[i + 1] = value;
    }
  }
  table = newTable;
  return true;
}

IdentityHashMap value

public V get(Object key) {
    Object k = maskNull(key);
    Object[] tab = table;
    int len = tab.length;
    int i = hash(k, len);
    // Dead loop traversal table array
    while (true) {
        Object item = tab[i];
        // If the same key is hit, the value of table[i+1] is returned
        if (item == k)
            return (V) tab[i + 1];
        // If the traversal array has not been hit, it returns null
        if (item == null)
            return null;
        i = nextKeyIndex(i, len);
    }
}

IdentityHashMap deletion

public V remove(Object key) {
    Object k = maskNull(key);
    Object[] tab = table;
    int len = tab.length;
    // Determine location
    int i = hash(k, len);
    while (true) {
        // Get key
        Object item = tab[i];
        if (item == k) {
            modCount++;
            size--;
            // Get original value
            V oldValue = (V) tab[i + 1];
            // Clear the location of the original value
            tab[i + 1] = null;
            // Clear key location
            tab[i] = null;
            closeDeletion(i);
            // Return original value
            return oldValue;
        }
        if (item == null)
            return null;
        // Index plus 2 not found
        i = nextKeyIndex(i, len);
    }
}

IdentityHashMap empty

public void clear() {
    modCount++;
    Object[] tab = table;
    // Clear the tab here. Each element is equivalent to the key and value will be cleared
    for (int i = 0; i < tab.length; i++)
        tab[i] = null;
    size = 0;
}

​​​​​​​

Added by ryclem on Tue, 01 Feb 2022 07:10:39 +0200