寇油 发表于 2025-6-3 10:52:11

【Guava】BiMap&Multimap&Multiset

BiMap

Map 可以实现 key -> value 的映射,如果想要 value -> key 的映射,就需要定义两个 Map,并且同步更新,很不优雅。Guava 提供了 BiMap 支持支持双向的映射关系,常用实现有HashMap, EnumBiMap, EnumHashBiMap...。
而它对key和value严格的保证唯一性。如果使用put方法添加相同的value值或key值则会抛出异常:java.lang.IllegalArgumentException,如果使用forcePut方法添加则会覆盖掉原来的value值。
BiMap<String, Integer> biMap = HashBiMap.create();
biMap.put("A", 100);

// 删除已存在的 KV,重新添加 KV
biMap.forcePut("A", 200);

// 获取反向映射
BiMap<Integer, String> inverse = biMap.inverse();
log.debug("{}", inverse.get(100));这里主要使用HashBiMap 进行分析
成员变量

private static final double LOAD_FACTOR = 1.0D;
// BiEntry是HashBiMap中为的Map.Entry接口的实现类,这里定义了两个BiEntry,一个是实现使用Key找到value的,另一个是实现使用value找到key的
private transient HashBiMap.BiEntry<K, V>[] hashTableKToV;
private transient HashBiMap.BiEntry<K, V>[] hashTableVToK;
private transient int size;
private transient int mask;
private transient int modCount;
private transient BiMap<V, K> inverse;HashMap做的是唯一key值对应的value可以不唯一,而Bimap做的是唯一key值,value值也要唯一,方便从key找到value,从value找到key
private static final class BiEntry<K, V> extends ImmutableEntry<K, V> {
    //key的hash值
    final int keyHash;
    //value的hash值
    final int valueHash;
    @Nullable
    //为key链表做的指向下一个节点的变量
    HashBiMap.BiEntry<K, V> nextInKToVBucket;
    @Nullable
    //为value链表做的指向下一个节点的变量
    HashBiMap.BiEntry<K, V> nextInVToKBucket;
    BiEntry(K key, int keyHash, V value, int valueHash) {
      super(key, value);
      this.keyHash = keyHash;
      this.valueHash = valueHash;
    }
}对比一下HashMap的Node源码:
static class Node<K,V> implements Map.Entry<K,V> {
    //因为HashMap实现的功能只需要key找到value,所以这里的hash值默认就是key的hash值
    final int hash;
    final K key;
    V value;
    //在HashMap中的链表只做key的链表就好,所以只需要一个指向下一个节点的变量
    Node<K,V> next;
}构造方法

//传入期望容器长度
private HashBiMap(int expectedSize) {
    this.init(expectedSize);
}可以看到构造方法是私有的,所以在类中一定会有静态方法构造器会用到这个私有的构造方法。
这个构造方法调用了init方法,可以看一下init方法的源码:
private void init(int expectedSize) {
    CollectPreconditions.checkNonnegative(expectedSize, "expectedSize");
    //经过closedTableSize方法运算达到期望的实际值
    int tableSize = Hashing.closedTableSize(expectedSize, 1.0D);
    //初始化key和value存储链表的数组
    this.hashTableKToV = this.createTable(tableSize);
    this.hashTableVToK = this.createTable(tableSize);
    //初始化mask为数组最大小标值
    this.mask = tableSize - 1;
    //初始化modCount值为0
    this.modCount = 0;
    //初始化size值为0
    this.size = 0;
}静态方法构造器

public static <K, V> HashBiMap<K, V> create() {
    //调用另一个create构造器,期望长度为16
    return create(16);
}
public static <K, V> HashBiMap<K, V> create(int expectedSize) {
    //直接创建一个长度为expectedSize的HashBiMap
    return new HashBiMap(expectedSize);
}
public static <K, V> HashBiMap<K, V> create(Map<? extends K, ? extends V> map) {
    //创建一个与传入map相同长度的biMap
    HashBiMap bimap = create(map.size());
    //然后将传入map的值全部赋值给新的BiMap
    bimap.putAll(map);
    return bimap;
}Multiset的接口中方法的实现在AbstractMapBasedMultiset抽象类中,下面针对AbstractMapBasedMultiset类的存储数据结构。add、remove、count和迭代器的实现进行分析
存储数据结构

public V put(@Nullable K key, @Nullable V value) {
    return this.put(key, value, false);
}
public V forcePut(@Nullable K key, @Nullable V value) {
    return this.put(key, value, true);
}构造方法

