“万恶”之源的KieServices,获取代码就一行,表面代码越少里面东西就越多,本以为就是个简单的工厂方法,没想到里面弯弯绕绕这么多东西

news2024/12/23 5:51:04

Drools用户手册看了得有一段时间了,现在开始看源码了,因为每次使用drools都会看见这么一段代码:

代码段1 起手代码

KieServices ks = KieServices.Factory.get();

那我就从这段代码出发开始研究drools的源码吧,这么一小段代码起初我还真没看起它,结果被啪啪打脸了,里面东西可是多的狠啊,这段代码的目的不难看出来,就是获取KieServices的实例的,从代码来看,Factory就是KieSerices的内部类,然后其有一个get方法,用来获取KieServices的实例,源代码如下所示:

代码段2 KieServices类的内部类Factory

public static class Factory {
    public Factory() {
    }

    public static KieServices get() {
        return KieServices.Factory.LazyHolder.INSTANCE;
    }

    private static class LazyHolder {
        private static KieServices INSTANCE = (KieServices)ServiceRegistry
            .getService(KieServices.class);

        private LazyHolder() {
        }
    }
}

只看get方法,这里直接就是一个返回语句,返回的是LazyHolder,这又是一个内部类,也就是说LazyHolder是内部类的内部类,这个内部类的内部类里面有一个静态属性,叫做INSTANCE(实例),这个INSTANCE是如何获取的?是从ServiceRegistry类的getService方法获取的,因为要获取的就是KieServices的实例,所以将其作为参数传了进去。

这个时候就要研究ServiceRegistry类了,从类名来看,这是一个服务注册类,也就是说在获取KieService之前,这个服务已经在Kie中注册了 ,使用的时候就是直接获取,来看看这个ServiceRegistry的getService方法的源代码:

代码段3 ServiceRegistry的getService方法

static <T> T getService(Class<T> cls) {
    return getInstance().get(cls);
}

非常简单的代码,里面又涉及到了两个方法,一个是getInstance方法,一个是get方法。

ServiceRegistry类的getInstance方法

代码段4 ServiceRegistry的getinstance方法

static ServiceRegistry getInstance() {
    return ServiceRegistry.ServiceRegistryHolder.serviceRegistry;
}

这个方法涉及到了ServiceRegistry类中的内部类ServiceRegistryHolder的一个属性——serviceRegistry。

代码段5 ServiceRegistry的内部类ServiceRegistryHolder

public static class ServiceRegistryHolder {
    private static ServiceRegistry serviceRegistry = ServiceRegistry
        .Impl
        .getServiceRegistry();

    public ServiceRegistryHolder() {
    }
}

这是内部类ServiceRegistryHolder的全部源码,因为不多,就全放出来了,也很简单,serviceRegistry属性就是ServiceRegistry的内部类Impl中的getServiceRegistry方法,可以说从一个内部类调用另一个内部类的方法,我也是一时间没想到这么做的意义是什么,但是我之前在敲代码的时候好像也有这么干过,如果之后想起来,我再回来把想法给补上。

那就先来看看这个getServiceRegistry方法吧:

代码段6 ServiceRegistry的内部类Impl类的ServiceRegistry方法

public static ServiceRegistry() {
    if (supplier == null) {
        supplier = (Supplier)ServiceUtil
            .instanceFromNames(
                new String[]{"org.drools.dynamic.DynamicServiceRegistrySupplier", 
                    "org.drools.statics.StaticServiceRegistrySupplier"});
    }

    return (ServiceRegistry)supplier.get();
}

这是将传入的字符串数组进行流化,然后按照下面顺序进行操作:

  1. 映射:对每一个字符串进行操作,调用ServiceUtil的instance,通过全路径名实例化类,代码很简单,如下所示:

    代码段8
    private static Optional<?> instance(String className) {
        try {
            return Optional.of(Class.forName(className).getConstructor().newInstance());
        } catch (Exception var2) {
            return Optional.empty();
        }
    }

  2. 过滤:判断得到的实例化的类是否为空,如果为空则会被过滤掉

  3. 映射:将第二步没有过滤掉的实例从Optional中get出来

  4. 寻找:找到第一个符合条件的类,将其返回

  5. 异常:如果没有符合条件的类,就抛出异常

按照源码来走,这一步会获得到类DynamicServiceRegistrySupplier,源码如下:

