设计模式の单例工厂原型模式

news2025/4/11 17:10:44

文章目录

  • 前言
  • 一、单例模式
    • 1.1、饿汉式静态常量单例
    • 1.2、饿汉式静态代码块单例
    • 1.3、懒汉式单例(线程不安全)
    • 1.4、懒汉式单例(线程安全,同步代码块)
    • 1.5、懒汉式单例(线程不安全,同步代码块)
    • 1.6、懒汉式单例(线程安全,双检锁模式)
    • 1.7、静态内部类单例
    • 1.8、枚举单例
  • 二、工厂模式
    • 2.1、简单工厂模式
    • 2.2、工厂方法模式
    • 2.3、抽象工厂模式
    • 小结
  • 三、原型模式
    • 3.1、如何在原型模式中实现浅拷贝和深拷贝?
    • 小结


前言

  本篇是关于设计模式中单例模式(8种,包含线程安全,非安全的实现)、工厂模式(3种)、以及原型模式(深拷贝、浅拷贝)的笔记。


一、单例模式

  单例模式的核心目的是确保某个类只有一个实例,并且提供一个全局访问点来获取该实例。这种模式通常用于需要全局共享资源或者全局配置的场景,通常可用在日志管理器(只有一个日志实例用于输出日志)、线程池(避免重复创建)等全局资源只需要初始化一次的场景。
  如果需要实现单例模式,通常需要满足以下三大要素:

  1. 私有化构造函数:防止外部直接创建实例。
  2. 静态变量:保存唯一的实例。
  3. 公共的静态方法:提供对外的访问方式,返回该唯一实例。

1.1、饿汉式静态常量单例

  最常见的一种饿汉式单例,Singleton1的实例是在外部调用Singleton1getInstance方法时创建的,并且Java 的类加载懒加载的,也就是说只有当类第一次被引用时,才会加载这个类。在加载过程中,JVM 会保证静态变量的初始化是线程安全的。

JVM是如何保证静态变量初始化的线程安全?
类加载过程是串行的,每个类在被加载时会有一个单独的类加载过程,加载过程中的所有操作是线程安全的。JVM 会确保对类的初始化只有一个线程可以执行。其他线程会被阻塞,直到类初始化完成。
对于静态变量,JVM 在类的初始化过程中会执行双检模式,当线程第一次访问类时,如果该类尚未初始化,JVM 会进行初始化,并且只有一个线程会执行这个初始化过程。其他线程在初始化过程中会被阻塞,直到第一个线程完成初始化,类初始化过程保证只会被执行一次,避免了多个线程同时初始化实例的问题。
在案例中,是在同一个线程中获取了两次INSTANCE实例,为何都是同一个?
因为静态变量,是属于类而不是属于某个方法的,静态变量是类的所有实例共享的,当 Singleton1 类被加载并初始化时,JVM 会创建 INSTANCE 变量并赋值。这一过程只会发生一次。

public class HungryMan1 {
    public static void main(String[] args) {
        Singleton1 s1 = Singleton1.getInstance();
        Singleton1 s2 = Singleton1.getInstance();
        System.out.println(s1 == s2);
    }
}


/**
 * 类加载时就创建单例对象
 * 由JVM保证线程安全性
 */
class Singleton1{

    private Singleton1(){

    }

    private final static Singleton1 INSTANCE = new Singleton1();

    public static Singleton1 getInstance(){
        return INSTANCE;
    }

}

1.2、饿汉式静态代码块单例

  相比较于第一种实现,区别在于本实现是在静态代码块中完成单例对象初始化的。当调用Singleton2.getInstance(),这触发 Singleton2 类的加载,类加载过程中,JVM 会初始化静态成员变量和静态代码块。

public class HungryMan2 {
    public static void main(String[] args) {
        Singleton2 s1 = Singleton2.getInstance();
        Singleton2 s2 = Singleton2.getInstance();
        System.out.println(s1 == s2);
    }
}


