【Java】基于JWT+Token实现完整登入功能(原理+实操图解)

Java系列文章目录

补充内容 Windows通过SSH连接Linux
第一章 Linux基本命令的学习与Linux历史


文章目录

  • Java系列文章目录
  • 一、前言
  • 二、学习内容:
  • 三、问题描述
  • 四、解决方案:
    • 4.1 认识相关依赖
      • 4.1.1 工具包依赖
      • 4.1.2 非空注解依赖
      • 4.1.3 Token相关依赖
      • 4.1.4 依赖文件参考
    • 4.2 使用JWT
      • 4.2.1 JwtConfig配置
      • 4.2.2 JWT的工具类
        • 4.2.2.1 代码内容
    • 4.2.3 工具类代码解析
      • 4.2.3.1 JWT结构
      • 4.2.3.2 JWT工作流程
    • 4.3 登入实现
      • 4.3.1 登入步骤
      • 4.3.2 代码实现
      • 4.3.3 测试结果
    • 4.4 配置拦截器
    • 4.5 通过Token获取数据
      • 4.5.1 拦截器内部判断以及实现业务逻辑
      • 4.5.2 测试结果
    • 4.6 JWT令牌的过期时间方法比较
      • 4.6.1 通过Redis方法
      • 4.6.2 设置JWT过期时间方法
      • 4.6.3 比较
    • 4.7 JWT与Session与Token与Cookie区别
  • 五、总结:

一、前言

  • 学习JWT+Token的传输方式
  • 流程图原作者 流程图来源 JWT的讲解也有可看这篇文章
  • 本文以实操为主,部分以图片展示代码完整可自己敲加快掌握
  • 本文于24年9月9日补充理论部分

二、学习内容:

  • Token+Redis 方法

一种结合JWT与Redis的解决方案

  • 服务器生成令牌并将器存储在Redis中,同时只有前端持有此令牌本身

流程图来源地址

实操如下流程:
在这里插入图片描述

实操项目结构与数据流程:
在这里插入图片描述


三、问题描述

  • 保证传输的安全性
  • 想了解JWT原理可看这篇文章 流程图来源

四、解决方案:

4.1 认识相关依赖

4.1.1 工具包依赖

<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.16</version>
</dependency>
  • 加密用的就是工具包的加密工具

4.1.2 非空注解依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId>
</dependency>
  • DTO使用的非空注解要引依赖

4.1.3 Token相关依赖

<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.11.2</version>
</dependency>
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.11.2</version><scope>runtime</scope>
</dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId><version>0.11.2</version><scope>runtime</scope>
</dependency>
  • 自定义JWT工具类需要的依赖

4.1.4 依赖文件参考

  • pom.xml文件
    <dependencies>
<!--        提供加密工具包--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.16</version></dependency><!--非空注解--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency><!--        token依赖--><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.11.2</version></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.11.2</version><scope>runtime</scope></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId><version>0.11.2</version><scope>runtime</scope></dependency><!--redis依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!--MyBatis Plus 代码生成器--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-generator</artifactId><version>3.5.3.2</version></dependency><dependency><groupId>org.freemarker</groupId><artifactId>freemarker</artifactId></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3.2</version></dependency><!--mysql的连接--><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>provided</scope></dependency></dependencies>

4.2 使用JWT

4.2.1 JwtConfig配置

JwtConfig通过properties文件配置JWT,properties文件自己配好数据库与Redis

  • properties文件里面加上这个
jwt.key=12345678901234567890123456789012
jwt.ttl=3600000
  • 通过前缀引入

在这里插入图片描述

4.2.2 JWT的工具类

4.2.2.1 代码内容

这个只是对称加密方法的示例

这个注释掉的在后面的方法比较中会讲.setExpiration(new Date(System.currentTimeMillis() + jwtConfig.getTtl()))