代码段9 DynamicServiceRegistrySupplier源码

public class DynamicServiceRegistrySupplier implements Supplier<ServiceRegistry> {
    public DynamicServiceRegistrySupplier() {
    }

    public ServiceRegistry get() {
        return DynamicServiceRegistrySupplier.LazyHolder.INSTANCE;
    }

    static class LazyHolder {
        static final Impl INSTANCE = new Impl();

        LazyHolder() {
        }
    }
}

这一段代码会返回给代码段6,然后会执行get方法,这个方法返回的是ServiceRegistry内部类Impl的实例,这就是getInstance方法最终的结果,就是Impl的实例

内部类Impl的get方法

返回到代码段3中,里面的getInstance方法已经走了一遍,现在开始走这个get方法,其源码如下所示:

代码段10 ServiceRegistry的内部类Impl实现的get方法

public <T> T get(Class<T> cls) {
    Iterator var2 = this.getAll(cls).iterator();

    Object service;
    do {
        if (!var2.hasNext()) {
            return null;
        }

        service = var2.next();
    } while(!cls.isInstance(service));

    return service;
}

这段代码先是调用了一个getAll的方法,看看getAll的源码:

代码段11 ServiceRegistry的内部类Impl的getAll方法

public <T> List<T> getAll(Class<T> cls) {
    return (List)this.registry
        .getOrDefault(cls.getCanonicalName(), Collections.emptyList());
}

这个代码看似很简单,但是其实大部分的内容都在这里,这段代码是通过传入类的全路径名获取一个列表,如果没有该全路径名,就会返回一个空列表,那这个registry属性究竟是个什么呢?

代码段12 ServiceRegistry的内部类Impl的registry属性

private Map<String, List<Object>> registry = 
    ServiceDiscoveryImpl.getInstance().getServices();

是一个映射,我们可以看到,之前的操作,可没有对这个映射进行初始化的地方,这个东西又是另外的一个路线了,也就是在前面代码段9,实例化Impl的时候,这里就已经初始化了,为了这一路可以顺利结束,我先跳过这一段,咱们后面再讲,咱们回到代码段10,通过getAll获取了一个服务列表,通过迭代找到KieServices实例,然后返回该实例,这个时候代码段1就完成了调用,获得了一个KieServices。

服务注册与配置

这把我们再来看这个代码段12里面的东西,首先是ServiceDiscoveryImpl类,这个类的描述是这样的:

代码段13

public class ServiceDiscoveryImpl {...}

既不是哪个类的子类,也不是什么类的实现类,他就是一个孤零零的,类。那这就简单多了,我们直奔这个getInstance方法而去:

代码段14 ServiceDiscoveryImpl的getInstance方法和内部类LazyHolder

public static ServiceDiscoveryImpl getInstance() {
    return ServiceDiscoveryImpl.LazyHolder.INSTANCE;
}

private static class LazyHolder {
    static final ServiceDiscoveryImpl INSTANCE = new ServiceDiscoveryImpl();

    private LazyHolder() {
    }
}

这个getInstance方法,和之前的的某些代码段用法一致,获取某个内部类的属性,这个属性就是这个孤零零的类的实例,然后再是代码段12里面的getServices方法:

代码段15 ServiceDiscoveryImpl的getServices方法

public synchronized Map<String, List<Object>> getServices() {
    if (!this.sealed) {
        this.getKieConfs().ifPresent((kieConfs) -> {
            Iterator var2 = kieConfs.resources.iterator();

            while(var2.hasNext()) {
                URL kieConfUrl = (URL)var2.next();
                this.registerConfs(kieConfs.classLoader, kieConfUrl);
            }

        });
        //创建一个不可变的映射
        this.cachedServices = Collections.unmodifiableMap(this.buildMap());
        this.sealed = true;
    }

    return this.cachedServices;
}

这是一个同步方法,sealed初始默认值是false,所以进入方法后会直接进入到if语句中。先是获取到kie的配置内容,对非空配置进行遍历加载。获取配置是一个大活,操作异常的复杂,咱们就从这个getKieConfs方法开始走起:

代码段16 ServiceDiscoveryImpl的getKieConfs方法

private Optional<ServiceDiscoveryImpl.KieConfs> getKieConfs() {
    return Stream.of(this.getClass().getClassLoader(), 
            Thread.currentThread().getContextClassLoader(), 
            ClassLoader.getSystemClassLoader())
        .map(this::loadKieConfs)
        .filter(Objects::nonNull)
        .findFirst();
}

