Rust之错误处理(二):带结果信息的可恢复错误
开发环境
- Windows 10
- Rust 1.67.1
- VS Code 1.75.1
项目工程
这里继续沿用上次工程rust-demo
带结果信息的可恢复错误
大多数错误并没有严重到需要程序完全停止的程度。有时,当一个函数失败时,它的原因是你可以很容易地解释和应对的。例如,如果你试图打开一个文件,但由于该文件不存在而导致操作失败,你可能想创建该文件,而不是终止该进程。
回顾前面章节中的 "用结果处理潜在的失败",结果枚举被定义为有两个变体,Ok和Err,如下例所示。
enum Result<T, E> {Ok(T),Err(E),
}
T和E是通用类型参数:我们将在后面章节中详细讨论通用类型。你现在需要知道的是,T代表在Ok变量中成功情况下将被返回的值的类型,E代表在Err变量中失败情况下将被返回的错误类型。因为Result有这些通用的类型参数,我们可以在许多不同的情况下使用Result类型和定义在它上面的函数,我们想返回的成功值和错误值可能不同。
让我们调用一个返回结果值的函数,因为这个函数可能会失败。在下例3中,我们尝试打开一个文件。
例3:文件名: src/main.rs
use std::fs::File;fn main() {let greeting_file_result = File::open("hello.txt"); // hello.txt是否存在println!("greeting_file_result = {:?}", greeting_file_result )
}
编译运行
cargo run
File::open 的返回类型是一个 Result<T, E>。通用参数T已经被File::open的实现填入了成功值的类型,即std::fs::File,它是一个文件句柄。错误值中使用的E的类型是std::io::Error。这个返回类型意味着对 File::open 的调用可能会成功,并返回一个我们可以读取或写入的文件句柄。该函数的调用也可能失败:例如,该文件可能不存在,或者我们可能没有访问该文件的权限。File::open函数需要有一种方法来告诉我们它是成功还是失败,同时给我们提供文件柄或错误信息。这些信息正是Result枚举所传达的。
在File::open成功的情况下,变量greeting_file_result中的值将是一个Ok的实例,包含一个文件柄。在失败的情况下,greeting_file_result中的值将是一个Err的实例,包含关于发生的错误类型的更多信息。
我们需要在上例2中的代码中添加一些内容,以便根据File::open返回的值采取不同的行动。下例3显示了一种使用基本工具处理结果的方法,即我们在之前章节讨论过的match表达式。
例4:文件名: src/main.rs
use std::fs::File;fn main() {let greeting_file_result = File::open("hello.txt");let greeting_file = match greeting_file_result { // match表达式Ok(file) => file,Err(error) => panic!("Problem opening the file: {:?}", error),};
}
请注意,和Option枚举一样,Result枚举和它的变体已经被前奏带入了范围,所以我们不需要在匹配类中的Ok和Err变体之前指定Result::。
当结果为Ok时,该代码将从Ok变量中返回内部文件值,然后我们将该文件句柄值赋给变量greeting_file。match后,我们可以使用文件句柄来读或写。
match的另一方面处理从File::open获取一个Err值的情况。在这个例子中,我们选择了panic!宏观。如果在我们的当前目录中没有名为hello.txt的文件,并且我们运行了这段代码,我们将会看到下面的输出panic!宏:
编译运行
cargo run
像往常一样,这个输出告诉我们到底哪里出错了。
不同错误的匹配
上例中的代码会panic!不管为什么File::open失败。但是,我们希望针对不同的失败原因采取不同的操作:如果File::open因为文件不存在而失败,我们希望创建该文件并将句柄返回给新文件。如果File::open由于任何其他原因失败—例如,因为我们没有打开文件的权限—我们仍然希望代码panic!就像上例中一样。为此,我们添加了一个内部match表达式,如下例5所示。
例5:文件名: src/main.rs
use std::fs::File;
use std::io::ErrorKind;fn main() {let greeting_file_result = File::open("hello.txt");let greeting_file = match greeting_file_result {Ok(file) => file,Err(error) => match error.kind() { // match错误类型ErrorKind::NotFound => match File::create("hello.txt") { // 创建hello,extOk(fc) => fc,Err(e) => panic!("Problem creating the file: {:?}", e),},other_error => {panic!("Problem opening the file: {:?}", other_error);}},};
}
编译运行
cargo run
File::open在Err内部返回值的类型是io::Error,这是标准库提供的一个结构体。这个结构有一个方法kind,我们可以调用它来获取io::ErrorKind值。枚举io::ErrorKind由标准库提供,它具有代表io操作可能导致的不同类型错误的变量。我们要使用的变量是ErrorKind::NotFound,这表明我们试图打开的文件尚不存在。所以我们在greeting_file_result上进行匹配,但是在error.kind()上也有一个内部匹配。
我们要在内部匹配中检查的条件是error.kind()返回的值是否是ErrorKind枚举的NotFound变量。如果是,我们尝试用File::create创建文件。然而,因为File::create也可能失败,所以我们需要在内部match表达式中增加一个分支。当无法创建文件时,会打印一条不同的错误消息。外部match的第二个分支保持不变,因此除了丢失文件错误之外,程序还会对任何错误产生恐慌。
使用match Result< T,E >的替代方法
match的多了去了!match表达式非常有用,但也非常原始。在后面章节中,你将学习闭包,它与Result<T,E >上定义的许多方法一起使用。在代码中处理Result<T,E >值时,这些方法比使用match更简洁。
例如,这里有另一种方法来编写如上例中所示的逻辑,这次使用闭包和unwrap_or_else方法:
use std::fs::File; use std::io::ErrorKind;fn main() {let greeting_file = File::open("hello.txt").unwrap_or_else(|error| {if error.kind() == ErrorKind::NotFound {File::create("hello.txt").unwrap_or_else(|error| {panic!("Problem creating the file: {:?}", error);})} else {panic!("Problem opening the file: {:?}", error);}}); }
尽管这段代码的行为与上例相同,但它不包含任何match表达式,读起来更清晰。在你读完后面章节后回到这个例子,在标准库文档中查找unwrap_or_else方法。当您处理错误时,更多的这些方法可以清理大量嵌套的match表达式。
出错时死机的快捷方式:unwrap和expect
使用match可以很好地工作,但是它可能有点冗长,并且不总是很好地传达意图。Result<T,E >类型定义了许多方法来完成各种更具体的任务。unwrap方法是一个快捷方法,就像我们在上例中编写的match表达式一样。如果Result值是Ok变量,unwrap将返回Ok变量中的值。如果Result是Err变量,unwrap将调用panic!宏观对我们来说。下面是一个实际展开的例子:
文件名:src/main.rs
use std::fs::File;fn main() {let greeting_file = File::open("hello.txt").unwrap();println!("greeting_file = {:?}", greeting_file);
}
运行
cargo run
如果我们在没有hello.txt文件的情况下运行这段代码,我们将会看到一条来自panic!的错误消息!unwrap方法进行的调用:
同样,expect方法也让我们选择panic!错误消息。使用expect而不是unwrap并提供良好的错误消息可以传达您的意图,并使跟踪死机的根源变得更容易。expect的语法如下例所示:
文件名:src/main.rs
use std::fs::File;fn main() {let greeting_file = File::open("hello.txt").expect("hello.txt should be included in this project");println!("greeting_file = {:?}", greeting_file);
}
运行
cargo run
我们使用expect的方式和unwrap一样:返回文件句柄或者调用panic!宏。expect在调用panic!时使用的错误消息!将是我们传递给expect的参数,而不是默认的panic!unwrap使用的消息。它看起来是这样的:
在生产质量的代码中,大多数Rust程序员选择expect而不是unwrap,并给出更多关于为什么操作预期总是成功的上下文。这样,如果您的假设被证明是错误的,您就有更多的信息用于调试。
传播误差
当函数的实现调用可能失败的东西时,您可以将错误返回给调用代码,以便它可以决定做什么,而不是在函数本身中处理错误。这就是所谓的传播错误,并给予调用代码更多的控制,其中可能有比代码上下文中可用的更多的信息或逻辑来指示应该如何处理错误。
下例7显示了一个从文件中读取用户名的函数。如果该文件不存在或无法读取,该函数将把这些错误返回给调用该函数的代码。
例6:文件名:src/main.rs
use std::fs::File;
use std::io::{self, Read};fn read_username_from_file() -> Result<String, io::Error> {let username_file_result = File::open("hello.txt");let mut username_file = match username_file_result {Ok(file) => file,Err(e) => return Err(e),};let mut username = String::new();match username_file.read_to_string(&mut username) {Ok(_) => Ok(username),Err(e) => Err(e),}
}
这个函数可以用更短的方式编写,但是为了探索错误处理,我们将从手工做大量的工作开始;最后,我们将展示更短的方法。我们先来看看函数的返回类型:Result<String,io::Error >。这意味着该函数正在返回类型Result< T,E >的值,其中泛型参数T已经用具体类型String填充,泛型类型E已经用具体类型io::Error填充。
如果这个函数成功,没有任何问题,调用这个函数的代码将收到一个Ok值,该值包含一个String—这个函数从文件中读取的用户名。如果这个函数遇到任何问题,调用代码将收到一个Err值,该值包含io::Error的一个实例,该实例包含有关问题的更多信息。我们选择io::Error作为这个函数的返回类型,因为这恰好是我们在这个函数体中调用的可能失败的两个操作返回的错误值的类型:File::open函数和read_to_string方法。
函数体通过调用File::open函数开始。然后我们用类似于之前例子中的匹配来处理Result值。如果File::open成功,模式变量file中的文件句柄将变成可变变量username_file中的值,函数将继续运行。在Err的情况下,而不是叫painc!,我们使用return关键字从函数中提前返回,并将File::open中的错误值(现在在模式变量e中)作为该函数的错误值传递回调用代码。
因此,如果我们在username_file中有一个文件句柄,该函数就会在变量username中创建新String,并在username_file中的文件句柄上调用read_to_string方法,将文件内容读入username。read_to_string方法也返回一个Result,因为它可能会失败,即使File::open成功了。所以我们需要另一个匹配来处理这个结果:如果read_to_string成功了,那么我们的函数就成功了,我们从文件中返回username,这个文件现在包含在一个Ok中。如果read_to_string失败,我们返回错误值的方式与我们在处理File::open返回值的match中返回错误值的方式相同。但是,我们不需要显式地说return,因为这是函数中的最后一个表达式。
调用此代码的代码将处理获取包含用户名的Ok值或包含io::Error的Err值。由调用代码决定如何处理这些值。如果调用代码得到一个Err值,它可能会调用panic!并使程序崩溃,使用默认用户名,或者从文件以外的地方查找用户名。我们没有足够的信息来了解调用代码实际试图做什么,所以我们向上传播所有的成功或错误信息,以便它进行适当的处理。
这种传播错误的模式在Rust中非常普遍,以至于Rust提供了问号运算符?为了让事情变得简单
传播错误的快捷方式 :? 操作符
下例7显示了read_username_from_file的一个实现,它具有与上例相同的功能,但是这个实现使用了?操作符。
例7:文件名:src/main.rs
use std::fs::File;
use std::io::{self, Read};fn read_username_from_file() -> Result<String, io::Error> {let mut username_file = File::open("hello.txt")?; // ? 操作符let mut username = String::new();username_file.read_to_string(&mut username)?; // ? 操作符Ok(username)
}
?放置在Result值定义之后,其工作方式与我们在上例中定义的处理结果值的match表达式几乎相同。如果Result的值是Ok,Ok中的值将从这个表达式返回,程序将继续。如果值是一个Err,Err将从整个函数中返回,就像我们使用return关键字一样,因此错误值将传播到调用代码。
上例中的match表达式和? 操作符所做的事情是有区别的:被调用了? 操作符的错误值会经过标准库中的From特性中定义的from函数,该函数用于将值从一种类型转换成另一种类型。当? 操作符调用from函数时,收到的错误类型被转换为当前函数的返回类型中定义的错误类型。当一个函数返回一个错误类型时,这很有用,它代表了一个函数可能失败的所有方式,即使部分函数可能因为许多不同的原因而失败。
例如,我们可以改变上例7中的 read_username_from_file 函数来返回一个我们定义的名为 OurError 的自定义错误类型。如果我们也为OurError定义 impl From<io::Error> for io::Error构造一个OurError的实例,那么read_username_from_file正文中的? 操作符调用将调用from并转换错误类型,而不需要在函数中添加任何代码。
在上例的上下文中,File::open调用的结尾处的? 将返回Ok内的值到变量username_file。如果发生错误,? 操作符将提前从整个函数中返回,并将任何Err值交给调用代码。同样的事情也适用于read_to_string调用结束时的?
? 运算符消除了大量的模板,使这个函数的实现更加简单。我们甚至可以通过在"? "后面紧跟的方法调用链来进一步缩短这段代码,如下例8中所示。
例8:文件名:src/main.rs
use std::fs::File;
use std::io::{self, Read};fn read_username_from_file() -> Result<String, io::Error> {let mut username = String::new();File::open("hello.txt")?.read_to_string(&mut username)?;Ok(username)
}
我们把创建用username中的新String移到了函数的开头;这部分没有改变。我们没有创建一个变量username_file,而是将对read_to_string的调用直接链接到File::open("hello.txt")的结果上。我们在 read_to_string 调用的最后仍然有一个 ? ,而且当 File::open和 read_to_string 都成功时,我们仍然返回一个包含用户名的 Ok 值,而不是返回错误。其功能与例6和例7中的相同;这只是一种不同的、更符合人体工程学的写法而已。
例9显示了一种使用fs::read_to_string使其更短的方法。
例9:文件名:src/main.rs
use std::fs;
use std::io;fn read_username_from_file() -> Result<String, io::Error> {fs::read_to_string("hello.txt")
}
将文件读入字符串是一个相当常见的操作,所以标准库提供了方便的fs::read_to_string函数,该函数打开文件,创建一个新的String,读取文件的内容,将内容放入该String中,并返回它。当然,使用fs::read_to_string并没有给我们解释所有错误处理的机会,所以我们先用较长的方法来做。
哪些地方可以使用? "操作符
? 操作符只能在返回类型与? 操作符所使用的值兼容的函数中使用。这是因为?操作符被定义为执行从函数中提前返回一个值,其方式与我们在例6 中定义的match表达式相同。在清例6中,match使用的是一个Result值,而提前返回了一个Err(e)值。函数的返回类型必须是一个 Result,这样才能与这个Return兼容。
在例10中,让我们看看如果我们在一个main函数中使用? 操作符,其返回类型与我们使用的值的类型不兼容,我们会得到什么错误。
例10:文件名:src/main.rs
use std::fs::File;fn main() {let greeting_file = File::open("hello.txt")?;
}
这段代码打开了一个文件,这可能会失败。? 操作符跟随File::open返回的结果值,但这个main函数的返回类型是(),而不是Result。当我们编译这段代码时,我们会得到以下错误信息。
这个错误指出,我们只允许在返回 Result、Option 或其他实现 FromResidual 的类型的函数中使用 ? 操作符。
要解决这个错误,你有两个选择。一种选择是改变你的函数的返回类型,使之与你使用的? 运算符的值兼容,只要你没有限制阻止这样做。另一种技术是使用match或Result<T, E>方法之一,以任何适当的方式处理Result<T, E>。
该错误信息还提到,? 也可以用于 Option<T>值。与在Result上使用 ? 一样,你只能在一个返回 Option 的函数中对 Option 使用 ? 。在 Option<T>上调用 ? 操作符的行为与在 Result<T, E>上调用的行为类似:如果值是 None,None将在此时从函数中提前返回。如果值是Some,Some里面的值就是表达式的结果值,函数继续。例11中有一个函数的例子,它可以找到给定文本中第一行的最后一个字符。
例11
fn last_char_of_first_line(text: &str) -> Option<char> {text.lines().next()?.chars().last()
}
这个函数返回Option<char>,因为那里有可能有一个字符,但也有可能没有。这段代码接收了文text字符串的切片参数,并对其调用lines方法,该方法返回字符串中的行的迭代器。因为这个函数想检查第一行,所以它在迭代器上调用next,从迭代器中获得第一个值。如果text是空字符串,对next的调用将返回None,在这种情况下,我们用? 来停止,并从last_char_of_first_line返回None。如果text不是空字符串,next将返回一个Some值,包含text中第一行的字符串切片。
? 提取了字符串片段,我们可以在该字符串片段上调用chars来获得其字符的迭代器。我们对这第一行的最后一个字符感兴趣,所以我们调用last来返回迭代器中的最后一项。这是一个Option,因为第一行有可能是空字符串,例如,如果text以空行开始,但在其他行有字符,如"\nhi"。然而,如果第一行有最后一个字符,它将在Some变体中被返回。中间的? 操作符给了我们一个简洁的方式来表达这个逻辑,使我们可以在一行中实现这个函数。如果我们不能在 Option 上使用 ? 操作符,我们就必须使用更多的方法调用或match表达式来实现这个逻辑。
请注意,你可以在一个返回结果的函数中对一个Result使用 ? 操作符,也可以在一个返回 Option 的函数中对一个 Option 使用 ? 操作符,但是你不能混合匹配。运算符不会自动将一个Result转换为一个Option ,反之亦然;在这种情况下,你可以使用像Result的ok方法或Option 的ok_or方法来进行明确的转换。
到目前为止,我们所使用的所有main函数都返回()。main函数很特别,因为它是可执行程序的进入和退出点,它的返回类型是有限制的,以使程序的行为符合预期。
幸运的是,mainmain返回一个Result<(), E>。清例12中有例子10的代码,但我们将main的返回类型改为Result<(), Box<dyn Error>>,并在结尾处添加了一个返回值Ok(())。这段代码现在可以编译了。
例12
use std::error::Error;
use std::fs::File;fn main() -> Result<(), Box<dyn Error>> {let greeting_file = File::open("hello.txt")?;Ok(())
}
Box<dyn Error>类型是一个特质对象,我们将在后面章节的 "使用允许不同类型的值的特质对象 "一节中讨论这个问题。现在,你可以把Box<dyn Error>理解为 "任何类型的错误"。在错误类型为Box<dyn Error>的main函数中对Result值使用? 是允许的,因为它允许任何Err值被提前返回。即使这个main函数的主体只返回std::io::Error类型的错误,通过指定Box<dyn Error>,即使有更多返回其他错误的代码被添加到main的主体中,这个签名仍然是正确的。
当main函数返回一个Result<(), E>时,如果main返回Ok(()),可执行程序将以0的值退出,如果main返回Err值,则以非零值退出。用C语言编写的可执行文件在退出时返回整数:成功退出的程序返回整数0,出错的程序返回0以外的某个整数。
main函数可以返回任何实现std::process::Termination trait的类型,它包含一个返回ExitCode的函数report。关于为你自己的类型实现Termination特性的更多信息,请查阅标准库文档。
现在我们已经讨论了调用panic!或返回Result的细节,让我们回到如何决定在什么情况下使用哪种方法是合适的话题。
本章重点
- 带结果的可恢复错误概念和使用
- 不同错误的匹配
- 程序死掉的方式:unwrap和expect
- 传播误差的概念
- ?操作符的使用
- ?操作符的使用场景
相关文章:

Rust之错误处理(二):带结果信息的可恢复错误
开发环境 Windows 10Rust 1.67.1VS Code 1.75.1项目工程 这里继续沿用上次工程rust-demo 带结果信息的可恢复错误 大多数错误并没有严重到需要程序完全停止的程度。有时,当一个函数失败时,它的原因是你可以很容易地解释和应对的。例如,如…...

[ vulhub漏洞复现篇 ] Drupal Core 8 PECL YAML 反序列化任意代码执行漏洞(CVE-2017-6920)
🍬 博主介绍 👨🎓 博主介绍:大家好,我是 _PowerShell ,很高兴认识大家~ ✨主攻领域:【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 🎉点赞➕评论➕收藏 养成习…...
如何将数据库结构导入到word
在navicat执行查询语句 SELECT COLUMN_NAME 备注, COLUMN_COMMENT 名称, COLUMN_TYPE 数据类型, false as 是键 FROM INFORMATION_SCHEMA.COLUMNS where -- wx 为数据库名称,到时候只需要修改成你要导出表结构的数据库即可 table_schema yuncourt_ai AND -- articl…...

FreeRTOS内存管理 | FreeRTOS十五
目录 说明: 一、FreeRTOS内存管理 1.1、动态分配与用户分配内存空间 1.2、标准C库动态分配内存缺点 1.3、FreeRTOS的五种内存管理算法优缺点 1.4、heap_1内存管理算法 1.5、heap_2内存管理算法 1.6、heap_3内存管理算法 1.7、heap_4内存管理算法 1.8、hea…...

【数字电路】数字电路的学习核心
文章目录前言一、电子电路知识体系二、数电的学习目标三、数字电路分析例子四、数字电路设计例子总结前言 用数字信号完成对数字量进行算术运算和逻辑运算的电路称为数字电路,或数字系统。由于它具有逻辑运算和逻辑处理功能,所以又称数字逻辑电路。现代…...

day45【代码随想录】动态规划之完全平方数、单词拆分、打家劫舍、打家劫舍 II
文章目录前言一、完全平方数(力扣279)二、单词拆分(力扣139)三、打家劫舍(力扣198)四、打家劫舍 II前言 1、完全平方数 2、单词拆分 3、打家劫舍 4、打家劫舍 II 一、完全平方数(力扣279&#…...

java程序,springboot程序 找不到主类,找不到符号解决思路
文章目录问题解决方案一.可以尝试clean掉maven依赖,然后重新启动二.右键工程,选择maven然后重新加载工程,接着再启动试试三.删掉工程中的services.iml文件,重新配置后接着再启动试试四. 终极方案清除idea缓存,重启idea…...

AntD-tree组件使用详析
目录 一、selectedKeys与onSelect 官方文档 代码演示 onSelect 注意事项 二、expandedKeys与onExpand 官方文档 代码演示 onExpand 注意事项 三、loadedKeys与onLoad和onExpand 官方文档 代码演示 onExpand与onLoad: 注意事项 四、loadData …...

spring的事务控制
1.调用这个方法的对象是否是spring的代理对象($CGLIB结尾的) 2.这个方法是否是加了Transactional注释 都符合才可以被事物控制 如果调用方法的对象没有被事物控制,那么被调用的方法即便是加了Transactional也是没用的 事务失效情况…...

4.如何靠IT逆袭大学?
学习的动力不止于此: IT逆袭 这两天利用工作空余时间读了贺利坚老师的《逆袭大学——传给 IT 学子的正能量》,感触很多,有些后悔没有好好利用大学时光。 不过人都是撞了南墙再回头的,吃一堑长一智。 这本书无论你是工作了还是…...

提供网络可测试的接口【公共Webservice】
提供网络可测试的接口 1、腾讯QQ在线状态 WEB 服务 Endpoint: qqOnlineWebService Web 服务 Disco: http://www.webxml.com.cn/webservices/qqOnlineWebService.asmx?disco WSDL: http://www.webxml.com.cn/webservices/qqOnlineWebService.asmx?wsdl 腾讯QQ在线状态 WEB 服…...
【深入理解计算机系统】库打桩 - 阅读笔记
文章目录库打桩机制1. 编译时打桩2. 链接时打桩3. 运行时打桩库打桩机制 Linux 链接器支持一个很强大的技术,称为库打桩 (library interpositioning),它允许你截获对共享库函数的调用,取而代之执行自己的代码。使用打桩机制,你可以…...

RocketMQ高性能原理分析
目录一、读队列与写队列1.概念介绍2.读写队列个数关系分析二、消息持久化1.持久化文件介绍2.持久化结构介绍:三、过期文件删除1.如何判断文件过期2.什么时候删除过期文件四、高效文件写1.零拷贝技术加速文件读写2.文件顺序写3.刷盘机制五、 消息主从复制六、负载均衡…...

前端面试当中CDN会问啥------CDN详细教程来啦
⼀、CDN 1. CDN的概念 CDN(Content Delivery Network,内容分发⽹络)是指⼀种通过互联⽹互相连接的电脑⽹络系统,利 ⽤最靠近每位⽤户的服务器,更快、更可靠地将⾳乐、图⽚、视频、应⽤程序及其他⽂件发送给⽤户&…...
刷题记录:牛客NC19429红球进黑洞 区间拆位异或+区间求和
传送门:牛客 题目描述: 区间求和区间异或k 输入: 10 10 8 5 8 9 3 9 8 3 3 6 2 1 4 1 1 2 6 2 9 10 8 1 1 7 2 4 7 8 2 8 8 6 2 2 3 0 1 1 2 2 9 10 4 1 2 3 输出: 33 50 13 13一道区间求和区间异或的题目,可以称得上是线段树的一道好题 首先对于异或运算来说,并不满足…...
信息数智化招采系统源码——信息数智化招采系统
信息数智化招采系统 服务框架:Spring Cloud、Spring Boot2、Mybatis、OAuth2、Security 前端架构:VUE、Uniapp、Layui、Bootstrap、H5、CSS3 涉及技术:Eureka、Config、Zuul、OAuth2、Security、OSS、Turbine、Zipkin、Feign、Monit…...

20230217使AIO-3399J开发板上跑通Android11系统
20230217使AIO-3399J开发板上跑通Android11系统 2023/2/17 15:45 1、解压缩SDK:rk3399-android-11-r20211216.tar.xzrootrootrootroot-X99-Turbo:~$ tar xvf rk3399-android-11-r20211216.tar.xz 2、编译U-boot: rootrootrootroot-X99-Turbo:~/rk3399-a…...

Java 基础面试题——面向对象
目录1.面向对象和面向过程有什么区别?2.面向对象的有哪些特征?3.静态变量和实例变量有什么区别?4.Java 对象实例化顺序是怎样的?5.浅拷贝和深拷贝的区别是什么?5.1.浅拷贝5.2.深拷贝5.3.总结6.Java 中创建对象的方式有哪几种&…...
PDF文件替换内容(电子签章),依赖免费pdfbox
首先提前准备,压入如下依赖 <!-- https://mvnrepository.com/artifact/org.apache.pdfbox/pdfbox --> <dependency> <groupId>org.apache.pdfbox</groupId> <artifactId>pdfbox</artifactId>…...

nvm 控制 node版本
nvm 官网 https://nvm.uihtm.com/ 1、卸掉nodejs,根据官网操作 2、如果之前安装过的nodejs,且安装的目录改变了,需重新配置系统环境 第一步:打开此电脑 > 右键属性 > 高级系统设置 > 环境变量 第二步: 在系统变量中选中…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...
React hook之useRef
React useRef 详解 useRef 是 React 提供的一个 Hook,用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途,下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...

ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...

MODBUS TCP转CANopen 技术赋能高效协同作业
在现代工业自动化领域,MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步,这两种通讯协议也正在被逐步融合,形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...

佰力博科技与您探讨热释电测量的几种方法
热释电的测量主要涉及热释电系数的测定,这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中,积分电荷法最为常用,其原理是通过测量在电容器上积累的热释电电荷,从而确定热释电系数…...

【Redis】笔记|第8节|大厂高并发缓存架构实战与优化
缓存架构 代码结构 代码详情 功能点: 多级缓存,先查本地缓存,再查Redis,最后才查数据库热点数据重建逻辑使用分布式锁,二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...
适应性Java用于现代 API:REST、GraphQL 和事件驱动
在快速发展的软件开发领域,REST、GraphQL 和事件驱动架构等新的 API 标准对于构建可扩展、高效的系统至关重要。Java 在现代 API 方面以其在企业应用中的稳定性而闻名,不断适应这些现代范式的需求。随着不断发展的生态系统,Java 在现代 API 方…...
前端中slice和splic的区别
1. slice slice 用于从数组中提取一部分元素,返回一个新的数组。 特点: 不修改原数组:slice 不会改变原数组,而是返回一个新的数组。提取数组的部分:slice 会根据指定的开始索引和结束索引提取数组的一部分。不包含…...
React从基础入门到高级实战:React 实战项目 - 项目五:微前端与模块化架构
React 实战项目:微前端与模块化架构 欢迎来到 React 开发教程专栏 的第 30 篇!在前 29 篇文章中,我们从 React 的基础概念逐步深入到高级技巧,涵盖了组件设计、状态管理、路由配置、性能优化和企业级应用等核心内容。这一次&…...

数据结构:泰勒展开式:霍纳法则(Horner‘s Rule)
目录 🔍 若用递归计算每一项,会发生什么? Horners Rule(霍纳法则) 第一步:我们从最原始的泰勒公式出发 第二步:从形式上重新观察展开式 🌟 第三步:引出霍纳法则&…...