All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.common.collect.HashBiMap Maven / Gradle / Ivy

Go to download

This artifact provides a single jar that contains all classes required to use remote Jakarta Enterprise Beans and Jakarta Messaging, including all dependencies. It is intended for use by those not using maven, maven users should just import the Jakarta Enterprise Beans and Jakarta Messaging BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up with different versions on classes on the class path).

There is a newer version: 35.0.0.Beta1
Show newest version
/*
 * Copyright (C) 2007 The Guava Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package com.google.common.collect;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.CollectPreconditions.checkNonnegative;
import static com.google.common.collect.CollectPreconditions.checkRemove;
import static com.google.common.collect.Hashing.smearedHash;

import com.google.common.annotations.GwtCompatible;
import com.google.common.annotations.GwtIncompatible;
import com.google.common.base.Objects;
import com.google.common.collect.Maps.IteratorBasedAbstractMap;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.j2objc.annotations.RetainedWith;
import com.google.j2objc.annotations.WeakOuter;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Arrays;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import org.checkerframework.checker.nullness.compatqual.MonotonicNonNullDecl;
import org.checkerframework.checker.nullness.compatqual.NullableDecl;

/**
 * A {@link BiMap} backed by two hash tables. This implementation allows null keys and values. A
 * {@code HashBiMap} and its inverse are both serializable.
 *
 * 

This implementation guarantees insertion-based iteration order of its keys. * *

See the Guava User Guide article on {@code BiMap} . * * @author Louis Wasserman * @author Mike Bostock * @since 2.0 */ @GwtCompatible(emulated = true) public final class HashBiMap extends IteratorBasedAbstractMap implements BiMap, Serializable { /** Returns a new, empty {@code HashBiMap} with the default initial capacity (16). */ public static HashBiMap create() { return create(16); } /** * Constructs a new, empty bimap with the specified expected size. * * @param expectedSize the expected number of entries * @throws IllegalArgumentException if the specified expected size is negative */ public static HashBiMap create(int expectedSize) { return new HashBiMap<>(expectedSize); } /** * Constructs a new bimap containing initial values from {@code map}. The bimap is created with an * initial capacity sufficient to hold the mappings in the specified map. */ public static HashBiMap create(Map map) { HashBiMap bimap = create(map.size()); bimap.putAll(map); return bimap; } private static final class BiEntry extends ImmutableEntry { final int keyHash; final int valueHash; @NullableDecl BiEntry nextInKToVBucket; @NullableDecl BiEntry nextInVToKBucket; @NullableDecl BiEntry nextInKeyInsertionOrder; @NullableDecl BiEntry prevInKeyInsertionOrder; BiEntry(K key, int keyHash, V value, int valueHash) { super(key, value); this.keyHash = keyHash; this.valueHash = valueHash; } } private static final double LOAD_FACTOR = 1.0; private transient BiEntry[] hashTableKToV; private transient BiEntry[] hashTableVToK; @NullableDecl private transient BiEntry firstInKeyInsertionOrder; @NullableDecl private transient BiEntry lastInKeyInsertionOrder; private transient int size; private transient int mask; private transient int modCount; private HashBiMap(int expectedSize) { init(expectedSize); } private void init(int expectedSize) { checkNonnegative(expectedSize, "expectedSize"); int tableSize = Hashing.closedTableSize(expectedSize, LOAD_FACTOR); this.hashTableKToV = createTable(tableSize); this.hashTableVToK = createTable(tableSize); this.firstInKeyInsertionOrder = null; this.lastInKeyInsertionOrder = null; this.size = 0; this.mask = tableSize - 1; this.modCount = 0; } /** * Finds and removes {@code entry} from the bucket linked lists in both the key-to-value direction * and the value-to-key direction. */ private void delete(BiEntry entry) { int keyBucket = entry.keyHash & mask; BiEntry prevBucketEntry = null; for (BiEntry bucketEntry = hashTableKToV[keyBucket]; true; bucketEntry = bucketEntry.nextInKToVBucket) { if (bucketEntry == entry) { if (prevBucketEntry == null) { hashTableKToV[keyBucket] = entry.nextInKToVBucket; } else { prevBucketEntry.nextInKToVBucket = entry.nextInKToVBucket; } break; } prevBucketEntry = bucketEntry; } int valueBucket = entry.valueHash & mask; prevBucketEntry = null; for (BiEntry bucketEntry = hashTableVToK[valueBucket]; true; bucketEntry = bucketEntry.nextInVToKBucket) { if (bucketEntry == entry) { if (prevBucketEntry == null) { hashTableVToK[valueBucket] = entry.nextInVToKBucket; } else { prevBucketEntry.nextInVToKBucket = entry.nextInVToKBucket; } break; } prevBucketEntry = bucketEntry; } if (entry.prevInKeyInsertionOrder == null) { firstInKeyInsertionOrder = entry.nextInKeyInsertionOrder; } else { entry.prevInKeyInsertionOrder.nextInKeyInsertionOrder = entry.nextInKeyInsertionOrder; } if (entry.nextInKeyInsertionOrder == null) { lastInKeyInsertionOrder = entry.prevInKeyInsertionOrder; } else { entry.nextInKeyInsertionOrder.prevInKeyInsertionOrder = entry.prevInKeyInsertionOrder; } size--; modCount++; } private void insert(BiEntry entry, @NullableDecl BiEntry oldEntryForKey) { int keyBucket = entry.keyHash & mask; entry.nextInKToVBucket = hashTableKToV[keyBucket]; hashTableKToV[keyBucket] = entry; int valueBucket = entry.valueHash & mask; entry.nextInVToKBucket = hashTableVToK[valueBucket]; hashTableVToK[valueBucket] = entry; if (oldEntryForKey == null) { entry.prevInKeyInsertionOrder = lastInKeyInsertionOrder; entry.nextInKeyInsertionOrder = null; if (lastInKeyInsertionOrder == null) { firstInKeyInsertionOrder = entry; } else { lastInKeyInsertionOrder.nextInKeyInsertionOrder = entry; } lastInKeyInsertionOrder = entry; } else { entry.prevInKeyInsertionOrder = oldEntryForKey.prevInKeyInsertionOrder; if (entry.prevInKeyInsertionOrder == null) { firstInKeyInsertionOrder = entry; } else { entry.prevInKeyInsertionOrder.nextInKeyInsertionOrder = entry; } entry.nextInKeyInsertionOrder = oldEntryForKey.nextInKeyInsertionOrder; if (entry.nextInKeyInsertionOrder == null) { lastInKeyInsertionOrder = entry; } else { entry.nextInKeyInsertionOrder.prevInKeyInsertionOrder = entry; } } size++; modCount++; } private BiEntry seekByKey(@NullableDecl Object key, int keyHash) { for (BiEntry entry = hashTableKToV[keyHash & mask]; entry != null; entry = entry.nextInKToVBucket) { if (keyHash == entry.keyHash && Objects.equal(key, entry.key)) { return entry; } } return null; } private BiEntry seekByValue(@NullableDecl Object value, int valueHash) { for (BiEntry entry = hashTableVToK[valueHash & mask]; entry != null; entry = entry.nextInVToKBucket) { if (valueHash == entry.valueHash && Objects.equal(value, entry.value)) { return entry; } } return null; } @Override public boolean containsKey(@NullableDecl Object key) { return seekByKey(key, smearedHash(key)) != null; } @Override public boolean containsValue(@NullableDecl Object value) { return seekByValue(value, smearedHash(value)) != null; } @NullableDecl @Override public V get(@NullableDecl Object key) { return Maps.valueOrNull(seekByKey(key, smearedHash(key))); } @CanIgnoreReturnValue @Override public V put(@NullableDecl K key, @NullableDecl V value) { return put(key, value, false); } private V put(@NullableDecl K key, @NullableDecl V value, boolean force) { int keyHash = smearedHash(key); int valueHash = smearedHash(value); BiEntry oldEntryForKey = seekByKey(key, keyHash); if (oldEntryForKey != null && valueHash == oldEntryForKey.valueHash && Objects.equal(value, oldEntryForKey.value)) { return value; } BiEntry oldEntryForValue = seekByValue(value, valueHash); if (oldEntryForValue != null) { if (force) { delete(oldEntryForValue); } else { throw new IllegalArgumentException("value already present: " + value); } } BiEntry newEntry = new BiEntry<>(key, keyHash, value, valueHash); if (oldEntryForKey != null) { delete(oldEntryForKey); insert(newEntry, oldEntryForKey); oldEntryForKey.prevInKeyInsertionOrder = null; oldEntryForKey.nextInKeyInsertionOrder = null; return oldEntryForKey.value; } else { insert(newEntry, null); rehashIfNecessary(); return null; } } @CanIgnoreReturnValue @Override public V forcePut(@NullableDecl K key, @NullableDecl V value) { return put(key, value, true); } @NullableDecl private K putInverse(@NullableDecl V value, @NullableDecl K key, boolean force) { int valueHash = smearedHash(value); int keyHash = smearedHash(key); BiEntry oldEntryForValue = seekByValue(value, valueHash); BiEntry oldEntryForKey = seekByKey(key, keyHash); if (oldEntryForValue != null && keyHash == oldEntryForValue.keyHash && Objects.equal(key, oldEntryForValue.key)) { return key; } else if (oldEntryForKey != null && !force) { throw new IllegalArgumentException("key already present: " + key); } /* * The ordering here is important: if we deleted the key entry and then the value entry, * the key entry's prev or next pointer might point to the dead value entry, and when we * put the new entry in the key entry's position in iteration order, it might invalidate * the linked list. */ if (oldEntryForValue != null) { delete(oldEntryForValue); } if (oldEntryForKey != null) { delete(oldEntryForKey); } BiEntry newEntry = new BiEntry<>(key, keyHash, value, valueHash); insert(newEntry, oldEntryForKey); if (oldEntryForKey != null) { oldEntryForKey.prevInKeyInsertionOrder = null; oldEntryForKey.nextInKeyInsertionOrder = null; } if (oldEntryForValue != null) { oldEntryForValue.prevInKeyInsertionOrder = null; oldEntryForValue.nextInKeyInsertionOrder = null; } rehashIfNecessary(); return Maps.keyOrNull(oldEntryForValue); } private void rehashIfNecessary() { BiEntry[] oldKToV = hashTableKToV; if (Hashing.needsResizing(size, oldKToV.length, LOAD_FACTOR)) { int newTableSize = oldKToV.length * 2; this.hashTableKToV = createTable(newTableSize); this.hashTableVToK = createTable(newTableSize); this.mask = newTableSize - 1; this.size = 0; for (BiEntry entry = firstInKeyInsertionOrder; entry != null; entry = entry.nextInKeyInsertionOrder) { insert(entry, entry); } this.modCount++; } } @SuppressWarnings("unchecked") private BiEntry[] createTable(int length) { return new BiEntry[length]; } @CanIgnoreReturnValue @Override public V remove(@NullableDecl Object key) { BiEntry entry = seekByKey(key, smearedHash(key)); if (entry == null) { return null; } else { delete(entry); entry.prevInKeyInsertionOrder = null; entry.nextInKeyInsertionOrder = null; return entry.value; } } @Override public void clear() { size = 0; Arrays.fill(hashTableKToV, null); Arrays.fill(hashTableVToK, null); firstInKeyInsertionOrder = null; lastInKeyInsertionOrder = null; modCount++; } @Override public int size() { return size; } abstract class Itr implements Iterator { BiEntry next = firstInKeyInsertionOrder; BiEntry toRemove = null; int expectedModCount = modCount; int remaining = size(); @Override public boolean hasNext() { if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } return next != null && remaining > 0; } @Override public T next() { if (!hasNext()) { throw new NoSuchElementException(); } BiEntry entry = next; next = entry.nextInKeyInsertionOrder; toRemove = entry; remaining--; return output(entry); } @Override public void remove() { if (modCount != expectedModCount) { throw new ConcurrentModificationException(); } checkRemove(toRemove != null); delete(toRemove); expectedModCount = modCount; toRemove = null; } abstract T output(BiEntry entry); } @Override public Set keySet() { return new KeySet(); } @WeakOuter private final class KeySet extends Maps.KeySet { KeySet() { super(HashBiMap.this); } @Override public Iterator iterator() { return new Itr() { @Override K output(BiEntry entry) { return entry.key; } }; } @Override public boolean remove(@NullableDecl Object o) { BiEntry entry = seekByKey(o, smearedHash(o)); if (entry == null) { return false; } else { delete(entry); entry.prevInKeyInsertionOrder = null; entry.nextInKeyInsertionOrder = null; return true; } } } @Override public Set values() { return inverse().keySet(); } @Override Iterator> entryIterator() { return new Itr>() { @Override Entry output(BiEntry entry) { return new MapEntry(entry); } class MapEntry extends AbstractMapEntry { BiEntry delegate; MapEntry(BiEntry entry) { this.delegate = entry; } @Override public K getKey() { return delegate.key; } @Override public V getValue() { return delegate.value; } @Override public V setValue(V value) { V oldValue = delegate.value; int valueHash = smearedHash(value); if (valueHash == delegate.valueHash && Objects.equal(value, oldValue)) { return value; } checkArgument(seekByValue(value, valueHash) == null, "value already present: %s", value); delete(delegate); BiEntry newEntry = new BiEntry<>(delegate.key, delegate.keyHash, value, valueHash); insert(newEntry, delegate); delegate.prevInKeyInsertionOrder = null; delegate.nextInKeyInsertionOrder = null; expectedModCount = modCount; if (toRemove == delegate) { toRemove = newEntry; } delegate = newEntry; return oldValue; } } }; } @Override public void forEach(BiConsumer action) { checkNotNull(action); for (BiEntry entry = firstInKeyInsertionOrder; entry != null; entry = entry.nextInKeyInsertionOrder) { action.accept(entry.key, entry.value); } } @Override public void replaceAll(BiFunction function) { checkNotNull(function); BiEntry oldFirst = firstInKeyInsertionOrder; clear(); for (BiEntry entry = oldFirst; entry != null; entry = entry.nextInKeyInsertionOrder) { put(entry.key, function.apply(entry.key, entry.value)); } } @MonotonicNonNullDecl @RetainedWith private transient BiMap inverse; @Override public BiMap inverse() { BiMap result = inverse; return (result == null) ? inverse = new Inverse() : result; } private final class Inverse extends IteratorBasedAbstractMap implements BiMap, Serializable { BiMap forward() { return HashBiMap.this; } @Override public int size() { return size; } @Override public void clear() { forward().clear(); } @Override public boolean containsKey(@NullableDecl Object value) { return forward().containsValue(value); } @Override public K get(@NullableDecl Object value) { return Maps.keyOrNull(seekByValue(value, smearedHash(value))); } @CanIgnoreReturnValue @Override public K put(@NullableDecl V value, @NullableDecl K key) { return putInverse(value, key, false); } @Override public K forcePut(@NullableDecl V value, @NullableDecl K key) { return putInverse(value, key, true); } @Override public K remove(@NullableDecl Object value) { BiEntry entry = seekByValue(value, smearedHash(value)); if (entry == null) { return null; } else { delete(entry); entry.prevInKeyInsertionOrder = null; entry.nextInKeyInsertionOrder = null; return entry.key; } } @Override public BiMap inverse() { return forward(); } @Override public Set keySet() { return new InverseKeySet(); } @WeakOuter private final class InverseKeySet extends Maps.KeySet { InverseKeySet() { super(Inverse.this); } @Override public boolean remove(@NullableDecl Object o) { BiEntry entry = seekByValue(o, smearedHash(o)); if (entry == null) { return false; } else { delete(entry); return true; } } @Override public Iterator iterator() { return new Itr() { @Override V output(BiEntry entry) { return entry.value; } }; } } @Override public Set values() { return forward().keySet(); } @Override Iterator> entryIterator() { return new Itr>() { @Override Entry output(BiEntry entry) { return new InverseEntry(entry); } class InverseEntry extends AbstractMapEntry { BiEntry delegate; InverseEntry(BiEntry entry) { this.delegate = entry; } @Override public V getKey() { return delegate.value; } @Override public K getValue() { return delegate.key; } @Override public K setValue(K key) { K oldKey = delegate.key; int keyHash = smearedHash(key); if (keyHash == delegate.keyHash && Objects.equal(key, oldKey)) { return key; } checkArgument(seekByKey(key, keyHash) == null, "value already present: %s", key); delete(delegate); BiEntry newEntry = new BiEntry<>(key, keyHash, delegate.value, delegate.valueHash); delegate = newEntry; insert(newEntry, null); expectedModCount = modCount; return oldKey; } } }; } @Override public void forEach(BiConsumer action) { checkNotNull(action); HashBiMap.this.forEach((k, v) -> action.accept(v, k)); } @Override public void replaceAll(BiFunction function) { checkNotNull(function); BiEntry oldFirst = firstInKeyInsertionOrder; clear(); for (BiEntry entry = oldFirst; entry != null; entry = entry.nextInKeyInsertionOrder) { put(entry.value, function.apply(entry.value, entry.key)); } } Object writeReplace() { return new InverseSerializedForm<>(HashBiMap.this); } } private static final class InverseSerializedForm implements Serializable { private final HashBiMap bimap; InverseSerializedForm(HashBiMap bimap) { this.bimap = bimap; } Object readResolve() { return bimap.inverse(); } } /** * @serialData the number of entries, first key, first value, second key, second value, and so on. */ @GwtIncompatible // java.io.ObjectOutputStream private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); Serialization.writeMap(this, stream); } @GwtIncompatible // java.io.ObjectInputStream private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); int size = Serialization.readCount(stream); init(16); // resist hostile attempts to allocate gratuitous heap Serialization.populateMap(this, stream, size); } @GwtIncompatible // Not needed in emulated source private static final long serialVersionUID = 0; }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy