Java8 Stream流操作在用户系统中的妙用

原创 2016年07月04日 16:06:49

在做目前这个项目的时候,发现以前有一个筛选的需求,老程序员是这么做的,先请求Http服务器得到一长串json数据,大概用A4纸打了40多页那么多,然后将这些对象写入到sqlite数据库中,再用数据库查询语句根据筛选条件查出来。最后将数据库丢弃。把我们这些新程序员看的目瞪口呆。自从接触了Java8之后,发现可以像操作数据库一样操作内存,而且在Stream操作中对内存的开销十分友善,操作方式十分灵活,减少了IO的支出,简直爽歪歪。

传统的数据处理都是用循环来解决,而不是像搜索数据库那样有具体的搜索语句,而Java8的Stream提供了很好的方案,往往一行就搞定了,而且Stream还可以链式操作,一行代码实现多个循环的功能,代码风格十分像nosql数据库,但是在实际应用中发现一个巨大的问题,就是执行耗时特别长,时间开销是传统方法的几百倍,这是一个巨大的问题。

本文主要来讨论一下如何发挥Stream的优势展示对用户管理操作

首先我们制造一个User类用来代表用户,里面有姓名年龄密码等常用字段,顺道再写个构造函数和toString(),如下

public class User
	{
		public int age;//年龄
		public String name;//姓名
		private String password;//密码
		public short gendar;//性别,0未知,1男,2女
		public boolean hasMarried;//是否已婚
		
		
		public String getPassword() {
			return password;
		}
		
		public User(int age, String name, String password, short gendar,
				boolean hasMarried) {
			super();
			this.age = age;
			this.name = name;
			this.password = password;
			this.gendar = gendar;
			this.hasMarried = hasMarried;
		}
		@Override
		public String toString() {
			return "{\"age\":\"" + age + "\", \"name\":\"" + name
					+ "\", \"password\":\"" + password + "\", \"gendar\":\""
					+ gendar + "\", \"hasMarried\":\"" + hasMarried + "\"} \n";
		}
	}

现在我们伪造一点数据,暂时就用我大学同学的名字吧:

ArrayList<User> users = new ArrayList<User>();
		users.add(new User(22, "王旭", "123456", (short)1, true));
		users.add(new User(22, "王旭", "123456", (short)1, true));
		users.add(new User(22, "王旭", "123456", (short)1, true));
		users.add(new User(21, "孙萍", "a123456", (short)2, false));
		users.add(new User(23, "步传宇", "b123456", (short)1, false));
		users.add(new User(18, "蔡明浩", "c123456", (short)1, true));
		users.add(new User(17, "郭林杰", "d123456", (short)1, false));
		users.add(new User(5, "韩凯", "e123456", (short)1, true));
		users.add(new User(22, "韩天琪", "f123456", (short)2, false));
		users.add(new User(21, "郝玮", "g123456", (short)2, false));
		users.add(new User(19, "胡亚强", "h123456", (short)1, false));
		users.add(new User(14, "季恺", "i123456", (short)1, false));
		users.add(new User(17, "荆帅", "j123456", (short)1, true));
		users.add(new User(16, "姜有琪", "k123456", (short)1, false));
				
		

场景一、对用户进行排序

首先我们制定一个排序规则:按照年龄大小进行排序,设计一个Comparator

Comparator<User> ageComparator = new Comparator<User>() {

			@Override
			public int compare(User o1, User o2) {
				// TODO Auto-generated method stub
				if(o1.age>o2.age)return 1;
				if(o1.age<o2.age)return -1;
				return 0;
			}
		};

传统方式排序:

time = System.currentTimeMillis();
Collections.sort(users, ageComparator);
System.out.println("耗时"+(System.currentTimeMillis()-time));
System.out.println(users);
输出结果:

