详解23种设计模式——第一部分:概述+创建型模式

目录

1. 概述

2. 创建型模式

2.1 简单(静态)工厂模式

2.1.1 介绍

2.1.2 实现

2.2 工厂模式

2.3 抽象工厂模式

2.4 单例模式

2.4.1 饿汉模式

2.4.2 懒汉模式

2.4.3 线程安全的懒汉式

2.4.4 DCL单例 - 高性能的懒汉式

2.5 建造者模式

2.6 原型模式

2.7 创建型模式总结


1. 概述

设计模式(Design Pattern)是前辈们经过相当长的一段时间的试验和错误总结出来的,是软件开发过程中面临的通用问题的解决方案。这些解决方案使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性

设计模式的优点:

  • ① 可以提高程序员的思维能力、编程能力和设计能力。
  • ② 使程序设计更加标准化、代码编制更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期。
  • ③ 使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强。

有一些重要的设计原则在开篇和大家分享下,这些原则将贯通全文:

  • 针对接口编程,而不是针对实现编程。这个很重要,也是优雅的、可扩展的代码的第一步。
  • 职责单一原则。每个类都应该只有一个单一的功能,并且该功能应该由这个类完全封装起来。
  • 对修改关闭,对扩展开放。对修改关闭是说,我们辛辛苦苦加班写出来的代码,该实现的功能和该修复的 bug 都完成了,别人可不能说改就改;对扩展开放就比较好理解了,也就是说在我们写好的代码基础上,很容易实现扩展。

设计模式是对大家实际工作中写的各种代码进行高层次抽象的总结,其中最出名的当属 Gang of Four(GoF)的分类了,他们将设计模式分类为 23 种经典的模式,根据用途我们又可以分为三大类,分别为创建型模式(5种)结构型模式(7种)行为型模式(11种)