这个操作很简单,先是将三个类加载器传入Stream.of中,使其组成一个类加载器数组,然后将其流化。

对每一个类加载器执行lodaKieConfs操作,对map的返回值进行过滤,过滤掉null值,最后拿到第一个符合条件的配置,返回。这一步操作,只有lodaKieConfs是一个方法操作,这个操作的代码如下:

代码段17 ServiceDiscoveryImpl的loadKieConfs方法

private ServiceDiscoveryImpl.KieConfs loadKieConfs(ClassLoader cl) {
    if (cl == null) {
        return null;
    } else {
        try {
            Collection<URL> resources = findKieConfUrls(cl);
            return resources.isEmpty() ? null : new ServiceDiscoveryImpl.KieConfs(cl, resources);
        } catch (IOException var3) {
            return null;
        }
    }
}

先是判断传入的类加载器是否为空,如果不为空,会执行findKieConfUrls方法,该方法的代码如下:

代码段18 ServiceDiscoveryImpl的findKieConfUrls方法

private static Collection<URL> findKieConfUrls(ClassLoader cl) throws IOException {
    //声明一个空的URL地址列表
    List<URL> kieConfsUrls = new ArrayList();
    //获取类加载器中所有"Meta-INF/kie"文件夹下的资源的枚举
    Enumeration metaInfs = cl.getResources("META-INF/kie");

    //遍历枚举值
    while(metaInfs.hasMoreElements()) {
        //资源路径
        URL metaInf = (URL)metaInfs.nextElement();
        //如果资源是来自虚拟文件系统,则清空列表,并跳出循环
        //vfs是"Virtual File System"(虚拟文件系统)协议.
        if (metaInf.getProtocol().startsWith("vfs")) {
            ((List)kieConfsUrls).clear();
            break;
        }

        //打开资源连接
        URLConnection con = metaInf.openConnection();
        
        //判断当前连接类型是Jar还是其他类型
        if (con instanceof JarURLConnection) {
            //收集JAR中的kie配置地址
            collectKieConfsInJar((List)kieConfsUrls, metaInf, (JarURLConnection)con);
        } else {
            //收集文件中的kie配置地址
            collectKieConfsInFile((List)kieConfsUrls, new File(metaInf.getFile()));
        }
    }

    if (((List)kieConfsUrls).isEmpty()) {
        //如果经过之前操作,配置URL地址列表为空
        kieConfsUrls = (List)getKieConfsFromKnownModules(cl).collect(Collectors.toList());
    } else {
        //寻找未注册的模块
        List<String> notRegisteredModules = (List)((List)kieConfsUrls).stream()
            .map(ServiceDiscoveryImpl::getModuleName)
            .filter((module) -> {
                return Arrays.binarySearch(KIE_MODULES, module) < 0;
            }).collect(Collectors.toList());
        //如果未注册模块的列表不为空,则抛出异常
        if (!notRegisteredModules.isEmpty()) {
            throw new IllegalStateException("kie.conf file discovered for modules " + notRegisteredModules + " but not listed among the known modules. This will not work under OSGi or JBoss vfs.");
        }
    }
    
    //获取类加载器中所有"Meta-INF/kie.conf"文件
    Enumeration kieConfEnum = cl.getResources("META-INF/kie.conf");

    //直接将该文件的URL添加到URL地址列表中
    while(kieConfEnum.hasMoreElements()) {
        ((List)kieConfsUrls).add((URL)kieConfEnum.nextElement());
    }

    if (log.isDebugEnabled()) {
        log.debug("Discovered kie.conf files: " + kieConfsUrls);
    }

    //返回配置文件URL地址列表
    return (Collection)kieConfsUrls;
}

因为这是一段很长的代码,所以我把解释都放到了注释里面,这里面涉及到的其他方法的源代码如下:

收集JAR中的kie配置地址

代码段19 ServiceDiscoveryImpl的collectKieConfsInJar方法

