2.单例模式,工厂模式,建造者模式,原型模式

news2024/11/18 9:45:29

单例模式

单例模式的优点:

  1. 处理资源访问冲突
  2. 表示全局唯一类

实现单例的关键:

  1. 构造函数需要是 private 访问权限的,这样才能避免外部通过 new 创建实例;
  2. 考虑对象创建时的线程安全问题;
  3. 考虑是否支持延迟加载;
  4. 考虑 getInstance() 性能是否高(是否加锁)。

1. 饿汉式

  • 在类加载的时候,instance 静态实例就已经创建并初始化好了,所以,instance 实例的创建过程是线程安全的。
public class IdGenerator {
    private AtomicLong id = new AtomicLong(0);
    private static final IdGenerator instance = new IdGenerator();
    private IdGenerator() {}
    public static IdGenerator getInstance() {
        return instance;
    }
    public long getId() {
        return id.incrementAndGet();
    }
}

提前初始化占用资源,但是让耗时的操作提前到启动的时候完成,解决初始化导致的性能问题

2. 懒汉式

public class IdGenerator {
    private AtomicLong id = new AtomicLong(0);
    private static IdGenerator instance;
    private IdGenerator() {}
    public static synchronized IdGenerator getInstance() {
        if (instance == null) {
            instance = new IdGenerator();
        }
        return instance;
    }
    public long getId() {
        return id.incrementAndGet();
    }
}

如果频繁地用到,那频繁加锁、释放锁及并发度低等问题,会导致性能瓶颈。

3. 双重检测

饿汉式不支持延迟加载,懒汉式有性能问题,不支持高并发。

public class IdGenerator {
    private AtomicLong id = new AtomicLong(0);
    private static volatile IdGenerator instance;

    private IdGenerator() {
    }

    public static IdGenerator getInstance() {
        if (instance == null) {
            synchronized (IdGenerator.class) { // 此处为类级别的锁
                if (instance == null) {
                    instance = new IdGenerator();
                }
            }
        }
        return instance;
    }

    public long getId() {
        return id.incrementAndGet();
    }
}

CPU 指令重排序可能导致在 IdGenerator 类的对象被关键字 new 创建并赋值给 instance 之后,还没来得及初始化(执行构造函数中的代码逻辑),就被另一个线程使用了(volatile 来禁止指令重排序)。

4. 静态内部类

有点类似饿汉式,但又能做到了延迟加载

public class IdGenerator {
    private AtomicLong id = new AtomicLong(0);

    private IdGenerator() {
    }

    private static class SingletonHolder {
        private static final IdGenerator instance = new IdGenerator();
    }

    public static IdGenerator getInstance() {
        return SingletonHolder.instance;
    }

    public long getId() {
        return id.incrementAndGet();
    }
}

SingletonHolder 是一个静态内部类,当外部类 IdGenerator 被加载的时候,并不会创建
SingletonHolder 实例对象。只有当调用 getInstance() 方法时,SingletonHolder 才会被加载,这个时候才会创建 instance。instance 的唯一性、创建过程的线程安全性,都由 JVM 来保证。所以,这种实现方法既保证了线程安全,又能做到延迟加载。

5. 枚举

通过 Java枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性。

public enum IdGenerator {
    INSTANCE;
    private AtomicLong id = new AtomicLong(0);
    public long getId() {
        return id.incrementAndGet();
    }
}

单例的缺点

  1. 单例对 OOP 特性的支持不友好(一旦你选择将某个类设计成到单例类,也就意味着放弃了继承和多态这两个强有力的面向对象特性,也就相当于损失了可以应对未来需求变化的扩展性)
  2. 单例会隐藏类之间的依赖关系(单例类不需要显示创建、不需要依赖参数传递,在函数中直接调用就可以了。如果代码比较复杂,这种调用关系就会非常隐蔽。)
  3. 单例对代码的扩展性不友好(单例类只能有一个对象实例,如果需要创建两个实例或
    多个实例,那就要对代码有比较大的改动(比如 想要变成2个线程池,一个是快线程池,一个是慢线程池))
  4. 单例对代码的可测试性不友好
  5. 单例不支持有参数的构造函数

