Java设计模式:单例模式(饿汉式、懒汉式、枚举实现类)

❤ 作者主页:欢迎来到我的技术博客😎
❀ 个人介绍:大家好,本人热衷于Java后端开发,欢迎来交流学习哦!( ̄▽ ̄)~*
🍊 如果文章对您有帮助,记得关注点赞收藏评论⭐️⭐️⭐️
📣 您的支持将是我创作的动力,让我们一起加油进步吧!!!🎉🎉

文章目录

  • 一、什么是单例模式
  • 二、单例模式的结构
  • 三、单例模式的类型
  • 四、单例模式的实现
    • 1. 饿汉式
      • 1.1 静态变量实现
      • 1.2 静态代码块实现
    • 2. 懒汉式
      • 2.1 基本懒汉式(线程不安全)
      • 2.2 加锁的懒汉式(线程安全)
      • 2.3 双重检查锁(优化)
  • 五、单例模式存在的问题
    • 1. 单例模式存在什么问题
    • 2. 通过序列化与反序列化破坏单例模式
    • 3. 通过反射破坏单例模式
  • 六、利用枚举类实现单例模式(极力推荐)
    • 1. 枚举实现单例模式
    • 2. 枚举类单例模式防止序列化破坏
    • 3. 枚举类单例模式防止反射破坏
  • 七、单例模式的使用场景
  • 八、单例模式总结

一、什么是单例模式

单例模式 指在内存中只会创建且仅创建一次对象的设计模式。

在程序中 多次使用同一对象且作用相同 时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中 创建一个对象,让所有需要调用的地方都 共享 这一单例对象。

未使用单例模式:
在这里插入图片描述
 

使用单例模式:
在这里插入图片描述
 


二、单例模式的结构

单例模式通常由以下几个组成部分构成:

  1. 单例类(Singleton): 单例类是单例模式的 核心,它包含一个私有的构造方法、一个私有的静态实例变量和一个公共的静态访问方法。单例类负责创建并提供访问单例实例的唯一途径。

  2. 静态实例变量(Instance): 单例类中通常包含一个私有的静态实例变量,用于保存单例实例。该变量被声明为私有的,以保证只有单例类内部可以访问和修改它。

  3. 公共的静态访问方法(getInstance): 单例类通过公共的静态访问方法提供对单例实例的全局访问。该方法通常被命名为 getInstance(),在方法内部实现懒加载或饿加载实例的创建,并返回单例实例。

单例模式的结构示意图如下:
在这里插入图片描述
 

在这个结构中,单例模式的核心是单例类,它负责创建并提供访问单例实例的方法。通过静态实例变量,单例类确保只有一个实例存在,并通过静态访问方法使得外部代码可以获取该实例。


三、单例模式的类型

单例模式主要有以下两种类型:

  • 懒汉式:真正需要使用 对象时才会去创建该单例类对象。
  • 饿汉式:类加载 时已经创建好该单例对象,等待被程序调用。

四、单例模式的实现

1. 饿汉式

在饿汉式的单例模式中,实例在类加载时就被创建,因此不存在多线程安全问题。以下是两种常见的饿汉式单例模式的实现方式:

1.1 静态变量实现

核心方法:

public class Singleton {
    // 在类加载时就创建实例
    private static final Singleton instance = new Singleton();

    // 私有构造函数,防止外部实例化
    private Singleton() {
    }

    // 静态方法返回单例实例
    public static Singleton getInstance() {
      return instance;
    }
}

测试类:

public class Singleton {
    // 在类加载时就创建实例
    private static final Singleton instance = new Singleton();

    // 私有构造函数,防止外部实例化
    private Singleton() {
    }

    // 静态方法返回单例实例
    public static Singleton getInstance() {
      return instance;
    }
}

class SingletonTest {

  public static void main(String[] args) {

      //获取单例类的对象,因为对象私有,只能通过方法去获取
      Singleton instance1 = Singleton.getInstance();
      Singleton instance2 = Singleton.getInstance();

      //判断是否为同一个对象
      System.out.println("instance1和instance2的地址是否相同:" + instance1.equals(instance2));
   }

}

