Stream閃亮登場

funnyZpC發表於2019-02-16

Stream閃亮登場

一. Stream(流)是什麼,幹什麼

  Stream是一類用於替代對集合操作的工具類+Lambda式程式設計,他可以替代現有的遍歷、過濾、求和、求最值、排序、轉換等

二. Stream操作方式

  • 並行方式parallelStream
  • 順序方式Stream

三. Stream優勢

  • Lambda 可有效減少冗餘程式碼,減少開發工作量
  • 內建對集合List、Map的多種操作方式,含基本資料型別處理
  • 並行Stream有效率優勢(內建多執行緒)

四. Stream(流)的基本使用

  • 遍歷forEach
    @Test
    public void stream() {
        //操作List
        List<Map<String, String>> mapList = new ArrayList() {
            {
                Map<String, String> m = new HashMap();
                m.put("a", "1");
                Map<String, String> m2 = new HashMap();
                m2.put("b", "2");
                add(m);
                add(m2);
            }
        };
        mapList.stream().forEach(item-> System.out.println(item));

        //操作Map
        Map<String,Object> mp = new HashMap(){
            {
                put("a","1");
                put("b","2");
                put("c","3");
                put("d","4");
            }
        };
        mp.keySet().stream().forEachOrdered(item-> System.out.println(mp.get(item)));
    }
  • 過濾filter
          List<Integer> mapList = new ArrayList() {
          {
              add(1);
              add(10);
              add(12);
              add(33);
              add(99);
          }
      };
     //mapList.stream().forEach(item-> System.out.println(item));
      mapList = mapList.stream().filter(item->{
          return item>30;
      }).collect(Collectors.toList());
      System.out.println(mapList);
  • 轉換map和極值

        @Test
      public void trans(){
          List<Person> ps = new ArrayList<Person>(){
              {
                  Person p1 = new Person();
                  p1.setAge(11);
                  p1.setName("張強");
    
                  Person p2 = new Person();
                  p2.setAge(17);
                  p2.setName("李思");
    
                  Person p3 = new Person();
                  p3.setAge(20);
                  p3.setName("John");
    
                  add(p1);
                  add(p2);
                  add(p3);
              }
          };
          //取出所有age欄位為一個List
          List<Integer> sumAge = ps.stream().map(Person::getAge).collect(Collectors.toList());
          System.out.println(sumAge);
          //取出age最大的那
          Integer maxAge =sumAge.stream().max(Integer::compare).get();
          System.out.println(maxAge);
      }
    
      class Person{
    
      private String name;
      private Integer age;
    
      public String getName() {
          return name;
      }
    
      public void setName(String name) {
          this.name = name;
      }
    
      public Integer getAge() {
          return age;
      }
    
      public void setAge(Integer age) {
          this.age = age;
      }
     }

    五. Stream(流)的效率

  • 模擬非耗時簡單業務邏輯
    class Person{
    private String name;
    private int age;
    private Date joinDate;
    private String label;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Date getJoinDate() {
        return joinDate;
    }

    public void setJoinDate(Date joinDate) {
        this.joinDate = joinDate;
    }

    public String getLabel() {
        return label;
    }

    public void setLabel(String label) {
        this.label = label;
    }
public class DataLoopTest {
    private static final Logger LOG= LoggerFactory.getLogger(DataLoopTest.class);

    private static final List<Person> persons = new ArrayList<>();
    static {
        for(int i=0;i<=1000000;i++){
            Person p = new Person();
            p.setAge(i);
            p.setName("zhangSan");
            p.setJoinDate(new Date());
            persons.add(p);
        }
    }

    /**
     * for 迴圈耗時 ===> 1.988
     * for 迴圈耗時 ===> 2.198
     * for 迴圈耗時 ===> 1.978
     *
     */
    @Test
    public void forTest(){
        Instant date_start = Instant.now();
        int personSize = persons.size();
        for(int i=0;i<personSize;i++){
            persons.get(i).setLabel(persons.get(i).getName().concat("-"+persons.get(i).getAge()).concat("-"+persons.get(i).getJoinDate().getTime()));
        }
        Instant date_end = Instant.now();
        LOG.info("for 迴圈耗時 ===> {}", Duration.between(date_start,date_end).toMillis()/1000.0);
    }

    /**
     *  forEach 迴圈耗時 ===> 1.607
     *  forEach 迴圈耗時 ===> 2.242
     *  forEach 迴圈耗時 ===> 1.875
     */
    @Test
    public void forEach(){
        Instant date_start = Instant.now();
        for(Person p:persons){
            p.setLabel(p.getName().concat("-"+p.getAge()).concat("-"+p.getJoinDate().getTime()));
        }
        Instant date_end = Instant.now();
        LOG.info("forEach 迴圈耗時 ===> {}", Duration.between(date_start,date_end).toMillis()/1000.0);
    }

    /**
     *  streamForeach 迴圈耗時 ===> 1.972
     *  streamForeach 迴圈耗時 ===> 1.969
     *  streamForeach 迴圈耗時 ===> 2.125
     */
    @Test
    public void streamForeach(){
        Instant date_start = Instant.now();
        persons.stream().forEach(p->p.setLabel(p.getName().concat("-"+p.getAge()).concat("-"+p.getJoinDate().getTime())));
        Instant date_end = Instant.now();
        LOG.info("streamForeach 迴圈耗時 ===> {}", Duration.between(date_start,date_end).toMillis()/1000.0);
    }

    /**
     *  parallelStreamForeach 迴圈耗時 ===> 1.897
     *  parallelStreamForeach 迴圈耗時 ===> 1.942
     *  parallelStreamForeach 迴圈耗時 ===> 1.642
     */
    @Test
    public void parallelStreamForeach(){
        Instant date_start = Instant.now();
        persons.parallelStream().forEach(p->p.setLabel(p.getName().concat("-"+p.getAge()).concat("-"+p.getJoinDate().getTime())));
        Instant date_end = Instant.now();
        LOG.info("parallelStreamForeach 迴圈耗時 ===> {}", Duration.between(date_start,date_end).toMillis()/1000.0);
    }

}
  • 模擬耗時簡單業務邏輯
  public class DataLoopBlockTest {
    private static final Logger LOG= LoggerFactory.getLogger(DataLoopTest.class);

    private static final List<Person> persons = new ArrayList<>();
    static {
        for(int i=0;i<=100000;i++){
            Person p = new Person();
            p.setAge(i);
            p.setName("zhangSan");
            p.setJoinDate(new Date());
            persons.add(p);
        }
    }

    /**
     * for 迴圈耗時 ===> 101.385
     * for 迴圈耗時 ===> 102.161
     * for 迴圈耗時 ===> 101.472
     *
     */
    @Test
    public void forTest(){
        Instant date_start = Instant.now();
        int personSize = persons.size();
        for(int i=0;i<personSize;i++){
            try {
                Thread.sleep(1);
                persons.get(i).setLabel(persons.get(i).getName().concat("-"+persons.get(i).getAge()).concat("-"+persons.get(i).getJoinDate().getTime()));
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        Instant date_end = Instant.now();
        LOG.info("for 迴圈耗時 ===> {}", Duration.between(date_start,date_end).toMillis()/1000.0);
    }

    /**
     *  forEach 迴圈耗時 ===> 101.027
     *  forEach 迴圈耗時 ===> 102.488
     *  forEach 迴圈耗時 ===> 101.608
     */
    @Test
    public void forEach(){
        Instant date_start = Instant.now();
        for(Person p:persons){
            try {
                Thread.sleep(1);
                p.setLabel(p.getName().concat("-"+p.getAge()).concat("-"+p.getJoinDate().getTime()));
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        Instant date_end = Instant.now();
        LOG.info("forEach 迴圈耗時 ===> {}", Duration.between(date_start,date_end).toMillis()/1000.0);
    }

    /**
     *  streamForeach 迴圈耗時 ===> 103.246
     *  streamForeach 迴圈耗時 ===> 101.128
     *  streamForeach 迴圈耗時 ===> 102.615
     */
    @Test
    public void streamForeach(){
        Instant date_start = Instant.now();
        //persons.stream().forEach(p->p.setLabel(p.getName().concat("-"+p.getAge()).concat("-"+p.getJoinDate().getTime())));
        persons.stream().forEach(p->{
            try {
                Thread.sleep(1);
                p.setLabel(p.getName().concat("-"+p.getAge()).concat("-"+p.getJoinDate().getTime()));
            }catch (Exception e){
                e.printStackTrace();
            }
        });
        Instant date_end = Instant.now();
        LOG.info("streamForeach 迴圈耗時 ===> {}", Duration.between(date_start,date_end).toMillis()/1000.0);
    }

    /**
     *  parallelStreamForeach 迴圈耗時 ===> 51.391
     *  parallelStreamForeach 迴圈耗時 ===> 53.509
     *   parallelStreamForeach 迴圈耗時 ===> 50.831
     */
    @Test
    public void parallelStreamForeach(){
        Instant date_start = Instant.now();
        //persons.parallelStream().forEach(p->p.setLabel(p.getName().concat("-"+p.getAge()).concat("-"+p.getJoinDate().getTime())));
        persons.parallelStream().forEach(p->{
            try {
                Thread.sleep(1);
                p.setLabel(p.getName().concat("-"+p.getAge()).concat("-"+p.getJoinDate().getTime()));
            }catch (Exception e){
                e.printStackTrace();
            }
        });
        Instant date_end = Instant.now();
        LOG.info("parallelStreamForeach 迴圈耗時 ===> {}", Duration.between(date_start,date_end).toMillis()/1000.0);
        //LOG.info("\r\n===> {}",JSON.toJSONString(persons.get(10000)));
    }
}
   可以看到在<s>百萬資料</s>下做簡單資料迴圈處理,對於普通for(for\foreach)迴圈或stream(並行、非並行)下,幾者的效率差異並不明顯,
   注意: 在百萬資料下,普通for、foreach迴圈處理可能比stream的方式快許多,對於這點效率的損耗,其實lambda表示式對程式碼的簡化更大!
   另外,在並行流的迴圈下速度提升了一倍之多,當單個迴圈耗時較多時,會拉大與前幾者的迴圈效率
    (以上測試僅對於迴圈而言,其他型別業務處理,比如排序、求和、最大值等未做測試,個人猜測與以上測試結果相似)

六. Stream(流)注意項

  • 並行stream不是執行緒安全的,當對循壞外部統一物件進行讀寫時候會造成意想不到的錯誤,這需要留意
  • 因stream總是惰性的,原物件是不可以被修改的,在集合處理完成後需要將處理結果放入一個新的集合容器內
  • 普通迴圈與stream(非並行)迴圈,在處理處理資料量比較大的時候效率是一致的,推薦使用stream的形式
  • 對於List刪除操作,目前只提供了removeIf方法來實現,並不能使用並行方式
  • 對於lambda表示式的寫法
  • 當表示式內只有一個返回boolean型別的語句時候語句是可以簡寫的,例如:
 persons.parallelStream().forEach(p->p.setLabel(p.getName().concat("-"+p.getAge()).concat("-"+p.getJoinDate().getTime())));
  • 當表示式內會有一些複雜處理邏輯時需要加上大括號,這與初始化List引數方式大致一致
        persons.parallelStream().forEach(p->{
            try {
                Thread.sleep(1);
                p.setLabel(p.getName().concat("-"+p.getAge()).concat("-"+p.getJoinDate().getTime()));
            }catch (Exception e){
                e.printStackTrace();
            }
        });

七. stream&Lambda表示式常用api方法

  • 流到流之間的轉換類
    • filter(過濾)
    • map(對映轉換)
    • mapTo[Int|Long|Double] (到基本型別流的轉換)
    • flatMap(流展開合併)
    • flatMapTo[Int|Long|Double]
    • sorted(排序)
    • distinct(不重複值)
    • peek(執行某種操作,流不變,可用於除錯)
    • limit(限制到指定元素數量)
    • skip(跳過若干元素)
  • 流到終值的轉換類
    • toArray(轉為陣列)
    • reduce(推導結果)
    • collect(聚合結果)
    • min(最小值)
    • max(最大值)
    • count (元素個數)
    • anyMatch (任一匹配)
    • allMatch(所有都匹配)
    • noneMatch(一個都不匹配)
    • findFirst(選擇首元素)
    • findAny(任選一元素)
  • 直接遍歷類
    • forEach (不保證順序遍歷,比如並行流)
    • forEachOrdered(順序遍歷)
  • 構造流類
    • empty (構造空流)
    • of (單個元素的流及多元素順序流)
    • iterate (無限長度的有序順序流)
    • generate (將資料提供器轉換成無限非有序的順序流)
    • concat (流的連線)
    • Builder (用於構造流的Builder物件)

相關文章