一文带你彻底搞懂设计模式之单例模式!!由浅入深,图文并茂,超超超详细的单例模式讲解!!

一文带你彻底搞懂设计模式之单例模式!

  • 一、什么是单例模式?
      • 单例模式分类
        • 饿汉式创建单例对象
        • 懒汉式创建单例对象
      • 多问一个为什么?
  • 二、为什么要有单例模式?
      • 使用单例模式的原因
      • 单例模式的应用场景
  • 三、多线程下的单例模式
      • 饿汉式
      • 懒汉式
        • 懒汉加锁
        • 加锁优化(双重检验加锁)
      • 使用volatile防止指令重排
  • 四、破坏懒汉式单例与饿汉式单例
      • 反射破坏
      • 序列化与反序列化破坏
        • 原因分析
        • 问题解决
  • 五、枚举实现
  • 六、总结

一、什么是单例模式?

单例模式(Singleton),保证一个类仅有一个实例,并提供一个访问它的全局访问点。——《大话设计模式》

单例模式是在内存中仅会创建一次对象的设计模式
在这里插入图片描述

简单来说单例模式的简单实现就是
成员是 私有的静态的
构造方法是 私有的
对外暴露的获取访问是 公有的静态的

单例模式分类

  • 饿汉式:类加载就会导致该单实例对象被创建

  • 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时被创建

饿汉式创建单例对象

饿汉式在类加载时已经创建好该对象,在程序调用时直接返回该单例对象即可,即我们在编码时就已经指明了要马上创建这个对象,不需要等到被调用时再去创建。

饿汉式简单实现代码如下:

package org.example;/*** @Author:wjy*/public class Singleton{//在该类中创建一个该类的对象供外界去使用private static Singleton instance= new Singleton();// 构造方法 private 化private Singleton(){}// 得到 Singleton 的实例(唯一途径)public static Singleton getInstance() {return instance;}
}
懒汉式创建单例对象

懒汉式创建对象的方法是在程序使用对象前,先判断该对象是否已经实例化(判空),若已实例化直接返回该类对象。,否则则先执行实例化操作。

懒汉式简单实现代码如下:

package org.example;/*** @Author:wjy*/
public class Singleton {//在该类中创建一个该类的对象供外界去使用private static Singleton instance;// 构造方法 private 化private Singleton(){ }// 得到 Singleton 的实例(唯一途径)public static Singleton getInstance(){if (instance == null){instance = new Singleton();}return instance;}
}

客户端代码:

public class Main {public static void main(String[] args) {// Singleton s0 = new Singleton(); // 原先的实例化方法Singleton s1 = Singleton.getInstance();Singleton s2 = Singleton.getInstance();if (s1 == s2){System.out.println("两个对象是相同的实例");}}
}

可以看到打印结果是同一对象

在这里插入图片描述

多问一个为什么?

此处,我们可能在潜意识中就这样让代码过去,也许是因为认为这就是规定或者认为是很基础的内容而无需解释过多,在许多文章中,也并未更加详细的解释这个问题.
但是,如果是作为一个可能基础不那么稳固的初学者(比如本人T-T),在学习的时候,为加深理解,我们不妨多问一个为什么?为什么是这样设计呢?

