陈天 · Rust 编程第一课
陈天
Tubi TV 研发副总裁
23195 人已学习
新⼈⾸单¥68
登录后,你可以任选4讲全文学习
课程目录
已完结/共 65 讲
基础篇 (21讲)
陈天 · Rust 编程第一课
15
15
1.0x
00:00/00:00
登录|注册

16|数据结构:Vec<T>、&[T]、Box<[T]> ,你真的了解集合容器么?

区别
优化技巧
懒接口
迭代器和切片的关系
Box<[T]>
&mut [T]
&[T]
Box<[T]>
&[T]
Vec
Rust 的 Iterator 究竟有多快?
开发新的 Iterator trait IteratorExt
&str 的 print_slice1 函数
切片的引用和堆上的切片
特殊的切片:&str
迭代器 Iterator
比喻
使用方式
描述
双向列表 LinkedList
循环缓冲区 VecDeque
切片 slice
哈希表 HashMap<K, V>
列表 Vec
数组 [T; n]
字符串 String
容器类型
原生类型
参考资料
思考题
切片
集合容器
Rust 中的数据结构
数据结构

该思维导图由 AI 生成,仅供参考

你好,我是陈天。今天来学集合容器。
现在我们接触到了越来越多的数据结构,我把 Rust 中主要的数据结构从原生类型、容器类型和系统相关类型几个维度整理一下,你可以数数自己掌握了哪些。
可以看到,容器占据了数据结构的半壁江山。
提到容器,很可能你首先会想到的就是数组、列表这些可以遍历的容器,但其实只要把某种特定的数据封装在某个数据结构中,这个数据结构就是一个容器。比如 Option<T>,它是一个包裹了 T 存在或不存在的容器,而 Cow 是一个封装了内部数据 B 或被借用或拥有所有权的容器。
对于容器的两小类,到目前为止,像 Cow 这样,为特定目的而产生的容器我们已经介绍了不少,包括 Box、Rc、Arc、RefCell、还没讲到的 Option 和 Result 等。
今天我们来详细讲讲另一类,集合容器。

集合容器

集合容器,顾名思义,就是把一系列拥有相同类型的数据放在一起,统一处理,比如:
我们熟悉的字符串 String、数组 [T; n]、列表 Vec<T> 和哈希表 HashMap<K, V> 等;
虽然到处在使用,但还并不熟悉的切片 slice;
在其他语言中使用过,但在 Rust 中还没有用过的循环缓冲区 VecDeque<T>、双向列表 LinkedList<T> 等。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

Rust中的集合容器包括数组、列表、切片和哈希表等,它们具有共性特点,如可遍历、进行map-reduce操作等。本文重点介绍了切片的概念和使用方法。切片是一组同一类型、长度不确定、在内存中连续存放的数据结构,可以通过`&[T]`和`&mut [T]`表示只读和可写的切片引用,以及`Box<[T]>`表示在堆上分配的切片。切片可以通过解引用转换成切片类型,具有丰富的功能,如binary_search、chunks、concat等。此外,文章还介绍了切片与迭代器的关系,以及Rust中的懒接口特性。通过实例展示了对Vec<T>使用iter()方法进行map、filter和take操作,强调了Rust中函数式编程的性能优化。最后,提到了如果标准库中的功能无法满足需求,可以使用itertools库中的filter_map_ok()方法。整体而言,本文通过介绍切片和迭代器,展示了Rust中集合容器的灵活应用和性能优化特点。 文章介绍了Rust中集合容器的灵活应用和性能优化特点,重点讲解了切片的概念和使用方法。切片是一组同一类型、长度不确定、在内存中连续存放的数据结构,具有丰富的功能,如binary_search、chunks、concat等。此外,文章还介绍了切片与迭代器的关系,以及Rust中的懒接口特性。通过实例展示了对Vec<T>使用iter()方法进行map、filter和take操作,强调了Rust中函数式编程的性能优化。文章最后提到,如果标准库中的功能无法满足需求,可以使用itertools库中的filter_map_ok()方法。整体而言,本文通过介绍切片和迭代器,展示了Rust中集合容器的灵活应用和性能优化特点。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《陈天 · Rust 编程第一课》
新⼈⾸单¥68
立即购买
登录 后留言

