Guava庫學習:Guava 零碎知識

jopen 9年前發布 | 57K 次閱讀 Guava 常用工具包

    這將是Guava庫學習系列的最后一篇,但是仍然包含許多零零碎碎的知識。雖然不可能覆蓋所有Guava涉及的知識,但我們會竭盡所能。本篇將會介紹一些Guava中有用的工具,并不需要再開一個系列。本篇學習的一些工具可能并不會經常使用,但當你需要時,它是必不可少的。接下來,開始本篇的學習。 本篇,我們將主要學習以下內容:Hashing、BloomFilter、Optional、Throwable。  

  • Hashing散列類包含靜態實用方法獲取HashFunction實例

  • BloomFilter數據結構,用于判斷一個元素是否存在于一個Set集合中,BloomFilter數據結構有獨特的屬性,它可以明確返回一個元素不存在,不能確保一個元素肯定存在。

  • Optional類為我們提供了一種使用null引用的方式

  • Throwable類提供了一些靜態實用方法處理Throwable的實例

       創建合適的Hash函數

      散列Hash函數在編程時很基礎,它用于確定身份和檢查重復。另外,它對于正確使用Java集合至關重要。散列函數工作原理是提供各種長度的數據并將它們映射為數字。因為我們打算將任意數據映射到數字,所以保證Hash函數的耐碰撞性是至關重要的。換句話說,就是我們要避免為不同的數據產生同樣的編號。當然了要寫一個優秀的哈希函數,最好是留給專家來做。幸運的是,通過Guava,我們不需要編寫自己的Hash函數。Hashing類提供了靜態的方法來創建HashFunction實例和一些需要注意的類型。

      校驗總和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,它包含一些有用的方法,能夠方便的幫助我們處理程序中拋出的異常。

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!