Jdk8新特性(七):JDK8之集合框架(Stream、parallelStream、...)

7. JDK8之集合框架

7.1. 新增串行流(Stream)

7.1.1. Stream簡介

Stream即“流”,通過將集合轉換爲這麼一種叫做 “流”的元素隊列,通過聲明性⽅方式,能夠對集合中的每個元素進行一系列並行或串行的流⽔線操作。

Stream 就如同一個迭代器(Iterator),單向,不可往復,數據只能遍歷一次,遍歷過一次後即用盡了,就好比流水從面前流過,一去不復返。

而和迭代器又不同的是,Stream 可以並行化操作,迭代器只能命令式地、串行化操作。顧名思義,當使用串行方式去遍歷時,每個 item 讀完後再讀下一個 item。而使用並行去遍歷時,數據會被分成多個段,其中每一個都在不同的線程中處理,然後將結果一起輸出。Stream 的並行操作依賴於 Java7 中引入的 Fork/Join 框架(JSR166y)來拆分任務和加速處理過程。

Java 8 中的 Stream 是對集合(Collection)對象功能的增強,它專注於對集合對象進行各種非常便利、高效的聚合操作(aggregate operation),或者大批量數據操作 (bulk data operation)。Stream API 藉助於同樣新出現的 Lambda 表達式,極大的提高編程效率和程序可讀性。同時它提供串行和並行兩種模式進行匯聚操作,併發模式能夠充分利用多核處理器的優勢,使用 fork/join 並行方式來拆分任務和加速處理過程。通常編寫並行代碼很難而且容易出錯, 但使用 Stream API 無需編寫一行多線程的代碼,就可以很方便地寫出高性能的併發程序。所以說,Java 8 中首次出現的 java.util.stream 是一個函數式語言+多核時代綜合影響的產物。

7.1.2. Stream操作

7.1.2.1. Stream操作步驟

Stream操作步驟如下:

 數據元素集合
即原始集合List、 Set、 Map等。

 生成流(Stream)
可以是串行流stream() 或者並行流 parallelStream()。
通過一個數據源獲取一個流,例如,List中的stream()方法可以直接返回一個Stream對象。

 中間操作
對流中的數據進行的操作,比如循環處理(map),過濾(filter)、排序、聚合、轉換等。

 終端操作
很多流操作本身就會返回一個流,所以多個操作可以直接連接起來,最後統一進⾏收集。

7.1.2.2. map和filter函數

 map方法

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class MapAndFilter {

	public static void main(String[] args) {
		//1.遍歷String集合
		List<String> colorList = Arrays.asList("black", "white", "yellow", "red", "grey", "blue");
		Stream<String> cStream = colorList.stream();
		List<String> resList = cStream.map(color -> "我喜歡"+color).collect(Collectors.toList());
		for (String s : resList) {
			System.out.print(s+",");// 我喜歡black,我喜歡white,我喜歡yellow,我喜歡red,我喜歡grey,我喜歡blue,
		}
		
		//2.遍歷JavaBean集合
		List<User> userList = Arrays.asList(new User("李四", 13), new User("張三", 23), new User("王五", 88));
		Stream<User> uStream = userList.stream();
		List<User> uList = uStream.map(user -> {
			return new User(user.getName(), user.getAge());
		}).collect(Collectors.toList());
		for (User u : uList) {
			System.out.print(u+",");//User [姓名:李四, 年齡:13],User [姓名:張三, 年齡:23],User [姓名:王五, 年齡:88],
		}	
	}

}

 Filter方法

// 篩選出顏色名長度>4的顏色
List<String> colorList2 = Arrays.asList("black", "white", "yellow", "red", "grey", "blue");
List<String> cList = colorList2.stream()
.filter(color -> color.length() > 4)
.collect(Collectors.toList());
System.out.println(cList);// [black, white, yellow]

7.1.2.3. limit和sorted函數

 limit方法
截斷(限制)流,使其最多隻包含指定數量的元素。

