技术tips-1)javasteam的⼀些好⽤⽅法的总结,如分组后⾃定义排序等。
⽂章⽬录
背景
越来越多的场景下,从数据库获取数据被要求简单、不得包含更多的业务逻辑,⽽是建议单纯的打中【索引】取【合理数量】的数据⾄内存中,再通过代码进⾏⼆次处理。
在这⼀样的背景下,通过steam相关⽅法进⾏⼆次数据处理感觉是⼀个较为⽅便的⽅式。
场景
我们构建相关场景,并建⽴相关表进⾏后续案列表述。
相关项⽬地址:
1.数据表相关
班级表
CREATE TABLE`t_test_class`
(
`id`bigint(20)unsigned NOT NULL AUTO_INCREMENT COMMENT'⾃增id',
`name`varchar(32)NOT NULL COMMENT'名称',
`create_time`timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT'创建时间',
`modify_time`timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT'修改时间',
PRIMARY KEY(`id`)COMMENT'主键'
)ENGINE=InnoDB AUTO_INCREMENT=1DEFAULT CHARSET=utf8mb4 COMMENT='班级表';
爱好表
CREATE TABLE`t_test_hobby`
(
`id`bigint(20)unsigned NOT NULL AUTO_INCREMENT COMMENT'⾃增id',
`desc`varchar(32)NOT NULL COMMENT'描述',
`create_time`timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT'创建时间',
`modify_time`timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT'修改时间',
PRIMARY KEY(`id`)COMMENT'主键'
)ENGINE=InnoDB AUTO_INCREMENT=1DEFAULT CHARSET=utf8mb4 COMMENT='爱好';
学⽣表
CREATE TABLE`t_test_student`
(
`id`bigint(20)unsigned NOT NULL AUTO_INCREMENT COMMENT'⾃增id',
`class_id`bigint(20)unsigned NOT NULL COMMENT'班级id',
`hobby_id`bigint(20)unsigned NOT NULL COMMENT'兴趣班id',
`age`int(11)unsigned NOT NULL COMMENT'年龄',
`last_attend_time`timestamp NULL DEFAULT NULL COMMENT'最后⼀次参加兴趣班时间',
`create_time`timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT'创建时间',
`modify_time`timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT'修改时间', PRIMARY KEY(`id`)COMMENT'主键'
)ENGINE=InnoDB AUTO_INCREMENT=1DEFAULT CHARSET=utf8mb4 COMMENT='学⽣表';
模型⽐较简单,这次的案例主要涉及学⽣相关事项;学⽣:班级=1:1 , 学⽣:爱好=1:1。
为了后续扩展相关案例⽐较⽅便,搭建了⼀个springboot+mybatis plus 的⼀个案例,做数据填充跟持久层数据交互。
2.代码程序相关
1. 基础测试引导类 :cn.lcy.stream#BaseTransactionTest
@Slf4j
@Rollback(value =true)
@SpringBootTest(classes = StreamApplication.class)
public class BaseTransactionTest extends AbstractTransactionalTestNGSpringContextTests {
@Test
public void testEnv()throws InterruptedException {
Thread.sleep(2000L);
System.out.println("test env running");
}
}
2. cn.lcy.stream.demo1#StreamApplicationTests extends BaseTransactionTest
数据初始化,⽅便后续测试。
@BeforeClass(description ="初始化数据")
public void initData(){
Long classId =10L;
Long hobbyId =100L;
//create students;
ArrayList<StudentDO> firstConditionHobbyAndClass =new ArrayList<>();
for(int i =0; i <10; i++){
StudentDO studentDO =new StudentDO();
studentDO.setClassId(classId + Int(5));
studentDO.setHobbyId(hobbyId + Int(5));
studentDO.Int(18));
studentDO.w().Int(10)).toDate());
firstConditionHobbyAndClass.add(studentDO);
}
studentDataSupport.saveBatch(firstConditionHobbyAndClass);
}
case 1 :
从简单开始,第⼀个案例是根据⼀个字段或者多个字段对List等进⾏排序。
@Test(description ="case1 : 根据字段排序")
public void sortTest(){
//当前的全量学⽣
List<StudentDO> requestData = studentDataSupport.list();
List<StudentDO> sortList = requestData.stream().sorted(
Comparatorparing(StudentDO::getAge)//⾸先根据age排序
.thenComparing(StudentDO::getHobbyId).reversed()//其次根据id编号排序).List());
List<StudentDO> sortList2 = requestData.stream().sorted(Comparatorparing(StudentDO::getAge)) .List());
log.info("sortList response = {} ", sortList);
log.info("sortList2 response = {} ", sortList);
}
case 2 :
下⾯是⼀个分组的归并;
当groupBy的字段于list中的元素重复时,使⽤List进⾏装载。
⽬前发现两种⽐较好⽤的⽅式,分别是stream与guava中Multimaps。
简单测试了下,10万数据量下两种⽅法耗时对于业务开发可以忽略不计。
@Test(description ="case2 : 根据hobbyId对students进⾏分组,如果hobbyId重复,则使⽤集合进⾏装填;") public void extractedByHobbyId(){
//当前的全量学⽣
List<StudentDO> requestData = studentDataSupport.list();
StopWatch stopWatch =new StopWatch();
stopWatch.start();
//test stream
Map<Long/*hobby_id*/, List<StudentDO>/*hobby_id对应的students*/> result = requestData.stream() .collect(
StudentDO::getHobbyId,// key wrapper
s ->{// value wrapper
List<StudentDO> list =new ArrayList<>();
list.add(s);
return list;
},
(List<StudentDO> value1, List<StudentDO> value2)->{//mergeFunc
value1.addAll(value2);
return value1;
}
)
);
stopWatch.stop();
log.info("stream : elapsed[{}]", Time());
log.info("stream : size[{}]", result.size());// elapsed[27]
stopWatch.start();
//test Multimapsgroupby分组
ImmutableListMultimap<Long, StudentDO> index = Multimaps.index(requestData, StudentDO::getHobbyId); log.info("guava : elapsed[{}]", Time());//elapsed[58]
}
case 3:
根据condition进⾏分组,感觉蛮好⽤但是⽐较冷僻的⽅法。
@Test(description ="case3 : 根据表达式进⾏分组")
public void testCondition(){
List<StudentDO> requestData = studentDataSupport.list();
Map<Boolean/*true 有兴趣班*/, List<StudentDO>> haveHobbyStudents =
requestData.stream().upingBy(e -> e.getHobbyId()>0));
}
case 4:
重点,根据字段进⾏分组,对于分组后的数据进⾏排序。
题设:根据hobby_id进⾏分组,随后对分组后的数据选取last_attend_time字段最⼤的记录。
@Test(description ="case4 : 根据字段分组随后进⾏排序并选取top1或者topN")
public void groupAndThenSort(){
//当前的全量学⽣
List<StudentDO> requestData = studentDataSupport.list();
//⽅法⼀: 先分组,随后从组内寻【指定】数据
Collector<StudentDO,?, Optional<StudentDO>> sortFunction = ducing((c1, c2)-> c1.getAge()> c2.getAge()? c1 : c2);
Map<Long/*hobby_id*/, StudentDO> groupAndSorting1 = requestData.stream()
.upingBy(StudentDO::getHobbyId,// 先根据HobbyId分组
//⽅法⼆: 当key冲突时,以怎样的⽅式保留唯⼀key相关的数据
Map<Long/*hobby_id*/, StudentDO> groupAndSorting2 = requestData.stream()
.
Map(StudentDO::getHobbyId, Function.identity(),(c1, c2)-> c1.getAge()> c2.getAge()? c1 : c2));// 随后排序//case 3: 类似的sql
/**
* SELECT *
* FROM (SELECT id,
* hobby_id,
* class_id,
* last_attend_time,
* IF(@bak = hobby_id, @rounum := @rounum + 1, @rounum := 1) AS
* row_number,
* @bak := hobby_id
* FROM (SELECT id,
* hobby_id,
* class_id,
* last_attend_time
* FROM t_test_student
* ORDER BY hobby_id ASC,
* last_attend_time DESC) a,
* (SELECT @rounum := 0,
* @bak := 0) b) c
* WHERE c.row_number < 2
*/
}
groupAndSorting1 是做groupby随后进⾏andThen调⽤⼀个func,func中进⾏stream reduce计算。
groupAndSorting2 是将groupBy⾄⼀个map中的思路,其中做处理的⽅式主要表述在key重复的时候,如何选取。
SQL的⽅式⽐较通⽤,但同样写法与受众度⽐较⼩众,我们分拆来理解下。
SQL 解释:总体思路是想数据先以期望得⽅式进⾏排序,随后添加相关计数器,对出现的有序数据进⾏计数。因为原数据已拍过序,所有计数器约⼤的数据说明越在尾端。
1. 当前语句很简单的根据hobby_id 与 last_attend_time 进⾏排序,这⼀步得到的结果已经得到了相关期望顺序。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论