数据分组处理的java实现

大多情况下sql是可以实现数据的处理,可以减少程序去做额外的处理工作,只是有些业务情况sql做不到的,比如说本人在数据处理工作中,会涉及到对数据单独做分组展示的情况;不是sql的group by 返回list,而是返回的数据集为List<Map>等复杂的层级数组,而且具有分组的特点。
比如下面的Bean:

@Data
@Builder
@AllArgsConstructor
public class User {
    private Long id;
    private Long tenantId;
    private String name;
    private int age;
    private Integer testType;
    private Date testDate;
    private Long role;
    private String phone;
}

以 id,tenantId,age,name,phone 组合一级分组,
role 二级分组,
testType,testDate 组合三级分组,的层级展示

结果集:

  • List
[
    {
        "phone":"1351234567890",
        "tenantId":"2",
        "name":"什么",
        "id":"1",
        "age":"1",
        "content":[
            {
                "role":"12",
                "content":[
                    {
                        "testType":"1",
                        "testDate":"Sun Jun 23 13:47:46 CST 2019",
                        "content":[

                        ]
                    }
                ]
            }
        ]
    },
    {
        "phone":"1351234567892",
        "tenantId":"2",
        "name":"什么",
        "id":"1",
        "age":"3",
        "content":[
            {
                "role":"12",
                "content":[
                    {
                        "testType":"2",
                        "testDate":"Sun Jun 23 13:47:46 CST 2019",
                        "content":[

                        ]
                    }
                ]
            }
        ]
    },
    {
        "phone":"1351234567890",
        "tenantId":"2",
        "name":"撒子",
        "id":"1",
        "age":"2",
        "content":[
            {
                "role":"12",
                "content":[
                    {
                        "testType":"1",
                        "testDate":"Sun Jun 23 13:47:46 CST 2019",
                        "content":[

                        ]
                    }
                ]
            }
        ]
    },
    {
        "phone":"1351234567891",
        "tenantId":"2",
        "name":"撒子",
        "id":"1",
        "age":"2",
        "content":[
            {
                "role":"12",
                "content":[
                    {
                        "testType":"1",
                        "testDate":"Sun Jun 23 13:47:46 CST 2019",
                        "content":[

                        ]
                    },
                    {
                        "testType":"2",
                        "testDate":"Sun Jun 23 13:47:46 CST 2019",
                        "content":[

                        ]
                    }
                ]
            }
        ]
    },
    {
        "phone":"1351234567892",
        "tenantId":"2",
        "name":"撒子",
        "id":"1",
        "age":"3",
        "content":[
            {
                "role":"12",
                "content":[
                    {
                        "testType":"2",
                        "testDate":"Sun Jun 23 13:47:46 CST 2019",
                        "content":[

                        ]
                    }
                ]
            }
        ]
    }
]

通过数据结构Map,List进行递归算法实现,下面是源码展示和讲解:

    /**
     * 统一方法入口
     *
     * @param mocl     处理集合
     * @param index    层级
     * @param keyNames 字段
     * @param <T>
     * @return
     */
    public static <T> LinkedList<Map> group(List mocl, int index, String... keyNames) {
        return transform(groupClassifyList(mocl, keyNames), index);
    }

