寫給大忙人看的Java SE 8 讀書心得筆記與習題-第二章 Steam API
Collection API本來就很常用,多了Stream 更是讓人不用管實作的完成很多演算工作。
不過實際用起來還是要花非常多時間,用法實在太多了,配上lambda更是眼花撩亂
第二章要點
- 你可以從集合 陣列 Generator或是iterator中創建Stream
- 使用filter來選擇元素,使用map來改變元素
- 要從Stream中獲得結果,請使用reduction操作符,例如count、max、min
、findFirst 或findAny,其中一些方法會獲得一個Optional值 - 你可以收集(collect) 集合 陣列 字符串或是map中的Stream結果
- Collectors 類的groupBy和partitionBy允許你對Stream中的內容分組,並獲取每個組的結果
- Java 對 基本型別 int long和double提供了專門的Stream
練習題與自己版本的解答
- 編寫一個2.1節中for循環的並型版本,獲取處理器的數量,創造出多個獨立的執行緒(線程),每個都只處理列表的一個片段,然後將他們各自的結果彙總起來(我們不希望這些執行緒都更新一個計數器,為什麼?)
public class Exercise2_1 { public static void main(String[] args) throws IOException, InterruptedException, ExecutionException { String contents = new String( Files.readAllBytes(Paths.get("src/exercises/alice.txt")), StandardCharsets.UTF_8); // 邏輯直接使用範例程式碼 List
words = Arrays.asList(contents.split("[\\P{L}]+")); long count = 0; for (String w : words) { if (w.length() > 12) count++; } System.out.println(count); count = words.stream().filter(w -> w.length() > 12).count(); System.out.println(count); count = words.parallelStream().filter(w -> w.length() > 12).count(); System.out.println(count); // 以下是改寫 ExecutorService executorService = Executors.newCachedThreadPool(); AtomicInteger ai=new AtomicInteger(); Map map=new HashMap<>(); map.put("count", 0); List > results = new ArrayList<>(); for (String w : words) { results.add(executorService.submit(new Callable () { @Override public Integer call() throws Exception { if (w.length() > 12) { ai.incrementAndGet(); // 合法語法 Integer mCount=map.get("count"); map.put("count", mCount+1); // 不合法語法 // count++; return 1; } else { return 0; } } })); } int count2 = 0; for (Future result : results) { count2 += result.get(); } executorService.shutdown(); // 33 System.out.println("ai " + ai); // 結果有可能不是 33 System.out.println("map count " + map.get("count")); // 33 System.out.println("count2 " + count2); } } - 請想辦法驗證一下,對於獲得前五個最長單詞的代碼,一旦找到第五個最長的單磁,就不會再調用filter方法了。一個簡單的方式記錄每次的方法調用
public class Exercise2_2 { public static void main(String[] args) throws IOException, InterruptedException, ExecutionException { String contents = new String( Files.readAllBytes(Paths.get("src/exercises/alice.txt")), StandardCharsets.UTF_8); // 邏輯直接使用範例程式碼 List
words = Arrays.asList(contents.split("[\\P{L}]+")); // words.size 30420 System.out.println("words.size "+words.size()); AtomicInteger counter = new AtomicInteger(); AtomicInteger totalCounter = new AtomicInteger(); Stream limit = words.stream().filter((String word) -> { int incrementAndGet =totalCounter.incrementAndGet(); if (word.length() > 12) { counter.incrementAndGet(); System.out.println("於"+incrementAndGet+"次符合條件"); return true; } else return false; }).limit(5); // 確認 stream的延遲執行特性 // totalCounter 0 // counter 0 System.out.println("totalCounter "+ totalCounter); System.out.println("counter "+counter); totalCounter.set(0); counter.set(0); limit.forEach((x)->{}); // totalCounter 4526 // counter 5 System.out.println("totalCounter "+ totalCounter); System.out.println("counter "+counter); totalCounter.set(0); counter.set(0); words.stream().filter((String word) -> { int incrementAndGet = totalCounter.incrementAndGet(); if (word.length() > 12) { counter.incrementAndGet(); System.out.println("於"+incrementAndGet+"次符合條件"); return true; } else return false; }).limit(5).count(); // totalCounter 4526 // counter 5 System.out.println("totalCounter "+ totalCounter); System.out.println("counter "+counter); } } - 要統計長單詞的數量,使用parallelStream與與使用stream有什麼區別?請具體測試一下。
你可以用調用方法之前和之後調用System.nanoTime,並印出他們之間的區別。
如果你已就快的計算機,可以試著處裡一個較大的文檔(例如 戰爭與和平的英文原著)
parallelStream超快,只要你可以保證執行緒Safe。 - 假設你有一個陣列(數組) int[] values={1,4,9,16}。那麼Steam.of(values)的結果是什麼?你如何獲得一個int類型的流
提醒你java8有專門設計不讓你再多做boxing的api,long 跟Double也有int[] values={1,4,9,16}; Stream
of = Stream.of(values); IntStream of2 = IntStream.of(values); - 太多數學符號了,以圖替代.......
LCG是什麼,那群數學符號又是什麼,先等我搞定先...
查詢網路做法跟API說明才了解iterate這個無限Streampublic static Stream
randomStream(long a, long c, long m, long seed) { return Stream.iterate(seed, l -> (a * l + c) % m); } public static void main(String[] args) { randomStream(25214903917l, 11, (long) Math.pow(2, 48), System.currentTimeMillis()).limit(3) .forEach(System.out::println); } } - 第2.3節的characterStream方法不是很好用,他需要先填充一個陣列列表,然後再轉變成一個流。試著邊寫一行基於流(stream)的代碼。一個辦法是構造一個從0開始到s.length()-1的整數流,然後使用s::charAt方法引用來映射他
public static Stream
characterByIntStream(String s) { return IntStream.rangeClosed(0, s.length()-1).mapToObj(s::charAt); // 看你喜歡-1做結尾還是不減... // return IntStream.range(0, s.length()).mapToObj(s::charAt); } - 假設你的老闆讓你邊寫一個方法 public static<T>boolean isFinite(Stream<T> second )
,為什麼這不是一個好主意? 不管怎樣 先試著寫一寫
投降寫一半行不行... 第五題才剛來個無限Stream - 編寫一個方法public static<T> Stream<T> zip(Stream<T> first,Stream<T> second )
,依次掉換first跟 second中元素的位置,直到其中一個流結束
看不懂題目...
- 將一個Steam<ArrayList<T>>中的全部元素連結成一個ArrayList<T>。試著用三種不同的聚合(reduce)方式來實現
public <T> ArrayList<T> reduce1(Stream<ArrayList<T>> stream) { // 第一個參數給預設值 return stream.reduce(new ArrayList<T>(), (ArrayList<T> t1, ArrayList<T> t2) -> { t1.addAll(t2); return t1; }); } public <T> Optional<ArrayList<T>> reduce2Optional(Stream<ArrayList<T>> stream) { // 不給預設值參數,IDE提示的作法 return stream.reduce((ArrayList<T> t1, ArrayList<T> t2) -> { t1.addAll(t2); return t1; }); } public <T> ArrayList<T> reduce2(Stream<ArrayList<T>> stream) { // // 不給預設值參數,改由orElse給預設值 return stream.reduce((ArrayList<T> t1, ArrayList<T> t2) -> { t1.addAll(t2); return t1; }).orElse(new ArrayList<T>()); } public <T> ArrayList<T> reduce3(Stream<ArrayList<T>> stream) { // <U> U reduce(U identity, // BiFunction<U, ? super T, U> accumulator, // BinaryOperator<U> combiner); // 書上跟網路上的範例都是當你的 accumulator 中 兩個參數型別不同的時候, // 需要用這種方式讓combiner幫你整合 accumulator 的所有結果 // 不是很直覺 return stream.reduce(new ArrayList<>(), (result, added) -> { result.addAll(added); return result; }, (result, result2) -> { result.addAll(result2); return result; }); }
- 編寫一個可以用來計算Steam<Double>平均值的聚合方法,為什麼不能直接計算出總合再除以count
Stream操作過就沒用了,除非是該方法有返回新的StreamStream
of = Stream.of(1.1, 2.2, 3.3, 4.4, 5.5,6.6); // Double reduce = of.reduce(new Double(0), Double::sum) ; // stream has already been operated upon or closed // Double count = (double) of.count(); // 其中一種方法,拿書上的現學現用。一般比較常先MAP再操作 System.out.println(of.collect(Collectors.summarizingDouble(f -> f )).getAverage()); - 我們應該可以將Stream(流)的結果並發收集到一個ArrayList中,而不是將多個ArrayList合併起來,由於對集合不相交部份的開發操作是執行緒安全的,所以我們假設這個Array的初始大小即為流的大小,如何能做到這一點
看不太懂...
沒說ArrayList元素要照來源順序的話,會有什麼問題嗎?
又不會少塞元素到ArrayList... - 如2.13節所示,通過更新一個AtomicInteger 數組來計算一個並行Stream<String>宏的所有短單詞。利用原子操作方法getAndIncrement來安全地增加每個計數器的值。在
2.1解答證明執行緒安全性問題已經不小心用過了 - 重複上一個練習,這次使用collect方法、Collectors.groupBy方法和Collectors.counting來濾出短單詞
public class Exercise2_13 { public static void main(String[] args) throws IOException, InterruptedException, ExecutionException { String contents = new String( Files.readAllBytes(Paths.get("src/exercises/alice.txt")), StandardCharsets.UTF_8); // 邏輯直接使用範例程式碼 List
words = Arrays.asList(contents.split("[\\P{L}]+")); Map collect = words.parallelStream() .filter(w -> w.length() < 10) .collect(Collectors.groupingBy((String t) -> t.length(), Collectors.counting())); collect.forEach((k,v)->{ System.out.println("length :"+k+" ,count :"+v); }); } }
留言
張貼留言