单例的替代方案

  1. 我们还可以用静态方法来实现(比单例更不灵活,不能支持懒加载)
public class IdGenerator {
    private static AtomicLong id = new AtomicLong(0);

    public static long getId() {
        return id.incrementAndGet();
    }
}
    // 使用举例
    long id = IdGenerator.getId();

2.依赖注入(可以解决单例隐藏类之间依赖关系的问题,但是对 OOP 特性、扩展性、可测性不友好等问题,还是无法解决)

public demofunction(IdGenerator idGenerator){
        long id=idGenerator.getId();
        }
// 外部调用demofunction()的时候,传入idGenerator
        IdGenerator idGenerator=IdGenerator.getInsance();
        demofunction(idGenerator);
        }
  1. 我们既可以通过单例模式来强制保证,也可以通过工厂模式、IOC 容器(比如 Spring IOC 容器)来保证,还可以通过程序员自己来保证(自己在编写代码的时候自己保证不要创建两个类对象)

单例类中对象的唯一性的作用范围是进程内的,在进程间是不唯一的。
如果fork一个线程出来,会在新线程也创建一个单例对象

如何实现线程唯一的单例?

  • 通过一个 HashMap 来存储对象,其中 key 是线程 ID,value 是对象。这样我们就可以
    做到,不同的线程对应不同的对象,同一个线程只能对应一个对象。
  • ThreadLocal就是这个的实现

实现集群间单例

  • 把这个单例对象序列化并存储到外部共享存储区(比如文件)。进程在使用这个单例对象的时候,需要先从外部共享存储区中将它读取到内存,并反序列化成对象,然后再使用,使用完成之后还需要再存储回外部共享存储区。
  • 为了保证任何时刻,在进程间都只有一份对象存在,一个进程在获取到对象之后,需要对对象加锁,避免其他进程再将其获取。在进程使用完这个对象之后,还需要显式地将对象从内存中删除,并且释放对对象的加锁。
public class IdGenerator {
    private AtomicLong id = new AtomicLong(0);
    private static IdGenerator instance;
    private static SharedObjectStorage storage = FileSharedObjectStorage(/*入参省略*/);
    private static DistributedLock lock = new DistributedLock();

    private IdGenerator() {
    }

    public synchronized static IdGenerator getInstance() {
        if (instance == null) {
            lock.lock();
            instance = storage.load(IdGenerator.class);
        }
        return instance;
    }

    public synchronized void freeInstance() {
        storage.save(this, IdGeneator.class);
        instance = null; //释放对象
        lock.unlock();
    }

    public long getId() {
        return id.incrementAndGet();
    }
}
    // IdGenerator使用举例
    IdGenerator idGeneator = IdGenerator.getInstance();
    long id = idGenerator.getId();

    idGenerator.freeInstance();

实现一个多例模式?

public class BackendServer {
    private long serverNo;
    private String serverAddress;
    private static final int SERVER_COUNT = 3;
    private static final Map<Long, BackendServer> serverInstances = new HashMap<>()

    static {
        serverInstances.put(1L, new BackendServer(1L, "192.134.22.138:8080"));
        serverInstances.put(2L, new BackendServer(2L, "192.134.22.139:8080"));
        serverInstances.put(3L, new BackendServer(3L, "192.134.22.140:8080"));
    }

    private BackendServer(long serverNo, String serverAddress) {
        this.serverNo = serverNo;
        this.serverAddress = serverAddress;
    }

    public BackendServer getInstance(long serverNo) {
        return serverInstances.get(serverNo);
    }

    public BackendServer getRandomInstance() {
        Random r = new Random();
        int no = r.nextInt(SERVER_COUNT) + 1;
        return serverInstances.get(no);
    }
}

多例模式创建的对象都是同一个类的对象,而工厂模式创建的是不同子类的对象

工厂模式

简单工厂(Simple Factory)