(1)创建型模式

  1. 简单(静态)工厂模式:一个工厂类根据传入的参量决定创建出那一种产品类的实例。(该模式不属于23种GOF设计模式之一
  2. 工厂方法:定义一个创建对象的接口,让子类决定实例化那个类。
  3. 抽象工厂:创建相关或依赖对象的家族,而无需明确指定具体类。
  4. 单例模式:某个类只能有一个实例,提供一个全局的访问点。
    1. 饿汉模式
    2. 懒汉模式
  5. 建造者模式:封装一个复杂对象的构建过程,并可以按步骤构造。
  6. 原型模式:通过复制现有的实例来创建新的实例。

(2)结构型模式

  1. 外观模式:对外提供一个统一的方法,来访问子系统中的一群接口。
  2. 桥接模式:将抽象部分和它的实现部分分离,使它们都可以独立的变化。
  3. 组合模式:将对象组合成树形结构以表示“”部分-整体“”的层次结构。
  4. 装饰模式:动态的给对象添加新的功能。
  5. 代理模式:为其他对象提供一个代理以便控制这个对象的访问。
  6. 适配器模式:将一个类的方法接口转换成客户希望的另外一个接口。
  7. 亨元(蝇量)模式:通过共享技术来有效的支持大量细粒度的对象。

(3)行为型模式

  1. 模板模式:定义一个算法结构,而将一些步骤延迟到子类实现。
  2. 解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器。
  3. 策略模式:定义一系列算法,把他们封装起来,并且使它们可以相互替换。
  4. 状态模式:允许一个对象在其对象内部状态改变时改变它的行为。
  5. 观察者模式:对象间的一对多的依赖关系。
  6. 备忘录模式:在不破坏封装的前提下,保持对象的内部状态。
  7. 中介者模式:用一个中介对象来封装一系列的对象交互。
  8. 命令模式:将命令请求封装为一个对象,使得可以用不同的请求来进行参数化。
  9. 访问者模式:在不改变数据结构的前提下,增加作用于一组对象元素的新功能。
  10. 责任链模式:将请求的发送者和接收者解耦,使的多个对象都有处理这个请求的机会。
  11. 迭代器模式:一种遍历访问聚合对象中各个元素的方法,不暴露该对象的内部结构。

2. 创建型模式

创建型模式的作用就是创建对象,说到创建一个对象,最熟悉的就是 new 一个对象,然后 set 相关属性。但是,在很多场景下,我们需要给客户端提供更加友好的创建对象的方式,尤其是那种我们定义了类,但是需要提供给其他开发者用的时候。

2.1 简单(静态)工厂模式

2.1.1 介绍

① 一句话来说就是:一个工厂类根据传入的参量决定创建出那一种产品类的实例。因为逻辑实现简单,所以称为简单工厂模式,也因为工厂中的方法一般设置为静态,所以也称为静态工厂,它不属于23种模式

② 简单工厂模式专门定义一个工厂类来负责创建其他类的实例,被创建的实例通常都具有共同的父类,在工厂类中,可以根据参数的不同返回不同类的实例。升级版本简单工厂模式,通过反射根据类的全路径名生成对象。

③ 简单工厂模式就是将这部分创建对象语句分离出来,由工厂类来封装实例化对象的行为,修改时只需要修改类中的操作代码,使用时调用该类不需要考虑实例化对象的行为,使得后期代码维护升级更简单方便,有利于代码的可修改性与可读性。

④ 但是如果增加新的产品的话,需要修改工厂类的判断逻辑,违背开闭原则。

2.1.2 实现

直接上代码:

public class FoodFactory {public static Food makeFood(String name) {if (name.equals("noodle")) {Food noodle = new LanZhouNoodle();noodle.addSpicy("more");return noodle;} else if (name.equals("chicken")) {Food chicken = new HuangMenChicken();chicken.addCondiment("potato");return chicken;} else {return null;}}
}

其中,LanZhouNoodle 和 HuangMenChicken 都继承自 Food。

简单地说,简单工厂模式通常就是这样:一个工厂类 XxxFactory,里面有一个静态方法,根据我们不同的参数,返回不同的派生自同一个父类(或实现同一接口)的实例对象

我们强调职责单一原则,一个类只提供一种功能,FoodFactory 的功能就是只要负责生产各种 Food。

2.2 工厂模式

简单工厂模式很简单,如果它能满足我们的需要,我觉得就不要折腾了。之所以需要引入工厂模式,是因为我们往往需要使用两个或两个以上的工厂。

public interface FoodFactory {Food makeFood(String name);
}
public class ChineseFoodFactory implements FoodFactory {@Overridepublic Food makeFood(String name) {if (name.equals("A")) {return new ChineseFoodA();} else if (name.equals("B")) {return new ChineseFoodB();} else {return null;}}
}
public class AmericanFoodFactory implements FoodFactory {@Overridepublic Food makeFood(String name) {if (name.equals("A")) {return new AmericanFoodA();} else if (name.equals("B")) {return new AmericanFoodB();} else {return null;}}
}

其中,ChineseFoodA、ChineseFoodB、AmericanFoodA、AmericanFoodB 都派生自 Food。

客户端调用:

public class APP {public static void main(String[] args) {// 先选择一个具体的工厂FoodFactory factory = new ChineseFoodFactory();// 由第一步的工厂产生具体的对象,不同的工厂造出不一样的对象Food food = factory.makeFood("A");}
}

虽然都是调用 makeFood("A") 制作 A 类食物,但是,不同的工厂生产出来的完全不一样。

第一步,我们需要选取合适的工厂,然后第二步基本上和简单工厂一样。

核心在于,我们需要在第一步选好我们需要的工厂。比如,我们有 LogFactory 接口,实现类有 FileLogFactory 和 KafkaLogFactory,分别对应将日志写入文件和写入 Kafka 中,显然,我们客户端第一步就需要决定到底要实例化 FileLogFactory 还是 KafkaLogFactory,这将决定之后的所有的操作。

虽然简单,不过我也把所有的构件都画到一张图上,这样看着比较清晰:

2.3 抽象工厂模式

当涉及到产品族的时候,就需要引入抽象工厂模式了。

一个经典的例子是造一台电脑。我们先不引入抽象工厂模式,看看怎么实现。

因为电脑是由许多的构件组成的,我们将 CPU 和主板进行抽象,然后 CPU 由 CPUFactory 生产,主板由 MainBoardFactory 生产,然后,我们再将 CPU 和主板搭配起来组合在一起,如下图:

这个时候的客户端调用是这样的:

// 得到 Intel 的 CPU
CPUFactory cpuFactory = new IntelCPUFactory();
CPU cpu = intelCPUFactory.makeCPU();// 得到 AMD 的主板
MainBoardFactory mainBoardFactory = new AmdMainBoardFactory();
MainBoard mainBoard = mainBoardFactory.make();// 组装 CPU 和主板
Computer computer = new Computer(cpu, mainBoard);

单独看 CPU 工厂和主板工厂,它们分别是前面我们说的工厂模式。这种方式也容易扩展,因为要给电脑加硬盘的话,只需要加一个 HardDiskFactory 和相应的实现即可,不需要修改现有的工厂。

但是,这种方式有一个问题,那就是如果 Intel 家产的 CPU 和 AMD 产的主板不能兼容使用,那么这代码就容易出错,因为客户端并不知道它们不兼容,也就会错误地出现随意组合。

下面就是我们要说的产品族的概念,它代表了组成某个产品的一系列附件的集合:

当涉及到这种产品族的问题的时候,就需要抽象工厂模式来支持了。我们不再定义 CPU 工厂、主板工厂、硬盘工厂、显示屏工厂等等,我们直接定义电脑工厂,每个电脑工厂负责生产所有的设备,这样能保证肯定不存在兼容问题

这个时候,对于客户端来说,不再需要单独挑选 CPU厂商、主板厂商、硬盘厂商等,直接选择一家品牌工厂,品牌工厂会负责生产所有的东西,而且能保证肯定是兼容可用的。

public static void main(String[] args) {// 第一步就要选定一个“大厂”ComputerFactory cf = new AmdFactory();// 从这个大厂造 CPUCPU cpu = cf.makeCPU();// 从这个大厂造主板MainBoard board = cf.makeMainBoard();// 从这个大厂造硬盘HardDisk hardDisk = cf.makeHardDisk();// 将同一个厂子出来的 CPU、主板、硬盘组装在一起Computer result = new Computer(cpu, board, hardDisk);
}

当然,抽象工厂的问题也是显而易见的,比如我们要加个显示器,就需要修改所有的工厂,给所有的工厂都加上制造显示器的方法。这有点违反了对修改关闭,对扩展开放这个设计原则

2.4 单例模式

2.4.1 饿汉模式

饿汉模式最简单

public class Singleton {/*** static:* ①表示共享变量,语意符合* ②使得该变量能在getInstance()静态方法中使用* final:* ①final修饰的变量值不会改变即常量,语意也符合,当然不加final也是可以的* ②保证修饰的变量必须在类加载完成时就已经进行赋值。* final修饰的变量,前面一般加static*/private static final Singleton singleton = new Singleton();/*** 私有化构造方法,使外部无法通过构造方法构造除singleton外的类实例* 从而达到单例模式控制类实例数目的目的*/private Singleton(){}/*** 类实例的全局访问方法* 因为构造方法以及被私有化,外部不可能通过new对象来调用其中的方法* 加上static关键词使得外部可以通过类名直接调用该方法获取类实例* @return*/public static Singleton getSingleton() {return singleton;}
}

说明:

① 优点:一般使用static和final修饰变量(具体作用已经在代码里描述了),只在类加载时才会初始化,以后都不会,线程绝对安全,无锁,效率高

② 缺点:类加载的时候就初始化,不管用不用,都占用空间,会消耗一定的性能(当然很小很小,几乎可以忽略不计,所以这种模式在很多场合十分常用而且十分简单)。

2.4.2 懒汉模式

public class Singleton {private static Singleton singleton = null;private Singleton(){}public static Singleton getSingleton() {if(singleton == null){singleton = new Singleton();}return singleton;}
}

说明:

① 优点:在外部需要使用的时候才进行实例化,不使用的时候不会占用空间

② 缺点:线程不安全。看上去,这段代码没什么明显问题,但它不是线程安全的。假设当前有N个线程同时调用getInstance()方法,由于当前还没有对象生成,所以一部分同时都进入if语句new Singleton(),那么就会由多个线程创建多个多个user对象

2.4.3 线程安全的懒汉式

public class Singleton {private static Singleton singleton;private Singleton(){};private static synchronized Singleton getSingleton(){if(singleton == null){singleton = new Singleton();}return singleton;}
}

说明:

① 优点:解决了懒汉式线程不安全的问题

② 缺点:线程阻塞,影响性能

2.4.4 DCL单例 - 高性能的懒汉式

public class Singleton {/*volatile在这里发挥的作用是:禁止指令重排序(编译器和处理器为了优化程序性能* 而对指令序列进行排序的一种手段。)* singleton = new Singleton();这句代码是非原子性操作可分为三行伪代码* a:memory = allocate() //分配内存,在jvm堆中分配一段区域* b:ctorInstanc(memory) //初始化对象,在jvm堆中的内存中实例化对象* c:instance = memory //赋值,设置instance指向刚分配的内存地址* 上面的代码在编译运行时,可能会出现重排序从a-b-c排序为a-c-b。* 重排序是为了优化性能,但是不管怎么重排序,在单线程下程序的执行结果不能被改变* 保证最终一致性。而在多线程环境下,可能发生重排序,会影响结果。* ①若A线程执行到代码singleton = new Singleton()时;* ②同时若B线程进来执行到代码到第一层检查if (singleton == null)* ③当cpu切换到A线程执行代码singleton = new Singleton();时发生了指令重排序,* 执行了a-b,没有执行c,此时的singleton对象只有地址,没有内容。然后cpu又切换到了B线程,* 这时singleton == null为false(==比较的是内存地址),* 则代码会直接执行到了return,返回一个未初始化的对象(只有地址,没有内容)。* */private volatile static Singleton singleton;private Singleton() {}public static Singleton getSingleton() {/*第一层检查,检查是否有引用指向对象,高并发情况下会有多个线程同时进入* ①当多个线程第一次进入,所有线程都进入if语句* ②当多个线程第二次进入,因为singleton已经不为null,因此所有线程都不会进入if语句,* 即不会执行锁,从而也就不会因为锁而阻塞,避免锁竞争*/if (singleton == null) {/*第一层锁,保证只有一个线程进入,* ①多个线程第一次进入的时候,只有一个线程会进入,其他线程处于阻塞状态* 当进入的线程创建完对象出去之后,其他线程又会进入创建对象,所以有了第二次if检查* ②多个线程第二次是进入不到这里的,因为已被第一次if检查拦截*/synchronized (Singleton.class) {/*第二层检查,防止除了进入的第一个线程的其他线程重复创建对象*/if (singleton == null) {singleton = new Singleton();}}}return singleton;}
}

说明:代码注释已详细讲解volatile在该单例模式的作用,已经双重锁的作用。

① 优点:解决了线程阻塞的问题。

② 缺点:多个线程第一次进入的时候会造成大量的线程阻塞,代码不够优雅。

静态内部类的方式

public class Singleton {private Singleton(){}private static class LayzInner{private static Singleton singleton = new Singleton();}public static Singleton getSingleton(){return LayzInner.singleton;}
}

说明:

① 优点:第一次类创建的时候加载,避免了内存浪费,不存在阻塞问题,线程安全,唯一性

② 缺点:序列化-漏洞:反射,会破坏内部类单例模式

2.5 建造者模式

经常碰见的 XxxBuilder 的类,通常都是建造者模式的产物。建造者模式其实有很多的变种,但是对于客户端来说,我们的使用通常都是一个模式的:

Food food = new FoodBuilder().a().b().c().build();
Food food = Food.builder().a().b().c().build();

套路就是先 new 一个 Builder,然后可以链式地调用一堆方法,最后再调用一次 build() 方法,我们需要的对象就有了。

来一个中规中矩的建造者模式:

class User {// 下面是“一堆”的属性private String name;private String password;private String nickName;private int age;// 构造方法私有化,不然客户端就会直接调用构造方法了private User(String name, String password, String nickName, int age) {this.name = name;this.password = password;this.nickName = nickName;this.age = age;}// 静态方法,用于生成一个 Builder,这个不一定要有,不过写这个方法是一个很好的习惯,// 有些代码要求别人写 new User.UserBuilder().a()...build() 看上去就没那么好public static UserBuilder builder() {return new UserBuilder();}public static class UserBuilder {// 下面是和 User 一模一样的一堆属性private String  name;private String password;private String nickName;private int age;private UserBuilder() {}// 链式调用设置各个属性值,返回 this,即 UserBuilderpublic UserBuilder name(String name) {this.name = name;return this;}public UserBuilder password(String password) {this.password = password;return this;}public UserBuilder nickName(String nickName) {this.nickName = nickName;return this;}public UserBuilder age(int age) {this.age = age;return this;}// build() 方法负责将 UserBuilder 中设置好的属性“复制”到 User 中。// 当然,可以在 “复制” 之前做点检验public User build() {if (name == null || password == null) {throw new RuntimeException("用户名和密码必填");}if (age <= 0 || age >= 150) {throw new RuntimeException("年龄不合法");}// 还可以做赋予”默认值“的功能if (nickName == null) {nickName = name;}return new User(name, password, nickName, age);}}
}

核心是:先把所有的属性都设置给 Builder,然后 build() 方法的时候,将这些属性复制给实际产生的对象

看看客户端的调用:

public class APP {public static void main(String[] args) {User d = User.builder().name("foo").password("pAss12345").age(25).build();}
}

说实话,建造者模式的链式写法很吸引人,但是,多写了很多“无用”的 builder 的代码,感觉这个模式没什么用。不过,当属性很多,而且有些必填,有些选填的时候,这个模式会使代码清晰很多。我们可以在 Builder 的构造方法中强制让调用者提供必填字段,还有,在 build() 方法中校验各个参数比在 User 的构造方法中校验,代码要优雅一些。

题外话,强烈建议读者使用 lombok,用了 lombok 以后,上面的一大堆代码会变成如下这样:

@Builder
class User {private String  name;private String password;private String nickName;private int age;
}

当然,如果你只是想要链式写法,不想要建造者模式,有个很简单的办法,User 的 getter 方法不变,所有的 setter 方法都让其 return this 就可以了,然后就可以像下面这样调用:

User user = new User().setName("").setPassword("").setAge(20);

很多人是这么用的,但是这种写法不太优雅,不是很推荐使用。

2.6 原型模式

在某些情况下,需要创建对象的副本,但复制一个对象的成本可能很高,或者希望避免与对象的具体类耦合。例如,当创建对象的过程较为复杂,或者对象包含大量共享的状态时,使用常规的创建方法可能会导致性能下降。

原型模式的解决方案是通过复制现有对象来创建新对象,而不是从头开始构建。这允许我们以更高效的方式创建新对象,同时避免了与对象类的直接耦合。核心概念是在原型对象的基础上进行克隆,使得新对象具有与原型相同的初始状态。

原型模式很简单:有一个原型实例,基于这个原型实例产生新的实例,也就是“克隆”了

Object 类中有一个 clone() 方法,它用于生成一个新的对象,当然,如果我们要调用这个方法,java 要求我们的类必须先实现 Cloneable 接口,此接口没有定义任何方法,但是不这么做的话,在 clone() 的时候,会抛出 CloneNotSupportedException 异常。

protected native Object clone() throws CloneNotSupportedException;

注意事项: 

  • 深克隆问题:原型模式默认进行浅克隆,即复制对象本身和其引用。如果对象内部包含其他对象的引用,可能需要实现深克隆来复制整个对象结构。
  • 克隆方法的实现:某些对象可能不容易进行克隆,特别是涉及到文件、网络连接等资源的情况。

Java 的克隆是浅克隆,碰到对象引用的时候,克隆出来的对象和原对象中的引用将指向同一个对象。通常实现深克隆的方法是将对象进行序列化,然后再进行反序列化。

总之,原型模式是一种在需要创建对象副本时非常有用的设计模式,它提供了一种灵活且高效的方法来处理对象的复制需求。

2.7 创建型模式总结

创建型模式总体上比较简单,它们的作用就是为了产生实例对象,算是各种工作的第一步了,因为我们写的是面向对象的代码,所以我们第一步当然是需要创建一个对象了。

总结:

  • 工厂模式在简单工厂模式的基础上增加了选择工厂的维度,需要第一步选择合适的工厂;
  • 抽象工厂模式有产品族的概念,如果各个产品是存在兼容性问题的,就要用抽象工厂模式;
  • 单例模式为了保证全局使用的是同一对象,一方面是安全性考虑,一方面是为了节省资源;
  • 建造者模式专门对付属性很多的那种类,为了让代码更优美;
  • 原型模式用得比较少,了解和 Object 类中的 clone() 方法相关的知识即可。

后续待更新......

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

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

相关文章

kaptcha依赖maven无法拉取的问题

老依赖了&#xff0c;就是无法拉取&#xff0c;也不知道为什么&#xff0c;就是用maven一直拉去不成功&#xff0c;还以为是魔法的原因&#xff0c;试了好久发现不是&#xff0c;只好在百度寻求帮助了&#xff0c;好在寻找到了这位大佬的文章Maven - 解决无法安装 Kaptcha 依赖…

纯血鸿蒙!

纯血鸿蒙&#xff0c;这是哪个营销大师给起的名字啊&#xff01; 纯血&#xff01;象征着高贵、自信、自主、血性、英雄气概&#xff0c;都融入这纯血鸿蒙了&#xff01; 鸿蒙本就是开天辟地&#xff0c;加上纯血&#xff0c;真是荡气回肠&#xff01; 鸿蒙的推出背景 我们前…

Git安装与配置(2.47.0版本超详细)

一、背景 1.什么是gitt&#xff1f;&#xff08;官网引用&#xff09; Git 是一个快速、可扩展的分布式版本控制系统&#xff0c;它拥有异常丰富的命令集&#xff0c;可以提供高级操作和对内部的完全访问。 参阅 gittutorial[7] 开始使用&#xff0c;然后查看 giteveryday[7] …

ARM嵌入式学习--第四天

汇编与C混合编程 -汇编指令中调用C语言 .global _start _start:mov r0,#5mov r1,#3bl add stop:b stop int add(int a,int b) {int c a b;return c; } 无优化情况&#xff1a;&#xff08;反汇编之后&#xff0c;发现多了很多很多指令&#xff0c;运行之后结果是错误的&a…

MySQL-19.多表设计-一对多-外键

一.多表问题分析 二.添加外键 三.外键约束的问题

SpringCloud无介绍快使用,单机Eureka服务注册中心cloud-eureka-server7001搭建(十)

TOC 问题背景 从零开始学springcloud微服务项目 注意事项&#xff1a; 约定 > 配置 > 编码IDEA版本2021.1这个项目&#xff0c;我分了很多篇章&#xff0c;每篇文章一个操作步骤&#xff0c;目的是显得更简单明了controller调service&#xff0c;service调dao项目源码以及…

单位评职称需要在指定媒体上投稿发表文章看我如何轻松应对

在职场中,晋升与评职称是一项不可或缺的任务,而在这个过程中,完成相关的投稿更是至关重要。作为单位的一名员工,当我得知自己需要在指定的媒体上发表文章以满足职称评审要求时,心中既期待又忐忑。起初,我选择了传统的邮箱投稿方式,然而却没想到,这条路竟让我倍感挫折。 刚开始,…

交叉注意力融合时域、频域特征的FFT + CNN -BiLSTM-CrossAttention轴承故障识别模型

往期精彩内容&#xff1a; Python-凯斯西储大学&#xff08;CWRU&#xff09;轴承数据解读与分类处理 Pytorch-LSTM轴承故障一维信号分类(一)-CSDN博客 Pytorch-CNN轴承故障一维信号分类(二)-CSDN博客 Pytorch-Transformer轴承故障一维信号分类(三)-CSDN博客 三十多个开源…

【Pycharm默认解释器配置文件】怎样删除配置解释器的无效历史记录?

有时候我们希望删除无效的解释器路径&#xff0c;可以找到这个文件&#xff0c;进行删除修改。 C:\Users\你的用户名\AppData\Roaming\JetBrains\PyCharm2022.3\options\jdk.table.xml直接删除解释器名称对应的一整个<jdk version"2">节点即可&#xff01; …

深度学习领域,你心目中 idea 最惊艳的论文是哪篇?

深度学习发展至今&#xff0c;共经历了三次浪潮&#xff0c;20 世纪40年代到60年代深度学习的雏形出现在控制论(cybernetics)中&#xff0c;20 世纪 80 年代 到 90 年代深度学习表现为 联结主义(connectionism)&#xff0c;直到 2006 年&#xff0c;才真正以深度学习之名复兴。…

电气学习知识点

文章目录 NPN和PNP输出 NPN和PNP输出 NPN和PNP&#xff08;两种不同类型的三极管&#xff09;都是集电极输出。&#xff08;集电极开路输出&#xff09; 下图b:基极、c集电极、e发射极 NPN示意图&#xff08;集电极连接负载 — 正方形&#xff09; NPN的电流流向是从集电极…

电子物证的数字化时代:龙信科技引领取证技术革新

文章关键词&#xff1a;电子物证、手机取证、云取证、介质取证、电子数据取证 在信息技术飞速发展的今天&#xff0c;电子物证在司法领域扮演着越来越重要的角色。电子物证是指以存储于介质载体中的电磁记录或光电记录对案件事实起证明作用的电子信息数据及其附属物。与传统物…

《云计算网络技术与应用》实训6-1:配置KVM虚拟机使用NAT网络

任务1、计算节点基础环境准备 1. 使用VMware安装CentOS 7虚拟机&#xff0c;安装时记得开启CPU虚拟化&#xff0c;命名为“KVMC6”。 2. &#xff08;网卡配置和之前的一样&#xff0c;都用100网段&#xff09;网关设置为192.168.100.1&#xff0c;地址段为192.168.100.10-25…

LeetCode 3200.三角形的最大高度:枚举

【LetMeFly】3200.三角形的最大高度&#xff1a;枚举 力扣题目链接&#xff1a;https://leetcode.cn/problems/maximum-height-of-a-triangle/ 给你两个整数 red 和 blue&#xff0c;分别表示红色球和蓝色球的数量。你需要使用这些球来组成一个三角形&#xff0c;满足第 1 行…

Linux下内核空间和用户空间内存映射图详解

目录 一、简介二、内存空间定义三、内存权限四、内存空间映射图4.1 32位系统4.2 64位系统4.3 映射空间解析 五、其他相关链接1、关于linux下内存管理内容总结2、Linux内核中kzalloc分配内存时用的参数GFP_KERNEL详解3、Linux下stream内存带宽测试参数和示例详解附源码总结 一、…

HTTP cookie 与 session

一种关于登录的场景演示 - B 站登录和未登录 问题&#xff1a;B 站是如何认识我这个登录用户的&#xff1f;问题&#xff1a;HTTP 是无状态&#xff0c;无连接的&#xff0c;怎么能够记住我&#xff1f; 一、引入 HTTP Cookie 定义 HTTP Cookie&#xff08;也称为 Web Cooki…

如何区分不同类型的光源

" 声明&#xff1a;此文档中的大部分内容来源于网络&#xff0c;经校对和整理后分享给大家&#xff0c;仅供学习参考使用。" 1、问题背景 之前调试的项目中&#xff0c;客户提供的客观验收标准中要求用到TL83光源&#xff0c;用来测试图像的颜色误差及白平衡。 TL83光…

用Java爬虫API,轻松获取taobao商品SKU信息

在电子商务的世界里&#xff0c;SKU&#xff08;Stock Keeping Unit&#xff0c;库存单位&#xff09;是商品管理的基础。对于商家来说&#xff0c;SKU的详细信息对于库存管理、价格策略制定、市场分析等都有着重要作用。taobao作为中国最大的电子商务平台之一&#xff0c;提供…

windows下载配置CAS单点登录

下载 github下载 云盘瞎子啊 版本对应jdk&#xff0c;根据自身环境下载对应版本的cas。 安装 下载完成之后解压 按照.md文档执行打包命令 build.bat package配置 如果不用https&#xff0c;需要进行以下配置&#xff1a; 修改配置文件application.properties 在最后一行…

【远程监控新体验】OpenObserve结合内网穿透无公网IP远程访问全攻略

文章目录 前言1. 安装Docker2. Docker镜像源添加方法3. 创建并启动OpenObserve容器4. 本地访问测试5. 公网访问本地部署的OpenObserve5.1 内网穿透工具安装5.2 创建公网地址6. 配置固定公网地址前言 本文主要介绍如何在Linux系统使用Docker快速本地化部署OpenObserve云原生可观…