Guava翻譯之SmallFunction

normanli發表於2017-02-27

Guava 的一些小功能點

我們已經到了書的最後一章,但是任然感覺還有很多東西沒有覆蓋到。雖然在這麼短的篇幅裡我們不可能覆蓋到guava的多有功能,但是我們已經盡力了。 這一章我們將介紹一些沒有必要單獨一章介紹的一些小的工具類,雖然他們不是天天不用到,但是一旦用到,你就會發現真的非常方便. 這一章我們將學習一下的幾個知識點:

  1. Hashing 類: 通過Hashing類的靜態工具方法我們可以獲取HashFunction 例項
  2. 布隆過濾: Bloom 過濾這樣的資料結構,可以明確返回一個資料不存在,但是不能確保一個元素肯定存在.當存在Hash碰撞時有可能誤判。
  3. Optional類: Optional 類可以讓我們選擇使用 null 的引用
  4. Throwables類: Throwables 類中有靜態工具方法可以與Throwable協作

建立合適的Hash函式

Hash函式是確定物件身份標識和確定是否重複的基礎,同時它也是合理使用java結合的必備條件。 Hash函式的基本工作方式: 將物件資料對映為數字。 因此我們希望避免將不同的資料生成相同的數字,毋庸置疑,寫一個優秀的Hash函式的工作就留給那些專家吧, 但是幸運的是,在Guava的幫助下我們不需要自己去寫Hash函式, Hashing類提供了一些靜態方法可以建立HashFunction的例項。

校驗總和雜湊函式

Guava 提供了兩種實現了checksum演算法的HashFunction,分別是Adler-32 和 CRC-32,建立這兩個HashFunction 可以按照如下的方法:

HashFunction adler32 = Hashing.adler32();
HashFunction crc32 = Hashing.crc32();

這裡我們可以簡單的使用Hashing函式的靜態方法去獲取想要的例項.

一般雜湊函式

下面我們將看一下一般的hash函式,一般的hash函式是不帶加密語義,非常適合基於Hash的查詢類任務。 第一個要介紹的就是 murmur Hash,這是一個在2008年由Austin發現的。 其他的一般性hash演算法叫做GooFastHash。 下面讓我們看下怎樣建立這些一般性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. 第一個是實現了32-bit的murmur3_32演算法,第二個murmur Hash例項實現了128bit murmur3_128演算法。

密碼雜湊函式

雖然完整的討論密碼雜湊函式超出了本書的返回,但是簡單的說密碼雜湊函式就是為了保證資料的安全。 一般來說,密碼雜湊函式有一些如下的屬性:
— 一點小的資料改變,就會導致HashCode發生很大的變化
— 在理論上,通過hash code 反推出原始資料是什麼是不可能的。

下面Guava提供了建立密碼雜湊函式的變體:

HashFunction sha1 = Hashing.sha1();
HashFunction sha256 = Hashing.sha256();
HashFunction sha512 = Hashing.sha512();

上面的三種Hash演算法實現了sha1,sha256,sha512三種加密演算法。

Bloom Filter

Bloom 過濾器是一個獨特的資料結構,它可以用來確定一個元素是否存在於一個set中, 但是讓Bloom過濾更有意思的是,它能準確的判斷一個元素不存在,但是不能準確的判斷出一個元素存在於set中。這樣的特性可以用在比較耗時的資料查詢中.

BloomFilter 簡述

BloomFilter實際是一個bit的集合,按照以下方式工作:

  1. 增加一個元素到filter中。
  2. 將這個元素進行多次Hash,將的到的hash值的bit位設定為1。

當判斷一個元素是否存在在set中時,按照同樣的方法進行多次hash,如果對應的位上有一個不為1,那麼就可以說明這個元素在set中不存在,但是即使每個元素位都是為1,也不能表明這個元素就存在,因為可能會發生Hash碰撞。

Funnels and PrimitiveSinks

Funnels 介面接受一個物件,並將物件資料傳送給PrimitveSink介面, PrimitiveSink物件接受原始型別的資料,PrimitiveSink介面抽取參加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介面, ISBN和Price被放入到PrimitiveSink例項作為Hash函式的入參。

建立 BloomFilter 例項

上面我們學了怎樣去建立一個Funnel例項,現在我們可以去建立BloomFilter例項:

BloomFilter<Book> bloomFilter = BloomFilter.create(new
BookFunnel(), 5);

