【Java基礎(chǔ)概念】Stream流淺析

概述

Java8的兩個重大改變,一個是Lambdab表達(dá)式,另外一個就是Stream API表達(dá)式。Stream是Java8中處理集合的關(guān)鍵抽象概念,它可以對集合進(jìn)行復(fù)雜的查找、過濾、篩選等操作,可以極大的提高Java程序員的生產(chǎn)力,讓程序員寫出高效、干凈、簡潔的代碼。

這種風(fēng)格將要處理的元素集合看作一種流, 流在管道中傳輸, 并且可以在管道的節(jié)點上進(jìn)行處理, 比如篩選, 排序,聚合等。

元素流在管道中經(jīng)過中間操作的處理,最后由最終操作得到前面處理的結(jié)果。

Stream 創(chuàng)建

創(chuàng)建流的方法主要有以下幾種:

  • 通過集合創(chuàng)建流
  • 通過數(shù)組創(chuàng)建流
  • 創(chuàng)建空的流
  • 創(chuàng)建無限流
  • 創(chuàng)建規(guī)律無限流

以上這五種創(chuàng)建流的方法中使用最多的還是“通過集合創(chuàng)建流”,下面一一舉例,來熟悉下怎么通過這五種方式創(chuàng)建流。

通過集合創(chuàng)建流

    public void testCollectionStream(){
        List<String> list = Arrays.asList("111","xxxx","ccc","222");
        //創(chuàng)建普通串行流
        Stream<String> stringStream = list.stream();
        //創(chuàng)建并行流
        Stream<String> parallelStream = list.parallelStream();

    }

通過數(shù)組創(chuàng)建流

public void testArrayStream(){
    //通過Arrays.stream()創(chuàng)建
    int[] arrInt = new int[]{1,2,3,4,5};
    IntStream intStream = Arrays.stream(arrInt);

    Student[] students = new Student[]{new Student("xcy",18,false),new Student("lvp",3,false)};
    Stream<Student> studentStream = Arrays.stream(students);
    //通過Stream.of()創(chuàng)建
    Stream<Integer> IntegerStream = Stream.of(1,32,3,2312,332);

    Stream<int[]> streamInt = Stream.of(arrInt,arrInt);
    streamInt.forEach(System.out::println);


    }

創(chuàng)建空的流

public void testEmptyStream(){
        
        Stream<Integer> stream = Stream.empty();
    }

創(chuàng)建無限流

public void testGenerateStream(){
        //通過Stream.generate()創(chuàng)建無限流,通過limit來限制輸出個數(shù)。
        Stream.generate(()->"random_"+new Random().nextInt()).limit(100).forEach(System.out::println);
        Stream.generate(()->new Student("xcy",new Random(50).nextInt(),false)).limit(10).forEach(System.out::println);

    }

創(chuàng)建規(guī)律無限流

public void testGenerateStream1(){
        Stream.iterate(0,x->x+1).limit(10).forEach(System.out::println);

    }

Stream的操作

Stream上的所有操作都分為兩大類 “中間操作”“結(jié)束操作” 。中間操作只是一種標(biāo)記,只有結(jié)束操作才會觸發(fā)最后的計算。中間操作又可以分為 “無狀態(tài)操作”(Stateless)“有狀態(tài)操作”(Stateful)
無狀態(tài)中間操作;是指元素的處理不受前面元素的影響,而有狀態(tài)中間操作,必須等到所有元素處理之后才知道最終的結(jié)果。例如,排序操作就是有狀態(tài)操作,只有當(dāng)所有元素都讀取完了,才知道最終排序結(jié)果;結(jié)束操作又分為“短路操作”和
“非短路操作”。短路操作是指不用處理完所有數(shù)據(jù),就可以中斷的操作,例如anyMatch();而非短路操作,則需要操作完所有數(shù)據(jù)才能得出最終結(jié)果,例如forEach();下面是目前整理出的一個大概分類表:

操作類型 Stream API
中間操作-無狀態(tài) unordered();filter();map();mapToInt();mapToLong();mapToDouble();
flatMap();flatMapToInt();flatMapToDouble();flatMapToLong();peek();
中間操作-有狀態(tài) distinct();sorted();limit();skip();
結(jié)束操作-非短路操作 forEach();forEachOrdered();toArray();reduce();collect();max();min();
count();sum();
結(jié)束操作-短路操作 anyMatch();allMatch();noneMatch();findFirst();findAny();