/**
* 根据不同后缀,选择不同的解析器
*/
public class RuleConfigSource {
    public RuleConfig load(String ruleConfigFilePath) {
        String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);
        IRuleConfigParser parser = RuleConfigParserFactory.createParser(ruleConfigFil
        if (parser == null) {
            throw new InvalidRuleConfigException(
                    "Rule config file format is not supported: " + ruleConfigFilePath);
        }
        String configText = "";
//从ruleConfigFilePath文件中读取配置文本到configText中
        RuleConfig ruleConfig = parser.parse(configText);
        return ruleConfig;
    }

    private String getFileExtension(String filePath) {
//...解析文件名获取扩展名,比如rule.json,返回json
        return "json";
    }
}

public class RuleConfigParserFactory {
    public static IRuleConfigParser createParser(String configFormat) {
        IRuleConfigParser parser = null;
        if ("json".equalsIgnoreCase(configFormat)) {
            parser = new JsonRuleConfigParser();
        } else if ("xml".equalsIgnoreCase(configFormat)) {
            parser = new XmlRuleConfigParser();
        } else if ("yaml".equalsIgnoreCase(configFormat)) {
            parser = new YamlRuleConfigParser();
        } else if ("properties".equalsIgnoreCase(configFormat)) {
            parser = new PropertiesRuleConfigParser();
        }
        return parser;
    }
}

大部分工厂类都是以“Factory”这个单词结尾的,工厂类中创建对象的方法一般都是 create 开头

我们每次调用 RuleConfigParserFactory 的 createParser() 的时候,都要创建一个新的 parser,所以可以将 parser 事先创建好缓存起来

public class RuleConfigParserFactory {
    private static final Map<String, RuleConfigParser> cachedParsers = new HashMap<
    static {
        cachedParsers.put("json", new JsonRuleConfigParser());
        cachedParsers.put("xml", new XmlRuleConfigParser());
        cachedParsers.put("yaml", new YamlRuleConfigParser());
        cachedParsers.put("properties", new PropertiesRuleConfigParser());
    }
    public static IRuleConfigParser createParser(String configFormat) {
        if (configFormat == null || configFormat.isEmpty()) {
            return null;//返回null还是IllegalArgumentException全凭你自己说了算
        }
        IRuleConfigParser parser = cachedParsers.get(configFormat.toLowerCase());
        return parser;
    }
}

尽管简单工厂模式的代码实现中,有多处 if 分支判断逻辑,违背开闭原则,但权
衡扩展性和可读性,这样的代码实现在大多数情况下(比如,不需要频繁地添加 parser,也
没有太多的 parser)是没有问题的。

工厂方法(Factory Method)

可以通过多态避免多的if

public interface IRuleConfigParserFactory {
    IRuleConfigParser createParser();
}

public class JsonRuleConfigParserFactory implements IRuleConfigParserFactory {
    @Override
    public IRuleConfigParser createParser() {
        return new JsonRuleConfigParser();
    }
}

public class XmlRuleConfigParserFactory implements IRuleConfigParserFactory {
    @Override
    public IRuleConfigParser createParser() {
        return new XmlRuleConfigParser();
    }
}

public class YamlRuleConfigParserFactory implements IRuleConfigParserFactory {
    @Override
    public IRuleConfigParser createParser() {

        return new YamlRuleConfigParser();
    }
}

public class PropertiesRuleConfigParserFactory implements IRuleConfigParserFactor {

    @Override
    public IRuleConfigParser createParser() {
        return new PropertiesRuleConfigParser();
    }
}

public class RuleConfigParserFactoryMap { //工厂的工厂
    private static final Map<String, IRuleConfigParserFactory> cachedFactories = ne

    static {
        cachedFactories.put("json", new JsonRuleConfigParserFactory());
        cachedFactories.put("xml", new XmlRuleConfigParserFactory());
        cachedFactories.put("yaml", new YamlRuleConfigParserFactory());
        cachedFactories.put("properties", new PropertiesRuleConfigParserFactory());
    }

