前言:对于简单的数字(list-Int)遍历,普通for循环效率的确比Stream串行流执行效率高(1.5-2.5倍)。但是Stream流可以利用并行执行的方式发挥CPU的多核优势,因此并行流计算执行效率高于for循环。
对于list-Object类型的数据遍历,普通for循环和Stream串行流比也没有任何优势可言,更不用提Stream并行流计算。
虽然在不同的场景、不同的数据结构、不同的硬件环境下。Stream流与for循环性能测试结果差异较大,甚至发生逆转。但是总体上而言:
Stream并行流计算 >> 普通for循环 ~= Stream串行流计算 (之所以用两个大于号,你细品)
数据容量越大,Stream流的执行效率越高。
Stream并行流计算通常能够比较好的利用CPU的多核优势。CPU核心越多,Stream并行流计算效率越高。
https://blog.youkuaiyun.com/weixin_37948888/article/details/96995312
findany() 和 findfirst()
2.使用Stream.findAny()
顾名思义,findAny()方法允许您从Stream中查找任何元素。 在寻找元素时使用时不会关注元素顺序。
该方法返回一个Optional实例,如果Stream为空,则该实例为空:
@Test
public void createStream_whenFindAnyResultIsPresent_thenCorrect() {
List<String> list = Arrays.asList("A","B","C","D");
Optional<String> result = list.stream().findAny();
assertTrue(result.isPresent());
assertThat(result.get(), anyOf(is("A"), is("B"), is("C"), is("D")));
}
在非并行操作中,它很可能返回Stream中的第一个元素,但不保证这一点。
为了在处理并行操作时获得最佳性能,也无法返回确定结果:
@Test
public void createParallelStream_whenFindAnyResultIsNotFirst_thenCorrect() {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> result = list
.stream().parallel()
.filter(num -> num < 4).findAny();
assertTrue(result.isPresent());
assertThat(result.get(), anyOf(is(1), is(2), is(3)));
}
- 使用Stream.findFirst()
findFirst()方法查找Stream中的第一个元素。 当您特别需要序列中的第一个元素时,可以使用此方法。
某些流的返回的元素是有确定顺序的,我们称之为 encounter order。这个顺序是流提供它的元素的顺序,比如数组的encounter order是它的元素的排序顺序,List是它的迭代顺序(iteration order),
但是对于HashSet,它本身就没有encounter order。
所以如果流没有encounter order的时候,它返回Stream中的任何元素。
java.util.streams包文档:
Streams may or may not have a defined encounter order. It depends on the source and the intermediate operations.
一个流是否是encounter order主要依赖数据源和它的中间操作。
返回类型也是一个Optional实例,如果Stream也为空,则该实例为空:
@Test
public void createStream_whenFindFirstResultIsPresent_thenCorrect() {
List<String> list = Arrays.asList("A", "B", "C", "D");
Optional<String> result = list.stream().findFirst();
assertTrue(result.isPresent());
assertThat(result.get(), is("A"));
}
findFirst方法的行为在并行方案中也不会更改。 如果是encounter order,则它将始终具有确定性。
findAny()方法返回Stream中的任何元素,而findFirst()方法返回Stream中的第一个元素。
reduce
这个方法的主要作用是把 Stream 元素组合起来。它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。例如 Stream 的 sum 就相当于
Integer sum = integers.reduce(0, (a, b) -> a+b); 或
Integer sum = integers.reduce(0, Integer::sum);
也有没有起始值的情况,这时会把 Stream 的前面两个元素组合起来,返回的是 Optional。
清单 15. reduce 的用例
// 字符串连接,concat = “ABCD”
String concat = Stream.of(“A”, “B”, “C”, “D”).reduce("", String::concat);
// 求最小值,minValue = -3.0
double minValue = Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min);
// 求和,sumValue = 10, 有起始值
int sumValue = Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
// 求和,sumValue = 10, 无起始值
sumValue = Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
// 过滤,字符串连接,concat = “ace”
concat = Stream.of(“a”, “B”, “c”, “D”, “e”, “F”).
filter(x -> x.compareTo(“Z”) > 0).
reduce("", String::concat);
显示更多
上面代码例如第一个示例的 reduce(),第一个参数(空白字符)即为起始值,第二个参数(String::concat)为 BinaryOperator。这类有起始值的 reduce() 都返回具体的对象。而对于第四个示例没有起始值的 reduce(),由于可能没有足够的元素,返回的是 Optional,请留意这个区别。
使用lambda表达式对List<Map>、List<Model>
的过滤
1.对list
示例:
要求:过滤掉电话相同的map
List<Map<String, Object>> mapList = new ArrayList<Map<String, Object>>();
Map<String, Object> mapStr1 = new HashMap<String, Object>();
mapStr1.put("name", "丽丽");
mapStr1.put("sex", "女");
mapStr1.put("age", 22);
mapStr1.put("tel", "110");
Map<String, Object> mapStr2 = new HashMap<String, Object>();
mapStr2.put("name", "丽丽");
mapStr2.put("sex", "女");
mapStr2.put("age", 23);
mapStr2.put("tel", "120");
Map<String, Object> mapStr3 = new HashMap<String, Object>();
mapStr3.put("name", "丽丽");
mapStr3.put("sex", "女");
mapStr3.put("age", 24);
mapStr3.put("tel", "110");
mapList.add(mapStr1);
mapList.add(mapStr2);
mapList.add(mapStr3);
ArrayList<Map<String, Object>> cs = mapList.stream()
.collect(Collectors
.collectingAndThen(Collectors
.toCollection(() -> new TreeSet<>(new Comparator<Map<String, Object>>() {
@Override //重写比较器
public int compare(Map<String, Object> o1, Map<String, Object> o2) {
if (o1.get("tel").equals(o2.get("tel"))) {
return 0;
}
return 1;
}
})), ArrayList::new));
//循环取出结果
cs.forEach(m -> {
System.out.println("======map========");
m.keySet().forEach(n -> System.out.println(n + "-->" + m.get(n)));
});
应用场景(二):
对list<T>中对象T的指定属性的去重,比如唯一ID,tel,获取新的list:去除了重复出现的指定属性值的对象T
示例:
要求过滤掉电话相同的user
User u1 = new User(1, "小米1", "001号", "女", 21, "110");
User u2 = new User(2, "小米2", "002号", "男", 11, "120");
User u3 = new User(3, "小米3", "003号", "女", 21, "119");
User u4 = new User(4, "小米4", "004号", "女", 34, "110");
List<User> persons = Arrays.asList(u1, u2, u3, u4);
List<User> unique = persons.stream()
.collect(Collectors
.collectingAndThen(Collectors
.toCollection(() -> new TreeSet<>(Comparator
.comparing(User::getTel))), ArrayList::new));
//循环取出结果
unique.forEach(p -> System.out.println(p.toString()));
3.从stuList中过滤出符合条件的结果。
public static List<Student> stuList() {
List<Student> stuList = new ArrayList<Student>();
Student stu1 = new Student();
stu1.setStuNum(20180823);
stu1.setName("张三");
stu1.setAge(20);
stuList.add(stu1);
Student stu2 = new Student();
stu2.setStuNum(20180824);
stu2.setName("李四");
stu2.setAge(21);
stuList.add(stu2);
Student stu3 = new Student();
stu3.setStuNum(20180825);
stu3.setName("王二");
stu3.setAge(21);
stuList.add(stu3);
Student stu4 = new Student();
stu4.setStuNum(20180826);
stu4.setName("张五");
stu4.setAge(22);
stuList.add(stu4);
return stuList;
}
public static void main(String[] args) {
List<Student> stuList = stuList();
// 需要筛选的条件:从stuList中筛选出年龄为21和22的学生
List<Integer> ageList = new ArrayList<Integer>();
ageList.add(21);
ageList.add(22);
// JDK1.8提供了lambda表达式, 可以从stuList中过滤出符合条件的结果。
// 定义结果集
List<Student> result = null;
result = stuList.stream()
.filter((Student s) -> ageList.contains(s.getAge()))
.collect(Collectors.toList());
lambda优雅取出对象list中某个属性重复的集合数据
public class Test {
public static List<Dog> dogs = null;
static {
dogs = new ArrayList<Dog>(){
{
add(new Dog("黄一",11));
add(new Dog("黄一",22));
add(new Dog("黄三",33));
}
};
}
public static void main(String[] args) {
List<String> list =dogs.stream().
collect(Collectors.groupingBy(dog->dog.getName(),Collectors.counting()))
.entrySet().stream()
.filter(entry->entry.getValue()>1)
.map(entry->entry.getKey())
.collect(Collectors.toList());
System.out.println(list.toString());
tips:在调用 Collectors.groupingBy(Function<? super T, ? extends K> classifier) 方法时,实际上调用的是
Collectors.groupingBy(Function<? super T, ? extends K> classifier, HashMap::new, Collectors.toList()) 方法,
中间的参数 HashMap::new 指定返回的是HashMap类型,而HashMap集合底层是哈希表,所以是无序集合,也就是存取顺序不同。
解决方法:
Map接口提供了有序的实现类LinkedHashMap,也就是链表+哈希表的结构来实现有序。
所以在之后使用groupingBy方法来实现List和Map的转换时,可以直接使用:
Collectors.groupingBy(Function<? super T, ? extends K> classifier, LinkedHashMap::new, Collectors.toList()) 方法。
Stream API 与 Optional配合使用
两者配合使用进行stream提供了更加优雅的方法:
利用Stream API 中的 filter将队列中的空对象过滤掉,filter(Objects::nonNull)的意思是,list中的每个元素执行Objects的nonNull()方法,返回false的元素被过滤掉,保留返回true的元素。
public static void main(String[] args) {
List<Person> personList = new ArrayList<>();
personList.add(new Person());
personList.add(null);
personList.add(new Person("小明",10));
personList.add(new Person("小红",12));
personList.stream().filter(Objects::nonNull).forEach(person->{
System.out.println(person.getName());
System.out.println(person.getAge());
});
}
lambda排序
对map的排序
map是键值对的集合接口,它的实现类主要包括:HashMap,TreeMap,Hashtable以及LinkedHashMap等。其中这四者的区别如下(简单介绍):
HashMap:我们最常用的Map,它根据key的HashCode 值来存储数据,根据key可以直接获取它的Value,同时它具有很快的访问速度。HashMap最多只允许一条记录的key值为Null(多条会覆盖);允许多条记录的Value为 Null。非同步的。
TreeMap: 能够把它保存的记录根据key排序,默认是按升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。TreeMap不允许key的值为null。非同步的。
Hashtable: 与 HashMap类似,不同的是:key和value的值均不允许为null;它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了Hashtale在写入时会比较慢。
LinkedHashMap: 保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的.在遍历的时候会比HashMap慢。key和value均允许为空,非同步的。
hashmap针对map的key进行排序:
map.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEachOrdered(x -> resultMap.put(x.getKey(),(x.getValue())));
comparingByKey():根据key排序.
comparingByValue():根据value排序
自定义比较器:
Collections.sort(list,new Comparator<Map.Entry<String,String>>() {
//升序排序
public int compare(Entry<String, String> o1,
Entry<String, String> o2) {
return o1.getValue().compareTo(o2.getValue());
}
});
for(Map.Entry<String,String> mapping:list){
System.out.println(mapping.getKey()+":"+mapping.getValue());
}
}
TreeMap根据key进行排序:
默认是升序的,如果我们需要改变排序方式,则需要使用比较器:Comparator。
Comparator可以对集合对象或者数组进行排序的比较器接口,实现该接口的public compare(T o1,To2)方法即可实现排序,该方法主要是根据第一个参数o1,小于、等于或者大于o2分别返回负整数、0或者正整数。如下:
public class TreeMapTest {
public static void main(String[] args) {
Map<String, String> map = new TreeMap<String, String>(
new Comparator<String>() {
public int compare(String obj1, String obj2) {
// 降序排序
return obj2.compareTo(obj1);
}
});
map.put("c", "ccccc");
map.put("a", "aaaaa");
map.put("b", "bbbbb");
map.put("d", "ddddd");
Set<String> keySet = map.keySet();
Iterator<String> iter = keySet.iterator();
while (iter.hasNext()) {
String key = iter.next();
System.out.println(key + ":" + map.get(key));
}
}
}
运行结果如下:
d:ddddd
c:ccccc
b:bbbbb
a:aaaaa
根据TreeMap的value来进行排序:
对value排序需要借助于Collections的sort(List list, Comparator<? super T> c)方法,该方法根据指定比较器产生的顺序对指定列表进行排序。但是有一个前提条件,那就是所有的元素都必须能够根据所提供的比较器来进行比较。如下:
public class TreeMapTest {
public static void main(String[] args) {
Map<String, String> map = new TreeMap<String, String>();
map.put("d", "ddddd");
map.put("b", "bbbbb");
map.put("a", "aaaaa");
map.put("c", "ccccc");
//这里将map.entrySet()转换成list
List<Map.Entry<String,String>> list = new ArrayList<Map.Entry<String,String>>(map.entrySet());
//然后通过比较器来实现排序
Collections.sort(list,new Comparator<Map.Entry<String,String>>() {
//升序排序
public int compare(Entry<String, String> o1,
Entry<String, String> o2) {
return o1.getValue().compareTo(o2.getValue());
}
});
for(Map.Entry<String,String> mapping:list){
System.out.println(mapping.getKey()+":"+mapping.getValue());
}
}
}
运行结果
a:aaaaa
b:bbbbb
c:ccccc
d:ddddd
对list排序
对num进行排序:
List<TestBean> listNew = list.stream().filter(testBean -> testBean != null && testBean.getSortNum() != null)
.sorted(Comparator.comparing(TestBean::getSortNum)).collect(Collectors.toList());
这种排序一旦有sortNum为空的对象,就会抛出空指针异常。
如果确实要排序带有空值的对象、并不想把这些对象剔除、又不想有NPE怎么办呢?
1)正序,且空值排前面
list.sort(Comparator.comparing(TestBean::getSortNum, Comparator.nullsFirst(Comparator.naturalOrder())));
2)正序,且空值排后面
list.sort(Comparator.comparing(TestBean::getSortNum, Comparator.nullsLast(Comparator.naturalOrder())));
3)倒序,且空值排前面,并取前两个元素
list.sort(Comparator.comparing(TestBean::getSortNum, Comparator.nullsFirst(Comparator.reverseOrder()))).limit(2);
4)倒序,且空值排后面
list.sort(Comparator.comparing(TestBean::getSortNum, Comparator.nullsLast(Comparator.reverseOrder())));
5)多条件排序
//先配置再排序
Comparator<Person> ageComparator = (o1, o2)->o1.getName().compareTo(o2.getName());
persons.sort(ageComparator); //按上面配置的顺序取值
System.out.println("Name升序排序后:");
persons.forEach((person)->System.out.println(person));
System.out.println("Name降序排序后:");
persons.sort(ageComparator.reversed()); //按上面配置的顺序反向取值
persons.forEach((person)->System.out.println(person));
// 多条件排序第二个写法,先按Age排序,再根据name排序
persons.sort(Comparator.comparing(Person::getAge).thenComparing(Person::getName));
System.out.println("多条件排序后:");
persons.sort(ageComparator.reversed()); //按上面配置的顺序反向取值
persons.forEach((person)->System.out.println(person));
6)中文排序
Collections.sort(persons, (Persono1, Persono2) -> Collator.getInstance(Locale.CHINESE).compare(o1.getName(), o2.getName()));
(7)自定义复杂排序
https://www.jb51.net/article/197158.htm
数值运算
BigDecimal:
BigDecimal result2 = userList.stream()
// 将user对象的age取出来map为Bigdecimal
.map(User::getAge)
// 使用reduce()聚合函数,实现累加器
.reduce(BigDecimal.ZERO,BigDecimal::add);
reduce是一个终结操作,它能够通过某一个方法,对元素进行削减操作。该操作的结果会放在一个Optional变量里返回。可以利用它来实现很多聚合方法比如count,max,min等。
T reduce(T identity, BinaryOperator accumulator);
第一个参数是我们给出的初值,第二个参数是累加器,可以自己用实现接口完成想要的操作,这里使用Bigdecimal的add方法 最后reduce会返回计算后的结果
模糊查询用以筛选list中数据
List<IOTDeviceStatus> list = returnList.stream().filter(d -> d.getName().contains(iotMindDevice.getDeviceName())).collect(Collectors.toList());
anyMatch\allMatch\noneMatch
boolean anyMatch(Predicate<? super T> predicate)
只要有一个条件满足即返回true
boolean allMatch(Predicate<? super T> predicate)
必须全部都满足才会返回true
boolean noneMatch(Predicate<? super T> predicate)
全都不满足才会返回true
anyMatch(谓词谓词)返回此流的任何元素是否与提供的谓词匹配。如果不一定要确定结果,则可能不会评估所有元素上的谓词。这是短路端子操作。如果出现无限输入时,端子操作可能会在有限时间内终止,则该端子操作会发生短路
对于 allMatch 和 noneMatch 方法,“流为空将返回 true 且不再评估谓词”,因此两种方法中的谓词可以是任何值。而对于 anyMatch 方法,流为空将返回 false,这可能导致错误诊断异常困难,所以应用时务须谨慎。
Safes.of()
optional的返回形式 为空返回空集合
Map
修改map中的value结构
原理:Collectors.toMap的两个参数的方法,第一个参数代表如何获取key,第二个代表如何获取value,因为key没有变,所以直接取entry的key,value的话可根据需要进行转换。
Map<String, Java> collect = map.entrySet().stream()
.collect(Collectors.toMap(
stringListEntry -> stringListEntry.getKey(),
stringListEntry -> new Java(stringListEntry.getValue())));
map过滤
/**
* 统计map中包含某一类值的个数,如果是以前的话,这里的代码应该会多好几行
* contains:只要对应的String中包含,返回值就是true,否则false
* */
private static long mapTest(Map<String,String> map,String contains){
long count = map.entrySet().stream().filter(
entry -> (entry.getValue().contains(contains))
).count();
System.out.println(count);
return count;
}
/**
* 过滤掉map中包含指定的value,然后返回过滤之后的map
* */
private static Map<String,String> filterMapValue(Map<String,String> map,String contains){
if(map == null || map.isEmpty()){
return null;
}
return map.entrySet().stream().filter(entry -> (
! entry.getValue().contains(contains)
)).collect(Collectors.toMap(
entry1 -> entry1.getKey(),
entry2 -> entry2.getValue()
));
}
/**
* 使用并行流过滤掉map中包含指定的value,然后返回过滤之后的map
* todo 测试发现 parallelStream在数据大时速度明显优于stream
* todo 代码简便,速度又快,为什么不用呢。。。。。
* */
private static Map<String,String> filterMapValue1(Map<String,String> map,String contains){
if(map == null || map.isEmpty()){
return null;
}
return map.entrySet().parallelStream().filter(entry ->
! entry.getValue().contains(contains)
).collect(Collectors.toMap(
entry1 -> entry1.getKey(),
entry2 -> entry2.getValue()
));
}