日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

java8 streams_使用Java 8 Streams进行编程对算法性能的影响

發(fā)布時(shí)間:2023/12/3 java 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java8 streams_使用Java 8 Streams进行编程对算法性能的影响 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

java8 streams

多年來,使用Java進(jìn)行多范式編程已經(jīng)成為可能,它支持面向服務(wù),面向?qū)ο蠛兔嫦蚍矫娴木幊痰幕旌稀?帶有l(wèi)ambda和java.util.stream.Stream類的Java 8是個(gè)好消息,因?yàn)樗刮覀兛梢詫⒐δ苄跃幊谭独砑拥交旌现小?確實(shí),lambda周圍有很多炒作。 但是,改變我們的習(xí)慣和編寫代碼的方式是明智的選擇,而無需先了解可能隱患的危險(xiǎn)嗎?

Java 8的Stream類很簡(jiǎn)潔,因?yàn)樗鼓梢允占瘮?shù)據(jù)并將該數(shù)據(jù)上的多個(gè)功能調(diào)用鏈接在一起,從而使代碼整潔。 映射/歸約算法是一個(gè)很好的例子,您可以通過首先從復(fù)雜域中選擇或修改數(shù)據(jù)并對(duì)其進(jìn)行簡(jiǎn)化(“映射”部分),然后將其縮減為一個(gè)有用的值來收集數(shù)據(jù)并將其聚合。

以以下數(shù)據(jù)類為例(用Groovy編寫,這樣我就可以免費(fèi)生成構(gòu)造函數(shù),訪問器,哈希/等于和toString方法的代碼!):

//Groovy @Immutable class City {String nameList<Temperature> temperatures } @Immutable class Temperature {Date dateBigDecimal reading }

我可以使用這些類在City對(duì)象列表中構(gòu)造一些隨機(jī)天氣數(shù)據(jù),例如:

private static final long ONE_DAY_MS = 1000*60*60*24; private static final Random RANDOM = new Random();public static List<City> prepareData(int numCities, int numTemps) {List<City> cities = new ArrayList<>();IntStream.range(0, numCities).forEach( i ->cities.add(new City(generateName(), generateTemperatures(numTemps))));return cities; }private static List<Temperature> generateTemperatures(int numTemps) {List<Temperature> temps = new ArrayList<>();for(int i = 0; i < numTemps; i++){long when = System.currentTimeMillis();when += ONE_DAY_MS*RANDOM.nextInt(365);Date d = new Date(when);Temperature t = new Temperature(d, new BigDecimal(RANDOM.nextDouble()));temps.add(t);}return temps; }private static String generateName() {char[] chars = new char[RANDOM.nextInt(5)+5];for(int i = 0; i < chars.length; i++){chars[i] = (char)(RANDOM.nextInt(26) + 65);}return new String(chars); }

第7行使用同樣來自Java 8的IntStream類來構(gòu)造第8-13行進(jìn)行迭代的范圍,從而將新的城市添加到第6行構(gòu)建的列表中。第22-30行在隨機(jī)的日期生成隨機(jī)溫度。

如果要計(jì)算所有城市在八月記錄的平均溫度,則可以編寫以下函數(shù)算法:

Instant start = Instant.now(); Double averageTemperature = cities.stream().flatMap(c ->c.getTemperatures().stream() ).filter(t -> {LocalDate ld = LocalDateTime.ofEpochSecond(t.getDate().getTime(), 0, ZoneOffset.UTC).toLocalDate();return ld.getMonth() == Month.AUGUST; }).map(t ->t.getReading() ).collect(Collectors.averagingDouble(TestFilterMapReducePerformance::toDouble) );Instant end = Instant.now(); System.out.println("functional calculated in " + Duration.between(start, end) + ": " + averageTemperature);

第1行用于啟動(dòng)時(shí)鐘。 然后,代碼在第2行從城市列表中創(chuàng)建一個(gè)流。然后,我使用flatMap方法(也在第2行)通過創(chuàng)建所有溫度的單個(gè)長(zhǎng)列表來flatMap ,并在第3行傳遞一個(gè)lambda,該lambda返回每個(gè)以流的形式列出溫度, flatMap方法可以將其附加在一起。 完成此操作后,我將在第4行使用filter方法丟棄所有非8月份以來的數(shù)據(jù)。 然后,我在第11行調(diào)用map方法,將每個(gè)Temperature對(duì)象轉(zhuǎn)換為一個(gè)
BigDecimal以及生成的流,我在第13行使用了collect方法以及一個(gè)計(jì)算平均值的收集器。 第15行需要一個(gè)輔助函數(shù)來將BigDecimal實(shí)例轉(zhuǎn)換為double ,因?yàn)榈?4行使用double而不是
BigDecimal :