代码如下:

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;import org.example.learnjwt.config.JwtConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import java.security.Key;/*** JWT工具类,提供JWT的创建、解析和验证功能*/
@Component
public class JwtUtil {// 注入JWT配置@Autowiredprivate JwtConfig jwtConfig;// 用于签名的密钥private final Key key ;/*** 构造函数,初始化JWT配置和密钥* * @param jwtConfig JWT配置*/public JwtUtil(JwtConfig jwtConfig){this.jwtConfig=jwtConfig;key = Keys.hmacShaKeyFor(jwtConfig.getKey().getBytes());}/*** 创建JWT令牌* * @param id 要包含在JWT中的标识符* @return 生成的JWT字符串*/public String createJwt(String id) {// 创建并设置声明Claims claims = Jwts.claims();claims.put("adminId",id);// 构建并返回JWTreturn Jwts.builder().setClaims(claims)//.setExpiration(new Date(System.currentTimeMillis() + jwtConfig.getTtl())).signWith(key).compact();}/*** 解析JWT令牌,获取其中的声明* * @param token 待解析的JWT令牌* @return JWT中的声明*/public Claims parseJwt(String token) {// 使用密钥解析JWT,并返回其主体部分return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();}/*** 验证JWT令牌的有效性* * @param token 待验证的JWT令牌* @return 如果令牌有效则返回true,否则返回false*/public boolean validateToken(String token) {try {// 尝试解析JWT,如果成功则令牌有效Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);return true;} catch (Exception e) {// 如果解析异常,则令牌无效return false;}}
}
  • 补充前后端传输使用的DTO

非空注解记得引入相关依赖

@Data
public class AdminLoginDTO {@NotNull(message = "手机号不能为空")private String phone;@NotNull(message = "密码不能为空")private String password;
}

4.2.3 工具类代码解析

4.2.3.1 JWT结构

在JWT(JSON Web Token)中,令牌的结构由三部分组成:头部(Header)、有效载荷(Payload)和签名(Signature)

下面是对这三部分的详细解释,以及在你的代码中它们是如何被使用的:

  1. 标头(Header)

标头通常包含两部分信息:

  • 类型:通常是JWT。
  • 算法:用于签名的算法(如HMAC SHA256)。
  • 在你的代码中,标头没有显式地定义,因为Jwts.builder()会自动生成一个默认的标头。

可能结构如下:

{  "typ": "JWT",  "alg": "HS256"  
}
  1. 有效载荷(Payload)

有效载荷包含了JWT的主体信息,也就是你要传递的数据。它可以包含一些标准的声明(如iss、exp、sub等)和自定义声明。

在代码中,有效载荷是通过以下代码段创建的:

Claims claims = Jwts.claims();  
claims.put("adminId", id);  
  • 这里将adminId作为自定义声明放入有效载荷中。

可能结构如下:

{  "sub": "1234567890",  "name": "John Doe",  "adminId": "abc123",  "iat": 1516239022  
}
  1. 签名(Signature)

签名是通过将编码后的头部和有效载荷结合在一起,并用指定的算法和密钥进行加密生成的

签名的生成是在以下代码中完成的:

.signWith(key)  
  • 这里使用了前面定义的key来对JWT进行签名。

结构如下:

HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload), secret)

4.2.3.2 JWT工作流程

整个JWT的工作流程如下:

  1. 构建头部和有效载荷

    • 经过Base64Url编码是为了方便传输所以没有加密能被看到
  2. 生成签名

    • 对于对称签名(如HMAC),使用共享密钥进行签名
    • 对于非对称签名(如RSA),使用私钥进行签名
    • 对标头和有效载荷进行编码后,使用指定的算法和密钥生成的对方没有密钥无法破解
  3. 将三部分合并

    • 将头部、有效载荷和签名用.连接起来,形成最终的JWT字符串(对编码连接)。就是Token

签名和加密是不同的:

JWT的签名机制(如HMAC或RSA)用于验证信息的完整性和真实性,而不是用来保护信息的机密性。

  • 即使攻击者知道签名算法,也无法伪造有效的JWT,因为没有密钥

Base64Url编码:

编码可以被任何人解码,因此有效载荷中不应该包含敏感信息(如密码、个人身份信息等)

