JAVA的嵌套数组DIFF算法

大家好,我是wdm, 针对上一篇一层List的DIFF
https://blog.csdn.net/wudaiming/article/details/131500016?spm=1001.2014.3001.5501

这里我在项目组开发是碰到了一种情况,就是嵌套数组是否相等的比较,项目中我通过重写数组对象的equals方法和set实现了判断是否有改动【包括、增、删、改都属于一种状态返回true,另一种就是啥没改返回false】,写完了我想起上篇文章可以优化用到这。

对于嵌套数组的DIFF算法其实可以在比较器中通过反射获取对象属性的Class类型,如果是List或者Arrays就走数组的判断相等逻辑【可以转换成set去重写数组对象的equals方法,最后set1.equals(set2)来判断】,否则就走Java基本类型的equals。

对于实现,其实只需要改动比较器:

package com.wdm.util;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.Comparator;
import java.util.List;

public class ObjectComparator<T> implements Comparator<T> {
    @Override
    public int compare(T o1, T o2) {
        Field[] fields = o1.getClass().getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            try {
                Object value1 = field.get(o1);
                Object value2 = field.get(o2);
                if (value1 != null && value2 != null && value1.getClass().isArray() && value2.getClass().isArray()) {
                    if (!isArraysEqual(value1, value2)) {
                        return compareArrays(value1, value2);
                    }
                } else if (value1 instanceof List && value2 instanceof List) {
                    if (!isListEqual((List<?>) value1, (List<?>) value2)) {
                        return compareLists((List<?>) value1, (List<?>) value2);
                    }
                } else if (!isEqual(value1, value2)) {
                    return compareValues(value1, value2);
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return 0;
    }

    private boolean isArraysEqual(Object arr1, Object arr2) {
        int length1 = Array.getLength(arr1);
        int length2 = Array.getLength(arr2);
        if (length1 != length2) {
            return false;
        }
        for (int i = 0; i < length1; i++) {
            Object element1 = Array.get(arr1, i);
            Object element2 = Array.get(arr2, i);
            if (!isEqual(element1, element2)) {
                return false;
            }
        }
        return true;
    }

    private boolean isListEqual(List<?> list1, List<?> list2) {
        int size1 = list1.size();
        int size2 = list2.size();
        if (size1 != size2) {
            return false;
        }
        for (int i = 0; i < size1; i++) {
            Object element1 = list1.get(i);
            Object element2 = list2.get(i);
            if (!isEqual(element1, element2)) {
                return false;
            }
        }
        return true;
    }

    private boolean isEqual(Object obj1, Object obj2) {
        if (obj1 == null && obj2 == null) {
            return true;
        }
        if (obj1 == null || obj2 == null) {
            return false;
        }
        return obj1.equals(obj2);
    }

    private int compareArrays(Object arr1, Object arr2) {
        int length1 = Array.getLength(arr1);
        int length2 = Array.getLength(arr2);
        int minSize = Math.min(length1, length2);
        for (int i = 0; i < minSize; i++) {
            Object element1 = Array.get(arr1, i);
            Object element2 = Array.get(arr2, i);
            if (!isEqual(element1, element2)) {
                return compareValues(element1, element2);
            }
        }
        return Integer.compare(length1, length2);
    }

    private int compareLists(List<?> list1, List<?> list2) {
        int size1 = list1.size();
        int size2 = list2.size();
        int minSize = Math.min(size1, size2);
        for (int i = 0; i < minSize; i++) {
            Object element1 = list1.get(i);
            Object element2 = list2.get(i);
            if (!isEqual(element1, element2)) {
                return compareValues(element1, element2);
            }
        }
        return Integer.compare(size1, size2);
    }

    private int compareValues(Object value1, Object value2) {
        if (value1 instanceof Comparable && value2 instanceof Comparable) {
            return ((Comparable) value1).compareTo(value2);
        }
        return 0;
    }
}

然后对于DiffUtil在上篇文章的错误进行修复后的代码如下:

package com.wdm.util;

import cn.hutool.core.collection.CollUtil;
import com.wdm.enumm.EnumType;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.experimental.Accessors;
import org.jetbrains.annotations.NotNull;

import java.lang.reflect.Field;
import java.sql.Wrapper;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author: wudaiming
 * @date: 2023/6/29
 * @version: 1.0
 * @description:
 */
public class DiffListUtil {
    @Data
    public static class TargetWrapper<T> {
        private T target;
        private EnumType type;

        public TargetWrapper(T target, EnumType type) {
            this.target = target;
            this.type = type;
        }

        // Getters and setters for target and type
    }
    @Data
    @Accessors(chain = true)
    public static class DiffResult<T> {
        /**
         * 新增对象列表
         */
        private List<TargetWrapper<T>> addedList;
        /**
         * 修改后的对象列表
         */
        private List<TargetWrapper<T>> changedList;
        /**
         * 已删除对象列表
         */
        private List<TargetWrapper<T>> deletedList;
    }

    /**
     * 对比两个List的元素
     * <p>
     * 如果 baseList 的元素在 targetList 中存在 PrimaryKey 相等的元素并且 elementComparator 比较结果不相等,则将修改后的值添加到changedList列表中;
     * 如果 baseList 的元素在 targetList 中不存在,将baseList中的元素添加到deletedList中;
     * 如果 targetList 的元素在 baseList 中不存在,将targetList中的元素添加到addedList中;
     * <p>
     * complexity: O(n)
     *
     * @param baseList            基础List(原来的List)
     * @param targetList          目标List(最新的List)
     * @param elementComparator   元素比较器
     *primaryKeyExtractor
     * @param <T>
     * @return 对比结果
     */
    public static <T> DiffResult<T> diffList(List<T> baseList,
                                             List<T> targetList,
                                             @NotNull Function<T, Object> primaryKeyExtractor,
                                             @NotNull ObjectComparator<T> elementComparator) {

        DiffResult<T> checkResult = checkEmptyAndReturn(baseList, targetList);
        if (checkResult != null) {
            return checkResult;
        }

        Map<Object,T> baseMap = new HashMap<>(4096);
        for(T base : baseList){
            Object key = primaryKeyExtractor.apply(base);
            baseMap.put(key,base);
        }

        List<TargetWrapper<T>> addedList = new ArrayList<>();
        List<TargetWrapper<T>> changedList = new ArrayList<>();
        List<TargetWrapper<T>> deletedList = new ArrayList<>();

        //找出新增的 和需要更新的
        for (T target : targetList) {
            Object key = primaryKeyExtractor.apply(target);
            T base = baseMap.get(key);
            if(base == null){
                addedList.add(new TargetWrapper<T>(target, EnumType.ADD));
            }else{
                baseMap.remove(key);
                if (elementComparator.compare(base, target) != 0) {
                    changedList.add(new TargetWrapper<T>(target, EnumType.MODIFIED));
                }
            }
        }

        //剩余的就是需要删除的
        Set<Map.Entry<Object, T>> entrySet = baseMap.entrySet();
        if(CollUtil.isNotEmpty(entrySet)){
            for(Map.Entry<Object, T> entry:entrySet){
                deletedList.add(new TargetWrapper<T>(entry.getValue(), EnumType.DELETED));
            }
        }
        return new DiffResult<T>()
                .setAddedList(addedList)
                .setChangedList(changedList)
                .setDeletedList(deletedList);
    }

    private static <T, V> void setFieldValue(T object, Function<? super T,V> fieldGetter, String value) {
        try {
            Field field = fieldGetter.getClass().getDeclaredField("value");
            field.setAccessible(true);
            field.set(fieldGetter.apply(object), value);
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }


    /**
     * 检查baseList 和 targetList 为empty(null||size==0)的情况
     *
     * @param baseList
     * @param targetList
     * @param <T>
     * @return
     */
    private static <T> DiffResult<T> checkEmptyAndReturn(List<T> baseList, List<T> targetList) {

        if (CollUtil.isEmpty(baseList) && CollUtil.isEmpty(targetList)) {
            return new DiffResult<T>()
                    .setAddedList(null)
                    .setChangedList(null)
                    .setDeletedList(null);
        }

        if (CollUtil.isEmpty(baseList) && CollUtil.isNotEmpty(targetList)) {
            List<TargetWrapper<T>> wrapperTargetList =
                    targetList.stream().map(t -> new TargetWrapper<>(t, EnumType.ADD)).collect(Collectors.toList());
            return new DiffResult<T>()
                    .setAddedList(wrapperTargetList)
                    .setChangedList(null)
                    .setDeletedList(null);
        }

        if (CollUtil.isNotEmpty(baseList) && CollUtil.isEmpty(targetList)) {
            List<TargetWrapper<T>> wrapperBaseList = baseList.stream().map(t -> new TargetWrapper<>(t, EnumType.DELETED)).collect(Collectors.toList());
            return new DiffResult<T>()
                    .setAddedList(null)
                    .setChangedList(null)
                    .setDeletedList(wrapperBaseList);
        }
        return null;
    }

    @Data
    @AllArgsConstructor
    public static class User {
        private Integer id;
        private String userName;
        private String address;
        private String email;
        private List<User> sonUserList;
    }
    @Data
    @AllArgsConstructor
    public static class SonUser {
        private Integer id;
        private String userName;
        private String address;
        private String email;
    }

}

package com.wdm;

import com.wdm.util.DiffListUtil;
import com.wdm.util.ObjectComparator;
import org.apache.commons.collections.CollectionUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;

/**
 * @author: wudaiming
 * @date: 2023/6/29
 * @version: 1.0
 * @description:
 */

@RunWith(SpringRunner.class)
@SpringBootTest
public class DiffListUtilApplicationTest {
    @Test
    public void test1() {
        List<DiffListUtil.User> userList = new ArrayList<>();
        DiffListUtil diffListUtil = new DiffListUtil();
        DiffListUtil.User sonUser = new DiffListUtil.User(55, "John", "hunan", "hunan@faw.com", new ArrayList<>());
        DiffListUtil.User sonUser11 = new DiffListUtil.User(11, "John", "hunan", "hunan@faw.com", new ArrayList<>());
        DiffListUtil.User sonUser12 = new DiffListUtil.User(11, "John1", "hunan", "hunan@faw.com", new ArrayList<>());

        userList.add(new DiffListUtil.User(11,"John","hunan","hunan@faw.com", new ArrayList<>(){{
            add(sonUser);
            //add(sonUser11);
        }}));
        userList.add(new DiffListUtil.User(22,"Tom","jilin","jilin@faw.com", new ArrayList<>(){{
            add(sonUser);
            add(sonUser11);
        }}));
        List<DiffListUtil.User> userListAfter = new ArrayList<>();

        userListAfter.add(new DiffListUtil.User(11,"John","hunan","hunan@faw.com", new ArrayList<>(){{
            add(sonUser);
            //add(sonUser11);
        }}));
        userListAfter.add(new DiffListUtil.User(22,"Tom","jilin","jilin@faw.com", new ArrayList<>(){{
            add(sonUser);
            //add(sonUser11);
        }}));
        Function<DiffListUtil.User, Object> primaryKeyExtractor = user -> user.getId();

        ObjectComparator<DiffListUtil.User> userComparator = new ObjectComparator<>();
        DiffListUtil.DiffResult<DiffListUtil.User> userDiffResult = diffListUtil.diffList(userListAfter, userList, primaryKeyExtractor, userComparator);
        //unionSubIntersection(userList, userListAfter);
        System.out.println(userDiffResult);
    }

    private void unionSubIntersection(List<DiffListUtil.User> userList, List<DiffListUtil.User> userListAfter) {
        /**
         * @description 求两个集合的不同元素
         * @author wudaiming
         * @date 13:29 2023/7/20
         */
        List<DiffListUtil.User> union = (List<DiffListUtil.User>) CollectionUtils.union(userListAfter, userList);
        List<DiffListUtil.User> intersection = (List<DiffListUtil.User>) CollectionUtils.intersection(userListAfter, userList);

        List<DiffListUtil.User> difference = findDifference(union, intersection);
    }

    public List<DiffListUtil.User> findDifference(List<DiffListUtil.User> list1, List<DiffListUtil.User> list2) {
        return (List<DiffListUtil.User>) CollectionUtils.subtract(list1, list2);
    }
}

总结:

最后,其实这个实现我还是只以第一层为判断ADD,MODIFIED,DELETED的主体,也就是说对于嵌套数组中如果对象元素有任何增、删、改都看作第一层的MODIFIED,如果有朋友想要精确到嵌套数组的ADD,MODIFIED,DELETED可以留言讨论!!这个目前对我够用了。

感谢大家看完我的文章,如果大家喜欢我会继续更新项目中遇到的小东西,搞技术就是要不断积累,大家共勉!!!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值