全部留言(12)

  • 最新
  • 精选
  • pedro
    问老师一个工程性上的问题,也困扰了我好久,之前我在用rust开发项目的时候,数据解析性项目,会存在一个字段被多个类,或者函数使用,由于所有权的问题,导致代码中出现了大量的clone函数,后面在做性能分析的时候,发现20%的时间竟然浪费在clone上,求问老师,如何减少clone的调用次数?

    作者回复: 如果单线程,可以用 Rc<T>,多线程用 Arc<T>。

    2021-09-29
    23
  • lisiur
    1. 不可以,但稍微改造下也是可以的 str 实现了 AsRef<[u8]>,AsRef<OsStr>,AsRef<Path>,AsRef<str> 如果 T: AsRef<[U]>,编译器可以推断出 str 是 AsRef<[u8]>,即 U 是 u8 类型 如果 T: AsRef<U>,编译器就懵逼了,因为它有四种选择。 问题的关键就在于编译器无法推断出 U 的类型,因此如果稍微改造下,其实还是可以通过手动标记来通过编译的: ```rust use std::fmt; fn main() { let s = String::from("hello"); print_slice1::<_, [u8]>(&s); // [104, 101, 108, 108, 111] print_slice1::<_, str>(&s); // "hello" } fn print_slice1<T, U: ?Sized>(s: T) where T: AsRef<U>, U: fmt::Debug, { println!("{:?}", s.as_ref()); } ``` 2. 看了下 rxjs 的定义,第二个参数如果小于第一个参数的话,得到的结果好像没啥意义(反正我个人是没看懂), 所以只处理了第二个参数不小于第一个参数的情况。 ```rust struct WindowCountIter<T: Iterator> { iter: T, window_size: usize, start_window_every: usize, } impl<T: Iterator> Iterator for WindowCountIter<T> { type Item = Vec<<T as Iterator>::Item>; fn next(&mut self) -> Option<Self::Item> { let mut item = Vec::with_capacity(self.window_size); for _ in 0..self.window_size { if let Some(v) = self.iter.next() { item.push(v); } } for _ in 0..(self.start_window_every - self.window_size) { self.iter.next(); } if item.is_empty() { None } else { Some(item) } } } trait IteratorExt: Iterator { fn window_count(self, window_size: usize, start_window_every: usize) -> WindowCountIter<Self> where Self: Sized, { if start_window_every > 0 && start_window_every < window_size { panic!("start_window_every 不能小于 window_size") } WindowCountIter { iter: self, window_size, start_window_every: if start_window_every == 0 { window_size } else { start_window_every }, } } } impl<T: Iterator> IteratorExt for T {} ```

    作者回复: 嗯。挺不错。第二题可以看看我的参考实现: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c51bf256df8be1c51c16bbe4885b810a

    2021-09-30
    4
    11
  • 阿成
    怎么留言越来越少了…… 不要放弃啊,我也有被卡好几天的时候,但慢慢地就走出来了。 1. 编译器推断不出 U 的类型,因为 T 实现了多个 AsRef trait。可以使用 turbofish 手动指定 U,同时也要注意到对于 str 和 [u8] 来说,U 需要是 ?Sized。 2. 一开始我以为 WindowCount 结构体 next 方法返回的是一个新的 Iterator,这个新的 Iterator 里是 count 个 Item。后来我发现这不可能实现啊……我为什么一开始是这么想的呢,是受 slice 的 chunks 方法影响,chunks 方法这不是正好符合题目要求么,但 slice 是有长度信息的,而 Iterator 只能一直 next。后来我偷瞄了老师的实现,发现原来是想用 Vec 来承载每一组数据…… 具体实现代码就不贴了,和老师的差不多。 但我又回过头来想 rxjs 的 windowCount 好像不是这个意思,它的每一组数据还是一个流。那它怎么实现的呢? 我想这可能跟 rxjs 的设计有关,它是把数据 push 到订阅者,而 Iterator 是 pull。 rxjs 单独使用 windowCount 是这样的效果:假如数据源是点击事件,count 是 3,一开始还没点击就产生一个 Observable(我们叫它 A),然后我点击第一下,这次点击就被推送到 A 了,点击第二下,也推送到 A,点击第三下,也推送到 A,这时候 A 已经吐 3 个数据了,紧接着就会产生下一个高阶 Observable B,用来承载接下来的三次点击…… 但这个 Iterator 是个同步模型,而且还没有数据总量的信息,我根本无法判断这次 next 是应该返回 None 还是 Some。 建议类似题目可以给出多一点的提示……

    作者回复: 👍 很好的思考。一般我都会把题目的答案放在 github repo 下。

    2021-12-01
    4
  • 阿海
    老师问个问题,为什么rust解引用是用&T 来表示,而不是用*T

    作者回复: &T 是引用,*T 是解引用。比如你有一个 b = &mut u32,你可以 *b = 10 来解引用更改 b 指向的内存。 Rust 大部分情况下都会做自动解引用(使用 . 的时候)。所以你会感觉很少需要用 *。https://stackoverflow.com/questions/28519997/what-are-rusts-exact-auto-dereferencing-rules/28552082

    2021-09-29
    3
    4
  • Marvichov
    还有个问题, 为啥需要 import FromIterator 才能使用 String::from_iter呢? String不都已经impl了吗? https://doc.rust-lang.org/src/alloc/string.rs.html#1866-1872

    作者回复: FromIterator 目前还没有加入 Rust 的 prelude,2021 edition 才会自动加入: The first new feature that Rust 2021 will have is a new prelude that includes TryInto, TryFrom and FromIterator from the Rust standard library. 对于 trait 方法,如果你要使用,需要先确保在上下文中引入了这个 trait。

    2021-09-30
    3
    3
  • Marvichov
    1. 有歧义, U可以是str, 也可以是[u8]; 2. 用vec作弊了: eagerly load window_size大小的element; 没有lazy load https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=e6759f0d43bfbbb9f9a4b4aaf4a8ed8b 没有贴tests; 在link里面有 ``` struct WindowCount<T> where T: Iterator, { window_size: usize, start_window_every: usize, iter: T, } impl<T> Iterator for WindowCount<T> where T: Iterator, { type Item = <Vec<<T as Iterator>::Item> as IntoIterator>::IntoIter; fn next(&mut self) -> Option<Self::Item> { if self.window_size == 0 { return None; } let mut v = Vec::with_capacity(self.window_size); for _ in 0..self.window_size { if let Some(item) = self.iter.next() { v.push(item); } else { break; } } // advance steps for _ in 0..self.start_window_every { if self.iter.next().is_none() { break; } } if v.is_empty() { None } else { Some(v.into_iter()) } } } trait IteratorExt: Iterator { fn window_count(self, window_size: usize, start_window_every: usize) -> WindowCount<Self> where Self::Item: std::fmt::Debug, Self: Sized, { WindowCount { window_size, start_window_every, iter: self, } } } impl<T: Iterator> IteratorExt for T {} ```

    作者回复: 对。 第二题可以看看我的实现:https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c51bf256df8be1c51c16bbe4885b810a 如果你纠结 lazy,可以看看 slice chunk 的实现:https://doc.rust-lang.org/src/core/slice/iter.rs.html#1363

    2021-09-30
    2
    2
  • 给我点阳光就灿烂
    写了一个缓存库,想问一下老师如何优化hashmap的性能,目前为了算法上的O1,使用了box和raw指针,但是会box和rebox又让性能慢了一些。https://github.com/al8n/caches-rs

    作者回复: 没有太好的想法。不过,算法上的 O1(HashMap + LinkedList) 和 box/raw 关系不大。 如果测量的结果是频繁地分配释放是罪魁祸首,那么,可以考虑使用 slab 来预分配 RawLRU 的 entry。

    2021-09-29
    1
  • 朱中喜
    let b1 = v1.into_boxed_slice(); let mut b2 = b1.clone(); let v2 = b1.into_vec(); println!("cap should be exactly 5: {}", v2.capacity()); assert!(b2.deref() == v2); b2的类型是Box([T]), 为何对b2做deref就变成Vec了?在标准库里没找到针对Box slice的Deref实现😭

    作者回复: Box<T> 实现了 Deref 啊:https://doc.rust-lang.org/std/boxed/struct.Box.html#impl-Deref。注意你这里 T 是个 slice,所以 b2.deref() 和 v2(Vec)可以比较,因为实现了相应的 eq

    2021-10-17
    2
  • D. D
    1. 可以为同一个具体类型实现不同的AsRef Trait, 编译器无法从上下文中推断出U的具体类型,所以不能这样写。 2. 不知道实现的符不符合要求,以及有什么问题。 pub struct Window<I> { iter: I, count: usize, start: usize, } pub trait IteratorExt: Iterator { fn window_count(self, count: usize, start: usize) -> Window<Self> where Self: Sized, { Window { iter: self, count, start, } } } impl<T: Iterator> IteratorExt for T {} impl<I: Iterator> Iterator for Window<I> { type Item = Vec<<I as Iterator>::Item>; fn next(&mut self) -> Option<Self::Item> { if self.count == 0 { return None; } for _ in 0..self.start { self.iter.next()?; } let mut v = Vec::with_capacity(self.count); for _ in 0..self.count { v.push(self.iter.next()?); } Some(v) } } #[test] fn if_it_works() { let v1 = vec!['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']; let mut window = v1.iter().window_count(0, 0); assert_eq!(window.next(), None); let mut window = v1.into_iter().window_count(3, 0); assert_eq!(window.next(), Some(vec!['a', 'b', 'c'])); assert_eq!(window.next(), Some(vec!['d', 'e', 'f'])); assert_eq!(window.next(), Some(vec!['g', 'h', 'i'])); assert_eq!(window.next(), None); let v2 = vec!['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']; let mut window = v2.into_iter().window_count(3, 0); assert_eq!(window.next(), Some(vec!['a', 'b', 'c'])); assert_eq!(window.next(), Some(vec!['d', 'e', 'f'])); assert_eq!(window.next(), None); let v3 = vec![1, 2, 3, 4, 5, 6, 7, 8]; let mut window = v3.into_iter().window_count(3, 3); assert_eq!(window.next(), Some(vec![4, 5, 6])); assert_eq!(window.next(), None); let v4 = [1, 2, 3, 4, 5, 6, 7, 8]; let mut window = v4.iter().window_count(3, 100); assert_eq!(window.next(), None); }

    作者回复: 1. 对! 2. 可以看看我的参考实现:https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c51bf256df8be1c51c16bbe4885b810a

    2021-09-29
    2
  • 罗杰
    漂亮,老师叕解答了我的好多疑惑。现在唯一有点要适应的就是函数数式编程。C++ 和 Go 写多了,一上来就是 for 循环,要适应 Rust 的想法也是个不小的挑战。

    作者回复: Rust 也可以使用 for / while / loop,并不见得都需要用函数式编程方式。选择最合适的方式处理就好。

    2021-09-29
    4
收起评论
显示
设置
留言
12
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部