基于版本:Guava 22.0

Wiki:New collection types

0. 简介

Guava提供了很多好用的集合工具,比如Multiset和BiMap,本文介绍了这些新集合类型的使用方式与实现原理。

1. Multiset

  a. 简介

  一般的Set会对相同元素去重,而Multiset则会记下某个元素重复出现的次数。可以理解为Multiset内部维护了一个HashMap,对每个元素的重复次数进行计数,每次插入或者删除元素,都会更新这个HashMap。

  b. Multiset类图

  c. Multiset接口

  int size();
int count(@Nullable @CompatibleWith("E") Object element);
int add(@Nullable E element, int occurrences);
int remove(@Nullable @CompatibleWith("E") Object element, int occurrences);
int setCount(E element, int count);
boolean setCount(E element, int oldCount, int newCount);
Set<E> elementSet();
Set<Entry<E>> entrySet();
default void forEachEntry(ObjIntConsumer<? super E> action)
boolean equals(@Nullable Object object);
int hashCode();
String toString();
Iterator<E> iterator();
boolean contains(@Nullable Object element);
boolean containsAll(Collection<?> elements);
boolean add(E element);
boolean remove(@Nullable Object element);
boolean removeAll(Collection<?> c);
boolean retainAll(Collection<?> c);
default void forEach(Consumer<? super E> action)
default Spliterator<E> spliterator()

  Multiset的子类很多,后续只介绍最有代表性的HashMultiset的实现

  d. HashMultiset的类图

  

  e. HashMultiset.add方法

Multiset.add  
@CanIgnoreReturnValue
@Override
boolean add(E element); AbstractMultiset.add
@CanIgnoreReturnValue
@Override
public boolean add(@Nullable E element) {
add(element, 1);
return true;
} @CanIgnoreReturnValue
@Override
public int add(@Nullable E element, int occurrences) {
throw new UnsupportedOperationException();
}

AbstractMapBasedMultiset.add
@CanIgnoreReturnValue
@Override
public int add(@Nullable E element, int occurrences) {
if (occurrences == 0) {
return count(element);
}
checkArgument(occurrences > 0, "occurrences cannot be negative: %s", occurrences);
Count frequency = backingMap.get(element);
int oldCount;
if (frequency == null) {
oldCount = 0;
backingMap.put(element, new Count(occurrences));
} else {
oldCount = frequency.get();
long newCount = (long) oldCount + (long) occurrences;
checkArgument(newCount <= Integer.MAX_VALUE, "too many occurrences: %s", newCount);
frequency.add(occurrences);
}
size += occurrences;
return oldCount;
}

   从add方法中,我们就能看出HashMultiset的基本逻辑:内部维护了一个Map,每次add key的时候,更新Map中key对应的value(计数器)

2. BiMap

  a. 简介

  一般的Map是维护了从key到value的单向映射,某些场景下我们可能会需要双向映射。一般的做法是同时维护两个Map,一个Map的key是另外一个Map的value。但是这样麻烦而且容易出错。为了解决这一需求,Guava提供了BiMap接口与若干实现类。

  b. BiMap类图

  c. BiMap接口

  

  V put(@Nullable K key, @Nullable V value);

  /**
* An alternate form of {@code put} that silently removes any existing entry
* with the value {@code value} before proceeding with the {@link #put}
* operation. If the bimap previously contained the provided key-value
* mapping, this method has no effect.
*
* <p>Note that a successful call to this method could cause the size of the
* bimap to increase by one, stay the same, or even decrease by one.
*
* <p><b>Warning:</b> If an existing entry with this value is removed, the key
* for that entry is discarded and not returned.
*
* @param key the key with which the specified value is to be associated
* @param value the value to be associated with the specified key
* @return the value which was previously associated with the key, which may
* be {@code null}, or {@code null} if there was no previous entry
*/
@CanIgnoreReturnValue
@Nullable
V forcePut(@Nullable K key, @Nullable V value); void putAll(Map<? extends K, ? extends V> map);
Set<V> values(); /**
* Returns the inverse view of this bimap, which maps each of this bimap's
* values to its associated key. The two bimaps are backed by the same data;
* any changes to one will appear in the other.
*
* <p><b>Note:</b>There is no guaranteed correspondence between the iteration
* order of a bimap and that of its inverse.
*
* @return the inverse view of this bimap
*/
BiMap<V, K> inverse();

