【推荐】steam流太难用了看看JDFrame

0、简介

由于经常记不住stream的一些API每次要复制来复制去并且又长又臭,想要更加语意化的api,于是想到了以前写大数据Spark pandnas 等DataFrame模型时的API, 然后发现其实也存在java的JVM层的DataFrame模型比如 tablesaw,joinery

但是他们得硬编码去指定字段名,这对于有代码洁癖的人实在难以忍受,而且我只是简单统计下数据,我想在一些场景下能不能使用匿名函数去指定的字段处理去处理,于是便有了这个

一个jvm层级的仿DataFrame工具,语意化和简化java8的stream流式处理工具

1、快速开始

1.1、引入依赖

xml 复制代码
<dependency>
    <groupId>io.github.burukeyou</groupId>
    <artifactId>jdframe</artifactId>
    <version>0.0.1</version>
</dependency>

1.2、案例

统计每个学校的里学生年龄不为空并且年龄在9到16岁间的合计分数,并且获取合计分前2名的学校

java 复制代码
    static List<Student> studentList = new ArrayList<>();

    static {
        studentList.add(new Student(1,"a","一中","一年级",11, new BigDecimal(1)));
        studentList.add(new Student(2,"a","一中","一年级",11, new BigDecimal(1)));
        studentList.add(new Student(3,"b","一中","三年级",12, new BigDecimal(2)));
        studentList.add(new Student(4,"c","二中","一年级",13, new BigDecimal(3)));
        studentList.add(new Student(5,"d","二中","一年级",14, new BigDecimal(4)));
        studentList.add(new Student(6,"e","三中","二年级",14, new BigDecimal(5)));
        studentList.add(new Student(7,"e","三中","二年级",15, new BigDecimal(5)));
    }

// 
SDFrame<FI2<String, BigDecimal>> sdf2 = SDFrame.read(studentList)
        .whereNotNull(Student::getAge)
        .whereBetween(Student::getAge,9,16)
        .groupBySum(Student::getSchool, Student::getScore)
        .sortDesc(FI2::getC2)
        .cutFirst(2);

sdf2.show();

输出信息;

java 复制代码
c1	       c2	
三中	10	
二中	7 	
java 复制代码
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {

    private int id;
    private String name;
    private String school;
    private String level;
    private Integer age;
    private BigDecimal score;

    private Integer rank;

    public Student(String level, BigDecimal score) {
        this.level = level;
        this.score = score;
    }

    public Student(int id, String name, String school, String level, Integer age, BigDecimal score) {
        this.id = id;
        this.name = name;
        this.school = school;
        this.level = level;
        this.age = age;
        this.score = score;
    }
}

2、API案例

2.1、矩阵信息相关

java 复制代码
        void show(int n); // 打印矩阵信息到控制台
        List<String> columns();   // 获取矩阵的表头字段名
        List<R> col(Function<T, R> function);   // 获取矩阵某一列值
        T head();                   // 获取第一个元素
        List<T> head(int n);          // 获取前n个元素
        T tail();                       // 获取最后一个元素
        List<T> tail(int n);            // 获取后n个元素

2.2、筛选相关

java 复制代码
SDFrame.read(studentList)
        .whereBetween(Student::getAge,3,6) // 过滤年龄在[3,6]岁的
        .whereBetweenR(Student::getAge,3,6) // 过滤年龄在(3,6]岁的, 不含3岁
        .whereBetweenL(Student::getAge,3,6)      // 过滤年龄在[3,6)岁的, 不含6岁
        .whereNotNull(Student::getName) // 过滤名字不为空的数据, 兼容了空字符串''的判断
        .whereGt(Student::getAge,3)    // 过滤年龄大于3岁
        .whereGe(Student::getAge,3)   // 过滤年龄大于等于3岁
        .whereLt(Student::getAge,3)  // 过滤年龄小于3岁的
        .whereIn(Student::getAge, Arrays.asList(3,7,8)) // 过滤年龄为3岁 或者7岁 或者 8岁的数据
        .whereNotIn(Student::getAge, Arrays.asList(3,7,8)) // 过滤年龄不为为3岁 或者7岁 或者 8岁的数据
        .whereEq(Student::getAge,3) // 过滤年龄等于3岁的数据
        .whereNotEq(Student::getAge,3) // 过滤年龄不等于3岁的数据
        .whereLike(Student::getName,"jay") // 模糊查询,等价于 like "%jay%"
        .whereLikeLeft(Student::getName,"jay") // 模糊查询,等价于 like "jay%"
        .whereLikeRight(Student::getName,"jay"); // 模糊查询,等价于 like "%jay"