private static void collectKieConfsInJar(List<URL> kieConfsUrls, 
    URL metaInf, 
    JarURLConnection con) throws IOException {
    //获取jar文件
    JarFile jarFile = con.getJarFile();
    //获取该jar里面的资源条目
    Enumeration entries = jarFile.entries();

    //遍历条目
    while(entries.hasMoreElements()) {
        JarEntry entry = (JarEntry)entries.nextElement();
        //如果条目是以kie.conf为结尾,则将其添加到kie配置地址列表中
        if (entry.getName().endsWith("kie.conf")) {
            String metaInfString = metaInf.toString();
            int confFileFolderLength = metaInfString.endsWith("/") ? "META-INF/kie".length() + 1 : "META-INF/kie".length();
            kieConfsUrls.add(new URL(metaInfString.substring(0, metaInfString.length() - confFileFolderLength) + entry.getName()));
        }
    }

}

收集文件中的kie配置地址

代码段20 ServiceDiscoveryImpl的collectKieConfsInFile方法

private static void collectKieConfsInFile(List<URL> kieConfsUrls, File file) throws IOException {
    if (file.isDirectory()) {
        //如果文件是一个文件夹,则获取文件夹里面的文件数组
        File[] var2 = file.listFiles();
        //获取文件数组的大小
        int var3 = var2.length;

        //遍历文件
        for(int var4 = 0; var4 < var3; ++var4) {
            File child = var2[var4];
            //递归调用
            collectKieConfsInFile(kieConfsUrls, child);
        }
    } else if (file.toString().endsWith("kie.conf")) {
        //如果文件是以kie.conf结尾,则将当前文件的URL直接加入到kie配置地址列表中
        kieConfsUrls.add(file.toURI().toURL());
    }
}

从已知的模块中寻找配置文件地址

代码段21 ServiceDiscoveryImpl的getKieConfsFromKnownModules方法

public static Stream<URL> getKieConfsFromKnownModules(ClassLoader cl) {
    return Stream.of(KIE_MODULES).map((module) -> {
        return cl.getResource("META-INF/kie/" + module + (module.length() > 0 ? "/" : "") + "kie.conf");
    }).filter(Objects::nonNull);
}

这里面主要的就是KIE_MODULES,需要将这里面的东西流化之后找到对应的资源地址

代码段22 ServiceDiscoveryImpl的KIE_MODULES常量

private static final String[] KIE_MODULES = new String[]{
    "", 
    "drools-alphanetwork-compiler", 
    "drools-beliefs", 
    "drools-compiler", 
    "drools-core", 
    "drools-decisiontables", 
    "drools-metric", 
    "drools-model-compiler", 
    "drools-mvel", 
    "drools-persistence-jpa", 
    "drools-ruleunit", 
    "drools-scorecards", 
    "drools-serialization-protobuf", 
    "drools-traits", 
    "drools-workbench-models-guided-dtable",
    "drools-workbench-models-guided-scorecard", 
    "drools-workbench-models-guided-template", 
    "jbpm-bpmn2", 
    "jbpm-case-mgmt-cmmn", 
    "jbpm-flow", "jbpm-flow-builder", 
    "jbpm-human-task-jpa", "kie-ci", 
    "kie-dmn-core", "kie-dmn-jpmml",
    "kie-internal", "kie-pmml",
    "kie-pmml-evaluator-assembler", 
    "kie-pmml-evaluator-core", 
    "kie-server-services-jbpm-cluster"};

注册配置

这个地方需要回到代码段15,在获取到配置文件地址列表之后,对配置进行一个注册

代码段23 ServiceDiscoveryImpl的registerConfs方法

public void registerConfs(ClassLoader classLoader, URL url) {
    log.debug("Loading kie.conf from {} in classloader {}", url, classLoader);
    try {
        BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()));
        try {
            for(String line = br.readLine(); line != null; line = br.readLine()) {
                if (line.contains("=") && !line.contains("[")) {
                    String[] entry = line.split("=");
                    //这里需要再解释一下<----------------<----------------<--------看这里
                    this.processKieService(classLoader, entry[0].trim(), entry[1].trim());
                }
            }
        } catch (Throwable var7) {
            try {
                br.close();
            } catch (Throwable var6) {
                var7.addSuppressed(var6);
            }

            throw var7;
        }

        br.close();
    } catch (Exception var8) {
        throw new RuntimeException("Unable to build kie service url = " + url.toExternalForm(), var8);
    }
}

这一段代码没什么好解释的,就是读取文件内容,进行处理,处理的内容需要好好解释一下,也就是代码段23中注释的地方