下面通過一些例子來熟悉一下常用的一些方法;

map()

map()是在Stream()流中使用較為頻繁的一個方法,其作用是將一種類型的流轉(zhuǎn)換為另外一種類型的流。

//將Stream<Student>轉(zhuǎn)換為Stream<String>
@Test
public void testMap(){
    List<Student> students = Arrays.asList(new Student("xcy",18,false),new Student("lvp",3,false));
    List<String> names = students.stream().map(Student::getName).collect(Collectors.toList());
    System.out.println(names);
}

filter()

filter,顧名思義,用處是過濾流,將不需要的部分過濾掉。

@Test
public void testFilter(){
    List<Integer> integers = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
    integers = integers.stream().filter(e-> e<5).collect(Collectors.toList());
    //result:[1, 2, 3, 4]
    System.out.println(integers);
}

sorted()

sorted方法用于排序,

@Test
public void testSorted(){
    String[] arr = {"a","abc","bcd","abcd","bc"};
    //按照字符串長度排序
    System.out.println("字符串按照長度排序===================");
    Arrays.stream(arr).sorted((x,y)->{
        if(x.length()>y.length()){
            return 1;
        }else if (x.length() == y.length()){
            return 0;
        }else{
            return -1;
        }
    }).forEach(System.out::println);
    System.out.println("字符串按照自然順序排序,即首字母順序===================");
    //按照自然順序排序
    Arrays.stream(arr).sorted().forEach(System.out::println);
    System.out.println("字符處按自然順序倒序===================");
    //按照自然順序倒序
    Arrays.stream(arr).sorted(Comparator.reverseOrder()).forEach(System.out::println);

    //引用類型集合按照某個屬性正序
    System.out.println("將對象按照某個屬性順序排序===================");
    List<Student> students = Arrays.asList(new Student("小紅",18),new Student("小藍(lán)",3),new Student("小綠",10));
    students.stream().sorted(Comparator.comparing(Student::getAge)).forEach(System.out::println);
    //引用類型集合按照某個屬性倒序
    System.out.println("將對象按照某個屬性倒序排序===================");
    students.stream().sorted(Comparator.comparing(Student::getAge).reversed()).forEach(System.out::println);

}
distinct()

類似于sql 中的distinct,都是去重的作用。該方法通過equals()比較兩個對象是否相等。所以對實體對象去重的時候,需要提前定義好equals()方法。
例如:[student(name="xcy",age = 18);student(name="xcy",age=18)],如果沒有定義equals方法,系統(tǒng)就不會去重。

skip()

skip用于跳過前n個元素

@Test
public void  testSkip(){
    List<Student> students = Arrays.asList(new Student("小紅",18),new Student("小藍(lán)",3),new Student("小綠",10));
    students = students.stream().skip(1).collect(Collectors.toList());
    System.out.println(students);
}
limit()

返回前N個元素,用法如下:

@Test
    public void testLimit(){
        List<String> strings = Arrays.asList("aaa","bbb","ccc","ddd","eee","fff");
        strings = strings.stream().limit(3).collect(Collectors.toList());
        System.out.println(strings);
        //result:[aaa, bbb, ccc]
    }
flatMap()

flatMap的作用是將流中的每個元素映射為一個流,再將每個流連接成為一個流。下面舉一個具體的實例。

給定單詞列表["Hello","World"],輸出["H","e","l","o","W","r","d"]。
解決方案如下:

@Test
public void testFlatMap(){
    String[] ss = new String[]{"Hello","World"};
    List result = Arrays.stream(ss).map(e->e.split("")).flatMap(Arrays::stream).distinct().collect(Collectors.toList());
    System.out.println(result);

}
allMatch()/anyMatch()/noneMatch()