    public static IRuleConfigParserFactory getParserFactory(String type) {
        if (type == null || type.isEmpty()) {
            return null;
        }
        IRuleConfigParserFactory parserFactory = cachedFactories.get(type.toLowerCase
        return parserFactory;
    }
}

工厂方法模式比起简单工厂模式更加符合开闭原则。

那什么时候该用工厂方法模式,而非简单工厂模式呢?

当对象的创建逻辑比较复杂,不只是简单的 new 一下就可以,而是要组合其他类对象,做各种初始化操作的时候,我们推荐使用工厂方法模式,将复杂的创建逻辑拆分到多个工厂类中,让每个工厂类都不至于过于复杂。而使用简单工厂模式,将所有的创建逻辑都放到一个工厂类中,会导致这个工厂类变得很复杂。

抽象工厂(Abstract Factory)

在简单工厂和工厂方法中,类只有一种分类方式。如果还用简单工厂或者工厂方法就会创建过多的工厂类。抽象工厂可以让一个工厂负责创建多个不同类型的对象,有效地减少工厂类的个数。

public interface IConfigParserFactory {
    IRuleConfigParser createRuleParser();
    ISystemConfigParser createSystemParser();
//此处可以扩展新的parser类型,比如IBizConfigParser
}
public class JsonConfigParserFactory implements IConfigParserFactory {
    @Override
    public IRuleConfigParser createRuleParser() {
        return new JsonRuleConfigParser();
    }
    @Override
    public ISystemConfigParser createSystemParser() {
        return new JsonSystemConfigParser();
    }
}
public class XmlConfigParserFactory implements IConfigParserFactory {
    @Override
    public IRuleConfigParser createRuleParser() {
        return new XmlRuleConfigParser();
    }
    @Override
    public ISystemConfigParser createSystemParser() {
        return new XmlSystemConfigParser();
    }
}

工厂模式和 DI 容器有何区别?

一个工厂类只负责某个类对象或者某一组相关类对象(继承自同一抽象类或者接口的子类)的创建,而 DI 容器负责的是整个应用中所有类对象的创建。

DI 容器的核心功能一般有三个:配置解析、对象创建和对象生命周期管理。