class Singleton2{


    private Singleton2(){

    }

    private final static Singleton2 INSTANCE;

    /**
     * 在静态代码块中完成初始化
     */
    static {
        INSTANCE = new Singleton2();
    }

    public static Singleton2 getInstance(){
        return INSTANCE;
    }
}

1.3、懒汉式单例(线程不安全)

  该种单例的设计思想是,在调用getInstance()时才会主动去创建单例实例。但是下面的实现是存在线程安全问题的,如果两个线程同时到达了if块,都判断为空,就会创建两个不同的实例。

public class LazyMan1 {
    public static void main(String[] args) throws InterruptedException {
        Singletion3 s1 = Singletion3.getInstance();
        Singletion3 s2 = Singletion3.getInstance();
        System.out.println(s1 == s2);

    }
}


class Singletion3{

    private Singletion3(){

    }

    private static Singletion3 instance;

    /**
     * 多线程下存在并发问题
     * @return
     */
    public static Singletion3 getInstance(){
        if (instance == null){
            instance = new Singletion3();
        }
        return instance;
    }
}

1.4、懒汉式单例(线程安全,同步代码块)

public class LazyMan2 {
    public static void main(String[] args) {
        Singletion4 s1 = Singletion4.getInstance();
        Singletion4 s2 = Singletion4.getInstance();
        System.out.println(s1 == s2);

    }
}


class Singletion4{

    private Singletion4(){

    }

    private static Singletion4 instance;

    /**
     * 解决线程安全问题,但是synchronized是重量级锁,效率低
     * @return
     */
    public static synchronized Singletion4 getInstance(){
        if (instance == null){
            instance = new Singletion4();
        }
        return instance;
    }
}

1.5、懒汉式单例(线程不安全,同步代码块)

  1.4的案例,使用重量级锁保证线程安全,弊端在于锁的粒度过大。如果缩小锁的范围?本案例的写法依旧会存在线程安全问题:

public class LazyMan3 {
    public static void main(String[] args) {
        Singletion5 s1 = Singletion5.getInstance();
        Singletion5 s2 = Singletion5.getInstance();
        System.out.println(s1 == s2);

    }
}


class Singletion5 {

    private Singletion5() {

    }

    private static Singletion5 instance;

    /**
     * 降低锁的粒度,依旧会存在线程安全问题
     * 比如AB两个线程在IF处判断,都为空,都进入了IF块
     * 虽然只有一个线程能争抢到锁,但是在释放锁之后,另一个线程也能再次进入同步代码块创建一个新的对象
     * @return
     */
    public static Singletion5 getInstance() {
        if (instance == null) {
            synchronized (Singletion5.class) {
                instance = new Singletion5();
            }
        }
        return instance;
    }
}

1.6、懒汉式单例(线程安全,双检锁模式)

JUC并发编程,java内存模型,volatile关键字,双检锁单例

public class LazyMan4 {
    public static void main(String[] args) {

    }
}


class Singletion6 {

    private Singletion6() {

    }

    /**
     * 这里的volatile 一定要加 第一是避免指令重排序问题,第二是将对于instance的更改立刻同步到主存,防止缓存问题=-
     *
     */
    private static volatile Singletion6 instance;

    /**
     * 降低锁的粒度,并且进行双重检查
     * @return
     */
    public static Singletion6 getInstance() {
        if (instance == null) {
            synchronized (Singletion6.class) {
                if (instance == null) {
                    instance = new Singletion6();
                }
            }
        }
        return instance;
    }
}

1.7、静态内部类单例

  保证线程安全的方式,和饿汉式的类似。

public class StaticInner {
    public static void main(String[] args) {
        Singleton7 s1 = Singleton7.getInstance();
        Singleton7 s2 = Singleton7.getInstance();
        System.out.println(s1 == s2);
    }
}


class Singleton7{

    private Singleton7(){

    }

