当前位置: 首页 > news >正文

【Rust】集合的使用——Rust语言基础16

文章目录

  • 1. 前言
  • 2. Vector
    • 2.1. 构建一个 vector
    • 2.2. 获取 vector 中的元素
    • 2.3. 遍历 vector
    • 2.4. 使用枚举来储存多种类型
  • 3. String
    • 3.1. 新建字符串
    • 3.2. 更新字符串
    • 3.3. 字符串的内部结构
      • 3.3.1. 字符串如何访问内部元素?
      • 3.3.2. 字节、标量值和字形簇
    • 3.4. 字符串 slice
    • 3.5. 字符串遍历
  • 4. Hash Map
    • 4.1. 新建一个 Hash Map
    • 4.2. 获取 map 中值
    • 4.4. 哈希 map 和所有权
    • 4.5. 覆盖关键字的值
    • 4.6. 当关键字不存在时才插入键值对
    • 4.7. 根据旧值更新一个值

1. 前言

看过上一篇博客的长篇大论一定很疲惫,不过笔者保证这一篇我们将会很轻松的度过~

在其它编程语言中也会有集合这样的类似概念,例如在 C++ 中将这种数据结构称为容器,而在 Rust 中则将这些数据结构称为集合collections)。集合是一种非常有用的数据结构,当有一类具有相同属性或者共同点的东西归类或统计时,集合就会派上大用处。

Rust 中常见的几个集合:

  • vector :存储一系列同类型数量可变的值;
  • 字符串(string):这时字符的集合,本文将会着重介绍它;
  • 哈希 map(hash map):每个成员是由一个唯一存在的关键字(key)和其所一一对应的值(value) 所组成。

接下来让我们一个个的认识并掌握!

2. Vector

Vec<T>,也被称为 vectorvector 允许我们在一个单独的数据结构中储存多于一个的值,它在内存中彼此相邻地排列所有的值。vector 只能储存相同类型的值。

正如所看到的一样这里的 <> 表示这是一个泛型实现的类型,到目前为止可以不用过于深入理解泛型,只要大概明白它的概念即可,这里的 T 可以被任何类型替换,表示该向量将会且只能存储这种类型的值。

2.1. 构建一个 vector

创建一个新的空 vector,可以调用 Vec::new 函数:

    let v: Vec<i32> = Vec::new();

可以看到这里创建的 vector 并没有初始化,因此我们在声明的时候需要指明其具体类型。

当有初始值时,创建的 vector 允许不显示声明其类型:

    let v = vec![1, 2, 3];

这里虽然没有显示的注明 vector 的类型,但是编译器通过其初始值可以推断出 v 的类型是 Vec<i32>

当我们新建完一个空的 vector 后可以用 push 方法向其添加元素:

	// 注意,此时则需要创建可变类型let mut v = Vec::new();// 向最后插入一个新的元素v.push(5);v.push(6);v.push(7);v.push(8);// 删除末尾的一个元素v.pop();

[注]:Rust 编译器也可以通过后面所添加的值来推断 vector 类型。

2.2. 获取 vector 中的元素

有两种方式可以获取 vector 中的元素:

  • 通过索引的方式访问获取;
  • 使用 get 方法获取。
    let v = vec![1, 2, 3, 4, 5];// 通过索引的方式访问let third: &i32 = &v[2];println!("The third element is {third}");// 通过 vector 的 get 方法访问let third: Option<&i32> = v.get(2);match third {Some(third) => println!("The third element is {third}"),None => println!("There is no third element."),}

这里需要注意的一个点是 get 方法返回的是 Option<&T> 类型并非元素值本身,因此我们需要通过 match 提取具体的值。

越界问题

访问越界这是一个很常见的问题,我们来看看 Rust 是如何处理的:

	let v = vec![32, 26, 48];let unexist = &v[10];		// 索引let unexist = v.get(10);	// get

首先这段代码是可以编译通过的,因为语法没有任何问题,但是在运行时将会使得程序崩溃,由于 Rust 发现试图去访问一个超过 vector 范围的内存,直接会终止程序运行。

那么当我们注释掉 “索引” 那行代码后,这段程序时可以正常运行的,这是何原因呢?

由于 get 返回的是 Option<&T> 类型,也就是当有值的时候会返回 Some(i),没有值时候返回 None,这两个都不会导致程序崩溃。这样设计的原因在于 Rust 考虑到这种越界特殊情况的处理工作也可以交给开发者自己处理,而不是一味的终止代码运行,这是很不好的体验。

再看另外一种情况:

    let mut v = vec![1, 2, 3, 4, 5];let first = &v[0];v.push(6);println!("The first element is: {first}");

在编译这段代码时会报错:

$ cargo runCompiling collections v0.1.0 (file:///projects/collections)
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable--> src/main.rs:6:5|
4 |     let first = &v[0];|                  - immutable borrow occurs here
5 |
6 |     v.push(6);|     ^^^^^^^^^ mutable borrow occurs here
7 |
8 |     println!("The first element is: {first}");|                                     ------- immutable borrow later used hereFor more information about this error, try `rustc --explain E0502`.
error: could not compile `collections` (bin "collections") due to 1 previous error

编译器告诉我们,因为 first 这里用的是 v 的不可变引用,而下面尝试调用 push 方法时的 v 应该是可变变量,而在其后又希望再继续使用 first 这样的操作是不被允许的,其实当我们注释掉最后一行的打印这段代码也是没问题的。

那么为什么呢?这个看起来是之前我们学习引用那块学习到的一个规则 “不能在相同作用域中同时存在可变和不可变引用”

这里就涉及到 vector 的工作原理,vector 的结尾增加新元素时,在没有足够空间将所有元素依次相邻存放的情况下,可能会要求分配新内存并将老的元素拷贝到新的空间中。这时,第一个元素的引用就指向了被释放的内存

2.3. 遍历 vector

Rust 提供了非常便利的遍历方式,无需我们自己通过索引一个个的访问:

    let v = vec![32, 26, 48];for i in &v {println!("{i}");}

也可以遍历过程中使用元素的值:

    let mut v = vec![32, 26, 48];for i in &mut v {*i += 50;}

修改可变引用所指向的值,需要在使用 += 运算符之前必须使用解引用运算符(*)获取 i 中的值。

2.4. 使用枚举来储存多种类型

上文提到了 vector 只能够存储同一类型的值,不过这一点概念是相对的。还记得之前我们学习过一种叫做枚举的类型吗?

没错!你知道我想说什么了,是的,枚举变量中可以包含各种各样的类型,而对外这一个整体只会被认作同一个类型那就是枚举本身。

    enum SpreadsheetCell {Int(i32),Float(f64),Text(String),}let row = vec![SpreadsheetCell::Int(3),SpreadsheetCell::Text(String::from("blue")),SpreadsheetCell::Float(10.12),];

这样一来 row 的类型就是 Vec<enum SpreadsheetCell>,再配合 match,我们就可以利用同一个 vector 存储不同类型的值:

	enum SpreadsheetCell {Int(i32),Float(f64),Text(String),}let row  = vec![SpreadsheetCell::Int(3),SpreadsheetCell::Text(String::from("blue")),SpreadsheetCell::Float(10.12),];for i in &row {match i {SpreadsheetCell::Int(i) => println!("{}", i),SpreadsheetCell::Float(f) => println!("{}", f),SpreadsheetCell::Text(s) => println!("{}", s),}}

同样的当 vector 变量离开自己所属的作用域时将会被丢弃,所有的内容将会清空,同时变量本身也会清理变得无效。

3. String

这一小节是本文的重头戏,请各位同学打起精神仔细阅读本小节。

其实之前我们已经使用过 String 类型,因此它对于我们并不算陌生,而 String 类型在初学者一路学习过来很容易与另一个类型 str 搞混,这个类型值之前有解释过,strString 中的一部分值的引用,这是两种类型。

RustString 类型实际上是对 Vec<u8> 类型的封装实现的。且字符串是 UTF-8 编码的。

3.1. 新建字符串

使用 new 方法创建一个空的字符串。

    let mut s = String::new();

有两种方式可以让我们为空的字符串填充数据:

  • to_string() 方法;
  • String::from() 函数。
    let data = "initial contents";let s = data.to_string();// 效果与上行代码相同//let s = String::from("initial contents");// 该方法也可直接用于字符串字面值:let s = "initial contents".to_string();

正因为 String 类型是 UTF-8 编码的,因此可以使用任何符合 UTF-8 编码的数据:

    let hello = String::from("السلام عليكم");let hello = String::from("Dobrý den");let hello = String::from("Hello");let hello = String::from("שלום");let hello = String::from("नमस्ते");let hello = String::from("こんにちは");let hello = String::from("안녕하세요");let hello = String::from("你好");let hello = String::from("Olá");let hello = String::from("Здравствуйте");let hello = String::from("Hola");let hello = String::from("🌟");

3.2. 更新字符串

String 的大小可以增加,其内容也可以改变。可以方便的使用 + 运算符或 format! 宏来拼接 String 值。
[注]:还记得吧,带有 ! 表示一个宏。忘了的同学罚款3000w。

可以通过 push_str 函数为 String 类型追加 str 类型而使得 String 变长。

    let mut s = String::from("hello,");s.push_str("world!");

执行这两行代码之后,s 将会包含 hello,world!push_str 方法采用字符串 slice,因为该方法并不需要获取参数的所有权。

    let mut s1 = String::from("hello,");let s2 = "world!";s1.push_str(s2);println!("s2 is {s2}");

例如,这里希望在将 s2 的内容附加到 s1 之后还能使用它。是的,由于 push_str 使用的字符串 slice,还记得吧,str 只是 String 内容的一部分的引用,是的它是引用,因此不会获取所有权,因此这里在后面是可以继续用 s2

还可以通过使用 push 方法为末尾添加一个字符。

    let mut s = String::from("Ma");s.push('x');

通过使用 + 组合两个字符串。

    let s1 = String::from("Hello, ");let s2 = String::from("world!");let s3 = s1 + &s2; // 注意 s1 被移动了,不能继续使用

执行完这些代码之后,字符串 s3 将会包含 Hello, world!s1 在相加后不再有效的原因,和使用 s2 的引用的原因,与使用 + 运算符时调用的函数签名有关。+ 运算符使用了 add 函数,这个函数签名看起来像这样:

fn add(self, s: &str) -> String {

在标准库中你会发现,add 的定义使用了泛型和关联类型。在这里我们替换为了具体类型,这也正是当使用 String 值调用这个方法会发生的。这个签名提供了理解 + 运算那微妙部分的线索。

首先,s2 使用了 &,意味着我们使用第二个字符串的 引用 与第一个字符串相加。这是因为 add 函数的 s 参数:只能将 String&str 相加,不能将两个 String 值相加。&s2 的类型是 &String, 而不是 add 第二个参数所指定的 &str。但为什么编译器没有报错呢?

是的,你想的没错,这里的确发生了隐式的强制类型转换,将 &String 转为 &str,这是 Rust 替我们完成的工作。而由于 s2 这里用的是引用,因此在 add 之后还是可以正常使用的。而这里的 s1 可就惨了,被 add 获取的所有权,在 add 之后就不能在使用了。

再来看 format! 这个宏的使用:

    let s1 = String::from("tic");let s2 = String::from("tac");let s3 = String::from("toe");let s = format!("{s1}-{s2}-{s3}");

这些代码也会将 s 设置为 “tic-tac-toe”format!println! 的工作原理相同,不过不同于将输出打印到屏幕上,它返回一个带有结果内容的 String

3.3. 字符串的内部结构

3.3.1. 字符串如何访问内部元素?

在大多数编程语言中,通过索引来引用字符串中的单独字符是有效且常见的操作。然而在 Rust 中,如果你尝试使用索引语法访问 String 的一部分,会出现一个错误。

    let s1 = String::from("hello");let h = s1[0];

这样的访问编译器将会报错:

$ cargo runCompiling collections v0.1.0 (file:///projects/collections)
error[E0277]: the type `str` cannot be indexed by `{integer}`--> src/main.rs:3:16|
3 |     let h = s1[0];|                ^ string indices are ranges of `usize`|= help: the trait `SliceIndex<str>` is not implemented for `{integer}`, which is required by `String: Index<_>`= note: you can use `.chars().nth()` or `.bytes().nth()`for more information, see chapter 8 in The Book: <https://doc.rust-lang.org/book/ch08-02-strings.html#indexing-into-strings>= help: the trait `SliceIndex<[_]>` is implemented for `usize`= help: for that trait implementation, expected `[_]`, found `str`= note: required for `String` to implement `Index<{integer}>`For more information about this error, try `rustc --explain E0277`.
error: could not compile `collections` (bin "collections") due to 1 previous error

错误和提示说明了全部问题:Rust 的字符串不支持索引。那么接下来的问题是,为什么不支持呢?为了回答这个问题,我们必须先聊一聊 Rust 是如何在内存中储存字符串的。

上文提到了 String 其实是一个 Vec<u8> 的封装,让我们来看一个例子:

    let hello = String::from("Hello");

在这里,len 的值是 5,这意味着储存字符串 “Hello” 的 Vec 的长度是五个字节:这里每一个字母的 UTF-8 编码都占用一个字节。那下面这个例子又如何呢?(注意这个字符串中的首字母是西里尔字母的 Ze 而不是数字 3。)

    let hello = String::from("Здравствуйте");

当问及这个字符是多长的时候有人可能会说是 12。然而,Rust 的回答是 24。这是使用 UTF-8 编码 “Здравствуйте” 所需要的字节数,这是因为每个 Unicode 标量值需要两个字节存储。因此一个字符串字节值的索引并不总是对应一个有效的 Unicode 标量值。

let hello = "Здравствуйте";
let answer = &hello[0];

[注]:这段代码无法编译通过,在这里仅为说明问题。

我们已经知道 answer 不是第一个字符 3。当使用 UTF-8 编码时,(西里尔字母的 ZeЗ 的第一个字节是 208,第二个是 151,所以 answer 实际上应该是 208,不过 208 自身并不是一个有效的字母。返回 208 可不是一个请求字符串第一个字母的人所希望看到的,不过它是 Rust 在字节索引 0 位置所能提供的唯一数据。用户通常不会想要一个字节值被返回。即使这个字符串只有拉丁字母,如果 &"hello"[0] 是返回字节值的有效代码,它也会返回 104 而不是 h

为了避免返回意外的值并造成不能立刻发现的 bugRust 根本不会编译这些代码,并在开发过程中及早杜绝了误会的发生。

3.3.2. 字节、标量值和字形簇

这引起了关于 UTF-8 的另外一个问题:从 Rust 的角度来讲,事实上有三种相关方式可以理解字符串:字节标量值字形簇(最接近人们眼中字符的概念)。
比如这个用梵文书写的印度语单词 “नमस्ते”,最终它储存在 vector 中的 u8 值看起来像这样:

[224, 164, 168, 224, 164, 174, 224, 164, 184, 224, 165, 141, 224, 164, 164,
224, 165, 135]

这里有 18 个字节,也就是计算机最终会储存的数据。如果从 Unicode 标量值的角度理解它们,也就像 Rustchar 类型那样,这些字节看起来像这样:

['न', 'म', 'स', '्', 'त', 'े']

这里有六个 char,不过第四个和第六个都不是字母,它们是发音符号本身并没有任何意义。最后,如果以字形簇的角度理解,就会得到人们所说的构成这个单词的四个字母:

["न", "म", "स्", "ते"]

Rust 提供了多种不同的方式来解释计算机储存的原始字符串数据,这样程序就可以选择它需要的表现方式,而无所谓是何种人类语言。

最后一个 Rust 不允许使用索引获取 String 字符的原因是,索引操作预期总是需要常数时间(O(1))。但是对于 String 不可能保证这样的性能,因为 Rust 必须从开头到索引位置遍历来确定有多少有效的字符。

3.4. 字符串 slice

索引字符串通常是一个坏点子,因为字符串索引应该返回的类型是不明确的:字节值、字符、字形簇或者字符串 slice。因此,如果你真的希望使用索引创建字符串 slice 时,Rust 会要求你更明确一些。为了更明确索引并表明你需要一个字符串 slice,相比使用 [] 和单个值的索引,可以使用 [] 和一个 range 来创建含特定字节的字符串 slice

let hello = "Здравствуйте";let s = &hello[0..4];

这里,s 会是一个 &str,它包含字符串的头四个字节。早些时候,我们提到了这些字母都是两个字节长的,所以这意味着 s 将会是 “Зд”

如果获取 &hello[0..1] 会发生什么呢?答案是,在运行中 Rust 将会报错,不允许将一个编码的字符拆开输出,因为没有与之对应的字符。

3.5. 字符串遍历

操作字符串每一部分的最好的方法是明确表示需要字符还是字节。对于单独的 Unicode 标量值使用 chars 方法。对 “Зд” 调用 chars 方法会将其分开并返回两个 char 类型的值,接着就可以遍历其结果来访问每一个元素了:

for c in "Зд".chars() {println!("{c}");
}

运行结果:

З
д

另外 bytes 方法返回每一个原始字节,这可能会适合你的使用场景:

for b in "Зд".bytes() {println!("{b}");
}

这些代码会打印出组成 String4 个字节:

208
151
208
180

总而言之,字符串还是很复杂的。不同的语言选择了不同的向程序员展示其复杂性的方式。Rust 选择了以准确的方式处理 String 数据作为所有 Rust 程序的默认行为,这意味着程序员们必须更多的思考如何预先处理 UTF-8 数据。这种权衡取舍相比其他语言更多的暴露出了字符串的复杂性,不过也使你在开发周期后期免于处理涉及非 ASCII 字符的错误。

好消息是标准库提供了很多围绕 String&str 构建的功能,来帮助我们正确处理这些复杂场景。请务必查看这些使用方法的文档,例如 contains 来搜索一个字符串,和 replace 将字符串的一部分替换为另一个字符串。

好了,本文最繁杂的部分已经结束了,接下来将会非常轻松的进行。

4. Hash Map

哈希 map(hash map)HashMap<K, V> 类型储存了一个键类型 K 对应一个值类型 V 的映射。它通过一个 哈希函数(hashing function)来实现映射,决定如何将键和值放入内存中。

在很多变成语言都支持 map 这个数据结构,因此也不算陌生。

4.1. 新建一个 Hash Map

使用 new 创建一个空的 HashMap,并使用 insert 增加元素。这里我们记录两支队伍的分数,分别是蓝队和黄队。蓝队开始有 10 分而黄队开始有 50 分:

    use std::collections::HashMap;let mut scores = HashMap::new();scores.insert(String::from("Blue"), 10);scores.insert(String::from("Yellow"), 50);

注意必须首先 use 标准库中集合部分的 HashMap。像 vector 一样,哈希 map 将它们的数据储存在堆上,这个 HashMap 的键类型是 String 而值类型是 i32

4.2. 获取 map 中值

通过 get 方法根据 关键字 可以获取对应的

    use std::collections::HashMap;let mut scores = HashMap::new();scores.insert(String::from("Blue"), 10);scores.insert(String::from("Yellow"), 50);let team_name = String::from("Blue");let score = scores.get(&team_name).copied().unwrap_or(0);

这里,score 是与蓝队分数相关的值,应为 10get 方法返回 Option<&V>,如果某个键在哈希 map 中没有对应的值,get 会返回 None

程序中通过调用 copied 方法来获取一个 Option<i32> 而不是 Option<&i32>,接着调用 unwrap_orscores 中没有该键所对应的项时将其设置为零。

可以使用与 vector 类似的方式来遍历哈希 map 中的每一个键值对,也就是 for 循环:

    use std::collections::HashMap;let mut scores = HashMap::new();scores.insert(String::from("Blue"), 10);scores.insert(String::from("Yellow"), 50);for (key, value) in &scores {println!("{key}: {value}");}

这会以任意顺序打印出每一个键值对:

Yellow: 50
Blue: 10

4.4. 哈希 map 和所有权

对于像 i32 这样的实现了 Copy trait 的类型,其值可以拷贝进哈希 map。对于像 String 这样拥有所有权的值,其值将被移动而哈希 map 会成为这些值的所有者:

    use std::collections::HashMap;let field_name = String::from("Favorite color");let field_value = String::from("Blue");let mut map = HashMap::new();map.insert(field_name, field_value);// 这里 field_name 和 field_value 不再有效,// 尝试使用它们看看会出现什么编译错误!

insert 调用将 field_namefield_value 移动到哈希 map 中后,将不能使用这两个绑定。

如果将值的引用插入哈希 map,这些值本身将不会被移动进哈希 map。但是这些引用指向的值必须至少在哈希 map 有效时也是有效的。

4.5. 覆盖关键字的值

当插入了一个键值对,接着用相同的键插入一个不同的值,与这个键相关联的旧值将被替换。

    use std::collections::HashMap;let mut scores = HashMap::new();scores.insert(String::from("Blue"), 10);scores.insert(String::from("Blue"), 25);println!("{scores:?}");

这会打印出 {"Blue": 25}。原始的值 10 则被覆盖了。

4.6. 当关键字不存在时才插入键值对

我们经常会检查某个特定的键是否已经存在于哈希 map 中并进行如下操作:如果哈希 map 中键已经存在则不做任何操作。如果不存在则连同值一块插入。

    use std::collections::HashMap;let mut scores = HashMap::new();scores.insert(String::from("Blue"), 10);scores.entry(String::from("Yellow")).or_insert(50);scores.entry(String::from("Blue")).or_insert(50);println!("{scores:?}");

Entryor_insert 方法在键对应的值存在时就返回这个值的可变引用如果不存在则将参数作为新值插入并返回新值的可变引用。这比编写自己的逻辑要简明的多,另外也与借用检查器结合得更好。

运行结果会打印出 {"Yellow": 50, "Blue": 10}。第一个 entry 调用会插入黄队的键和值 50,因为黄队并没有一个值。第二个 entry 调用不会改变哈希 map 因为蓝队已经有了值 10

4.7. 根据旧值更新一个值

另一个常见的哈希 map 的应用场景是找到一个键对应的值并根据旧的值更新它。计数一些文本中每一个单词分别出现了多少次:

    use std::collections::HashMap;let text = "hello world wonderful world";let mut map = HashMap::new();for word in text.split_whitespace() {let count = map.entry(word).or_insert(0);*count += 1;}println!("{map:?}");

这会打印出 {"world": 2, "hello": 1, "wonderful": 1}。你可能会看到相同的键值对以不同的顺序打印:回忆一下“获取 map 中的值”部分中提到遍历哈希 map 会以任意顺序进行。

其中 split_whitespace 方法返回一个由空格分隔 text 值子 slice 的迭代器。or_insert 方法返回这个键的值的一个可变引用(&mut V)。这里我们将这个可变引用储存在 count 变量中,所以为了赋值必须首先使用星号(*)解引用 count。这个可变引用在 for 循环的结尾离开作用域,这样所有这些改变都是安全的并符合借用规则。


下一篇《Rust语言基础17》


觉得这篇文章对你有帮助的话,就留下一个赞吧v*
请尊重作者,转载还请注明出处!感谢配合~
[作者]: Imagine Miracle
[版权]: 本作品采用知识共享署名-非商业性-相同方式共享 4.0 国际许可协议进行许可。
[本文链接]: https://blog.csdn.net/qq_36393978/article/details/146397162

相关文章:

【Rust】集合的使用——Rust语言基础16

文章目录 1. 前言2. Vector2.1. 构建一个 vector2.2. 获取 vector 中的元素2.3. 遍历 vector2.4. 使用枚举来储存多种类型 3. String3.1. 新建字符串3.2. 更新字符串3.3. 字符串的内部结构3.3.1. 字符串如何访问内部元素&#xff1f;3.3.2. 字节、标量值和字形簇 3.4. 字符串 s…...

Kafka集成Debezium监听postgresql变更

下载postgres的插件&#xff1a;https://debezium.io/documentation/reference/2.7/install.html 2.7版本支持postgresql12数据库。 debezium-connector-postgres-2.7.4.Final-plugin.tar.gz 上传插件并解压 mkdir /usr/local/kafka/kafka_2.12-2.2.1/connector cd /usr/local…...

自动学习和优化过程,实现更加精准的预测和决策的智慧交通开源了

智慧交通视觉监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒&#xff0c;省去繁琐重复的适配流程&#xff0c;实现芯片、算法、应用的全流程组合&#xff0c;从而大大减少企业级应用约95%的开发成本。通过高效的实时视…...

第2.2节 Android Jacoco插件覆盖率采集

JaCoCo&#xff08;Java Code Coverage&#xff09;是一款开源的代码覆盖率分析工具&#xff0c;适用于Java和Android项目。它通过插桩技术统计测试过程中代码的执行情况&#xff0c;生成可视化报告&#xff0c;帮助开发者评估测试用例的有效性。在github上开源的项目&#xff…...

从零开始:使用 Cython + JNI 在 Android 上运行 Python 算法

1. 引言 在 Android 设备上运行 Python 代码通常面临性能、兼容性和封装等挑战。尤其是当你希望在 Android 应用中使用 Python 编写的计算密集型算法时&#xff0c;直接运行 Python 代码可能导致较高的 CPU 占用和较差的性能。为了解决这个问题&#xff0c;我们可以使用 Cytho…...

开源软件许可证冲突的原因和解决方法

1、什么是开源许可证以及许可证冲突产生的问题 开源软件许可证是一种法律文件&#xff0c;它规定了软件用户、分发者和修改者使用、复制、修改和分发开源软件的权利和义务。开源许可证是由软件的版权所有者&#xff08;通常是开发者或开发团队&#xff09;发布的&#xff0c;它…...

stratis,容器podman

一、stratis 1.stratis可以实现动态的在线扩容&#xff0c;lvm虽然也可以实现在线扩容&#xff0c;但是是需要人为的手动扩容。 2.stratis不需要手动格式化&#xff0c;自动会创建文件系统&#xff08;默认是xfs&#xff09; 1. 安装stratis软件包 yum list | grep stratis…...

解决用three.js展示n个叠加的stl模型文件错位的问题

加载stl时可以明显看到下面有一部分模型是错位的。 将stl文件格式转化为glb 使用免费将 STL 转换为 GLB - ImageToStl 模型就没有错位了 代码如下 <template><div ref"threeContainer" class"three-container"></div></template&…...

从零开始实现 C++ TinyWebServer 数据库连接池 SqlConnectPool详解

文章目录 数据库连接池是什么&#xff1f;Web Server 中为什么需要数据库连接池&#xff1f;SqlConnectPool 成员变量实现 Init() 函数实现 ClosePool() 函数SqlConnectRAII 类SqlConnectPool 代码SqlConnectPool 测试 从零开始实现 C TinyWebServer 项目总览 项目源码 数据库连…...

利用ffmpeg库实现音频AAC编解码

AAC‌&#xff08;Advanced Audio Coding&#xff09;是一种音频编码技术&#xff0c;出现于1997年&#xff0c;基于MPEG-2的音频编码技术。AAC具有高效的数据压缩能力和较高的音质&#xff0c;适用于各种音频应用场景。例如&#xff0c;在智能设备中&#xff0c;AAC技术被广泛…...

Vue + CSS实现渐变栅格进度条

进度条作为可视化大屏系统中展示数据状态的关键元素&#xff0c;其视觉效果直接影响用户的使用体验&#xff0c;而传统的进度条往往呈现出固定的样式&#xff0c;缺乏视觉吸引力。在这种场景下&#xff0c;一种基于Vue和CSS实现渐变栅格进度条的方法应运而生&#xff0c;该方法…...

算法模型从入门到起飞系列——背包问题(探索最大价值的掘金之旅)

文章目录 前言一、背包问题溯源&#xff08;动态规划&#xff09;1.1 动态规划的概念1.2 动态规划的基本步骤1.3 动态规划的实际应用 二、背包问题2.1 背包问题衍生2.2 0-1背包2.2.1 0-1背包描述2.2.2 0-1背包图解2.2.3 0-1背包代码刨析 2.3 完全背包2.3.1 完全背包描述2.3.2 完…...

蓝桥杯—迷宫(bfs)

一.题目 分析:最短路径问题&#xff0c;给定一个迷宫&#xff0c;从左上角走到右下角&#xff0c;要求路径最短&#xff0c;并且要求字典序最小&#xff0c;也就是按照D&#xff0c;L&#xff0c;R&#xff0c;U&#xff0c;的搜索顺序去搜索&#xff0c;否则路径不是唯一的&am…...

【Android】安卓 Java下载ZIP文件并解压(笔记)

写在前面的话 在这篇文章中&#xff0c;我们将详细讲解如何在 Android 中通过 Java 下载 ZIP 文件并解压&#xff0c;同时处理下载进度、错误处理以及优化方案。 以下正文 1.权限配置 在 AndroidManifest.xml 中&#xff0c;我们需要添加相应的权限来确保应用能够访问网络和设…...

清晰易懂的 PHP 安装与配置教程

初学者也能看懂的 PHP 安装与配置教程 本教程将手把手教你如何在 Windows 系统上安装 PHP&#xff0c;并配置 Composer&#xff08;PHP 的依赖管理工具&#xff09;的缓存位置&#xff0c;即使你是零基础小白&#xff0c;也能轻松完成&#xff01; 一、准备工作 操作系统&…...

Ceph集群2025(Squid版)快速对接K8S cephFS文件存储

ceph的块存储太简单了。所以不做演示 查看集群 创建一个 CephFS 文件系统 # ceph fs volume create cephfs01 需要创建一个子卷# ceph fs subvolume create cephfs01 my-subvol -----------------#以下全部自动创建好 # ceph fs ls name: cephfs01, metadata pool: c…...

Linux进程控制(四)之进程程序替换

文章目录 进程程序替换单进程版程序替换替换原理多进程版程序替换替换函数函数解释小知识 命名理解 进程程序替换 如果要让子进程执行与父进程完全不同的代码&#xff0c;就要进行进程程序替换。 单进程版程序替换 执行一个可执行文件 makefile mycommand:mycommand.cgcc -…...

python-selenium 爬虫 由易到难

本质 python第三方库 selenium 空值 浏览器驱动 浏览器驱动控制浏览器 推荐 edge 浏览器驱动&#xff08;不容易遇到版本或者兼容性的问题&#xff09; 驱动下载网址&#xff1a;链接: link 1、实战1 &#xff08;1&#xff09;安装 selenium 库 pip install selenium&#…...

希尔排序

希尔排序是一种改进的插入排序算法&#xff0c;它通过将原始数据分成多个子序列来改善插入排序的性能&#xff0c;每个子序列的元素间隔为 d&#xff08;增量&#xff09;。随着算法的进行&#xff0c;d 逐渐减小&#xff0c;最终减为 1&#xff0c;此时整个序列就被排序好了。…...

Pydantic Mixin:构建可组合的验证系统体系

title: Pydantic Mixin:构建可组合的验证系统体系 date: 2025/3/22 updated: 2025/3/22 author: cmdragon excerpt: Pydantic的Mixin模式通过继承组合实现校验逻辑复用,遵循以Mixin后缀命名、不定义初始化方法等设计原则。支持基础校验模块化封装与多策略组合,如电话号码…...

策略模式 vs. 工厂模式:对比与分析

相同点 解耦思想 两者都通过接口/抽象类将实现与调用方解耦&#xff0c;降低模块间的直接依赖。 符合开闭原则 新增策略或产品时&#xff0c;只需扩展新类&#xff0c;无需修改已有代码。 封装变化 策略模式封装算法的变化&#xff0c;工厂模式封装对象创建的变化。 不同…...

RK3568 I2C底层驱动详解

前提须知&#xff1a;I2C协议不懂的话就去看之前的内容吧&#xff0c;这个文章需要读者一定的基础。 RK3568 I2C 简介 RK3568 支持 6 个独立 I2C: I2C0、I2C1、I2C2、I2C3、I2C4、I2C5。I2C 控制器支持以下特性: ① 兼容 i2c 总线 ② AMBA APB 从接口 ③ 支持 I2C 总线主模式…...

【大语言模型_8】vllm启动的模型通过fastapi封装增加api-key验证

背景&#xff1a; vllm推理框架启动模型不具备api-key验证。需借助fastapi可以实现该功能 代码实现&#xff1a; rom fastapi import FastAPI, Header, HTTPException, Request,Response import httpx import logging# 创建 FastAPI 应用 app FastAPI() logging.basicConfig(…...

hadoop-HDFS操作

1. 使用的是hadoop的用户登录到系统&#xff0c;那么 cd ~ 是跳转到/home/hadoop下。 2. 在操作hdfs时&#xff0c;需要在hadoop用户下的/usr/local/hadoop&#xff0c;此时是在根目录下。 cd /usr/local/hadoop或者cd / cd usr/local/hadoop 3. 回到Linux的操作目录 我们把…...

Mysql 安装教程和Workbench的安装教程以及workbench的菜单栏汉化

Mysql 安装教程和Workbench的安装教程 详细请参考我的文件 Mysql 安装教程和Workbench的安装教程 或者下载我的资源Mysql 安装教程和Workbench的安装教程 汉化菜单 英文版菜单文件&#xff1a;下载链接 汉化版菜单文件&#xff1a;下载链接 默认情况下&#xff0c;安…...

失物招领|校园失物招领系统|基于Springboot的校园失物招领系统设计与实现(源码+数据库+文档)

校园失物招领系统目录 目录 基于Springboot的校园失物招领系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、 管理员功能实现 (1) 失物招领管理 (2) 寻物启事管理 (3) 公告管理 (4) 公告类型管理 2、用户功能实现 (1) 失物招领 (2) 寻物启事 (3) 公告 …...

一条不太简单的TEX学习之路

目录 rule raisebox \includegraphics newenviro 、\vspace \stretch \setlength 解释&#xff1a; 总结&#xff1a; 、\linespread newcommand \par 小四 \small simple 、mutiput画网格 解释&#xff1a; 图案解释&#xff1a; xetex pdelatex etc index 报…...

如何为AI开发选择合适的服务器?

选择适合的服务器可以为您的AI项目带来更高的效率&#xff0c;确保最佳性能、可扩展性和可靠性&#xff0c;从而实现无缝的开发与部署。 选择适合的AI开发服务器可能并不容易。您需要一台能够处理大量计算和大型数据集的服务器&#xff0c;同时它还需要符合您的预算并易于管理…...

doris:审计日志

Doris 提供了对于数据库操作的审计能力&#xff0c;可以记录用户对数据库的登陆、查询、修改操作。在 Doris 中&#xff0c;可以直接通过内置系统表查询审计日志&#xff0c;也可以直接查看 Doris 的审计日志文件。 开启审计日志​ 通过全局变量 enable_audit_plugin 可以随时…...

CSS中的transition与渐变

目录 一、CSS transition 1. 核心属性 简写语法 2. 子属性详解 2.1 transition-property 2.2 transition-duration 2.3 transition-timing-function 2.4 transition-delay 3. 使用场景示例 3.1 悬停效果&#xff08;Hover&#xff09; 3.2 展开/收起动画 3.3 动态移…...

AI + 医疗 Qwq大模型离线本地应用

通义千问Qwq-32b-FP16可用于社区医院、乡镇卫生院、诊所等小型医疗机构&#xff0c;替代专业合理用药系统&#xff0c;作为药品知识库&#xff0c;实现以下功能&#xff1a; 药品信息智能查询&#xff1a;检索药品的详细说明书、适应症、禁忌症、不良反应及药物相互作用等关键信…...

大数据环境搭建

目录 一&#xff1a;虚拟机:VirtualBox 二&#xff1a;Shell工具:MobaXterm 三&#xff1a;安装脚本 四&#xff1a;JDK和Hadoop 4.1&#xff1a;安装 4.2&#xff1a;启动 4.3&#xff1a;Hadoop可视化访问 4.4&#xff1a;关机 一&#xff1a;虚拟机:VirtualBox Virt…...

七天免登录 为什么不能用seesion,客户端的http请求自动携带cookei的机制(比较重要)涉及HTTP规范

如果是七天免登录,和session肯定没关系,因为session不能持久化,主要是客户端一旦关闭,seesion就失效了/// 所以必须是能持久化的&#xff0c;这就清晰了&#xff0c;要莫在的服务器保存&#xff0c;要摸在客户端设置 cook机制 1. 使用Cookie实现七天免登录 前端&#xff08;登…...

从PGC到AIGC:海螺AI多模态内容生成系统的技术革命

一、内容生产的范式迁移&#xff1a;从PGC到AIGC的进化之路 在数字内容生产的历史长河中&#xff0c;人类经历了三次重大范式转变&#xff1a;专业生成内容&#xff08;PGC&#xff09;的工业化生产、用户生成内容&#xff08;UGC&#xff09;的全民创作浪潮&#xff0c;以及当…...

常考计算机操作系统面试习题(三上)

目录 1. 为何要引入与设备的无关性&#xff1f;如何实现设备的独立性&#xff1f; 2. 页面置换先进先出算法 3. 页面置换先进先出算法&#xff0c;4个页框 4. 进程优先级调度算法 5. 短作业优先调度策略 6. 平均内存访问时间计算 7. 页式存储和段式存储的物理地址计算 …...

数据结构之双向链表-初始化链表-头插法-遍历链表-获取尾部结点-尾插法-指定位置插入-删除节点-释放链表——完整代码

数据结构之双向链表-初始化链表-头插法-遍历链表-获取尾部结点-尾插法-指定位置插入-删除节点-释放链表——完整代码 #include <stdio.h> #include <stdlib.h>typedef int ElemType;typedef struct node{ElemType data;struct node *next, *prev; }Node;//初化链表…...

一键部署 GPU Kind 集群,体验 vLLM 极速推理

随着 Kubernetes 在大模型训练和推理领域的广泛应用&#xff0c;越来越多的开发者需要在本地环境中搭建支持 GPU 的 Kubernetes 集群&#xff0c;以便进行测试和开发。大家都知道&#xff0c;本地搭建 Kubernetes 集群通常可以使用 Kind&#xff08;Kubernetes IN Docker&#…...

C/C++蓝桥杯算法真题打卡(Day6)

一、P8615 [蓝桥杯 2014 国 C] 拼接平方数 - 洛谷 方法一&#xff1a;算法代码&#xff08;字符串分割法&#xff09; #include<bits/stdc.h> // 包含标准库中的所有头文件&#xff0c;方便编程 using namespace std; // 使用标准命名空间&#xff0c;避免每次调用…...

【C++】入门

1.命名空间 1.1 namespace的价值 在C/C中&#xff0c;变量&#xff0c;函数和后面要学到的类都是大量存在的&#xff0c;这些变量&#xff0c;函数和类的名称将存在于全局作用域中&#xff0c;可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化&#xff0c;…...

CUDA 学习(2)——CUDA 介绍

GeForce 256 是英伟达 1999 年开发的第一个 GPU&#xff0c;最初用作显示器上渲染高端图形&#xff0c;只用于像素计算。 在早期&#xff0c;OpenGL 和 DirectX 等图形 API 是与 GPU 唯一的交互方式。后来&#xff0c;人们意识到 GPU 除了用于渲染图形图像外&#xff0c;还可以…...

构建自定义MCP天气服务器:集成Claude for Desktop与实时天气数据

构建自定义MCP天气服务器:集成Claude for Desktop与实时天气数据 概述 本文将指导开发者构建一个MCP(Model Control Protocol)天气服务器,通过暴露get-alerts和get-forecast工具,为Claude for Desktop等客户端提供实时天气数据支持。该方案解决了传统LLM无法直接获取天气…...

[Lc_2 二叉树dfs] 布尔二叉树的值 | 根节点到叶节点数字之和 | 二叉树剪枝

目录 1.计算布尔二叉树的值 题解 2.求根节点到叶节点数字之和 3. 二叉树剪枝 题解 1.计算布尔二叉树的值 链接&#xff1a;2331. 计算布尔二叉树的值 给你一棵 完整二叉树 的根&#xff0c;这棵树有以下特征&#xff1a; 叶子节点 要么值为 0 要么值为 1 &#xff0c;其…...

搜广推校招面经五十六

字节推荐算法 一、Attention的复杂度是多少&#xff1f; 见【搜广推校招面经三十八】 二、如何对普适性强的物品&#xff08;如新华字典&#xff09;设计指标进行降权 2.1. 问题背景 普适性强的物品&#xff08;如新华字典&#xff09;在推荐系统或搜索排序中可能频繁出现…...

ZYNQ的cache原理与一致性操作

在Xilinx Zynq SoC中&#xff0c;Cache管理是确保处理器与外部设备&#xff08;如FPGA逻辑、DMA控制器&#xff09;之间数据一致性的关键。Zynq的ARM Cortex-A9处理器包含L1 Cache&#xff08;指令/数据&#xff09;和L2 Cache&#xff0c;其刷新&#xff08;Flush/Invalidate&…...

安装React开发者工具

我们在说组件之前&#xff0c;需要先安装一下React官方推出的开发者工具&#xff0c;首先我们分享在线安装方式 首先打开谷歌网上应用商店(针对谷歌浏览器)&#xff0c;在输入框内搜索react&#xff0c;安装如下插件&#xff1a; 注意安装提供方为Facebook的插件&#xff0c;这…...

多层感知机

多层感知机&#xff08;Multilayer Perceptron&#xff0c;简称 MLP&#xff09;是一种基于前馈神经网络&#xff08;Feedforward Neural Network&#xff09;的深度学习模型&#xff0c;由多个神经元层组成&#xff0c;每一层与前一层全连接。它包括至少一个隐藏层&#xff08…...

2025年渗透测试面试题总结- PingCAP安全工程师(题目+回答)

网络安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 PingCAP安全工程师 一、SQL注入判断数据库类型技术分析 1. 常规判断方法 2. 盲注场景下的判断 3. 补…...

CAD模型导入Geant4

CADMesh是一个开源项目&#xff0c;专门用于将STL格式的CAD模型导入Geant4。以下是使用CADMesh操作STL模型的步骤&#xff1a; 准备工作 下载CADMesh开源代码&#xff1a;可以从GitHub或Gitee下载CADMesh的开源代码。 将CAD模型转换为STL格式&#xff1a;在CAD软件中创建几何…...

DeepSORT 目标追踪算法详解

DeepSORT&#xff08;Deep Simple Online and Realtime Tracking&#xff09;是 多目标追踪&#xff08;MOT&#xff09; 领域的经典算法&#xff0c;通过结合目标检测、运动预测和外观特征匹配&#xff0c;实现了高效、稳定的实时追踪。其核心思想是通过 检测驱动追踪&#xf…...

mne溯源后的数据初步处理方法

文章目录 导入库 Yeo2011_7Networks_N1000绘制一些圆球来代表区域大小和强度 单网络绘制和扩展的方式AI补充一下背景知识&#x1f4da; **背景与研究来源**&#x1f9e0; **7 个功能网络的定义**&#x1f4c2; **标签数据获取**&#x1f50d; **标签文件内容解析**&#x1f6e0…...