耗时0
[{"age":"5", "name":"韩凯", "password":"e123456", "gendar":"1", "hasMarried":"true"}
, {"age":"14", "name":"季恺", "password":"i123456", "gendar":"1", "hasMarried":"false"}
, {"age":"16", "name":"姜有琪", "password":"k123456", "gendar":"1", "hasMarried":"false"}
, {"age":"17", "name":"郭林杰", "password":"d123456", "gendar":"1", "hasMarried":"false"}
, {"age":"17", "name":"荆帅", "password":"j123456", "gendar":"1", "hasMarried":"true"}
, {"age":"18", "name":"蔡明浩", "password":"c123456", "gendar":"1", "hasMarried":"true"}
, {"age":"19", "name":"胡亚强", "password":"h123456", "gendar":"1", "hasMarried":"false"}
, {"age":"21", "name":"孙萍", "password":"a123456", "gendar":"2", "hasMarried":"false"}
, {"age":"21", "name":"郝玮", "password":"g123456", "gendar":"2", "hasMarried":"false"}
, {"age":"22", "name":"王旭", "password":"123456", "gendar":"1", "hasMarried":"true"}
, {"age":"22", "name":"王旭", "password":"123456", "gendar":"1", "hasMarried":"true"}
, {"age":"22", "name":"王旭", "password":"123456", "gendar":"1", "hasMarried":"true"}
, {"age":"22", "name":"韩天琪", "password":"f123456", "gendar":"2", "hasMarried":"false"}
, {"age":"23", "name":"步传宇", "password":"b123456", "gendar":"1", "hasMarried":"false"}
]

Java8的方式排序:

long time = System.currentTimeMillis();
List<User> sortedUsers = users.stream().sorted(ageComparator).collect(Collectors.toList());
System.out.println("耗时"+(System.currentTimeMillis()-time));
System.out.println(sortedUsers);
输出结果相同,耗时234ms

从结果来看,java8 Stream操作的耗时至少是传统方法的200多倍,时间成本较大。

场景一(2)选出年龄最小的三个人


有时候我们也许并不需要获得排序的所有结果,只需要获得前几名就可以了,比如我想获得年龄最小的三个人

传统方法排序限制:

首先进行上面的排序,然后取出数组的前三个元素

Collections.sort(users, ageComparator);
users.subList(0, 2);
耗时0

Java8方式排序限制:

long time = System.currentTimeMillis();
List<User> resultArr = users.stream().sorted(ageComparator).limit(3).collect(Collectors.toList());
System.out.println("耗时"+(System.currentTimeMillis()-time));
System.out.println(resultArr);

结果:耗时375
[{"age":"5", "name":"韩凯", "password":"e123456", "gendar":"1", "hasMarried":"true"}
, {"age":"14", "name":"季恺", "password":"i123456", "gendar":"1", "hasMarried":"false"}
, {"age":"16", "name":"姜有琪", "password":"k123456", "gendar":"1", "hasMarried":"false"}
]


场景二:去除重复数据

为了识别两个对象是否重复,需要复写User的equals方法

@Override
		public boolean equals(Object obj) {
			// TODO Auto-generated method stub
			if(!(obj instanceof User))return false;
			User u = (User)obj;
			if(age != u.age
					|| gendar!=u.gendar
					|| hasMarried!=u.hasMarried
					|| !name.equals(u.name)
					|| !password.equals(u.getPassword())
					)return false;
			return true;
		}

要实现去重,必须首先将数组的顺序严格排好,也就是相似的数据要放在一起,便于排序,所以我们要重写一下比较器

