Java对象通用比对工具

目录

背景

思路

实现


背景

  前段时间的任务中,遇到了需要识别两个对象不同属性的场景,如果使用传统的一个个属性比对equals方法,会存在大量的重复工作,而且为对象新增了属性后,比对方法也需要同步修改,不方便维护,于是便打算构造一个通用的比对方法,需要支持对象嵌套,并支持过滤比对逻辑的策略,适用大部分比对场景

思路

  一个java对象,他的属性是多样的,会有集合类型(如Collection,Map,Array)基本类型(如String,int,double,long等认为是最基本对象的比较),或者对象类型,本质上是一个树状的结构

怎么去遍历对象树

常用的方法就是递归,那怎么定义递归的终点?基于上面三种类型,定为基本类型的比对为递归的终点,对于集合类型,那就再对每一个元素进行比较,对于对象类型,遍历属性进行比较

为了达成通用目的,这里使用java反射实现,基于约定和性能考虑,只通过无参的getter方法获取属性进行比较,另外如何记录当前属性所在的路径?这里参考spel表达式,相对轻量直观

实现

首先定义递归过程记录的上下文信息

这里记录了根对象,当前处理的对象,以及反射获取的对象信息

protected static class CompareContext {
        //对比结果
        private List<CompareResult.CompareInfo> resCollect;
        //当前遍历的对象信息,用于定位
        private ArrayDeque<String> pathMessageStack;
        protected Object rootSourceObj;
        protected Object rootOtherObj;
        protected Object sourceObj;
        protected Object otherObj;
        protected Object sourceInvokeVal;
        protected Object otherInvokeVal;
        //过滤耗费时间
        private long filterCost;

        private String checkCycle() {
            Set<String> set = new LinkedHashSet<>();
            //出现重复退出
            while (set.add(pathMessageStack.removeLast())) {
            }
            String[] elements = new String[set.size()];
            Iterator<String> iterator = set.iterator();
            int index = 0;
            while (iterator.hasNext()) {
                elements[set.size() - 1 - index++] = iterator.next();
            }
            return getPath(elements);
        }

        protected String getPath(String[] elements) {
            Object obj = this.rootSourceObj == null ? this.rootOtherObj : this.rootSourceObj;
            String simpleName = obj.getClass().getSimpleName();
            StringBuilder builder = new StringBuilder(simpleName);
            if (elements == null) {
                elements = this.pathMessageStack.toArray(new String[0]);
            }
            for (int i = elements.length - 1; i >= 0; i--) {
                String cur = elements[i];
                String value = cur.substring(2);
                if (cur.startsWith(FIELD_SIGN)) {
                    builder.append(".").append(value);
                } else {
                    builder.append("[").append(value).append("]");
                }
            }
            return builder.toString();
        }
    }

基于这个上下文,定义过滤接口,因为有些属性可能不用比对,如果命中过滤接口,则跳过

@FunctionalInterface
public interface MethodFilter {
    boolean isSkipCompare(Method method, String methodKey, CompareUtil.CompareContext compareContext);
}

比对结果类,主要存放每个比对不通过属性的值及其路径信息

@Getter
@ToString
public class CompareResult {
    protected boolean same;
    protected Object source;
    protected Object other;
    protected List<CompareInfo> diffList;


    //=====额外信息,可以不关注====
    //整体对比耗时
    private long totalCostMill;

    //方法过滤耗时
    protected long methodFilterCostMill;



    protected void genCost(long start) {
        this.totalCostMill = System.currentTimeMillis() - start;
    }

   

    @Getter
    public static class CompareInfo {
        protected Object sourceVal;
        protected Object otherVal;
        protected String path;

        protected boolean isShow() {
            if (sourceVal == null || otherVal == null) {
                return true;
            }
            if (CompareUtil.getObjType(sourceVal) != Object.class) {
                return true;
            }
            if (sourceVal instanceof Collection) {
                return ((Collection<?>) sourceVal).size() != ((Collection<?>) otherVal).size();
            }
            if (sourceVal instanceof Map) {
                return ((Map<?, ?>) sourceVal).size() != ((Map<?, ?>) otherVal).size();
            }
            if (sourceVal.getClass().isArray()) {
                return ((Object[]) sourceVal).length != ((Object[]) otherVal).length;
            }
            return false;
        }

