Guava-HashBiMap

前言

在学习了HashMultiset后,接着学习HashBiMap,该类非常有意思,与HashMap相反,当其插入k-vv相同时报错。猜想该类的实现是在底层将v当成k进行存储,而k当成v进行存储。

HashBiMap

下面是HashBiMap的类结构,其继承AbstractMapBiMap,其中AbstractMap定义了一些非结构性的操作,如get,而BiMap则定义了一些对结构进行修改的操作,如put

示例


package com.hust.grid.leesf.guavalearning;

import com.google.common.collect.HashBiMap;

public class HashBiMapTest {
    public static void main(String[] args) {
        HashBiMap<String, Integer> hashBiMap = HashBiMap.create();
        hashBiMap.put("leesf", 25);
        hashBiMap.put("dyd", 26);
        System.out.println(hashBiMap);
        hashBiMap.forcePut("ld", 25);

        System.out.println(hashBiMap);
    }
}


运行结果

{leesf=25, dyd=26}
{ld=25, dyd=26}

init

在调用create方法创建HashMapBiMap实例时,会调用到init方法,其源码如下。


  private void init(int expectedSize) {
	// 检查参数
    checkArgument(expectedSize >= 0, "expectedSize must be >= 0 but was %s", expectedSize);
	// 寻找大于expectedSize最小的2的整数次方的数
    int tableSize = Hashing.closedTableSize(expectedSize, LOAD_FACTOR);
	// 创建K->V表
    this.hashTableKToV = createTable(tableSize);
	// 创建V->K表
    this.hashTableVToK = createTable(tableSize);
	// 用于确定桶位置
    this.mask = tableSize - 1;
	// 初始化其他变量
    this.modCount = 0;
    this.size = 0;
  }

可以看到其首先会检查大小,必须要大于等于0;寻找最合适的表大小;创建k-vv-k的两张表;然后初始化其他变量值,如模、大小等。

createTable


  private BiEntry<K, V>[] createTable(int length) {
    return new BiEntry[length];
  }

其会创建长度为length,类型为BiEntry的数组。

put

调用put方法会调用put(@Nullable K key, @Nullable V value, boolean force),其中force置为false


  private V put(@Nullable K key, @Nullable V value, boolean force) {
	// 获取key的hash值
    int keyHash = hash(key);
	// 获取value的hash值
    int valueHash = hash(value);
	
	// 在k-v桶中寻找指定BiEntry
    BiEntry<K, V> oldEntryForKey = seekByKey(key, keyHash);
    if (oldEntryForKey != null && valueHash == oldEntryForKey.valueHash
        && Objects.equal(value, oldEntryForKey.value)) { // 若找到并且值也相等,直接返回值
      return value;
    }

	// 在v-k桶中寻找指定的BiEntry
    BiEntry<K, V> oldEntryForValue = seekByValue(value, valueHash);
    if (oldEntryForValue != null) { // 不存在
      if (force) { // 强制写
		// 删除老的项
        delete(oldEntryForValue);
      } else { // 非强制写
		// 抛出异常,表示值已经存在
        throw new IllegalArgumentException("value already present: " + value);
      }
    }

    if (oldEntryForKey != null) { // 存在老的项
	  // 删除老的项
      delete(oldEntryForKey);
    }
	// 创建BiEntry对象
    BiEntry<K, V> newEntry = new BiEntry<K, V>(key, keyHash, value, valueHash);
	// 插入
    insert(newEntry);
	// 判断是否需要进行重新哈希
    rehashIfNecessary();
	// 返回null或者老项的值
    return (oldEntryForKey == null) ? null : oldEntryForKey.value;
  }

可以看到,首先会根据keyk-v表中寻找项,若找到并且v相等,那么直接返回;再根据vv-k表中寻找项,若找到并且强制写,则删除老项,若非强制写,则需抛出异常,表明v已经存在;若根据keyk-v表中寻找到项,而v不相等,则删除老项;创建新的BiEntry对象,然后插入到k-vv-k表中;若达到再哈希条件还需进行再哈希处理。

insert


  private void insert(BiEntry<K, V> entry) {
	// 操作k->v表,确定桶位置
    int keyBucket = entry.keyHash & mask;
	// 指向下个项
    entry.nextInKToVBucket = hashTableKToV[keyBucket];
	// 指定桶中存放entry,都是从头部插入
    hashTableKToV[keyBucket] = entry;

	// 操作v->k表,确定桶位置
    int valueBucket = entry.valueHash & mask;
	// 指向下个项
    entry.nextInVToKBucket = hashTableVToK[valueBucket];
	// 指定桶中存放entry,都是从头部插入
    hashTableVToK[valueBucket] = entry;

	// 大小和模加1
    size++;
    modCount++;
  }

数据会插入到两张表中,k->v插入到k-v表,v->k插入到v-k表中,都采用从头部插入方式,时间复杂度为常量。

总结

本篇博文讲解了HashBiMap的使用,其与HashMap底层存储结构十分相似,只是添加了v-k的表来判断是否v存在重复情况,当需要保证v不能重复的场景下可以使用该类。分析完java源码后再分析该类就会非常简单。