Guava源码解析十四:BiMap源码解析

版权声明:本文为博主原创文章,未经博主允许不得转载。 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;
}

猜你喜欢

转载自blog.csdn.net/dancheng1/article/details/85256945