  • 将需要由 DI 容器来创建的类对象和创建类对象的必要信息(使用哪个构造函数以及对应的构造函数参数都是什么等等),放到配置文件中。容器读取配置文件,根据配置文件提供的信息来创建对象。
  • 通过“反射”,它能在程序运行的过程中,动态地加载类、创建对象。
  • 多例或者单例对象,是否懒加载,配置创建和销毁时调用的方法。

建造者

当创建对象要传递很多的参数,容易传递错误的参数,并且影响可读性
使用set方法可以解决创建对象参数列表过多的参数,但是不能解决各个参数设置直接的依赖关系,或者约束条件就很难解决(比如maxIdle要大于minIdle)
如果对象在创建好之后,就不能再修改内部的属性值,不能暴露ResourcePoolConfig 的set方法
这个时候就可以使用建造者模式(把校验逻辑放置到 Builder 类中,然后在使用 build() 方法真正创建对象之前,我们把 ResourcePoolConfig 的构造函数改为 private 私有权限,ResourcePoolConfig 没有提供任何 set() 方法,这样我们创建出来的对象就是不可变对象了)

public class ResourcePoolConfig {
    private String name;
    private int maxTotal;
    private int maxIdle;
    private int minIdle;
    private ResourcePoolConfig(Builder builder) {
        this.name = builder.name;
        this.maxTotal = builder.maxTotal;
        this.maxIdle = builder.maxIdle;
        this.minIdle = builder.minIdle;
    }
    //...省略getter方法...
//我们将Builder类设计成了ResourcePoolConfig的内部类。
//我们也可以将Builder类设计成独立的非内部类ResourcePoolConfigBuilder。
    public static class Builder {
        private static final int DEFAULT_MAX_TOTAL = 8;
        private static final int DEFAULT_MAX_IDLE = 8;
        private static final int DEFAULT_MIN_IDLE = 0;
        private String name;
        private int maxTotal = DEFAULT_MAX_TOTAL;
        private int maxIdle = DEFAULT_MAX_IDLE;
        private int minIdle = DEFAULT_MIN_IDLE;
        public ResourcePoolConfig build() {
        // 校验逻辑放到这里来做,包括必填项校验、依赖关系校验、约束条件校验等
            if (StringUtils.isBlank(name)) {
                throw new IllegalArgumentException("...");
            }

            return new ResourcePoolConfig(this);
        }
        public Builder setName(String name) {
            this.name = name;
            return this;
        }
        public Builder setMaxTotal(int maxTotal) {
            this.maxTotal = maxTotal;
            return this;
        }
        public Builder setMaxIdle(int maxIdle) {
            this.maxIdle = maxIdle;
            return this;
        }
        public Builder setMinIdle(int minIdle) {
            this.minIdle = minIdle;
            return this;
        }
    }
}
    // 这段代码会抛出IllegalArgumentException,因为minIdle>maxIdle
    ResourcePoolConfig config = new ResourcePoolConfig.Builder()
            .setName("dbconnectionpool")
            .setMaxTotal(16)
            .setMaxIdle(10)
            .setMinIdle(12)
            .build();

工厂模式是用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。建造者模式是用来创建一种类型的复杂对象,通过设置不同的可选参数,“定制化”地创建不同的对象。

原型模式

如果对象的创建成本比较大,而同一个类的不同对象之间差别不大(大部分字段都相同),在这种情况下,我们可以利用对已有对象(原型)进行复制(或者叫拷贝)的方式来创建新对象,以达到节省创建时间的目的。这种基于原型来创建对象的方式就叫作原型设计模式(Prototype Design Pattern),简称原型模式。
(如果对象中的数据需要经过复杂的计算才能得到(比如排序、计算哈希值),或者需要从 RPC、网络、数据库、文件系统等非常慢速的 IO 中读取)可以使用原型模式。

如果一次有10W条数据需要查询,使用原型模式,不断增加

public class Demo {
    private HashMap<String, SearchWord> currentKeywords=new HashMap<>();
    private long lastUpdateTime = -1;
    public void refresh() {
// 原型模式就这么简单,拷贝已有对象的数据,更新少量差值
        HashMap<String, SearchWord> newKeywords = (HashMap<String, SearchWord>) currentKeywords.clone();
// 从数据库中取出更新时间>lastUpdateTime的数据,放入到newKeywords中
        List<SearchWord> toBeUpdatedSearchWords = getSearchWords(lastUpdateTime);
        long maxNewUpdatedTime = lastUpdateTime;
        for (SearchWord searchWord : toBeUpdatedSearchWords) {
            if (searchWord.getLastUpdateTime() > maxNewUpdatedTime) {
                maxNewUpdatedTime = searchWord.getLastUpdateTime();
            }
            if (newKeywords.containsKey(searchWord.getKeyword())) {
                SearchWord oldSearchWord = newKeywords.get(searchWord.getKeyword());
                oldSearchWord.setCount(searchWord.getCount());
                oldSearchWord.setLastUpdateTime(searchWord.getLastUpdateTime());
            } else {
                newKeywords.put(searchWord.getKeyword(), searchWord);
            }
        }
        lastUpdateTime = maxNewUpdatedTime;
        currentKeywords = newKeywords;
    }
    private List<SearchWord> getSearchWords(long lastUpdateTime) {
// TODO: 从数据库中取出更新时间>lastUpdateTime的数据
        return null;
    }
}

深拷贝和浅拷贝

浅拷贝得到的对象跟原始对象共享数据,而深拷贝得到的是一份完完全全独立的对象

在这里插入图片描述

  • 在 Java 语言中,Object 类的 clone() 方法执行的就是我们刚刚说的浅拷贝。它只会拷贝对象中的基本数据类型的数据(比如,int、long),以及引用对象(SearchWord)的内存地址,不会递归地拷贝引用对象本身。(如果使用浅拷贝可能会存在数据不一致的情况)

深拷贝的两种方法