跟一般的Map的区别在于加黑标出的forcePut与inverse方法

forcePut:在BiMap中,如果你想把键映射到已经存在的值,会抛出IllegalArgumentException异常。如果对特定值,你想要强制替换它的键,需要使用forcePut方法

inverse:返回的BiMap是原BiMap的反转

  d. HashBiMap的基本原理

  内部维护了两个等长Entry数组hashTableKToV与hashTableVToK,采用链地址法解决哈希冲突,每次操作会同时维护这两个数组。在插入hashTableVToK时如果value已经存在且不为强制更新,则抛出异常。

  e. HashBiMap.put

  private V put(@Nullable K key, @Nullable V value, boolean force) {
int keyHash = smearedHash(key);
int valueHash = smearedHash(value); BiEntry<K, V> oldEntryForKey = seekByKey(key, keyHash);//去hashTableKToV里根据key寻找Entry
if (oldEntryForKey != null
&& valueHash == oldEntryForKey.valueHash
&& Objects.equal(value, oldEntryForKey.value)) {
return value;//Entry已经存在,无需更新,函数可以直接返回
} BiEntry<K, V> oldEntryForValue = seekByValue(value, valueHash);//去hashTableVToK里根据value选择Entry
if (oldEntryForValue != null) {
if (force) {//如果是强制更新,则删除关联的Entry
delete(oldEntryForValue);
} else {//抛出错误,否则会出现一个value对应多个key的情况,inverse后无法处理
throw new IllegalArgumentException("value already present: " + value);
}
} BiEntry<K, V> newEntry = new BiEntry<K, V>(key, keyHash, value, valueHash);//创建新BiEntry
if (oldEntryForKey != null) {//更新key对应的value的情况
delete(oldEntryForKey);//先删除老的BiEntry
insert(newEntry, oldEntryForKey);//插入新的BiEntry
oldEntryForKey.prevInKeyInsertionOrder = null;
oldEntryForKey.nextInKeyInsertionOrder = null;
rehashIfNecessary();//扩容
return oldEntryForKey.value;
} else {//插入新键值对的情况
insert(newEntry, null);
rehashIfNecessary();//扩容
return null;
}
} private void delete(BiEntry<K, V> entry) {
int keyBucket = entry.keyHash & mask;//删除hashTableKToV中的Entry
BiEntry<K, V> prevBucketEntry = null;
for (BiEntry<K, V> bucketEntry = hashTableKToV[keyBucket];
true;
bucketEntry = bucketEntry.nextInKToVBucket) {
if (bucketEntry == entry) {
if (prevBucketEntry == null) {
hashTableKToV[keyBucket] = entry.nextInKToVBucket;
} else {
prevBucketEntry.nextInKToVBucket = entry.nextInKToVBucket;
}
break;
}
prevBucketEntry = bucketEntry;
} int valueBucket = entry.valueHash & mask;//删除hashTableVToK中的Entry
prevBucketEntry = null;
for (BiEntry<K, V> bucketEntry = hashTableVToK[valueBucket];
true;
bucketEntry = bucketEntry.nextInVToKBucket) {
if (bucketEntry == entry) {
if (prevBucketEntry == null) {
hashTableVToK[valueBucket] = entry.nextInVToKBucket;
} else {
prevBucketEntry.nextInVToKBucket = entry.nextInVToKBucket;
}
break;
}
prevBucketEntry = bucketEntry;
} if (entry.prevInKeyInsertionOrder == null) {
firstInKeyInsertionOrder = entry.nextInKeyInsertionOrder;
} else {
entry.prevInKeyInsertionOrder.nextInKeyInsertionOrder = entry.nextInKeyInsertionOrder;
} if (entry.nextInKeyInsertionOrder == null) {
lastInKeyInsertionOrder = entry.prevInKeyInsertionOrder;
} else {
entry.nextInKeyInsertionOrder.prevInKeyInsertionOrder = entry.prevInKeyInsertionOrder;
} size--;
modCount++;
} private void insert(BiEntry<K, V> entry, @Nullable BiEntry<K, V> oldEntryForKey) {
int keyBucket = entry.keyHash & mask;
entry.nextInKToVBucket = hashTableKToV[keyBucket];
hashTableKToV[keyBucket] = entry; int valueBucket = entry.valueHash & mask;
entry.nextInVToKBucket = hashTableVToK[valueBucket];
hashTableVToK[valueBucket] = entry; if (oldEntryForKey == null) {
entry.prevInKeyInsertionOrder = lastInKeyInsertionOrder;
entry.nextInKeyInsertionOrder = null;
if (lastInKeyInsertionOrder == null) {
firstInKeyInsertionOrder = entry;
} else {
lastInKeyInsertionOrder.nextInKeyInsertionOrder = entry;
}
lastInKeyInsertionOrder = entry;
} else {
entry.prevInKeyInsertionOrder = oldEntryForKey.prevInKeyInsertionOrder;
if (entry.prevInKeyInsertionOrder == null) {
firstInKeyInsertionOrder = entry;
} else {
entry.prevInKeyInsertionOrder.nextInKeyInsertionOrder = entry;
}
entry.nextInKeyInsertionOrder = oldEntryForKey.nextInKeyInsertionOrder;
if (entry.nextInKeyInsertionOrder == null) {
lastInKeyInsertionOrder = entry;
} else {
entry.nextInKeyInsertionOrder.prevInKeyInsertionOrder = entry;
}
} size++;
modCount++;
}

  f. HashBiMap.inverse

  @Override