這里將這三個方法放一起將,從字面意思就可以看出來;

  • allMatch() 判斷流中是否所有元素都匹配給出的boolean條件;
  • anyMatch() 判斷流中是否存在元素匹配給出的boolean條件;
  • noneMatch() 判斷流中是否所有元素都不匹配給出的boolean條件。
    @Test
    public void testMatch(){
        List<Student> students = Arrays.asList(new Student("小紅",18),new Student("小藍(lán)",18),new Student("小綠",18));
        boolean allMatch = students.stream().allMatch(e->18 == e.getAge());
        System.out.println("allMatch:"+allMatch);
        //result allMatch:true
        boolean anyMatch = students.stream().anyMatch(e->"小藍(lán)".equals(e.getName()));
        System.out.println("anyMatch:"+anyMatch);
        //result  anyMatch:true
        boolean noneMatch = students.stream().noneMatch(e->"小黃".equals(e.getName()));
        System.out.println("noneMatch:"+noneMatch);
        //result noneMatch:true
    }
forEach()
   @Test
   public void testForEach(){
       List<Student> students = Arrays.asList(new Student("小紅",18),new Student("小藍(lán)",18),new Student("小綠",18));
       students.stream().forEach(System.out::println);//和student.forEach(System.out::println)
       //可以forEach中調(diào)用其他類的方法。
       students.stream().forEach(StudentMapper::insert);
   }
max(),min(),count(),sum(),average()

這是數(shù)據(jù)流對應(yīng)的相關(guān)操錯,具體操作方法如下:

@Test
    public void testMath(){
        List<Student> students = Arrays.asList(new Student("小紅",18),new Student("小藍(lán)",18),new Student("小綠",18));
        System.out.println(students.stream().count());
        System.out.println(students.stream().mapToInt(Student::getAge).sum());
        System.out.println(students.stream().mapToInt(Student::getAge).average());
        System.out.println(students.stream().mapToInt(Student::getAge).max());
        System.out.println(students.stream().mapToInt(Student::getAge).min());
    }
collect()

collect收集器中包含很多方法。下面也舉例一些常用方法。

  • collectors.toList() : 轉(zhuǎn)換list集合
  • collectors.toSet(): 轉(zhuǎn)換set集合
  • collectors.toCollection(TreeSet::new):轉(zhuǎn)換成任意指定類型的集合
  • collectors.minBy():求最小值,相對應(yīng)的有求最大值
  • collectors.averagingInt():求平均值
  • collectors.summing(): 求和
  • Collectors.summarizingDouble(x -> x):可以獲取最大值、最小值、平均值、總和值、總數(shù)。
  • Collectors.groupingBy(x -> x): 分組,這里有三個方法,下面統(tǒng)一看一下。
  • Collectors.partitioningBy(x -> x>2) : 把數(shù)據(jù)分成兩部分,key為ture/false。第一個方法也是調(diào)用第二個方法,第二個參數(shù)默認(rèn)為Collectors.toList().
  • Collectors.joining():拼接字符串
  • Collectors.collectingAndThen(Collectors.toList(), x -> x.size()):先執(zhí)行collect操作后再執(zhí)行第二個參數(shù)的表達(dá)式。這里是先塞到集合,再得出集合長度

下面是這些方法的示例:

@Test
   public void testCollect(){
       List<Student> students = Arrays.asList(new Student("小紅",18),new Student("小藍(lán)",18),new Student("小綠",18));
       LinkedList<Student> students1 = students.stream().collect(Collectors.toCollection(LinkedList::new));
       System.out.println("students1:"+students1);
       Map<Integer,List<Student>> studentListMap = students.stream().collect(Collectors.groupingBy(Student::getAge));
       System.out.println("studentListMap:"+studentListMap);
       Map<String,Integer> studentMap = students.stream().collect(Collectors.toMap(Student::getName
       ,Student::getAge));
       System.out.println("studentMap:"+studentMap);
//        Map<Integer,String> stringMap1 = students.stream().collect(Collectors.toMap(Student::getAge,Student::getName));
//        System.out.println("stringMap1"+stringMap1);
       Student student = students.stream().collect(Collectors.minBy((o1, o2) -> {
           if(o1.getAge()>o2.getAge()){
               return 1;
           }else if (o1.getAge() == o2.getAge()){
               return 0;
           }else{
               return -1;
           }
       })).get();
       System.out.println("student : " + student);

       DoubleSummaryStatistics doubleSummaryStatistics = students.stream().collect(Collectors.summarizingDouble(Student::getAge));
       System.out.println("average:"+ doubleSummaryStatistics.getAverage());
       System.out.println("count:"+ doubleSummaryStatistics.getCount());
       Map<Integer, List<Integer>> groupByMap = Stream.of(1, 3, 3, 2).collect(Collectors.groupingBy(Function.identity()));
       System.out.println("groupByMap:" + groupByMap);
       //result:{1=[1], 2=[2], 3=[3, 3]}
       Map<Integer, Integer> groupByMap1 = Stream.of(1, 3, 3, 2).collect(Collectors.groupingBy(Function.identity(), Collectors.summingInt(x -> x)));
       System.out.println("groupByMap1:"+groupByMap1);
       //result:{1=1, 2=2, 3=6}
       HashMap<Integer, List<Integer>> groupByMap2 = Stream.of(1, 3, 3, 2).collect(Collectors.groupingBy(Function.identity(), HashMap::new, Collectors.mapping(x -> x + 1, Collectors.toList())));
       System.out.println("groupByMap2:" + groupByMap2);
       //result :{1=[2], 2=[3], 3=[4, 4]}
       Map<Boolean, List<Integer>> partitioningByMap1 = Stream.of(1, 3, 3, 2).collect(Collectors.partitioningBy(x -> x > 2));
       Map<Boolean, Long> longMap = Stream.of(1, 3, 3, 2).collect(Collectors.partitioningBy(x -> x > 1, Collectors.counting()));
       System.out.println("partitioningByMap1:"+partitioningByMap1);
       //result : {false=[1, 2], true=[3, 3]}
       System.out.println("longMap:"+longMap);
       //result : {false=1, true=3}
       Integer integer = Stream.of("1", "2", "3").collect(Collectors.collectingAndThen(Collectors.toList(), x -> x.size()));
       System.out.println("integer:"+integer);
   }

并行流

并行流就是把一個內(nèi)容分成多個模塊,并用不同的線程處理每個模塊的數(shù)據(jù)的流。Stream API可以聲明性的通過parallel()方法和sequential()方法在并行流和串行流之間進(jìn)行切換。

Java8 的并行流,其底層使用的是Java7引入的fork/join框架,這里簡單說下fork/join,如果想了解的更徹底,可以去翻下資料。

fork/join框架,就是在必要的情況下,將一個大任務(wù),進(jìn)行拆分(fork)成若干個小任務(wù)(拆分到不可拆分)時,再將一個個小任務(wù)的執(zhí)行結(jié)果進(jìn)行整合(join)匯總。

fork-join.png

將一個順序流改為并行流只需要調(diào)用parallel()方法:

    @Test
    public void testParallel(){
        Long result  = Stream.iterate(1L,i -> i+1).limit(10).parallel().reduce(0L,Long::sum);
        System.out.println(result);
    }

將一個并行流改為串行流也只需要調(diào)用sequential()方法;

stream.parallel() .filter(...) .sequential() .map(...) .parallel() .reduce();

parallel()和sequential()方法可以交替重復(fù)調(diào)用,只有最后一次調(diào)用,會決定這個流是順序執(zhí)行還是并行執(zhí)行的。并發(fā)執(zhí)行的默認(rèn)線程數(shù)等于機器的處理器核心數(shù)。

事實上,并不是使用并行流的效率就一定比串行的高,這種同理于多線程與單線程的執(zhí)行效率受很多因素的影響。并發(fā)流需要對數(shù)據(jù)進(jìn)行分解,并不是所有的數(shù)據(jù)結(jié)構(gòu)都適合分解,下圖是整理的一些常用數(shù)據(jù)結(jié)構(gòu)的可分解性。

數(shù)據(jù)源 可分解性
ArrayList 非常好
LinkedList
IntStream.range 非常好
Stream.iterate
HashSet
TreeSet

除了上面的數(shù)據(jù)源的可分解性會對性能產(chǎn)生影響,還有對流的操作也會有影響。比如findFirst(),limit(n),skip(n)這種對順序有要求的操作,在并發(fā)流中是非常消耗性能的;而findAny(),anyMatch()這種的就非常適合使用并行流。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容