第二章创建模式—单例设计模式
文章目录
- 单例模式的结构
- 如何控制只有一个对象呢
- 怎么设计这个类的内部对象
- 外部怎么访问
- 单例模式的主要有以下角色
- 单例模式的实现
- 饿汉式 1:静态变量
- 饿汉式 2:静态代码块
- 懒汉式 1:线程不安全
- 懒汉式 2:线程安全—方法级上锁
- 懒汉式 3:双重检查锁🚀
- 懒汉式 4:静态内部类🚀
- 饿汉式 3:枚举🚀
- 存在的问题
- 序列化破坏单例
- 反射破坏单例
- JDK 源码 - Runtime 类
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
- 所谓的单例模式保证某个类在程序中有且只有一个对象,类比现实生活中的地球类,只有一个地球对象
单例模式的结构
如何控制只有一个对象呢
- 我们创建一个对象是通过该类的构造方法进行创建对象
- 我们不能让外部进行创建对象,不然谁都能创建对象,那么就保证不了单例,那么我们的构造方法必须是private修饰,来控制对象的创建,也就是私有的构造方法
- 外部不能创建对象,那么这个创建唯一的对象的任务也就会在我们单例类来进行实现
怎么设计这个类的内部对象
- 首先我们知道在外部是创建不了对象的,所以这个对象的类型肯定不能是成员的,必须是静态的,通过类调用
- 因为成员变量的属性是通过我们的对象进行赋值,我们只有一个对象,如果是成员变量,就先要有对象才能赋值,而赋值了才有那个唯一的对象,就出现了死循环
外部怎么访问
- 一般属性我们用private进行修饰,为了实现其封装性
- 直接通过公开的方法获取这个唯一的对象
单例模式的主要有以下角色
- 单例类。只能创建一个实例的类
- 访问类。使用单例类
单例模式的实现
单例设计模式分类两种:
- 饿汉式:类加载就会导致该单实例对象被创建
- 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
饿汉式 1:静态变量
public class Singleton {//1定义静态成员变量,在类加载时创建private static Singleton singleton=new Singleton();//2私有化构造方法private Singleton(){}// 3.提供一个公共的访问方式,让外界获取该对象public static Singleton getSingleton() {return singleton;}
}
- 因为singleton是类成员变量,在我 Singleton类加载过程中的初始化过程中进行创建对象赋值,因为只会执行一次,所以是天然的线程安全
- 缺点:singleton 对象是随着类的加载而创建的,如果该对象很大,却一直没有使用就会造成内存的浪费
饿汉式 2:静态代码块
该方式在成员位置声明 Singleton 类型的静态变量,而对象的创建是在静态代码块中,也是随着类的加载而创建。
public class Singleton {//1定义静态成员变量,在类加载时创建private static Singleton singleton;static {singleton=new Singleton();}//2私有化构造方法private Singleton(){}// 3.提供一个公共的访问方式,让外界获取该对象public static Singleton getSingleton() {return singleton;}
}
该方式和饿汉式的方式 1 基本一样,所以该方式也存在内存浪费问题。
懒汉式 1:线程不安全
该方式在成员位置声明 Singleton 类型的静态变量,并没有进行对象的赋值操作,那么什么时候赋值的呢?
当调用 getInstance()
方法获取 Singleton 类的对象的时候才创建 Singleton 类的对象,这样就实现了懒加载效果。
public class Singleton {//1定义静态成员变量,在类加载时创建private static Singleton singleton;//2私有化构造方法private Singleton(){}// 3.提供一个公共的访问方式,让外界获取该对象public static Singleton getSingleton(){if(singleton==null){singleton=new Singleton();}return singleton;}
}
测试
public class Client {public static void main(String[] args) {Thread thread1=new Thread(()->{System.out.println(Singleton.getSingleton());});thread1.start();System.out.println(Singleton.getSingleton());}
}
//com.lsc.itheima.pattern.singleton.demo3.Singleton@3d075dc0
//com.lsc.itheima.pattern.singleton.demo3.Singleton@6b047ec
- 发现不是同一个对象,没有实现其单例的效果
- 为什么不是线程安全的
- 比如我们的thread1线程调用了getSingleton,进行if(singleton==null)判断,进入了判断体
- 然后我们的main线程抢占了CPU,进行运行,进行if(singleton==null)判断,因为thread1线程并没有进行创建对象,也进入了判断体
- 故两个线程各自创建了对应的对象
懒汉式 2:线程安全—方法级上锁
在懒汉式 1 的基础上,使用 synchronized
关键字对getSingleton这个方法进行加锁
public class Singleton {//1定义静态成员变量,在类加载时创建private static Singleton singleton;//2私有化构造方法private Singleton(){}// 3.提供一个公共的访问方式,让外界获取该对象public static synchronized Singleton getSingleton(){if(singleton==null){singleton=new Singleton();}return singleton;}
}
- 但是该方法的执行效率特别低。
- 懒汉模式中加锁的问题,对于 getSingleton 方法来说,绝大部分的操作都是读操作,读操作是线程安全的,所以我们没必让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时机。由此也产生了一种新的实现模式:双重检查锁模式
- 注意:其实只有在初始化singleton的时候才会出现线程安全问题,一旦初始化完成就不存在了。
懒汉式 3:双重检查锁🚀
public class Singleton {//1定义静态成员变量,在类加载时创建private static Singleton singleton;//2私有化构造方法private Singleton(){}// 3.提供一个公共的访问方式,让外界获取该对象public static Singleton getSingleton(){//第一次判断,如果singleton不为null,不进入抢锁阶段,直接返回实例if(singleton==null){synchronized (Singleton.class) {// 第二次判断,抢占到锁以后再次判断if (singleton == null) {singleton = new Singleton();}}}return singleton;}
}
双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测锁模式看上去完美无缺,其实是存在问题,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。
- 要解决双重检查锁模式带来空指针异常的问题,只需要使用 volatile 关键字, volatile 关键字可以保证可见性和有序性。
- 使用volatile关键字保证单例对象初始化不会被中断,保证其他线程获得的对象一定是初始化完成的对象
- 比如现在t1线程执行到了初始化的实例对象,正在执行new操作,还没完全结束(但是LazySingleTon!=null),然后t2线程执行到了判断唯一的对象是否为空,对于t2发现不为空,就直接返回了,但是返回的对象是没有完全初始化的,所以需要加volatile,就相当加了一层内存屏障,保证其他线程返回的对象必须等操作完全结束才能执行return语句
public class Singleton {//1定义静态成员变量,在类加载时创建// 声明Singleton类型的变量,使用volatile保证可见性和有序性private static volatile Singleton singleton;//2私有化构造方法private Singleton(){}// 3.提供一个公共的访问方式,让外界获取该对象public static Singleton getSingleton(){// 第一次判断,如果instance不为null,不需要抢占锁,直接返回对象if(singleton==null){synchronized (Singleton.class) {// 第二次判断,抢占到锁以后再次判断if (singleton == null) {singleton = new Singleton();}}}return singleton;}
}
懒汉式 4:静态内部类🚀
静态内部类单例模式中实例由内部类创建,由于 JVM 在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性/方法被调用时才会被加载,并初始化其静态属性。静态属性由于被 static 修饰,保证只被实例化一次,并且严格保证实例化顺序。
public class Singleton {//2私有化构造方法private Singleton(){}// 定义一个静态内部类private static class SingletonHolder {// 在内部类中声明并初始化外部类的对象private static final Singleton singleton = new Singleton();}// 提供公共的访问方式public static Singleton getInstance() {return SingletonHolder.INSTANCE;}
}
第一次加载 Singleton 类时不会去初始化 singleton,只有第一次调用 getInstance(),虚拟机加载 SingletonHolder 类并初始化 singleton,这样不仅能确保线程安全,也能保证 Singleton 类的唯一性。
静态内部类单例模式是一种优秀的单例模式,是开源项目中比较常用的一种单例模式。在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。
饿汉式 3:枚举🚀
首先,枚举方式是饿汉式单例模式,如果不考虑浪费内存空间的问题,这是极力推荐的单例实现模式。
因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式。
枚举的写法非常简单,而且枚举方式是所用单例实现中唯一一种不会被破坏的单例实现模式。
public class SingleTon {/*** 饿汉式:枚举实现*/public enum Singleton {INSTANCE}
}
存在的问题
有两种方式可以使上面定义的单例类可以创建多个对象(枚举方式除外),分别是序列化和反射。
枚举方式是利用了 Java 特性实现的单例模式,不会被破坏,其他实现方式都有可能会被破坏
序列化破坏单例
问题演示:下面代码运行结果是false
,表明序列化和反序列化破坏了单例设计模式。
public class Client {public static void main(String[] args) throws IOException, ClassNotFoundException {writeObject2File();Singleton s1 = readObjectFormFile();Singleton s2 = readObjectFormFile();
// System.out.println(s1 == s2); // false}//从文件读取数据public static Singleton readObjectFormFile() throws IOException, ClassNotFoundException {//创建对象输入流对象ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\a.txt"));//2.读取对象Singleton singleton =(Singleton) objectInputStream.readObject();//3释放资源objectInputStream.close();return singleton;}//向文件写数据public static void writeObject2File() throws IOException {//1获取Singleton对象Singleton singleton = Singleton.getSingleton();//创建对象输出流对象ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\a.txt"));// 3.写对象objectOutputStream.writeObject(singleton);// 4.释放资源objectOutputStream.close();}
}
解决方案:在 Singleton 类中添加readResolve()
方法。
在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新 new 出来的对象。
/*** 双重加锁方式(解决序列化破解单例模式)*/
public class Singleton implements Serializable {//1定义静态成员变量,在类加载时创建 声明Singleton类型的变量,使用volatile保证可见性和有序性private static volatile Singleton singleton;//2私有化构造方法private Singleton(){}// 3.提供一个公共的访问方式,让外界获取该对象public static Singleton getSingleton(){// 第一次判断,如果instance不为null,不需要抢占锁,直接返回对象if(singleton==null){synchronized (Singleton.class) {// 第二次判断,抢占到锁以后再次判断if (singleton == null) {singleton = new Singleton();}}}return singleton;}/*** 下面是为了解决序列化反序列化破解单例模式* 当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回*/private Object readResolve() {return getSingleton();}}
反射破坏单例
public class Client {public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {//1 获取Singleton的字节码对象Class<Singleton> singletonClass = Singleton.class;//2获取无参构造方法对象Constructor<Singleton> declaredConstructor = singletonClass.getDeclaredConstructor();//3取消访问检查declaredConstructor.setAccessible(true);// 4.创建Singleton对象Singleton s1 = (Singleton) declaredConstructor.newInstance();Singleton s2 = (Singleton) declaredConstructor.newInstance();System.out.println(s1 == s2); // false}
}
解决方案:当通过反射方式调用构造方法进行创建时,直接抛异常
public class Singleton {//1定义静态成员变量,在类加载时创建 声明Singleton类型的变量,使用volatile保证可见性和有序性private static volatile Singleton singleton;private static boolean flag = false;//2私有化构造方法// 私有构造方法private Singleton() {synchronized (Singleton.class) {// 如果是true,说明非第一次访问,直接抛一个异常,如果是false,说明第一次访问if (flag) {throw new RuntimeException("不能创建多个对象");}flag = true;}}// 3.提供一个公共的访问方式,让外界获取该对象public static Singleton getSingleton(){// 第一次判断,如果instance不为null,不需要抢占锁,直接返回对象if(singleton==null){synchronized (Singleton.class) {// 第二次判断,抢占到锁以后再次判断if (singleton == null) {singleton = new Singleton();}}}return singleton;}
}
JDK 源码 - Runtime 类
Runtime 类使用的就是单例设计模式。
源码查看:下面是 Runtime 类的源码,是静态变量方式的饿汉单例模式。
public class Runtime {private static Runtime currentRuntime = new Runtime();/*** Returns the runtime object associated with the current Java application.* Most of the methods of class <code>Runtime</code> are instance* methods and must be invoked with respect to the current runtime object.** @return the <code>Runtime</code> object associated with the current* Java application.*/public static Runtime getRuntime() {return currentRuntime;}/** Don't let anyone else instantiate this class */private Runtime() {}...
}
/*** RuntimeDemo*/
public class RuntimeDemo {public static void main(String[] args) throws IOException {// 获取Runtime类的对象Runtime runtime = Runtime.getRuntime();// 调用runtime的方法exec,参数要的是一个命令Process process = runtime.exec("ifconfig");// 调用process对象的获取输入流的方法InputStream is = process.getInputStream();byte[] arr = new byte[1024 * 1024 * 100];// 读取数据int len = is.read(arr); // 返回读到的字节的个数// 将字节数组转换为字符串输出到控制台System.out.println(new String(arr, 0, len, "GBK"));}
}
相关文章:
第二章创建模式—单例设计模式
文章目录 单例模式的结构如何控制只有一个对象呢怎么设计这个类的内部对象外部怎么访问 单例模式的主要有以下角色 单例模式的实现饿汉式 1:静态变量饿汉式 2:静态代码块懒汉式 1:线程不安全懒汉式 2:线程安全—方法级上锁懒汉式 …...
数据结构学习记录——堆的插入(堆的结构类型定义、最大堆的创建、堆的插入:堆的插入的三种情况、哨兵元素)
目录 堆的结构类型定义 最大堆的创建 堆的插入 堆的插入的三种情况 代码实现 哨兵元素 堆的结构类型定义 #define ElementType int typedef struct HNode* Heap; /* 堆的类型定义 */ struct HNode {ElementType* Data; /* 存储元素的数组 */int Size; /* 堆中…...
netperf测试
netperf测试 目录 批量网络流量性能测试 TCP_STREAM测试UDP_STREAM 测试请求/应答网络流量测试 TCP_RR TCP_CRR Netperf 是一个网络性能测试工具,它可以测试网络协议栈的性能,例如TCP和UDP协议。Netperf可以测量网络吞吐量、延迟和CPU利用率等指标。…...
ORACLE常用语句
1.修改用户密码 alter user 用户名 identified by 新密码; 2.表空间扩容 1.增加数据文件 alter tablespace AA add datafile ‘DATA’ size 20G autoextend off; 2.修改数据文件大小 ALTER DATABASE DATAFILE ‘E:\ORACLE\PRODUCT\10.2.0\ORADATA\aa\aa.DBF’ RESIZE 400M;…...

[论文笔记]C^3F,MCNN:图片人群计数模型
(万能代码)CommissarMa/Crowd_counting_from_scratch 代码:https://github.com/CommissarMa/Crowd_counting_from_scratch (万能代码)C^3 Framework开源人群计数框架 科普中文博文:https://zhuanlan.zhihu.com/p/65650998 框架网址:https…...

HCIP-7.2VLAN间通信单臂、多臂、三层交换方式学习
VLAN间通信单臂、多臂、三层交换方式学习 1、单臂路由2、多臂路由3、三层交换机的SVI接口实现VLAN间通讯3.1、VLANIF虚拟接口3.2、VLAN间路由3.2.1、单台三层路由VLAN间通信,在一台三层交换机内部VLAN之间直连。3.2.2、两台三层交换机的之间的VLAN通信。3.2.3、将物…...
PHP快速入门17-用spl_autoload_register实现类的自动加载
文章目录 前言实现过程创建两个类创建入口文件 总结 前言 本文已收录于PHP全栈系列专栏:PHP快速入门与实战 PHP类自动载入是指在PHP应用程序中,当需要使用某个类文件时,系统会自动加载该类文件,无需手动引入。 在PHP中…...

【黑马程序员 C++教程从0到1入门编程】【笔记8】 泛型编程——模板
https://www.bilibili.com/video/BV1et411b73Z?p167 C泛型编程是一种编程范式,它的核心思想是编写通用的代码,使得代码可以适用于多种不同的数据类型。 而模板是C中实现泛型编程的一种机制,它允许我们编写通用的代码模板,然后在需…...

分享10个精美可视化模板,解决95%的大屏需求!
前段时间和朋友一起喝茶,我吐槽着excel表格做报表的繁琐,他惊讶的问我竟然不知道大屏模板这种东西,说是直接套用数据就可以,我震惊的同时吃下了这个安利。 回来之后,我好好研究了一番这个叫可视化大屏的“新鲜玩意儿”…...

好用的项目管理软件的具体功能有哪些
随着企业规模不断的扩大,项目管理往往会面临更多的挑战与难题,最常见的会出现以下几个问题:资源消耗失控,而项目部门和相关部门之间沟通越来越困难;团队凝聚力下降、项目进度难以把控,项目成本几乎失控&…...

< 每日小技巧: 基于Vue状态的过渡动画 - Transition 和 TransitionGroup>
》基于Vue状态的过渡动画 - Transition 和 TransitionGroup 👉 一、Vue Transition 简介> Transition 和 TransitionGroup 之间的区别 👉 二、<Transition> 组件> 触发 <Transition> 组件的场景:> 基于 CSS 的过渡效果&…...

vmware安装redhat 8
vmware安装redhat 8 1、下载镜像文件1.1 镜像文件 2、安装系统2.1、选择自定义安装2.2、兼容性选择2.3、选择镜像文件导入2.4、设置用户名密码2.5、选择虚拟机在磁盘上的位置2.6、选择处理器数量2.7、选择内存大小2.8、选择桥接或NAT2.9、选择SCSI控制器类型2.10、选择虚拟机磁…...

OpenCV C++案例实战三十一《动态时钟》
OpenCV C案例实战三十一《动态时钟》 前言一、绘制表盘二、绘制刻线三、获取系统时间四、结果展示五、源码总结 前言 本案例将使用OpenCV C实现动态时钟效果。原理也很简单,主要分为绘制表盘、以及获取系统时间两步。 一、绘制表盘 首先为了效果显示美观一点&…...

字节后端入门 - Go 语言原理与实践
1.1什么是Go语言 1.2Go语言入门 环境 1.3基础语法 1.3.1变量 var name"value" 自己推断变量类型; 也可以显式类型 var c int 1 name: type(value) 常量: const name "value" g : a"foo" 字符串拼接 1.3.2 if else {}花括号…...

锂电材料浆料匀浆搅拌设备轴承经常故障如何处理?
锂电材料浆料匀浆搅拌设备是锂电池生产中重要的设备之一,用于将活性材料、导电剂、粘结剂和溶剂混合成均匀的浆料,是电极制备过程中不可或缺的步骤。然而,由于高速搅拌和化学腐蚀等因素的影响,轴承经常会出现故障,导致…...

设计模式——设计模式介绍和单例设计模式
导航: 【黑马Java笔记踩坑汇总】JavaSEJavaWebSSMSpringBoot瑞吉外卖SpringCloud黑马旅游谷粒商城学成在线设计模式牛客面试题 目录 一、设计模式概述和分类 1.1 设计模式介绍 1.2 设计模式分类 二、创建型设计模式-单例模式 2.1 介绍 2.2 八种单例模式的创…...

利用Iptables构建虚拟路由器
利用Iptables构建虚拟路由器 (1)修改网络类型 在VMware Workstation软件中选择“编辑→虚拟网络编辑器”菜单命令,在虚拟网络列表中选中VMnet1,将其配置为“仅主机模式(在专用网络内连接虚拟机)”&#x…...

C++——类和对象[中]
0.关注博主有更多知识 C知识合集 目录 1.类的默认成员函数 2.构造函数和析构函数基础 3.构造函数进阶 4.析构函数进阶 5.拷贝构造函数 6.运算符重载 7.日期类 7.1输入&输出&友元函数 8.赋值运算符重载 9.const成员函数 9.1日期类完整代码 10.取地址重载 …...
Symbol.iterator和Symbol.asyncIterator
Symbol是什么? symbol是ES6标准中新增的一种基本数据类型,symbol 的值是通过 Symbol()函数返回的,每一个 symbol 的值都是唯一的,即使传入相同的描述值。 注:Symbol 函数不允许通过 new 的方式调用 Symbol的作用是什…...

忆暖行动|“他一个人推着老式自行车在厚雪堆的道路上走,车上带着学生考试要用的司机”
忆暖行动|“他一个人推着老式自行车在厚雪堆的道路上走,车上带着学生考试要用的sj” 一头白发,满山青葱 在那斑驳的物件褶皱中,透过泛黄的相片,掩藏着岁月的冲刷和青葱的时光。曾经的青年早已经不复年轻,但是那份热爱…...

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?
编辑:陈萍萍的公主一点人工一点智能 未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战,在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…...

深度学习在微纳光子学中的应用
深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向: 逆向设计 通过神经网络快速预测微纳结构的光学响应,替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…...

边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...

【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...

对WWDC 2025 Keynote 内容的预测
借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...
C# SqlSugar:依赖注入与仓储模式实践
C# SqlSugar:依赖注入与仓储模式实践 在 C# 的应用开发中,数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护,许多开发者会选择成熟的 ORM(对象关系映射)框架,SqlSugar 就是其中备受…...

Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...
iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈
在日常iOS开发过程中,性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期,开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发,但背后往往隐藏着系统资源调度不当…...

【笔记】WSL 中 Rust 安装与测试完整记录
#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统:Ubuntu 24.04 LTS (WSL2)架构:x86_64 (GNU/Linux)Rust 版本:rustc 1.87.0 (2025-05-09)Cargo 版本:cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...
WebRTC从入门到实践 - 零基础教程
WebRTC从入门到实践 - 零基础教程 目录 WebRTC简介 基础概念 工作原理 开发环境搭建 基础实践 三个实战案例 常见问题解答 1. WebRTC简介 1.1 什么是WebRTC? WebRTC(Web Real-Time Communication)是一个支持网页浏览器进行实时语音…...