基于Spring封装一个websocket工具类使用事件发布进行解耦和管理

最近工作中,需要将原先的Http请求换成WebSocket,故此需要使用到WebSocket与前端交互。故此这边需要研究一下WebSocket到底有何优点和不可替代性:

WebSocket优点:

WebSocket 协议提供了一种在客户端和服务器之间进行全双工通信的机制,这意味着客户端和服务器可以在任何时候互相发送消息,而不需要预先建立请求。与传统的 HTTP 轮询相比,WebSocket 有以下不可替代的优点:

1. 低延迟: WebSocket 提供了真正的实时通信能力,因为它允许服务器在数据可用时立即将其推送到客户端。这比 HTTP 轮询的“询问-回答”模式更高效,轮询模式可能会引入不必要的延迟。

2. 减少网络流量: 在 HTTP 轮询中,客户端需要定期发送请求以检查更新,即使没有更新也是如此。这会产生大量冗余的 HTTP 头部信息和请求响应。相比之下,WebSocket 在建立连接后,只需要非常少的控制开销就可以发送和接收消息。

3. 持久连接: WebSocket 使用单个持久连接进行通信,而不需要为每个消息或请求重新建立连接。这减少了频繁建立和关闭连接的开销,提高了效率。

4. 双向通信: WebSocket 支持全双工通信,客户端和服务器可以同时发送消息,而不需要等待对方的响应。这对于需要快速双向数据交换的应用程序来说是非常重要的。

5. 更好的服务器资源利用: 由于 WebSocket 连接是持久的,服务器可以更有效地管理资源,而不是在每个轮询请求中重新初始化资源。

6. 协议开销小: WebSocket 消息包含非常少的协议开销,相比之下,HTTP 协议的每个请求/响应都包含了完整的头部信息。

7. 支持二进制数据: WebSocket 不仅支持文本数据,还支持二进制数据,这使得它可以用于更广泛的应用场景,如游戏、视频流和其他需要高效二进制数据传输的应用。

8. 兼容性: 尽管是较新的技术,WebSocket 已经得到了现代浏览器的广泛支持,并且可以通过 Polyfills 在不支持的浏览器上使用。

时序图:

 

这个流程图展示了以下步骤:

  1. 握手阶段:客户端向服务器发送 WebSocket 连接请求,服务器响应并切换协议。
  2. 连接建立:WebSocket 连接建立后,客户端和服务器可以相互发送消息。
  3. 通信循环:客户端和服务器在建立的 WebSocket 连接上进行消息交换。
  4. 关闭握手:客户端或服务器发起关闭连接的请求,另一方响应,然后连接关闭。

因为以上优点这边将需要重新构建一套WebSocket工具类实现这边的要求:

工具类实现:

在 Spring 中封装 WebSocket 工具类通常涉及使用 Spring 提供的 WebSocket API。

WebSocketUtils

WebSocket 工具类封装示例,它使用 Spring 的 WebSocketSession 来发送消息给客户端。

  1. 异常处理: 在发送消息时,如果发生异常,我们可以添加更详细的异常处理逻辑。
  2. 会话管理: 我们可以添加同步块或使用 ConcurrentHashMap 的原子操作来确保线程安全。
  3. 用户标识符管理: 提供一个更灵活的方式来管理用户标识符和会话之间的关系。
  4. 事件发布: 使用 Spring 事件发布机制来解耦和管理 WebSocket 事件。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/*** @Author derek_smart* @Date 202/5/11 10:05* @Description WebSocket 工具类*/