        @Override
        public String toString() {
            return String.format("path:%s,source:[%s],other:[%s]", path, sourceVal, otherVal);
        }
    }


    public String getBaseObjDiffInfo() {
        return getBaseObjDiffInfo("\n");
    }

    /**
     * 过滤出为空的父对象或基本对象,原生的diffList会包含父对象,不方便查看
     *
     * @param seperator
     * @return
     */
    public String getBaseObjDiffInfo(String seperator) {
        if (!same) {
            StringBuilder builder = new StringBuilder();
            diffList.stream().filter(CompareInfo::isShow).forEach(v -> builder.append(v).append(seperator));
            return builder.toString();
        }
        return "";
    }

}

随后定义入口方法

  public static <T> CompareResult compareObjByGetter(T source, T other, MethodFilter... methodFilters) {
        List<CompareResult.CompareInfo> diffList = new ArrayList<>();
        CompareContext compareContext = new CompareContext();
        compareContext.resCollect = diffList;
        compareContext.pathMessageStack = new ArrayDeque<>(MAX_DEEP_SIZE);
        compareContext.rootSourceObj = source;
        compareContext.rootOtherObj = other;
        long start = System.currentTimeMillis();
        boolean res = compareObjByGetter(source, other, compareContext, methodFilters);
        CompareResult compareResult = new CompareResult();
        compareResult.genCost(start);
        compareResult.diffList = diffList;
        compareResult.same = res;
        compareResult.other = other;
        compareResult.source = source;
        compareResult.methodFilterCostMill = compareContext.filterCost;
        return compareResult;
    }

主流程方法,流程如下

  1. 校验
    1. 路径长度校验
    2. 类校验
  2. 递归终点,对于基本类型,直接走比对方法,并生成路径
  3. 对于复合类型,递归进行比对逻辑
  4. 对于对象类型,采用反射获取具体值,并通过方法过滤进行比较
    1. 首先判断是否有public的无参数getter方法
    2. 有的话采用调用该方法获取两边的值,设置上下文信息
    3. 过滤器过滤无需比对的字段
    4. 如果任一方为空,构造结果
    5. 对于集合类型的属性,需要进一步获取里面的元素进行递归处理
    6. 对于对象类型或基本类型,重复上述步骤

主流程代码如下