List<String> colorList = Arrays.asList("black", "white", "yellow", 
"red", "grey", "blue");
List<String> list = colorList.stream().limit(2)
.collect(Collectors.toList());
System.out.println(list);//[black, white]

 sorted方法

(1) Stream sorted()
返回由此流的元素組成的流,根據自然順序排序。

//按字母自然排序
List<String> colorList = Arrays.asList("black", "white", "yellow", "red", "grey", "blue");
List<String> list = colorList.stream().sorted()
.collect(Collectors.toList());
System.out.println(list);//[black, blue, grey, red, white, yellow]

(2) Stream sorted(Comparator<? super T> comparator)
返回由該流的元素組成的流,根據提供的 Comparator進行排序。

//按長度排序
List<String> colorList = Arrays.asList("black", "white", "yellow", "red", "grey", "blue");
List<String> list = colorList.stream()
.sorted(Comparator.comparing(obj -> obj.length()))
.collect(Collectors.toList());
System.out.println(list);//[red, grey, blue, black, white, yellow]

//方式一:按長度排序,並反轉(逆序)
List<String> colorList2 = Arrays.asList("black", "white", "yellow", "red", "grey", "blue");
List<String> list2 = colorList2.stream()
.sorted(Comparator.comparing(obj -> obj.length(), Comparator.reverseOrder()))
.collect(Collectors.toList());
System.out.println(list2);//[yellow, black, white, grey, blue, red]

//方式二:按長度排序,並反轉(逆序)
List<String> colorList3 = Arrays.asList("black", "white", "yellow", "red", "grey", "blue");
List<String> list3 = colorList3.stream()
.sorted(Comparator.comparing(String::length).reversed())
.collect(Collectors.toList());
System.out.println(list3);//[yellow, black, white, grey, blue, red]

7.1.2.4. allMatch和anyMatch函數

 allMatch方法

//allMatch,所有元素均符合,返回true
boolean flag = colorList.stream().allMatch(obj -> obj.length() > 4);
System.out.println(flag);//false
boolean flag2 = colorList.stream().allMatch(obj -> obj.length() > 1);
System.out.println(flag2);//true

 anyMatch方法

//anyMatch,有任意一個元素匹配,返回true
boolean flag3 = colorList.stream().anyMatch(obj -> obj.length() > 4);
System.out.println(flag3);//true
boolean flag4 = colorList.stream().anyMatch(obj -> obj.length() > 6);
System.out.println(flag4);//false

7.1.2.5. max和min函數

List<User> userList = Arrays.asList(new User("李四", 13), new User("張三", 23), new User("王五", 88));
		
//第一種:求最大、最小值
Optional<User> max = userList.stream()
.max(Comparator.comparingInt(User::getAge));
System.out.println(max.get());//User [姓名:王五, 年齡:88]
Optional<User> min = userList.stream()
.min(Comparator.comparingInt(User::getAge));
System.out.println(min.get());//User [姓名:李四, 年齡:13]

//第二種:求最大、最小值
//Optional<User> max2 = userList.stream()
.max((O1, O2) -> Integer.compare(O1.getAge(), O2.getAge()));
Optional<User> max2 = userList.stream()
.max((O1, O2) -> O1.getAge()-O2.getAge());
System.out.println(max2.get());//User [姓名:王五, 年齡:88]
Optional<User> min2 = userList.stream()
.min((O1, O2) -> O1.getAge()-O2.getAge());
System.out.println(min2.get());//User [姓名:李四, 年齡:13]

7.2. 新增並行流parallelStream

 爲什麼會有這個並行流?

集合做重複的操作,如果使用串行執行會相當耗時,因此一般會採用多線程來加快, Java8的paralleStream用fork/join框架提供了併發執行能力。

底層原理:
(1) 線程池(ForkJoinPool)維護了一個線程隊列。
(2) 可以分割任務,將父任務拆分成子任務,完全貼合分治思想。

List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);

//串行,順序輸出
list.stream().forEach(System.out::print);//12345678910
		
//並行,亂序輸出
list.parallelStream().forEach(System.out::print);//76891045123

 問題