Comparator<User> equalComparator = new Comparator<User>() {

			@Override
			public int compare(User o1, User o2) {
				// TODO Auto-generated method stub
				//首先比较年龄大小,因为年龄的区分度比较高
				if(o1.age>o2.age)return 1;
				if(o1.age<o2.age)return -1;
				//如果年龄相同就比较性别,女的排在前面
				if(o1.gendar>o2.gendar)return 1;
				if(o1.gendar<o2.gendar)return -1;
				//如果性别也一样就比较是否已婚
				if(o1.hasMarried == true && o2.hasMarried==false)return 1;//结婚的排在前面
				if(o1.hasMarried == false && o2.hasMarried==true)return 1;//结婚的排在前面
				//最后比较姓名,因为字符串比较耗时较长
				if(o1.name.hashCode()>o2.name.hashCode())return 1;
				if(o1.name.hashCode()<o2.name.hashCode())return -1;
				return 0;
			}
		};

传统方法去重:

Collections.sort(users, ageComparator);
		time = System.currentTimeMillis();
		int length = users.size();
		for(int i=1;i<length;i++){
			if(users.get(i).equals(users.get(i-1))){
				users.remove(i);
				i--;
				length--;
			}
		}
		System.out.println("耗时"+(System.currentTimeMillis()-time));
		System.out.println(users);
输出结果:

耗时0
[{"age":"5", "name":"韩凯", "password":"e123456", "gendar":"1", "hasMarried":"true"}
, {"age":"14", "name":"季恺", "password":"i123456", "gendar":"1", "hasMarried":"false"}
, {"age":"16", "name":"姜有琪", "password":"k123456", "gendar":"1", "hasMarried":"false"}
, {"age":"17", "name":"郭林杰", "password":"d123456", "gendar":"1", "hasMarried":"false"}
, {"age":"17", "name":"荆帅", "password":"j123456", "gendar":"1", "hasMarried":"true"}
, {"age":"18", "name":"蔡明浩", "password":"c123456", "gendar":"1", "hasMarried":"true"}
, {"age":"19", "name":"胡亚强", "password":"h123456", "gendar":"1", "hasMarried":"false"}
, {"age":"21", "name":"孙萍", "password":"a123456", "gendar":"2", "hasMarried":"false"}
, {"age":"21", "name":"郝玮", "password":"g123456", "gendar":"2", "hasMarried":"false"}
, {"age":"22", "name":"王旭", "password":"123456", "gendar":"1", "hasMarried":"true"}
, {"age":"22", "name":"韩天琪", "password":"f123456", "gendar":"2", "hasMarried":"false"}
, {"age":"23", "name":"步传宇", "password":"b123456", "gendar":"1", "hasMarried":"false"}
]

Java8 去重:

Stream去重有一个先决条件,就是要去重的对象必须实现comparable接口,不能使用比较器,于是让user类implement comparable,并复写其方法compareTo,另外equals()方法与上面一样.

public int compareTo(User o2) {
			// TODO Auto-generated method stub
			//首先比较年龄大小,因为年龄的区分度比较高
			User o1 = this;
			if(o1.age>o2.age)return 1;
			if(o1.age<o2.age)return -1;
			//如果年龄相同就比较性别,女的排在前面
			if(o1.gendar>o2.gendar)return 1;
			if(o1.gendar<o2.gendar)return -1;
			//如果性别也一样就比较是否已婚
			if(o1.hasMarried == true && o2.hasMarried==false)return 1;//结婚的排在前面
			if(o1.hasMarried == false && o2.hasMarried==true)return 1;//结婚的排在前面
			//最后比较姓名,因为字符串比较耗时较长
			if(o1.name.hashCode()>o2.name.hashCode())return 1;
			if(o1.name.hashCode()<o2.name.hashCode())return -1;
			return 0;
		}
然后先调用stream的sorted()方法进行排序,再调用disdinct()方法进行去重,结果同上面一样,耗时249ms

long time = System.currentTimeMillis();
List resultArr = users.stream().sorted().distinct().collect(Collectors.toList());
System.out.println("耗时"+(System.currentTimeMillis()-time));
System.out.println(resultArr);


场景三:按条件筛选

这种场景也许是最常见的一种应用场景了,在许多元素构成的数组中筛选出我们需要的满足特定条件的元素,在这里我们把所有姓韩的筛选出来