2.3、汇总相关

java 复制代码
JDFrame<Student> frame = JDFrame.read(studentList);
Student s1 = frame.max(Student::getAge);// 获取年龄最大的学生
Integer s2  = frame.maxValue(Student::getAge);      // 获取学生里最大的年龄
Student s3 = frame.min(Student::getAge);// 获取年龄最小的学生
Integer s4  = frame.minValue(Student::getAge);      // 获取学生里最小的年龄
BigDecimal s5 = frame.avg(Student::getAge); // 获取所有学生的年龄的平均值
BigDecimal s6 = frame.sum(Student::getAge); // 获取所有学生的年龄合计
MaxMin<Student> s7 = frame.maxMin(Student::getAge); // 同时获取年龄最大和最小的学生
MaxMin<Integer> s8 = frame.maxMinValue(Student::getAge); // 同时获取学生里最大和最小的年龄

2.4、去重相关

原生steam只支持对象去重,不支持按特定字段去重

java 复制代码
List<Student> std = null;
std = SDFrame.read(studentList).distinct().toLists(); // 根据对象hashCode去重
std = SDFrame.read(studentList).distinct(Student::getSchool).toLists(); // 根据学校名去重
std = SDFrame.read(studentList).distinct(e -> e.getSchool() + e.getLevel()).toLists(); // 根据学校名拼接级别去重复
std =SDFrame.read(studentList).distinct(Student::getSchool).distinct(Student::getLevel).toLists(); // 先根据学校名去除重复再根据级别去除重复

2.5、简单分组聚合相关

类似sql的 group by语义 简化处理分组和聚合的逻辑, 如果用原生stream需要写可能一大串逻辑.

java 复制代码
JDFrame<Student> frame = JDFrame.from(studentList);
// 等价于 select school,sum(age) ... group by school
List<FI2<String, BigDecimal>> a = frame.groupBySum(Student::getSchool, Student::getAge).toLists();
// 等价于 select school,max(age) ... group by school
List<FI2<String, Integer>> a2 = frame.groupByMaxValue(Student::getSchool, Student::getAge).toLists();
//  与 groupByMaxValue 含义一致,只是返回的是最大的值对象
List<FI2<String, Student>> a3 = frame.groupByMax(Student::getSchool, Student::getAge).toLists();
// 等价于 select school,min(age) ... group by school
List<FI2<String, Integer>> a4 = frame.groupByMinValue(Student::getSchool, Student::getAge).toLists();
// 等价于 select school,count(*) ... group by school
List<FI2<String, Long>> a5 = frame.groupByCount(Student::getSchool).toLists();
// 等价于 select school,avg(age) ... group by school
List<FI2<String, BigDecimal>> a6 = frame.groupByAvg(Student::getSchool, Student::getAge).toLists();

// 等价于 select school,sum(age),count(age) group by school
List<FI3<String, BigDecimal, Long>> a7 = frame.groupBySumCount(Student::getSchool, Student::getAge).toLists();

// (二级分组)等价于 select school,level,sum(age),count(age) group by school,level
List<FI3<String, String, BigDecimal>> a8 = frame.groupBySum(Student::getSchool, Student::getLevel, Student::getAge).toLists();

// (三级分组)等价于 select school,level,name,sum(age),count(age) group by school,level,name
List<FI4<String, String, String, BigDecimal>> a9 = frame.groupBySum(Student::getSchool, Student::getLevel, Student::getName, Student::getAge).toLists();