public BiMap<V, K> inverse() {
return (inverse == null) ? inverse = new Inverse() : inverse;
} private final class Inverse extends IteratorBasedAbstractMap<V, K>
implements BiMap<V, K>, Serializable {
BiMap<K, V> forward() {
return HashBiMap.this;//原来的正向的HashBiMap
} @Override
public int size() {
return size;
} @Override
public void clear() {
forward().clear();//调用原来的HashBiMap的方法
} @Override
public boolean containsKey(@Nullable Object value) {
return forward().containsValue(value);
} @Override
public K get(@Nullable Object value) {
return Maps.keyOrNull(seekByValue(value, smearedHash(value)));
} @CanIgnoreReturnValue
@Override
public K put(@Nullable V value, @Nullable K key) {
return putInverse(value, key, false);
} @Override
public K forcePut(@Nullable V value, @Nullable K key) {
return putInverse(value, key, true);
} @Override
public K remove(@Nullable Object value) {
BiEntry<K, V> entry = seekByValue(value, smearedHash(value));
if (entry == null) {
return null;
} else {
delete(entry);
entry.prevInKeyInsertionOrder = null;
entry.nextInKeyInsertionOrder = null;
return entry.key;
}
} @Override
public BiMap<K, V> inverse() {
return forward();//直接返回原HashBiMap
} @Override
public Set<V> keySet() {
return new InverseKeySet();
}
....
}