代码段24 ServiceDiscoveryImpl的processKieService方法

private void processKieService(ClassLoader classLoader, String key, String values) {
    //将值通过字符串“,”号分割为字符串数组
    String[] var4 = values.split(",");
    //获取分割后数量
    int var5 = var4.length;

    //遍历分割后的数组
    for(int var6 = 0; var6 < var5; ++var6) {
        String value = var4[var6];
        //判断key值是不是以“?”号开头
        boolean optional = key.startsWith("?");
        //将开头的“?”号去掉,获取service名
        String serviceName = optional ? key.substring(1) : key;

        try {
            if (value.startsWith("+")) {
                //如果值是以“+”开头,从childServices映射中获取serviceName对应的列表,
                //如果childServices没有serviceName关键字,则直接返回一个新的列表
                //将返回的列表中加入实例
                ((List)this.childServices.computeIfAbsent(serviceName, (k) -> {
                    return new ArrayList();
                }))
                .add(this.newInstance(classLoader, value.substring(1)));
                log.debug("Added child Service {}", value);
            } else {
                //通过符号“;”将值分割,如果分割的结果数组数量大于2,则抛出异常
                String[] splitValues = value.split(";");
                if (splitValues.length > 2) {
                    throw new RuntimeException("Invalid kie.conf entry: " + value);
                }

                //如果分割的数组长度是2,则优先级为数组下标为1对应的数,
                //如果分割的数组长度是1.也就是没有配置优先级,则优先级为0
                int priority = splitValues.length == 2 ? 
                    Integer.parseInt(splitValues[1].trim()) : 0;
                //将服务添加到一个优先级映射中
                this.services.put(priority, serviceName, 
                    this.newInstance(classLoader, splitValues[0].trim()));
                log.debug("Added Service {} with priority {}", value, priority);
            }
        } catch (RuntimeException var12) {
            if (!optional) {
                log.error("Loading failed because {}", var12.getMessage());
                throw var12;
            }

            log.info("Cannot load service: {}", serviceName);
        }
    }

}

总结

最后我们看一下,一小段短短的代码,里面却包含了如此多的工作,先是要实例化服务注册也就是Impl类,在实例化的时候需要通过服务发现类将所有的配置文件获取,建立服务列表,最后通过传入服务类的类名,获取服务类。流程就是这么个流程,你说简单他也简单,你说难我觉得你说的对,最后我也有个地方没有明白,可能是源码看太多脑子浆糊了,代码段15里面,还有一个buildMap的方法,源码如下,谁能给我解释解释,最后那一段代码在做什么?

代码段X ServiceDiscoveryImpl的buildMap方法

private Map<String, List<Object>> buildMap() {
    Map<String, List<Object>> servicesMap = new HashMap();
    //处理KieService方法里面的优先级映射转成迭代器
    Iterator var2 = this.services.entrySet().iterator();

    while(true) {
        Entry serviceEntry;
        List children;
        do {
            if (!var2.hasNext()) {
            //如果优先级映射里面没有下一个元素
                if (!this.childServices.isEmpty()) {
                //如果子服务列表不空,则抛出异常
                    throw new RuntimeException("Child services " + this.childServices.keySet() + " have no parent");
                }
                
                if (log.isTraceEnabled()) {
                    //如果日志是可追踪的,则将优先级列表里面的内容重新迭代一遍,用来打印日志
                    var2 = servicesMap.entrySet().iterator();
                    
                    while(var2.hasNext()) {
                        serviceEntry = (Entry)var2.next();
                        if (((List)serviceEntry.getValue()).size() == 1) {
                            log.trace("Service {} is implemented by {}", serviceEntry.getKey(), ((List)serviceEntry.getValue()).get(0));
                        } else {
                            log.trace("Service {} is implemented (in order of priority) by {}", serviceEntry.getKey(), serviceEntry.getValue());
                        }
                    }
                }

                return servicesMap;
            }

            //获取服务条目
            serviceEntry = (Entry)var2.next();
            log.debug("Service {} is implemented by {}", serviceEntry.getKey(), ((List)serviceEntry.getValue()).get(0));
            //将服务条目放入到服务映射中
            servicesMap.put((String)serviceEntry.getKey(), (List)serviceEntry.getValue());
            //移除childService中的已经添加到服务映射中的条目
            children = (List)this.childServices.remove(serviceEntry.getKey());
        } while(children == null);

//从这开始下面的这段代码我是没太搞懂是要做什么
        Iterator var5 = children.iterator();

        while(var5.hasNext()) {
            Object child = var5.next();
            Iterator var7 = ((List)serviceEntry.getValue()).iterator();

            while(var7.hasNext()) {
                Object service = var7.next();
                ((Consumer)service).accept(child);
            }
        }
    }
}

 

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