private static <T> boolean compareObjByGetter(T source, T other, CompareContext compareContext, MethodFilter... methodFilters) {
        //对比路径太深或出现循环引用不支持
        if (compareContext.pathMessageStack.size() > MAX_DEEP_SIZE) {
            String path = compareContext.checkCycle();
            if (!StringUtils.isEmpty(path)) {
                //路径仅供参考,不一定准确
                throw new IllegalStateException(String.format("reference cycle happen,please check your object,path:%s", path));
            }
            throw new IllegalStateException(String.format("compare path over max size:%s,please check your object", MAX_DEEP_SIZE));
        }
        if(source==other){
            return true;
        }
        if (source == null || other == null) {
            generateResult(source, other, compareContext);
            return false;
        }
        if (!source.getClass().equals(other.getClass())) {
            throw new IllegalArgumentException(String.format("not the same object,class source:%s,class other:%s,path:%s", source.getClass(), other.getClass(),compareContext.getPath(null)));
        }
        //基本类型不再对比getter方法
        if (getObjType(source) != Object.class) {
            boolean isSame = compareBaseObj(source, other);
            if (!isSame) {
                generateResult(source, other, compareContext);
            }
            return isSame;
        }
        //复合类型
        if (isCollectionOrMapOrArray(source)) {
            return dealWithCMA(source, other, compareContext, methodFilters);
        }
        //对象类型,遍历对应的方法
        final boolean[] val = new boolean[]{true};
        ReflectionUtils.doWithMethods(source.getClass(), new ReflectionUtils.MethodCallback() {
            @Override
            public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
                String name = method.getName();

                if (method.getModifiers() == Modifier.PUBLIC && (name.startsWith("get") || name.startsWith("is"))) {
                    //有入参的getter不处理
                    if (method.getParameterTypes().length != 0) {
                        return;
                    }
                    String methodKey = method.getDeclaringClass().getName() + "#" + method.getName();
                    Object sourceInvokeVal = null;
                    Object otherInvokeVal = null;
                    try {
                        sourceInvokeVal = method.invoke(source);
                        otherInvokeVal = method.invoke(other);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                    compareContext.otherObj = other;
                    compareContext.otherInvokeVal = otherInvokeVal;
                    compareContext.sourceObj = source;
                    compareContext.sourceInvokeVal = sourceInvokeVal;
                    //过滤,methodFilter是||关系
                    long start = System.currentTimeMillis();
                    try {
                        for (MethodFilter methodFilter : methodFilters) {
                            if (methodFilter.isSkipCompare(method, methodKey, compareContext)) {
                                return;
                            }
                        }
                    } finally {
                        compareContext.filterCost += System.currentTimeMillis() - start;
                    }
                    if (sourceInvokeVal == null && otherInvokeVal == null) {
                        return;
                    }
                    compareContext.pathMessageStack.push(String.format("%s%s", FIELD_SIGN, ColumnSelector.getFieldName(method.getName(), method.getDeclaringClass().getName())));
                    if (sourceInvokeVal == null || otherInvokeVal == null) {
                        generateResult(sourceInvokeVal, otherInvokeVal, compareContext);
                        val[0] = false;
                        compareContext.pathMessageStack.pop();
                        return;
                    }
                    if (isCollectionOrMapOrArray(sourceInvokeVal)) {
                        val[0] &= dealWithCMA(sourceInvokeVal, otherInvokeVal, compareContext, methodFilters);
                    } else {
                        //对象类型 or 基本类型
                        val[0] &= compareObjByGetter(sourceInvokeVal, otherInvokeVal, compareContext, methodFilters);
                    }
                    compareContext.pathMessageStack.pop();
                }
            }
        });
        return val[0];
    }

对于集合类型的处理如下

主要根据各自的特性取出元素,再给到上面的主流程去执行

 private static boolean dealWithCMA(Object sourceObj, Object otherObj, CompareContext compareContext, MethodFilter... methodFilters) {
        if(sourceObj==otherObj){
            return true;
        }
        boolean isDiff = true;
        if (sourceObj instanceof Collection) {

            Collection<?> sourceCollection = ((Collection<?>) sourceObj);
            Collection<?> otherCollection = ((Collection<?>) otherObj);
            //要求顺序,这里不做排序
            if (sourceCollection.size() != otherCollection.size()) {
                isDiff = false;
            } else {
                Iterator<?> sourceI = sourceCollection.iterator();
                Iterator<?> otherI = otherCollection.iterator();
                int index = 0;
                while (sourceI.hasNext()) {
                    Object sourceElement = sourceI.next();
                    Object otherElement = otherI.next();
                    //下一层不匹配的值
                    compareContext.pathMessageStack.push(String.format("%s%s", COLLECTION_SIGN, index++));
                    isDiff &= compareObjByGetter(sourceElement, otherElement, compareContext, methodFilters);
                    compareContext.pathMessageStack.pop();
                }
            }
        }

        if (sourceObj.getClass().isArray()) {
            Object[] sourceArray = (Object[]) sourceObj;
            Object[] otherArray = (Object[]) otherObj;
            if (sourceArray.length != otherArray.length) {
                isDiff = false;
            } else {
                for (int i = 0; i < sourceArray.length; i++) {
                    Object sourceElement = sourceArray[i];
                    Object otherElement = otherArray[i];
                    compareContext.pathMessageStack.push(String.format("%s%s", ARRAY_SIGN, i));
                    isDiff &= compareObjByGetter(sourceElement, otherElement, compareContext, methodFilters);
                    compareContext.pathMessageStack.pop();
                }
            }
        }
        if (sourceObj instanceof Map) {
            Map<?, ?> sourceMap = (Map) sourceObj;
            Map<?, ?> otherMap = (Map) otherObj;
            if (sourceMap.size() != otherMap.size()) {
                isDiff = false;
            } else {
                HashSet<?> otherKeySet = new HashSet<>(otherMap.keySet());
                for (Map.Entry<?, ?> entry : sourceMap.entrySet()) {
                    Object sourceKey = entry.getKey();
                    Object otherVal = otherMap.get(sourceKey);
                    otherKeySet.remove(sourceKey);
                    compareContext.pathMessageStack.push(String.format("%s%s", KEY_SIGN, sourceKey));
                    isDiff &= compareObjByGetter(entry.getValue(), otherVal, compareContext, methodFilters);
                    compareContext.pathMessageStack.pop();
                }
                if (!otherKeySet.isEmpty()) {
                    for (Object otherKey : otherKeySet) {
                        compareContext.pathMessageStack.push(String.format("%s%s", KEY_SIGN, otherKey));
                        isDiff &= compareObjByGetter(null, otherMap.get(otherKey), compareContext, methodFilters);
                        compareContext.pathMessageStack.pop();
                    }
                }
            }
        }
        if (!isDiff) {
            generateResult(sourceObj, otherObj, compareContext);
        }
        return isDiff;
    }