(1) paralleStream並⾏是否一定比Stream串行快?

不一定,數據量少的情況,可能串行更快,ForkJoin會耗性能。

(2) 多數情況下並⾏比串⾏快,是否可以都⽤並行?

不⾏,部分情況會有線程安全問題, parallelStream裏面使用的外部變量,比如:集合一定要使用線程安全集合,不然就會引發多線程安全問題。

(1) 非線程安全集合ArrayList:

for (int i = 0; i < 10; i++) {
	List<Integer> list2 = new ArrayList<Integer>();
	IntStream.range(0, 100).parallel().forEach(list2::add);
	System.out.println(list2.size());
}

出現如下異常:
在這裏插入圖片描述

ArrayList的add()源碼:

/**
 * Appends the specified element to the end of this list.
 *
 * @param e element to be appended to this list
 * @return <tt>true</tt> (as specified by {@link Collection#add})
 */
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

(2) 線程安全集合CopyOnWriteArrayList

for (int i = 0; i < 10; i++) {
List<Integer> list2 = new CopyOnWriteArrayList<Integer>();
IntStream.range(0, 100).parallel().forEach(list2::add);
System.out.println(list2.size());
}

CopyOnWriteArrayList的add()源碼:

/**
 * Appends the specified element to the end of this list.
 *
 * @param e element to be appended to this list
 * @return {@code true} (as specified by {@link Collection#add})
 */
public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

7.3. 集合的聚合操作(reduce)

 常用方法:Optional reduce(BinaryOperator accumulator)

//1.普通寫法
Optional<Integer> opt = Stream.of(1,2,3,4,5)
.reduce(new BinaryOperator<Integer>() {
	@Override
	public Integer apply(Integer item1, Integer item2) {
		return item1+item2;
	}
});
if(opt.isPresent()) {
	System.out.println(opt.get());//15
}

//2.Lambda寫法
int value = Stream.of(1, 2, 3, 4, 5)
.reduce((item1, item2) -> item1 + item2).get();
System.out.println(value);//15

 常用方法:T reduce(T identity, BinaryOperator accumulator)

//1.普通寫法
Integer val = Stream.of(1,2,3,4,5)
.reduce(100, new BinaryOperator<Integer>() {
	@Override
	public Integer apply(Integer item1, Integer item2) {
		return item1+item2;
	}
});
System.out.println(val);//115

//2.Lambda寫法
Integer val2 = Stream.of(1,2,3,4,5)
.reduce(100, (item1, item2) -> item1+item2);
System.out.println(val2);//115

練習:求最大值

//1.普通寫法
Integer v = Stream.of(12,70,32).max(new Comparator<Integer>() {
	@Override
	public int compare(Integer o1, Integer o2) {
		return o1-o2;
	}
}).get();
System.out.println(v);//70

//2.Lambda寫法一
Integer v1 = Stream.of(12,70,32).max((o1,o2)-> o1-o2).get();
System.out.println(v1);//70

//3.Lambda寫法二
Integer v2 = Stream.of(12,70,32).reduce((item1,item2)->
item1>item2 ? item1:item2).get();
System.out.println(v2);//70

7.4. 集合的遍歷操作(foreach)

注意:
(1) 不能修改包含外部變量的值。
(2) 不能用break或return或continue等關鍵詞結束或跳過循環。

List<User> userList = Arrays.asList(new User("李四", 13), new User("張三", 23), new User("王五", 88));
userList.forEach(obj -> {
	System.out.println(obj);
});
/*
	User [姓名:李四, 年齡:13]
	User [姓名:張三, 年齡:23]
	User [姓名:王五, 年齡:88]
*/

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
list.stream().forEach(System.out::print);// 12345678910

list.stream().forEach(obj -> {
	System.out.print(obj + ",");// 1,2,3,4,5,6,7,8,9,10,
});

7.5. JDK8收集器和集合統計

7.5.1. JDK8收集器(collector)

 collect()方法的作用

一個終端操作,用於對流中的數據進行歸集操作, collect方法接受的參數是一個Collector。

