背景
前段时间,接到一个sku摆放的需求,要求sku划分到指定的位置。因此,需要我们对每一条原始的sku数据按位置进行分组展示(见originSku)。其中位置是多层次划分,大致划分为:区域 -> 货架 -> 分区 -> 具体位置 -> 商品明细(如下图所示)。
本次需求涉及多层嵌套分组,为了保证执行效率,本次采用方案是按需查询所有的sku,在内存中进行分组处理。对于内存处理分组,我们很自然想到了JDK 8的新特性Stream,利用Stream下的Group By可以很好完成本次功能。
因为涉及的嵌套分组过多,达到4次,当sku数量增多时,这里很快就会出行性能问题,因此不得不考虑分组效率的问题。本次列举了3种分组的实现方式,并通过JMH工具进行性能测试,得到多层嵌套分组的最佳实践。
-
Stream 多字段 group by
-
多层嵌套 group by
-
group by 后遍历再 group by
测试类
@BenchmarkMode(Mode.SingleShotTime) // 测量调用1次耗时
@Warmup(iterations = 2) // 预热2次,避免JIT机制对结果进行干扰
@Threads(2) // 2个线程,由执行环境cpu数量而定,本次电脑cpu核心数为8
@Fork(2) // fork 出两个进程
@OutputTimeUnit(TimeUnit.MILLISECONDS) // 单位 ms
public class MultiGroupByTest {
private static List<OriginSku> originSkuList = getOriginSku();
public static List<OriginSku> getOriginSku() {
// 数据准备: 10 个区域, 区域下有 20 个货架, 货架下有 30 个分区, 分区下有 30 个位置, 每个位置放 10 sku
List<OriginSku> originSkuList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 20; j++) {
for (int k = 0; k < 30; k++) {
for (int l = 0; l < 30; l++) {
for (int m = 0; m < 10; m++) {
OriginSku sku = new OriginSku();
sku.setAreaNo("AreaNo-" + i);
sku.setShelfNo("ShelfNo-" + j);
sku.setPartitionNo("Partition-" + k);
sku.setLocationNo("LocationNo-" + l);
sku.setSkuCode(i + "-SkuCode-" + m);
sku.setSkuName(i + "-SkuName-" + m);
sku.setSkuQty(new BigDecimal(m));
sku.setOrder(m);
originSkuList.add(sku);
}
}
}
}
}
return originSkuList;
}
public static void main(String[] args) throws RunnerException {
Options options = new OptionsBuilder()
.include(MultiGroupByTest.class.getSimpleName())
.build();
new Runner(options).run();
}
}
Stream 多字段 group by
先多字段group by,之后遍历组装数据。
@Benchmark
public List<AreaVo> buildSkuLocation3() {
Map<String, Map<String, Map<String, Map<String, List<OriginSku>>>>> map = getOriginSku()
.stream()
.