JWT中的有效载荷部分(Payload)是经过Base64Url编码的,这只是编码,而不是加密。JWT的设计假设内容是公开的信息,且应该在有效载荷中加工数据,而不是保护数据。

4.3 登入实现

4.3.1 登入步骤

密码比较不可以用equals,明码比较的话安全性低

  1. 前端输入账号密码
  2. 根据账号获得数据库加密的密码
  3. 前端密码跟数据库已加密的密码比较
  4. 比对成功后签Token返回

🌟 前端要获取Token
🌟 JWT不是很安全,用户的密码一定不能保存到JWT中

登入逻辑如下:
在这里插入图片描述

4.3.2 代码实现

  • 登入成功后签发Token并放到Redis

放Redis里面并设置时间是为了方便后续验证与延期,验证成功方便获取ID

后端控制层与逻辑层:
在这里插入图片描述

4.3.3 测试结果

  • 测试登入

ApiPost模拟前端

登入后返回前端的数据
在这里插入图片描述

  • 后台显示结果

在这里插入图片描述

4.4 配置拦截器

  • 拦截器排除登入以外的路径

拦截器拦截判断请求头与Token

🌟 前端已经收到Token了接下来交互验证都通过这个Token
🌟 拦截后验证Token是否过期
🌟 TokenInterceptor用来判断请求是否通过
⭐️ 通过请求头携带Token

拦截器逻辑如下:
在这里插入图片描述

拦截器代码如下:
在这里插入图片描述

4.5 通过Token获取数据

4.5.1 拦截器内部判断以及实现业务逻辑

  • 判断有了Token才能继续进入否则直接被拦截

主要判断头部以及Token是否过期并顺便进行延期,登入后访问其他页面不需要再重新登入,因为已经持有Token且没有过期

🌟 通过拦截器后执行业务逻辑

经过拦截器成功后经过AdminController层最后到AdminServicelmpl执行获取数据方法

获取数据逻辑:
在这里插入图片描述

主要代码:
在这里插入图片描述

存储存储和获取当前线程的上下文信息代码:

/*** BaseContext类是用于存储和获取当前线程的上下文信息* 主要用于在多线程环境下,为每个线程提供独立的存储空间*/
public class BaseContext {// 使用ThreadLocal为每个线程提供独立的存储空间,避免数据共享带来的问题private static ThreadLocal<String> threadLocal = new ThreadLocal<>();/*** 设置当前线程的上下文信息* 通常用于标识当前操作的用户ID,以便在日志记录或数据权限控制中使用** @param adminId 管理员ID,用于标识当前操作的用户*/public static void set(String adminId){threadLocal.set(adminId);}/*** 获取当前线程的上下文信息** @return 当前线程存储的管理员ID,如果未设置则返回null*/public static String get(){return threadLocal.get();}}
  • 如果Token存储时间不够会进行延期处理

如果长时间没使用就需要重新登入

在这里插入图片描述

4.5.2 测试结果

  • 测试成功

ApiPost模拟前端

在这里插入图片描述

  • 测试失败

此时Token在Redis里面已经过期

在这里插入图片描述

4.6 JWT令牌的过期时间方法比较

4.6.1 通过Redis方法

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String token = request.getHeader("Authorization");log.info("token还剩下{}秒", redisTemplate.getExpire("adminLogin:token", TimeUnit.SECONDS));// 检查Redisif (!redisTemplate.hasKey("adminLogin:token")) {throw new RuntimeException("redis里面token不存在");}Long expire = redisTemplate.getExpire("adminLogin:token", TimeUnit.SECONDS);if (expire < 30) {redisTemplate.expire("adminLogin:token", 40, TimeUnit.SECONDS);}// 获取JWT ClaimsClaims claims = jwtUtil.parseJwt(token);String id = claims.get("adminId", String.class);log.info("id:{}", id);BaseContext.set(id);return true;
}