Guava源码学习(四)新集合类型的更多相关文章

  1. Guava源码学习(五)EventBus

    基于版本:Guava 22.0 Wiki:EventBus 0. EventBus简介 提供了发布-订阅模型,可以方便的在EventBus上注册订阅者,发布者可以简单的将事件传递给EventBus,E ...

  2. Guava源码学习(二)Ordering

    基于版本:Guava 22.0 Wiki:Ordering 0. Ordering简介 Guava的Ordering提供了链式风格的比较器的实现,我们可以用Ordering轻松构建复杂的比较器. 1. ...

  3. [spring源码学习]四、IOC源码——普通bean初始化

    一.代码例子 此节开始涉及到一个bean具体生成和保存的过程,仅仅涉及到最简单的bean,代码依旧是最简单的 public static void main(String[] args) { Defa ...

  4. Guava源码学习(零)前言

    Guava是由Google出品的Java类库,功能强大且易用. 后续我会用多篇博客介绍Guava的使用方法,以及从源码层面分析其实现原理. 分析次序基于Guava的官方Wiki 基于版本:Guava ...

  5. dubbo源码学习(四):暴露服务的过程

    dubbo采用的nio异步的通信,通信协议默认为 netty,当然也可以选择 mina,grizzy.在服务端(provider)在启动时主要是开启netty监听,在zookeeper上注册服务节点, ...

  6. redis源码学习_整数集合

    redis里面的整数集合保存的都是整数,有int_16.int_32和int_64这3种类型,和C++中的set容器差不多. 同时具备如下特点: 1.set里面的数不重复,均为唯一. 2.set里面的 ...

  7. Guava源码学习(一)Optional

    基于版本:Guava 22.0 Wiki:Using and avoiding null 0:Optional简介 null在很多场景下会引发问题,NullPointerException困扰过无数的 ...

  8. Guava源码学习(三)ImmutableCollection

    基于版本:Guava 22.0 Wiki:Immutable collections 0. ImmutableCollection简介 类似于JDK的Collections.unmodifiableX ...

  9. mybatis源码学习(四)--springboot整合mybatis原理

    我们接下来说:springboot是如何和mybatis进行整合的 1.首先,springboot中使用mybatis需要用到mybatis-spring-boot-start,可以理解为mybati ...

随机推荐

  1. TCP重组问题

    今天问题: vqmon 测试一pcap抓包文件18.pcap.发现实际输出的视频分片信息和抓包不符合. ===>pts : 00:00:33 Too much data in TCP recei ...

  2. Maven学习 (二) Eclipse 上安装 Maven3插件

    eclipse 安装插件的方式最常见的有两种: 1. 一种是在线安装,这貌似是用的最多的,就是:Help  -->  Install New Software,然后输入 HTTP 地址来安装,但 ...

  3. Lambda与LINQ

    Lambda与LINQ写法对比: 上为Lambda 下为LINQ 显示指定列 Students.select(u=>(new {Name=u.Sname,Address=u.Saddress}) ...

  4. Robot Framwork +Selenium2环境搭建

    Robot Framwork +Selenium2环境搭建 安装python 参考文章http://blog.csdn.net/sealion111/article/details/78690686 ...

  5. JS——BOM、DOM

    BOM.DOM BOM window对象 history对象 location对象 screen对象 DOM DOM对HTML元素访问操作 HTML DOM树 DOM 节点 DOM访问HTML元素 D ...

  6. python学习笔记-参数带*

    #!/usr/bin/python # -*- coding: utf-8 -*- def powersum (power,*args): #所有多余的参数都会作为一个元组存储在args中     s ...

  7. rest_framework_jwt

    安装配置 安装 pip install djangorestframework-jwt 配置 REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ...

  8. 孤荷凌寒自学python第五天初识python的列表

    孤荷凌寒自学python第五天 列表 (完整学习过程屏幕记录视频地址在文末,手写笔记在文末) 粗俗地区分列表,可以这样理解,定义或print列表后显示时,列表中的各元素都是用一个方括号[]括起来的. ...

  9. parameter localparam define的区别

    `define 语法格式 `define A 12 //注意不加:不能忘记" ` " 作用区域 在整个工程中均有效,因为它是可以跨模块的定义 parameter 和 localpa ...

  10. Android之测试相关知识点

    程序员在开发的过程中一定要进行严格的测试: --->相关概念 * 根据是否知道源代码可以分为: 黑盒测试:只关心程序执行的过程和结果并不知道程序源代码. 白盒测试: 根据源代码写测试方法 或者 ...