对于基本对象判断,如果返回object类型需要进一步解析判断,后续如果有其他基本对象,比如业务自定义的基本对象也可以往里面加,或者再进行优化,通过本地线程的方式动态指定基本对象类型

/**
     * 后续比对有其他基本类型可以往里加
     * @param obj
     * @return
     */
    protected static Class<?> getObjType(Object obj) {
        if (obj instanceof Integer) {
            return Integer.class;
        } else if (obj instanceof String) {
            return String.class;
        } else if (obj instanceof BigDecimal) {
            return BigDecimal.class;
        } else if (obj instanceof Long) {
            return Long.class;
        } else if (obj instanceof Enum) {
            return Enum.class;
        } else if (obj instanceof Double) {
            return Double.class;
        } else if (obj instanceof Boolean) {
            return Boolean.class;
        }
        return Object.class;
    }

对于基本类型的比较方法

private static <T> boolean compareBaseObj(T obj, T other) {
        //同一个对象直接通过
        if(obj==other){
            return true;
        }
        //数字类型
        if (obj instanceof Number && obj instanceof Comparable) {
            return ((Comparable<T>) obj).compareTo(other) == 0;
        }
        //其他类型
        return Objects.equals(obj, other);
    }

效果

Java对象比对功能实现大概如此,来看看效果

定义了比对对象

@Getter
    public static class ComposeObj {
        private String id;
        private Object[] arrays;

        private List<Object> list;

        private Map<String, Object> map;

        private ComposeObj son;

        private int idLength;

        public ComposeObj(String id) {
            this.id = id;
            this.idLength = id.length();
        }
    }

测试代码

 ComposeObj sourceObj = new ComposeObj("source");
        ComposeObj arrayObj = new ComposeObj("A1");
        sourceObj.arrays = new Object[]{arrayObj, new ComposeObj("A22")};
        sourceObj.list = Arrays.asList(new ComposeObj("C1"), new ComposeObj("C11"));
        Map<String, Object> map = new HashMap<>();
        map.put("test", "test");
        map.put("test2", "test2");
        sourceObj.map = map;

      ComposeObj  otherObj = new ComposeObj("other");
        ComposeObj arrayObj2 = new ComposeObj("A2");
        otherObj.arrays = new Object[]{arrayObj2, new ComposeObj("A22")};
        otherObj.list = Arrays.asList(new ComposeObj("C2"), new ComposeObj("C11"));
        Map<String, Object> map2 = new HashMap<>();
        map2.put("test", "test2");
        map2.put("test2", "test22");
        otherObj.map = map2;

