版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/dancheng1/article/details/85256945
BiMap是一个双向关联的数据结构,而它对key和value严格的保证唯一性。如果使用put方法添加相同的value值或key值则会抛出异常:java.lang.IllegalArgumentException,如果使用forcePut方法添加则会覆盖掉原来的value值。
由上图可以看到BiMap的实现类有很多,我就使用HashBiMap对BiMap进行分析
成员变量
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实现的Entry的Node类与HashBiMap.BiEntry的源码进行对比:
首先我们要了解,HashMap做的是唯一key值对应的value可以不唯一
Bimap做的是唯一key值,value值也要唯一,方便从key找到value,从value找到key
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;
}
接下来在看看HashBiMap实现的BiEntry:
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;
}
}
构造方法
//传入期望容器长度
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;
}
BiMap的添加功能
添加功能有两种,一个是put方法,一个是forcePut方法:
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);
}
可以看到,这两个方法同时调用了本类的put方法,只不过是这两个方法的第三个参数不同,一个为ture,一个为false,看一下put的源码,看看第三个参数有什么用
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) {
//如果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;
}
}
看一下insert源码:
private void insert(HashBiMap.BiEntry<K, V> entry) {
//计算出这个节点在key容器中的下标位置
int keyBucket = entry.keyHash & this.mask;
//使当前节点的keynext指向当前下标位置上
entry.nextInKToVBucket = this.hashTableKToV[keyBucket];
//将当前节点赋值给这个下标位置
this.hashTableKToV[keyBucket] = entry;
//value如key一样
int valueBucket = entry.valueHash & this.mask;
entry.nextInVToKBucket = this.hashTableVToK[valueBucket];
this.hashTableVToK[valueBucket] = entry;
//size加1
++this.size;
++this.modCount;
}