这将是Guava库学习系列的最后一篇,可是仍然包含许多零零碎碎的知识。虽然不可能覆盖全部Guava涉及的知识,但咱们会竭尽所能。本篇将会介绍一些Guava中有用的工具,并不须要再开一个系列。本篇学习的一些工具可能并不会常用,但当你须要时,它是必不可少的。接下来,开始本篇的学习。 本篇,咱们将主要学习如下内容:Hashing、BloomFilter、Optional、Throwable。 java
Hashing散列类包含静态实用方法获取HashFunction实例web
BloomFilter数据结构,用于判断一个元素是否存在于一个Set集合中,BloomFilter数据结构有独特的属性,它能够明确返回一个元素不存在,不能确保一个元素确定存在。算法
Optional类为咱们提供了一种使用null引用的方式编程
Throwable类提供了一些静态实用方法处理Throwable的实例安全
建立合适的Hash函数
数据结构
散列Hash函数在编程时很基础,它用于肯定身份和检查重复。另外,它对于正确使用Java集合相当重要。散列函数工做原理是提供各类长度的数据并将它们映射为数字。由于咱们打算将任意数据映射到数字,因此保证Hash函数的耐碰撞性是相当重要的。换句话说,就是咱们要避免为不一样的数据产生一样的编号。固然了要写一个优秀的哈希函数,最好是留给专家来作。幸运的是,经过Guava,咱们不须要编写本身的Hash函数。Hashing类提供了静态的方法来建立HashFunction实例和一些须要注意的类型。ide
校验总和Hash函数
函数
Guava提供了两种HashFunction类,实现众所周知的校验总和算法:Adler-32和CRC-32。经过以下代码,能够为HashFunction建立一个实例:工具
HashFunction adler32 = Hashing.adler32(); HashFunction crc32 = Hashing.crc32();
上面,咱们简单的作了一个Hashing类静态方法的调用,来讲明所需的HashFuction的实现。单元测试
通常Hash函数
接下来咱们将调用通常的Hash函数,通常的散列函数非加密,适合用于基于散列的查询任务。第一个是murmur Hash算法,由Austin Appleby建立于2008年,其余通常的散列函数称为goodFastHash。下面来看怎样建立这些通常性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实例,第一个murmur Hash实例是一个32位murmur3_32算法的实现,第二个murmur Hash实例是一个128位murmur3_128算法的实现。
加密Hash函数
加密哈希函数用于信息安全,对加密Hash函数进行完整描述超出了本文的范围,这里只作简单的学习。通常来讲,加密哈希函数有如下属性:
数据的任何小变化,产生的Hash码都会发生大的变化
经过反向工程,根据Hash code值推算出原始的数据是不可能的
Guava提供了以下的方式建立加密Hash函数:
HashFunction sha1 = Hashing.sha1(); HashFunction sha256 = Hashing.sha256(); HashFunction sha512 = Hashing.sha512();
上面的三种Hash算法实现了sha1,sha256,sha512三种加密算法。
Bloom Filter
Bloom Filter是一个独特的数据结构,用来肯定一个元素是否存在于一个集合中。有意思的一点是,它能准确的判断一个元素不存在,不能准确的判断元素存在。这种特性能够用在比较耗时的操做中,如磁盘检索。
BloomFilter简述
BloomFilter本质上是位向量。它以以下方式工做:
添加一个元素到filter中
将这个元素进行屡次Hash运算,将获得的hash值的bit位设置为1
当判断一个元素是否存在在set中时,按照一样的方法进行屡次hash,并判断bit位设置的是1仍是0。这就是BloomFilter确保一个元素不存在的过程。 若是bit位不为1,就说明这个元素不存在于集合中,相反,即便元素位都是为1,也不能肯定元素存在于集合中,由于可能在以前已经发生了hash碰撞。在 学习Guava BloomFilter的建立和使用以前,咱们须要了解如何获得对象的字节并读入BloomFilter进行hash运算。
Funnels 和 PrimitiveSinks
Funnel接口接收一个肯定类型的对象,并将数据发送给PrimitiveSinks实例。PrimitiveSinks对象用来接收原始类型的数据。PrimitiveSinks实例将抽取hash运算所需的字节数。BloomFilter中使用Funnel接口来抽取那些在BloomFilter数据结构中等待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接口,这有助于保持BloomFilter的序列化,也须要Funnel实例是可序列化的。ISBN和Price被放入到PrimitiveSink实例做为Hash函数的入参。
建立BloomFilter实例
上面咱们了解了如何建立Funnel实例,下面介绍如何建立BloomFilter实例:
BloomFilter<Book> bloomFilter = BloomFilter.create(BookFunnel.FUNNEL, 5);
上面的例子中,咱们经过调用BloomFilter静态的create方法,传入Funnel实例和一个表示int值,这个值表示BloomFilter中使用hash函数的个数。若是使用的hash函数的个数大大超过了,假阳性的数量将大幅上升。咱们来看一个建立BloomFilter实例的例子:
@Test public void testBloomFilter() throws IOException { File booksPipeDelimited = new File("src/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(BookFunnel.FUNNEL, 5); for (Book book : books) { bloomFilter.put(book); } Book newBook = new Book.Builder().title("Test Cook Book 2").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 [Test Cook Book] contained true book [Test Cook Book 2] contained false
在上面的例子中,咱们经过Files.readLines方法以 | 为分隔符读取和使用文件,结合LineProcessor回调将每行的文本转换为Book对象。每个Book对象都添加到List里面并返回。以后咱们经过BookFunnel枚举和指望的hash次数 5,建立了一个BloomFilter实例。以后将全部的Book对象从list中添加到BloomFilter,最后,经过调用mightContain方法测试添加和未添加到BloomFilter的Book。
虽然咱们可能不须要常用BloomFilter,但在工做中这是一个很是有用的工具。
Optional
空对象的处理比较麻烦,有很大一部分问题,都是因为咱们认为一个方法返回的值可能不是null,但咱们惊讶的发现对象竟然为null,为了解决这个问题,Guava提供了一个Optional类,它是一个不可变对象,可能包含或不包含另外一个对象的引用。若是Optional包含实例,它被认为是存在present,若是不包含实例,它被认为是缺席absent。Optional类比较好的使用方式是使用Optional做为方法的返回值。这样咱们迫使客户端考虑返回值可能不存在,咱们应该采起相应的措施防止此状况。
建立Optional实例
Optional类是抽象类,咱们能够直接继承,咱们可使用它提供的一些静态方法来建立Optional实例,例如:
Optional.absent() ,返回一个空的Optional实例
Optional.of(T ref) ,返回一个包含Type ref的Optioanal实例
Optioanal.fromNullable(T ref) ,若是 ref不为null,那么返回一个包含Type ref的Optional实例,不然返回一个空的Optional实例
Optional.or(Supplier<T> supplier),若是引用存在,返回此引用,不然,返回Supplier.get。
咱们来看一些简单的例子:
@Test public void testOptionalOfInstance() { Book book = new Book.Builder().build(); Optional<Book> bookOptional = Optional.of(book); assertThat(bookOptional.isPresent(), is(true)); }
在上面的单元测试中,咱们使用了静态的Optional.of方法对传入的对象装饰后,返回一个Optional实例。咱们经过调用isPresent方 法来断言包含的对象存在(为true)。更有趣的是经过以下方式使用Optional.fromNullable方法:
@Test(expected = IllegalStateException.class) public void testOptionalNull() { Optional<Book> bookOptional = Optional.fromNullable(null); assertThat(bookOptional.isPresent(), is(false)); bookOptional.get(); }
在上面的单元测试中,咱们经过fromNullable静态方法建立了Optional实例,一样咱们也返回了Optional实例,这里咱们断言调用 isPresent方法返回的是false。以后,因为没有实例存在,咱们断言调用get方法会抛出IllegalStateExeption异常。 Optional.fromNullable是很好的方法,用来在调用返回结果以前装饰对象。Optional的真正重要性是,它对于返回值是否存在是没 有保证的,它迫使咱们必须去处理Null值的状况。
Throwables
Throwables类包含一些实用的静 态方法,用来处理在java中常常遇到的java.lang.Throwable、Errors 和 Exceptions错误。有的时候,有一个工具类去处理异常堆栈是很方便的,Throwables类给咱们提供了方便。下面咱们将介绍两个比较特别的方 法:Throwables.getCausalChain 和 Throwables.getRootCause。
获取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(ExecutionException.class), is(true)); assertThat(throwAbles.get(1).getClass().isAssignableFrom(FileNotFoundException.class), is(true)); executor.shutdownNow(); }
在这个例子中,咱们建立了一个Callable实例指望返回一个FileInputStream对象,咱们故意制造了一个 FileNotFoundException。以后,咱们将Callable实例提交给ExecutorService,并返回了Future引用。当我 们调用Future.get方法,抛出了一个异常,咱们调用Throwables.getCausalChain方法获取到具体的异常链。最后,咱们断言 异常链中的第一个Throwable实例是ExecutionException,第二个是FileNotFoundException。经过这个 Throwable的异常链,咱们能够选择性的过滤咱们想要检查的异常。
获取根异常
Throwables.getRootCause方法接收一个Throwable实例,并返回根异常信息。下面是一个例子:
@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(NullPointerException.class), is(true)); executor.shutdownNow(); }
咱们一样使用一个Callable 实例,并故意抛出一个异常,此次是NullPointerException。当咱们经过返回的Future对象stringFuture调用get方法 捕获到相应的异常后,咱们调用了Throwables.getRootCause方法,并将返回的Throwable对象赋值给cause变量,而后咱们 断言根异常确实是NullPointerException。虽然这些方法不会取代日志文件中对异常堆栈信息的追踪,但咱们可使用这些方法在之后处理那些有价值的信息。
Summary
在本文中,咱们介绍了一些很是有用的类,这些类可能并不常常被用到,可是在须要的时候,会变得很方便。首先咱们学习了Hash函数和Hashing提供的 一些工具,以后咱们利用hash函数构造了一个有用的数据结构BloomFilter。咱们也学习了Optional类,使用它能够避免那些null值引 起的异常,让咱们的代码变得更健壮。最后,咱们介绍了Throwables,它包含一些有用的方法,可以方便的帮助咱们处理程序中抛出的异常。