🙏废话不多说系列,直接开整🙏
一、背景
官方介绍:burukeyou的私房工具, 由于经常记不住stream的一些api每次要复制来复制去,想要更加语意化的api;于是想到了以前写大数据Spark pandnas 等DataFrame模型时的api, 然后发现其实也存在java的JVM层的DataFrame模型比如 tablesaw,joinery 但是他们得硬编码去指定字段名,这对于有代码洁癖的burukeyou实在难以忍受,而且我只是简单统计下数据,我想在一些场景下能不能使用匿名函数去指定的字段处理去处理,于是便有了这个一个jvm层级的仿DataFrame工具,语意化和简化java8的stream流式处理工具;
简要描述-关键字:① 简化 JDK8 自带的 stream 流式处理接口;② 基于 JVM 的 仿照 DataFrame 工具。
二、快速开始
(1)引入 JDFrame 框架 maven 依赖
<!-- [核心] JDFrame 框架 -->
<dependency>
<groupId>io.github.burukeyou</groupId>
<artifactId>jdframe</artifactId>
<version>0.0.2</version>
</dependency>
<!-- 简化对象 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
(2)相关案例
// 获取学生年龄在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);
(3)完整案例(亲测有效,一目了然不骗人)
【1】数据对象DO准备
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private Integer 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(Integer 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】筛选
@Test
void findDemo() {
System.out.println("筛选相关:(可自行调整筛选输出条件测试哈)");
SDFrame<Student> data = SDFrame.read(studentList)
//.whereBetween(Student::getAge, 11, 15) // 过滤年龄在[11,15]岁的
//.whereBetweenR(Student::getAge, 11, 15) // 过滤年龄在(11,15]岁的, 不含11岁
//.whereBetweenL(Student::getAge, 11, 15) // 过滤年龄在[11,15)岁的, 不含15岁
//.whereNotNull(Student::getName) // 过滤名字不为空的数据, 兼容了空字符串''的判断
//.whereGt(Student::getAge, 12) // 过滤年龄大于12岁
.whereGe(Student::getAge, 14) // 过滤年龄大于等于14岁
//.whereLt(Student::getAge, 12) // 过滤年龄小于12岁的
//.whereIn(Student::getAge, Arrays.asList(3, 11, 14)) // 过滤年龄为3岁 或者11岁 或者 14岁的数据
//.whereNotIn(Student::getAge, Arrays.asList(3, 11, 14)) // 过滤年龄不为为3岁 或者11岁 或者 14岁的数据
//.whereEq(Student::getAge, 11) // 过滤年龄等于11岁的数据
//.whereNotEq(Student::getAge, 11) // 过滤年龄不等于11岁的数据
//.whereLike(Student::getName, "jay") // 模糊查询,等价于 like "%jay%"
//.whereLikeLeft(Student::getName, "jay") // 模糊查询,等价于 like "jay%"
//.whereLikeRight(Student::getName, "jay") // 模糊查询,等价于 like "%jay"
;
// 输出格式1:按照 JDK 的形式输出
data.forEach(System.out::println);
// 输出格式2:按照 JDFrame 的形式(数据库表)输出
//data.show();
// 输出格式2.1 :输出指定需要的数据
data.show(1);// 输出从0~1行数据 data.show(0) 表示输出 列字段名
}
测试结果:
筛选相关:(可自行调整筛选输出条件测试哈)
Student(id=5, name=d, school=二中, level=一年级, age=14, score=4, rank=null)
Student(id=6, name=e, school=三中, level=二年级, age=14, score=5, rank=null)
Student(id=7, name=e, school=三中, level=二年级, age=15, score=5, rank=null)
id name school level age score rank
5 d 二中 一年级 14 4
【3】统计
@Test
void analysis() {
JDFrame<Student> frame = JDFrame.read(studentList);
System.out.println("获取年龄最大的学生: " + frame.max(Student::getAge));
System.out.println("获取学生里最大的年龄: " + frame.maxValue(Student::getAge));
System.out.println("获取年龄最小的学生: " + frame.min(Student::getAge));
System.out.println("获取学生里最小的年龄: " + frame.minValue(Student::getAge));
System.out.println("获取所有学生的年龄的平均值: " + frame.avg(Student::getAge)); // 获取所有学生的年龄的平均值
System.out.println("获取所有学生的年龄合计: " + frame.sum(Student::getAge)); // 获取所有学生的年龄合计
System.out.println("同时获取年龄最大和最小的学生: " + frame.maxMin(Student::getAge)); // 同时获取年龄最大和最小的学生
System.out.println("同时获取学生里最大和最小的年龄: " + frame.maxMinValue(Student::getAge)); // 同时获取学生里最大和最小的年龄
}
【4】去重
原生steam只支持对象去重,不支持按特定字段去重:
@Test
void distinctDemo() {
List<Student> std;
std = SDFrame.read(studentList).distinct().toLists(); // 根据对象hashCode去重
System.out.println("根据对象hashCode去重: " + std);
std = SDFrame.read(studentList).distinct(Student::getSchool).toLists(); // 根据学校名去重
System.out.println("根据学校名去重: " + std);
std = SDFrame.read(studentList).distinct(e -> e.getSchool() + e.getLevel()).toLists(); // 根据学校名拼接级别去重复
System.out.println("根据学校名拼接级别去重复: " + std);
std =SDFrame.read(studentList).distinct(Student::getSchool).distinct(Student::getLevel).toLists(); // 先根据学校名去除重复再根据级别去除重复
System.out.println("先根据学校名去除重复再根据级别去除重复: " + std);
}
结果如下:
根据对象hashCode去重: [Student(id=1, name=a, school=一中, level=一年级, age=11, score=1, rank=null), Student(id=2, name=a, school=一中, level=一年级, age=11, score=1, rank=null), Student(id=3, name=jay, school=一中, level=三年级, age=12, score=2, rank=null), Student(id=4, name=c, school=二中, level=一年级, age=13, score=3, rank=null), Student(id=5, name=d, school=二中, level=一年级, age=14, score=4, rank=null), Student(id=6, name=e, school=三中, level=二年级, age=14, score=5, rank=null), Student(id=7, name=e, school=三中, level=二年级, age=15, score=5, rank=null)]
根据学校名去重: [Student(id=1, name=a, school=一中, level=一年级, age=11, score=1, rank=null), Student(id=6, name=e, school=三中, level=二年级, age=14, score=5, rank=null), Student(id=4, name=c, school=二中, level=一年级, age=13, score=3, rank=null)]
根据学校名拼接级别去重复: [Student(id=1, name=a, school=一中, level=一年级, age=11, score=1, rank=null), Student(id=3, name=jay, school=一中, level=三年级, age=12, score=2, rank=null), Student(id=6, name=e, school=三中, level=二年级, age=14, score=5, rank=null), Student(id=4, name=c, school=二中, level=一年级, age=13, score=3, rank=null)]
先根据学校名去除重复再根据级别去除重复: [Student(id=1, name=a, school=一中, level=一年级, age=11, score=1, rank=null), Student(id=6, name=e, school=三中, level=二年级, age=14, score=5, rank=null)]
【5】简单分组聚合
类似sql的 group by语义 简化处理分组和聚合的逻辑, 如果用原生stream需要写可能一大串逻辑.
@Test
void simpleGroup() {
JDFrame<Student> frame = JDFrame.read(studentList);
// 等价于 select school,sum(age) ... group by school
List<FI2<String, BigDecimal>> a = frame.groupBySum(Student::getSchool, Student::getAge).toLists();
a.forEach(item -> System.out.println("a 学校:" + item.getC1() + "\t年龄:" + item.getC2()));
// 等价于 select school,max(age) ... group by school
List<FI2<String, Integer>> a2 = frame.groupByMaxValue(Student::getSchool, Student::getAge).toLists();
a2.forEach(item -> System.out.println("a2学校:" + item.getC1() + "\t最大年龄:" + item.getC2()));
// 与 groupByMaxValue 含义一致,只是返回的是最大的值对象
List<FI2<String, Student>> a3 = frame.groupByMax(Student::getSchool, Student::getAge).toLists();
a3.forEach(item -> System.out.println("a3学校:" + item.getC1() + "\t最大年龄的学生信息:" + item.getC2()));
// 等价于 select school,min(age) ... group by school
List<FI2<String, Integer>> a4 = frame.groupByMinValue(Student::getSchool, Student::getAge).toLists();
a4.forEach(item -> System.out.println("a4学校:" + item.getC1() + "\t最小年龄:" + item.getC2()));
// 等价于 select school,count(*) ... group by school
List<FI2<String, Long>> a5 = frame.groupByCount(Student::getSchool).toLists();
a5.forEach(item -> System.out.println("a5学校:" + item.getC1() + "\t学生人数:" + item.getC2()));
// 等价于 select school,avg(age) ... group by school
List<FI2<String, BigDecimal>> a6 = frame.groupByAvg(Student::getSchool, Student::getAge).toLists();
a6.forEach(item -> System.out.println("a6学校:" + item.getC1() + "\t学生平均年龄:" + item.getC2()));
// 等价于 select school,sum(age),count(age) group by school
List<FI3<String, BigDecimal, Long>> a7 = frame.groupBySumCount(Student::getSchool, Student::getAge).toLists();
a7.forEach(item -> System.out.println("a7学校:" + item.getC1() + "\t年龄总和:" + item.getC2() + "\t统计年龄人数: " + item.getC3()));
// (二级分组)等价于 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();
a8.forEach(item -> System.out.println("a8[二级分组]学校:" + item.getC1() + "\t年级:" + item.getC2() + "\t统计此组年龄总和: " + item.getC3()));
// (三级分组)等价于 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();
a9.forEach(item -> System.out.println("a9[三级分组]学校:" + item.getC1() + "\t年级:" + item.getC2() + "\t名称: " + item.getC3() + "\t此组年龄总和: " + item.getC4()));
}
结果展示:
a 学校:一中 年龄:34
a 学校:二中 年龄:27
a 学校:三中 年龄:29
a2学校:一中 最大年龄:12
a2学校:二中 最大年龄:14
a2学校:三中 最大年龄:15
a3学校:一中 最大年龄的学生信息:Student(id=3, name=jay, school=一中, level=三年级, age=12, score=2, rank=null)
a3学校:二中 最大年龄的学生信息:Student(id=5, name=d, school=二中, level=一年级, age=14, score=4, rank=null)
a3学校:三中 最大年龄的学生信息:Student(id=7, name=e, school=三中, level=二年级, age=15, score=5, rank=null)
a4学校:一中 最小年龄:11
a4学校:二中 最小年龄:13
a4学校:三中 最小年龄:14
a5学校:一中 学生人数:3
a5学校:二中 学生人数:2
a5学校:三中 学生人数:2
a6学校:一中 学生平均年龄:11.33
a6学校:二中 学生平均年龄:13.50
a6学校:三中 学生平均年龄:14.50
a7学校:一中 年龄总和:34 统计年龄人数: 3
a7学校:二中 年龄总和:27 统计年龄人数: 2
a7学校:三中 年龄总和:29 统计年龄人数: 2
a8[二级分组]学校:一中 年级:一年级 统计此组年龄总和: 22
a8[二级分组]学校:一中 年级:三年级 统计此组年龄总和: 12
a8[二级分组]学校:二中 年级:一年级 统计此组年龄总和: 27
a8[二级分组]学校:三中 年级:二年级 统计此组年龄总和: 29
a9[三级分组]学校:一中 年级:一年级 名称: a 此组年龄总和: 22
a9[三级分组]学校:一中 年级:三年级 名称: jay 此组年龄总和: 12
a9[三级分组]学校:二中 年级:一年级 名称: c 此组年龄总和: 13
a9[三级分组]学校:二中 年级:一年级 名称: d 此组年龄总和: 14
a9[三级分组]学校:三中 年级:二年级 名称: e 此组年龄总和: 29
【6】排序相关
简化原生stream的排序方式,直接指定字段即可,不用使用Comparator还要去关注升序还是降序
⚠特别注意⚠:① 不支持中文排序;② 多字段排序失败,指挥按照最后一个排序写法进行排序;
@Test
void orderList() {
// 注意:中文不支持排序。
// 等价于 order by age desc
System.out.println("按照年龄降序:");
SDFrame.read(studentList).sortDesc(Student::getAge).show();
// 等价于 order by age desc, level asc
System.out.println("按照年龄降序-年级升序:【JDFrame严重BUG】不支持多字段排序,只识别最后一个字段的sortXXX(YYYY)");
SDFrame.read(studentList).sortDesc(Student::getAge).sortAsc(Student::getLevel).show();
//SDFrame.read(studentList).sortDesc(Student::getAge).sortAsc(Student::getScore).show();
// 等价于 order by age asc
System.out.println("按照年龄升序:");
SDFrame.read(studentList).sortAsc(Student::getAge).show();
// 使用Comparator 排序
System.out.println("按照年纪升序:(比较器)");
SDFrame.read(studentList).sortAsc(Comparator.comparing(Student::getAge)).show();
}
测试结果:
按照年龄降序:
id name school level age score rank
7 e 三中 二年级 15 5
5 d 二中 一年级 14 4
6 e 三中 二年级 14 5
4 c 二中 一年级 13 3
3 jay 一中 三年级 12 2
1 a 一中 三年级 11 1
2 a 一中 一年级 11 1
按照年龄降序-年级升序:【JDFrame严重BUG】不支持多字段排序,只识别最后一个字段的sortXXX(YYYY)
id name school level age score rank
5 d 二中 一年级 14 4
4 c 二中 一年级 13 3
2 a 一中 一年级 11 1
3 jay 一中 三年级 12 2
1 a 一中 三年级 11 1
7 e 三中 二年级 15 5
6 e 三中 二年级 14 5
按照年龄升序:
id name school level age score rank
1 a 一中 三年级 11 1
2 a 一中 一年级 11 1
3 jay 一中 三年级 12 2
4 c 二中 一年级 13 3
5 d 二中 一年级 14 4
6 e 三中 二年级 14 5
7 e 三中 二年级 15 5
按照年纪升序:(比较器)
id name school level age score rank
1 a 一中 三年级 11 1
2 a 一中 一年级 11 1
3 jay 一中 三年级 12 2
4 c 二中 一年级 13 3
5 d 二中 一年级 14 4
6 e 三中 二年级 14 5
7 e 三中 二年级 15 5
【7】连接矩阵相关
// API 信息接口
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,需手动判断
内连接例子:
@Test
void joinMatlab() {
System.out.println("======== 矩阵1 =======");
SDFrame<Student> sdf = SDFrame.read(studentList);
sdf.show(20);// 显示从1~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();// 显示所有
// 类似于SQL种的 select a.*,b.* from sdf a inner join sdf2 b on a.school = b.c1
SDFrame<UserInfo> frame = sdf.join(sdf2, (a, b) -> a.getSchool().equals(b.getC1()), (a, b) -> {
UserInfo userInfo = new UserInfo();
userInfo.setSchoolName(a.getSchool());
userInfo.setSchoolScoreSum(b.getC2().intValue());
userInfo.setNumIndex(String.valueOf(a.getId()));
return userInfo;
});
System.out.println("======== 连接后结果 =======");
frame.show(100);// 显示从1~100行数据
}
@Data
@NoArgsConstructor
@AllArgsConstructor
class UserInfo {
private String schoolName;// 学校ID
private Integer schoolScoreSum;// 按学校分组的总分数
private String numIndex;// 序号
}
测试结果:
======== 矩阵1 =======
id name school level age score rank
1 a 一中 三年级 11 1
2 a 一中 一年级 11 1
3 jay 一中 三年级 12 2
4 c 二中 一年级 13 3
5 d 二中 一年级 14 4
6 e 三中 二年级 14 5
7 e 三中 二年级 15 5
======== 矩阵2 =======
c1 c2
三中 10
二中 7
一中 4
======== 连接后结果 =======
schoolName schoolScoreSum numIndex
一中 4 1
一中 4 2
一中 4 3
二中 7 4
二中 7 5
三中 10 6
三中 10 7
【8】百分数转换
@Test
void transferData() {
// 等价于 select round(score*100, 2) from student
SDFrame<Student> map2 = SDFrame.read(studentList).mapPercent(Student::getScore, Student::setScore,2);
map2.show();
}
// 输出结果
id name school level age score rank
1 a 一中 三年级 11 100.00
2 a 一中 一年级 11 100.00
3 jay 一中 三年级 12 200.00
4 c 二中 一年级 13 300.00
5 d 二中 一年级 14 400.00
6 e 三中 二年级 14 500.00
7 e 三中 二年级 15 500.00
【9】分区(发现问题-需要对编译环境进行额外设置)
@Test
void partitionList() {
// 将每个5个元素分成一个小集合,用于将大任务拆成小任务[使用 JDK8+ 自带的吧]
List<List<Student>> lists = SDFrame.read(studentList).partition(5).stream().toList();
// 遇到问题:Unable to make field private final java.util.ArrayList java.util.ArrayList$SubList.root accessible: module java.base does not "opens java.util" to unnamed module @1ce92674
// - 解决方案:(https://blog.csdn.net/qq_31532979/article/details/138070564)
}
@Test
void partitionList() {
// 将每个5个元素分成一个小集合,用于将大任务拆成小任务
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
int MAX_SEND = 4;// 按每3个一组分割
int limit = (list.size() + MAX_SEND - 1) / MAX_SEND; // 计算切分次数
//方法一:使用流遍历操作
List<List<Integer>> mgList = new ArrayList<>();
Stream.iterate(0, n -> n + 1).limit(limit)
.forEach(i -> mgList.add(list.stream().skip(i * MAX_SEND).limit(MAX_SEND)
.collect(Collectors.toList())));
System.out.println("JDK8+流式遍历操作 实现集合拆分:\n" + JSONUtil.toJsonPrettyStr(mgList));
//方法二:获取分割后的集合
List<List<Integer>> splitList = Stream.iterate(0, n -> n + 1).limit(limit).parallel()
.map(a -> list.stream().skip(a * MAX_SEND).limit(MAX_SEND).parallel().collect(Collectors.toList()))
.toList();
System.out.println("JDK8+实现集合拆分:\n" + JSONUtil.toJsonPrettyStr(splitList));
}
// 结果如下:
JDK8+流式遍历操作 实现集合拆分:
[
[
1,
2,
3,
4
],
[
5,
6,
7,
8
],
[
9,
10
]
]
JDK8+实现集合拆分:
[
[
1,
2,
3,
4
],
[
5,
6,
7,
8
],
[
9,
10
]
]
【10】生成序号
按照age排序,然后根据当前顺序生成排序号到rank字段 (序号从0开始)
@Test
void genOrderIndex() {
SDFrame<Student> students = SDFrame.read(studentList)
.sortDesc(Student::getAge)
.addSortNoCol(Student::setRank); // 写法一:默认设置字段序号列
//.addSortNoCol(i -> (Objects.isNull(i.getRank()) ? 0 : i.getRank()) + 1) // 写法二:自定义(见[自定义生成序号])
students.forEach(System.out::println);
}
// 写法一输出结果:
Student(id=7, name=e, school=三中, level=二年级, age=15, score=5, rank=0)
Student(id=5, name=d, school=二中, level=一年级, age=14, score=4, rank=1)
Student(id=6, name=e, school=三中, level=二年级, age=14, score=5, rank=2)
Student(id=4, name=c, school=二中, level=一年级, age=13, score=3, rank=3)
Student(id=3, name=jay, school=一中, level=三年级, age=12, score=2, rank=4)
Student(id=1, name=a, school=一中, level=三年级, age=11, score=1, rank=5)
Student(id=2, name=a, school=一中, level=一年级, age=11, score=1, rank=6)
// 【注意】如果是需要从自定义的序号开始,注意输出结果的不同!
// 写法二输出结果:两列(第一列为 student实体对象信息列,第二列 为自定义需要列)
// 如果需要获取 学生信息,需要再一次针对 students.getC1(),才能得到对应的学生集合信息。
自定义生成序号(排序起始值和跨度)
@Test
void genOrderIndex() {
SDFrame<FI2<Student, Integer>> fi2s = SDFrame.read(studentList)
.sortDesc(Student::getAge)
//.addSortNoCol(Student::setRank);
.addSortNoCol(i -> (Objects.isNull(i.getRank()) ? 0 : i.getRank()) + 1);
fi2s.forEach(item -> {
item.getC1().setRank(item.getC2());
System.out.println(item.getC1());
});
}
输出结果
Student(id=7, name=e, school=三中, level=二年级, age=15, score=5, rank=1)
Student(id=5, name=d, school=二中, level=一年级, age=14, score=4, rank=2)
Student(id=6, name=e, school=三中, level=二年级, age=14, score=5, rank=3)
Student(id=4, name=c, school=二中, level=一年级, age=13, score=3, rank=4)
Student(id=3, name=jay, school=一中, level=三年级, age=12, score=2, rank=5)
Student(id=1, name=a, school=一中, level=三年级, age=11, score=1, rank=6)
Student(id=2, name=a, school=一中, level=一年级, age=11, score=1, rank=7)
// fi2s.show(); 的输出结果
c1 c2
Student(id=7, name=e, school=三中, level=二年级, age=15, score=5, rank=null) 1
Student(id=5, name=d, school=二中, level=一年级, age=14, score=4, rank=null) 2
Student(id=6, name=e, school=三中, level=二年级, age=14, score=5, rank=null) 3
Student(id=4, name=c, school=二中, level=一年级, age=13, score=3, rank=null) 4
Student(id=3, name=jay, school=一中, level=三年级, age=12, score=2, rank=null) 5
Student(id=1, name=a, school=一中, level=三年级, age=11, score=1, rank=null) 6
Student(id=2, name=a, school=一中, level=一年级, age=11, score=1, rank=null) 7
【11】生成排名号
按照age降序排序,然后根据当前顺序生成排名号到rank字段 (排名从0开始)与序号不同的是, 排名是如果值相同认为排名一样。
@Test
void genOrder() {
// 按照age降序排序,然后根据当前顺序生成排名号到rank字段 (排名从0开始)与序号不同的是, 排名是如果值相同认为排名一样。
SDFrame<Student> df = SDFrame.read(studentList).addRankingSameColDesc(Student::getAge, Student::setRank);
df.show(20);
}
输出结果
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 jay 一中 三年级 12 2 4
1 a 一中 三年级 11 1 5
2 a 一中 一年级 11 1 5
【12】补充条目
// 1. 补充缺失的学校条目
@Test
void addOtherInfo() {
// 所有需要的学校条目
List<String> allDim = Arrays.asList("一中", "二中", "三中", "四中", "五中");
// 根据学校字段和allDim比较去补充缺失的条目, 缺失的学校按照ReplenishFunction生成补充条目作为结果一起返回
SDFrame<Student> studentSDFrame = SDFrame.read(studentList).replenish(Student::getSchool, allDim, (school) -> {
Student student = new Student();
student.setSchool(school);
return student;
});
studentSDFrame.show();
// 输出Student对象信息:Student(id=null, name=null, school=四中, level=null, age=null, score=null, rank=null)
studentSDFrame.stream().filter(item -> item.getSchool().equals("四中")).toList().forEach(System.out::println);
}
// 输出结果:
id name school level age score rank
1 a 一中 三年级 11 1
2 a 一中 一年级 11 1
3 jay 一中 三年级 12 2
4 c 二中 一年级 13 3
5 d 二中 一年级 14 4
6 e 三中 二年级 14 5
7 e 三中 二年级 15 5
五中
四中
Student(id=null, name=null, school=四中, level=null, age=null, score=null, rank=null)
// 2.分组补充组内缺失的条目
// 按照学校进行分组, 汇总所有年级allDim. 然后与allDim比较补充每个分组内缺失的年级,缺失的年级按照ReplenishFunction生成补充条目
@Test
void addOtherInfo() {
// 2.按照学校进行分组, 汇总所有年级allDim. 然后与allDim比较补充每个分组内缺失的年级,缺失的年级按照ReplenishFunction生成补充条目
SDFrame.read(studentList).replenish(Student::getSchool,Student::getLevel,(school,level) -> {
Student student = new Student();
student.setSchool(school);
student.setLevel(level);
return student;
}).show(30);
}
// 输出结果
id name school level age score rank
1 a 一中 三年级 11 1
2 a 一中 一年级 11 1
3 jay 一中 三年级 12 2
一中 二年级
4 c 二中 一年级 13 3
5 d 二中 一年级 14 4
二中 三年级
二中 二年级
6 e 三中 二年级 14 5
7 e 三中 二年级 15 5
三中 一年级
三中 三年级
【最后】全部测试示例奉上
import cn.hutool.json.JSONUtil;
import io.github.burukeyou.dataframe.iframe.JDFrame;
import io.github.burukeyou.dataframe.iframe.SDFrame;
import io.github.burukeyou.dataframe.iframe.item.FI2;
import io.github.burukeyou.dataframe.iframe.item.FI3;
import io.github.burukeyou.dataframe.iframe.item.FI4;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@SpringBootTest
class JDFrameTests {
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, "jay", "一中", "三年级", 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)));
}
@Test
void demo() {
System.out.println("【官方案例】统计每个学校的里学生年龄不为空并且年龄在9到16岁间的合计分数,并且获取合计分前2名的学校: ");
// 等价于SQL:
// select school,sum(score)
// from students
// where age is not null and age >=9 and age <= 16
// group by school
// order by sum(score) desc
// limit 2
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();
}
@Test
void findDemo() {
System.out.println("筛选相关:(可自行调整筛选输出条件测试哈)");
SDFrame<Student> data = SDFrame.read(studentList)
//.whereBetween(Student::getAge, 11, 15) // 过滤年龄在[11,15]岁的
//.whereBetweenR(Student::getAge, 11, 15) // 过滤年龄在(11,15]岁的, 不含11岁
//.whereBetweenL(Student::getAge, 11, 15) // 过滤年龄在[11,15)岁的, 不含15岁
//.whereNotNull(Student::getName) // 过滤名字不为空的数据, 兼容了空字符串''的判断
//.whereGt(Student::getAge, 12) // 过滤年龄大于12岁
.whereGe(Student::getAge, 14) // 过滤年龄大于等于14岁
//.whereLt(Student::getAge, 12) // 过滤年龄小于12岁的
//.whereIn(Student::getAge, Arrays.asList(3, 11, 14)) // 过滤年龄为3岁 或者11岁 或者 14岁的数据
//.whereNotIn(Student::getAge, Arrays.asList(3, 11, 14)) // 过滤年龄不为为3岁 或者11岁 或者 14岁的数据
//.whereEq(Student::getAge, 11) // 过滤年龄等于11岁的数据
//.whereNotEq(Student::getAge, 11) // 过滤年龄不等于11岁的数据
//.whereLike(Student::getName, "jay") // 模糊查询,等价于 like "%jay%"
//.whereLikeLeft(Student::getName, "jay") // 模糊查询,等价于 like "jay%"
//.whereLikeRight(Student::getName, "jay") // 模糊查询,等价于 like "%jay"
;
// 输出格式1:按照 JDK 的形式输出
data.forEach(System.out::println);
// 输出格式2:按照 JDFrame 的形式(数据库表)输出
//data.show();
// 输出格式2.1 :输出指定需要的数据
data.show(1);// 输出从0~1行数据 data.show(0) 表示输出 列字段名
}
@Test
void analysis() {
JDFrame<Student> frame = JDFrame.read(studentList);
System.out.println("获取年龄最大的学生: " + frame.max(Student::getAge));
System.out.println("获取学生里最大的年龄: " + frame.maxValue(Student::getAge));
System.out.println("获取年龄最小的学生: " + frame.min(Student::getAge));
System.out.println("获取学生里最小的年龄: " + frame.minValue(Student::getAge));
System.out.println("获取所有学生的年龄的平均值: " + frame.avg(Student::getAge)); // 获取所有学生的年龄的平均值
System.out.println("获取所有学生的年龄合计: " + frame.sum(Student::getAge)); // 获取所有学生的年龄合计
System.out.println("同时获取年龄最大和最小的学生: " + frame.maxMin(Student::getAge)); // 同时获取年龄最大和最小的学生
System.out.println("同时获取学生里最大和最小的年龄: " + frame.maxMinValue(Student::getAge)); // 同时获取学生里最大和最小的年龄
}
@Test
void distinctDemo() {
List<Student> std;
std = SDFrame.read(studentList).distinct().toLists(); // 根据对象hashCode去重
System.out.println("根据对象hashCode去重: " + std);
std = SDFrame.read(studentList).distinct(Student::getSchool).toLists(); // 根据学校名去重
System.out.println("根据学校名去重: " + std);
std = SDFrame.read(studentList).distinct(e -> e.getSchool() + e.getLevel()).toLists(); // 根据学校名拼接级别去重复
System.out.println("根据学校名拼接级别去重复: " + std);
std = SDFrame.read(studentList).distinct(Student::getSchool).distinct(Student::getLevel).toLists(); // 先根据学校名去除重复再根据级别去除重复
System.out.println("先根据学校名去除重复再根据级别去除重复: " + std);
}
@Test
void simpleGroup() {
JDFrame<Student> frame = JDFrame.read(studentList);
// 等价于 select school,sum(age) ... group by school
List<FI2<String, BigDecimal>> a = frame.groupBySum(Student::getSchool, Student::getAge).toLists();
a.forEach(item -> System.out.println("a 学校:" + item.getC1() + "\t年龄:" + item.getC2()));
// 等价于 select school,max(age) ... group by school
List<FI2<String, Integer>> a2 = frame.groupByMaxValue(Student::getSchool, Student::getAge).toLists();
a2.forEach(item -> System.out.println("a2学校:" + item.getC1() + "\t最大年龄:" + item.getC2()));
// 与 groupByMaxValue 含义一致,只是返回的是最大的值对象
List<FI2<String, Student>> a3 = frame.groupByMax(Student::getSchool, Student::getAge).toLists();
a3.forEach(item -> System.out.println("a3学校:" + item.getC1() + "\t最大年龄的学生信息:" + item.getC2()));
// 等价于 select school,min(age) ... group by school
List<FI2<String, Integer>> a4 = frame.groupByMinValue(Student::getSchool, Student::getAge).toLists();
a4.forEach(item -> System.out.println("a4学校:" + item.getC1() + "\t最小年龄:" + item.getC2()));
// 等价于 select school,count(*) ... group by school
List<FI2<String, Long>> a5 = frame.groupByCount(Student::getSchool).toLists();
a5.forEach(item -> System.out.println("a5学校:" + item.getC1() + "\t学生人数:" + item.getC2()));
// 等价于 select school,avg(age) ... group by school
List<FI2<String, BigDecimal>> a6 = frame.groupByAvg(Student::getSchool, Student::getAge).toLists();
a6.forEach(item -> System.out.println("a6学校:" + item.getC1() + "\t学生平均年龄:" + item.getC2()));
// 等价于 select school,sum(age),count(age) group by school
List<FI3<String, BigDecimal, Long>> a7 = frame.groupBySumCount(Student::getSchool, Student::getAge).toLists();
a7.forEach(item -> System.out.println("a7学校:" + item.getC1() + "\t年龄总和:" + item.getC2() + "\t统计年龄人数: " + item.getC3()));
// (二级分组)等价于 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();
a8.forEach(item -> System.out.println("a8[二级分组]学校:" + item.getC1() + "\t年级:" + item.getC2() + "\t统计此组年龄总和: " + item.getC3()));
// (三级分组)等价于 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();
a9.forEach(item -> System.out.println("a9[三级分组]学校:" + item.getC1() + "\t年级:" + item.getC2() + "\t名称: " + item.getC3() + "\t此组年龄总和: " + item.getC4()));
}
@Test
void orderList() {
// 注意:中文不支持排序。
// 等价于 order by age desc
System.out.println("按照年龄降序:");
SDFrame.read(studentList).sortDesc(Student::getAge).show();
// 等价于 order by age desc, level asc
System.out.println("按照年龄降序-年级升序:【JDFrame严重BUG】不支持多字段排序,只识别最后一个字段的sortXXX(YYYY)");
SDFrame.read(studentList).sortDesc(Student::getAge).sortAsc(Student::getLevel).show();
//SDFrame.read(studentList).sortDesc(Student::getAge).sortAsc(Student::getScore).show();
// 等价于 order by age asc
System.out.println("按照年龄升序:");
SDFrame.read(studentList).sortAsc(Student::getAge).show();
// 使用Comparator 排序
System.out.println("按照年纪升序:(比较器)");
SDFrame.read(studentList).sortAsc(Comparator.comparing(Student::getAge)).show();
}
@Test
void joinMatlab() {
System.out.println("======== 矩阵1 =======");
SDFrame<Student> sdf = SDFrame.read(studentList);
sdf.show(20);// 显示从1~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.setSchoolName(a.getSchool());
userInfo.setSchoolScoreSum(b.getC2().intValue());
userInfo.setNumIndex(String.valueOf(a.getId()));
return userInfo;
});
System.out.println("======== 连接后结果 =======");
frame.show(100);// 显示从1~100行数据
}
@Test
void transferData() {
// 等价于 select round(score*100,2) from student
SDFrame<Student> map2 = SDFrame.read(studentList).mapPercent(Student::getScore, Student::setScore, 2);
map2.show();
}
@Test
void partitionList() {
// 将每个5个元素分成一个小集合,用于将大任务拆成小任务
//List<List<Student>> lists = SDFrame.read(studentList).partition(5).stream().toList();
// 遇到问题:Unable to make field private final java.util.ArrayList java.util.ArrayList$SubList.root accessible: module java.base does not "opens java.util" to unnamed module @1ce92674
// - 解决方案:(https://blog.csdn.net/qq_31532979/article/details/138070564)
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
int MAX_SEND = 4;// 按每3个一组分割
int limit = (list.size() + MAX_SEND - 1) / MAX_SEND; // 计算切分次数
//方法一:使用流遍历操作
List<List<Integer>> mgList = new ArrayList<>();
Stream.iterate(0, n -> n + 1).limit(limit)
.forEach(i -> mgList.add(list.stream().skip(i * MAX_SEND).limit(MAX_SEND)
.collect(Collectors.toList())));
System.out.println("JDK8+流式遍历操作 实现集合拆分:\n" + JSONUtil.toJsonPrettyStr(mgList));
//方法二:获取分割后的集合
List<List<Integer>> splitList = Stream.iterate(0, n -> n + 1).limit(limit).parallel()
.map(a -> list.stream().skip(a * MAX_SEND).limit(MAX_SEND).parallel().collect(Collectors.toList()))
.toList();
System.out.println("JDK8+实现集合拆分:\n" + JSONUtil.toJsonPrettyStr(splitList));
}
@Test
void genOrderIndex() {
SDFrame<FI2<Student, Integer>> fi2s = SDFrame.read(studentList)
.sortDesc(Student::getAge)
//.addSortNoCol(Student::setRank);
.addSortNoCol(i -> (Objects.isNull(i.getRank()) ? 0 : i.getRank()) + 1);
fi2s.forEach(item -> {
item.getC1().setRank(item.getC2());
System.out.println(item.getC1());
});
}
@Test
void genOrder() {
// 按照age降序排序,然后根据当前顺序生成排名号到rank字段 (排名从0开始)与序号不同的是, 排名是如果值相同认为排名一样。
SDFrame<Student> df = SDFrame.read(studentList).addRankingSameColDesc(Student::getAge, Student::setRank);
df.show(20);
}
@Test
void addOtherInfo() {
//// 1. 根据学校字段和allDim比较去补充缺失的条目, 缺失的学校按照ReplenishFunction生成补充条目作为结果一起返回
//// 所有需要的学校条目
//List<String> allDim = Arrays.asList("一中", "二中", "三中", "四中", "五中");
//SDFrame<Student> studentSDFrame = SDFrame.read(studentList).replenish(Student::getSchool, allDim, (school) -> {
// Student student = new Student();
// student.setSchool(school);
// return student;
//});
//studentSDFrame.show();
//// 输出Student对象信息:Student(id=null, name=null, school=四中, level=null, age=null, score=null, rank=null)
//studentSDFrame.stream().filter(item -> item.getSchool().equals("四中")).toList().forEach(System.out::println);
// 2.按照学校进行分组, 汇总所有年级allDim. 然后与allDim比较补充每个分组内缺失的年级,缺失的年级按照ReplenishFunction生成补充条目
SDFrame.read(studentList).replenish(Student::getSchool,Student::getLevel,(school,level) -> {
Student student = new Student();
student.setSchool(school);
student.setLevel(level);
return student;
}).show(30);
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
class UserInfo {
private String schoolName;// 学校ID
private Integer schoolScoreSum;// 分数
private String numIndex;//
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class Student {
private Integer 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(Integer 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;
}
}
至此,常用的功能示例完毕,建议还是使用 内部 JDK8+ 的流式处理吧😄(主要是 ① 学习成本;② 安全可靠性考量);
附录
🙏至此,非常感谢阅读🙏