private V put(@Nullable K key, @Nullable V value, boolean force) {
    //获取传入key的hash值
    int keyHash = hash(key);
    //获取传入value的hash值
    int valueHash = hash(value);
    //根据key的值和它的hash值查找是否存在这个节点,seekByKey方法就是遍历了这个keyhash所映射的下标上的链表进行查找。
    HashBiMap.BiEntry oldEntryForKey = this.seekByKey(key, keyHash);
    if(oldEntryForKey != null && valueHash == oldEntryForKey.valueHash && Objects.equal(value, oldEntryForKey.value)) {
      //如果这个key值存在,并且value也相等,则返回这个value
      return value;
    } else {
      //使用seekByValue查找这个value是否存在
      HashBiMap.BiEntry oldEntryForValue = this.seekByValue(value, valueHash);
      if(oldEntryForValue != null) {
                        //如果存在,则判断force(第三个参数)是否为false
            if(!force) {//Value已经存在了,因此要判断是否允许强制插入
                //如果force(第三个参数)为false
                //则直接抛出异常
                String newEntry1 = String.valueOf(String.valueOf(value));
                throw new IllegalArgumentException((new StringBuilder(23 + newEntry1.length())).append("value already present: ").append(newEntry1).toString());
            }
            //如果force(第三个参数)为true,则删除这个节点,这个方法是双向删除
            this.delete(oldEntryForValue);
      }
      //如果key存在,则删除这个节点
      if(oldEntryForKey != null) {
            this.delete(oldEntryForKey);
      }
      //根据key,value,keyHash,valueHash创建一个BiEntry
      HashBiMap.BiEntry newEntry = new HashBiMap.BiEntry(key, keyHash, value, valueHash);
      //插入这个节点。
      this.insert(newEntry);
      //插入完成,刷新一下,看看是否需要扩容
      this.rehashIfNecessary();
      return oldEntryForKey == null?null:oldEntryForKey.value;
    }
}add方法

private void insert(HashBiMap.BiEntry<K, V> entry) {
    //计算出这个节点在key容器中的下标位置
    int keyBucket = entry.keyHash & this.mask;
    //使当前节点的keynext指向当前下标位置上
    entry.nextInKToVBucket = this.hashTableKToV;
    //将当前节点赋值给这个下标位置
    this.hashTableKToV = entry;

    //value如key一样
    int valueBucket = entry.valueHash & this.mask;
    entry.nextInVToKBucket = this.hashTableVToK;
    this.hashTableVToK = entry;
    //size加1
    ++this.size;
    ++this.modCount;
}Count方法

// 列表实现
ListMultimap<String, Integer> listMultimap = MultimapBuilder.hashKeys().arrayListValues().build();
// 集合实现
SetMultimap<String, Integer> setMultimap = MultimapBuilder.treeKeys().hashSetValues().build();

listMultimap.put("A", 1);
listMultimap.put("A", 2);
listMultimap.put("B", 1);
// {A=, B=}
log.debug("{}", listMultimap);
// ,不存在则返回一个空集合
log.debug("{}", listMultimap.get("A"));
// 移除 key 关联的所有 value
List<Integer> valList = listMultimap.removeAll("A");

// 返回普通 map 的视图,仅支持 remove,不能 put,且会更新原始的 listMultimap
Map<String, Collection<Integer>> map = listMultimap.asMap();迭代器

public static <K, V> HashMultimap<K, V> create() {
        //new一个HashMultimap,不传入任何值
    return new HashMultimap();
}
public static <K, V> HashMultimap<K, V> create(int expectedKeys, int expectedValuesPerKey) {
        //new一个HashHultimap,传入两个值,一个是期望key的长度,另一个是期望value的长度
    return new HashMultimap(expectedKeys, expectedValuesPerKey);
}
public static <K, V> HashMultimap<K, V> create(Multimap<? extends K, ? extends V> multimap) {
        //传入一个Multimap值
    return new HashMultimap(multimap);
}Multiset中有一个实现了Iterator接口的类:
private class MapBasedMultisetIterator implements Iterator {    final Iterator entryIterator;    java.util.Map.Entry currentEntry;    int occurrencesLeft;    boolean canRemove;    MapBasedMultisetIterator() {      //获取当前map容器的迭代器      this.entryIterator = AbstractMapBasedMultiset.this.backingMap.entrySet().iterator();    }    //根据当前迭代器判断是否还有元素    public boolean hasNext() {      return this.occurrencesLeft > 0 || this.entryIterator.hasNext();    }    public E next() {      //如果occurrencesLeft为0,则说明现在处于刚开始,或上一个元素完成      if(this.occurrencesLeft == 0) {            //迭代器向下获取一个元素            this.currentEntry = (java.util.Map.Entry)this.entryIterator.next();            //获取到当前元素的个数            this.occurrencesLeft = ((Count)this.currentEntry.getValue()).get();      }      //因为是获取一个元素,所以减去这一个      --this.occurrencesLeft;      this.canRemove = true;      return this.currentEntry.getKey();    }    public void remove() {      CollectPreconditions.checkRemove(this.canRemove);      int frequency = ((Count)this.currentEntry.getValue()).get();      if(frequency
页: [1]
查看完整版本: 【Guava】BiMap&Multimap&Multiset