/** method to convert to double */ public static Double toDouble(BigDecimal a) {return a.doubleValue(); }

上面清單中的數(shù)字運(yùn)算部分可以用命令式編寫,如下所示:

BigDecimal total = BigDecimal.ZERO; int count = 0; for(City c : cities){for(Temperature t : c.getTemperatures()){LocalDate ld = LocalDateTime.ofEpochSecond(t.getDate().getTime(), 0, ZoneOffset.UTC).toLocalDate();if(ld.getMonth() == Month.AUGUST){total = total.add(t.getReading());count++;}} } double averageTemperature = total.doubleValue() / count;

在命令式的命令式版本中,我以不同的順序進(jìn)行映射,過濾和歸約,但是結(jié)果是相同的。 您認(rèn)為哪種風(fēng)格(功能性或命令性)更快,并且提高了多少?

為了更準(zhǔn)確地讀取性能數(shù)據(jù),我需要多次運(yùn)行算法,以便熱點(diǎn)編譯器有時(shí)間進(jìn)行預(yù)熱。 以偽隨機(jī)順序多次運(yùn)行算法,我能夠測(cè)量出以功能樣式編寫的代碼平均大約需要0.93秒(使用一千個(gè)城市,每個(gè)城市的溫度為一千;使用英特爾筆記本電腦進(jìn)行計(jì)算i5 2.40GHz 64位處理器(4核)。 以命令式風(fēng)格編寫的代碼花費(fèi)了0.70秒,速度提高了25%。

所以我問自己,命令式代碼是否總是比功能代碼更快。 讓我們嘗試簡(jiǎn)單地計(jì)算8月記錄的溫度數(shù)。 功能代碼如下所示:

long count = cities.stream().flatMap(c ->c.getTemperatures().stream() ).filter(t -> {LocalDate ld = LocalDateTime.ofEpochSecond(t.getDate().getTime(), 0, ZoneOffset.UTC).toLocalDate();return ld.getMonth() == Month.AUGUST; }).count();

功能代碼涉及過濾,然后調(diào)用count方法。 另外,等效的命令性代碼可能如下所示:

long count = 0; for(City c : cities){for(Temperature t : c.getTemperatures()){LocalDate ld = LocalDateTime.ofEpochSecond(t.getDate().getTime(), 0, ZoneOffset.UTC).toLocalDate();if(ld.getMonth() == Month.AUGUST){count++;}} }

在此示例中,運(yùn)行的數(shù)據(jù)集與用于計(jì)算平均8月溫度的數(shù)據(jù)集不同,命令性代碼的平均時(shí)間為1.80秒,而功能代碼的平均時(shí)間略短。 因此,我們無法推斷出功能性代碼比命令性代碼更快或更慢。 這實(shí)際上取決于用例。 有趣的是,我們可以使用parallelStream()方法而不是stream()方法來使計(jì)算并行運(yùn)行。 在計(jì)算平均溫度的情況下,使用并行流意味著計(jì)算平均時(shí)間為0.46秒而不是0.93秒。 并行計(jì)算溫度需要0.90秒,而不是連續(xù)1.80秒。 嘗試編寫命令式代碼,該命令將數(shù)據(jù)分割,在內(nèi)核之間分布計(jì)算并將結(jié)果匯??總為一個(gè)平均溫度,這將需要大量工作! 正是這是想要向Java 8中添加函數(shù)式編程的主要原因之一。它如何工作? 拆分器和完成器用于在默認(rèn)的ForkJoinPool中分發(fā)工作,默認(rèn)情況下,該ForkJoinPool已優(yōu)化為使用與內(nèi)核一樣多的線程。 從理論上講,只使用與內(nèi)核一樣多的線程就意味著不會(huì)浪費(fèi)任何時(shí)間進(jìn)行上下文切換,但這取決于所完成的工作是否包含任何阻塞的I / O –這就是我在有關(guān)Scala的書中所討論的。

在使用Java EE應(yīng)用程序服務(wù)器時(shí),生成線程是一個(gè)有趣的主題,因?yàn)閲?yán)格來說,不允許您生成線程。 但是由于創(chuàng)建并行流不會(huì)產(chǎn)生任何線程,因此無需擔(dān)心! 在Java EE環(huán)境中,使用并行流完全合法!

