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,它包含一些有用的方法,可以方便的幫助咱們處理程序中拋出的異常。

相關文章
相關標籤/搜索