该篇文章为转载文章,
Guava
的功能强大,由于自己使用
Guava
并没有特别频繁,很多工具类也是在阅读本篇文章之后才发现原来开发的过程中可以省去很多的繁琐,例如
Charsets
的使用,例如
HttpHeaders
的使用,例如
Multimap
相关集合类的使用,都是在阅读过本篇文章之后才发现可以在实际开发中省去很多的繁琐的。
只不过本篇文章中还有挺多需要补充的地方,后续有时间我会自己实现代码然后做补充的。
另外,本篇文章在做转载时,已经把
Guava
的版本升级为
23.0
,所以有一些方法做了修改
Guava介绍
Guava
工程包含了若干被
Google
的
Java
项目广泛依赖的核心库,例如:集合 [
collections
] 、缓存 [
caching
] 、原生类型支持 [
primitives support
] 、并发库 [
concurrency libraries
] 、通用注解 [
commonannotations
] 、字符串处理 [
string processing
] 、
I/O
等等。所有这些工具每天都在被
Google
的工程师应用在产品服务中。
这些高质量的
API
可以使你的
Java
代码更加优雅,更加简洁,让你的工作更加轻松愉悦,下面我们来开启
Java
编程学习之旅。
源码包的简单说明
com.google.common.annotations
:普通注解类型
com.google.common.base
:基本工具类库和接口
com.google.common.cache
:缓存工具包,非常简单易用且功能强大的
JVM
内缓存
com.google.common.collect
:带泛型的集合接口扩展和实现,以及工具类,这里你会发现很多好玩的集合
com.google.common.eventbus
:发布订阅风格的事件总线
com.google.common.hash
: 哈希工具包
com.google.common.io
:
I/O
工具包
com.google.common.math
:原始算术类型和超大数的运算工具包
com.google.common.net
:网络工具包
com.google.common.primitives
:八种原始类型和无符号类型的静态工具包
com.google.common.reflect
:反射工具包
com.google.common.util.concurrent
:多线程工具包
1 基本工具(
Basic utilities
)
1.1 使用和避免
null
(
Optional
)
null
会引起歧义,会造成让人迷惑的错误,有时也会让人感到不爽。
Guava
中的许多工具遇到
null
时,会拒绝或者马上报错,而不是盲目的接受。
鉴于此
Google
的
Guava
库中提供了
Optional
接口来使
null
快速失败,即在可能为
null
的对象上做了一层封装,在使用
Optional
静态方法
of
时,如果传入的参数为
null
就抛出
NullPointerException
异常。
在
Guava
中
Optional
类就是用来强制提醒程序员,注意对
null
的判断。
Optional
的另外几个方法
Optional<T>.of(T)
为
Optional
赋值,当
T
为
null
直接抛
NullPointException
,建议这个方法在调用的时候直接传常量,不要传变量
1.2 前提条件(
Preconditions
)
使方法的条件检查更简单。
Guava
在
Preconditions
类中提供了若干前置条件判断的实用方法,我们强烈建议在
Eclipse
中静态导入这些方法。每个方法都有三个变种:
没有额外参数:抛出的异常中没有错误消息;
有一个
Object
对象作为额外参数:抛出的异常使用
Object.toString()
作为错误消息;
有一个
String
对象作为额外参数,并且有一组任意数量的附加
Object
对象:这个变种处理异常消息的方式有点类似
printf
,但考虑
GWT
的兼容性和效率,只支持
%s
指示符。
例如:查看源代码打印帮助
checkNotNull(T)
检查
value
是否为
null
,该方法直接返回
value
,因此可以内嵌使用
checkNotNull
。
NullPointerException
checkState(boolean)
用来检查对象的某些状态。
IllegalStateException
checkElementIndex(int index, int size)
检查
index
作为索引值对某个列表、字符串或数组是否有效。
index >= 0 && index < size
IndexOutOfBoundsException
checkPositionIndex(int index, int size)
检查
index
作为位置值对某个列表、字符串或数组是否有效。
index >= 0 && index <= size
IndexOutOfBoundsException
checkPositionIndexes(int start, int end, int size)
检查
[start, end]
表示的位置范围对某个列表、字符串或数组是否有效
IndexOutOfBoundsException
1.3 常见的对象方法(
Objects
)
简化
Object
方法实现,如
hashCode()
和
equals()
;
equals()
当一个对象中的字段可以为
null
时,实现
Object.equals
方法会很痛苦,因为不得不分别对它们进行
null
检查。使用
Objects.equal
帮助你执行
null
敏感的
equals
判断,从而避免抛出
NullPointerException
。
Guava
强大的”流畅风格比较器”,具体到下章会介绍到。
1.5
Throwable
类
简化了异常和错误的传播与检查;
Guava
类库中的
Throwables
提供了一些异常处理的静态方法,这些方法的从功能上分为两类,一类是帮你抛出异常,另外一类是帮你处理异常。
方法作用
2 集合(
Collections
)
介绍
Guava
对
Jdk
集合类的扩展,包括不可变集合,新集合类型包括
Multisets
,
Multimaps
,
Tables
等。强大的集合工具类,提供
java.util.Collections
中没有的集合工具,扩展工具类。让实现和扩展集合类变得更容易,比如创建
Collection
的装饰器,或实现迭代器。集合
API
的使用,可以简化集合的创建和初始化。
Guava API
提供了有用的新的集合类型,协同已经存在的
Java
集合工作。
分别是
MultiMap
,
Multiset
,
Table
,
BiMap
,
ClassToInstanceMap
2.1
Guava
的不可变集合
不可变对象有很多优点:
当对象被不可信的库调用时,不可变形式是安全的
当不可变对象被多个线程调用时,不存在竞态条件问题
不可变集合不需要考虑变化,因此可以节约时间和空间,所有不可变集合都比可变集合形式有更好的内存利用率(分析和测试细节)
不可变对象因为有固定不变,可以用作常量来安全使用
创建对象的不可拷贝是一项很好的防御性编程技巧,
Guava
为所有
JDK
标准集合类型和
Guava
新集合类型都提供了简单易用的不可变版本。
JDK
也提供了可以将集合变成不可变的方法,
Collections.unmodifiableXXX
,但是被认为是不好的。
笨重而且累赘:不能舒适地用在所有想做防御性拷贝的场景
不安全:要保证没人通过原集合的引用进行修改,返回的集合才是事实上不可变的
低效:包装过的集合仍然保有可变集合的开销,比如并发修改的检查、散列表的额外空间,等等
2.2
Guava
集合之
Multiset
Multiset
看似是一个
Set
,但是实质上它不是一个
Set
它没有继承
Set
接口,它继承的是
Collection<E>
接口
你可以向
Multiset
中添加重复的元素,
Multiset
会对添加的元素做一个计数
它本质上是一个
Set
加一个元素计数器
显然计数不是问题,
Multiset
还提供了
add
和
remove
的重载方法,可以在
add
或
remove
的同时指定计数的值。
常用实现
Multiset
接口的类有:
HashMultiset
: 元素存放于
HashMap
LinkedHashMultiset
:元素存放于
LinkedHashMap
,即元素的排列顺序由第一次放入的顺序决定
TreeMultiset
:元素被排序存放于
TreeMap
EnumMultiset
: 元素必须是
Enum
类型
ImmutableMultiset
:不可修改的
Mutiset
看到这里你可能已经发现
Guava
Collections
都是以
create
或
of
这样的静态方法来构造对象。这是因为这些集合类大多有多个参数的私有构造方法,由于参数数目很多,程序员使用起来就很不方便。而且以这种方式可以返回原类型的子类型对象。另外,对于创建范型对象来讲,这种方式更加简洁。
2.3
Guava
的
BiMap
:双向
Map
我们知道
Map
是一种键值对映射,这个映射是键到值的映射
而BiMap首先也是一种
Map
他的特别之处在于,既提供键到值的映射,也提供值到键的映射
所以它是双向
Map
BiMap
的常用实现有:
HashBiMap
:
key
集合与
value
集合都有
HashMap
实现
EnumBiMap
:
key
与
value
都必须是
Enum
类型
ImmutableBiMap
: 不可修改的
BiMap
2.4
Guava
的
Multimap
:一键多值的
Map
有时候我们需要这样的数据类型
Map<String,Collection<String>>
,
Guava
中的
Multimap
就是为了解决这类问题的。
Multimap
提供了丰富的实现,所以你可以用它来替代程序里的
Map<K, Collection<V>>
,具体的实现如下:
实现
Key
实现
Value
实现
2.5
Guava
集合之
Table
在
Guava
库中还提供了一种二维表结构:
Table
使用
Table
可以实现二维矩阵的数据结构,可以是稀疏矩阵。
通常来说,当你想使用多个键做索引的时候,你可能会用类似
Map<FirstName, Map<LastName, Person>>
的实现
这种方式很丑陋,使用上也不友好
Guava
为此提供了新集合类型
Table
它有两个支持所有类型的键:
行
和
列
Table
提供多种视图,以便你从各种角度使用它:
rowMap()
:用
Map<R, Map<C, V>>
表现
Table<R, C, V>
同样的,
rowKeySet()
返回
行
的集合
Set<R>
。
row(r)
:用
Map<C, V>
返回给定
行
的所有列
对这个
map
进行的写操作也将写入
Table
中。
类似的列访问方法:
columnMap()
、
columnKeySet()
、
column(c)
。(基于列的访问会比基于的行访问稍微低效点)
cellSet()
:用元素类型为
Table.Cell<R, C, V>
的
Set
表现
Table<R,C, V>
。
Cell
类似于
Map.Entry
,但它是用行和列两个键区分的。
2.6
Guava
集合:使用
Iterators
简化
Iterator
操作
Iterators
是
Guava
中对
Iterator
迭代器操作的帮助类,这个类提供了很多有用的方法来简化
Iterator
的操作。
2.7
ClassToInstanceMap
可以实现
map
的
key
值是多个类型
有的时候,你的
map
的
key
并不是一种类型,他们是很多类型
你想通过映射他们得到这种类型
Guava
提供了
ClassToInstanceMap
满足了这个目的
除了继承自
Map
接口,
ClassToInstaceMap
提供了方法
T getInstance(Class<T>)
和
T putInstance(Class<T>, T)
,消除了强制类型转换。
2.8
Ordering
犀利的比较器
Ordering
是
Guava
类库提供的一个犀利强大的比较器工具
Guava
的
Ordering
和
JDK
Comparator
相比功能更强
它非常容易扩展,可以轻松构造复杂的
comparator
,然后用在容器的比较、排序等操作中
本质上来说,
Ordering
实例无非就是一个特殊的
Comparator
实例
Ordering
只是需要依赖于一个比较器(例如:
Collections.max
)的方法,并使其可作为实例方法
另外,
Ordering
提供了链式方法调用和加强现有的比较器。
常见的静态方法:
natural()
:使用
Comparable
类型的自然顺序,例如:整数从小到大,字符串是按字典顺序
usingToString()
:使用
toString()
返回的字符串按字典顺序进行排序
arbitrary()
:返回一个所有对象的任意顺序,即
compare(a, b) == 0
就是
a == b (identity equality)
。本身的排序是没有任何含义,但是在
VM
的生命周期是一个常量
2.9
ComparisonChain
比较
实现一个比较器[
Comparator
],或者直接实现
Comparable
接口有时也伤不起。考虑一下这种情况:
class Student implements Comparable<Student> {
private String lastName;
private String firstName;
private int zipCode;
这种
Fluent
接口风格的可读性更高,发生错误编码的几率更小
并且能避免做不必要的工作。
3 缓存(
Caches
)
Google Guava
框架提供了内存缓存的功能
可以很方便的缓存对象,设置生命周期
及缓存对象的弱引用,强应用,软引用等
3.1 使用
Guava
做内存缓存
Guava
中有
cache
包,此包提供内存缓存功能
内存缓存需要考虑很多问题,包括并发问题,缓存失效机制
内存不够用时缓存释放,缓存的命中率,缓存的移除等等
当然这些东西
Guava
都考虑到了。
Guava
的内存缓存非常强大,可以设置各种选项
而且很轻量,使用方便
另外还提供了下面一些方法,来方便各种需要:
ImmutableMap<K, V> getAllPresent(Iterable<?> keys)
一次获得多个键的缓存值
put()
和
putAll()
方法向缓存中添加一个或者多个缓存项
invalidate()
和
invalidateAll()
方法从缓存中移除缓存项
asMap()
方法获得缓存数据的
ConcurrentMap<K, V>
快照
cleanUp()
清空缓存
refresh(Key)
刷新缓存,即重新取缓存数据,更新缓存
3.2
Guava
缓存分析
Guava
缓存过期时间分为两种,一种是从写入时开始计时,一种是从最后访问时间开始计时
而且
Guava
缓存的过期时间是设置到整个一组缓存上的
这和
EHCache
,
Redis
,
Memcached
等不同
这些缓存系统设置都将缓存时间设置到了单个缓存上
Guava
缓存设计成了一组对象一个缓存实例
这样做的好处是一组对象设置一组缓存策略,你可以根据不同的业务来设置不同的缓存策略
包括弱引用,软引用,过期时间,最大项数等
另外一点好处是你可以根据不同的组来统计缓存的命中率,这样更有意义一些
这样做也是有缺点的
缺点是首先是每个缓存组都需要声明不同的缓存实例,具体到业务程序中可能就是每个业务对象一个缓存了。
这样就把不同的业务缓存分散到不同的业务系统中了,不太好管理。
4 函数式风格(
Functional idioms
)
5 并发(
Concurrency
)
并发编程是一个难题,但是一个强大而简单的抽象可以显著的简化并发的编写
出于这样的考虑,
Guava
定义了
ListenableFuture
接口并继承了
JDK concurrent
包下的
Future
接口。
5.1
Guava
并发:
ListenableFuture
使用介绍以及示例
ListenableFuture
顾名思义就是可以监听的Future
它是对
Java
原生
Future
的扩展增强
本文介绍
ListenableFuture
的用法和扩展实现
我们知道
Future
表示一个异步计算任务,当任务完成时可以得到计算结果
如果我们希望一旦计算完成就拿到结果展示给用户或者做另外的计算,就必须使用另一个线程不断的查询计算状态
这样做,代码复杂,而且效率低下
使用
ListenableFuture
Guava
帮我们检测
Future
是否完成了
如果完成就自动调用回调函数,这样可以减少并发程序的复杂度
另外
ListenableFuture
还有其他几种内置实现:
SettableFuture
:不需要实现一个方法来计算返回值,而只需要返回一个固定值来做为返回值,可以通过程序设置此
Future
的返回值或者异常信息
CheckedFuture
:这是一个继承自
ListenableFuture
接口,他提供了
checkedGet()
方法,此方法在
Future
执行发生异常时,可以抛出指定类型的异常。
5.2
Guava
并发:
RateLimiter
限制资源的并发访问线程数
RateLimiter
类似于
JDK
的信号量
Semphore
用来限制对资源并发访问的线程数
6 字符串处理(
Strings
)
6.1 连接器(
Joiner
)
用分隔符把字符串序列连接起来也可能会遇上不必要的麻烦
如果字符串序列中含有
null
,那连接操作会更难
Fluent
风格的
Joiner
让连接字符串更简单
警告:
Joiner
实例总是不可变的。用来定义
Joiner
目标语义的配置方法总会返回一个新的
Joiner
实例。这使得
Joiner
实例都是线程安全的,你可以将其定义为
static final
常量。
6.2 拆分器(
Splitter
)
JDK
内建的字符串拆分工具有一些古怪的特性
比如,
String.split
悄悄丢弃了尾部的分隔符。例如:
警告:
Splitter
实例总是不可变的。用来定义
Splitter
目标语义的配置方法总会返回一个新的
Splitter
实例。这使得
Splitter
实例都是线程安全的,你可以将其定义为
static final
常量。
6.3 字符匹配器(
CharMatcher
)
使用
CharMatcher
的好处更在于它提供了一系列方法
让你对字符作特定类型的操作:
修剪[
trim
]、折叠[
collapse
]、移除[
remove
]、保留[
retain
]等等
CharMatcher
实例首先代表
概念1:怎么才算匹配字符?
概念2:如何处理这些匹配字符?
这样的设计使得
API
复杂度的线性增加可以带来灵活性和功能两方面的增长。
注:
CharMatcher
只处理
char
类型代表的字符;它不能理解
0x10000
到
0x10FFFF
的
Unicode
增补字符。这些逻辑字符以代理对[
surrogatepairs
]的形式编码进字符串,而
CharMatcher
只能将这种逻辑字符看待成两个独立的字符。
6.4 字符集(
Charsets
)
Charsets
针对所有
Java
平台都要保证支持的六种字符集提供了常量引用。尝试使用这些常量,而不是通过名称获取字符集实例。
CaseFormat
被用来方便地在各种
ASCII
大小写规范间转换字符串。
比如,编程语言的命名规范
CaseFormat
支持的格式如下:
格式范例
7 原生类型(
Primitives
)
Java
的原生类型也称原始类型
也是基本数据类型
byte
、
short
、
int
、
long
、
float
、
double
、
char
和
boolean
在从
Guava
查找原生类型方法之前,可以先查查
Arrays
类
或者对应的基础类型包装类,如Integer
原生类型不能当作对象或泛型的类型参数使用
这意味着许多通用方法都不能应用于它们
Guava
提供了若干通用工具,包括原生类型数组与集合
API
的交互
原生类型和字节数组的相互转换
以及对某些原生类型的无符号形式的支持
原生类型
Guava
工具类(都在
com.google.common.primitives
包)
Bytes
工具类没有定义任何区分有符号和无符号字节的方法
而是把它们都放到了
SignedBytes
和
UnsignedBytes
工具类中
因为字节类型的符号性比起其它类型要略微含糊一些
int
和
long
的无符号形式方法在
UnsignedInts
和
UnsignedLongs
类中
但由于这两个类型的大多数用法都是有符号的
Ints
和
Longs
类按照有符号形式处理方法的输入参数
此外,
Guava
为
int
和
long
的无符号形式提供了包装类
即
UnsignedInteger
和
UnsignedLong
,以帮助你使用类型系统
以极小的性能消耗对有符号和无符号值进行强制转换。
原生类型数组工具:
方法签名描述
符号无关方法存在于
Bytes
,
Shorts
,
Ints
,
Longs
,
Floats
,
Doubles
,
Chars
,
Booleans
。而
UnsignedInts
,
UnsignedLongs
,
SignedBytes
,
UnsignedBytes
不存在。
符号相关方法存在于
SignedBytes
,
UnsignedBytes
,
Shorts
,
Ints
,
Longs
,
Floats
,
Doubles
,
Chars
,
Booleans
,
UnsignedInts
,
UnsignedLongs
。
而
Bytes
不存在。
通用工具方法:
|方法签名|描述|
|
int compare(prim a, prim b)
|传统的
Comparator.compare
方法,但针对原生类型。
JDK7
的原生类型包装类也提供这样的方法|
|
prim checkedCast(long value)
|把给定
long
值转为某一原生类型,若给定值不符合该原生类型,则抛出
IllegalArgumentException
|
|
prim saturatedCast(long value)
|把给定
long
值转为某一原生类型,若给定值不符合则使用最接近的原生类型值|
这里的整型包括byte, short, int, long。不包括char, boolean, float, 或double。
字节转换方法:
Guava
提供了若干方法,用来把原生类型按大字节序与字节数组相互转换。所有这些方法都是符号无关的,此外
Booleans
没有提供任何下面的方法。
|方法签名或字段签名|描述|
|
int BYTES
|常量:表示该原生类型需要的字节数|
|
prim fromByteArray(byte[] bytes)
|使用字节数组的前
Prims.BYTES
个字节,按大字节序返回原生类型值;如果
bytes.length <= Prims.BYTES
,抛出
IAE
|
|
prim fromBytes(byte b1, …, byte bk)
|接受
Prims.BYTES
个字节参数,按大字节序返回原生类型值|
|
byte[] toByteArray(prim value)
|按大字节序返回
value
的字节数组|
8 区间(
Ranges
)
8.1 简介
区间,有时也称为范围
是特定域中的凸性(非正式说法为连续的或不中断的)部分
在形式上,凸性表示对
a <= b <= c
,
range.contains(a)
且
range.contains(c)
意味着
range.contains(b)
。
区间可以延伸至无限,例如范围
x > 3
包括任意大于
3
的值,也可以被限制为有限,如
2 <= x < 5
Guava
用更紧凑的方法表示范围,有数学背景的程序员对此是耳熟能详的:
上面的
a
、
b
称为端点
为了提高一致性,
Guava
中的
Range
要求上端点不能小于下端点
上下端点有可能是相等的,但要求区间是闭区间或半开半闭区间(至少有一个端点是包含在区间中的):
[a..a]:单元素区间
[a..a); (a..a]:空区间,但它们是有效的
(a..a):无效区间
Guava
用类型
Range<C>
表示区间。所有区间实现都是不可变类型。
8.2 构建区间
区间实例可以由
Range
类的静态方法获取:
数学形式区间静态方法
8.3 区间运算
Range
的基本运算是它的
contains(C)
方法
和你期望的一样,它用来区间判断是否包含某个值
此外,
Range
实例也可以当作
Predicate
,并且在函数式编程中使用
任何
Range
实例也都支持
containsAll(Iterable<? extends C>)
方法
8.4 查询运算
Range
类提供了以下方法来 查看区间的端点:
hasLowerBound()
和
hasUpperBound()
:判断区间是否有特定边界,或是无限的;
lowerBoundType()
和
upperBoundType()
:返回区间边界类型,
CLOSED
或
OPEN
;如果区间没有对应的边界,抛出
IllegalStateException
;
lowerEndpoint()
和
upperEndpoint()
:返回区间的端点值;如果区间没有对应的边界,抛出
IllegalStateException
;
isEmpty()
:判断是否为空区间。
8.5 关系运算
包含[
enclose
]
区间之间的最基本关系就是包含[
encloses(Range)
]:如果内区间的边界没有超出外区间的边界,则外区间包含内区间。包含判断的结果完全取决于区间端点的比较
相连[
isConnected
]
Range.isConnected(Range)
判断区间是否是相连的。具体来说,
isConnected
测试是否有区间同时包含于这两个区间,这等同于数学上的定义“两个区间的并集是连续集合的形式”(空区间的特殊情况除外)。
交集[
intersection
]
Range.intersection(Range)
返回两个区间的交集:既包含于第一个区间,又包含于另一个区间的最大区间。当且仅当两个区间是相连的,它们才有交集。如果两个区间没有交集,该方法将抛出
IllegalArgumentException
。
跨区间[
span
]
Range.span(Range)
返回“同时包括两个区间的最小区间”,如果两个区间相连,那就是它们的并集。span是可互换的[
commutative
] 、关联的[
associative
] 、闭合的[
closed
]、运算[
operation
]。
8.6 离散域
部分(但不是全部)可比较类型是离散的
即区间的上下边界都是可枚举的。
在
Guava
中,用
DiscreteDomain<C>
实现类型
C
的离散形式操作
一个离散域总是代表某种类型值的全集
它不能代表类似“素数”、“长度为5的字符串”或“午夜的时间戳”这样的局部域。
DiscreteDomain
提供的离散域实例包括:
类型离散域
一旦获取了
DiscreteDomain
实例,你就可以使用下面的
Range
运算方法:
ContiguousSet.create(range, domain)
:用
ImmutableSortedSet<C>
形式表示
Range<C>
中符合离散域定义的元素,并增加一些额外操作——译者注:实际返回
ImmutableSortedSet
的子类
ContiguousSet
。(对无限区间不起作用,除非类型
C
本身是有限的,比如
int
就是可枚举的)
canonical(domain)
:把离散域转为区间的“规范形式”。如果
ContiguousSet.create(a, domain).equals(ContiguousSet.create(b,domain))
并且
!a.isEmpty()
,则有
a.canonical(domain).equals(b.canonical(domain))
。(这并不意味着
a.equals(b)
)
你可以创建自己的离散域,但必须记住
DiscreteDomain
契约的几个重要方面。
一个离散域总是代表某种类型值的全集;它不能代表类似“素数”或“长度为5的字符串”这样的局部域。所以举例来说,你无法构造一个·DiscreteDomain·以表示精确到秒的
JODA DateTime
日期集合:因为那将无法包含
JODA DateTime
的所有值。
DiscreteDomain
可能是无限的——比如
BigInteger DiscreteDomain
。这种情况下,你应当用
minValue()
和
maxValue()
的默认实现,它们会抛出
NoSuchElementException
。但
Guava
禁止把无限区间传入
ContiguousSet.create
。
9
I/O
9.1
Guava Files
中的文件操作
Java
的基本
API
对文件的操作很繁琐,为了向文件中写入一行文本,都需要写十几行的代码。
Guava
对此作了很多改进,提供了很多方便的操作。
10 散列(
Hash
)
10.1 概述
Java
内建的散列码[
hash code
]概念被限制为
32
位,并且没有分离散列算法和它们所作用的数据,因此很难用备选算法进行替换。此外,使用
Java
内建方法实现的散列码通常是劣质的,部分是因为它们最终都依赖于
JDK
类中已有的劣质散列码。
Object.hashCode
往往很快,但是在预防碰撞上却很弱,也没有对分散性的预期。这使得它们很适合在散列表中运用,因为额外碰撞只会带来轻微的性能损失,同时差劲的分散性也可以容易地通过再散列来纠正(
Java
中所有合理的散列表都用了再散列方法)。然而,在简单散列表以外的散列运用中,
Object.hashCode
几乎总是达不到要求,因此有了
com.google.common.has
h包。
10.2 散列包的组成
在这个包的
Java doc
中,我们可以看到很多不同的类,但是文档中没有明显地表明它们是怎样一起配合工作的。
HashFunction
HashFunction
是一个单纯的(引用透明的)、无状态的方法,它把任意的数据块映射到固定数目的位置,并且保证相同的输入一定产生相同的输出,不同的输入尽可能产生不同的输出。
Hasher
HashFunction
的实例可以提供有状态的
Hasher
,
Hasher
提供了流畅的语法把数据添加到散列运算,然后获取散列值。
Hasher
可以接受所有原生类型、字节数组、字节数组的片段、字符序列、特定字符集的字符序列等等,或者任何给定了
Funnel
实现的对象。
Hasher
实现了
PrimitiveSink
接口,这个接口为接受原生类型流的对象定义了
fluent
风格的
API
Funnel
Funnel
描述了如何把一个具体的对象类型分解为原生字段值,从而写入
PrimitiveSink
。
注:
putString("abc",Charsets.UTF_8).putString("def", Charsets.UTF_8)
完全等同于
putString("ab", Charsets.UTF_8).putString("cdef",Charsets.UTF_8)
,因为它们提供了相同的字节序列。这可能带来预料之外的散列冲突。增加某种形式的分隔符有助于消除散列冲突。
HashCode
一旦
Hasher
被赋予了所有输入,就可以通过
hash()
方法获取
HashCode
实例(多次调用
hash()
方法的结果是不确定的)。
HashCode
可以通过
asInt()
、
asLong()
、
asBytes()
方法来做相等性检测,此外,
writeBytesTo(array, offset, maxLength)
把散列值的前
maxLength
字节写入字节数组。
10.3 布鲁姆过滤器[
BloomFilter
]
布鲁姆过滤器是哈希运算的一项优雅运用
它可以简单地基于
Object.hashCode()
实现
简而言之,布鲁姆过滤器是一种概率数据结构
它允许你检测某个对象是一定不在过滤器中
还是可能已经添加到过滤器了
Guava
散列包有一个内建的布鲁姆过滤器实现
你只要提供
Funnel
就可以使用它
你可以使用
create(Funnel funnel, int expectedInsertions, doublefalsePositiveProbability)
方法获取
BloomFilter<T>
缺省误检率[
falsePositiveProbability
]为
3%
BloomFilter<T>
提供了
booleanmightContain(T)
和
void put(T)
它们的含义都不言自明了。
10.4
Hashing
类
Hashing
类提供了若干散列函数
以及运算
HashCode
对象的工具方法
已提供的散列函数
md5()
murmur3_128()
murmur3_32()
sha1()
sha256()
sha512()
goodFastHash(int bits)
HashCode运算
方法描述
11 事件总线(
EventBus
)
传统上,
Java
的进程内事件分发都是通过发布者和订阅者之间的显式注册实现的
设计
EventBus
就是为了取代这种显示注册方式
使组件间有了更好的解耦
EventBus
不是通用型的发布-订阅实现,不适用于进程间通信。
12 数学运算(
Math
)
13 反射(
Reflection
)
13.1
Guava
反射
TypeToken
解决泛型运行时类型擦除的问题
介绍
Guava
中的
TypeToken
类解决
Java
运行时泛型类型擦除问题。
TypeToken
的方法列表如下:
方法描述
13.2
Gava
反射之
Invokable
使用
Guava
的
Invokable
是对
java.lang.reflect.Method
和
java.lang.reflect.Constructor
的流式包装。它简化了常见的反射代码的使用。
一些使用例子:
方法是否是
public
的?
JDK
:
Modifier.isPublic(method.getModifiers());
Invokable
:
invokable.isPublic();
13.3
Guava
反射:
Reflection.newProxy
方法简化动态代理
原理上
Guava
的动态代理也是使用
JDK
的动态代理,这是做了封装,更加简便
另外一点是能够很好的检查需要代理的对象必须拥有接口。
使用
Class
类的
isInterface()
来做检查。
14 注解(
Annotations
)
com.google.common.annotations
包下注解类的含义和用法:
14.1
Beta
15 网络编程(
Net
)
Guava
中的
net
包目前提供的功能较少
而且大多类都标注了
@Beta
的注解
在
Guava
中标记
Beta
注解表示这个类还不稳定
有可能在以后的版本中变化,或者去掉
所以不建议大量使用,这里也是只做简单的介绍
先介绍下唯一一个没有
Beta
注解的类
HttpHeaders
,
这个类中并没有实质的方法,只是定义了一些
Http
头名称的常量
通常如果需要我们会自己定义这些常量
如果你引用了
guava
包,那么就不再建议我们自己定义这些头名称的常量了,直接用它定义的即可
这里面应该有几乎所有的
Http
头名称,例如:
X_FORWARDED_FOR
,
UPGRADE
,
REFERER
等等
用法也没有必要介绍了,直接引用常量就可以了
再介绍下一个比较常用的小功能
有时候我们需要在配置文件中配置
IP+PORT
这时候需要自己写解析ip,端口的方法
Guava
为我们提供了解析类,我们看下用法实例:
HostAndPort
类的静态方法
fromString(String)
可以解析出字符串中定义的
Host
和端口信息。
另外
Guava
包中还提供了
InetAddresses
类
这个类是
InetAddress
的帮助类
通过这个类可以方便的从字符串中解析出
InetAddress
类
但是此类也有
@Beta
的注解,所以要谨慎使用。
参考资料:
http://ifeve.com/google-guava/
https://code.google.com/p/guava-libraries/
原文作者:文/debugo
原文标题:
baoq_v5_java