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物件)