这段代码主要做了以下几件事:

  • 检查JWT令牌的存在:从请求头中获取JWT令牌。
  • 检查Redis中的过期时间:获取Redis中存储的令牌的过期时间。
  • 自动续期:如果过期时间小于30秒,则将其延长到40秒。
  • 解析JWT令牌:通过Token解析JWT令牌中的claims,并从中提取用户ID对应有效载荷里的ID

4.6.2 设置JWT过期时间方法

.setExpiration(new Date(System.currentTimeMillis() + jwtConfig.getTtl()))
  • 工具类里面创建令牌时候的配置

4.6.3 比较

区别如下

  1. 存储位置:

    • JWT:令牌存储在客户端,每次请求时都携带。

    • Redis:令牌或相关状态存储在服务器端的Redis中。

  2. 过期时间管理:

    • JWT:令牌本身的过期时间是在生成时就固定的,一旦过期就无法再使用。
    • Redis:令牌或相关状态的过期时间可以动态调整。例如,你的代码中在过期时间接近时会自动续期。
  3. 复杂度:

    • JWT:相对简单,无需服务器端额外的逻辑来管理过期时间。
    • Redis:需要额外的逻辑来检查和续期过期时间,增加了服务器端的复杂度。
  4. 效果

    • JWT过期时间:一旦过期,令牌就无效,客户端需要重新获取新的令牌。
    • Redis过期时间:可以动态续期,使得令牌在一定条件下始终有效。
  5. 使用场景

    • JWT:适用于需要简单、快速的身份验证场景,减少服务器负担。
    • Redis:适用于需要更灵活的过期时间管理场景,如刷新令牌机制。
  6. 总结

    • JWT:简单、无状态,适合轻量级的身份验证。
    • Redis:灵活、状态化,适合需要动态管理过期时间的场景。

选择哪种方式取决于具体的应用需求和场景。

  • 如果你需要更灵活的过期时间管理,可以选择Redis;
  • 如果需要简单的无状态验证,可以选择JWT。

4.7 JWT与Session与Token与Cookie区别

网上文章很详细,这里记录关键

  • JWT是一种用于身份验证的开放标准,而Token是一种用于身份验证和访问控制的令牌
  • Session是在服务器端存储用户信息的机制,而Cookie是在客户端存储用户信息的机制;
  • JWT和Token都可以用于跨域身份验证和授权,而Session和Cookie通常用于同域身份验证和授权;
  • JWT和Token都使用数字签名来验证其有效性,而Session和Cookie通过唯一标识来验证用户。

五、总结:

JWT的优点是简单、轻量级、跨平台、可扩展性强,并且不需要在服务器端保存用户的登录状态。
缺点是一旦签发的JWT被盗用,无法立即使其失效,除非附加一些额外的逻辑来实现JWT的撤销。

JWT(JSON Web Token)是一种用于身份验证和授权的开放标准。

JWT由三部分组成:头部(Header)、负载(Payload)和签名(Signature)。

  • 头部包含了加密算法、令牌类型等信息,一般使用Base64编码进行编码。
  • 负载是JWT的主要部分,包含了一些声明(claims),例如用户ID、角色等信息。负载也可以包含自定义的声明。负载也使用Base64编码进行编码。
  • 签名用于保证令牌的完整性和真实性。签名由头部、负载、预先定义的密钥和指定的加密算法组成。一般使用HMAC或RSA算法进行签名。

使用JWT进行身份验证和授权的流程如下:

  • 用户登录,服务器验证用户信息。
  • 服务器生成JWT,将用户信息和其他必要的信息编码到负载中
  • 服务器使用密钥对JWT进行签名,生成签名。
  • 服务器将JWT和签名返回给客户端。
  • 客户端在后续请求中将JWT放入请求头、Cookie或其他合适的位置进行传输。
  • 服务器验证JWT的签名,并根据负载中的信息进行权限控制和身份验证。

在使用JWT时需要注意以下几点:

  • JWT中不应包含敏感信息,因为负载是经过Base64编码的,可能会被解码。
  • 密钥的安全非常重要,因为密钥用于生成和验证签名。应该使用强密码来保护密钥,并定期更换密钥。
  • JWT的过期时间应该适当设置,以免被盗用后长时间有效。
  • 如果需要撤销JWT,可以使用黑名单或者额外的逻辑来实现。