文件操作/IO

文件 文件是一种在硬盘上存储数据的方式&#xff0c;操作系统帮我们把硬盘的一些细节都封装起来了&#xff0c;程序员只需要了解文件相关的接口即可&#xff0c;相当于操作文件就是间接的操作硬盘了 硬盘用来存储数据&#xff0c;和内存相比硬盘的存储空间更大&#xff0c;访问…

代码运行出现:No module named ‘torch_geometric‘

这是没有torch_geometric库导致的&#xff0c;但是不能像一般库安装‘pip install 库名’或者‘conda install 库名’进行安装&#xff0c;经常会报错!!! 需要先安装四个小部件再安装torch_geometric&#xff0c;具体安装步骤如下&#xff1a; step 1&#xff1a;查看自己环境…

简约时尚的健康手表,智能守护每一刻,dido Y60上手

智能手表是现在很流行的一种智能设备&#xff0c;很多品牌都推出了各种各样的产品&#xff0c;但是大部分都更侧重功能和运动的方面&#xff0c;健康监测往往只是配角&#xff0c;而随着人们对自己的健康越来越重视&#xff0c;有些朋友只是单纯的需要一块专业的健康监测手表。…

SIFT 算法 | 如何在 Python 中使用 SIFT 进行图像匹配

介绍 人类通过记忆和理解来识别物体、人和图像。你看到某件事的次数越多,你就越容易记住它。此外,每当一个图像在你的脑海中弹出时,它就会将该项目或图像与一堆相关的图像或事物联系起来。如果我告诉你我们可以使用一种称为 SIFT 算法的技术来教机器做同样的事情呢? 尽管…

部署私有知识库项目FastGPT

FastGPT 是一个基于 LLM 大语言模型的知识库问答系统&#xff0c;提供开箱即用的数据处理、模型调用等能力。同时可以通过 Flow 可视化进行工作流编排&#xff0c;从而实现复杂的问答场景。 项目源码&#xff1a; GitHub - c121914yu/FastGPT: A platform that uses the Open…

【推荐】7个可以改变我们在3dMax中工作方式的插件

​以下给大家介绍的这些插件&#xff0c;将改变我们在3d Max中的工作方式&#xff0c;让生活更加轻松。 Glue Utility&#xff08;粘合实用程序&#xff09; 这些年来&#xff0c;这个工具改变了我的游戏规则。它使我能够执行许多任务&#xff0c;否则这些任务要么是不可能…

在矩池云使用 ChatGLM2-6B ptuning

本文参考 ChatGLM2-6B 官方文档&#xff0c;在矩池云复现了对于 ChatGLM2-6B 模型基于 P-Tuning v2 的微调。P-Tuning v2 将需要微调的参数量减少到原来的 0.1%&#xff0c;再通过模型量化、Gradient Checkpoint 等方法&#xff0c;最低只需要 7GB 显存即可运行。 官方文档地址…

一百五十六、Kettle——Linux上安装的Kettle9.3连接ClickHouse数据库(亲测,附流程截图)

一、目标 kettle9.3在Linux上安装好后&#xff0c;需要与ClickHouse数据库建立连接 二、前提准备 &#xff08;一&#xff09;在Linux已经安装好kettle并可以启动kettle &#xff08;二&#xff09;已知kettle和ClickHouse版本 1、kettle版本是9.3 2、ClickHouse版本是21…

深度学习优化器

1、什么是优化器 优化器用来寻找模型的最优解。 2、常见优化器 2.1. 批量梯度下降法BGD(Batch Gradient Descent) 2.1.1、BGD表示 BGD 采用整个训练集的数据来计算 cost function 对参数的梯度&#xff1a; 假设要学习训练的模型参数为W&#xff0c;代价函数为J(W)&#xff0c;…

项目管理工具探析:详细介绍四种常用选择

