前言
在学习了HashMultiset
后,接着学习HashBiMap
,该类非常有意思,与HashMap
相反,当其插入k-v
的v
相同时报错。猜想该类的实现是在底层将v
当成k
进行存储,而k
当成v
进行存储。
HashBiMap
下面是HashBiMap
的类结构,其继承AbstractMap
和BiMap
,其中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-v
和v-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;
}
可以看到,首先会根据key
从k-v
表中寻找项,若找到并且v
相等,那么直接返回;再根据v
在v-k
表中寻找项,若找到并且强制写,则删除老项,若非强制写,则需抛出异常,表明v
已经存在;若根据key
在k-v
表中寻找到项,而v
不相等,则删除老项;创建新的BiEntry
对象,然后插入到k-v
、v-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
源码后再分析该类就会非常简单。