  • 递归拷贝对象、对象的引用对象以及引用对象的引用对象……直到要拷贝的对象只包含基本数据类型数据,没有引用对象为止。
public class Demo {
    private HashMap<String, SearchWord> currentKeywords = new HashMap<>();
    private long lastUpdateTime = -1;

    public void refresh() {
// Deep copy
        HashMap<String, SearchWord> newKeywords = new HashMap<>();
        for (HashMap.Entry<String, SearchWord> e : currentKeywords.entrySet()) {
            SearchWord searchWord = e.getValue();
            SearchWord newSearchWord = new SearchWord(
                    searchWord.getKeyword(), searchWord.getCount(), searchWord.getLastU
                    newKeywords.put(e.getKey(), newSearchWord);
        }
// 从数据库中取出更新时间>lastUpdateTime的数据,放入到newKeywords中
        List<SearchWord> toBeUpdatedSearchWords = getSearchWords(lastUpdateTime);
        long maxNewUpdatedTime = lastUpdateTime;
        for (SearchWord searchWord : toBeUpdatedSearchWords) {
            if (searchWord.getLastUpdateTime() > maxNewUpdatedTime) {
                maxNewUpdatedTime = searchWord.getLastUpdateTime();
            }
            if (newKeywords.containsKey(searchWord.getKeyword())) {
                SearchWord oldSearchWord = newKeywords.get(searchWord.getKeyword());
                oldSearchWord.setCount(searchWord.getCount());
                oldSearchWord.setLastUpdateTime(searchWord.getLastUpdateTime());
            } else {
                newKeywords.put(searchWord.getKeyword(), searchWord);
            }
        }
        lastUpdateTime = maxNewUpdatedTime;
        currentKeywords = newKeywords;
    }

    private List<SearchWord> getSearchWords(long lastUpdateTime) {
// TODO: 从数据库中取出更新时间>lastUpdateTime的数据
        return null;
    }
}
  • 先将对象序列化,然后再反序列化成新的对象。
    public Object deepCopy(Object object) {
        ByteArrayOutputStream bo = new ByteArrayOutputStream();
        ObjectOutputStream oo = new ObjectOutputStream(bo);
        oo.writeObject(object);
        ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
        ObjectInputStream oi = new ObjectInputStream(bi);
        return oi.readObject();
    }

深拷贝都要比浅拷贝耗时、耗内存空间,我们可以先采用浅拷贝的方式创建 newKeywords。对于需要更新的 SearchWord 对象,我们再使用深度拷贝的方式创建一份新的对象,替换 newKeywords 中的老对象。