市场上的项目管理工具&#xff0c;主要是解决项目计划制定、任务协作、文档协作这几方面的问题&#xff0c; 下面简单聊聊一些自己用过的工具&#xff1a; 1、Excel/在线协作表格 如果项目简单&#xff0c;任务数少&#xff0c;没什么依赖&#xff0c;那么就可以用Excel来做项目…

静电放电发生器的操控模式和释放模式有哪些方式

静电放电是一种自然现象 &#xff0c;当两种不同介电强度的材料相互摩擦时&#xff0c;就会产生静电电荷&#xff0c;当其中一种材料上的静电荷积累到一定程度&#xff0c;在与另外一个物体接触时&#xff0c;就会通过这个物体到大地的阻抗而进行放电。静电放电及影响是电子设备…

四大运营商的大流量卡测评,看完您会选哪个运营商?

很多朋友都说网上的流量卡资费是真的便宜&#xff0c;但是小编认为资费便宜归便宜&#xff0c;但是运营商的小心思也有不少。 ​ 今天小编就带大家看一看三大运营商推出的正规流量卡都有哪些小心思&#xff1f; 首先&#xff0c;移动推出的线上大流量卡数量是最少的&#xff…

算法|Day37 动态规划5

LeetCode 1049- 最后一块石头的重量 II 题目链接&#xff1a;力扣 题目描述&#xff1a;有一堆石头&#xff0c;用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。 每一回合&#xff0c;从中选出任意两块石头&#xff0c;然后将它们一起粉碎。假设石头的重量…

Blazor : Component parameter should be auto property,无法修改get;set;

文章目录 Blazor 无法在get;set;里面定义方法 Blazor 无法在get;set;里面定义方法 Blazor Component组件 其实原因就是微软觉得你在get;set;里面放自定义逻辑&#xff0c;太容易出现无限父子回调的问题。如果你要在get时候进行某种逻辑&#xff0c;那你就在OnParameterSetAs…

归并排序 与 计数排序

目录 1.归并排序 1.1 递归实现归并排序&#xff1a; 1.2 非递归实现归并排序 1.3 归并排序的特性总结: 1.4 外部排序 2.计数排序 2.1 操作步骤: 2.2 计数排序的特性总结: 3. 7种常见比较排序比较 1.归并排序 基本思想: 归并排序(MERGE-SORT)是建立在归并操作上的一种…

Qgis统计面要素内点的数量,不同类型点的数量

简单&#xff1a; 统计面要素内点的数量。 工具栏直接搜索&#xff1a;统计点在多边形中的数量 统计面要素内不同类型点的数量。 查看QGIS-11 “按位置连接属性工具”——“按类别统计工具”——pandas透视表统计 数据透视表参考链接 参考链接&#xff1a; QGIS入门-9 统计面…

leetcode375. 猜数字大小 II(动态规划-java)

猜数字大小 II lc - 375 猜数字大小 II题目描述暴力递归 记忆化搜索代码演示动态规划 动态规划 lc - 375 猜数字大小 II 题目描述 我们正在玩一个猜数游戏&#xff0c;游戏规则如下&#xff1a; 我从 1 到 n 之间选择一个数字。 你来猜我选了哪个数字。 如果你猜到正确的数字&…

开学季 | 新生入学必备好物,快来看看你漏掉了哪些!

一转眼年就过完了&#xff0c;又到了开学季。有很多学生又要重新走进校园&#xff0c;开始自己的学生宿舍生活。作为一个从初中就开始住宿舍的过来人&#xff0c;有一些东西确实是我当时在学校里用过的&#xff0c;非常好用的神器&#xff0c;可以非常好的帮助我们生活、学习或…

界面组件DevExpress Reporting——支持图表本地化和可绑定属性

DevExpress Reporting是.NET Framework下功能完善的报表平台&#xff0c;它附带了易于使用的Visual Studio报表设计器和丰富的报表控件集&#xff0c;包括数据透视表、图表&#xff0c;因此您可以构建无与伦比、信息清晰的报表。 在最近的更新(v23.1)中&#xff0c;官方扩展了…

无脑入门pytorch系列(三)—— nn.Linear

本系列教程适用于没有任何pytorch的同学&#xff08;简单的python语法还是要的&#xff09;&#xff0c;从代码的表层出发挖掘代码的深层含义&#xff0c;理解具体的意思和内涵。pytorch的很多函数看着非常简单&#xff0c;但是其中包含了很多内容&#xff0c;不了解其中的意思…