大概需求:希望对每个区间的数据量进行统计。
这是最初的写法
List<String> oilRateList = new ArrayList<>(); //数据集合
int[] spotDistributing = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
oilRateList.forEach(s -> {
double aDouble = Double.parseDouble(s) * 100;
//包前不包后
if (aDouble >= 0 && aDouble < 5) {
spotDistributing[0]++;
} else if (aDouble >= 5 && aDouble < 10) {
spotDistributing[1]++;
} else if (aDouble >= 10 && aDouble < 15) {
spotDistributing[2]++;
} else if (aDouble >= 15 && aDouble < 20) {
spotDistributing[3]++;
} else if (aDouble >= 20 && aDouble < 25) {
spotDistributing[4]++;
} else if (aDouble >= 25 && aDouble < 30) {
spotDistributing[5]++;
} else if (aDouble >= 30 && aDouble < 35) {
spotDistributing[6]++;
} else if (aDouble >= 35 && aDouble < 40) {
spotDistributing[7]++;
} else if (aDouble >= 40 && aDouble < 45) {
spotDistributing[8]++;
} else {
spotDistributing[9]++;
}
});
性能并不差,但是很丑。所以需要优化
第一版本
大佬给一些思路,优化后的:
@Data
public class IntervalData {
/**
* 数据区间对象集合
*/
private static List<Rule> rules;
static {
rules = new ArrayList<>();
rules.add(new Rule(0D, 5D, DataRangeEnum.ZeroIsFive));
rules.add(new Rule(5D, 10D, DataRangeEnum.FiveIsTen));
rules.add(new Rule(10D, 15D, DataRangeEnum.TenIsFifteen));
rules.add(new Rule(15D, 20D, DataRangeEnum.FifteenIsTwenty));
rules.add(new Rule(20D, 25D, DataRangeEnum.TwentyIsTwentyFive));
rules.add(new Rule(25D, 30D, DataRangeEnum.TwentyFiveIsThirty));
rules.add(new Rule(30D, 35D, DataRangeEnum.ThirtyIsThirtyFive));
rules.add(new Rule(35D, 40D, DataRangeEnum.ThirtyFiveIsForty));
rules.add(new Rule(40D, 45D, DataRangeEnum.FortyIsFortyFive));
rules.add(new Rule(45D, -1D, DataRangeEnum.FortyFiveIsAll));
}
/**
* 生成数据
*/
public static Map<DataRangeEnum, Integer> check(List<String> doubles) {
Map<Rule, AtomicInteger> counts = new HashMap<>();
doubles.forEach(v -> {
Double doublev = Double.parseDouble(v) * 100;
for (Rule rule : rules) {
boolean aTrue = rule.isTrue(doublev);
if (!aTrue) continue;
if (counts.containsKey(rule)) {
counts.get(rule).addAndGet(1);
} else {
counts.put(rule, new AtomicInteger(1));
}
}
});
Map<DataRangeEnum, Integer> data = new HashMap<>();
counts.keySet().forEach(v -> data.put(v.dataRangeEnum, counts.get(v).get()));
Map<DataRangeEnum, Integer> returnData = new LinkedHashMap<>();
Arrays.stream(DataRangeEnum.values()).forEach(v -> {
Integer integer = data.get(v);
if (integer == null) {
returnData.put(v, 0);
} else {
returnData.put(v, integer);
}
});
return returnData;
}
/**
* 数据区间对象
*/
static class Rule {
/**
* 左侧
*/
private Double left;
/**
* 右侧
*/
private Double right;
/**
* 对应的枚举
*/
private DataRangeEnum dataRangeEnum;
public Rule(Double left, Double right, DataRangeEnum dataRangeEnum) {
this.left = left;
this.right = right;
this.dataRangeEnum = dataRangeEnum;
}
public boolean isTrue(Double number) {
if (this.right == -1D) {
return number >= left;
}
return number >= left && number < right;
}
}
}
对应的枚举代码
@Getter
public enum DataRangeEnum {
/**
* 0-5
*/
ZeroIsFive,
/**
* 5-10
*/
FiveIsTen,
/**
* 10-15
*/
TenIsFifteen,
/**
* 15-20
*/
FifteenIsTwenty,
/**
* 20-25
*/
TwentyIsTwentyFive,
/**
* 25-30
*/
TwentyFiveIsThirty,
/**
* 30-35
*/
ThirtyIsThirtyFive,
/**
* 35-40
*/
ThirtyFiveIsForty,
/**
* 40-45
*/
FortyIsFortyFive,
/**
* 45-
*/
FortyFiveIsAll;
}
这个解决方法是通过对象来优化那些if-else
获取数据只用调一句话就行
Map<DataRangeEnum, Integer> check = IntervalData.check(oilRateList);
xx.setData(check.values());
很简便,但是通过观察大家可以看出,这个的获取数据的规则其实是写死的,正好我这个项目里面有两套规则,一套是上面的0-5,5-10…,还有一套是0-10,10-20…,上面写死了,如果要加的话,很不好看,这样不好,需要优化
我这里个人的解决方式是把规则提取出来
第二版本
上代码,规则枚举
@Getter
public enum BigRangeEnum {
/**
* 0-50 以5为间隔 统计十次
*/
OneSpace(0, -1, 5, 10),
/**
* 0-100 以10为间隔 统计十次
*/
TwoSpace(0, -1, 10, 10);
/**
* 范围左侧
*/
private Integer bigLeft;
/**
* 范围右侧 (-1的意思是 最有一个判断区间范围 仅使用>=即可)
*/
private Integer bigRight;
/**
* 间隔
*/
private Integer space;
/**
* 计算次数
*/
private Integer count;
BigRangeEnum(Integer bigLeft, Integer bigRight, Integer space, Integer count) {
this.bigLeft = bigLeft;
this.bigRight = bigRight;
this.space = space;
this.count = count;
}
}
上上一个枚举的升级版本
@Getter
public enum DataRangeEnum {
/**
* 0-5
*/
ZeroIsFive(BigRangeEnum.OneSpace, 1),
/**
* 5-10
*/
FiveIsTen(BigRangeEnum.OneSpace, 2),
/**
* 10-15
*/
TenIsFifteen(BigRangeEnum.OneSpace, 3),
/**
* 15-20
*/
FifteenIsTwenty(BigRangeEnum.OneSpace, 4),
/**
* 20-25
*/
TwentyIsTwentyFive(BigRangeEnum.OneSpace, 5),
/**
* 25-30
*/
TwentyFiveIsThirty(BigRangeEnum.OneSpace, 6),
/**
* 30-35
*/
ThirtyIsThirtyFive(BigRangeEnum.OneSpace, 7),
/**
* 35-40
*/
ThirtyFiveIsForty(BigRangeEnum.OneSpace, 8),
/**
* 40-45
*/
FortyIsFortyFive(BigRangeEnum.OneSpace, 9),
/**
* 45-
*/
FortyFiveIsAll(BigRangeEnum.OneSpace, 10),
/**
* 0-10
*/
ZeroIsTen(BigRangeEnum.TwoSpace, 1),
/**
* 10-20
*/
TenIsTwenty(BigRangeEnum.TwoSpace, 2),
/**
* 20-30
*/
TwentyIsThirty(BigRangeEnum.TwoSpace, 3),
/**
* 30-40
*/
ThirtyIsForty(BigRangeEnum.TwoSpace, 4),
/**
* 40-50
*/
FortyIsFifty(BigRangeEnum.TwoSpace, 5),
/**
* 50-60
*/
FiftyIsSixty(BigRangeEnum.TwoSpace, 6),
/**
* 60-70
*/
SixtyIsSeventy(BigRangeEnum.TwoSpace, 7),
/**
* 70-80
*/
SeventyIsEighty(BigRangeEnum.TwoSpace, 8),
/**
* 80-90
*/
EightyIsNinety(BigRangeEnum.TwoSpace, 9),
/**
* 90-
*/
NinetyIsAll(BigRangeEnum.TwoSpace, 10);
private BigRangeEnum belong;
private Integer sort;
DataRangeEnum(BigRangeEnum belong, Integer sort) {
this.belong = belong;
this.sort = sort;
}
/**
* 根据归属和排序 获取枚举
*/
public static DataRangeEnum getDataRangeEnumByBelongAndSort(BigRangeEnum belong, Integer sort) {
DataRangeEnum[] dataRangeEnums = DataRangeEnum.values();
Optional<DataRangeEnum> first = Arrays.stream(dataRangeEnums).filter(v -> v.getBelong().equals(belong) && v.getSort().equals(sort)).findFirst();
return first.orElse(null);
}
/**
* 根据归属,获取枚举集合
*/
public static List<DataRangeEnum> getListByBelong(BigRangeEnum belong) {
DataRangeEnum[] dataRangeEnums = DataRangeEnum.values();
return Arrays.stream(dataRangeEnums).filter(v -> v.getBelong().equals(belong)).collect(Collectors.toList());
}
}
核心计算代码
@Data
public class IntervalData {
/**
* 数据区间对象集合
*/
private static List<Rule> rules;
/**
* 计算入口
*/
public static Map<DataRangeEnum, Integer> CalculateEntrance(BigRangeEnum bigRangeEnum, List<String> doubles) {
rules = new ArrayList<>();
for (int i = 1; i <= bigRangeEnum.getCount(); i++) {
Rule rule;
if (i == bigRangeEnum.getCount()) {
rule = new Rule(bigRangeEnum.getSpace() * (i - 1) * 1.0, bigRangeEnum.getBigRight() * 1.0, DataRangeEnum.getDataRangeEnumByBelongAndSort(bigRangeEnum, i));
} else {
rule = new Rule(bigRangeEnum.getSpace() * (i - 1) * 1.0, bigRangeEnum.getSpace() * i * 1.0, DataRangeEnum.getDataRangeEnumByBelongAndSort(bigRangeEnum, i));
}
rules.add(rule);
}
return check(bigRangeEnum, doubles);
}
/**
* 生成数据
*/
private static Map<DataRangeEnum, Integer> check(BigRangeEnum bigRangeEnum, List<String> doubles) {
Map<DataRangeEnum, AtomicInteger> counts = new HashMap<>();
doubles.forEach(v -> {
Double doublev = Double.parseDouble(v) * 100;
for (Rule rule : rules) {
boolean aTrue = rule.isTrue(doublev);
if (!aTrue) continue;
if (counts.containsKey(rule.dataRangeEnum)) {
counts.get(rule.dataRangeEnum).addAndGet(1);
} else {
counts.put(rule.dataRangeEnum, new AtomicInteger(1));
}
}
});
Map<DataRangeEnum, Integer> returnData = new LinkedHashMap<>();
DataRangeEnum.getListByBelong(bigRangeEnum).forEach(v -> {
AtomicInteger integer = counts.get(v);
if (integer == null) {
returnData.put(v, 0);
} else {
returnData.put(v, integer.get());
}
});
return returnData;
}
/**
* 数据区间对象
*/
static class Rule {
/**
* 左侧
*/
private Double left;
/**
* 右侧
*/
private Double right;
/**
* 对应的枚举
*/
private DataRangeEnum dataRangeEnum;
public Rule(Double left, Double right, DataRangeEnum dataRangeEnum) {
this.left = left;
this.right = right;
this.dataRangeEnum = dataRangeEnum;
}
public boolean isTrue(Double number) {
if (this.right == -1D) {
return number >= left;
}
return number >= left && number < right;
}
}
}
这里其实就是通过指定的规则,去生成rules
然后沿用上一版本的计算方法。
看到这,大家可能心里会有一个疑问,这样的处理,好看是好看了,代码性能怎么样。经过我的测试,在数据量小于10w条的时候,第二版本的运行时间是小于大量的if-else的,但是数据量一旦超过10w条,第二版本的性能就大打折扣,测试中1000w条数据,大量的if-else的执行时间平均为600ms,但是第二版本的执行时间平均达到了1200ms,性能太差了,需要优化。
通过观察第二版本的核心计算代码其实可以看出来doubles
为数据总量,rules
是每一个范围的抽象对象
——大量的if-else的循环次数就=doubles个数
——第二版本的总循环次数=doubles个数 * rules个数
当数据量较小的时候,看不出来,一旦数据量足够多,比如1000w条,那么此时大量的if-else的循环次数为1000w次,但是第二版本的循环次数达到了1000w*10,一亿次,也怪不得性能差了。需要优化
第三版本
核心计算代码
@Data
public class IntervalData {
/**
* 数据区间对象集合
*/
private static List<Rule> rules;
/**
* 计算入口
*/
public static Map<DataRangeEnum, Integer> CalculateEntrance(BigRangeEnum bigRangeEnum, List<String> doubles) {
rules = new ArrayList<>();
for (int i = 1; i <= bigRangeEnum.getCount(); i++) {
Rule rule;
if (i == bigRangeEnum.getCount()) {
rule = new Rule(bigRangeEnum.getSpace() * (i - 1) * 1.0, bigRangeEnum.getBigRight() * 1.0, DataRangeEnum.getDataRangeEnumByBelongAndSort(bigRangeEnum, i));
} else {
rule = new Rule(bigRangeEnum.getSpace() * (i - 1) * 1.0, bigRangeEnum.getSpace() * i * 1.0, DataRangeEnum.getDataRangeEnumByBelongAndSort(bigRangeEnum, i));
}
rules.add(rule);
}
return check(bigRangeEnum, doubles);
}
/**
* 生成数据
*/
private static Map<DataRangeEnum, Integer> check(BigRangeEnum bigRangeEnum, List<String> doubles) {
Map<DataRangeEnum, AtomicInteger> counts = new HashMap<>();
doubles.forEach(v -> {
Double doublev = Double.parseDouble(v) * 100;
int i = (int) (doublev / bigRangeEnum.getSpace());
Rule rule = (i >= rules.size()) ? rules.get(rules.size() - 1) : rules.get(i);
if (counts.containsKey(rule.dataRangeEnum)) {
counts.get(rule.dataRangeEnum).addAndGet(1);
} else {
counts.put(rule.dataRangeEnum, new AtomicInteger(1));
}
});
Map<DataRangeEnum, Integer> returnData = new LinkedHashMap<>();
DataRangeEnum.getListByBelong(bigRangeEnum).forEach(v -> {
AtomicInteger integer = counts.get(v);
if (integer == null) {
returnData.put(v, 0);
} else {
returnData.put(v, integer.get());
}
});
return returnData;
}
/**
* 数据区间对象
*/
static class Rule {
/**
* 左侧
*/
private Double left;
/**
* 右侧
*/
private Double right;
/**
* 对应的枚举
*/
private DataRangeEnum dataRangeEnum;
public Rule(Double left, Double right, DataRangeEnum dataRangeEnum) {
this.left = left;
this.right = right;
this.dataRangeEnum = dataRangeEnum;
}
}
}
通过观察第二版本的代码可以发现rules
的顺序一直都是一个好的顺序
,是一个按照0-5,5-10…的一个顺序,那么此时,我们就可以用数据值/规则间距
得出来的值,就是rules
对应的区间的数据位置,当然,需要考虑超过rules
范围的值,超过范围的就去最后一个即可
这样处理,第二版本的循环数量直接减少了rules
个数倍。
那么最后的结果是多少呢
跑一个1000w条数据试试
上面是大量的if-else的结果,下面是第三版本的结果,相差无几!!!(if-else的性能不差)
这个设计的话,还有一个好处,后续有任何需要添加规则,修改规则,去规则枚举里改就行。
到此,撒花,完美解决。
2023-03-13 补
其实再再仔细观察一下可以发现Rule
对象是没有什么用的,最初的版本需要通过它来判断区间,但是现在区间的判断不需要他了,在这个对象里面,只会用到DataRangeEnum
这个枚举,每次创建对象,都需要开辟内存空间,所以,就把Rule
对象给优化掉吧
上代码
@Data
public class IntervalData {
/**
* 数据范围枚举集合
*/
private static List<DataRangeEnum> rangeEnumList;
/**
* 计算入口
*/
public static Map<DataRangeEnum, Integer> CalculateEntrance(BigRangeEnum bigRangeEnum, List<String> doubles) {
rangeEnumList = new ArrayList<>();
for (int i = 1; i <= bigRangeEnum.getCount(); i++) {
rangeEnumList.add(DataRangeEnum.getDataRangeEnumByBelongAndSort(bigRangeEnum, i));
}
return check(bigRangeEnum, doubles);
}
/**
* 生成数据
*/
private static Map<DataRangeEnum, Integer> check(BigRangeEnum bigRangeEnum, List<String> doubles) {
Map<DataRangeEnum, AtomicInteger> counts = new HashMap<>();
doubles.forEach(v -> {
Double doublev = Double.parseDouble(v) * 100;
int i = (int) (doublev / bigRangeEnum.getSpace());
DataRangeEnum dataRangeEnum = (i >= rangeEnumList.size()) ? rangeEnumList.get(rangeEnumList.size() - 1) : rangeEnumList.get(i);
if (counts.containsKey(dataRangeEnum)) {
counts.get(dataRangeEnum).addAndGet(1);
} else {
counts.put(dataRangeEnum, new AtomicInteger(1));
}
});
Map<DataRangeEnum, Integer> returnData = new LinkedHashMap<>();
DataRangeEnum.getListByBelong(bigRangeEnum).forEach(v -> {
AtomicInteger integer = counts.get(v);
if (integer == null) {
returnData.put(v, 0);
} else {
returnData.put(v, integer.get());
}
});
return returnData;
}
}
最后,再跑一次1000w数据瞅瞅
嗯,很完美