有兩個重載方法,在Stream接口裏面。

//重載方法⼀
<R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T>
accumulator, BiConsumer<R, R>combiner);
//重載方法⼆
<R, A> R collect(Collector<? super T, A, R> collector);

 Collector的作用
就是收集器,也是一個接口,它的工具類Collectors提供了很多工廠方法。

 Collectors 的作用
工具類,提供了很多常見的收集器的實現。

// 1.遍歷String集合
List<String> colorList = Arrays.asList("black", "white", "yellow", "red", "grey", "blue");
Stream<String> cStream = colorList.stream();
List<String> resList = cStream.map(color -> "我喜歡" + color).collect(Collectors.toList());
for (String s : resList) {
	System.out.print(s + ",");// 我喜歡black,我喜歡white,我喜歡yellow,我喜歡red,我喜歡grey,我喜歡blue,
}

Collectors.toList()源碼:

/**
 * Returns a {@code Collector} that accumulates the input elements      
 * into a new {@code List}.
 *
 * @param <T> the type of the input elements
 * @return a {@code Collector} which collects all the input elements into a
 * {@code List}, in encounter order
 */
public static <T>
    Collector<T, ?, List<T>> toList() {
        return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
 (left, right) -> { left.addAll(right); return left; },CH_ID);
}

ArrayList::new,創建一個ArrayList作爲累加器。
List::add,對流中元素的操作就是直接添加到累加器中
reduce操作,對子任務歸集結果addAll,後一個子任務的結果直接全部添加到前一個子任務結果中。
CH_ID 是一個unmodifiableSet集合。
Collectors.toMap()
Collectors.toSet()
Collectors.toCollection() :用戶自定義的實現Collection的數據結構收集
Collectors.toCollection(LinkedList::new)
Collectors.toCollection(CopyOnWriteArrayList::new)
Collectors.toCollection(TreeSet::new)


7.5.2. 集合統計函數

7.5.2.1. 拼接函數(joining)

List<String> colorList = Arrays.asList("black", "white", "yellow", "red", "grey", "blue");
String str = colorList.stream().collect(Collectors.joining("_", "$", "^"));
System.out.println(str);//$black_white_yellow_red_grey_blue^

Collectors.joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix) 源碼:

/**
 * Returns a Collector that concatenates the input elements,
 * separated by the specified delimiter, with the specified prefix and
 * suffix, in encounter order.
 *
 * @param delimiter the delimiter to be used between each element
 * @param  prefix the sequence of characters to be used at the beginning
 *                of the joined result
 * @param  suffix the sequence of characters to be used at the end
 *                of the joined result
 * @return A {@code Collector} which concatenates CharSequence elements,
 * separated by the specified delimiter, in encounter order
 */
public static Collector<CharSequence, ?, String> joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix) {
    return new CollectorImpl<>(
            () -> new StringJoiner(delimiter, prefix, suffix),
            StringJoiner::add, StringJoiner::merge,
            StringJoiner::toString, CH_NOID);
}

7.5.2.2. 分組函數(partitioningBy)

List<String> colorList = Arrays.asList("black", "white", "yellow", "red", "grey", "blue");
Map<Boolean, List<String>> result = colorList.stream()
.collect(Collectors.partitioningBy(obj -> obj.length() > 4));
for (Map.Entry<Boolean,List<String>> map : result.entrySet()) {
	System.out.println(map.getKey()+":"+map.getValue());
}
/*
false:[red, grey, blue]
true:[black, white, yellow]
*/
//Lambda遍歷map
List<String> colorList2 = Arrays.asList("black", "white", "yellow", "red", "grey", "blue");
Map<Boolean, List<String>> map = colorList2.stream()
.collect(Collectors.partitioningBy(obj -> obj.length() > 4));
map.keySet().forEach(key -> {
	System.out.println(key+":"+map.get(key));
});
/*
false:[red, grey, blue]
true:[black, white, yellow]
*/

7.5.2.3. 分組函數(groupingBy)和分組統計(counting)

 Collectors.groupingBy:分組