    /**
     * 使用静态内部类的方式,静态内部类会在其中方法/变量被调用时初始化
     */
    public static class inner{
        private static final Singleton7 INSTANCE = new Singleton7();
    }

    /**
     * 外部调用Singleton7的getInstance静态方法时,初始化inner内部类
     * JVM在加载类时是线程安全的,通过静态内部类只加载一次,保证只创建一次外部类的实例
     * @return
     */
    public static Singleton7 getInstance(){
        return inner.INSTANCE;
    }


}

1.8、枚举单例

  最后一种是枚举单例,也是推荐使用的一种方式。上面所有的单例,即使是线程安全的,也会有可能因为使用反射而被破坏。

枚举单例为什么能保证线程安全?
在类加载时,JVM 会创建枚举实例并将其保存在内存中。在整个应用生命周期内,枚举的实例是唯一的,JVM 会在类加载时保证它的线程安全和单例性。
当 Singleton.INSTANCE 被访问时,枚举实例已经由 JVM 在类加载时创建好并且只会创建一次。

public class EnumSingleton {
    public static void main(String[] args) {
        Singleton8 s1 = Singleton8.SINGLETON;
        Singleton8 s2 = Singleton8.SINGLETON;
        System.out.println(s1 == s2);
    }
}

/**
 * 枚举单例 没有线程安全问题,也不会导致通过暴力反射破坏单例
 * 前面的方式,虽然将构造私有化,但是都是可以通过反射破解的
 */
enum Singleton8{
    SINGLETON;

}

二、工厂模式

  工厂模式的核心思想在于,将对象的实例化过程封装起来,使得代码不需要直接调用构造方法来创建对象,而是通过一个工厂方法来获取实例客户端代码不需要关心如何创建对象,只需要关心如何使用对象。(七大原则中的迪米特原则,依赖倒置原则)即,将对象的创建过程和使用过程分离,客户端可以获取到对象,而不需要知道对象的具体创建细节。

2.1、简单工厂模式

  假设现在要模拟一个制作披萨的过程,披萨的种类有CheessGreek两种,制作的过程有prepare准备bake烘烤cut切割box打包。由于不同的披萨准备原材料的方式是不一样的,可以这样设计:

public abstract class Pizza {

    private String name;

    public void setName(String name) {
        this.name = name;
    }

    /**
     * 每种披萨的准备过程是不一样的,留给子类去实现
     */
    public void prepare() {}

    public void bake() {
        System.out.println("烘烤 " + name);
    }

    public void cut() {
        System.out.println("切割 " + name);
    }

    public void box() {
        System.out.println("打包 " + name);
    }

}

public class GreekPizza extends Pizza {


    @Override
    public void prepare() {
        System.out.println("制作希腊披萨 准备材料");
    }
}

public class CheesePizza extends Pizza {

    @Override
    public void prepare() {
        System.out.println("制作奶酪披萨 准备材料");
    }
}

  再用一个类模拟订购披萨的过程:

public class OrderPizza {

    public OrderPizza() {

        Pizza pizza = null;
        Scanner sc = new Scanner(System.in);
        while (true){
            System.out.println("***********请输入需要制作的pizza***********");
            String next = sc.next();
            //调用工厂的方法,创建具体的实例
            PizzaFactory pizzaFactory = new PizzaFactory();
            pizza = pizzaFactory.createPizza(next);
            if (pizza == null){
                break;
            }
            pizza.bake();
            pizza.cut();
            pizza.box();
        }
        sc.close();

    }
}

  将制作具体披萨的代码放置到了工厂类中,这样有什么好处?如果有多个订购披萨的类,并且我现在要加一个披萨的种类,如果制作具体披萨的代码还是放置在每个订购披萨的类中,那么所有的类都需要进行修改,扩展性很差。

/**
 * 创建Pizza的工厂类
 */
public class PizzaFactory {