测试方法

  CompareResult compareResult = CompareUtil.compareObjByGetter(sourceObj, otherObj);
        assert checkPath(compareResult);
        assert compareResult.getDiffList().size() == 9;
        log.info(compareResult.getBaseObjDiffInfo());

结果

默认的支持的表达式过滤器如下

/**
 * 通过简单的表达式
 * "*abc|xxx" ->标识以abc结尾或xxx的属性忽略,不做比对,*代表通配符
 */
public class RegexFieldFilter implements MethodFilter {
    private static final Cache<String, Pattern> REGEX_MAP = CacheBuilder.newBuilder()
            .softValues().maximumSize(2048).build();

    private final List<String> rules;

    private static final String WILDCARD = "*";
    //是否缓存结果,如果调用次数较多(比如属性为list,且实际场景数量较多)可以用启用
    private boolean cacheResult = false;
    //方法key
    private Cache<String, Boolean> resultMap;

    private final Set<String> equalsField = new HashSet<>();

    private final Map<String, String> prefixFieldMap = new HashMap<>();

    private final Map<String, String> suffixFieldMap = new HashMap<>();

    private RegexFieldFilter(String regex, boolean cacheResult) {
        this.cacheResult = cacheResult;
        rules = Splitter.on("|").splitToList(regex);
        for (String rule : rules) {
            //普通比对
            if (!rule.contains(WILDCARD)) {
                equalsField.add(rule);
            }
            //首尾通配符特殊逻辑
            if (onlyOneWildcard(rule)) {
                if (rule.startsWith(WILDCARD)) {
                    suffixFieldMap.put(rule, rule.substring(1));
                }
                if (rule.endsWith(WILDCARD)) {
                    prefixFieldMap.put(rule, rule.substring(0, rule.length() - 1));
                }
            }
        }
        if (cacheResult) {
            resultMap = CacheBuilder.newBuilder().softValues().maximumSize(1024).build();
        }

    }

    public static RegexFieldFilter of(String regex, boolean cacheResult) {
        return new RegexFieldFilter(regex, cacheResult);
    }

    public static RegexFieldFilter of(String regex) {
        return of(regex, false);
    }


    private boolean canSkip(String rule, String fieldName) {
        if (rule.contains(WILDCARD)) {
            if (suffixFieldMap.containsKey(rule)) {
                return fieldName.endsWith(suffixFieldMap.get(rule));
            }
            if (prefixFieldMap.containsKey(rule)) {
                return fieldName.startsWith(prefixFieldMap.get(rule));
            }
            //在中间或多个通配符
            String replace = StringUtils.replace(rule, WILDCARD, ".*");
            Pattern pattern = REGEX_MAP.asMap().computeIfAbsent(replace, Pattern::compile);
            return pattern.matcher(fieldName).matches();
        }
        return equalsField.contains(fieldName);
    }

    private boolean onlyOneWildcard(String rule) {
        if (!rule.contains(WILDCARD)) {
            return false;
        }
        return rule.indexOf(WILDCARD, rule.indexOf(WILDCARD) + 1) == -1;
    }

    @Override
    public boolean isSkipCompare(Method method, String methodKey, CompareUtil.CompareContext context) {
        return cacheResult ? resultMap.asMap().computeIfAbsent(methodKey, s -> judgeSkip(method)) : judgeSkip(method);

    }

    private boolean judgeSkip(Method method) {
        if (!CollectionUtils.isEmpty(rules)) {
            String name = method.getName();
            String fieldName = CompareUtil.getFieldName(name, method.getDeclaringClass().getCanonicalName());
            for (String rule : rules) {
                if (canSkip(rule, fieldName)) {
                    return true;
                }
            }
        }
        return false;
    }


}