测试结果:
在这里插入图片描述


1.2 静态代码块实现

核心方法:

public class Singleton {
  
    // 声明实例变量
    private static final Singleton instance;

    // 使用静态代码块在类加载时初始化实例
    static {
      instance = new Singleton();
    }

    // 私有构造函数,防止外部实例化
    private Singleton() {
    }

    // 静态方法返回单例实例
    public static Singleton getInstance() {
      return instance;
    }
}

测试类:

public class Singleton {

    // 声明实例变量
    private static final Singleton instance;

    // 使用静态代码块在类加载时初始化实例
    static {
      instance = new Singleton();
    }

    // 私有构造函数,防止外部实例化
    private Singleton() {
    }

    // 静态方法返回单例实例
    public static Singleton getInstance() {
      return instance;
    }
}


class SingletonTest {

  public static void main(String[] args) {

      //获取单例类的对象,因为对象私有,只能通过方法去获取
      Singleton instance1 = Singleton.getInstance();
      Singleton instance2 = Singleton.getInstance();

      //判断是否为同一个对象
      System.out.println("instance1和instance2的地址是否相同:" + instance1.equals(instance2));
   }

}

测试结果:
在这里插入图片描述


2. 懒汉式

2.1 基本懒汉式(线程不安全)

这种实现方式最简单,就是在程序使用对象前,先判断该对象是否已经实例化(判空),若已实例化直接返回该类对象,否则先执行实例化操作。

核心代码:

public class Singleton {

	//私有静态变量实例,用于保存单例实例
    private static Singleton instance;

	//私有构造函数,确保只有该类内部可以实例化对象,阻止外部通过构造函数创建新的实例
    private Singleton() {
    }

	//静态方法返回单例实例
    public static Singleton getInstance() {
      //如果实例为null,则创建一个新的实例
      if (instance == null) {
        instance = new Singleton();
      }
      //返回单例实例
      return instance;
    }
}

2.2 加锁的懒汉式(线程安全)

首先,我们先看一下基本懒汉式中的核心方法:

public static Singleton getInstance() {
    if (singleton == null) {
        singleton = new Singleton();
    }
    return singleton;
}

这种实现方式的 getInstance() 方法并没有考虑多线程情况,在单线程的情况下是线程安全的。但是在多线程下,如果这时候有两个线程同时调用方法并判断 singleton 为空时,那么它们就都会去实例化一个 Singleton 对象,这样就可能导致创建多个实例。

因此,为了解决多线程带来的并发问题,最直接的方法就是加锁,这里可以在方法上加锁,也可以对类对象进行加锁,加锁后的核心代码如下:

//方式一:在方法上加锁
public static synchronized Singleton getInstance() {
    if (singleton == null) {
        singleton = new Singleton();
    }
    return singleton;
}

//方式二:对类对象加锁
public static Singleton getInstance() {
    synchronized(Singleton.class) {   
        if (singleton == null) {
            singleton = new Singleton();
        }
    }
    return singleton;
}

2.3 双重检查锁(优化)

首先,我们先来讨论一下在加锁的懒汉式模式中,该模式下我们解决并发问题使用的是对方法或者类对象进行加锁操作,但是这种方式也存在着问题,虽然加锁能解决线程安全问题,但是造成性能下降。每次调用 getInstance() 方法都需要进行同步,即使实例已经创建,仍然需要获取锁,这就大大影响了并发性能。

因此,我们进行优化的地方就是:如果没有实例化对象则加锁创建,如果已经实例化了,则不需要加锁,直接获取实例对象即可

最终,产生了一种新的实现模式:双重检查加锁

核心代码如下:

public static Singleton getInstance() {

    if (singleton == null) {  //第一次判断,如果singleton不为null,则不进入抢锁阶段,直接返回实例
        synchronized(Singleton.class) { //如果singleton为null,则进行抢锁操作
            if (singleton == null) { //第二次判断,抢到锁之后再次判断是否为null
                singleton = new Singleton();
            }
        }
    }
    return singleton;
}