    public Pizza createPizza(String type) {
        Pizza pizza;
        if (type.equals("Cheese")){
            pizza = new CheesePizza();
            pizza.setName("奶酪披萨");
        }else if (type.equals("Greek")){
            pizza = new GreekPizza();
            pizza.setName("希腊披萨");
        }else {
            return null;
        }
        return pizza;
    }
}

/**
 * Pizza店
 */
public class PizzaStore {
    public static void main(String[] args) {
        new OrderPizza();
    }
}

2.2、工厂方法模式

  工厂方法模式是对简单工厂模式的一种改进。假设需求发生变更,除了披萨有不同的种类,还有不同地区供应披萨,比如北京的Cheess披萨,伦敦的Greek披萨…等,简单工厂模式不适合这种复杂的需求,我们可以用工厂方法模式将一个大的工厂,拆分成多个子工厂实现:
在这里插入图片描述

public abstract class Pizza {

    private String name;

    public void setName(String name) {
        this.name = name;
    }

    /**
     * 每种披萨的准备过程是不一样的,留给子类去实现
     */
    public void prepare() {}

    public void bake() {
        System.out.println("烘烤 " + name);
    }

    public void cut() {
        System.out.println("切割 " + name);
    }

    public void box() {
        System.out.println("打包 " + name);
    }

}

class LDGreekPizza extends Pizza {

    @Override
    public void prepare() {
        System.out.println("制作伦敦希腊披萨 准备材料");
    }
}

class LDCheesePizza extends Pizza{

    @Override
    public void prepare() {
        System.out.println("制作伦敦奶酪披萨 准备材料");
    }
}

class BJGreekPizza extends Pizza {

    @Override
    public void prepare() {
        System.out.println("制作北京希腊披萨 准备材料");
    }
}

class BJCheesePizza extends Pizza {

    @Override
    public void prepare() {
        System.out.println("制作北京奶酪披萨 准备材料");
    }
}

  对工厂进行拆分:

/**
 * 订购pizza
 */
public abstract class OrderPizza {

    /**
     * 该方法留给具体的 伦敦 北京 披萨的类去实现,做自己类型的pizza
     * @param type
     * @return
     */
    public abstract Pizza createPizza(String type);

    public OrderPizza() {

        Pizza pizza = null;
        Scanner sc = new Scanner(System.in);
        while (true){
            System.out.println("***********请输入需要制作的pizza***********");
            String next = sc.next();
            //制作pizza
            pizza = createPizza(next);
            if (pizza == null){
                break;
            }
            pizza.bake();
            pizza.cut();
            pizza.box();
        }
        sc.close();

    }
}

class LDOrderPizza extends OrderPizza{

    public LDOrderPizza() {
        super();
    }


    @Override
    public Pizza createPizza(String type) {
        Pizza pizza;
        if (type.equals("Cheese")){
            pizza = new CheesePizza();
            pizza.setName("伦敦奶酪披萨");
        }else if (type.equals("Greek")){
            pizza = new GreekPizza();
            pizza.setName("伦敦希腊披萨");
        }else {
            return null;
        }
        return pizza;
    }
}

class BJOrderPizza extends OrderPizza {

    public BJOrderPizza() {
        super();
    }

    @Override
    public Pizza createPizza(String type) {
        Pizza pizza;
        if (type.equals("Cheese")){
            pizza = new CheesePizza();
            pizza.setName("北京奶酪披萨");
        }else if (type.equals("Greek")){
            pizza = new GreekPizza();
            pizza.setName("北京希腊披萨");
        }else {
            return null;
        }
        return pizza;
    }
}

  在模拟订购披萨时,只需要创建具体实现类的对象即可:

/**
 * Pizza店
 */
public class PizzaStore {
    public static void main(String[] args) {
        //创建具体的实现类
        new BJOrderPizza();
    }
}

2.3、抽象工厂模式

  抽象工厂模式工厂方法模式的进一步扩展,将工厂抽象成两层,接口层:抽象工厂,实现类:具体负责生产各自产品的工厂:
在这里插入图片描述

/**
 * 抽象工厂模式
 * 侧重于将工厂分为了多层
 */
public interface AbsFactory {

    Pizza createPizza(String type);

}

class BJPizzaFactory implements AbsFactory {
    @Override
    public Pizza createPizza(String type) {
        Pizza pizza;
        if (type.equals("Cheese")){
            pizza = new CheesePizza();
            pizza.setName("北京奶酪披萨");
        }else if (type.equals("Greek")){
            pizza = new GreekPizza();
            pizza.setName("北京希腊披萨");
        }else {
            return null;
        }
        return pizza;
    }
}

class LDPizzaFactory implements AbsFactory {
    @Override
    public Pizza createPizza(String type) {
        Pizza pizza;
        if (type.equals("Cheese")){
            pizza = new CheesePizza();
            pizza.setName("伦敦奶酪披萨");
        }else if (type.equals("Greek")){
            pizza = new GreekPizza();
            pizza.setName("伦敦希腊披萨");
        }else {
            return null;
        }
        return pizza;
    }
}

  只需要实例化对应的工厂类实现,即可获得相应的对象。


/**
 * Pizza店
 */
public class PizzaStore {
    public static void main(String[] args) {
        new OrderPizza(new BJPizzaFactory());
    }
}

class OrderPizza {

    public OrderPizza(AbsFactory factory) {
        setFactory(factory);
    }

    private void setFactory(AbsFactory factory) {
        Pizza pizza = null;
        Scanner sc = new Scanner(System.in);
        while (true){
            System.out.println("***********请输入需要制作的pizza***********");
            String type = sc.next();
            //制作pizza 由传入的AbsFactory子类类型 去路由到不同的子类
            pizza = factory.createPizza(type);
            if (pizza == null){
                break;
            }
            pizza.bake();
            pizza.cut();
            pizza.box();
        }
        sc.close();

    }
}

小结

  简单工厂模式可以理解成仅仅是把具有共性的创建对象的代码抽取到了一个类中,无法应对复杂的业务。而工厂方法模式是对简单工厂模式的一种改进,将产品的创建过程委托给子类来解决简单工厂模式的缺点。会将子类进行分类。每个子类负责创建一个特定类型的产品,客户端只需要通过工厂方法来获取对象。抽象工厂模式可以看做是简单工厂模式工厂方法模式的结合,既包含了抽象工厂和实现工厂,也包含了抽象产品和实现产品。

  • 简单工厂模式适合产品种类较少的情况。
  • 工厂方法模式适合产品种类较多且需要通过继承进行扩展的情况。
  • 抽象工厂模式适合需要创建一系列相关产品的情况。

三、原型模式

  原型模式的核心思想在于,通过复制现有的对象来创建新对象,而不是通过使用构造函数直接创建。这种模式通过克隆现有对象来生成新实例,从而避免了重复创建对象的复杂性和性能开销,特别适合在需要频繁创建类似对象的场景中。
  原型模式的结构通常包括以下几个角色:

  • Prototype(原型接口):定义了一个抽象的克隆方法,通常是 clone() 方法,供具体类实现。
public interface Prototype {
    Prototype clone();  // 克隆方法
}
  • ConcretePrototype(具体原型类):实现了原型接口的具体类,具体定义克隆方法,返回一个自己对象的副本。
public class ConcretePrototype implements Prototype {
    private String name;

    public ConcretePrototype(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    @Override
    public Prototype clone() {
        // 通过构造函数创建新的对象
        return new ConcretePrototype(this.name);
    }
}

  • Client(客户端):使用原型对象并通过克隆方法来创建新的对象。
public class Client {
    public static void main(String[] args) {
        // 创建一个原型对象
        ConcretePrototype prototype = new ConcretePrototype("Prototype1");
        
        // 通过克隆方法创建一个新对象
        ConcretePrototype clonePrototype = (ConcretePrototype) prototype.clone();
        
        // 输出原型对象的名称
        System.out.println("Original: " + prototype.getName());
        System.out.println("Clone: " + clonePrototype.getName());
	
			 System.out.println(prototype == clonePrototype);
    }
}