index 为需要输出的层级数
keyNames 为需要分组的字段,以,号分隔.
对数据如何分组处理的呢?


    public static final String SEPARATOR_FIELD = "#";
    public static final String CONNECTOR_KV = "=";
    public static final String SEPARATOR_REG = "\\,";
    public static final String CONTENT_NULL = "null";
    public static final String CONTENT_CHILDREN = "content";


    /**
     * 运用PropertyUtils取得bean的值,并根据keyName归类
     *
     * @param list    List beans
     * @param keyName 需要归类的bean的属性名称
     * @return LinkedHashMap<String, List>,有顺序的map,map的key为需要归类的bean的属性名+"="+对应的属性值:eg:"key=value",value为List<bean>
     */

    public static <T> LinkedHashMap<String, List<T>> groupClassify(List<T> list, String keyName) {
        LinkedHashMap<String, List<T>> target = new LinkedHashMap();

        String[] keyNames = keyName.split(SEPARATOR_REG);

        for (T obj : list) {
            // 取得bean需要归类的属性(keyName)的值,不做类型转换
            StringBuilder keyValueBf = new StringBuilder();
            for (String key : keyNames) {
                Object value = null;
                try {
                    value = PropertyUtils.getProperty(obj, key);
                } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
                    e.printStackTrace();
                }
                keyValueBf
                        .append(key)
                        .append(CONNECTOR_KV)
                        .append(null == value ? "" : value)
                        .append(SEPARATOR_FIELD);
            }
            String keyValue = keyValueBf.deleteCharAt(keyValueBf.length() - 1).toString();
            if (!target.containsKey(keyValue)) {
                // 如果map中没有归类key值,则添加key值和相应的list
                List keyList = new ArrayList();
                keyList.add(obj);
                target.put(keyValue, keyList);
            } else {
                // 如果有归类key值,则在相应的list中添加这个bean
                ArrayList keyList = (ArrayList) target.get(keyValue);
                keyList.add(obj);
            }
        }
        return target;
    }

将list进行keyName值进行组合成map的key=value结构,value以list形式赋值,


    /**
     * 将归类的Map<String, List>按照 keyName归类,并用index控制递归。
     *
     * @param mocl     map of classified list,也就是运用方法 LinkedHashMap<String, List> groupClassify(List list, String keyName),将list归类成的map
     * @param index    用条件 index < keyNames.length控制递归
     * @param keyNames 需要归类的bean的属性名称
     * @return
     */
    private static <T> LinkedHashMap<String, Map> groupClassify(Map<String, List<T>> mocl, int index, String... keyNames) {
        // 单步理解:target是函数参数Map<String, List> mocl再次归类成的LinkedHashMap<String,Map>
        // 递归到最后这个是最终归类好的map
        LinkedHashMap<String, Map> target = new LinkedHashMap();
        // 控制递归条件,起始的index应该总是1。
        if (index < keyNames.length) {
            // swap用来保存参数index的值,这是最容易出错的一个地方
            // 用它保证:在参数Map<String, List> mocl层面循环时用相同的index参数值。
            int swap = index;
            for (Map.Entry<String, List<T>> entry : mocl.entrySet()) {
                String moclKey = entry.getKey();
                List<T> moclList = entry.getValue();
                // 将List<bean>再次归类
                LinkedHashMap<String, List<T>> mocl$ = groupClassify(moclList, keyNames[index]);
                // 如果index达到了数组的最后一个,一定是List<bean>转map,递归结束
                if (index == keyNames.length - 1) {
                    target.put(moclKey, mocl$);
                } else {
                    // 将List<bean>转map得到的_mocl,再次归类
                    // mocm 为map of classified map的简称
                    LinkedHashMap<String, Map> mocm = groupClassify(mocl$, ++index, keyNames);
                    target.put(moclKey, mocm);
                }
                index = swap;
            }
        }
        return target;
    }

    /**
     * 将Map<String, List> map按照bean需要归类的属性名keyName归类
     *
     * @param map     map of classified list list归类成的map
     * @param keyName bean需要归类的属性名
     * @return
     */
    public static <T> LinkedHashMap<String, Map> groupClassifyMap(Map<String, List<T>> map, String keyName) {
        LinkedHashMap<String, Map> target = new LinkedHashMap();
        for (Map.Entry<String, List<T>> entry : map.entrySet()) {
            List mapList = entry.getValue();
            String mapKey = entry.getKey();
            LinkedHashMap<String, List<T>> keyMap = groupClassify(mapList, keyName);
            target.put(mapKey, keyMap);
        }
        return target;
    }

    /**
     * 将List<bean> 按照指定的bean的属性进行归类,keyNames的先后顺序会影响归类结果。
     * eg:一个学生列表,按照班级和性别归类<br>
     * Map map = CollectionUtils.groupClassifyList(studentList, "classId","sex");
     *
     * @param list     List beans
     * @param keyNames 数组包含需要归类的bean的属性名称
     * @return 归类的有顺序的树状map,eg:"key1#key2"
     * map的值为List或者map
     */
    public static LinkedHashMap groupClassifyList(List list, String... keyNames) {
        if (keyNames == null || keyNames.length == 0) {
            return null;
        }
        if (keyNames.length == 1) {
            return groupClassify(list, keyNames[0]);
        } else {
            return groupClassify(groupClassify(list, keyNames[0]), 1, keyNames);
        }
    }


    /**
     * key值转换
     *
     * @param value
     * @return
     */
    public static Map str2Map(String value) {
        HashMap map = new HashMap();
        String[] k_vs = value.split(SEPARATOR_FIELD);
        for (int i = 0; i < k_vs.length; i++) {
            String[] k_v = k_vs[i].split(CONNECTOR_KV);
            if (StringUtils.isBlank(k_v[0])) {
                // key为空时,是否存入
                continue;
            } else if (k_v.length == 1 || CONTENT_NULL.equals(k_v[1])) {
                // value为空时,json格式是否显示
                map.put(k_v[0], null);
            } else {
                map.put(k_v[0], k_v[1]);
            }
        }
        return map;
    }
    
 /**
     * 所有节点遍历转换成map
     *
     * @param mocl
     * @param index
     * @param <T>
     * @return
     */
    public static <T> LinkedList<Map> transform(Map<String, Object> mocl, int index) {
        LinkedList<Map> root = new LinkedList<>();
        for (String moclKey : mocl.keySet()) {
            LinkedList<Map> objects = new LinkedList<>();
            Map<Object, Object> objectMap = str2Map(moclKey);
            Object obj = mocl.get(moclKey);
            if (obj instanceof String) {
                Map map = str2Map((String) obj);
                objects.add(map);
            } else if (obj instanceof List) {
                LinkedList<Object> maps = transformList((List) obj, --index);
                objectMap.put(CONTENT_CHILDREN, maps);
                objects.add(objectMap);
            } else if (obj instanceof Map) {
                LinkedList<Map> maps = transformMap((Map<String, Object>) obj, --index);
                objects.addAll(maps);
            }
            objectMap.put(CONTENT_CHILDREN, objects);
            root.add(objectMap);
        }
        return root;
    }