传统方法:

long time = System.currentTimeMillis();
		ArrayList<User> resultArr = new ArrayList<User>();//用于存放结果
		for(User u:users){
			if(u.name.startsWith("韩"))resultArr.add(u);
		}
		System.out.println("耗时"+(System.currentTimeMillis()-time));
		System.out.println(resultArr);
结果:耗时0
[{"age":"5", "name":"韩凯", "password":"e123456", "gendar":"1", "hasMarried":"true"}
, {"age":"22", "name":"韩天琪", "password":"f123456", "gendar":"2", "hasMarried":"false"}
]

Java8方法:

long time = System.currentTimeMillis();
		List<User> resultArr = users.stream().filter(t->t.name.startsWith("韩")).collect(Collectors.toList());
		System.out.println("耗时"+(System.currentTimeMillis()-time));
		System.out.println(resultArr);

结果相同,耗时266ms

其中,java8采用的泛型进行处理,上面的t->t.name中的t是Stream<T>的泛型,而这个T又是List<T>中的泛型,t可以换成其他任何字母,并且也可以点出User类的相关方法,并且还可以支持复合筛选,比如我们要筛选姓韩的女生,可以这样写

List<User> resultArr = users.stream().filter(t->t.name.startsWith("韩") && t.gendar==2).collect(Collectors.toList());		
结果:耗时281
[{"age":"22", "name":"韩天琪", "password":"f123456", "gendar":"2", "hasMarried":"false"} ]

也可以这样写

List<User> resultArr = users.stream().filter(t->t.name.startsWith("韩")).filter(t->t.gendar==2).collect(Collectors.toList());	
耗时296ms

还可以改变条件的顺序:

List<User> resultArr = users.stream().filter(t->t.gendar==2).filter(t->t.name.startsWith("韩")).collect(Collectors.toList());
耗时265ms

场景四:只列出所有人的名字和婚姻状况

这次要用的.map()函数,map()就是为了只显示对象的一部分信息而准备的。

传统方式:

long time = System.currentTimeMillis();
ArrayList<String> marryStatus = new ArrayList<String>();
		for(User u:users){
			marryStatus.add(u.name+":".concat(u.hasMarried?"已婚":"未婚")+"\n");
		}
System.out.println("耗时"+(System.currentTimeMillis()-time));
System.out.println(marryStatus);
耗时:0

[王旭:已婚
, 王旭:已婚
, 王旭:已婚
, 孙萍:未婚
, 步传宇:未婚
, 蔡明浩:已婚
, 郭林杰:未婚
, 韩凯:已婚
, 韩天琪:未婚
, 郝玮:未婚
, 胡亚强:未婚
, 季恺:未婚
, 荆帅:已婚
, 姜有琪:未婚
]

java8方式:

long time = System.currentTimeMillis();
List<String> marryStatus = users.stream().map(t->t.name+":".concat(t.hasMarried?"已婚":"未婚")+"\n").collect(Collectors.toList());
System.out.println("耗时"+(System.currentTimeMillis()-time));
System.out.println(marryStatus);

结果相同:耗时234


场景五:判断当前数组是否包含某些特定元素

如果我要看看现在的用户中是否有未成年人怎么办呢

传统方法:

long time = System.currentTimeMillis();
		boolean isChild = false;
		for(User u:users){
			if(u.age<18){
				isChild = true;
				break;
			}
		}
System.out.println("耗时"+(System.currentTimeMillis()-time)+hasChild);
结果:耗时0 true

Java8方法:

long time = System.currentTimeMillis();
boolean isChild = users.stream().anyMatch(t->t.age<18);
System.out.println("耗时"+(System.currentTimeMillis()-time)+isChild);
耗时78ms 结果相同

场景六:确认所有元素均满足某一条件

这里以查看所有人是否都已婚为例

传统方法:

long time = System.currentTimeMillis();
		boolean allMarried = true;
		for(User u:users){
			if(!u.hasMarried){
				allMarried = false;
				break;
			}
		}
System.out.println("耗时"+(System.currentTimeMillis()-time)+allMarried);
	

结果:耗时0 false

java8方法:

long time = System.currentTimeMillis();
boolean allMarried = users.stream().allMatch(t->t.hasMarried);
System.out.println("耗时"+(System.currentTimeMillis()-time)+allMarried);
结果相同,耗时46ms

场景七:求和求平均值

求和这种操作在用户管理上十分频繁,java8的流操作省去了循环,节省了大量代码,比如我们要求所有用户的平均年龄

传统方法:

long time = System.currentTimeMillis();
		int sum = 0;
		for(User u:users){
			sum+=u.age;
		}
System.out.println("耗时"+(System.currentTimeMillis()-time)+sum/users.size());
结果:耗时0 平均年龄18

java8方法:

这里先用map方法把所有元素的age取出来,然后调用Integer.sum方法进行聚合(reduce函数),得到年龄和,返回是一个OptionalInt对象,这里面包含一个int,但也有可能为null,注意这里reduce()函数的参数是一个方法,注意Java8支持将函数作为参数传入了,有点像c++写法,规则是完整类名::方法名(方法参数...)

long time = System.currentTimeMillis();
OptionalInt sum = users.stream().mapToInt(t->t.age).reduce(Integer::sum);
System.out.println("耗时"+(System.currentTimeMillis()-time)+sum.getAsInt()/users.size());
结果相同,耗时63ms

场景八:分组

比如我们要按用户的年龄进行分组,相同年龄的人分在同一组,用一个Map<Integer,List<User>>存放,key是年龄,value是该年龄的所有用户

传统方法:

long time = System.currentTimeMillis();
		Map<Integer,List<User>> group = new HashMap<Integer,List<User>>();
		for(User u:users){
			List<User> list = group.get(u.age);
			if(list==null){
				list = new ArrayList<User>();
				group.put(u.age,list);
			}
			list.add(u);
		}
System.out.println("耗时"+(System.currentTimeMillis()-time));
System.out.println(group);

结果:

耗时0
{16=[{"age":"16", "name":"姜有琪", "password":"k123456", "gendar":"1", "hasMarried":"false"} 
], 17=[{"age":"17", "name":"郭林杰", "password":"d123456", "gendar":"1", "hasMarried":"false"} 
, {"age":"17", "name":"荆帅", "password":"j123456", "gendar":"1", "hasMarried":"true"} 
], 18=[{"age":"18", "name":"蔡明浩", "password":"c123456", "gendar":"1", "hasMarried":"true"} 
], 19=[{"age":"19", "name":"胡亚强", "password":"h123456", "gendar":"1", "hasMarried":"false"} 
], 21=[{"age":"21", "name":"孙萍", "password":"a123456", "gendar":"2", "hasMarried":"false"} 
, {"age":"21", "name":"郝玮", "password":"g123456", "gendar":"2", "hasMarried":"false"} 
], 5=[{"age":"5", "name":"韩凯", "password":"e123456", "gendar":"1", "hasMarried":"true"} 
], 22=[{"age":"22", "name":"王旭", "password":"123456", "gendar":"1", "hasMarried":"true"} 
, {"age":"22", "name":"王旭", "password":"123456", "gendar":"1", "hasMarried":"true"} 
, {"age":"22", "name":"王旭", "password":"123456", "gendar":"1", "hasMarried":"true"} 
, {"age":"22", "name":"韩天琪", "password":"f123456", "gendar":"2", "hasMarried":"false"} 
], 23=[{"age":"23", "name":"步传宇", "password":"b123456", "gendar":"1", "hasMarried":"false"} 
], 14=[{"age":"14", "name":"季恺", "password":"i123456", "gendar":"1", "hasMarried":"false"} 
]}

Java8方法:

long time = System.currentTimeMillis();
Map<Integer,List<User>> group = users.stream().collect(Collectors.groupingBy(t->t.age));
System.out.println("耗时"+(System.currentTimeMillis()-time));
System.out.println(group);
结果相同,耗时62ms

如果想按是否结婚分组,也就是key变成bool,那就应该这么写

Map<Boolean,List<User>> group = users.stream().collect(Collectors.partitioningBy(t->t.hasMarried));
耗时也是62ms

场景九:链式操作

如果我们需要打印所有女生的名字,那么同样可以一行代码搞定,思路是先通过源Stream通过筛选得到一个新Stream,再对这个新的Stream进行操作,如此循环,注意这里使用的forEach()函数是遍历Stream中的每一个元素,参数是方法,注意Java8支持将函数作为参数传入了,有点像c++写法,规则是完整类名::方法名(方法参数...)

long time = System.currentTimeMillis();
users.stream().filter(t->t.gendar==2).map(t->t.name).forEach(System.out::println);
System.out.println("耗时"+(System.currentTimeMillis()-time));
结果:孙萍
韩天琪
郝玮
耗时47







版权声明:本文为博主原创文章,未经博主允许不得转载。 举报

相关文章推荐

Stream语法详解

1. Stream初体验 我们先来看看Java里面是怎么定义Stream的: A sequence of elements supporting sequential and para...

list字符串去重的三种方式 list去重 字符串去重

list字符串去重的三种方式求List中元素去重,并且求出去重后的个数 采用原始的for循环遍历 采用set集合的特点 采用Java8流处理方式 package sun.rain.amazing.st...

精选:深入理解 Docker 内部原理及网络配置

网络绝对是任何系统的核心,对于容器而言也是如此。Docker 作为目前最火的轻量级容器技术,有很多令人称道的功能,如 Docker 的镜像管理。然而,Docker的网络一直以来都比较薄弱,所以我们有必要深入了解Docker的网络知识,以满足更高的网络需求。

Java8初体验(二)Stream语法详解

转自:http://ifeve.com/stream/Java8初体验(二)Stream语法详解 感谢同事【天锦】的投稿。投稿请联系 tengfei@ifeve.com 上篇文章Java8初体验(...

Java List<Object>去掉重复对象-java8

一、去除List中重复的Stringpublic List removeStringListDupli(List stringList) { Set set = new LinkedHashS...

java List 去重(两种方式)

方法一: 通过Iterator 的remove方法 public void testList() {    List list=new ArrayList();    list.add(1);  l...

原来 Java8 Stream 中的排序是插入排序

写了小程序,验证 Java8 Steam sort是如何实现 package com.pnp.tryJ8col; import java.util.Arrays; import java.uti...
  • span76
  • span76
  • 2016-08-25 16:09
  • 5360

JDK 8 之 Stream sorted() 示例

原文链接:http://www.concretepage.com/java/jdk-8/java-8-stream-sorted-example 国外对Java8一系列总结的不错, 翻译过来给大家共...
  • lsgqjh
  • lsgqjh
  • 2017-03-19 17:34
  • 3866

Java 8 Stream 流已被操作或关闭

在Java 8中,Stream不能重复使用,一旦被消耗或使用,流将被关闭,类似流水线,水龙头的水一样一去不复返 示例 - 流关闭 查看以下示例,它会抛出一个IllegalStateExceptio...

Java8 Stream详解

Java8中提供了Stream对集合操作作出了极大的简化,学习了Stream之后,我们以后不用使用for循环就能对集合作出很好的操作。 一、流的初始化与转换:   Java中的Stream的所有操作...

JAVA8特性之STREAM

Java 8 引入了流式操作(Stream),通过该操作可以实现对集合(Collection)的并行处理和函数式操作。根据操作返回的结果不同,流式操作分为中间操作和最终操作两种。最终操作返回一特定类型...
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)