测试代码如下,定义这个过滤会把属性名为arrays,带有'l',或者ma开头的属性都过滤掉,不参与比对逻辑

  boolean cacheResult = false;
        RegexFieldFilter of = RegexFieldFilter.of("arrays|*l*|ma*", cacheResult);
        CompareResult compareResult = CompareUtil.compareObjByGetter(source, other, of);
        assert checkPath(compareResult);
        assert compareResult.getDiffList().size() == 15;

ComopareUtil完整代码

https://github.com/97lele/redis-aux/tree/dev/common/src/main/java/com/xl/redisaux/common/utils/compare

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/780143.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

微软拼音输入法不显示选字框问题

问题展示&#xff1a;不显示选字框 解决方式 打开兼容性即可&#xff08;估计是升级带来的bug&#xff09;

STM32 - 内存分区与OTA

最近搞MCU&#xff0c;发现它与SOC之间存在诸多差异&#xff0c;不能沿用SOC上一些技术理论。本文以STM L4为例&#xff0c;总结了一些STM32 小白入门指南。 标题MCU没有DDR&#xff1f; 是的。MCU并没有DDR&#xff0c;而是让代码存储在nor flash上&#xff0c;临时变量和栈…

LeetCode题练习与总结:直线上最多的点数--149

一、题目描述 给你一个数组 points &#xff0c;其中 points[i] [xi, yi] 表示 X-Y 平面上的一个点。求最多有多少个点在同一条直线上。 示例 1&#xff1a; 输入&#xff1a;points [[1,1],[2,2],[3,3]] 输出&#xff1a;3示例 2&#xff1a; 输入&#xff1a;points [[1,…

水箱高低水位浮球液位开关

水箱高低水位浮球液位开关概述 水箱高低水位浮球液位开关是一种用于监测和控制水箱中液位的自动化设备&#xff0c;它能够在水箱液位达到预设的高低限制时&#xff0c;输出开关信号&#xff0c;以控制水泵或电磁阀的开闭&#xff0c;从而维持水箱液位在一个安全的范围内。这类设…

STM32快速复习(八)SPI通信

文章目录 前言一、SPI是什么&#xff1f;SPI的硬件电路&#xff1f;SPI发送的时序&#xff1f;二、库函数二、库函数示例代码总结 前言 SPI和IIC通信算是我在大学和面试中用的最多&#xff0c;问的最多的通信协议 IIC问到了&#xff0c;一般SPI也一定会问到。 SPI相对于IIC多了…

3.js - 模板渲染 - 简单

3.js 真tm枯燥啊&#xff0c;狗都不学 效果图 源码 // ts-nocheck// 引入three.js import * as THREE from three// 导入轨道控制器 import { OrbitControls } from three/examples/jsm/controls/OrbitControls// 导入lil.gui import { GUI } from three/examples/jsm/libs/li…

【数据结构】09.树与二叉树

一、树的概念与结构 1.1 树的概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的。 根结点&#xff1a;根…

webGL可用的14种3D文件格式,但要具体问题具体分析。

hello&#xff0c;我威斯数据&#xff0c;你在网上看到的各种炫酷的3d交互效果&#xff0c;背后都必须有三维文件支撑&#xff0c;就好比你网页的时候&#xff0c;得有设计稿源文件一样。WebGL是一种基于OpenGL ES 2.0标准的3D图形库&#xff0c;可以在网页上实现硬件加速的3D图…

阶段三:项目开发---大数据开发运行环境搭建:任务2:安装配置ZooKeeper

任务描述 知识点&#xff1a;安装配置ZooKeeper 重 点&#xff1a; 安装配置ZooKeeper 难 点&#xff1a;无 内 容&#xff1a; ZooKeeper是一个开源分布式协调服务&#xff0c;其独特的Leader-Follower集群结构&#xff0c;很好的解决了分布式单点问题。目前主要用于诸…

IT之家最新科技热点 | 小米 AI 研究院开创多模态通用模型

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…

一.2.(3)放大电路的图解分析方法和微变等效电路分析方法;

