爲什麼需要 Stream
Stream 作爲 Java 8 的一大亮點,它與 java.io 包裏的 InputStream 和 OutputStream 是完全不同的概念。它也不同於 StAX 對 XML 解析的 Stream,也不是 Amazon Kinesis 對大數據實時處理的 Stream。Java 8 中的 Stream 是對集合(Collection)對象功能的增強,它專注於對集合對象進行各種非常便利、高效的聚合操作(aggregate operation),或者大批量數據操作 (bulk data operation)。Stream API 藉助於同樣新出現的 Lambda 表達式,極大的提高編程效率和程序可讀性。同時它提供串行和並行兩種模式進行匯聚操作,併發模式能夠充分利用多核處理器的優勢,使用 fork/join 並行方式來拆分任務和加速處理過程。通常編寫並行代碼很難而且容易出錯, 但使用 Stream API 無需編寫一行多線程的代碼,就可以很方便地寫出高性能的併發程序。所以說,Java 8 中首次出現的 java.util.stream 是一個函數式語言+多核時代綜合影響的產物。
直接開始介紹stream的主要使用的方法
1:map,操作元素將一個stream中的元素經過處理放入另一個stream中
List<CustomerStatisticsDTO> dtos = datas.stream().map(r->ConvertHelper.convert(r, CustomerStatisticsDTO.class)).collect(Collectors.toList());
這個例子中,我們使用了我們經常使用ConvertHelp將datas這個list中每一個節點對象轉化爲CustomerStatisticsDTO中並放到List這個新的list中,對應我們的java7以前的寫法爲
List<CustomerStatisticsDTO> dtos = new ArrayList<>();
for(CustomerStatistics data : datas){
dtos.add(ConvertHelper.convert(r, CustomerStatisticsDTO.class));
}
瞬間感覺逼格提升了許多,而且使用stream本身的效率也能有所提升
map還有一個好用的地方就是可以方便的取出一個pojo類中的某個屬性的集合,例如:
List<Long> ids = datas.stream().map(CustomerStatistics::getId).collect(Collectors.toList());
這個方法就是取出datas集合中所有節點的id並且放到ids這個列表中,對應的java7函數爲
List<Long> ids = new ArrayList<>();
for(CustomerStatistics data : datas){
ids.add(data.getId());
}
2.forEach,遍歷元素進行操作
stream還有一個很好用的函數就是forEach。它可以完美的替代java7的foreach,我們直接上代碼
Lock lock = new ReentrantLock();
……
cmd.getContacts().parallelStream.forEach((c) -> {
lock.lock();
CustomerContact contact = ConvertHelper.convert(c, CustomerContact.class);
contact.setCustomerId(cmd.getId());
contact.setCommunityId(cmd.getCommunityId());
contact.setNamespaceId(cmd.getNamespaceId());
contact.setStatus(CommonStatus.ACTIVE.getCode());
contact.setCustomerSource(cmd.getCustomerSource());
if(StringUtils.isNotBlank(contact.getName()) && StringUtils.isNotBlank(contact.getPhoneNumber())){
invitedCustomerProvider.createContact(contact);
}
lock.unlock();
});
原來的foreach:
for(CustomerContactDTO dto : cmd.getContacts()){
CustomerContact contact = ConvertHelper.convert(dto, CustomerContact.class);
contact.setCustomerId(cmd.getId());
contact.setCommunityId(cmd.getCommunityId());
contact.setNamespaceId(cmd.getNamespaceId());
contact.setStatus(CommonStatus.ACTIVE.getCode());
contact.setCustomerSource(cmd.getCustomerSource());
if(StringUtils.isNotBlank(contact.getName()) && StringUtils.isNotBlank(contact.getPhoneNumber())){
invitedCustomerProvider.createContact(contact);
}
}
這個forEach寫起來看起來與foreach的差別不大,但是不光省略了取dto的步驟,而且使用parallelStream的話可以並行處理list,在複雜邏輯的時候尤其有效,但是注意parallelStream不是線程安全的,所以使用的時候要記得加鎖,如果實在不放心,直接使用stream().forEach()也是可以的。
3.filter,通過條件過濾集合元素
filter也是一個很好用的方法,可以根據我們給出的條件在原有的集合中過濾出想要的節點組成新的集合,比如:
List<FieldItem> exists = existItemList.stream().filter(r-> r.getFieldId().equals(item.getFieldId()) && r.getDisplayName().equals(item.getItemDisplayName())).collect(Collectors.toList());
這個例子中,existItemList也是List,通過限定條件,我們可以遍歷existItemList這個list然後取出符合我們的過濾條件的數組的集合,如果使用java7及以前的方法,我們是這麼寫的:
List<FieldItem> exists = new ArrayList<>();
for(FieldItem item : existItemList){
if(item.equals(item.getFieldId()) && r.getDisplayName().equals(item.getItemDisplayName())){
exists.add(item);
}
}
這個也是可以直觀簡潔的讓我們用一行代碼寫出6行代碼的效果,用起來很舒服。
4.統計中使用stream
stream還有一個重要的意義就是統計,提供了mapToInt方法來幫助我們用更加簡潔的代碼,寫出更高的效率
ps:由於統計是默認返回long的,我這裏做了類型轉換
data.setTrackingNum(Integer.valueOf(String.valueOf(tempResult.stream().map(StatisticDataDTO::getTrackingNum).collect(Collectors.toList()).stream().mapToInt(x->x).summaryStatistics().getSum())));
這裏我用了兩次stream的操作,分解一下可以分爲以下的操作
被解析的list是tempResult,是一個StatisticDataDTO的鏈表,第一次操作是從tempResult中取出每一個DTO中的trackingNum屬性值,可以寫爲:
List<Integer> trackingNums = tempResult.stream().map(StatisticDataDTO::getTrackingNum).collect(Collectors.toList());
然後使用mapToInt
//由於stream的統計是統計成Long型的,但是我接收的DTO中是Integer型的,所以做了一次強轉
Long trackingSum = trackingNums.stream().mapToInt(x->x).summaryStatistics().getSum();
data.setTrackingNum(Integer.valueOf(String.valueOf(trackingSum)));
stream效率較高,比foreach要好用不少
Integer trackingSum = 0;
for(StatisticDataDTO tempNote : tempResult){
trackingSum += tempNote.getTrackingNum();
}
接下來我寫了一個測試代碼測試效率的,這是統計java8的流統計和我們傳統的方法效率的差別,首先是是最簡單的相加算法,先試一下1,000,000萬個數相加
public static void main(String[] args)
{
List<Integer> filterLists = new ArrayList<>();
for(int i=0;i<1000000;i++)
{
filterLists.add(i);
}
Long sum = 0L;
Date a = new Date();
for(Integer i : filterLists)
{
sum += i;
}
System.out.println(sum);
Date b = new Date();
sum = 0L;
Date c = new Date();
sum = filterLists.stream().mapToInt(x->x).summaryStatistics().getSum();
System.out.println(sum);
Date d = new Date();
long interval = b.getTime()-a.getTime();
long interval2 = d.getTime()-c.getTime();
System.out.println("兩個時間相差1:"+interval);//
System.out.println("兩個時間相差2:"+interval2);//
}
499999500000
499999500000
兩個時間相差1:21
兩個時間相差2:91
此時傳統的foreach循環更快
然後將數據量擴大十倍,取10,000,000個數之和
public static void main(String[] args)
{
List<Integer> filterLists = new ArrayList<>();
for(int i=0;i<10000000;i++)
{
filterLists.add(i);
}
Long sum = 0L;
Date a = new Date();
for(Integer i : filterLists)
{
sum += i;
}
System.out.println(sum);
Date b = new Date();
sum = 0L;
Date c = new Date();
sum = filterLists.stream().mapToInt(x->x).summaryStatistics().getSum();
System.out.println(sum);
Date d = new Date();
long interval = b.getTime()-a.getTime();
long interval2 = d.getTime()-c.getTime();
System.out.println("兩個時間相差1:"+interval);//7251
System.out.println("兩個時間相差2:"+interval2);//307
}
49999995000000
49999995000000
兩個時間相差1:1462
兩個時間相差2:98
最後後將數據量再擴大十倍,取100,000,000個數之和
public static void main(String[] args)
{
List<Integer> filterLists = new ArrayList<>();
for(int i=0;i<100000000;i++)
{
filterLists.add(i);
}
Long sum = 0L;
Date a = new Date();
for(int j=0;j<100000000;j++)
{
sum += filterLists.get(j);
}
System.out.println(sum);
Date b = new Date();
sum = 0L;
Date c = new Date();
sum = filterLists.stream().mapToInt(x->x).summaryStatistics().getSum();
System.out.println(sum);
Date d = new Date();
long interval = b.getTime()-a.getTime();
long interval2 = d.getTime()-c.getTime();
System.out.println("兩個時間相差1:"+interval);//7251
System.out.println("兩個時間相差2:"+interval2);//307
}
4999999950000000
4999999950000000
兩個時間相差1:4386
兩個時間相差2:188
雖然在處理數據量不大的集合的時候性能相差無幾,但是如果涉及複雜邏輯或者大量計算的話,stream的統計和傳統的for循環的時間複雜度不是同一個級別的,所以強烈推薦大家在涉及到統計的部分可以使用stream中的統計,真的效率很不錯的。