您也可以使用地圖/減少算法來計(jì)算8月的溫度總數(shù):

int count = cities.stream().map(c ->c.getTemperatures().size() ).reduce(Integer::sum ).get();

第1行從列表中創(chuàng)建流,并使用第2行上的lambda將城市映射(轉(zhuǎn)換)為城市的溫度數(shù)。第3行通過使用總和將“溫度數(shù)”流減少為單個(gè)值第4行上的Integer類的method。由于流可能不包含任何元素, reduce方法返回Optional ,我們調(diào)用get方法獲取總數(shù)。 我們可以安全地這樣做,因?yàn)槲覀冎莱鞘兄邪瑪?shù)據(jù)。 如果您正在使用可能為空的數(shù)據(jù),則可以調(diào)用orElse(T)方法,該方法允許您指定默認(rèn)值(如果沒有可用結(jié)果時(shí)使用)。

就編寫功能代碼而言,還有另一種編寫此算法的方法:

long count = cities.stream().map(c ->c.getTemperatures().stream().count() ).reduce(Long::sum ).get();

使用上述方法,第2行上的lambda通過將溫度列表轉(zhuǎn)換為蒸汽并調(diào)用count方法來count溫度列表的大小。 就性能而言, 這是獲取列表大小的一種不好的方法。 在第一個(gè)算法中,每個(gè)城市有1000個(gè)城市,溫度有1000個(gè),總計(jì)數(shù)在160毫秒內(nèi)計(jì)算。 第二種算法將時(shí)間增加到280ms! 原因是ArrayList知道其大小,因?yàn)樗谔砑踊騽h除元素時(shí)對(duì)其進(jìn)行跟蹤。 另一方面,流首先通過將每個(gè)元素映射到值1L ,然后使用Long::sum方法減少1L的流來計(jì)算大小。 在較長(zhǎng)的數(shù)據(jù)列表上,與僅從列表中的屬性查找大小相比,這是相當(dāng)大的開銷。

將功能代碼所需的時(shí)間與以下命令代碼所需的時(shí)間進(jìn)行比較,可以看出該功能代碼的運(yùn)行速度慢了一倍–命令代碼計(jì)算的平均溫度總數(shù)僅為80ms。

long count = 0; for(City c : cities){count += c.getTemperatures().size(); }

通過使用并行流而不是順序流,再次通過簡(jiǎn)單地在上面三個(gè)清單中的第1行上調(diào)用parallelStream()方法而不是stream()方法,結(jié)果是該算法平均需要90毫秒,即比命令性代碼略長(zhǎng)。

計(jì)算溫度的第三種方法是使用收集器 。 在這里,我使用了一百萬個(gè)城市,每個(gè)城市只有兩個(gè)溫度。 該算法是:

int count = cities.stream().collect(Collectors.summingInt(c -> c.getTemperatures().size()) );

等效的命令性代碼為:

long count = 0; for(City c : cities){count += c.getTemperatures().size(); }

平均而言,功能性列表花費(fèi)了100毫秒,這與命令性列表花費(fèi)的時(shí)間相同。 另一方面,使用并行流將計(jì)算時(shí)間減少了一半,僅為50ms。

我問自己的下一個(gè)問題是,是否有可能確定需要處理多少數(shù)據(jù),因此使用并行流值得嗎? 拆分?jǐn)?shù)據(jù),將其提交給ForkJoinPool類的ExecutorService并在計(jì)算后將結(jié)果收集在一起并不是免費(fèi)的-它會(huì)降低性能。 當(dāng)可以并行處理數(shù)據(jù)時(shí),肯定可以計(jì)算出來,通常的答案是,這取決于用例。

在此實(shí)驗(yàn)中,我計(jì)算了一個(gè)數(shù)字列表的平均值。 我NUM_RUNS地重復(fù)工作( NUM_RUNS次),以獲得可測(cè)量的值,因?yàn)橛?jì)算三個(gè)數(shù)字的平均值太快了,無法可靠地進(jìn)行測(cè)量。 我將列表的大小從3個(gè)數(shù)字更改為3百萬個(gè),以確定列表需要多大才能使用并行流計(jì)算平均值才能得到回報(bào)。