List<Student> stuList = Arrays.asList(new Student("廣東", 23), new Student("廣東", 24), new Student("廣東", 23),new Student("北京", 22), new Student("北京", 20), new Student("北京", 20), new Student("海南", 25));
Map<String, List<Student>> maps = stuList.stream()
.collect(Collectors.groupingBy(obj -> obj.getProvince()));
// 1.普通遍歷
for (Map.Entry<String, List<Student>> map : maps.entrySet()) {
	System.out.println(map.getKey()+":"+map.getValue());
}
/*		
廣東:[Student [province=廣東, age=23], Student [province=廣東, age=24], Student [province=廣東, age=23]]
海南:[Student [province=海南, age=25]]
北京:[Student [province=北京, age=22], Student [province=北京, age=20], Student [province=北京, age=20]]
*/

//2.Lambda遍歷(一)
maps.entrySet().forEach(obj -> {
	System.out.println(obj.getKey()+":"+obj.getValue());
});
/*		
廣東:[Student [province=廣東, age=23], Student [province=廣東, age=24], Student [province=廣東, age=23]]
海南:[Student [province=海南, age=25]]
北京:[Student [province=北京, age=22], Student [province=北京, age=20], Student [province=北京, age=20]]
*/

//3.Lambda遍歷(二)
maps.forEach((key, value) -> {
	System.out.println(key+":");
	value.forEach(obj -> {
		System.out.println(obj);
	});
});
/*
	廣東:
	Student [province=廣東, age=23]
	Student [province=廣東, age=24]
	Student [province=廣東, age=23]
	海南:
	Student [province=海南, age=25]
	北京:
	Student [province=北京, age=22]
	Student [province=北京, age=20]
	Student [province=北京, age=20]
*/	

 Collectors.counting():分組統計

List<Student> stuList2 = Arrays.asList(new Student("廣東", 23), new Student("廣東", 24), new Student("廣東", 23),new Student("北京", 22), new Student("北京", 20), new Student("北京", 20), new Student("海南", 25));
Map<String, Long> maps2 = stuList2.stream().collect(Collectors
.groupingBy(Student::getProvince, Collectors.counting()));
maps2.entrySet().forEach(obj -> {
	System.out.println(obj.getKey()+":"+obj.getValue());
});
/*
廣東:3
海南:1
北京:3
*/

7.5.2.4. summarizing 統計相關函數

List<Student> stuList3 = Arrays.asList(new Student("廣東", 23), new Student("廣東", 24), new Student("廣東", 23),new Student("北京", 22), new Student("北京", 20), new Student("北京", 20), new Student("海南", 25));
IntSummaryStatistics summaryStatistics = stuList3.stream()
.collect(Collectors.summarizingInt(Student::getAge));
System.out.println("平均值: " + summaryStatistics.getAverage());//平均值: 22.428571428571427
System.out.println("人數: " + summaryStatistics.getCount());//人數: 7
System.out.println("最大值: " + summaryStatistics.getMax());//最大值: 25
System.out.println("最小值: " + summaryStatistics.getMin());//最小值: 20
System.out.println("總和: " + summaryStatistics.getSum());//總和: 157

源碼:

/**
 * Returns a {@code Collector} which applies an {@code int}-producing
 * mapping function to each input element, and returns summary statistics for the resulting values.
 *
 * @param <T> the type of the input elements
 * @param mapper a mapping function to apply to each element
 * @return a {@code Collector} implementing the summary-statistics reduction
 *
 * @see #summarizingDouble(ToDoubleFunction)
 * @see #summarizingLong(ToLongFunction)
 */
public static <T>
Collector<T, ?, IntSummaryStatistics> summarizingInt(ToIntFunction<? super T> mapper) {
    return new CollectorImpl<T, IntSummaryStatistics, IntSummaryStatistics>(
            IntSummaryStatistics::new,
            (r, t) -> r.accept(mapper.applyAsInt(t)),
            (l, r) -> { l.combine(r); return l; }, CH_ID);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章