/** Constructor for RegularImmutableBiMap that makes no assumptions about the input entries. */ RegularImmutableBiMap(Entry<?, ?>[] entriesToAdd) { int n = entriesToAdd.length; int tableSize = Hashing.closedTableSize(n, MAX_LOAD_FACTOR); this.mask = tableSize - 1; ImmutableMapEntry<K, V>[] keyTable = createEntryArray(tableSize); ImmutableMapEntry<K, V>[] valueTable = createEntryArray(tableSize); ImmutableMapEntry<K, V>[] entries = createEntryArray(n); int hashCode = 0; for (int i = 0; i < n; i++) { @SuppressWarnings("unchecked") Entry<K, V> entry = (Entry<K, V>) entriesToAdd[i]; K key = entry.getKey(); V value = entry.getValue(); checkEntryNotNull(key, value); int keyHash = key.hashCode(); int valueHash = value.hashCode(); int keyBucket = Hashing.smear(keyHash) & mask; int valueBucket = Hashing.smear(valueHash) & mask; ImmutableMapEntry<K, V> nextInKeyBucket = keyTable[keyBucket]; for (ImmutableMapEntry<K, V> keyEntry = nextInKeyBucket; keyEntry != null; keyEntry = keyEntry.getNextInKeyBucket()) { checkNoConflict(!key.equals(keyEntry.getKey()), "key", entry, keyEntry); } ImmutableMapEntry<K, V> nextInValueBucket = valueTable[valueBucket]; for (ImmutableMapEntry<K, V> valueEntry = nextInValueBucket; valueEntry != null; valueEntry = valueEntry.getNextInValueBucket()) { checkNoConflict(!value.equals(valueEntry.getValue()), "value", entry, valueEntry); } ImmutableMapEntry<K, V> newEntry = (nextInKeyBucket == null && nextInValueBucket == null) ? new TerminalEntry<K, V>(key, value) : new NonTerminalBiMapEntry<K, V>(key, value, nextInKeyBucket, nextInValueBucket); keyTable[keyBucket] = newEntry; valueTable[valueBucket] = newEntry; entries[i] = newEntry; hashCode += keyHash ^ valueHash; } this.keyTable = keyTable; this.valueTable = valueTable; this.entries = entries; this.hashCode = hashCode; }
@Override public K get(@Nullable Object value) { if (value == null) { return null; } int bucket = Hashing.smear(value.hashCode()) & mask; for (ImmutableMapEntry<K, V> entry = valueTable[bucket]; entry != null; entry = entry.getNextInValueBucket()) { if (value.equals(entry.getValue())) { return entry.getKey(); } } return null; }
@Override public boolean contains(Object target) { if (target == null) { return false; } for (int i = Hashing.smear(target.hashCode()); true; i++) { Object candidate = table[i & mask]; if (candidate == null) { return false; } if (candidate.equals(target)) { return true; } } }
@Override @Nullable public V get(@Nullable Object key) { if (key == null) { return null; } int bucket = Hashing.smear(key.hashCode()) & mask; for (ImmutableMapEntry<K, V> entry = keyTable[bucket]; entry != null; entry = entry.getNextInKeyBucket()) { if (key.equals(entry.getKey())) { return entry.getValue(); } } return null; }
/** * Constructor for RegularImmutableMap that takes as input an array of {@code TerminalEntry} * entries. Assumes that these entries have already been checked for null. * * <p>This allows reuse of the entry objects from the array in the actual implementation. */ RegularImmutableMap(int size, TerminalEntry<?, ?>[] theEntries) { entries = createEntryArray(size); int tableSize = Hashing.closedTableSize(size, MAX_LOAD_FACTOR); table = createEntryArray(tableSize); mask = tableSize - 1; for (int entryIndex = 0; entryIndex < size; entryIndex++) { @SuppressWarnings("unchecked") TerminalEntry<K, V> entry = (TerminalEntry<K, V>) theEntries[entryIndex]; K key = entry.getKey(); int tableIndex = Hashing.smear(key.hashCode()) & mask; @Nullable ImmutableMapEntry<K, V> existing = table[tableIndex]; // prepend, not append, so the entries can be immutable ImmutableMapEntry<K, V> newEntry = (existing == null) ? entry : new NonTerminalMapEntry<K, V>(entry, existing); table[tableIndex] = newEntry; entries[entryIndex] = newEntry; checkNoConflictInBucket(key, newEntry, existing); } }
@Override public V get(@Nullable Object key) { if (key == null) { return null; } int index = Hashing.smear(key.hashCode()) & mask; for (ImmutableMapEntry<K, V> entry = table[index]; entry != null; entry = entry.getNextInKeyBucket()) { K candidateKey = entry.getKey(); /* * Assume that equals uses the == optimization when appropriate, and that * it would check hash codes as an optimization when appropriate. If we * did these things, it would just make things worse for the most * performance-conscious users. */ if (key.equals(candidateKey)) { return entry.getValue(); } } return null; }
/** Constructor for RegularImmutableMap that makes no assumptions about the input entries. */ RegularImmutableMap(Entry<?, ?>[] theEntries) { int size = theEntries.length; entries = createEntryArray(size); int tableSize = Hashing.closedTableSize(size, MAX_LOAD_FACTOR); table = createEntryArray(tableSize); mask = tableSize - 1; for (int entryIndex = 0; entryIndex < size; entryIndex++) { @SuppressWarnings("unchecked") // all our callers carefully put in only Entry<K, V>s Entry<K, V> entry = (Entry<K, V>) theEntries[entryIndex]; K key = entry.getKey(); V value = entry.getValue(); checkEntryNotNull(key, value); int tableIndex = Hashing.smear(key.hashCode()) & mask; @Nullable ImmutableMapEntry<K, V> existing = table[tableIndex]; // prepend, not append, so the entries can be immutable ImmutableMapEntry<K, V> newEntry = (existing == null) ? new TerminalEntry<K, V>(key, value) : new NonTerminalMapEntry<K, V>(key, value, existing); table[tableIndex] = newEntry; entries[entryIndex] = newEntry; checkNoConflictInBucket(key, newEntry, existing); } }