上面這個例子中,我們通過呼叫BloomFilter的靜態create方法傳入一個Funnel例項和一個整數,這個整數其實就是使用的Hash函式個數,比如上面的5,我們其實是使用5個hash函式生成5個對應的Hashcode值。將這5個對應的hashcode值對應的bit位修改為1. 接下來我們來看一個具體的例子:

public class BloomFilterExample {
public static void main(String[] args) throws Exception {
File booksPipeDelimited = new
File("src/main/resources/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(new
BookFunnel(), 5);
for (Book book : books) {
bloomFilter.put(book);
}
Book newBook = new Book.Builder().title("Mountain
Climbing").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 [Being A Great Cook] contained true
Book [Mountain Climbing] contained false

上面的例子比較簡單,就不多做介紹了。

雖然我們不會經常用到BloomFilter,但是我們還是應該將它放到我們常用java工具庫中。

Optional

null objects是比較棘手的一個問題,有很大一部分問題,都是由於我們認為一個方法返回的值可能不是null,但是我們經常驚訝的發現她居然是是null,為了解決這個問題,Guava中有這樣的一個類,叫Optional, Optional是一個不可變物件,可能會包含一個物件引用,也可能不包含物件引用, 一個好的使用Optional類的方式是讓方法的返回值為Optional,通過這種方式,我們可以強制呼叫方認為方法的返回值可能是不存在的。必須採取措施防止這種情況。

建立 Optioanal例項

Optional類是一個抽象類,我們可以直接繼承,也有一些靜態方法我們可以使用去建立Optional例項。

  1. Optional.absent()返回一個空的Optional例項
  2. Optional.of(T ref) 返回一個包含Type ref的Optioanal例項
  3. OPtioanal.fromNullable(T ref) 如果 ref不為null,那麼返回一個包含 Type t的Optional例項,否則返回一個空的Optional例項

讓我們來看一些簡單的例子:

@Test
public void testOptionalOfInstance(){
TradeAccount tradeAccount = new
TradeAccount.Builder().build();
Optional<TradeAccount> tradeAccountOptional =
Optional.of(tradeAccount);
assertThat(tradeAccountOptional.isPresent(),is(true));
}

在這個例子中,我們使用Optional.of方法,返回一個包含給定物件的Optional例項,我們通過呼叫isPresent方法來確認包含的物件是否存在。
更有趣的Optional.fromNullable例子如下:

@Test(expected = IllegalStateException.class)
public void testOptionalNull(){
Optional<TradeAccount> tradeAccountOptional =
Optional.fromNullable(null);
assertThat(tradeAccountOptional.isPresent(),is(false));
tradeAccountOptional.get();
}

在上面的單元測試的例子中,我們使用Optional.fromNullable()靜態方法,這個例子中,我們也返回了一個Optional例項,這次我們呼叫isPresent方法,期望得到的結果為false,呼叫get方法會丟擲IllegalStateException.
總的來說,使用Optional類似的我們必須處理Null值的情況

Throwables

Throwables類包含了很多靜態方法區處理在java中必然會遇到的 java.lang.Throwable,Errors和Exceptions錯誤。有的時候有這樣的一個工具類去處理異常堆疊是什麼方便的。 而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(Execution
Exception.class),is(true));
assertThat(throwables.get(1).getClass().
isAssignableFrom(FileNotFo
undException.class),is(true));
executor.shutdownNow();
}

上面的這個例子中,我們建立了一個Callable例項期望返回一個FileInputStream物件,但是我們故意寫了一個檔案不存在的地址,這樣就會出現FileNotFoundException。 當我們呼叫get方法時,出現了異常,我們呼叫Throwables.getCausalChain方法獲取具體的異常,首先第一個是ExecutionException,第二個是FileNotFoundException.類似這樣我們就得到所有的異常型別,這樣我們就可以處理感興趣的異常。

獲取根異常

Throwables.getRootCause方法接受一個Throwable例項,返回異常物件的Root Cause. 下面是具體的例子:

@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(NullPointerExcep
tion.
class),is(true));
executor.shutdownNow();
}

上面的這個例子中我們通過getRootCause() 獲取到了根異常

總結

在這一章中我們覆蓋到了一些非常有用的類,雖然它們不是經常的使用到,但是一旦需要,就會感覺非常的方便,首先我們學了Hash函式,和Hashing類提供的一個有用的工具, 接著我們學習了利用Hash函式構造的一個比較好用的資料結構BloomFilter. 我們還學習了Optional類,使用這樣的類可以使我們的程式碼更加強壯,避免出現Null值引起的異常,最後我們學習了Throwables類,Throwables類包含了一些有用的靜態方法,可以方便的處理程式碼丟擲的異常。


相關文章