网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
定义便量,循环累加
都到了最后,顺手就把最基础的,也就是一开始就会的方案带上吧,虽然它看起来最不优雅,写起来也很是麻烦,但是嗯带着吧(哈哈哈哈哈,其实有个坑啊,到时候就懂了) 当然啦,最朴素的逻辑就放一段,下面详细测试代码里可以看到完整逻辑.
private static void originalAddition(List<Map<String, Object>> calculateList) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); double calculatedValue1 = 0.0; double calculatedValue2 = 0.0; for (Map<String, Object> map : calculateList) { calculatedValue1 += Double.parseDouble(map.get("calculated_value_1").toString()); calculatedValue2 += Double.parseDouble(map.get("calculated_value_2").toString()); } HashMap<String, Object> res = new HashMap<>(); res.put("calculatedValue1", calculatedValue1); res.put("calculatedValue2", calculatedValue2); }
测试部分
实践是检验真理的唯一标准,测试部分主要有两份测试报告,一份完整的测试代码,和一些反常现象和其背后的一些逻辑组成,当然啦,肯定是能优化的,但是老规矩先挖坑.
先上测试代码,最后给出两份执行的结果分别说一下情况,代码由几部分组成
- 调用部分,统一调用测试不同实现效率在不同数量级别下的情况.
- 实现部分,列举不同实现
- 数据生成部分,因为肯定不能那实际业务代码出来看,也不能用实际上数据,所以我只能模拟一个有着十几列的 list 来完成剩下的部分,他有着需计算总和的部分,和不需要计算总和的部分,模拟实际情况.
- 测试形式,注掉不需要的代码,不如我想看过滤不需要累加求和的数据对各种实现的影响,那我就在各个部分注释掉它,在代码中加入 stopWatch 来检测代码的运行实践,已经输出写参数方便我判断,也会通过增加减少参数列来实现模拟我有不同多的参数列,(注释掉,或者新写一个).
测试代码
package com.yidiansishiyi.aimodule.job.once;
import org.springframework.util.StopWatch;
import java.util.*;
import java.util.stream.Collectors;
public class SimulatedDataGenerator {
static int TEN = 10;
static int HUNDRED = 100;
static int THOUSAND = 1000;
static int TEN_THOUSAND = 10000;
static int ONE_HUNDRED_THOUSAND = 100000;
static int MILLION = 1000000;
static int TEN_MILLION = 10000000;
static int BILLION = 100000000;
public static void main(String[] args) {
List<Integer> integers = Arrays.asList(TEN, HUNDRED, THOUSAND, TEN_THOUSAND, ONE_HUNDRED_THOUSAND, MILLION);
for (int order : integers) {
List<Map<String, Object>> simulatedData = generateSimulatedData(order);
System.out.println("数量分组 " + order + " 条下性能报告: (单位毫秒)");
// 忽略清单, 部分不列举在累加内的参数
List<String> ignoreList = Arrays.asList("gender", "non_calculable_1", "id", "name", "non-calculable", "non_calculable_2");
// 流分组计算
streamGroupingBy(simulatedData, ignoreList);
// 自定义累加器去计算
singleStream(simulatedData, ignoreList);
// 并行流分组计算
parallelStreamGroupingBy(simulatedData, ignoreList);
// 单条流去计算
singleStream(simulatedData);
// 原始定义变量相加
originalAddition(simulatedData);
List<String> actualColumn = Arrays.asList("calculated_value_1", "calculated_value_2", "txt4", "txt2", "txt3", "calculated_value_3", "bonus", "txt1", "salary", "age", "expenses");
calculateSumByType(simulatedData, actualColumn);
}
}
private static void originalAddition(List<Map<String, Object>> calculateList) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
double calculatedValue1 = 0.0;
double calculatedValue2 = 0.0;
double txt4 = 0.0;
double txt2 = 0.0;
double txt3 = 0.0;
double calculatedValue3 = 0.0;
double bonus = 0.0;
double txt1 = 0.0;
double salary = 0.0;
double age = 0.0;
double expenses = 0.0;
for (Map<String, Object> map : calculateList) {
calculatedValue1 += Double.parseDouble(map.get("calculated_value_1").toString());
calculatedValue2 += Double.parseDouble(map.get("calculated_value_2").toString());
txt4 += Double.parseDouble(map.get("txt4").toString());
txt2 += Double.parseDouble(map.get("txt2").toString());
txt3 += Double.parseDouble(map.get("txt3").toString());
calculatedValue3 += Double.parseDouble(map.get("calculated_value_3").toString());
bonus += Double.parseDouble(map.get("bonus").toString());
txt1 += Double.parseDouble(map.get("txt1").toString());
salary += Double.parseDouble(map.get("salary").toString());
age += Double.parseDouble(map.get("age").toString());
expenses += Double.parseDouble(map.get("expenses").toString());
}
HashMap<String, Object> res = new HashMap<>();
res.put("calculatedValue1", calculatedValue1);
res.put("calculatedValue2", calculatedValue2);
res.put("calculatedValue3", calculatedValue3);
res.put("txt4", txt4);
res.put("txt2", txt2);
res.put("txt3", txt3);
res.put("txt1", txt1);
res.put("bonus", bonus);
res.put("salary", salary);
res.put("age", age);
res.put("expenses", expenses);
stopWatch.stop();
System.out.print("原始定义变量相加 :");
System.out.println(stopWatch.getTotalTimeMillis());
// 打印前 结果
// sumByType.forEach((key, value) -> System.out.println(key + ":" + value));
}
private static HashMap<String, Double> calculateSumByType(List<Map<String, Object>> calculateList, List<String> fields) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
HashMap<String, Double> sumByType = new HashMap<>();
for (String field : fields) {
double sum = calculateList.stream()
.mapToDouble(map -> Double.parseDouble(map.get(field).toString()))
.sum();
sumByType.put(field, sum);
}
stopWatch.stop();
System.out.print("流加循环 :");
System.out.println(stopWatch.getTotalTimeMillis());
return sumByType;
}
private static void singleStream(List<Map<String, Object>> calculateList) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
HashMap<String, Object> sumByType = new HashMap<>();
sumByType.put("calculated_value_1", calculateList.stream().mapToDouble(map -> Double.parseDouble(map.get("calculated_value_1").toString())).sum());
sumByType.put("calculated_value_2", calculateList.stream().mapToDouble(map -> Double.parseDouble(map.get("calculated_value_2").toString())).sum());
sumByType.put("txt4", calculateList.stream().mapToDouble(map -> Double.parseDouble(map.get("txt4").toString())).sum());
sumByType.put("txt2", calculateList.stream().mapToDouble(map -> Double.parseDouble(map.get("txt2").toString())).sum());
sumByType.put("txt3", calculateList.stream().mapToDouble(map -> Double.parseDouble(map.get("txt3").toString())).sum());
sumByType.put("calculated_value_3", calculateList.stream().mapToDouble(map -> Double.parseDouble(map.get("calculated_value_3").toString())).sum());
sumByType.put("bonus", calculateList.stream().mapToDouble(map -> Double.parseDouble(map.get("bonus").toString())).sum());
sumByType.put("txt1", calculateList.stream().mapToDouble(map -> Double.parseDouble(map.get("txt1").toString())).sum());
sumByType.put("salary", calculateList.stream().mapToDouble(map -> Double.parseDouble(map.get("salary").toString())).sum());
sumByType.put("age", calculateList.stream().mapToDouble(map -> Double.parseDouble(map.get("age").toString())).sum());
sumByType.put("expenses", calculateList.stream().mapToDouble(map -> Double.parseDouble(map.get("expenses").toString())).sum());
stopWatch.stop();
System.out.print("单条流去计算 :");
System.out.println(stopWatch.getTotalTimeMillis());
// 打印前 结果
// sumByType.forEach((key, value) -> System.out.println(key + ":" + value));
}
private static void singleStream(List<Map<String, Object>> calculateList, List<String> ignoreList) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Map<String, Object> sumByType = new HashMap<>();
for (Map<String, Object> record : calculateList) {
for (Map.Entry<String, Object> entry : record.entrySet()) {
// 忽略组合计算清单
String key = entry.getKey();
if (ignoreList.contains(key)) {
continue;
}
// double newValue = 0.0;
// Object value = entry.getValue();
// // 计算参数转换 (如扩展其他类型数据补全改结构)
// if (value instanceof BigDecimal) {
// newValue = ((BigDecimal) value).doubleValue();
// } else if (value instanceof String) {
// newValue = Double.valueOf((String) value);
// } else {
// continue;
// }
Object value = entry.getValue();
double newValue = Double.parseDouble(value.toString());
// 计算浮点数类型字段的和
if (sumByType.containsKey(key)) {
// 已有键新增值
double oldValue = Double.parseDouble((String) sumByType.get(key));
newValue = oldValue + newValue;
sumByType.put(key, String.valueOf(newValue));
} else {
sumByType.put(key, value.toString());
}
}
}
stopWatch.stop();
System.out.print("自定义累加器去计算 :");
System.out.println(stopWatch.getTotalTimeMillis());
// 打印前 结果
// sumByType.forEach((key, value) -> System.out.println(key + ":" + value));
}
// 流计算
private static void streamGroupingBy(List<Map<String, Object>> simulatedData, List<String> strings) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Map<String, Double> sumByType = simulatedData.stream()
.flatMap(map -> map.entrySet().stream())
.filter(entry -> strings.stream().noneMatch(b -> entry.getKey().equals(b)))
.collect(Collectors.groupingBy(
Map.Entry::getKey,
Collectors.summingDouble(entry -> Double.parseDouble(entry.getValue().toString()))
));
stopWatch.stop();
System.out.print("流分组计算 :");
System.out.println(stopWatch.getTotalTimeMillis());
// 打印前 结果
// sumByType.forEach((key, value) -> System.out.println(key + ":" + value));
}
private static void parallelStreamGroupingBy(List<Map<String, Object>> simulatedData, List<String> strings) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Map<String, Double> sumByType = simulatedData.parallelStream()
.flatMap(map -> map.entrySet().stream())
.filter(entry -> strings.stream().noneMatch(b -> entry.getKey().equals(b)))
.collect(Collectors.groupingBy(
Map.Entry::getKey,
Collectors.summingDouble(entry -> Double.parseDouble(entry.getValue().toString()))
));
stopWatch.stop();
System.out.print("并行流分组计算 :");
System.out.println(stopWatch.getTotalTimeMillis());
// 打印前 结果
// sumByType.forEach((key, value) -> System.out.println(key + ":" + value));
}
// mork 数据生成部分
private static List<Map<String, Object>> generateSimulatedData(int dataSize) {
List<Map<String, Object>> data = new LinkedList<>();
for (int i = 0; i < dataSize; i++) {
Map<String, Object> record = new HashMap<>();
record.put("id", i);
record.put("name", "Record_" + i);
record.put("age", getRandomValue(18, 60));
record.put("gender", getRandomGender());
record.put("salary", getRandomValue(30, 80));
record.put("expenses", getRandomValue(10, 50));
record.put("bonus", getRandomValue(0, 100));
record.put("txt1", getRandomValue(0, 100));
record.put("txt2", getRandomValue(0, 100));
record.put("txt3", getRandomValue(0, 100));
record.put("txt4", getRandomValue(0, 100));
record.put("non_calculable_1", "Some non-calculable data");
record.put("non_calculable_2", "Another non-calculable data");
// 计算项
double calculatedValue1 = (double) record.get("salary") - (double) record.get("expenses");
double calculatedValue2 = (double) record.get("salary") + (double) record.get("bonus");
double calculatedValue3 = (double) record.get("expenses") / (double) record.get("age");
record.put("calculated_value_1", calculatedValue1);
record.put("calculated_value_2", calculatedValue2);
record.put("calculated_value_3", calculatedValue3);
data.add(record);
}
return data;
}
// private static double getRandomValue(double min, double max) {
// double randomValue = min + (max - min) * new Random().nextDouble();
// // 保留两位小数
// return Math.round(randomValue * 100.0) / 100.0;
// }
private static final Random random = new Random();
private static double getRandomValue(double min, double max) {
double randomValue = min + (max - min) * random.nextDouble();
// 保留两位小数
return Math.round(randomValue * 100.0) / 100.0;
}
private static String getRandomGender() {
return new Random().nextBoolean() ? "Male" : "Female";
}
}
测试中情况说明
先说结论
不管在测试集中哪个数据量情况下,从效果上看,代码最少,看起来最清晰(熟悉流语法后)完全使用流版本竟然出乎意料的是最慢的,反而最普通的原始的丑陋的,循环中写
+=
的反而是最快的(当然在并行流不出手的情况下,好吧是有点耍流氓,并行流不是流吗,哈哈哈哈哈). 这一结论打败了我流效率永远优于循环
的刻板印象 (当然也是我算法不到家,以后肯定能写出来更快的) . 其次写法贼抽象的
使用单条流一项一项的计算数据
竟然在我最后写循环调用之前反而是第二快的(也没算并行流),果然码不可貌相
,甚至当只有一个值需要计算的时候,第二种写法在 Stream 的优化先是快于循环的,果然流的内部迭代还是优化过的.不过在这个写法外面套一层循环后效率比这一条条写快,而且出错的概率还低. 直接以白名单去计算的 循环 + 流 和 硬编码的,在没有需要过滤的条件下和需要过滤的条件下没有影响,而剩下的需要判别的或多或少有些影响,
并行流在处理 1000 条这样数据以上,有着明显的优势,代码简单,速度最快.
原因
上学的时候数据结构与算法的老师说过看时间复杂度,就数循环数,看空间复杂度,就看它有没有开辟新的空间,一般程序可以在时间和空间上进行转换(有些流畅的代码源码非常大,也巨耗存储,嗯我记得有些比赛作品就挺抽象,不过最牛逼的还是
超级马里奥
,将节省进行到底),从现象上看很容易 将流和循环画上等号,但是想起老师最开始讲的时间复杂度计算方式, 计算语句频度,求极限值,这才为现象画上了等号,流绝非等价于循环是循环的特殊化,先看下流最初的定义,可能和我一样,有好多人没有关心过.Java 中的流(Stream)是一种用于对集合进行处理的高级抽象。它提供了一种更为函数式的编程风格,允许你通过一系列的操作来处理数据,如过滤、映射、排序等。 流的核心思想是将数据处理操作串联起来,形成一个流水线。你可以将多个中间操作连接起来,最后添加一个终端操作来触发实际的计算。这样的设计使得流可以进行懒加载,只有在终端操作被触发时,中间操作才会被执行。
当然,流的不同操作看起来是一次循环,但是流有自己的处理方式,将不同操作合并到同一次遍历中了(我们把这种技术叫作循环合并)。
当然其中肯定有优化的空间,但是要更加深入了解一些原理.
结果
不过从结果上来看,如何快速的写出简单易懂,不令人头大的代码好像已经有了结论,即使不用并行流它处理速度在大数据量下会慢一些,但是也绝对够用,能节省很多编码时间,以及在系统开发运维中可能一不小心带来的错误,没有防止四海皆准的真理,我们的一切都在现有条件的定义下才是正确的, 流不是万用银弹,但是在很多场合下是对开发有好处的,虽然需要一点时间入门,嗯等我看完那本书应该算是入门了,哈哈哈哈.这个坑是我填起来最快的,因为赶到这了,正好.
Java 8 实战,不仅仅有流的知识,函数式编程等 Java 8 特性也在其中挺好看的,当然需要一定的前置基础,不过嗯很有意思,当然和小说没得比.偶尔还是令人头大.
测试报告
数量分组 10 条下性能报告: (单位毫秒)
流分组计算 :62
自定义累加器去计算 :2
并行流分组计算 :5
单条流去计算 :4
原始定义变量相加 :0
流加循环 :0
数量分组 100 条下性能报告: (单位毫秒)
流分组计算 :10
自定义累加器去计算 :4
并行流分组计算 :3
单条流去计算 :2
原始定义变量相加 :1
流加循环 :1
数量分组 1000 条下性能报告: (单位毫秒)
流分组计算 :44
自定义累加器去计算 :26
并行流分组计算 :12
单条流去计算 :13
原始定义变量相加 :6
流加循环 :8
数量分组 10000 条下性能报告: (单位毫秒)
流分组计算 :135
自定义累加器去计算 :125
并行流分组计算 :19
单条流去计算 :49
原始定义变量相加 :40
流加循环 :38
数量分组 100000 条下性能报告: (单位毫秒)
流分组计算 :717
自定义累加器去计算 :866
并行流分组计算 :142
单条流去计算 :408
原始定义变量相加 :280
流加循环 :396
数量分组 1000000 条下性能报告: (单位毫秒)
流分组计算 :12512
自定义累加器去计算 :8803
并行流分组计算 :1553
单条流去计算 :3994
原始定义变量相加 :2724
![img](https://img-blog.csdnimg.cn/img_convert/279af2b6f73d6d15a8755c74a9264459.png)
![img](https://img-blog.csdnimg.cn/img_convert/c378ff1cdbf0ca0d7c3cff85d2ed19c9.png)
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化的资料的朋友,可以戳这里获取](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**
0 条下性能报告: (单位毫秒)
流分组计算 :12512
自定义累加器去计算 :8803
并行流分组计算 :1553
单条流去计算 :3994
原始定义变量相加 :2724
[外链图片转存中...(img-nG2pjeO1-1715383514218)]
[外链图片转存中...(img-0oRO3tu2-1715383514218)]
**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
**[需要这份系统化的资料的朋友,可以戳这里获取](https://bbs.csdn.net/forums/4f45ff00ff254613a03fab5e56a57acb)**
**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**