Guava 的一些小功能点
我们已经到了书的最后一章,但是任然感觉还有很多东西没有覆盖到。虽然在这么短的篇幅里我们不可能覆盖到guava的多有功能,但是我们已经尽力了。 这一章我们将介绍一些没有必要单独一章介绍的一些小的工具类,虽然他们不是天天不用到,但是一旦用到,你就会发现真的非常方便. 这一章我们将学习一下的几个知识点:
1. Hashing 类: 通过Hashing类的静态工具方法我们可以获取HashFunction 实例
2. 布隆过滤: Bloom 过滤这样的数据结构,可以明确返回一个数据不存在,但是不能确保一个元素肯定存在.当存在Hash碰撞时有可能误判。
3. Optional类: Optional 类可以让我们选择使用 null 的引用
4. Throwables类: Throwables 类中有静态工具方法可以与Throwable协作
创建合适的Hash函数
Hash函数是确定对象身份标识和确定是否重复的基础,同时它也是合理使用java结合的必备条件。 Hash函数的基本工作方式: 将对象数据映射为数字。 因此我们希望避免将不同的数据生成相同的数字,毋庸置疑,写一个优秀的Hash函数的工作就留给那些专家吧, 但是幸运的是,在Guava的帮助下我们不需要自己去写Hash函数, Hashing类提供了一些静态方法可以创建HashFunction的实例。
校验总和哈希函数
Guava 提供了两种实现了checksum算法的HashFunction,分别是Adler-32 和 CRC-32,创建这两个HashFunction 可以按照如下的方法:
HashFunction adler32 = Hashing.adler32();
HashFunction crc32 = Hashing.crc32();
这里我们可以简单的使用Hashing函数的静态方法去获取想要的实例.
一般哈希函数
下面我们将看一下一般的hash函数,一般的hash函数是不带加密语义,非常适合基于Hash的查找类任务。 第一个要介绍的就是 murmur Hash,这是一个在2008年由Austin发现的。 其他的一般性hash算法叫做GooFastHash。 下面让我们看下怎样创建这些一般性Hash函数:
HashFunction gfh = Hashing.goodFastHash(128);
HashFunction murmur3_32 = Hashing.murmur3_32();
HashFunction murmur3_128 = Hashing.murmur3_128();
上面的例子中我们使用GoodFastHash算法返回一个最小包含128长度bit位,一个字节包含8个bit位,因此调用GoodFastHash至少返回16个字节(128/8).接下来我们创建了两个murmur实例的Hash. 第一个是实现了32-bit的murmur3_32算法,第二个murmur Hash实例实现了128bit murmur3_128算法。
密码哈希函数
虽然完整的讨论密码哈希函数超出了本书的返回,但是简单的说密码哈希函数就是为了保证数据的安全。 一般来说,密码哈希函数有一些如下的属性:
-- 一点小的数据改变,就会导致HashCode发生很大的变化
-- 在理论上,通过hash code 反推出原始数据是什么是不可能的。
下面Guava提供了创建密码哈希函数的变体:
HashFunction sha1 = Hashing.sha1();
HashFunction sha256 = Hashing.sha256();
HashFunction sha512 = Hashing.sha512();
上面的三种Hash算法实现了sha1,sha256,sha512三种加密算法。
Bloom Filter
Bloom 过滤器是一个独特的数据结构,它可以用来确定一个元素是否存在于一个set中, 但是让Bloom过滤更有意思的是,它能准确的判断一个元素不存在,但是不能准确的判断出一个元素存在于set中。这样的特性可以用在比较耗时的数据查找中.
BloomFilter 简述
BloomFilter实际是一个bit的集合,按照以下方式工作:
1. 增加一个元素到filter中。
2. 将这个元素进行多次Hash,将的到的hash值的bit位设置为1。
当判断一个元素是否存在在set中时,按照同样的方法进行多次hash,如果对应的位上有一个不为1,那么就可以说明这个元素在set中不存在,但是即使每个元素位都是为1,也不能表明这个元素就存在,因为可能会发生Hash碰撞。
Funnels and PrimitiveSinks
Funnels 接口接受一个对象,并将对象数据发送给PrimitveSink接口, PrimitiveSink对象接受原始类型的数据,PrimitiveSink接口抽取参加Hash的数据,下面我们看一下具体的例子:
public enum BookFunnel implements Funnel<Book> {
//This is the single enum value
FUNNEL;
public void funnel(Book from, PrimitiveSink into) {
into.putBytes(from.getIsbn().getBytes(Charsets.UTF_8))
.putDouble(from.getPrice());
}
}
上面的例子中我们创建一个Funnel实例接受一个Book的实例。 注意我们这里通过枚举实现Funnel接口, ISBN和Price被放入到PrimitiveSink实例作为Hash函数的入参。
创建 BloomFilter 实例
上面我们学了怎样去创建一个Funnel实例,现在我们可以去创建BloomFilter实例:
BloomFilter<Book> bloomFilter = BloomFilter.create(new
BookFunnel(), 5);
上面这个例子中,我们通过调用BloomFilter的静态create方法传入一个Funnel实例和一个整数,这个整数其实就是使用的Hash函数个数,比如上面的5,我们其实是使用5个hash函数生成5个对应的Hashcode值。将这5个对应的hashcode值对应的bit位修改为1. 接下来我们来看一个具体的例子:
public class BloomFilterExample {
public static void main(String[] args) throws Exception {
File booksPipeDelimited = new
File("src/main/resources/books.data");
List<Book> books = Files.readLines(booksPipeDelimited,
Charsets.UTF_8, new LineProcessor<List<Book>>() {
Splitter splitter = Splitter.on('|');
List<Book> books = Lists.newArrayList();
Book.Builder builder = new Book.Builder();
public boolean processLine(String line) throws
IOException {
List<String> parts =
Lists.newArrayList(splitter.split(line));
builder.author(parts.get(0))
.title(parts.get(1))
.publisher(parts.get(2))
.isbn(parts.get(3))
.price(Double.parseDouble(parts.get(4)));
books.add(builder.build());
return true;
}
@Override
public List<Book> getResult() {
return books;
}
});
BloomFilter<Book> bloomFilter = BloomFilter.create(new
BookFunnel(), 5);
for (Book book : books) {
bloomFilter.put(book);
}
Book newBook = new Book.Builder().title("Mountain
Climbing").build();
Book book1 = books.get(0);
System.out.println("book "+book1.getTitle()+" contained
"+bloomFilter.mightContain(book1));
System.out.println("book "+newBook.getTitle()+" contained
"+bloomFilter.mightContain(newBook));
}
下面是执行结果:
Book [Being A Great Cook] contained true
Book [Mountain Climbing] contained false
上面的例子比较简单,就不多做介绍了。
虽然我们不会经常用到BloomFilter,但是我们还是应该将它放到我们常用java工具库中。
Optional
null objects是比较棘手的一个问题,有很大一部分问题,都是由于我们认为一个方法返回的值可能不是null,但是我们经常惊讶的发现她居然是是null,为了解决这个问题,Guava中有这样的一个类,叫Optional, Optional是一个不可变对象,可能会包含一个对象引用,也可能不包含对象引用, 一个好的使用Optional类的方式是让方法的返回值为Optional,通过这种方式,我们可以强制调用方认为方法的返回值可能是不存在的。必须采取措施防止这种情况。
创建 Optioanal实例
Optional类是一个抽象类,我们可以直接继承,也有一些静态方法我们可以使用去创建Optional实例。
1. Optional.absent()返回一个空的Optional实例
2. Optional.of(T ref) 返回一个包含Type ref的Optioanal实例
3. OPtioanal.fromNullable(T ref) 如果 ref不为null,那么返回一个包含 Type t的Optional实例,否则返回一个空的Optional实例
让我们来看一些简单的例子:
@Test
public void testOptionalOfInstance(){
TradeAccount tradeAccount = new
TradeAccount.Builder().build();
Optional<TradeAccount> tradeAccountOptional =
Optional.of(tradeAccount);
assertThat(tradeAccountOptional.isPresent(),is(true));
}
在这个例子中,我们使用Optional.of方法,返回一个包含给定对象的Optional实例,我们通过调用isPresent方法来确认包含的对象是否存在。
更有趣的Optional.fromNullable例子如下:
@Test(expected = IllegalStateException.class)
public void testOptionalNull(){
Optional<TradeAccount> tradeAccountOptional =
Optional.fromNullable(null);
assertThat(tradeAccountOptional.isPresent(),is(false));
tradeAccountOptional.get();
}
在上面的单元测试的例子中,我们使用Optional.fromNullable()静态方法,这个例子中,我们也返回了一个Optional实例,这次我们调用isPresent方法,期望得到的结果为false,调用get方法会抛出IllegalStateException.
总的来说,使用Optional类似的我们必须处理Null值的情况
Throwables
Throwables类包含了很多静态方法区处理在java中必然会遇到的 java.lang.Throwable,Errors和Exceptions错误。有的时候有这样的一个工具类去处理异常堆栈是什么方便的。 而Throwables类刚好提供了这样的工具。 下面我们将去看下面两个比较特别的方法:
获取异常链
Throwables.getCausalChain 方法返回一个Throwable对象集合。从堆栈的最顶层依次到最底层,下面是一个比较好的例子:
@Test
public void testGetCausalChain() {
ExecutorService executor =
Executors.newSingleThreadExecutor();
List<Throwable> throwables = null;
Callable<FileInputStream> fileCallable = new
Callable<FileInputStream>() {
@Override
public FileInputStream call() throws Exception {
return new FileInputStream("Bogus file");
}
};
Future<FileInputStream> fisFuture =
executor.submit(fileCallable);
try {
fisFuture.get();
} catch (Exception e) {
throwables = Throwables.getCausalChain(e);
}
assertThat(throwables.get(0).getClass().
isAssignableFrom(Execution
Exception.class),is(true));
assertThat(throwables.get(1).getClass().
isAssignableFrom(FileNotFo
undException.class),is(true));
executor.shutdownNow();
}
上面的这个例子中,我们创建了一个Callable实例期望返回一个FileInputStream对象,但是我们故意写了一个文件不存在的地址,这样就会出现FileNotFoundException。 当我们调用get方法时,出现了异常,我们调用Throwables.getCausalChain方法获取具体的异常,首先第一个是ExecutionException,第二个是FileNotFoundException.类似这样我们就得到所有的异常类型,这样我们就可以处理感兴趣的异常。
获取根异常
Throwables.getRootCause方法接受一个Throwable实例,返回异常对象的Root Cause. 下面是具体的例子:
@Test
public void testGetRootCause() throws Exception {
ExecutorService executor =
Executors.newSingleThreadExecutor();
Throwable cause = null;
final String nullString = null;
Callable<String> stringCallable = new Callable<String>() {
@Override
public String call() throws Exception {
return nullString.substring(0,2);
}
};
Future<String> stringFuture=
executor.submit(stringCallable);
try {
stringFuture.get();
} catch (Exception e) {
cause = Throwables.getRootCause(e);
}
assertThat(cause.getClass().isAssignableFrom(NullPointerExcep
tion.
class),is(true));
executor.shutdownNow();
}
上面的这个例子中我们通过getRootCause() 获取到了根异常
总结
在这一章中我们覆盖到了一些非常有用的类,虽然它们不是经常的使用到,但是一旦需要,就会感觉非常的方便,首先我们学了Hash函数,和Hashing类提供的一个有用的工具, 接着我们学习了利用Hash函数构造的一个比较好用的数据结构BloomFilter. 我们还学习了Optional类,使用这样的类可以使我们的代码更加强壮,避免出现Null值引起的异常,最后我们学习了Throwables类,Throwables类包含了一些有用的静态方法,可以方便的处理代码抛出的异常。