2.6、排序相关

简化原生stream的排序方式,直接指定字段即可,不用使用Comparator还要去关注升序还是降序

java 复制代码
        // 等价于 order by age desc
        SDFrame.read(studentList).sortDesc(Student::getAge);
        //  等价于 order by age desc, level asc
        SDFrame.read(studentList).sortDesc(Student::getAge).sortAsc(Student::getLevel);
        // 等价于 order by age asc
        SDFrame.read(studentList).sortAsc(Student::getAge);
        // 使用Comparator 排序
        SDFrame.read(studentList).sortAsc(Comparator.comparing(e -> e.getLevel() + e.getId()));

2.7、连接矩阵相关

API列表

java 复制代码
        append(T t);                    // 等价于集合 add
        union(IFrame<T> other);         //  等价于集合 addAll
        join(IFrame<K> other, JoinOn<T,K> on, Join<T,K,R> join);   // 等价于 sql内连接
        leftJoin(IFrame<K> other, JoinOn<T,K> on, Join<T,K,R> join);   // 等价于sql左连接,如果左连接失败,K值为null,需手动判断
        rightJoin(IFrame<K> other, JoinOn<T,K> on, Join<T,K,R> join);    // 等价于sql右连接,如果右连接失败,T值为null,需手动判断

内连接例子:

java 复制代码
        System.out.println("======== 矩阵1 =======");

        SDFrame<Student> sdf = SDFrame.read(studentList);

        sdf.show(20);

        // 获取学生年龄在9到16岁的学学校合计分数最高的前10名
        SDFrame<FI2<String, BigDecimal>> sdf2 = SDFrame.read(studentList)
                .whereNotNull(Student::getAge)
                .whereBetween(Student::getAge,9,16)
                .groupBySum(Student::getSchool, Student::getScore)
                .sortDesc(FI2::getC2)
                .cutFirst(10);

        System.out.println("======== 矩阵2 =======");
        sdf2.show();

        SDFrame<UserInfo> frame = sdf.join(sdf2, (a, b) -> a.getSchool().equals(b.getC1()), (a, b) -> {
            UserInfo userInfo = new UserInfo();
            userInfo.setKey1(a.getSchool());
            userInfo.setKey2(b.getC2().intValue());
            userInfo.setKey3(String.valueOf(a.getId()));
            return userInfo;
        });

        System.out.println("======== 连接后结果 =======");
        frame.show(5);

打印信息:

java 复制代码
======== 矩阵1 =======
id	name	school	level	age	score	rank	
1 	a   	一中    	一年级  	11 	1    	    	
2 	a   	一中    	一年级  	11 	1    	    	
3 	b   	一中    	一年级  	12 	2    	    	
4 	c   	二中    	一年级  	13 	3    	    	
5 	d   	二中    	一年级  	14 	4    	    	
6 	e   	三中    	二年级  	14 	5    	    	
7 	e   	三中    	二年级  	15 	5    	    	

======== 矩阵2 =======
c1	c2	
三中	10	
二中	7 	
一中	4 	

======== 连接后结果 =======
key1	key2	key3	key4	
一中  	4   	1   	    	
一中  	4   	2   	    	
一中  	4   	3   	    	
二中  	7   	4   	    	
二中  	7   	5   

类似于

sql 复制代码
select a.*,b.* from sdf a inner join sdf2 b on  a.school = b.c1

2.8、其他

百分数转换

java 复制代码
// 等价于 select round(score*100,2) from student
SDFrame<Student> map2 = SDFrame.read(studentList).mapPercent(Student::getScore, Student::setScore,2);

分区

将每个5个元素分成一个小集合,用于将大任务拆成小任务

java 复制代码
List<List<Student>> t = SDFrame.read(studentList).partition(5).toLists();

生成序号

按照age排序,然后根据当前顺序生成排序号到rank字段 (序号从0开始)

java 复制代码
List<Student> students = SDFrame.read(studentList)
    .sortDesc(Student::getAge)
    .addSortNoCol(Student::setRank)
    .toLists();

生成排名号

