pull/535/head
sunface 3 years ago
commit ba0323cdec

@ -52,7 +52,7 @@
站在巨人的肩膀上,能帮我们看的更远,特此感谢以下巨人:
- [Rust Book](https://doc.rust-lang.org/book)
- [Rust nomicon](https://doc.rust-lang.org/nomicon/dot-operator.html)
- [Rust nomicon](https://doc.rust-lang.org/nomicon/intro.html)
- [Async Rust](https://rust-lang.github.io/async-book/01_getting_started/01_chapter.html)
- 详细清单参见 [这里](./assets/writing-material/books.md)

@ -1,12 +1,12 @@
# KV 存储 HashMap
和动态数组一样,`HashMap` 也是 Rust 标准库中提供的集合类型,但是又与动态数组不同,`HashMap` 中存储的是一一映射的 `KV `键值对,并提供了平均复杂度为 `O(1)` 的查询方法,当我们希望通过一个 `Key` 去查询值时,该类型非常有用,以致于 Go 语言将该类型设置成了语言级别的内置特性。
和动态数组一样,`HashMap` 也是 Rust 标准库中提供的集合类型,但是又与动态数组不同,`HashMap` 中存储的是一一映射的 `KV` 键值对,并提供了平均复杂度为 `O(1)` 的查询方法,当我们希望通过一个 `Key` 去查询值时,该类型非常有用,以致于 Go 语言将该类型设置成了语言级别的内置特性。
Rust 中哈希类型(哈希映射)为 `HashMap<K,V>`,在其它语言中,也有类似的数据结构,例如 `hash map``map``object``hash table``字典` 等等,引用小品演员孙涛的一句台词:大家都是本地狐狸,别搁那装貂 :)。
## 创建 HashMap
跟创建动态数组 `Vec` 的方法类似,可以使用 `new` 方法来创建` HashMap`,然后通过` insert` 方法插入键值对。
跟创建动态数组 `Vec` 的方法类似,可以使用 `new` 方法来创建 `HashMap`,然后通过 `insert` 方法插入键值对。
#### 使用 new 方法创建
@ -24,7 +24,7 @@ my_gems.insert("河边捡的误以为是宝石的破石头", 18);
很简单对吧?跟其它语言没有区别,聪明的同学甚至能够猜到该 `HashMap` 的类型:`HashMap<&str,i32>`。
但是还有一点,你可能没有注意,那就是使用 `HashMap` 需要手动通过 `use ...` 从标准库中引入到我们当前的作用域中来,仔细回忆下,之前使用另外两个集合类型 `String` 和` Vec` 时,我们是否有手动引用过?答案是 `No`,因为 `HashMap` 并没有包含在 Rust 的 [`prelude`](../../appendix/prelude.md) 中Rust 为了简化用户使用,提前将最常用的类型自动引入到作用域中)。
但是还有一点,你可能没有注意,那就是使用 `HashMap` 需要手动通过 `use ...` 从标准库中引入到我们当前的作用域中来,仔细回忆下,之前使用另外两个集合类型 `String` `Vec` 时,我们是否有手动引用过?答案是 `No`,因为 `HashMap` 并没有包含在 Rust 的 [`prelude`](../../appendix/prelude.md) 中Rust 为了简化用户使用,提前将最常用的类型自动引入到作用域中)。
所有的集合类型都是动态的,意味着它们没有固定的内存大小,因此它们底层的数据都存储在内存堆上,然后通过一个存储在栈中的引用类型来访问。同时,跟其它集合类型一致,`HashMap` 也是内聚性的,即所有的 `K` 必须拥有同样的类型,`V` 也是如此。
@ -46,8 +46,8 @@ fn main() {
let teams_list = vec![
("中国队".to_string(), 100),
("美国队".to_string(),10),
("日本队".to_string(),50),
("美国队".to_string(), 10),
("日本队".to_string(), 50),
];
let mut teams_map = HashMap::new();
@ -69,8 +69,8 @@ fn main() {
let teams_list = vec![
("中国队".to_string(), 100),
("美国队".to_string(),10),
("日本队".to_string(),50),
("美国队".to_string(), 10),
("日本队".to_string(), 50),
];
let teams_map: HashMap<_,_> = teams_list.into_iter().collect();
@ -111,7 +111,7 @@ fn main() {
handsome_boys.insert(name, age);
println!("因为过于无耻,{}已经被从帅气男孩名单中除名", name);
println!("还有,他的真实年龄远远不止{}岁",age);
println!("还有,他的真实年龄远远不止{}岁", age);
}
```
@ -147,7 +147,7 @@ fn main() {
std::mem::drop(name);
println!("因为过于无耻,{:?}已经被除名", handsome_boys);
println!("还有,他的真实年龄远远不止{}岁",age);
println!("还有,他的真实年龄远远不止{}岁", age);
}
```
@ -183,7 +183,7 @@ let score: Option<&i32> = scores.get(&team_name);
上面有几点需要注意:
- `get` 方法返回一个 `Option<&i32> `类型:当查询不到时,会返回一个 `None`,查询到时返回 `Some(&i32)`
- `get` 方法返回一个 `Option<&i32>` 类型:当查询不到时,会返回一个 `None`,查询到时返回 `Some(&i32)`
- `&i32` 是对 `HashMap` 中值的借用,如果不使用借用,可能会发生所有权的转移
还可以通过循环的方式依次遍历 `KV` 对:
@ -297,6 +297,6 @@ hash.insert(42, "the answer");
assert_eq!(hash.get(&42), Some(&"the answer"));
```
> 目前,`HashMap` 使用的哈希函数是 `SipHash`,它的性能不是很高,但是安全性很高。`SipHash` 在中等大小的 `Key` 上,性能相当不错,但是对于小型的 `Key` (例如整数)或者大型 `Key` (例如字符串)来说,性能还是不够好。若你需要极致性能,例如实现算法,可以考虑这个库:[ahash](https://github.com/tkaitchuck/ahash)
> 目前,`HashMap` 使用的哈希函数是 `SipHash`,它的性能不是很高,但是安全性很高。`SipHash` 在中等大小的 `Key` 上,性能相当不错,但是对于小型的 `Key` (例如整数)或者大型 `Key` (例如字符串)来说,性能还是不够好。若你需要极致性能,例如实现算法,可以考虑这个库:[ahash](https://github.com/tkaitchuck/ahash)
最后,如果你想要了解 `HashMap` 更多的用法,请参见本书的标准库解析章节:[HashMap 常用方法](../../std/hashmap.md)

@ -10,4 +10,4 @@
最后,请用热烈的掌声迎接我们的 `String` 集合,哦,抱歉,`String` 集合天生低调,见不得前两个那样,因此被气走了,你可以去[这里](../compound-type/string-slice)找它。
言归正传,本章所讲的 `Vector`、`HashMap `再加上之前的 `String` 类型,是标准库中最最常用的集合类型,可以说,几乎任何一段代码中都可以找到它们的身影,那么先来看看 `Vector`
言归正传,本章所讲的 `Vector`、`HashMap` 再加上之前的 `String` 类型,是标准库中最最常用的集合类型,可以说,几乎任何一段代码中都可以找到它们的身影,那么先来看看 `Vector`

@ -1,6 +1,6 @@
# 动态数组 Vector
动态数组类型用`Vec<T>`表示,事实上,在之前的章节,它的身影多次出现,我们一直没有细讲,只是简单的把它当作数组处理。
动态数组类型用 `Vec<T>` 表示,事实上,在之前的章节,它的身影多次出现,我们一直没有细讲,只是简单的把它当作数组处理。
动态数组允许你存储多个值,这些值在内存中一个紧挨着另一个排列,因此访问其中某个元素的成本非常低。动态数组只能存储相同类型的元素,如果你想存储不同类型的元素,可以使用之前讲过的枚举类型或者特征对象。
@ -18,7 +18,7 @@
let v: Vec<i32> = Vec::new();
```
这里,`v` 被显式地声明了类型`Vec<i32>`,这是因为 Rust 编译器无法从 `Vec::new()` 中得到任何关于类型的暗示信息,因此也无法推导出 `v` 的具体类型,但是当你向里面增加一个元素后,一切又不同了:
这里,`v` 被显式地声明了类型 `Vec<i32>`,这是因为 Rust 编译器无法从 `Vec::new()` 中得到任何关于类型的暗示信息,因此也无法推导出 `v` 的具体类型,但是当你向里面增加一个元素后,一切又不同了:
```rust
let mut v = Vec::new();
@ -66,7 +66,10 @@ v.push(1);
## 从 Vector 中读取元素
读取指定位置的元素有两种方式可选:通过下标索引访问或者使用 `get` 方法:
读取指定位置的元素有两种方式可选:
- 通过下标索引访问。
- 使用 `get` 方法。
```rust
let v = vec![1, 2, 3, 4, 5];
@ -142,7 +145,7 @@ error: could not compile `collections` due to previous error
其实想想,**在长大之后,我们感激人生路上遇到过的严师益友,正是因为他们,我们才在正确的道路上不断前行,虽然在那个时候,并不能理解他们**,而 Rust 就如那个良师益友,它不断的在纠正我们不好的编程习惯,直到某一天,你发现自己能写出一次性通过的漂亮代码时,就能明白它的良苦用心。
> 若读者想要更深入的了解`Vec<T>`,可以看看[Rustonomicon](https://nomicon.purewhite.io/vec/vec.html),其中从零手撸一个动态数组,非常适合深入学习
> 若读者想要更深入的了解 `Vec<T>`,可以看看[Rustonomicon](https://nomicon.purewhite.io/vec/vec.html),其中从零手撸一个动态数组,非常适合深入学习
## 迭代遍历 Vector 中的元素
@ -228,4 +231,4 @@ fn main() {
在实际使用场景中,特征对象数组要比枚举数组常见很多,主要原因在于[特征对象](../trait/trait-object.md)非常灵活,而编译器对枚举的限制较多,且无法动态增加类型。
最后,如果你想要了解 `Vector `更多的用法,请参见本书的标准库解析章节:[`Vector`常用方法](../../std/vector.md)
最后,如果你想要了解 `Vector` 更多的用法,请参见本书的标准库解析章节:[`Vector`常用方法](../../std/vector.md)

@ -55,7 +55,7 @@ let world = &s[6..11];
<img alt="" src="https://pic1.zhimg.com/80/v2-69da917741b2c610732d8526a9cc86f5_1440w.jpg" class="center" style="width: 50%;" />
在使用 Rust 的 `..` [range 序列](https://course.rs/base-type/numbers.html#序列range)语法时,如果你想从索引 0 开始,可以使用如下的方式,这两个是等效的:
在使用 Rust 的 `..` [range 序列](https://course.rs/basic/base-type/numbers.html#序列range)语法时,如果你想从索引 0 开始,可以使用如下的方式,这两个是等效的:
```rust
let s = String::from("hello");
@ -429,4 +429,4 @@ for b in "中国人".bytes() {
> Rust By Practice支持代码在线编辑和运行并提供详细的[习题解答](https://github.com/sunface/rust-by-practice)。
> - [字符串](https://zh.practice.rs/compound-types/string.html)
> - [切片](https://zh.practice.rs/compound-types/slice.html)
> - [切片](https://zh.practice.rs/compound-types/slice.html)

@ -4,9 +4,9 @@
## 为何要手动设置变量的可变性?
在其它大多数语言中变量一旦创建要么是可变的要么是不可变的ClosureScript前者为编程提供了灵活性后者为编程提供了安全性而 Rust 比较野,选择了两者我都要,既要灵活性又要安全性。
在其它大多数语言中变量一旦创建要么是可变的要么是不可变的ClojureScript前者为编程提供了灵活性后者为编程提供了安全性而 Rust 比较野,选择了两者我都要,既要灵活性又要安全性。
能想要学习 Rust说明我们的读者都是相当有水平的程序员了你们应该能理解**一切选择皆是权衡**,那么两者都要的权衡是什么呢?这就是 Rust 开发团队为我们做出的贡献,两者都要意味着 Rust 语言底层代码的实现复杂度大幅提升,因此 Respect to The Rust Team!
能想要学习 Rust说明我们的读者都是相当有水平的程序员了你们应该能理解**一切选择皆是权衡**,那么两者都要的权衡是什么呢?这就是 Rust 开发团队为我们做出的贡献,两者都要意味着 Rust 语言底层代码的实现复杂度大幅提升,因此 Salute to The Rust Team!
除了以上两个优点,还有一个很大的优点,那就是运行性能上的提升,因为将本身无需改变的变量声明为不可变在运行期会避免一些多余的 `runtime` 检查。
@ -22,15 +22,15 @@
为何不用赋值而用绑定呢(其实你也可以称之为赋值,但是绑定的含义更清晰准确)?这里就涉及 Rust 最核心的原则——**所有权**,简单来讲,任何内存对象都是有主人的,而且一般情况下完全属于它的主人,绑定就是把这个对象绑定给一个变量,让这个变量成为它的主人(聪明的读者应该能猜到,在这种情况下,该对象之前的主人就会丧失对该对象的所有权),像极了我们的现实世界,不是吗?
至于为何要采用所有权这种复杂的东东,先别急,等时机合适,我们会为你详细道来
那为什么要引进“所有权”这个新的概念呢?请稍安勿躁,时机一旦成熟,我们就回来继续讨论这个话题
## 变量可变性
Rust 的变量在默认情况下是**不可变的**。在上文提到过,这是 Rust 团队为我们精心设计的语言特性之一,这样可以让我们编写更安全、更高性能的代码。当然你可以通过 `mut` 关键字让变量变为**可变的**,以实现更加灵活的设计
Rust 的变量在默认情况下是**不可变的**。前文提到,这是 Rust 团队为我们精心设计的语言特性之一,让我们编写的代码更安全,性能也更好。当然你可以通过 `mut` 关键字让变量变为**可变的**,让设计更灵活
当变量不可变时,这意味着一旦一个值绑定到一个变量 `a` 后,就不能再更改 `a` 的值了。为了说明,在我们的工程目录下使用 `cargo new variables` 来创建一个名为 _variables_ 的新项目
如果变量 `a` 不可变,那么一旦为它绑定值,就不能再修改 `a`。举个例子,在我们的工程目录下使用 `cargo new variables` 新建一个项目,叫做 _variables_
然后在新建的 _variables_ 目录下,打开 _src/main.rs_ 并将代码替换为下面还未能通过编译的代码:
然后在新建的 _variables_ 目录下,编辑 _src/main.rs_ ,改为下面代码:
```rust
fn main() {
@ -41,7 +41,7 @@ fn main() {
}
```
保存文件,并使用 `cargo run` 运行程序,你将会收到一条错误消息,输出如下所示:
保存文件,再使用 `cargo run` 运行它,迎面而来的是一条错误提示:
```console
$ cargo run
@ -61,13 +61,13 @@ error[E0384]: cannot assign twice to immutable variable `x`
error: aborting due to previous error
```
具体的错误原因是 `cannot assign twice to immutable variable x`对不可变的变量无法进行二次再赋值),因为我们尝试给不可变的 `x` 变量赋予了第二个值。
具体的错误原因是 `cannot assign twice to immutable variable x`无法对不可变的变量进行重复赋值),因为我们想为不可变的 `x` 变量再次赋值。
这种错误是为了避免无法预期的错误发生在我们的变量上:一个变量往往被多处代码所使用,其中一部分代码假定该变量的值永远不会改变,而另外一部分代码却无情的改变了这个值,在实际开发过程中,这个错误是很难被发现的,特别是在多线程编程中。
这种规则让我们的代码变得非常清晰,只有你想让你的变量改变时,它才能改变,这样就不会造成心智上的负担,也给别人阅读代码带来便利。
但是可变性也非常重要,否则我们就要像 ClosureScript 那样,每次要改变,就要重新生成一个对象,在拥有大量对象的场景,性能会变得非常低下,内存拷贝的成本异常的高。
但是可变性也非常重要,否则我们就要像 ClojureScript 那样,每次要改变,就要重新生成一个对象,在拥有大量对象的场景,性能会变得非常低下,内存拷贝的成本异常的高。
在 Rust 中,可变性很简单,只要在变量名前加一个 `mut` 即可, 而且这种显式的声明方式还会给后来人传达这样的信息:嗯,这个变量在后面代码部分会发生改变。

@ -24,7 +24,7 @@
2. 社区驱动的 `rust-analyzer`非常推荐上面说的所有问题在这个插件上都得到了解决不得不说Rust 社区 yyds!
所以,综上所述,我们选择 `rust-analyer` 作为 Rust 语言的插件,具体的安装很简单,点击插件,选择安装即可,根据提示可能需要重新加载 IDE。
所以,综上所述,我们选择 `rust-analyzer` 作为 Rust 语言的插件,具体的安装很简单,点击插件,选择安装即可,根据提示可能需要重新加载 IDE。
> 在搜索 VSCode 插件时,报错:`提取扩展出错XHR failed`,这个报错是因为网络原因导致,很可能是你的网络不行或者翻墙工具阻拦你的访问,试着关掉翻墙,再进行尝试。

@ -141,7 +141,7 @@ Rust 语言表达能力更强,性能更高。同时线程安全方面 Rust 也
与其它语言相比Rust 的更新迭代较为频繁(得益于精心设计过的发布流程以及 Rust 语言开发者团队的严格管理):
- 每 6 周发布一个迭代版本
- 2-3 年发布一个新的大版本Rust 2018 editionRust 2021 edtion
- 2-3 年发布一个新的大版本Rust 2018 editionRust 2021 edition
好处在于,可以满足不同的用户群体的需求:

@ -10,4 +10,6 @@
- [记一次 Rust 技术面试](https://zhuanlan.zhihu.com/p/411979704)
- [飞书 Rust 实习](https://blog.kuangjux.top/2021/10/22/飞书Rust实习面试/)
- [字节跳动 Rust/C++ 实习](https://www.nowcoder.com/discuss/538078)
- [字节跳动 Rust/C++ 实习](https://www.nowcoder.com/discuss/538078)
- [记一次面试](https://huangjj27.github.io/interview.html)
- [字节跳动面试经历](https://blog.sbw.so/u/byte-dance-rust-cpp-interview-experience.html)

@ -10,7 +10,7 @@
| 模块 Modules | `snake_case` |
| 类型 Types | `UpperCamelCase` |
| 特征 Traits | `UpperCamelCase` |
| 枚举 Emumerations | `UpperCamelCase` |
| 枚举 Enumerations | `UpperCamelCase` |
| 结构体 Structs | `UpperCamelCase` |
| 函数 Functions | `snake_case` |
| 方法 Methods | `snake_case` |

Loading…
Cancel
Save