寫給大忙人看的Java SE 8 讀書心得筆記與習題-第二章 Steam API

Collection API本來就很常用,多了Stream 更是讓人不用管實作的完成很多演算工作。
不過實際用起來還是要花非常多時間,用法實在太多了,配上lambda更是眼花撩亂


第二章要點

  1. 你可以從集合 陣列 Generator或是iterator中創建Stream
  2. 使用filter來選擇元素,使用map來改變元素
  3. 要從Stream中獲得結果,請使用reduction操作符,例如count、max、min
    、findFirst 或findAny,其中一些方法會獲得一個Optional值
  4. 你可以收集(collect) 集合 陣列 字符串或是map中的Stream結果
  5. Collectors 類的groupBy和partitionBy允許你對Stream中的內容分組,並獲取每個組的結果
  6. Java 對 基本型別 int long和double提供了專門的Stream

練習題與自己版本的解答


  1. 編寫一個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);
    
            
        }
    }
    
    
  2. 請想辦法驗證一下,對於獲得前五個最長單詞的代碼,一旦找到第五個最長的單磁,就不會再調用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);                        
        }
        
    }
    
    
  3. 要統計長單詞的數量,使用parallelStream與與使用stream有什麼區別?請具體測試一下。
    你可以用調用方法之前和之後調用System.nanoTime,並印出他們之間的區別。
    如果你已就快的計算機,可以試著處裡一個較大的文檔(例如 戰爭與和平的英文原著)

    parallelStream超快,只要你可以保證執行緒Safe。

  4. 假設你有一個陣列(數組) 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);
    
    
  5. 太多數學符號了,以圖替代.......

    LCG是什麼,那群數學符號又是什麼,先等我搞定先...
    查詢網路做法跟API說明才了解iterate這個無限Stream
    
        public 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);
        }
    
    }
    
  6. 第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);                   
        }
    
    
  7. 假設你的老闆讓你邊寫一個方法 public static<T>boolean isFinite(Stream<T> second )
    ,為什麼這不是一個好主意? 不管怎樣 先試著寫一寫


    投降寫一半行不行... 第五題才剛來個無限Stream

  8. 編寫一個方法public static<T> Stream<T>  zip(Stream<T> first,Stream<T> second )
    ,依次掉換first跟 second中元素的位置,直到其中一個流結束

    看不懂題目...
     
  9. 將一個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;
                    });
        }
        
    

  10. 編寫一個可以用來計算Steam<Double>平均值的聚合方法,為什麼不能直接計算出總合再除以count

    Stream操作過就沒用了,除非是該方法有返回新的Stream
    
        Stream 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());
    
    
  11. 我們應該可以將Stream(流)的結果並發收集到一個ArrayList中,而不是將多個ArrayList合併起來,由於對集合不相交部份的開發操作是執行緒安全的,所以我們假設這個Array的初始大小即為流的大小,如何能做到這一點

    看不太懂...
    沒說ArrayList元素要照來源順序的話,會有什麼問題嗎?
     又不會少塞元素到ArrayList...

  12. 如2.13節所示,通過更新一個AtomicInteger 數組來計算一個並行Stream<String>宏的所有短單詞。利用原子操作方法getAndIncrement來安全地增加每個計數器的值。

    2.1解答證明執行緒安全性問題已經不小心用過了

  13. 重複上一個練習,這次使用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);          
            });
            
        }
    }
    
    

留言

熱門文章

汐科定便當記錄(一)

汐科定便當記錄 (完)

ireport換行