(后续有遇到问题再添加)


声明:如本内容中存在错误或不准确之处,欢迎指正。转载时请注明原作者信息(麻辣香蝈蝈)。

在这里插入图片描述

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

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

相关文章

【正式版】深度技术Win10系统22H2最新版本:免费下载!

今日&#xff0c;系统之家小编给大家分享2024年最新发布的深度技术Win10正式版系统&#xff0c;该版本系统基于微软官方最新Windows10 22H2 19045.4842 64位专业版进行离线制作&#xff0c;确保安全无病毒&#xff0c;且修复了部分系统Bug&#xff0c;整体操作体验感更出色。系…

6.1排序——插入排序与希尔排序

本篇博客来梳理两种常见排序算法&#xff1a;插入排序与希尔排序 常见的排序算法如图 写排序算法的原则&#xff1a;先写单趟&#xff0c;再写整体 一、直接插入排序 1&#xff0e;算法思想 先假定第一个数据有序&#xff0c;把第二个数据插入&#xff1b;再假设前两个数据…

[【人工智能学习笔记】4_3 深度学习基础之卷积神经网络

卷积神经网络概述 卷积神经网络(Convolutional Neural Network, CNN)一种带有卷积结构的深度神经网络,通过特征提取和分类识别完成对输入数据的判别;在1989年提出,早期被成功用于手写字符图像识别;2012年更深层次的AlexNet网络取得成功,伺候卷积神经网络被广泛应用于各…

uniapp使用uni-popup做底部弹出选项(vue3)

效果图 页面代码 <!-- 发票筛选弹出框 --><uni-popup ref"popupRef" type"bottom" border-radius"10px 10px 0 0" background-color"#fff"><h4 style"text-align: center;margin-bottom: 20px;">发票筛…

node解析dxf文件

1、dxf数据说明 DXF是一种开放的矢量数据格式&#xff0c;可以分为两类&#xff1a;ASCII格式和二进制格式&#xff1b;ASCII具有可读性好的特点&#xff0c;但占用的空间较大&#xff1b;二进制格式则占用的空间小、读取速度快。由于AutoCAD是最流行的CAD系统&#xff0c;DXF也…

uniapp 懒加载、预加载、缓存机制深度解析

uniapp 懒加载、预加载、缓存机制深度解析 文章目录 uniapp 懒加载、预加载、缓存机制深度解析一、为什么要使用uniapp的懒加载、预加载和缓存机制二、如何使用uniapp的懒加载、预加载和缓存机制1. 懒加载2. 预加载3. 缓存机制 四、扩展与高级技巧1. 结合懒加载和预加载优化页面…

眼科市场格局固化,排名靠后的光正眼科还能逆袭吗?

眼科是A股的热门领域&#xff0c;也是医疗的黄金赛道。或许也正因为如此&#xff0c;这条赛道已经习惯了通过并购&#xff0c;利用资本杠杆跑马圈地。以最大规模的龙头爱尔眼科为首&#xff0c;并购是眼科的常规操作。 然而&#xff0c;真正观察赛道腰部及以下的公司&#xff…

elementUI根据列表id进行列合并@莫成尘

本文章提供了elementUI根据列表id进行列合并的demo&#xff0c;效果如图&#xff08;可直接复制代码粘贴&#xff09; <template><div id"app"><el-table border :data"tableList" style"width: 100%" :span-method"objectS…

2024.9.9营养小题【2】

营养&#xff1a; 1、什么数是丑数&#xff1f; 2、数学数学&#xff0c;丑数的数学意义&#xff0c;哎&#xff0c;数学思维我是忘干净了。 3、可以把while循环换成for循环。由此又想到了一点&#xff0c;三个循环结构各有使用场景。 for(;n%factors[i]0;n/factors[i]){}

网络编程day02(字节序、TCP编程)

目录 【1】字节序 1》大小端转换 2》端口转换 3》IP地址转换 主机字节序转换为网络字节序 &#xff08;小端序->大端序&#xff09; 网络字节序转换为主机字节序&#xff08;大端序->小端序&#xff09; 【2】TCP编程 1》流程 2》函数接口 1> socket 2> …

C# 删除Word文档中的段落

在编辑Word文档时&#xff0c;我们有时需要调整段落的布局、删除不必要的段落以优化文档的结构和阅读体验。本文将通过以下3个简单示例演示如何使用免费.NET库删除Word文档中的段落 。 目录 C# 删除Word中的指定段落 C# 删除Word中的所有段落 C# 删除Word中的空白段落 免费…

分组注解和自定义注解及分页查询

自定义注解的使用步骤 案例&#xff1a; 此时state需要进行的校验使用普通方式无法满足&#xff0c;需要我们根据需求进行自定义注解 创建一个注解 Documented//元注解 Retention(RetentionPolicy.RUNTIME)//元注解 Constraint(validatedBy {StateValidation.class}//指定提供…

网络编程day04(UDP、Linux IO 模型)

目录 【1】UDP 1》通信流程 2》函数接口 1> recvfrom 2> sendto 3》代码展示 1> 服务器代码 2> 客户端代码 【2】Linux IO 模型 场景假设一 1》阻塞式IO&#xff1a;最常见、效率低、不耗费CPU 2》 非阻塞 IO&#xff1a;轮询、耗费CPU&#xff0c;可以处…

Java后台生成二维码

一、效果图 二、实现代码 1.添加依赖 <!-- zxing生成二维码 --> <dependency><groupId>com.google.zxing</groupId><artifactId>core</artifactId><version>3.3.3</version> </dependency><dependency><grou…

大数据之Flink(四)

11、水位线 11.1、水位线概念 一般实时流处理场景中&#xff0c;事件时间基本与处理时间保持同步&#xff0c;可能会略微延迟。 flink中用来衡量事件时间进展的标记就是水位线&#xff08;WaterMark&#xff09;。水位线可以看作一条特殊的数据记录&#xff0c;它是插入到数…

机械学习—零基础学习日志(Python做数据分析02)

现在开始使用Python尝试做数据分析。具体参考的网址链接放在了文章末尾。 引言 我通过学习《利用Python进行数据分析》这本书来尝试使用Python做数据分析。书里让下载&#xff0c;anaconda&#xff0c;使用Jupyter来写代码&#xff0c;只是下载一个anaconda的确有点费时间&am…

计算机的发展史和基本结构

好久不见&#xff0c;粉粉们&#xff0c;我是#Y清墨。今天来分享一下最近学习做的笔记。 计算机发展史和四代计算机概述 阶段 年代 电子元件 运算速度&#xff08;每秒/次&#xff09; 第一代 1946-1958 真空电子管 数千至数万 第二代 1958-1964 晶体管 几十万至百万…

王道考研操作系统笔记(一)

虚拟内存的定义和特征&#xff1a; 基于局部性的原理&#xff0c; 在程序装入时&#xff0c;可以将程序中很快用到的部分装入内存&#xff0c;暂时用不到的数据装入外存&#xff0c;就可以让程序开始执行&#xff0c;在程序执行过程中&#xff0c;当所访问的信息不在内存的时…

更高级的主播美颜体验:直播美颜SDK的集成与开发方案详解

本篇文章&#xff0c;小编将详细解析如何通过直播美颜SDK实现更高级的主播美颜体验&#xff0c;并提供集成与开发的最佳方案。 一、直播美颜SDK的核心功能 直播美颜SDK是一种集成包&#xff0c;能够提供各种美颜功能&#xff0c;帮助主播在直播过程中实时调整面部特征&#…

147.最小栈

题目 链接&#xff1a;leetcode链接 思路 这道题目做起来还是比较简单的&#xff0c;使用两个栈就可以实现题目要求。 其中一个栈s实现栈的基本功能&#xff0c;另一个栈mins实现检索最小元素的功能。 来看一下怎么样实现检索最小元素的功能呢&#xff1f; 我们可以这么…