按照age降序排序,然后根据当前顺序生成排名号到rank字段 (排名从0开始)

  • 与序号不同的是, 排名是如果值相同认为排名一样。
ini 复制代码
SDFrame<Student> df = SDFrame.read(studentList).addRankingSameColDesc(Student::getAge, Student::setRank);
df.show(20);

输出信息

js 复制代码
id	name	school	level	age	score	rank	
7 	e   	三中    	二年级  	15 	5    	1   	
5 	d   	二中    	一年级  	14 	4    	2   	
6 	e   	三中    	二年级  	14 	5    	2   	
4 	c   	二中    	一年级  	13 	3    	3   	
3 	b   	一中    	一年级  	12 	2    	4   	
1 	a   	一中    	一年级  	11 	1    	5   	
2 	a   	一中    	一年级  	11 	1    	5   	

补充条目

1、补充缺失的学校条目

ini 复制代码
// 所有需要的条目
List<String> allDim = Arrays.asList("一中","二中","三中","四中");
SDFrame.read(studentList).replenish(Student::getSchool,allDim,(school) -> new Student(school)).show();

输出

js 复制代码
id	name	school	level	age	score	rank	
1 	a   	一中    	一年级  	11 	1    	    	
2 	a   	一中    	一年级  	11 	1    	    	
3 	b   	一中    	一年级  	12 	2    	    	
4 	c   	二中    	一年级  	13 	3    	    	
5 	d   	二中    	一年级  	14 	4    	    	
6 	e   	三中    	二年级  	14 	5    	    	
7 	e   	三中    	二年级  	15 	5    	    	
0 	    	四中  

2、分组补充缺失的条目

按照学校进行分组, 汇总所有年级allDim. 然后与allDim比较补充每个分组内缺失的年级,缺失的年级按照ReplenishFunction生成补充条目

java 复制代码
SDFrame.read(studentList).replenish(Student::getSchool,Student::getLevel,(school,level) -> new Student(school,level)).show(30);

输出

js 复制代码
id	name	school	level	age	score	rank	
1 	a   	一中    	一年级  	11 	1    	    	
2 	a   	一中    	一年级  	11 	1    	    	
3 	b   	一中    	三年级  	12 	2    	    	
0 	    	一中    	二年级  	   	     	    	
4 	c   	二中    	一年级  	13 	3    	    	
5 	d   	二中    	一年级  	14 	4    	    	
0 	    	二中    	三年级  	   	     	    	
0 	    	二中    	二年级  	   	     	    	
6 	e   	三中    	二年级  	14 	5    	    	
7 	e   	三中    	二年级  	15 	5    	    	
0 	    	三中    	一年级  	   	     	    	
0 	    	三中    	三年级 

应用场景举例: 要求计算近两年每个月的数据,但是数据的年月可能不全,这时就补充缺失的年月数据作为结果一起返回

最后

代码地址

Maven依赖地址

还有一些api没有列举出来使用的比较少 主要是对逻辑的封装和语意化,如果还有哪些逻辑和api可以扩展可以在评论区留下你的想法。

相关推荐
一只叫煤球的猫3 小时前
写代码很6,面试秒变菜鸟?不卖课,面试官视角走心探讨
前端·后端·面试
bobz9654 小时前
tcp/ip 中的多路复用
后端
bobz9654 小时前
tls ingress 简单记录
后端
皮皮林5515 小时前
IDEA 源码阅读利器,你居然还不会?
java·intellij idea
你的人类朋友5 小时前
什么是OpenSSL
后端·安全·程序员
bobz9655 小时前
mcp 直接操作浏览器
后端
前端小张同学8 小时前
服务器部署 gitlab 占用空间太大怎么办,优化思路。
后端
databook8 小时前
Manim实现闪光轨迹特效
后端·python·动效
武子康9 小时前
大数据-98 Spark 从 DStream 到 Structured Streaming:Spark 实时计算的演进
大数据·后端·spark
该用户已不存在9 小时前
6个值得收藏的.NET ORM 框架
前端·后端·.net