lambdas for_Wordcounter,使用Lambdas和Fork / Join计算Java中的单词数
lambdas for
這些天來(lái),我發(fā)布了Wordcounter ,這是一個(gè)Java庫(kù)和命令行實(shí)用程序,用于對(duì)文本文件中的單詞進(jìn)行計(jì)數(shù)并對(duì)單詞計(jì)數(shù)進(jìn)行分析,從而大量使用了功能編程結(jié)構(gòu)和并行計(jì)算方法。 這是我在“令人討厭的快速問(wèn)答”大賽第四個(gè)條目SAP ,經(jīng)過(guò)給料機(jī) , 托多爾和Hanoier 。
該庫(kù)使用JDK 8 lambda ,以及新的JDK 7功能,例如Fork / Join和NIO.2 。 它是內(nèi)置的,只能與支持lambda的JDK 8的早期訪問(wèn)版本一起使用 。
隨著JDK 8中l(wèi)ambda及其支持功能的引入,我們用Java構(gòu)建軟件的方式將發(fā)生變化。 如果您想了解幾年后Java代碼的外觀,可以看看Wordcounter。 與當(dāng)前大多數(shù)可用資源不同,這不是簡(jiǎn)單的教程,而是一個(gè)實(shí)際的項(xiàng)目。
競(jìng)賽任務(wù)要求使用Fork / Join和lambdas實(shí)現(xiàn)算法,該算法分析目錄中的所有文件并查找文件中十個(gè)最常用的單詞以及它們出現(xiàn)的次數(shù)。 我沒(méi)有簡(jiǎn)單地堅(jiān)持使用Fork / Join,而是嘗試找到最適合此任務(wù)的并行方法,這使我選擇了Producer / Consumer作為核心的單詞計(jì)數(shù)邏輯。
您可以在github上探索源代碼。 還有一個(gè)相當(dāng)全面的自述文件,提供了更詳細(xì)的文檔。
最新的二進(jìn)制,javadoc和源代碼包可在GitHub 下載部分中找到 。 如果有足夠的興趣,我將在線發(fā)布Javadoc,并在中央Maven存儲(chǔ)庫(kù)中提供該庫(kù)。
歡迎提供反饋,評(píng)論和貢獻(xiàn)!
總覽
圖書館特色
- 計(jì)算字符串,單個(gè)文本文件或包含文本文件的目錄樹中的所有單詞。
- 分析單詞計(jì)數(shù)以找到前N個(gè)最常用的單詞,后N個(gè)最不常用的單詞或總單詞數(shù)。
- 通過(guò)外部謂詞指定字符是否為文字字符。
- 指定要對(duì)單詞執(zhí)行的可選操作,例如通過(guò)外部運(yùn)算符轉(zhuǎn)換為小寫字母。
- 在非并行和并行實(shí)現(xiàn)之間進(jìn)行選擇,以比較其性能。
- 如果需要,將并行度指定為與內(nèi)核數(shù)不同的值。
編程要點(diǎn)
- 使用生產(chǎn)者/使用者來(lái)讀取文件并并行計(jì)算每個(gè)文件中的單詞。 實(shí)際的機(jī)制封裝在通用的可重用實(shí)現(xiàn)中。
- 使用Fork / Join對(duì)字?jǐn)?shù)進(jìn)行分析。 這里,實(shí)際的機(jī)制再次封裝在通用的可重用實(shí)現(xiàn)中。
- 使用NIO.2遍歷目錄樹和讀取文件。
- 大量使用函數(shù)接口和lambda表達(dá)式 ,以便在適當(dāng)?shù)牡胤絺鬟f函數(shù)而不是數(shù)據(jù)。
- 有兩個(gè)最重要的類的綜合單元測(cè)試和性能測(cè)試。
- 像往常一樣,該代碼干凈,結(jié)構(gòu)合理且易于閱讀。 格式,命名和注釋是統(tǒng)一且一致的。 適當(dāng)?shù)厥褂昧嗣嫦驅(qū)ο蠛凸δ芫幊碳夹g(shù)已經(jīng)引起了很多關(guān)注。
命令行界面
要啟動(dòng)命令行程序,請(qǐng)執(zhí)行以下命令:
java -jar wordcounter-1.0.4.jar <options>所有選項(xiàng)都有合理的默認(rèn)值,因此都不是必需的。 對(duì)所有選項(xiàng)使用默認(rèn)值會(huì)導(dǎo)致在當(dāng)前目錄及其子目錄中找到前10個(gè)最常用的單詞。 指定非默認(rèn)值允許指定不同的目錄,分析模式,單詞字符,單詞數(shù)和并行度,以及忽略大小寫或使用串行而不是并行計(jì)算,例如:
在“單詞”目錄中找到最常用的10個(gè)單詞-p words
在目錄“ wordsx”中查找前5個(gè)最不常用的單詞,并將數(shù)字視為單詞字符,忽略大小寫,并進(jìn)行信息記錄-p wordsx -m bottom -d 1234567890 -i -n 5 -l info
有關(guān)命令行界面選項(xiàng)的更多信息,請(qǐng)參見(jiàn)自述文件中的命令行界面 。
設(shè)計(jì)
庫(kù)的設(shè)計(jì)將問(wèn)題劃分為通用并行處理實(shí)用程序,封裝用于表示原始字?jǐn)?shù)和排序字?jǐn)?shù)的數(shù)據(jù)結(jié)構(gòu)的類,最后是使用前兩組功能執(zhí)行計(jì)數(shù)和分析的類。 實(shí)際上,所有這些類都大量使用功能接口的實(shí)例,以便允許對(duì)其通用行為進(jìn)行特定的自定義。 這導(dǎo)致代碼中大量注入了lambda表達(dá)式和方法引用。 歡迎來(lái)到Java函數(shù)編程的世界!
通用并行處理實(shí)用程序
ForkJoinComputer類
ForkJoinComputer<T>類是通用的Fork / Join計(jì)算機(jī)。 它將初始大小除以2,直到達(dá)到指定的并行度或低于指定的閾值,然后使用指定的Computer<T>串行計(jì)算每個(gè)部分,然后使用指定的Merger<T>所有計(jì)算的結(jié)果。 在這里,計(jì)算機(jī)和合并是定義如下的功能接口:
public interface Computer<T> {T compute(int lo, int hi); }public interface Merger<T> {T merge(T result1, T result2); }可以通過(guò)簡(jiǎn)單地使用適當(dāng)?shù)膌ambda實(shí)例化該類,然后調(diào)用其compute方法來(lái)使用此類。
new ForkJoinComputer<Integer>(n, 1000,(lo, hi) -> { int sum = 0; for (int i = lo + 1; i <= hi; i++) sum += i; return sum; },(a, b) -> a + b).compute();ProducerConsumerExecutor類
ProducerConsumerExecutor<T1, T2>類是通用的Producer / Consumer執(zhí)行程序。 它啟動(dòng)一個(gè)Producer<T1>任務(wù)和多個(gè)Mediator<T1, T2>和Consumer<T2>任務(wù),它們的數(shù)量等于指定的并行度。 生產(chǎn)者將T1實(shí)例放入BlockingQueue<T1> 。 中介者從那里獲取這些實(shí)例,將其轉(zhuǎn)換為T2 ,并將其放入另一個(gè)類型為BlockingQueue<T2>阻塞隊(duì)列中。 最后,使用者從第二個(gè)阻塞隊(duì)列中獲取T2實(shí)例并對(duì)其進(jìn)行處理。
在這里, Producer, Consumer和Mediator是定義如下的功能接口:
public interface Producer<T> {void produce(Block<T> block); }public interface Consumer<T> {void consume(T t); }public interface Mediator<T1, T2> {void mediate(T1 t, Block<T2> block); }在上面的代碼中, Block是java.util.functions定義的標(biāo)準(zhǔn)函數(shù)。 傳遞給Producer和Mediator方法的塊將產(chǎn)生的數(shù)據(jù)放入相應(yīng)的阻塞隊(duì)列中。
與ForkJoinComputer相似,可以通過(guò)簡(jiǎn)單地使用適當(dāng)?shù)膌ambda實(shí)例化該類,然后調(diào)用其execute方法來(lái)使用此類。
數(shù)據(jù)結(jié)構(gòu)類
這些類封裝了用于表示原始和已排序字?jǐn)?shù)的數(shù)據(jù)結(jié)構(gòu)。
- WordCounts類表示映射到其用法計(jì)數(shù)的單詞列表。
- TopWordCounts類表示映射到所有具有此類計(jì)數(shù)的單詞的單詞使用情況計(jì)數(shù)的排序列表。
字?jǐn)?shù)統(tǒng)計(jì)和分析類
WordCounter類
WordCounter類提供了一種方法,用于以串行或并行方式對(duì)表示文件或目錄樹的Path單詞進(jìn)行計(jì)數(shù)。 只需使用適當(dāng)?shù)膌ambda實(shí)例化它,然后調(diào)用其count方法即可使用它:
// Count all words consisting of only alphabetic chars, ignoring case, using parallel processing WordCounts wc = new WordCounter(path, (c) -> Character.isAlphabetic(c), (s) -> s.toLowerCase(), true).count();并行實(shí)現(xiàn)使用ProducerConsumerExecutor<Path, String> 。 生產(chǎn)者只需遍歷目錄樹并產(chǎn)生Path實(shí)例。 中介者將文件讀入文本片段,使用者將每個(gè)文本片段中的單詞計(jì)數(shù),然后將它們收集到單個(gè)WordCounts實(shí)例中。 這是通過(guò)以下代碼完成的:
private WordCounts countPar() {final WordCounts wc = new WordCounts(parLevel);new ProducerConsumerExecutor<Path, String>((block) -> collectPaths(block),(file, block) -> readFileToBlock(file, block),(text) -> wc.add(countWords(text, pred, op)), parLevel).execute();return wc; }WordCountAnalyzer類
WordCountAnalyzer類提供了對(duì)WordCounter產(chǎn)生的字?jǐn)?shù)進(jìn)行分析的方法,例如查找前N個(gè)最常用的字。 也可以通過(guò)簡(jiǎn)單地實(shí)例化它,然后調(diào)用它的方法之一(例如findTop或total來(lái)使用它:
// Find the top 10 most used words in wc TopWordCounts twc = new WordCountAnalyzer(wc, true).findTop(10, (x, y) -> (y - x));Differentnet分析類型實(shí)現(xiàn)內(nèi)部Analysis<T>接口,該接口定義如下:
interface Analysis<T> {T compute(int lo, int hi);T merge(T r1, T r2); }由于以上兩種方法的簽名模仿了ForkJoinComputer使用的Computer和Merger功能接口,因此我們可以通過(guò)以下方式對(duì)所有分析類型使用fork / join:
public TopWordCounts findTop(int number, Comparator<Integer> comparator) {return analyse(new FindTopAnalysis(number, comparator)); }private <T> T analyse(Analysis<T> a) {if (par) {return new ForkJoinComputer<T>(wc.getSize(), THRESHOLD, a::compute, a::merge, parLevel).compute();} else {return a.compute(0, wc.getSize());} }有關(guān)庫(kù)設(shè)計(jì)的更多信息,請(qǐng)參見(jiàn)自述文件中的設(shè)計(jì)。
性能
我發(fā)現(xiàn)并行的Producer / Consumer字?jǐn)?shù)統(tǒng)計(jì)實(shí)現(xiàn)很好地適應(yīng)了不同數(shù)量的內(nèi)核和I / O速度。 它比串行實(shí)現(xiàn)要快得多。 與之不同的是,當(dāng)使用不切實(shí)際的大量唯一單詞進(jìn)行測(cè)試時(shí),并行的Fork / Join分析實(shí)現(xiàn)僅比串行的實(shí)現(xiàn)快,而且程度適中。 由于唯一字的數(shù)量很少,因此實(shí)際上比串行字慢。
下表比較了單詞計(jì)數(shù)的性能,并在以下情況下找到了最佳分析:
- CPU AMD Phenom II X4 965 3.4 GHz(4核),4 GB RAM,Windows 7,JDK 8
- 默認(rèn)選項(xiàng):由字母字符組成的單詞,區(qū)分大小寫
- 默認(rèn)并行度,等于內(nèi)核數(shù)
字?jǐn)?shù)統(tǒng)計(jì)性能
| 序列號(hào) | 1個(gè) | 10000000 | ?65 | 2200-2400 |
| 平行 | 1個(gè) | 10000000 | ?65 | 500-600 |
| 序列號(hào) | 100 | 10000000 | ?65 | 1600-1800 |
| 平行 | 100 | 10000000 | ?65 | 500-600 |
查找最佳分析性能
| 序列號(hào) | 2000000 | 10000000 | 10 | 200-250 |
| 平行 | 2000000 | 10000000 | 10 | 200-250 |
玩代碼
如果您想使用這些代碼,我建議您使用最新的NetBeans 7.3 beta,在撰寫本文時(shí)為NetBeans IDE 7.3 Beta 2 。 請(qǐng)注意,即使在此版本中,也無(wú)法在IDE中編譯lambda,因此周圍到處都有紅色標(biāo)記。 但是,從IDE啟動(dòng)Maven構(gòu)建并運(yùn)行測(cè)試仍然可以正常工作。 根據(jù)此博客文章 ,應(yīng)該可以對(duì)lambda使用IntelliJ IDEA 12 EAP內(nèi)部版本122.202或更高版本,但是我沒(méi)有親自嘗試過(guò)。 我確實(shí)嘗試了Eclipse,但由于Eclipse使用了自己的對(duì)lambda無(wú)知的JDT編譯器,因此發(fā)現(xiàn)它是一場(chǎng)失敗的比賽。
結(jié)論
這是我第一次接觸Java函數(shù)編程。 盡管Java仍然不是Scala,但是與我以前的Java代碼相比,新的函數(shù)式編程結(jié)構(gòu)大大改變了我設(shè)計(jì)和實(shí)現(xiàn)Wordcounter的方式。 我發(fā)現(xiàn)這種新的編程風(fēng)格功能強(qiáng)大且富有表現(xiàn)力,我相信隨著Java 8的發(fā)布,它將很快成為主流。
對(duì)我來(lái)說(shuō),這也是最后的“怪異敏捷”任務(wù)。 評(píng)審團(tuán)慷慨地授予了我的意見(jiàn)書,甚至在比賽結(jié)束之前,我就找到了自己的優(yōu)勝者。
如果這篇文章引起了您的興趣,請(qǐng)隨時(shí)下載并探索Wordcounter,用它來(lái)學(xué)習(xí)新的Java函數(shù)編程構(gòu)造,并讓我知道在這個(gè)過(guò)程中是否可以幫助您。
參考: Wordcounter,來(lái)自JCG合作伙伴 Stoyan Rachev的Lambdas和Fork / Join中的Java單詞計(jì)數(shù)在 Stoyan Rachev博客中。
翻譯自: https://www.javacodegeeks.com/2012/12/wordcounter-counting-words-in-java-with-lambdas-and-forkjoin.html
lambdas for
總結(jié)
以上是生活随笔為你收集整理的lambdas for_Wordcounter,使用Lambdas和Fork / Join计算Java中的单词数的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Redmi路由器AC2100发布手机如何
- 下一篇: 只有经验丰富的开发人员才能教您有关Jav