我们已经到了书的最后一章,但是任然感觉还有很多东西没有覆盖到。虽然在这么短的篇幅里我们不可能覆盖到guava的多有功能,但是我们已经尽力了。 这一章我们将介绍一些没有必要单独一章介绍的一些小的工具类,虽然他们不是天天不用到,但是一旦用到,你就会发现真的非常方便. 这一章我们将学习一下的几个知识点:
Hashing 类: 通过Hashing类的静态工具方法我们可以获取HashFunction 实例布隆过滤: Bloom 过滤这样的数据结构,可以明确返回一个数据不存在,但是不能确保一个元素肯定存在.当存在Hash碰撞时有可能误判。Optional类: Optional 类可以让我们选择使用 null 的引用Throwables类: Throwables 类中有静态工具方法可以与Throwable协作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 过滤器是一个独特的数据结构,它可以用来确定一个元素是否存在于一个set中, 但是让Bloom过滤更有意思的是,它能准确的判断一个元素不存在,但是不能准确的判断出一个元素存在于set中。这样的特性可以用在比较耗时的数据查找中.
BloomFilter实际是一个bit的集合,按照以下方式工作:
增加一个元素到filter中。将这个元素进行多次Hash,将的到的hash值的bit位设置为1。当判断一个元素是否存在在set中时,按照同样的方法进行多次hash,如果对应的位上有一个不为1,那么就可以说明这个元素在set中不存在,但是即使每个元素位都是为1,也不能表明这个元素就存在,因为可能会发生Hash碰撞。
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函数的入参。
上面我们学了怎样去创建一个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 trueBook [Mountain Climbing] contained false
上面的例子比较简单,就不多做介绍了。
虽然我们不会经常用到BloomFilter,但是我们还是应该将它放到我们常用java工具库中。
null objects是比较棘手的一个问题,有很大一部分问题,都是由于我们认为一个方法返回的值可能不是null,但是我们经常惊讶的发现她居然是是null,为了解决这个问题,Guava中有这样的一个类,叫Optional, Optional是一个不可变对象,可能会包含一个对象引用,也可能不包含对象引用, 一个好的使用Optional类的方式是让方法的返回值为Optional,通过这种方式,我们可以强制调用方认为方法的返回值可能是不存在的。必须采取措施防止这种情况。
Optional类是一个抽象类,我们可以直接继承,也有一些静态方法我们可以使用去创建Optional实例。
Optional.absent()返回一个空的Optional实例Optional.of(T ref) 返回一个包含Type ref的Optioanal实例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类包含了很多静态方法区处理在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类包含了一些有用的静态方法,可以方便的处理代码抛出的异常。
相关资源:python入门教程(PDF版)