  1. 成员变量 instance 为什么是私有的?
    最直接的原因就是 防止外部直接访问,如果不声明为 private ,其他类将能直接访问和修改它,而这就违反了单例模式的原则,因为单例模式要求类只有一个实例化
  2. 成员变量 instance 为什么是静态的?
    使用 static 修饰后,意味着该变量是属于类的,而不是类的实例
    你可以通过 类名.变量名 的方式直接访问
    同时,将只存在一个 instance
    你可以像
    Singleton s1 = Singleton.getInstance();
    Singleton s2 = Singleton.getInstance();
    这样创建两个实例化对象,但他们都共享一个 instance 对象,确保只有一个单例
  3. 构造方法 Singleton() 为什么是私有的?
    与成员变量相似,是为了防止外部实例化 ,防止其他类通过 new 关键字直接创建该类的实例(new 关键字本质是调用了类的构造方法),而应该通过特定途径(通常是类提供的静态方法,也就是 getInstance()
  4. getInstance() 方法为什么是公有的?
    很显然,这是我们暴露给其他类调用来创建实例的方法,因此必须是公有的,如果是私有那不就是成黑盒搁这里圈地自萌了~
  5. getInstance() 方法为什么是静态的?
    与成员变量相似,因为构造方法被私有化了,我们无法通过 new 关键字来实例化对象,而通过 类名.方法名 的方式可以直接访问

好的,我们已经明白了一个简单的单例模式的基本实现,了解了单例模式是什么,而在我们继续深入单例模式的各种实现之前,我们加入一个小插曲,你也许会有这样的疑问,我们为什么要有单例模式呢?单例模式应用场景有哪些?

二、为什么要有单例模式?

使用单例模式的原因

  1. 资源控制:单例模式可以用来控制系统中的资源,例如数据库连接池或线程池,确保这些关键资源不会被过度使用。

  2. 内存节省:当需要一个对象进行全局访问,但创建多个实例会造成资源浪费时,单例模式可以确保只创建一个实例,节省内存

  3. 共享:单例模式允许状态或配置信息在系统的不同部分之间共享,而不需要传递实例。

  4. 延迟初始化:单例模式支持延迟初始化,即实例在首次使用时才创建,而不是在类加载时。

  5. 一致的接口:单例模式为客户端提供了一个统一的接口来获取类的实例,使得客户端代码更简洁。

  6. 易于维护:单例模式使得代码更易于维护,因为所有的实例都使用相同的实例,便于跟踪和修改变更。

单例模式的应用场景

  1. 配置管理器:在应用程序中,配置信息通常只需要读取一次,并全局使用。单例模式用于确保配置管理器只被实例化一次。

  2. 日志记录器:一个系统中通常只需要一个日志记录器来记录所有的日志信息,使用单例模式可以避免日志文件的重复写入。

  3. 数据库连接池:数据库连接是一种有限的资源,使用单例模式可以确保数据库连接池的唯一性,并且能够重用连接,减少连接创建和销毁的开销。

  4. 线程池:类似于数据库连接池,线程池也是有限的资源,使用单例模式可以避免创建过多的线程,提高应用程序的并发性能。

  5. 任务调度器:在需要全局调度和管理的场景下,如定时任务调度器,单例模式提供了一个集中的管理方式。

  6. 网站的计数器:一般也是采用单例模式实现,否则难以同步。

值得注意的是,在许多框架中,单例模式也有广泛的应用,比如Spring,可以看看这篇文章

说一说Spring中的单例模式

总之,单例模式在确保资源有效管理、减少不必要的开销、以及提供全局访问控制方面有着广泛的应用。当然,滥用单例模式也可能导致代码的灵活性和可测试性降低,因此应当在适当的场景下谨慎使用。


好的我们在知晓单例模式的强大之处后,继续深入探讨一下单例模式的实现问题吧!

三、多线程下的单例模式

让我们编写一个测试,让三个线程去尝试实例化:

package org.example;/*** @Author:wjy*/
public class Main1 {public static void main(String[] args) {// 创建两个线程,尝试创建两个 Singleton 实例Thread t1 = new Thread(() -> {Singleton s = Singleton.getInstance();System.out.println("Thread 1 created instance: " + s);});Thread t2 = new Thread(() -> {Singleton s = Singleton.getInstance();System.out.println("Thread 2 created instance: " + s);});Thread t3 = new Thread(() -> {Singleton s = Singleton.getInstance();System.out.println("Thread 3 created instance: " + s);});// 启动线程t1.start();t2.start();t3.start();// 等待线程结束try {t1.join();t2.join();t3.join();} catch (InterruptedException e) {e.printStackTrace();}}
}

饿汉式

因为饿汉式单例模式下,单例实例在类加载的时候就已经创建,并在静态化容器中完成初始化,所以他通常是线程安全的

在这里插入图片描述

可以看到,三个线程获取到的实例是相同的

懒汉式

接着是懒汉式单例模式
首先,让我们回顾一下核心的 getInstance 方法实现:

    // 得到 Singleton 的实例(唯一途径)public static Singleton getInstance(){if (instance == null){instance = new Singleton();}return instance;}

测试一下,会发现出现问题,创建了三个实例!!

在这里插入图片描述

这是因为,如果三个线程在调用 getInstance() 方法时
同时判断 instance 为空,那么它们都会去实例化一个 instance 对象,这就变成三例了。如图所示:

在这里插入图片描述
在这里插入图片描述
所以,我们要解决的是线程安全问题。

懒汉加锁

当然,我们最容易想到的呢就是在方法上加锁,或者对类对象进行加锁


public static synchronized Singleton getInstance() {if (instance== null) {instance= new Singleton();}return singleton;
}// 或者public static Singleton getInstance() {synchronized(Singleton.class) {   if (instance== null) {instance = new Singleton();}}return instance ;
}

通过 synchronized 关键字,可以让每个线程在进入方法之前,都要等到别的线程都离开此方法,不会有两个线程同时进入此方法

在这里插入图片描述

可以看到,此时三个线程获取到的实例都是同样的了

但是这带来了新的问题:每次去获取对象都需要先获取锁,并发性能非常地差

加锁优化(双重检验加锁)

接下来要做的就是优化性能,目标是:
如果没有实例化对象则加锁创建,
如果已经实例化了,则不需要加锁,直接获取实例

在这里插入图片描述

所以直接在方法上加锁的方式就被废掉了,因为这种方式无论如何都需要先获取锁

所以优化的关键点其实是在于 加锁的时机

    public static Singleton getInstance(){if(instance == null){ // 检查实例是否存在,不存在则进入下一步synchronized (Singleton.class){  // 防止多个线程同时进入创建实例if(instance == null){instance = new Singleton();}}}return instance;}

上面的代码已经完美地解决了并发安全+性能低效问题:

  • 第2行代码,如果 instance 不为空,则直接返回对象,不需要获取锁;而如果多个线程发现 instance 为空,则进入分支;
  • 第3行代码,多个线程尝试争抢同一个锁,只有一个线程争抢成功,第一个获取到锁的线程会再次判断 instance 是否为空,因为 instance 有可能已经被之前的线程实例化
  • 其它之后获取到锁的线程在执行到第4行校验代码,发现 instance 已经不为空了,则不会再 new 一个对象,直接返回对象即可
  • 之后所有进入该方法的线程都不会去获取锁,在第一次判断 instance 对象时已经不为空了

一图胜千言,如图,假设这是初次实例化

  1. 三个线程都进行实例化判断,发现实例不存在,进入三线程的三国争霸(抢锁)!!

在这里插入图片描述

  1. 线程 C 抢赢了,获取到了锁,然后进行判断实例是否存在,发现不存在,直接 new 一个 —— 该单例就被线程C创建成功!
    在这里插入图片描述

  2. 线程 C 退出了,然后归还了锁,此时在锁外面双眼红光,饥渴难耐的线程B立马就抢了锁,在他正准备大干一场创建实例时,判断发现已经创建过了,实例 instance 存在,大败而归!

在这里插入图片描述

  1. 线程A 同理,接着线程 ABC就都退出了本次的 getInstance() 实例争霸赛

这里再着重讲述一下,为什么需要在加锁后再次进行实例化的判断呢?
根据上述步骤3,可以发现如果不进行判断,刚拿到锁的线程B会再次创建一个实例,此时就会创建两个实例,违背了单例模式的要求

因为需要两次判空,且对类对象加锁,该懒汉式写法也被称为:Double Check(双重校验) + Lock(加锁)

使用volatile防止指令重排

创建一个对象,在JVM中会经过三步:

(1)为 instance 分配内存空间

(2)初始化 instance 对象

(3)将 instance 指向分配好的内存空间

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

通俗点来讲,就是先给 instance 找到住的地方,然后把 instance 安排入住,最后给 instance 贴上门牌号
而指令重排序也就是为了提高效率,可能在为 instance 找到住的地方后,先不让他住进去,而是先贴门牌号再让他住进去

在这三步中,第2、3步有可能会发生指令重排现象,创建对象的顺序变为1-3-2,会导致多个线程获取对象时,有可能线程A创建对象的过程中,执行了1、3步骤时,

线程B判断 instance 已经不为空,获取到还未初始化的 instance 对象并返回,这就会报 NPE 异常。

注意在 if(instance == null)== 操作符判断的是该对象的内存地址,也就是步骤 3
还是门牌号的例子,== 就相当于检查门牌号,然后线程 B 发现你家有门牌号,就把你物业费账单提交了,但其实这时候你甚至还没入住!!

文字较为晦涩,可以看流程图:

在这里插入图片描述
使用 volatile 关键字可以防止指令重排序,​其原理较为复杂,这篇博客不打算展开,可以这样理解:使用 volatile 关键字修饰的变量,可以保证其指令执行的顺序与程序指明的顺序一致,不会发生顺序变换,这样在多线程环境下就不会发生NPE异常了。

最终的代码如下:

public class Singleton {private static volatile Singleton instance;// 构造方法 private 化private Singleton(){}// 得到 Singleton 的实例(唯一途径)public static Singleton getInstance(){if(instance == null){ // 检查实例是否存在,不存在则进入下一步synchronized (Singleton.class){  // 防止多个线程同时进入创建实例if(instance == null){instance = new Singleton();}}}return instance;}
}

四、破坏懒汉式单例与饿汉式单例

无论是完美的懒汉式还是饿汉式,终究敌不过反射和序列化,它们俩都可以把单例对象破坏掉(产生多个对象)。

反射破坏

  1. 演示利用反射破坏单例模式
  public static void main(String[] args) throws Exception {//获取Singletion类的字节码;Class<Singleton> singletonClass = Singleton.class;//获取无参构造方法,用来创建对象。Constructor con = singletonClass.getDeclaredConstructor();//由于是private修饰,所以使用暴力破解;con.setAccessible(true);//创建对象Singleton s1 = (Singleton) con.newInstance();Singleton s2 = (Singleton) con.newInstance();System.out.println(s1 == s2); // 返回 false}

上述的代码一针见血了:利用反射,强制访问类的私有构造器,去创建另一个对象

序列化与反序列化破坏

  1. 利用序列化与反序列化破坏单例模式
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() 方法读入对象时,它必定会返回一个新的对象实例,必然指向新的内存地址。

原因分析

实际上对于序列化与反序列化破坏单例模式的问题,主要是通过readObject()方法,出现了破坏单例模式的现象,主要是因为这个方法最后会通过反射调用无参数的构造方法创建一个新的对象,从而每次返回的对象都不一致。

对于反射破坏单例模式是因为单例模式通过 setAccessible(true) 指示反射的对象在使用时,取消了 Java 语言访问检查,使得私有的构造函数能够被访问,

而单例模式的设计在于只保留一个公有静态函数来获取唯一的实例,其他方法(构造函数)或字段为私有,外界不能访问。

而反射破坏了这一原则,它突破了构造函数私有的限制,可以获取单例类的私有构造函数并使用其创建多个对象。

问题解决

● 序列化、反序列方式破坏单例模式的解决方法
readObject()方法的调用栈的底层方法中有这么两个方法:
hasReadResolveMethod:
表示如果实现了serializable 或者 externalizable接口的类中包含readResolve则返回true

invokeReadResolve :通过反射的方式调用要被反序列化的类的readResolve方法。

详细讲解可以看这篇文章:
设计模式|序列化、反序列化对单例的破坏、原因分析、解决方案及解析

所以,原理也就清楚了,主要在Singleton中定义readResolve方法,并在该方法中指定要返回的对象的生成策略,就可以防止单例被破坏。

public class Singleton implements Serializable {private static volatile Singleton singleton;private Singleton() {}public static Singleton getSingleton() {if (singleton == null) {synchronized (Singleton.class) {if (singleton == null) {singleton = new Singleton();}}}return singleton;}// 防止序列化private Object readResolve() {return singleton;}}

● 反射方式破解单例的解决方法
因为反射是一种暴力获取对象实例的方法,因为他可以直接访问private修饰的构造函数,所以在对于反射方式破坏单例模式的问题上我们只能采取被动的防御,

既然你能访问我的构造函数,我就在我的构造函数中建立防御机制,不让你通过我的构造函数创建多个实例对象。

在构造方法来添加一些限制,如果存在实例,那么就抛出异常!

public class Singleton{private static volatile Singleton singleton;private Singleton() {if (Singleton.singleton != null) {throw new RuntimeException("不允许创建多个单例!");}}public static Singleton getSingleton() {if (singleton == null) {synchronized (Singleton.class) {if (singleton == null) {singleton = new Singleton();}}}return singleton;}}

五、枚举实现

我们已经掌握了懒汉式与饿汉式的常见写法了,在《图解设计模式》以及《大话设计模式》中,并没有再做更多的涉及。但是,追求极致的我们,怎么能够止步于此,在《Effective Java》书中,给出了终极解决方法。

在 JDK1.5 后,使用 Java 语言实现单例模式的方式又多了一种:枚举

我们先来看看枚举如何实现单例模式的,如下代码:

public enum Singleton {INSTANCE;public void businessMethod() {System.out.println("我是一个单例!");}
}

枚举类实现单例模式是极力推荐的单例实现模式,因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式,枚举的写法非常简单,而且枚举类型是所用单例实现中唯一一种不会被破坏的单例实现模式。

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

  1. 防止反射攻击
  • 枚举类型的实例在编译后会被编译成静态常量,并且在运行时通过Enum.valueOf()方法来获取实例。这个方法在内部会检查传递的名称是否与枚举定义中的名称匹配,并且直接返回对应的枚举实例,而不允许创建新的枚举实例。
  • 因此,即使调用者尝试使用反射创建新的枚举实例,也会因为名称不匹配而失败。例如,Enum.valueOf(MyEnum.class, "NEW_VALUE")将会抛出IllegalArgumentException
  1. 防止序列化/反序列化攻击
  • 枚举实例在序列化时会被转换为其名称,而不是实例本身。这意味着,即使反序列化时提供了枚举实例的字节流,也会根据名称来查找对应的枚举实例,而不是创建一个新的实例。
  • 由于枚举实例是静态的,反序列化时并不会创建新的实例,而是恢复之前序列化时的那个实例。这样,即使通过序列化/反序列化机制,也无法创建新的枚举实例。
  1. 防止直接实例化
  • 枚举类型默认有一个私有构造器,这意味着不能直接实例化枚举类型,只能通过枚举的静态方法来获取实例。
  • 例如,对于public enum MyEnum { INSTANCE },你不能写代码MyEnum myEnum = new MyEnum();,因为这会编译错误,因为MyEnum的构造器是私有的。

通过这些机制,枚举类有效地阻止了通过反射、序列化和反序列化机制来破坏单例的行为。这使得枚举类成为实现单例模式的一种非常安全和简洁的方法。


六、总结

(1)单例模式常见的写法有两种:懒汉式、饿汉式

(2)懒汉式:在需要用到对象时才实例化对象,正确的实现方式是:Double Check + Lock,解决了并发安全和性能低下问题

(3)饿汉式:在类加载时已经创建好该单例对象,在获取单例对象时直接返回对象即可,不会存在并发安全和性能问题。

(4)在开发中如果对内存要求非常高,那么使用懒汉式写法,可以在特定时候才创建该对象;

(5)如果对内存要求不高使用饿汉式写法,因为简单不易出错,且没有任何并发安全和性能问题

(6)为了防止多线程环境下,因为指令重排序导致变量报NPE,需要在单例对象上添加volatile关键字防止指令重排序

(7)最优雅的实现方式是使用枚举,其代码精简,没有线程安全问题,且 Enum 类内部防止反射和反序列化时破坏单例

参考文章:
我给面试官讲解了单例模式后,他对我竖起了大拇指!
设计模式之单例模式(七种方法超详细)
设计模式|序列化、反序列化对单例的破坏、原因分析、解决方案及解析

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

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

相关文章

鸿蒙:自定义组件、自定义函数、自定义样式

一、自定义组件 1.新建组件文件夹&#xff0c;新建自定义组件文件 . 2.编辑自定义组件&#xff0c;并通过 Component //声明组件 export struct PageHeader { //结构体private title: stringbuild() { //uiRow() {Image($rawfile(左返回.png)).width(15%)Text(this.title)…

Docker 安装最新版本 Jenkins

目录 1、下载、启动容器、更新到最新版本 2、查看初始密码两种方式&#xff1a; 3、默认安装的部分未汉化&#xff0c;删除默认的汉化插件。重启容器&#xff0c;重新安装汉化插件 4、安装 Publish over SSH、docker-build-step 、Docker Commons 插件 5、配置服务器连接信…

前端学习 Vue 插槽如何实现组件内容分发?

目录 一、Vue.js框架介绍二、什么是Vue 插槽三、Vue 插槽的应用场景四、Vue 插槽如何实现组件内容分发 一、Vue.js框架介绍 Vue.js是一个用于构建用户界面的渐进式JavaScript框架。它设计得非常灵活&#xff0c;可以轻松地被集成到现有的项目中&#xff0c;也可以作为一个完整…

Vitis Accelerated Libraries 学习笔记--OpenCV 运行测试

目录 1. 简介 2. 实例测试 2.1 实例介绍 2.2 创建工程 2.2.1 创建工程 2.2.2 获取路径 2.2.3 设置路径 2.2.4 打开工程 2.2.5 添加文件 2.2.6 启动 GUI 2.2.7 配置 csim 参数 3 常见错误 3.1 核心共享库报错 4. 总结 1. 简介 在《Vitis Accelerated Libraries …

如何清空Comfyui的gpu缓存

由于我电脑上同时装了两个Comfyui作为我站点的绘图服务&#xff0c;一个是给正式服使用&#xff0c;一个是开发测试使用&#xff0c;在使用过程中经常会因为两个Comfyui服务跑图后没有自动释放显存导致爆显存。所以我需要让Comfyui跑完图之后可以自动释放显存。 我自己在网上找…

C语言学习记录(十一)——指针基本知识及运算

文章目录 前言1. 指针的概念2.指针变量的说明3. 指针的含义4. 指针运算①指针加减&#xff1a;②指针的关系运算符 前言 一个学习嵌入式的小白~ 有问题评论区或私信指出~ 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 1. 指针的概念 在C语言中&…

阐述以下方法 @classmethod, @staticmethod, @property?

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

【模板】项目建设方案(Word原件)

1 引言 1.1 编写目的 1.2 项目概述 1.3 名词解释 2 项目背景 3 业务分析 3.1 业务需求 3.2 业务需求分析与解决思路 3.3 数据需求分析【可选】 4 项目建设总体规划【可选】 4.1 系统定位【可选】 4.2 系统建设规划 5 建设目标 5.1 总体目标 5.2 分阶段目标【可选】 5.2.1 业务目…

Flutter循序渐进==>基金管理APP首页

目录 查看版本 组件 组件源码学习 做个基金APP首页源代码 效果 查看版本 组件 组件的本质就是个类。 import package:flutter/material.dart;void main() {runApp(const OurFirstApp(),); } OurFirstApp()实例化&#xff0c;就是给runApp用的&#xff0c;runApp就是运行实…

自适应蚁群算法优化的攀爬机器人的路径规划

大家好&#xff0c;我是带我去滑雪&#xff01; 攀爬机器人是一种能够在复杂环境中自主移动和攀爬的具有广阔应用前景的智能机器人&#xff0c;具有较强的应用潜力和广泛的研究价值。随着科技的不断发展&#xff0c;攀爬机器人在许多领域中的应用越来越广泛&#xff0c;例如建筑…

Talk|CityU 助理教授马佳葳: CVPR 2024, 基于多模态理解的混合数据专家模型

本期为TechBeat人工智能社区第604期线上Talk。 北京时间6月27日(周四)20:00&#xff0c;香港城市大学助理教授—马佳葳的Talk已经准时在TechBeat人工智能社区开播&#xff01; 他与大家分享的主题是: “基于多模态理解的混合数据专家模型”&#xff0c;他向大家介绍了混合数据专…

【合作ACM出版,稳定EI、Scopus稳定检索】第五届城市工程与管理科学国际会议(ICUEMS 2024,8月2-4)

第五届城市工程与管理科学国际会议&#xff08;ICUEMS 2024&#xff09;将于2024年8月2-4日在天津举行。 会议的目的是为从事城市工程、管理科学相关领域的专家、学者、工程师和技术研究人员提供一个平台&#xff0c;分享科研成果和前沿技术&#xff0c;了解学术发展趋势&…

航空电子制造业企业数字化转型:智能工厂建设

引言 航空电子制造业是航空工业的重要组成部分&#xff0c;涵盖了飞机的电子系统、导航设备、通信系统、自动驾驶仪等关键组件。自20世纪中期以来&#xff0c;航空电子技术经历了快速发展&#xff0c;从最初的机械和模拟设备逐步过渡到数字化、网络化和智能化系统。现代航空电子…

中国高分辨率土壤质地数据(1KM)

土壤中各粒级占土壤重量的百分比组合&#xff0c;叫做土壤质地。土壤质地是土壤的最基本物理性质之一&#xff0c;对土壤的各种性状&#xff0c;如土壤的通透性、保蓄性、耕性以及养分含量等都有很大的影响是评价土壤肥力和作物适宜性的重要依据。 中国土壤质地空间分布数据是根…

搭建ragflow的步骤

前提条件 CPU > 4 核 RAM > 16 GB Disk > 50 GB Docker > 24.0.0 & Docker Compose > v2.26.1 如果你并没有在本机安装 Docker&#xff08;Windows、Mac&#xff0c;或者 Linux&#xff09;, 可以参考文档 Install Docker Engine 自行安装。 启动服务器 …

C盘太满怎么办

C盘红了怎么办&#xff0c;最常见的问题是微信装在了C盘&#xff0c;需要通过设置来更换缓存文件位置。 此外&#xff0c;如果是工作电脑&#xff0c;钉钉、企业微信等都有可能产生和微信同样的问题&#xff0c;解决方式也相同&#xff0c;通过设置更换文件位置。 此外&…

Linux创建目录——mkdir命令,du命令,touch用法,创建tree拓扑图

1. mkdir 命令 格式 mkdir - 参数 路径 / 目录名 参数 -p &#xff1a;快速创建多级目录&#xff08;递归目录&#xff09; -v &#xff1a;显示创建目录的详细过程 例&#xff1a; [rootserver ~] # mkdir t1 [rootserver ~] # mkdir t2 t3 t4 [rootserver ~] # mk…

什么是GPIO口,GPIO口最简单的input/output

目录 一&#xff0c;什么是GPIO口 二&#xff0c;GPIO内部结构 三&#xff0c;GPIO口工作模式 一&#xff0c;什么是GPIO口 1.GPIO口是通用输入输出端口&#xff08;General-purpose input/output&#xff09;的英文缩写&#xff0c;是所有的微控制器必不可少的外设之一&…

每日一题系列-把字符串转换成整数

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 题目 题目分析 对于这道题目而言&#xff0c;我们需要做到的是将字符串转换成整数。 这里我们需要注意几个点 首先我们需要保证下标在这个范围之内&#xff0c;所以我们会在每…

【Python】已解决:(SqlServer报错)SQL错误(208):对象名‘string_split’无效

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决&#xff1a;&#xff08;SqlServer报错&#xff09;SQL错误&#xff08;208&#xff09;&#xff1a;对象名‘string_split’无效 一、分析问题背景 在使用Python连接SqlSe…