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