统一对String类型的key进行Map转换,对value进行子集转换,包括Map、List类型的content


    /**
     * 所有节点遍历转换成List
     * 通过递归方式整理数据
     *
     * @param mocl
     * @param index
     * @param <T>
     * @return
     */
    public static <T> LinkedList<Object> transformList(List mocl, int index) {
        // 控制递归条件,起始的index应该总是1。
        LinkedList<Object> content = new LinkedList<>();
        if (index <= 0) {
            return content;
        }
        index--;
        for (Object obj : mocl) {
            if (obj instanceof List) {
                LinkedList<Object> maps = transformList((List) obj, index);
                content.addAll(maps);
            } else if (obj instanceof Map) {
                LinkedList<Map> maps = transformMap((Map<String, Object>) obj, index);
                content.addAll(maps);
            } else {
                content.add(obj);
            }
        }
        return content;
    }


    /**
     * 所有节点遍历转换成map
     * 通过递归方式整理数据
     *
     * @param mocl
     * @param index
     * @param <T>
     * @return
     */
    public static <T> LinkedList<Map> transformMap(Map<String, Object> mocl, int index) {
        // 控制递归条件,起始的index应该总是1。
        LinkedList<Map> objects = new LinkedList<>();
        if (index <= 0) {
            return objects;
        }
        // swap用来保存参数index的值,这是最容易出错的一个地方
        // 用它保证:在参数Map<String, List> mocl层面循环时用相同的index参数值。
        index--;
        for (String moclKey : mocl.keySet()) {
            Map<Object, Object> objectMap = str2Map(moclKey);
            // 将List<bean>再次归类
            Object object = mocl.get(moclKey);
            if (object instanceof List) {
                LinkedList<Object> maps = transformList((List) object, index);
                objectMap.put(CONTENT_CHILDREN, maps);
                objects.add(objectMap);
            } else if (object instanceof Map) {
                LinkedList<Map> maps = transformMap((Map<String, Object>) mocl.get(moclKey), index);
                objectMap.put(CONTENT_CHILDREN, maps);
                objects.add(objectMap);
            }
        }
        return objects;
    }