代码详细解析:

  • 第3行代码,如果 singleton 不为空,则直接返回对象,不需要获取锁;而如果多个线程发现 singleton 为空,则进入下一环节;
  • 第4行代码,多个线程抢占同一把锁,只有一个线程会获取锁成功,第一个获取到锁的线程会再次判断 singleton 是否为空,因为 singleton 有可能已经被之前的线程实例化了;
  • 接下来,其他线程抢占到上一个线程释放的锁后,会去执行第4行代码进行判断,发现 singleton 已经不为空了,则不需要再去 new 一个对象,直接返回对象即可;
  • 之后所有进入该方法的线程都不会去获取锁了,因为在第一次判断的时候 singleton 已经不为空了。

双重检查锁模式下的完整代码如下:

public class Singleton {

    private static Singleton singleton;

    private Singleton() {
    }

    public static Singleton getInstance() {

        if (singleton == null) {
            synchronized(Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

到这里就基本理解了双重检查加锁,但大家可能有这个疑问:就是在双重检查锁的模式下,为什么需要进行两次的判断呢?

现在我们假设有两个线程A、B,他们同时都去请求我们单例模式下类的实例,当第一次判断的时候,此时 singleton 为空,所以两个线程都进入到对锁的抢占,假设线程A先抢占到了锁,那么线程B只能在加锁的代码外部进行等待,这个时候线程A创建了对象的实例,完成功能后释放了锁,这个时候线程B就抢占到了锁,进入代码内部。假设此时没有第二次判断的话,那么线程B也会去再次创建一个对象,这样就会出现多个对象了,因为线程A已经实例化对象,所以等到线程B的时候 singleton 已经是不为空了(线程A创建的),因此线程B只需直接返回即可。


双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,但是还存在这最后一个问题:指令重排

JVM在实例化对象的时候会进行优化和指令重排操作,在 多线程 的环境下,就会出现 空指针 问题。

指令重排序是指:JVM在保证最终结果正确的情况下,可以不按照程序编码的顺序执行语句,尽可能提高程序的性能。

以下将对在多线程环境中,因为JVM的指令重排操作导致出现空指针的问题进行讲解:
JVM创建上述对象的过程主要分为三步:

  1. singleton 分配内存空间;
  2. 初始化 singleton 对象;
  3. singleton 指向分配好的内存空间

在这三步中,第2、3步有可能发生发生指令重排现象,即创建对象的顺序由原本的 1-2-3 变为 1-3-2,这样对创建对象并没有什么影响,但是在多线程环境下,假设线程A、线程B都去获取对象,有可能线程A在创建对象的过程中先执行了 1-3 操作,然后在准备去执行 2 操作的时候,这时候线程B提前进来了,此时线程B判断 singleton 已经不为空了,但是线程A还没有对 singleton 进行初始化,因此线程B获取到的是线程A未初始化好的 singleton 对象,最终就会报 空指针异常

具体的示意图如下:
在这里插入图片描述
 

在Java中,可以使用 volatile 关键字来防止JVM对指令进行重排。当一个字段被声明为 volatile 时,编译器和处理器会注意到这个字段可能被其他线程同时访问,因此不会对其进行优化重排。

具体来说,volatile 关键字提供了两个主要的功能:

  1. 禁止指令重排: 当一个字段被声明为 volatile 时,编译器和处理器会确保在读取、写入这个字段的操作前后不会进行重排,保证操作的有序性。
  2. 可见性: volatile 关键字还具有可见性的特性,即当一个线程修改了被volatile修饰的变量的值,其他线程会立即看到这个变化。这确保了多线程环境下的正确性。

最终实现的完整代码如下:

public class Singleton {

    private static volatile  Singleton singleton;

    private Singleton() {
    }

    public static Singleton getInstance() {

        if (singleton == null) {
            synchronized(Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

五、单例模式存在的问题

1. 单例模式存在什么问题

在上面我们定义的单例类(Singleton)中正常情况下只可以同时只有一个对象存在,但是存在着一些操作可以破坏这种现象,即可以使得上述单例模式中可以创建多个对象。

破坏这种现象主要有以下两种方式:

  • 序列化
  • 反射

2. 通过序列化与反序列化破坏单例模式

我们通过以下测试类来检验序列化与反序列化是否会创建不同的对象:

    public static void main(String[] args) {
    
        // 创建输出流
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Singleton.file"));
        
        // 将单例对象写到文件中
        oos.writeObject(Singleton.getInstance());
        
        // 从文件中读取单例对象
        File file = new File("Singleton.file");
        ObjectInputStream ois =  new ObjectInputStream(new FileInputStream(file));
        Singleton newInstance = (Singleton) ois.readObject();
        
        // 判断是否是同一个对象
        System.out.println(newInstance == Singleton.getInstance()); // false
    }

很显然,通过序列化和反序列化的方式,最终创建了两个不同的对象。

原因分析:
出现这种情况的原因是:readObject() 方法读入对象时,它必定会返回一个新的对象实例,因此必然会只想新的内存地址。

解决方案:
解决序列化和反序列化破坏单例模式的问题通常需要在单例类中添加一个特殊的方法,即 readResolve() 方法。这个方法在序列化时被调用,它允许开发者指定在反序列化后返回哪个实例,从而防止生成新的实例破坏单例模式。

以下展示了如何使用 readResolve() 方法来解决序列化与反序列化破坏单例模式的问题:

import java.io.Serializable;

public class Singleton implements Serializable {
  // 私有静态实例
  private static final Singleton INSTANCE = new Singleton();

  // 私有构造方法
  private Singleton() {}

  // 公共静态方法获取实例
  public static Singleton getInstance() {
    return INSTANCE;
  }

  // readResolve方法,确保在反序列化时返回同一实例
  private Object readResolve() {
    return INSTANCE;
  }
}

3. 通过反射破坏单例模式

同样,我们通过以下测试类来检验反射是否会创建不同的对象:

    public static void main(String[] args) {
    
        // 获取类的显式构造器
        Constructor<Singleton> construct = Singleton.class.getDeclaredConstructor();
        
        // 可访问私有构造器
        construct.setAccessible(true);
        
        // 利用反射构造新对象
        Singleton obj1 = construct.newInstance();
        
        // 通过正常方式获取单例对象
        Singleton obj2 = Singleton.getInstance();
        System.out.println(obj1 == obj2); // false
    }

很显然,通过反射的方式,最终也是创建了两个对象。

原因分析:
出现这种情况的原因是:通过反射,可以绕过单例类的私有构造方法的限制,直接调用构造方法创建新的实例,从而破坏了单例模式的约束。

解决方案:
要解决反射破坏单例模式的问题,可以在单例类的私有构造方法中进行特殊处理,以防止多次实例化。一种常见的方法是在构造方法中检查是否已存在实例,如果存在则抛出异常,防止通过反射再次创建实例。

public class Singleton {
  // 私有静态实例
  private static final Singleton INSTANCE = new Singleton();

  // 私有构造方法
  private Singleton() {
    // 在构造方法中检查实例是否已存在,如果存在则抛出异常
    if (INSTANCE != null) {
      throw new IllegalStateException("Singleton instance already created");
    }
  }

  // 公共静态方法获取实例
  public static Singleton getInstance() {
    return INSTANCE;
  }
}

六、利用枚举类实现单例模式(极力推荐)

1. 枚举实现单例模式

枚举实现方式属于饿汉式方式。

枚举类实现单例模式是极力推荐的单例实现模式,因为在Java中,枚举类型是线程安全且只会被实例化一次的,因此,当使用枚举方式实现单例模式时,无需担心多线程并发访问的问题。枚举类的实例在类加载时被创建,并且无法通过反射或序列化等方式再次实例化,是 唯一不会被破坏 的单例模式。

枚举实现的单例模式中,代码写法非常简单,具体如下:

public enum Singleton {
    INSTANCE;
}

我们可以这样来简单理解枚举实现单例的过程: 在程序启动时,会调用 Singleton 的空参构造器,实例化一个 Singleton 对象赋给 INSTANCE,之后再也不会去实例化。

接下来,我们使用一个测试类来证明在程序启动时仅会创建一个 Singleton 对象,并且是线程安全的。

public enum Singleton {
    
    INSTANCE;
    
    Singleton() { 
        System.out.println("枚举创建对象了"); 
    }
        
    public static void main(String[] args) {
        
        Singleton t1 = Singleton.INSTANCE;
        Singleton t2 = Singleton.INSTANCE;
        
        System.out.print("t1和t2的地址是否相同:" + t1 == t2);
    }
}

测试结果如下:

枚举创建对象了
t1和t2的地址是否相同:true

2. 枚举类单例模式防止序列化破坏

在读入 Singleton 对象时,每个枚举类型和枚举名字都是唯一的,所以在序列化时,仅仅只是对枚举的 类型和变量名 输出到文件中,在读入文件反序列化成对象时,利用 Enum 类的 valueOf(String name) 方法根据变量的名字查找对应的枚举对象。

所以,在序列化与反序列化的过程中,只是写出和读入了 枚举类型和名字,没有任何关于对象的操作。


3. 枚举类单例模式防止反射破坏

枚举类默认继承了 Enum 类,在利用反射调用 newInstance() 方法时,会判断该类是否是一个枚举类,如果是的话,则直接抛出异常。


七、单例模式的使用场景

单例模式适用于以下场景:

  1. 资源共享的情况: 当多个模块需要共享某个资源(例如数据库连接池或配置信息对象)时,使用单例模式可以确保只有一个实例存在,避免资源的重复创建和浪费。
  2. 控制资源的访问: 单例模式可以用于控制对资源的访问,例如线程池、线程管理、日志管理等,确保在系统中只有一个实例来管理这些资源,避免竞态条件和冲突。
  3. 配置管理: 当一个系统需要有一个全局的配置管理对象,负责读取配置文件,并提供配置信息给其他模块使用时,单例模式可以确保配置信息的一致性和唯一性。
  4. 日志记录: 在多线程环境下,使用单例模式可以确保日志记录的一致性,避免多个日志实例同时写入导致混乱。
  5. 线程池: 在需要维护线程池的应用中,单例模式可以用于确保只有一个线程池实例,以便更好地控制线程的创建和管理。
  6. 数据库连接池: 单例模式可以用于管理数据库连接池,确保系统中只有一个连接池实例,避免过多的数据库连接占用资源。
  7. 缓存管理: 当需要管理全局的缓存对象时,单例模式可以确保只有一个缓存管理实例,便于统一管理和更新缓存。

八、单例模式总结

  1. 单例模式常见的两种类型: 懒汉式、饿汉式
  2. 懒汉式: 在需要用到对象时才实例化对象,最佳的实现方式是 双重检查加锁 ,解决了并发安全和性能低下问题。
  3. 饿汉式: 在类加载时就创建好了单例对象,在获取单例对象时直接返回对象即可,不会存在并发安全和性能问题。
  4. 如果对内存要求非常高,则使用懒汉式写法,可以在特定的时候才创建该对象。
  5. 如果对内存要求不高,则使用饿汉式写法,因为实现简单,并且没有任何并发安全和性能安全。
  6. 为了防止在多线程环境下,因为指令重排导致出现空指针问题,可以在单例对象上添加 volatile 关键字来防止指令重排。
  7. 最好的实现方式是通过 枚举 来实现单例模式,实现方式简单,没有任何线程安全问题,且 Enum 类内部可以防止反射和序列化来破坏单例模式。

 
非常感谢您阅读到这里,如果这篇文章对您有帮助,希望能留下您的点赞👍 关注💖 分享👥 留言💬thanks!!!

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

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

相关文章

[足式机器人]Part2 Dr. CAN学习笔记-数学基础Ch0-4线性时不变系统中的冲激响应与卷积

本文仅供学习使用 本文参考&#xff1a; B站&#xff1a;DR_CAN Dr. CAN学习笔记-数学基础Ch0-4线性时不变系统中的冲激响应与卷积 1. LIT System&#xff1a;Linear Time Invariant2. 卷积 Convolution3. 单位冲激 Unit Impulse——Dirac Delta 线性时不变系统 &#xff1a; L…

目标检测综述(待补ing)

文章目录 摘要引言目标检测发展历程目标检测路线图传统检测器基于CNN的两阶段检测器基于CNN的一阶段检测器 目标检测数据集及指标数据集评价指标标注软件 backboneAlexNet&#xff08;2012&#xff09;VGGNet&#xff08;2014&#xff09;GoogleNet&#xff08;2014&#xff09…

使用函数计算,数禾如何实现高效的数据处理?

作者&#xff5c;邱鑫鑫&#xff0c;王彬&#xff0c;牟柏旭 公司背景和业务 数禾科技以大数据和技术为驱动&#xff0c;为金融机构提供高效的智能零售金融解决方案&#xff0c;服务银行、信托、消费金融公司、保险、小贷公司等持牌金融机构&#xff0c;业务涵盖消费信贷、小…

Node.js快速搭建简单的HTTP服务器并发布公网远程访问

文章目录 前言1.安装Node.js环境2.创建node.js服务3. 访问node.js 服务4.内网穿透4.1 安装配置cpolar内网穿透4.2 创建隧道映射本地端口 5.固定公网地址 前言 Node.js 是能够在服务器端运行 JavaScript 的开放源代码、跨平台运行环境。Node.js 由 OpenJS Foundation&#xff0…

什么牌子的灯具性价比高?性价比高适合学生的护眼台灯推荐

国家卫生健康委疾控局副局长再那吾东玉山在发布会上介绍&#xff0c;国家卫生健康委2020年9到12月全面开展了近视专项调查&#xff0c;覆盖了全国8604所学校&#xff0c;共筛查247.7万名学生。结果显示&#xff1a;2020年&#xff0c;我国儿童青少年总体近视率为52.7%。其中6岁…

王树森深度强化学习 笔记

本笔记基于王树森的深度强化学习课程 文章目录 王树森深度强化学习 笔记一、基础1. 概率论2. 名词3. Return U t U_t Ut​4. Action-Value Function Q π ( s , a ) Q_\pi(s, a) Qπ​(s,a)5. State-Value Function V π ( s ) V_\pi(s) Vπ​(s) 二、Value-Based Reinforc…

深入理解Sentinel系列-2.Sentinel原理及核心源码分析

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱吃芝士的土豆倪&#xff0c;24届校招生Java选手&#xff0c;很高兴认识大家&#x1f4d5;系列专栏&#xff1a;Spring源码、JUC源码、Kafka原理、分布式技术原理&#x1f525;如果感觉博主的文章还不错的话&#xff…

04 ECharts基础入门

文章目录 一、ECharts介绍1. 简介2. 相关网站3. HTML引入方式4. 基本概念 二、常见图表1. 柱状图2. 折线图3. 饼图4. 雷达图5. 地图 三、应用1. 动画2. 交互 一、ECharts介绍 1. 简介 ECharts是一个使用JavaScript实现的开源可视化库&#xff0c;用于生成各种图表和图形。 EC…

RT_Thread_修改为外部晶振及验证

关注两处&#xff1a; 1、stm32f4xx_hal_conf.h&#xff0c;外部晶振频率HSE宏定义 2、drv_clk.c&#xff0c;system_clock_config函数 1、外部晶振频率HSE宏定义 根据实际外部晶振的频率去定义&#xff0c;使用的是8MHz&#xff1b; 2、system_clock_config 开启HSE&#…

改造python3中的http.server为简单的文件上传下载服务

改造 修改python3中的http.server.SimpleHTTPRequestHandler&#xff0c;实现简单的文件上传下载服务 simple_http_file_server.py&#xff1a; # !/usr/bin/env python3import datetime import email import html import http.server import io import mimetypes import os …

Thymeleaf生成pdf表格合并单元格描边不显示

生成pdf后左侧第一列的右描边不显示&#xff0c;但是html显示正常 显示异常时描边的写法 cellpadding“0” cellspacing“0” &#xff0c;td,th描边 .self-table{border:1px solid #000;border-collapse: collapse;width:100%}.self-table th{font-size:12px;border:1px sol…

markdown学习(初学者)

学习途径&#xff1a; 在线markdown编辑器_微信公众号markdown排版工具 Markdown 基本语法 | Markdown 官方教程 标题 要创建标题&#xff0c;请在单词或短语前面添加井号 (#) 。# 的数量代表了标题的级别。例如&#xff0c;添加三个 # 表示创建一个三级标题 (<h3>) (…

【华为网络-配置-023】- 一般企业网架构方案(单节点方案)

要求&#xff1a; 1、防火墙 FW1 G1/0/0 接口使用 PPPoE 拨号获取 IP 地址。 2、FW1 配置信任&#xff08;内网包含服务器&#xff09;和非信任区域&#xff08;Internet 外网&#xff09;。 3、FW1 配置 NAPT 使内网可以上网。 4、核心交换机 LSW1 划分 VLAN 并配置各接口及三…

Python 日期时间模块详解(datetime)

文章目录 1 概述1.1 datetime 类图1.2 类描述 2 常用方法2.1 获取当前日期时间&#xff1a;now()、today()、time()2.2 日期时间格式化&#xff1a;strftime()2.3 日期时间大小比较&#xff1a;>、、<2.4 日期时间间隔&#xff1a;- 3 扩展3.1 Python 中日期时间格式化符…

Java / Scala - Trie 树简介与应用实现

目录 一.引言 二.Tire 树简介 1.树 Tree 2.二叉搜索树 Binary Search Tree 3.字典树 Trie Tree 3.1 基本概念 3.2 额外信息 3.3 结点实现 3.4 查找与存储 三.Trie 树应用 1.应用场景 2.Java / Scala 实现 2.1 Pom 依赖 2.2 关键词匹配 四.总结 一.引言 Trie 树…

案例054:基于微信的追星小程序

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

安装node.js并创建第一个vue项目

目录 一&#xff0c;下载node.js 二&#xff0c;创建一个vue项目 一&#xff0c;下载node.js 1.进入官网&#xff1a;Node.js (nodejs.org) 2.选择版本 3.选择安装方式 4.运行安装包&#xff0c;下载文件 5.选择要安装的路径后一直next 6.安装完成后打开命令提示符&#xff…

PHP短信接口防刷防轰炸多重解决方案三(可正式使用)

短信接口盗刷轰炸&#xff1a;指的是黑客利用非法手段获取短信接口的访问权限&#xff0c;然后使用该接口发送大量垃圾短信给目标用户 短信验证码轰炸解决方案一(验证码类解决)-CSDN博客 短信验证码轰炸解决方案二(防止海外ip、限制ip、限制手机号次数解决)-CSDN博客 PHP短信…

推荐几款转换视频格式的好用转换工具,小白也能上手

视频格式转换工具是一种专门转换视频的软件&#xff0c;可让你将一种视频格式转换为另一种视频格式&#xff08;例如&#xff0c;MOV 到 MP4&#xff09;&#xff0c;通常可以节省空间。 本文将介绍一些用于转换视频格式的好用转换工具&#xff0c;并且详细描述了它们的主要功…

【FPGA】数字电路设计基础

数字电路基础 1 什么是数字电路 在学习数字电路之前&#xff0c;我们先要了解下什么是数字电路。想要搞明白数字电路&#xff0c;就要搞明白生活中有 两种概念&#xff0c; 数字信号和模拟信号&#xff0c;模拟信号一般包括压力、气温、速度等信号&#xff0c;模拟量的值是可…