  同时JDK自带的clone方法也是原型模式的体现,默认是浅拷贝,需要实现Cloneable接口。

什么是浅拷贝和深拷贝?
浅拷贝对于基本数据类型,拷贝的是值。对于引用类型,拷贝的是引用(即地址),源对象和目标对象的引用类型成员指向同一个内存位置。如果源对象或目标对象对引用类型的成员进行修改,另一对象的对应成员也会发生变化。
深拷贝对于基本数据类型,拷贝的是值。对于引用类型,会递归地拷贝引用类型所指向的对象。源对象和目标对象不会共享任何引用类型成员,修改一个对象的引用类型成员不会影响另一个对象。也是不可变类设计的一个要素。
JUC并发编程,不可变类设计

3.1、如何在原型模式中实现浅拷贝和深拷贝?

  浅拷贝通常通过调用 clone() 方法来实现:

public class ConcretePrototype implements Cloneable {
    private String name;
    private Address address;

    public ConcretePrototype(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    @Override
    public ConcretePrototype clone() {
        try {
            return (ConcretePrototype) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public String toString() {
        return "ConcretePrototype{" +
                "name='" + name + '\'' +
                ", address=" + address +
                '}';
    }
}

class Address {
    
    private String street;


    public Address() {
    }

    public Address(String street) {
        this.street = street;
    }

    /**
     * 获取
     * @return street
     */
    public String getStreet() {
        return street;
    }

    /**
     * 设置
     * @param street
     */
    public void setStreet(String street) {
        this.street = street;
    }

    public String toString() {
        return "Address{street = " + street + "}";
    }
}

public class ShallowCopy {
    public static void main(String[] args) {
        Address address = new Address();
        address.setStreet("xx街道");
        ConcretePrototype original = new ConcretePrototype("测试", address);
        ConcretePrototype copy = original.clone();

        System.out.println("原件=" + original.toString());
        System.out.println("复印件=" + copy.toString());

        System.out.println("修改复印件的Address中的Street字段==================");

        copy.getAddress().setStreet("yy街道");
        System.out.println("原件=" + original.toString());
        System.out.println("复印件=" + copy.toString());
    }
}

最终的结果是,复印件引用的Address中的字段值发生改变,原件的同步更新
原件=ConcretePrototype{name=‘测试’, address=Address{street = xx街道}}
复印件=ConcretePrototype{name=‘测试’, address=Address{street = xx街道}}
修改复印件的Address中的Street字段==================
原件=ConcretePrototype{name=‘测试’, address=Address{street = yy街道}}
复印件=ConcretePrototype{name=‘测试’, address=Address{street = yy街道}}

  深拷贝的区别在于,在调用clone()方法进行浅克隆后,还需要对其中的引用类型创建新的实例,赋值给复制品的引用类型字段,这样原件和复印件的引用类型字段的引用,指向的地址就不相同:

public class ConcretePrototype implements Cloneable {
  	
  	//......

    @Override
    public ConcretePrototype clone() {
        try {
            // 深拷贝:创建新的 Address 对象
            ConcretePrototype clone = (ConcretePrototype) super.clone();
            // 手动创建新的引用,使原件和复印件的地址不相同
            clone.address = new Address(this.address.getStreet());
            return clone;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public String toString() {
        return "ConcretePrototype{" +
                "name='" + name + '\'' +
                ", address=" + address +
                '}';
    }
}

原件=ConcretePrototype{name=‘测试’, address=Address{street = xx街道}}
复印件=ConcretePrototype{name=‘测试’, address=Address{street = xx街道}}
修改复印件的Address中的Street字段==================
原件=ConcretePrototype{name=‘测试’, address=Address{street = xx街道}}
复印件=ConcretePrototype{name=‘测试’, address=Address{street = yy街道}}

小结

  原型模式的适用场景:

  • 创建对象的代价较大:当对象的创建成本高(如涉及到数据库操作、网络调用等),而且创建出来的对象大部分都相似时,原型模式可以通过复制现有对象来降低性能开销。(和单例模式的区别在于,因为业务需求,该对象必须要创建多份)。
  • 需要大量相似对象:如果需要创建很多类似的对象,通过克隆现有对象可以避免反复执行相同的构造逻辑。
  • 避免重复代码:当多个对象具有相似的构建过程时,原型模式能够避免重复编写对象创建的代码。

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

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

相关文章

深入理解Java的 JIT(即时编译器)

🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/literature?__c1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,…

重生之我在21世纪学C++—关系、条件、逻辑操作符

一、关系操作符 1、关系操作符介绍 用于比较的表达式,称为 “关系表达式”(relational expression),里面使用的运算符称为 “关系运算符”(relational operator),主要有下面 6 个: 运算符描述>大于运算符,用于比…

工作:SolidWorks从3D文件导出2D的DWG或DXF类型文件方法

工作:SolidWorks从3D文件导出2D的DWG或DXF类型文件方法 SolidWorks从3D文件导出2D的DWG或2D DXF类型文件方法(一)打开3D文件(二)从装配体到工程图(三)拖出想要的角度的图型(四&#…

Spring Cloud Alibaba:一站式微服务解决方案

一、简介 Spring Cloud Alibaba(简称SCA) 是一个基于 Spring Cloud 构建的开源微服务框架,专为解决分布式系统中的服务治理、配置管理、服务发现、消息总线等问题而设计。它集成了阿里巴巴开源的各种分布式服务技术,提供了一系列…

基于51单片机64位病床呼叫系统设计( proteus仿真+程序+设计报告+原理图+讲解视频)

基于51单片机病床呼叫系统设计( proteus仿真程序设计报告原理图讲解视频) 仿真图proteus7.8及以上 程序编译器:keil 4/keil 5 编程语言:C语言 设计编号:S0095 1. 主要功能: 基于51单片机的病床呼叫系统proteus仿…

windows10下,nvidia控制面板升级驱动后osgearth三维程序无法运行

osg系列文章目录 文章目录 osg系列文章目录一.前言二.解决办法 一.前言 windows10下,nvidia控制面板升级驱动后osgearth三维程序无法运行,情景如下: 笔者使用的是天选1笔记本,硬件配置如下 osgearth和osg对应的版本是: Osg3.4.1…

【计算机学习笔记】GB2312、GBK、Unicode等字符编码的理解

之前编写win32程序时没怎么关注过宽字符到底是个啥东西,最近在编写网络框架又遇到字符相关的问题,所以写一篇文章记录一下(有些部分属于个人理解,如果有错误欢迎指出) 目录 几个常见的编码方式Unicode和UTF-8、UTF-16、…

七种msvcp140.dll丢失的解决方法及了解msvcp140.dll丢失的原因

在Windows操作系统中,msvcp140.dll是Microsoft Visual C 2015 Redistributable Package的一部分。这个动态链接库文件对于许多应用程序的正常运行至关重要,因为它包含了C程序所需的标准库函数。当用户遇到“msvcp140.dll丢失”或类似的错误信息时&#x…

QT4和 QT5 槽函数连接的区别

正常连接方式 //QT4官方用列QLabel *label new QLabel;QScrollBar *scrollBar new QScrollBar;QObject::connect(scrollBar, SIGNAL(valueChanged(int)),label, SLOT(setNum(int)));//QT5官方用列QLabel *label new QLabel;QLineEdit *lineEdit new QLineEdit;QObject::c…

LeetCode - #152 乘积最大子数组(Top 100)

文章目录 前言1. 描述2. 示例3. 答案关于我们 前言 本题为 LeetCode 前 100 高频题 我们社区陆续会将顾毅(Netflix 增长黑客,《iOS 面试之道》作者,ACE 职业健身教练。)的 Swift 算法题题解整理为文字版以方便大家学习与阅读。 …

新手前端开发入职公司全流程

作为一名前端开发新手,进入一家公司开启职业生涯是既兴奋又充满挑战的旅程。今天就来和大家分享一下这个过程中的各个环节。 一、入职准备 在收到心仪公司的offer后,可别只顾着高兴啦。首先要准备好入职资料,像身份证、学历证明这些是必不可…

【深入探索 C++ STL 容器 list】 —— 编程世界的万能胶,数据结构中的百变精灵

STL系列学习参考: STL 数据结构与算法__Zwy的博客-CSDN博客https://blog.csdn.net/bite_zwy/category_12852141.html 学习C STL的三个境界,会用,明理,能扩展,STL中的所有容器都遵循这个规律,下面我们就按…

【数电】常见时序逻辑电路设计和分析

本文目的:一是对真题常考题型总结,二是对常见时序电路设计方法进行归纳,给后面看这个文档的人留有一点有价值的东西。 1.不同模计数器设计 2.序列信号产生和检测电路 2.1序列信号产生电路 2.1.1设计思路 主要设计思路有三种 1&#xff09…

MCU、ARM体系结构,单片机基础,单片机操作

计算机基础 计算机的组成 输入设备、输出设备、存储器、运算器、控制器 输入设备:将其他信号转换为计算机可以识别的信号(电信号)。输出设备:将电信号(0、1)转为人或其他设备能理解的…

数字图像处理考研考点(持续更新)

一、数字图像基本概念 1、人眼视觉特性 (1)眼睛上有两类光感受器:锥状体和杆状体 锥状体(锥细胞):约 700 万个,对颜色高度敏感,每个锥状体都连接到神经末梢,人可以充分地分辨图像细节。锥细胞…

Lumos学习王佩丰Excel第二十讲:图表基础

Excel图表就像是把一堆复杂的数字变成了一幅幅直观的图画,让我们能够更快地理解数据之间的关系和趋势,使工作表更易于读懂和交流。学好这几节课,不仅可以辅助工作,还可以装X哈哈哈~~~ 一、认识图表中的元素(七块积木&…

VTK知识学习(20)- 数据的存储与表达

1、数据的存储 1)、vtkDataArray VTK中的内存分配采用连续内存,可以快速地创建、删除和遍历,称之为数据数组(DataArray),用类 vtkDataArray 实现。数组数据的访问是基于索引的,从零开始计数。 以 vtkFloatArray 类来说明如何在 …

如何在UI自动化测试中创建稳定的定位器?

如何在UI自动化测试中创建稳定的定位器? 前言1. 避免使用绝对路径2. 避免在定位器中使用索引3. 避免多个类名的定位器4. 避免动态和自动生成的ID5. 确保定位器唯一6. 处理隐藏元素的策略7. 谨慎使用基于文本的定位器8. 使用AI创建稳定的定位器 总结 前言 在自动化测…

SparkSQL 读写数据攻略:从基础到实战

目录 一、输入Source 1)代码演示最普通的文件读取方式: 2) 通过jdbc读取数据库数据 3) 读取table中的数据【hive】 二、输出Sink 实战一:保存普通格式 实战二:保存到数据库中 实战三:将结果保存在h…

【1】Python交叉编译到OpenHarmony标准系统运行(arm32位)

本文介绍如何Python语言如何在OpenHarmony标准系统运行,包括5.0r和4.1r以及4.0r,和未来版本的OpenHarmony版本上。 Python语言在OpenHarmony上使用,需要将Python解释器CPython移植到OpenHarmony标准系统。通过交叉编译的方式。 首先来了解几个概念: CPython 是 Python 编…