Guava库学习:Guava 零碎知识

    这将是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本质上是位向量。它以以下方式工做:

  1. 添加一个元素到filter中

  2. 将这个元素进行屡次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实例,例如:

  1. Optional.absent() ,返回一个空的Optional实例 

  2. Optional.of(T ref) ,返回一个包含Type ref的Optioanal实例 

  3. Optioanal.fromNullable(T ref) ,若是 ref不为null,那么返回一个包含Type ref的Optional实例,不然返回一个空的Optional实例

  4. 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,它包含一些有用的方法,可以方便的帮助咱们处理程序中抛出的异常。

相关文章
相关标签/搜索