这时候键值对就统一做好了转换,

通过主函数一起来测试下,仅仅为测试数据

 /**
     * eg:
     *
     * @param args
     */
    public static void main(String[] args) {
        // 待处理信息
        LinkedList<Object> objects = new LinkedList<>();
        objects.add(User.builder().id(1L).tenantId(2L).age(1).name("什么").phone("1351234567890").role(12L).testDate(new Date()).testType(1).build());
        objects.add(User.builder().id(1L).tenantId(2L).age(1).name("什么").phone("1351234567890").role(12L).testDate(new Date()).testType(1).build());
        objects.add(User.builder().id(1L).tenantId(2L).age(2).name("撒子").phone("1351234567890").role(12L).testDate(new Date()).testType(1).build());
        objects.add(User.builder().id(1L).tenantId(2L).age(2).name("撒子").phone("1351234567891").role(12L).testDate(new Date()).testType(1).build());
        objects.add(User.builder().id(1L).tenantId(2L).age(2).name("撒子").phone("1351234567891").role(12L).testDate(new Date()).testType(2).build());
        objects.add(User.builder().id(1L).tenantId(2L).age(3).name("撒子").phone("1351234567892").role(12L).testDate(new Date()).testType(2).build());
        objects.add(User.builder().id(1L).tenantId(2L).age(3).name("什么").phone("1351234567892").role(12L).testDate(new Date()).testType(2).build());
        objects.add(User.builder().id(1L).tenantId(2L).age(3).name("什么").phone("1351234567890").role(12L).testDate(new Date()).testType(2).build());

        // 最终分解的信息
        LinkedList<Map> role = group(objects, 1, "id,tenantId,age,name,phone", "role", "testType,testDate");
        System.out.println(JsonUtils.toJson(role));
    }

输出结果:

[
    {
        "phone":"1351234567890",
        "tenantId":"2",
        "name":"什么",
        "id":"1",
        "age":"1",
        "content":[

        ]
    },
    {
        "phone":"1351234567890",
        "tenantId":"2",
        "name":"撒子",
        "id":"1",
        "age":"2",
        "content":[

        ]
    },
    {
        "phone":"1351234567891",
        "tenantId":"2",
        "name":"撒子",
        "id":"1",
        "age":"2",
        "content":[

        ]
    },
    {
        "phone":"1351234567892",
        "tenantId":"2",
        "name":"撒子",
        "id":"1",
        "age":"3",
        "content":[

        ]
    },
    {
        "phone":"1351234567892",
        "tenantId":"2",
        "name":"什么",
        "id":"1",
        "age":"3",
        "content":[

        ]
    },
    {
        "phone":"1351234567890",
        "tenantId":"2",
        "name":"什么",
        "id":"1",
        "age":"3",
        "content":[

        ]
    }
]

大家可以复制代码去体验测试下输出结果。


stream 的方式也能简单实现类似的结果,


```java

	Map<Integer, Map<String, List<Tuple3>>> collect1 =
                Optional.ofNullable(objects)
                        .orElse(Collections.emptyList())
                        .stream()
                        .sorted(Comparator.comparing(User::getAge).reversed())
                        .collect(Collectors.groupingBy(User::getTestType,
                                Collectors.groupingBy(User::getName,
                                        Collectors.collectingAndThen(Collectors.toList(),
                                                lists -> lists.stream().map(history ->
                                                        new Tuple3(
                                                                history.getName(),
                                                                history.getPhone(),
                                                                history.getTestDate())
                                                ).collect(Collectors.toList())))));
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值