Guava庫學習: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本質上是位向量。它以如下方式工作:
-
添加一個元素到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,它包含一些有用的方法,能夠方便的幫助我們處理程序中拋出的異常。