放大电路的主要分析方法:图解法、微变等效电路法 这里以共射放大电路为例 (1) 图解法: 1.静态分析 首先确定静态工作点Q,然后根据电路的特点,做出直流负载线,进而画出交流负载线,最后,画出各极电流电压的波形。求出最大不失真输出电压。 估算IBQ&#xff0c;然后根据数据手册里…

二分查找2

1. 山脉数组的峰顶索引&#xff08;852&#xff09; 题目描述&#xff1a; 算法原理&#xff1a; 根据题意我们可以将数组分为两个部分&#xff0c;一个部分是arr[mid-1]<arr[mid]&#xff0c;另一个部分为arr[mid-1]>arr[mid]&#xff0c;此时不难发现我们可以将二分…

U.S.News发布全美最佳本科AI专业排名

10 加州大学圣迭戈分校 University of California, San Diego UCSD的人工智能项目从事广泛的理论和实验研究&#xff0c;学校的优势领域包括机器学习、不确定性下的推理和认知建模。除了理论学习&#xff0c;UCSD教授非常注重把计算机知识运用到自然语言处理、数据挖掘、计算…

从模拟到预测,从智能到智慧:数字孪生技术在水库管理中的应用演变,推动水利行业向更高层次的智慧化迈进

目录 引言 一、数字孪生技术概述 二、数字孪生技术在水库管理中的应用演变 1. 从模拟到预测&#xff1a;构建精准的数字孪生模型 2. 从智能到智慧&#xff1a;实现全面感知与精准控制 3. 推动公众参与与透明化管理 三、数字孪生技术推动水利行业向更高层次的智慧化迈进 …

密室逃脱——收集版修改测试

一、原版修改 1、导入资源 Unity Learn | 3D Beginner: Complete Project | URP 2、设置Scene 删除SampleScene&#xff0c;打开UnityTechnologies-3DBeginnerComplete下的MainScene 3、降低音量 (1) 打开Hierarchy面板上的Audio降低音量 (2) 打开Prefabs文件夹&#xf…

推荐3款【王炸级别】的效率软件,免费无广告,你一定要收藏

Temp Cleaner Temp Cleaner 是一款专为 Windows 操作系统设计的临时文件清理工具。它的主要功能是安全且快速地清理磁盘上的临时文件和系统缓存&#xff0c;从而释放磁盘空间。该软件体积小巧&#xff08;仅有826KB&#xff09;&#xff0c;并且是无广告的绿色软件&#xff0c;…

智能交通(3)——Learning Phase Competition for Traffic Signal Control

论文分享 https://dl.acm.org/doi/pdf/10.1145/3357384.3357900https://dl.acm.org/doi/pdf/10.1145/3357384.3357900 论文代码 https://github.com/gjzheng93/frap-pubhttps://github.com/gjzheng93/frap-pub 摘要 越来越多可用的城市数据和先进的学习技术使人们能够提…

初学Spring之 AOP 面向切面编程

AOP&#xff08;Aspect Oriented Programming&#xff09;面向切面编程 通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术 是面向对象&#xff08;OOP&#xff09;的延续 AOP 在 Spring 中的作用&#xff1a; 1.提供声明式事务 2.允许用户自定义切面 导…

Java 基础--File - IO流(2)

I/O流 定义 数据从硬盘流向内存为输入流&#xff0c;数据从内存流向硬盘为输出流。输入也叫读取数据&#xff0c;输出也叫写出数据。 IO分类 1.按照数据的流向分为&#xff1a;输入流和输出流 ①输入流&#xff1a;把数据从其他设备上读取到内存中的流 ②输出流&#xff1…

pdf怎么转换成图片格式文件,pdf文档怎么转换成图片格式

在数字化时代&#xff0c;pdf文件转换成图片格式是一种常见的操作&#xff0c;无论是在工作还是日常生活中&#xff0c;我们总会遇到需要将pdf文件转换为图片的需求。这可能是因为图片格式更易于分享、展示或编辑。那么&#xff0c;如何高效地将pdf转换成图片呢&#xff1f;本文…