    public void refresh() {
        // Shallow copy
        HashMap<String, SearchWord> newKeywords = (HashMap<String, SearchWord>) currentKeywords.clone();
        // 从数据库中取出更新时间>lastUpdateTime的数据,放入到newKeywords中
        List<SearchWord> toBeUpdatedSearchWords = getSearchWords(lastUpdateTime);
        long maxNewUpdatedTime = lastUpdateTime;
        for (SearchWord searchWord : toBeUpdatedSearchWords) {
            if (searchWord.getLastUpdateTime() > maxNewUpdatedTime) {
                maxNewUpdatedTime = searchWord.getLastUpdateTime();
            }
            if (newKeywords.containsKey(searchWord.getKeyword())) {
                newKeywords.remove(searchWord.getKeyword());
            }
            newKeywords.put(searchWord.getKeyword(), searchWord);
        }
        lastUpdateTime = maxNewUpdatedTime;
        currentKeywords = newKeywords;
    }

如果要拷贝的对象是不可变对象,浅拷贝共享不可变对象是没问题的,但对于可变对象来说,浅拷贝得到的对象和原始对象会共享部分数据,就有可能出现数据被修改的风险,

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

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

相关文章

Deformable DETR TBD范式的不二选择

TBD范式检测和跟踪是不分家的。当前,性能较优的目标检测方法大都基于Transformer来做,CNN在目标检测的表现逐渐走低。DETR是基于Transformer的目标检测开山作,其解决了霸榜的yolo系列一些令人讨厌的事情,不需要前处理和后处理,做到了真正意义上的end to end: 前处理:Anc…

测试做得好,犯错少不了【30个最容易犯的错误】谨记

最近跟一些刚刚进入软件测试行业的朋友去交流&#xff0c;发现了一个有趣的现象&#xff0c;就是对于这个行业的很多问题的认识都是一致的片面&#xff0c;当然也可以理解为误区。自己利用一点时间&#xff0c;把他们对于这个行业的认识误区都罗列出来&#xff0c;然后结合自己…

Centos7搭建Hadoop集群(V3.3.4)

Centos7搭建Hadoop集群V3.3.4一、准备工作1、配置hostname2、hosts映射3、关闭防火墙4、同步时间5、关闭selinux6、配置ssh免密登陆7、重启二、安装所需环境1、jdk安装2、hadoop安装三、修改配置hadoop-env.shcore-site.xmlhdfs-site.xmlmapred-site.xmlyarn-site.xmlworkers四…

web测试1:在ubuntu中运行boa

参考文档&#xff1a; https://blog.csdn.net/feit2417/article/details/84777523 1.下载boa源码 Boa Webserver主页&#xff1a;Boa Webserver 下载链接&#xff1a;http://www.boa.org/boa-0.94.13.tar.gz 下载后&#xff0c;解压 tar zxvf boa-0.94.13.tar.gz。 文件列表…

Mysql-query优化之explainProfiling

1、explain 要对执行计划有个比较好的理解&#xff0c;需要先对MySQL的基础结构及查询基本原理有简单的了解。 MySQL本身的功能架构分为三个部分&#xff0c;分别是 应用层、逻辑层、物理层&#xff0c;不只是MySQL &#xff0c;其他大多数数据库产品都是按这种架构来进行划分的…

Spring的快速入门代码实现

Spring的快速入门代码实现前言&#xff1a;需要创建Maven的项目&#xff08;这个一定要看下面的链接&#xff09;1.如何创建Maven项目直达链接2.将这个项目的名字 命名为spring_ioc一、导入坐标&#xff08;pom.xml文件中实现&#xff09;1.1 代码&#xff1a;1.2 version的版本…

【GD32F427开发板试用】从0开始到RTthread移植

本篇文章来自极术社区与兆易创新组织的GD32F427开发板评测活动&#xff0c;更多开发板试用活动请关注极术社区网站。作者&#xff1a;军军 写在前面: 很高兴获得这次试用机会&#xff0c;一直很想了解国内ARM MCU的发展&#xff0c;尤其是GD32系列的MCU。我也成功获得了第一次使…

PyQt5开发环境搭建 1.2 简单的例子

目录 基本开发步骤 创建Eric6工程目录 使用Qt Creator创建Qt项目 创建项目 Kit Selection 打开编辑窗口 打开form 放一个Label 拖动Label 放一个Button 拖动Push Button 保存UI文件 编译ui文件 当前项目下文件列表 将ui文件编译成py文件 将ui文件和py文件拷贝到…

学习记录678@项目管理之合同管理案例

案例 系统集成商 Simple 公司与生产型企业 Perfect 集团签订了一份企业MIS(管理信息系统)开发合同&#xff0c;合同已执行到设计和开发阶段&#xff0c;由于 Perfect 集团内部组织结构调整可能会影响核心业务的流程。集成商 Simple 公司提出建议&#xff0c;合同暂停执行至新的…

使用 Jenkins + Gitee + maven 自动化部署 Spring Boot

目录 1、前言 1 .1、插件简介 1.2、插件安装 2、创建Maven项目 2.1、新建一个全新的项目 2.2、拷贝已有项目 3、项目配置 3.1 、General 3.2、源码管理 3.3、构建触发器 3.4、构建环境 3.5、Pre Steps 3.6、Build 1&#xff09;Root POM 2&#xff09;Goals and…

kubenertes集群部署kubeadm方式

一、安装要求 1、3台机器&#xff09;&#xff0c;操作系统Centos7,5.4内核,CentOS 7.x 系统自带的3.10.x内核存在一些Bugs.导致运行的Docker.Kubernetes不稳定。 2、硬件配置:4GB内存&#xff0c;至少2个CPU或更多CPU&#xff0c;至少硬盘30GB或更多 3、集群中所有机器之间网…

41 锚框【动手学深度学习v2】】

41 锚框【动手学深度学习v2】】 锚框&#xff1a;对边框位置的猜测。 先提出多个框在某个地方&#xff0c;比如5个&#xff0c;然后去看这5个框里面到底有没有物体。 两次预测&#xff1a;锚框位置和锚框内物体的预测。 如何处理锚框&#xff1f; IoU - 交并比 比较两个框之…

Java开发实现图书管理系统(超详细)

本文用Java代码实现图书代码管理系统&#xff0c;有些地方可能会有纰漏&#xff0c;希望各位大佬鉴赏&#xff01;&#xff01; 文章目录 文章目录 一、Java实现图书管理系统 1.1创建book包 二、创建图书管理系统的操作包 2.1创建Operation接口 三、创建User包 3.1创建User类 四…

深度盘点时间序列预测方法

本篇介绍时间序列的定义、任务、构成以及预测方法&#xff0c;主要是基本概念的介绍和理解。 时间序列定义 时间序列&#xff0c;通俗的字面含义为一系列历史时间的序列集合。比如2013年到2022年我国全国总人口数依次记录下来&#xff0c;就构成了一个序列长度为10的时间序列…

CAN FD的一致性测试 助力汽车电子智能化

后起之秀——CAN FD&#xff1a;随着各个行业的快速发展&#xff0c;消费者对汽车电子智能化的诉求越来越强烈&#xff0c;这也致使整车厂将越来越多的电子控制系统加入到汽车控制中。在传统汽车、新能源汽车、ADAS和自动驾驶等汽车领域中&#xff0c;无不催生着更高的需求&…

4.kafka--生产调优

文章目录1.硬件配置选择1.场景说明2.服务器台数选择3.磁盘选择4.内存选择1) 堆内存配置2&#xff09;页缓存配置5. cpu选择6.网络选择2.生产者3.kafka broker4. 服役新节点&#xff0c;退役旧节点1&#xff09;创建一个要均衡的主题。2) 生成一个负载均衡的计划leader分布不均匀…

多人配音怎么做的?这两个多人配音方法分享给你

大家在刷一些短视频的时候&#xff0c;肯定有看到过一些搞笑的视频&#xff0c;而这些视频总能让我们捧腹大笑&#xff0c;过后再多看几次&#xff0c;其实你可以明显的发现这是多人互动对话或者一人分饰多角所呈现的&#xff0c;我们想要做出这种类型的视频&#xff0c;一般需…

法律常识(五)《消费者权益保护法》解读与举例

目录 酒楼谢绝客户自带酒水&#xff0c;合法吗&#xff1f; 侵犯消费者人格尊严&#xff0c;应承担民事责任 某公司出售伪劣产品致人损害&#xff0c;需双倍赔偿 农民购买、使用直接用于农业生产的生产资料 《中华人民共和国产品质量法》相关记录 《中华人民共和国反不正…

Android 双屏异显(Presentation) 开发,将第二个页面投屏到副屏上

1. 背景 最近开发的一个项目&#xff0c;有两个屏幕&#xff0c;需要将第二个页面投屏到副屏上&#xff0c;这就需要用到Android的双屏异显(Presentation)技术了&#xff0c;研究了一下&#xff0c;这里做下笔记。 我们那个副屏是一块汽车的后视镜(流媒体后视镜)&#xff0c;是…

超详细:KNN与K-means从入门到实战

作者&#xff1a;王同学 来源&#xff1a;投稿 编辑&#xff1a;学姐 1. 基本概念 1.1 KNN k近邻法&#xff08;k-nearest neighbor&#xff0c;k-NN&#xff09;是一种基本分类与回归方法。 k近邻法的输入为实例的特征向量对应于特征空间的点&#xff1b;输出为实例的类别&…