使用的算法是:

double avg = -1.0; for(int i = 0; i < NUM_RUNS; i++){avg = numbers.stream().collect(Collectors.averagingInt(n->n)); }

只是為了好玩,這是另一種計(jì)算方法:

double avg = -1.0; for(int i = 0; i < NUM_RUNS; i++){avg = numbers.stream().mapToInt(n->n).average().getAsDouble(); }

結(jié)果如下。 僅使用列表中的三個(gè)數(shù)字,我就運(yùn)行了100,000次計(jì)算。 多次運(yùn)行測(cè)試表明,平均而言,串行計(jì)算花費(fèi)了20ms,而并行計(jì)算則花費(fèi)了370ms。 因此,在這種情況下,使用少量數(shù)據(jù)樣本,不值得使用并行流。

另一方面,列表中有300萬個(gè)數(shù)字,串行計(jì)算花費(fèi)了1.58秒,而并行計(jì)算僅花費(fèi)了0.93秒。 因此,在這種情況下,對(duì)于大量數(shù)據(jù)樣本,值得使用并行流。 請(qǐng)注意,隨著數(shù)據(jù)集大小的增加,運(yùn)行次數(shù)減少了,因此我不必等待很長(zhǎng)的時(shí)間(我不喝咖啡!)。

列表中的#個(gè)數(shù)字 平均 時(shí)間序列 平均 時(shí)間平行 NUM_RUNS
3 0.02秒 0.37秒 100,000
30 0.02秒 0.46秒 100,000
300 0.07秒 0.53秒 100,000
3,000 1.98秒 2.76秒 100,000
30,000 0.67秒 1.90秒 10,000
30萬 1.71秒 1.98秒 1,000
3,000,000 1.58秒 0.93秒 100

這是否意味著并行流僅對(duì)大型數(shù)據(jù)集有用? 沒有! 這完全取決于手頭的計(jì)算強(qiáng)度。 以下無效的算法只是加熱CPU,但演示了復(fù)雜的計(jì)算。

private void doIntensiveWork() {double a = Math.PI;for(int i = 0; i < 100; i++){for(int j = 0; j < 1000; j++){for(int k = 0; k < 100; k++){a = Math.sqrt(a+1);a *= a;}}}System.out.println(a); }

我們可以使用以下清單生成兩個(gè)可運(yùn)行對(duì)象的列表,它們將完成這項(xiàng)繁重的工作:

private List<Runnable> generateRunnables() {Runnable r = () -> {doIntensiveWork();};return Arrays.asList(r, r); }

最后,我們可以測(cè)量運(yùn)行兩個(gè)可運(yùn)行對(duì)象的時(shí)間,例如,并行運(yùn)行(請(qǐng)參見第3行對(duì)parallelStream()方法的調(diào)用):

List<Runnable> runnables = generateRunnables(); Instant start = Instant.now(); runnables.parallelStream().forEach(r -> r.run()); Instant end = Instant.now(); System.out.println("functional parallel calculated in " + Duration.between(start, end));

使用并行流平均要花費(fèi)260毫秒來完成兩次密集的工作。 使用串行流,平均花費(fèi)460毫秒,即幾乎翻倍。

從所有這些實(shí)驗(yàn)中我們可以得出什么結(jié)論? 好吧,不可能最終說出功能代碼比命令性代碼慢,也不能說使用并行流比使用串行流快。 我們可以得出的結(jié)論是,程序員在編寫對(duì)性能至關(guān)重要的代碼時(shí),需要試驗(yàn)不同的解決方案并測(cè)量編碼風(fēng)格對(duì)性能的影響。 但是說實(shí)話,這不是什么新鮮事! 對(duì)我而言,閱讀本文后您應(yīng)該帶走的是,總是有很多方法可以編寫算法,并且選擇正確的方法很重要。 知道哪種方法是對(duì)的,這是經(jīng)驗(yàn)的結(jié)合,但更重要的是,嘗試使用代碼并嘗試不同的解決方案。 最后,盡管如此,還是不??要過早地進(jìn)行優(yōu)化!

翻譯自: https://www.javacodegeeks.com/2014/05/the-effects-of-programming-with-java-8-streams-on-algorithm-performance.html

java8 streams

總結(jié)

以上是生活随笔為你收集整理的java8 streams_使用Java 8 Streams进行编程对算法性能的影响的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。