@Component
public class WebSocketUtils extends TextWebSocketHandler {private final Map<String, WebSocketSession> sessions = new ConcurrentHashMap<>();@Autowiredprivate ApplicationEventPublisher eventPublisher;public void registerSession(String userIdentifier, WebSocketSession session) {sessions.put(userIdentifier, session);// Publish an event when a session is registeredeventPublisher.publishEvent(new WebSocketSessionRegisteredEvent(this, session, userIdentifier));}public void removeSession(String userIdentifier) {WebSocketSession session = sessions.remove(userIdentifier);if (session != null) {// Publish an event when a session is removedeventPublisher.publishEvent(new WebSocketSessionRemovedEvent(this, session, userIdentifier));}}public void sendMessageToUser(String userIdentifier, String message) {WebSocketSession session = sessions.get(userIdentifier);if (session != null && session.isOpen()) {try {session.sendMessage(new TextMessage(message));} catch (IOException e) {// Handle the exception, e.g., logging or removing the sessionhandleWebSocketException(session, e);}}}public void sendMessageToAllUsers(String message) {TextMessage textMessage = new TextMessage(message);sessions.forEach((userIdentifier, session) -> {if (session.isOpen()) {try {session.sendMessage(textMessage);} catch (IOException e) {// Handle the exception, e.g., logging or removing the sessionhandleWebSocketException(session, e);}}});}private void handleWebSocketException(WebSocketSession session, IOException e) {// Log the exception// Attempt to close the session if it's still openif (session.isOpen()) {try {session.close();} catch (IOException ex) {// Log the exception during close}}// Remove the session from the mapsessions.values().remove(session);// Further exception handling...}@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {// This method can be overridden to handle connection established event}@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {// This method can be overridden to handle connection closed event}// Additional methods like handleTextMessage can be overridden if needed// Custom eventspublic static class WebSocketSessionRegisteredEvent extends ApplicationEvent {private final WebSocketSession session;private final String userIdentifier;/*** Create a new WebSocketSessionRegisteredEvent.* @param source the object on which the event initially occurred (never {@code null})* @param session the WebSocket session which has been registered* @param userIdentifier the identifier of the user for whom the session is registered*/public WebSocketSessionRegisteredEvent(Object source, WebSocketSession session, String userIdentifier) {super(source);this.session = session;this.userIdentifier = userIdentifier;}public WebSocketSession getSession() {return session;}public String getUserIdentifier() {return userIdentifier;}}public static class WebSocketSessionRemovedEvent extends ApplicationEvent {private final WebSocketSession session;private final String userIdentifier;/*** Create a new WebSocketSessionRemovedEvent.* @param source the object on which the event initially occurred (never {@code null})*      * @param session the WebSocket session which has been removed*      * @param userIdentifier the identifier of the user for whom the session was removed*      */public WebSocketSessionRemovedEvent(Object source, WebSocketSession session, String userIdentifier) {super(source);this.session = session;this.userIdentifier = userIdentifier;}public WebSocketSession getSession() {return session;}public String getUserIdentifier() {return userIdentifier;}}
}

 

 

在这个工具类中,我们使用了 ConcurrentHashMap 来存储和管理 WebSocket 会话。每个会话都与一个用户标识符相关联,这允许我们向特定用户发送消息。 使用了 ApplicationEventPublisher 来发布会话注册和移除事件,这样可以让其他组件在需要时响应这些事件。

另外,我们让 WebSocketUtils 继承了 TextWebSocketHandler,这样它可以直接作为一个 WebSocket 处理器。这意味着你可以重写
afterConnectionEstablished 和 afterConnectionClosed 方法来处理连接建立和关闭的事件,而不是在一个单独的 WebSocketHandler 中处理它们。

通过这些优化,WebSocketUtils 工具类变得更加健壮和灵活,能够更好地集成到 Spring 应用程序中

工具类提供了以下方法:

  • registerSession: 当新的 WebSocket 连接打开时,将该会话添加到映射中。
  • removeSession: 当 WebSocket 连接关闭时,从映射中移除该会话。
  • sendMessageToUser: 向特定用户发送文本消息。
  • sendMessageToAllUsers: 向所有连接的用户发送文本消息。
  • getSessions: 返回当前所有的 WebSocket 会话。

WebSocketHandler

为了完整地实现一个 WebSocket 工具类,你还需要创建一个 WebSocketHandler 来处理 WebSocket 事件,如下所示:

import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;
/*** @Author derek_smart* @Date 202/5/11 10:05* @Description WebSocketHandler*/
public class MyWebSocketHandler implements WebSocketHandler {private final WebSocketUtils webSocketUtils;public MyWebSocketHandler(WebSocketUtils webSocketUtils) {this.webSocketUtils = webSocketUtils;}@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {String userIdentifier = retrieveUserIdentifier(session);webSocketUtils.registerSession(userIdentifier, session);}@Overridepublic void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {// Handle incoming messages}@Overridepublic void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {// Handle transport error}@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {String userIdentifier = retrieveUserIdentifier(session);webSocketUtils.removeSession(userIdentifier);}@Overridepublic boolean supportsPartialMessages() {return false;}private String retrieveUserIdentifier(WebSocketSession session) {// Implement logic to retrieve the user identifier from the sessionreturn session.getId(); // For example, use the WebSocket session ID}
}

在 MyWebSocketHandler 中,我们处理了 WebSocket 连接的建立和关闭事件,并且在这些事件发生时调用 WebSocketUtils 的方法注册或移除会话。

WebSocketConfig

要在 Spring 中配置 WebSocket,你需要在配置类中添加 WebSocketHandler 和 WebSocket 的映射。以下是一个简单的配置示例:

import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {private final MyWebSocketHandler myWebSocketHandler;public WebSocketConfig(WebSocketUtils webSocketUtils) {this.myWebSocketHandler = new MyWebSocketHandler(webSocketUtils);}@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {registry.addHandler(myWebSocketHandler, "/ws").setAllowedOrigins("*");}
}

WebSocketEventListener

我们扩展了 ApplicationEvent 类,这是所有 Spring 事件的基类。我们添加了两个字段:session 和 userIdentifier,以及相应的构造函数和访问器方法。这样,当事件被发布时,监听器可以访问到事件的详细信息。

要发布这个事件,你需要注入 ApplicationEventPublisher 到你的 WebSocketUtils 类中,并在会话注册时调用 publishEvent 方法: 要发布这个事件,你需要在会话移除时调用 ApplicationEventPublisher 的 publishEvent 方法

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketSession;@Component
public class WebSocketEventListener {@EventListenerpublic void handleWebSocketSessionRegistered(WebSocketUtils.WebSocketSessionRegisteredEvent event) {// 处理会话注册事件WebSocketSession session = event.getSession();String userIdentifier = event.getUserIdentifier();// 你可以在这里添加你的逻辑,例如发送欢迎消息或记录会话信息}@EventListenerpublic void handleWebSocketSessionRemoved(WebSocketUtils.WebSocketSessionRemovedEvent event) {// 处理会话移除事件WebSocketSession session = event.getSession();String userIdentifier = event.getUserIdentifier();// 你可以在这里添加你的逻辑,例如更新用户状态或释放资源}}

类图:

 

在这个类图中,我们展示了以下类及其关系:

  • WebSocketUtils: 包含了会话管理和消息发送的方法。
  • WebSocketSessionRegisteredEvent: 一个自定义事件类,用于表示 WebSocket 会话注册事件。
  • WebSocketSessionRemovedEvent: 一个自定义事件类,用于表示 WebSocket 会话移除事件。
  • WebSocketEventListener: 一个监听器类,它监听并处理 WebSocket 会话相关的事件。
  • WebSocketController: 一个控制器类,用于处理 HTTP 请求并使用 WebSocketUtils 类的方法。
  • ChatWebSocketHandler: 一个 WebSocket 处理器类,用于处理 WebSocket 事件。
  • WebSocketConfig: 配置类,用于注册 WebSocket 处理器。

测试类实现:

WebSocketController

构建一个简单的聊天应用程序,我们将使用 WebSocketUtils 来管理 WebSocket 会话并向用户发送消息。 在这个示例中,我们将创建一个控制器来处理发送消息的请求,并使用 WebSocketUtils 类中的方法来实际发送消息。 首先,确保 WebSocketUtils 类已经被定义并包含了之前讨论的方法。 接下来,我们将创建一个 WebSocketController 来处理发送消息的请求:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
/*** @Author derek_smart* @Date 202/5/11 10:09* @Description WebSocket 测试类*/
@RestController
@RequestMapping("/chat")
public class WebSocketController {private final WebSocketUtils webSocketUtils;@Autowiredpublic WebSocketController(WebSocketUtils webSocketUtils) {  this.webSocketUtils = webSocketUtils;}// 发送消息给特定用户@PostMapping("/send-to-user")public ResponseEntity<?> sendMessageToUser(@RequestParam String userIdentifier, @RequestParam String message) {try {webSocketUtils.sendMessageToUser(userIdentifier, message);return ResponseEntity.ok().build();} catch (IOException e) {// 日志记录异常,返回错误响应return ResponseEntity.status(500).body("Failed to send message to user.");}}// 发送广播消息给所有用户@PostMapping("/broadcast")public ResponseEntity<?> broadcastMessage(@RequestParam String message) {webSocketUtils.sendMessageToAllUsers(message);return ResponseEntity.ok().build();}
}

在 WebSocketController 中,我们提供了两个端点:一个用于向特定用户发送消息,另一个用于广播消息给所有连接的用户。

 

ChatWebSocketHandler

现在,让我们创建一个 WebSocketHandler 来处理 WebSocket 事件:

import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;public class ChatWebSocketHandler extends TextWebSocketHandler {private final WebSocketUtils webSocketUtils;public ChatWebSocketHandler(WebSocketUtils webSocketUtils) {this.webSocketUtils = webSocketUtils;}@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {// 使用用户的唯一标识符注册会话String userIdentifier = retrieveUserIdentifier(session);webSocketUtils.registerSession(userIdentifier, session);}@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {// 处理接收到的消息,例如在聊天室中广播String userIdentifier = retrieveUserIdentifier(session);webSocketUtils.sendMessageToAllUsers("User " + userIdentifier + " says: " + message.getPayload());}@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {// 移除会话String userIdentifier = retrieveUserIdentifier(session);webSocketUtils.removeSession(userIdentifier);}private String retrieveUserIdentifier(WebSocketSession session) {// 根据实际情况提取用户标识符,这里假设使用 WebSocketSession 的 IDreturn session.getId();}
}

在 ChatWebSocketHandler 中,我们处理了连接建立和关闭事件,并在这些事件发生时调用 WebSocketUtils 的方法注册或移除会话。我们还实现了 handleTextMessage 方法来处理接收到的文本消息。

WebSocketConfig

最后,我们需要配置 WebSocket 端点:

import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {private final ChatWebSocketHandler chatWebSocketHandler;public WebSocketConfig(WebSocketUtils webSocketUtils) {this.chatWebSocketHandler = new ChatWebSocketHandler(webSocketUtils);}@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {registry.addHandler(chatWebSocketHandler, "/ws/chat").setAllowedOrigins("*");}
}

在 WebSocketConfig 配置类中,我们注册了 ChatWebSocketHandler 到 /ws/chat 路径。这样,客户端就可以通过这个路径来建立 WebSocket 连接。

这个示例展示了如何使用 WebSocketUtils 工具类来管理 WebSocket 会话,并通过 REST 控制器端点发送消息。客户端可以连接到 WebSocket 端点,并使用提供的 REST 端点发送和接收消息。

总结:

总的来说,WebSocket 在需要快速、实时、双向通信的应用中提供了显著的优势,例如在线游戏、聊天应用、实时数据监控和协作工具。然而,不是所有的场景都需要 WebSocket 的能力,对于不需要实时通信的应用,传统的 HTTP 请求或 HTTP 轮询可能仍然是合适的。

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

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

相关文章

异地组网群晖不能访问怎么办?

在日常使用群晖网络储存设备时&#xff0c;我们常常会遇到无法访问的情况&#xff0c;特别是在异地组网时。这个问题很常见&#xff0c;但也很让人困扰。本文将针对异地组网群晖无法访问的问题进行详细解答和分析。 异地组网的问题 在异地组网中&#xff0c;群晖设备无法访问的…

Unity | Spine动画动态加载

一、准备工作 Spine插件及基本知识可查看这篇文章&#xff1a;Unity | Spine动画记录-CSDN博客 二、Spine资源动态加载 1.官方说明 官方文档指出不建议这种操作。但spine-unity API允许在运行时从SkeletonDataAsset或甚至直接从三个导出的资产实例化SkeletonAnimation和Skel…

HNU-算法设计与分析-作业3

第三次作业【动态规划】 文章目录 第三次作业【动态规划】<1>算法实现题 3-1 独立任务最优解问题<2>算法实现题 3-4 数字三角形问题<3>算法实现题 3-8 最小m段和问题<4>算法实现题 3-25 m处理器问题 <1>算法实现题 3-1 独立任务最优解问题 ▲问…

Linux(七) 动静态库

目录 一、动静态库的概念 二、静态库的打包与使用 2.1 静态库的打包 2.2 静态库的使用 三、动态库的打包与使用 3.1 动态库的打包 3.2 动态库的使用 3.3 运行动态库的四种方法 四、总makefile 一、动静态库的概念 静态库&#xff1a; Linux下&#xff0c;以.a为后缀的…

Python专题:十五、JSON数据格式

Python的数据处理&#xff1a;JOSN 计算机的主要工作&#xff1a;处理数据 最容易处理的数据就是结构化数据 非结构化数据&#xff1a;视频&#xff0c;文件等 近些年的大数据、数据挖掘就是对互联网中的各种非结构化的数据的分析和处理 半结构化数据 明确的结构属性&…

陪诊服务运用预约小程序的效果是什么

在中高型城市里&#xff0c;陪诊师近些年也很有热度&#xff0c;已经衍生成为一个新的小众行业&#xff0c;不同医院/不同科目等其它情况针对不同群体往往很难完善&#xff0c;比如部分老年人腿脚不便、不认识字、外地语言难以沟通等&#xff0c;陪诊师的作用就尤为凸显. 对相…

[Bootloader][uboot]code总结

文章目录 1、U_BOOT_DRIVER2、DM框架dm_scan_platdatadm_extended_scan_fdt 1、U_BOOT_DRIVER 使用这个宏可以定义一个驱动实例&#xff0c;宏定义是 其中使用的struct driver结构体 使用的ll_entry_declare宏定义是 归结为 2、DM框架 1、 DM框架 DM模型抽象出了以下四个…

16.投影矩阵,最小二乘

文章目录 1. 投影矩阵1.1 投影矩阵P1.2 投影向量 1. 投影矩阵 1.1 投影矩阵P 根据上节知识&#xff0c;我们知道当我们在解 A X b AXb AXb的时候&#xff0c;发现当向量b不在矩阵A的列空间的时候&#xff0c;我们希望的是通过投影&#xff0c;将向量b投影到矩阵A的列空间中&…

ModuleNotFoundError: No module named ‘sklearn‘

ModuleNotFoundError: No module named sklearn 解决办法&#xff1a; pip install scikit-learn

7B2 PRO主题5.4.2免授权直接安装

B2 PRO 5.4.2 最新免授权版不再需要改hosts&#xff0c;直接在wordpress上传安装即可

Vue的学习 —— <网络请求库Axios>

目录 前言 正文 一、Axios基本概念 二、安装Axios 三、Axios使用方法 四、向服务器发送请求 前言 在之前的开发案例中&#xff0c;我们通常直接在组件中定义数据。但在实际的项目开发中&#xff0c;我们需要从服务器获取数据。当其他用户希望访问我们自己编写的网页时&a…

定档 11.2-3,COSCon'24 第九届中国开源年会暨开源社十周年嘉年华正式启动!

中国开源年会 COSCon 是业界最具影响力的开源盛会之一&#xff0c;由开源社在2015年首次发起&#xff0c;今年将举办第九届。 以其独特定位及日益增加的影响力&#xff0c;COSCon 吸引了越来越多的国内外企业、高校、开源组织/社区的大力支持。与一般企业、IT 媒体、行业协会举…

网络安全快速入门(十三)linux及vmware软件的网络配置

13.1 前言 在通过我们前面的了解&#xff0c;我们现在已经对Linux的基础知识有了大致的了解&#xff0c;今天我们来大概讲一下关于linux系统及vmware的网络配置问题&#xff0c;在这之前&#xff0c;我们需要对网络有一个大概的认识和了解&#xff0c;话不多说&#xff0c;我们…

HNU-算法设计与分析-作业5

第五次作业【回溯算法】 文章目录 第五次作业【回溯算法】<1> 算法分析题5-3 回溯法重写0-1背包<2> 算法分析题5-5 旅行商问题&#xff08;剪枝&#xff09;<3> 算法实现题5-2 最小长度电路板排列问题<4> 算法实现题5-7 n色方柱问题<5> 算法实现…

公共字段填充(AOP的使用)

Thread是线程池,ThreadLocal是线程变量,每个线程变量是封闭的,与其它线程变量分隔开来,在sky-common下的com.sky.context包下有一个Basecontext类 public class BaseContext {//每一个上下文创建了一个线程变量,用来存储long类型的id//创建三个方法,用来设置,取用,删除idpubli…

绝地求生:PGS3参赛队伍跳点一览,17压力有点大,4AM与PeRo大概率不roll点

在PCL春季赛结束后&#xff0c;PGS3的参赛队伍名单以及分组就正式确定了&#xff0c;最后确定名额的DDT和NH被安排在了A组和B组&#xff0c;感觉这次PGS3的分组比较均衡&#xff0c;没有“死亡之组”一说。这段时间已经有网友汇总了PGS3队伍在各个地图的跳点&#xff0c;并且把…

「AIGC算法」近邻算法原理详解

本文主要介绍近邻算法原理及实践demo。 一、原理 K近邻算法&#xff08;K-Nearest Neighbors&#xff0c;简称KNN&#xff09;是一种基于距离的分类算法&#xff0c;其核心思想是距离越近的样本点&#xff0c;其类别越有可能相似。以下是KNN算法的原理详解&#xff1a; 1. 算…

Vmvare—windows中打不开摄像头

1、检查本地摄像头是否能正常打开 设备管理器—查看—显示隐藏设备—选中照相机—启动 USB2.0 HD UVC—打开相机查看 2、检查虚拟机的设置 虚拟机—虚拟机—可移动设备—USB2.0 HD UVC—勾选在状态栏中显示 虚拟机—打开windows主机—右小角选中圆圈图标—勾选连接主机 此时…

Java | Leetcode Java题解之第91题解码方法

题目&#xff1a; 题解&#xff1a; class Solution {public int numDecodings(String s) {int n s.length();// a f[i-2], b f[i-1], cf[i]int a 0, b 1, c 0;for (int i 1; i < n; i) {c 0;if (s.charAt(i - 1) ! 0) {c b;}if (i > 1 && s.charAt(i …

小红书笔记怎么发浏览量高?

小红书笔记发布是有技巧和策略的&#xff0c;为什么有的小红书笔记浏览量那么高&#xff0c;是因为下足了功夫&#xff0c;小红书笔记怎么发浏览量高&#xff1f;今天伯乐网络传媒就来给大家揭秘一下。 一、选题与定位 1. 热门话题选择 要想提高小红书笔记的浏览量&#xff0…