通用集合类型与基本数据类型 的不同在于,集合类型的数据是存放在堆上的,基本数据类型是存放在栈上的。
一般堆上的数据都是通过用一个栈上的胖指针来指向的。集合类型不外如是。
动态数据vector
动态数据可以在内存中相邻地排布同类型的值。
可以创建一个动态数据
let v: Vec<i32> = Vec::new();还可以通过一个宏来创建含有初始值的动态数组
let v = vec![1,2,3];如果动态数组使用mut修饰可变,就可以对其进行修改。用push在末尾添加元素,用pop移除并返回末尾的元素
v.push(4);
v.push(5);当动态数组离开作用域销毁,其内部的数据也会被清理。
动态数组的元素可以像数组一样通过索引和[]读取,也可以通过get()方法读取。使用get()会返回Option<&T>枚举类型,并且在下标不合法的时候返回None而不是触发panic。使用[]的下标非法会触发panic。
当动态数组不断增长,当前的连续内存不足以存下的时候,会申请一块新的更大内存,将原来的值再拷贝过去。
使用for循环遍历数组
let v = vec![1,2,3];
for i in &v {
println!("{}", i);
}同时可以修改可变的动态数组
let mut v = vec![1,2,3];
for i in &mut v {
*i += 100;
}结合枚举存储多个类型的值
动态数组只能存储同种类型。但是枚举的不同变体可以存放不同的类型,并且这些变体被视作同一种类型,结合两者就可以实现用动态数组存储多个类型的值。 示例如下:
enum SpreadshellCell {
Int(i32),
Float(f64),
Text(String),
}
let row = vec![
SpreadshellCell::Int(10),
SpreadshellCell::Float(1.1),
SpreadshellCell::Text(String::from("blue"));
];String类型
Rust的语言核心部分就只有一种字符串类型,就是字符串切片str,通常以借用的方式&str出现。字符串切片是一些指向存储在别处的UTF-8编码字符串的引用。
String类型定义在Rust的标准库中而不是内置在语言的核心部分。
这两种类型都采用UTF-8编码。
创建String可以通过提供的new方法,也可以通过实现了Display trait的类型调用to_string()方法,如字符串字面量。
let mut s = String::new();
let s = "initial contents".to_string();
let s = String::from("initial contents");可以使用push_str拼接一个字符串切片到末尾,或者通过push添加一个字符到末尾,通过+或者format!宏来拼接字符串。
let mut s = String::from("foo");
s.push_str("bar");
s.push('l');
let s1 = String::from("hello");
let s2 = String::from(" world!");
let s3 = s1 + &s2; +会调用fn add(self,s: &str) -> String方法,这里执行完之后s1会失效,符合我们的使用方式。
&s2的类型为&String,而函数签名的类型为&str,==编译器可以自动将&String类型参数强制转为&str类型==。使用了一种被称作解引用强制转换 的技术。
对于复杂的字符串合并,可以使用format!宏,并且不会夺取任何参数的所有权.
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
let s = format!("{}-{}-{}", s1, s2, s3);String实际上是对Vec<u8>的封装类型。但是String是支持UTF8编码,对于不同的字符存储的字节数不一样,为了可读性,我们每次应该取出一个UTF8字符,因此String不支持索引。
不过String支持字符串切片,但是这个切片也不能从一个字符的中间字符开始和结束。
对String的遍历,一般是转为char类型,然后遍历
for c in "中文字符".chars() {
println!("{}", c);
}也可以按照字节进行遍历:
for b in "中文字符".bytes() {
println!("{}", b);
}HashMap
HashMap<K,V>存储从K类型键到V类型值之间的关系。
创建一个哈希并插入键值对
let mut scores = HashMap::new();
scores.insert(String::from("blue"), 10);
scores.insert(String::from("yellow"), 50);还可以通过collect来收集数据构建成数据结构,如下:
use std::collections::HashMap;
let teams = vec![String::from("Blue"), String::from("Yellow")];
let initial_scores = vec![10, 50];
let scores: HashMap<_, _> =teams.iter().zip(initial_scores.iter()).collect();对于那些实现了Copy trait的类型,例如i32,它们的值会被简单地复制到哈希映射中。而对于String这种持有所有权的值,其值将会转移且所有权会转移给哈希映射。 只是将值的引用插入哈希映射,那么这些值是不会被移动到哈希映射中的。这些引用所指向的值必须要保证,在哈希映射有效时自己也是有效的。在生命周期中会说明这一点。
通过get方法来访问哈希中的值。因为get返回的是一个Option<&V>。
使用for循环遍历哈希中的键值对:
for (key, value) in &scores {
println!("{}: {}", key, value);
}同样的键并配以不同的值来继续插入,之前的键所关联的值就会被替换掉。对于需要检测键不存在再插入的情景,提供了entry方法,返回一个Entry的枚举,示例代码如下:
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);or_insert返回键指向值的可变引用。也可以利用此来实现基于旧值更新新值:
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;
}哈希函数
为了提供抵御拒绝服务攻击(DoS,Denial of Service)的能力,HashMap默认使用了一个在密码学上安全的哈希函数。这确实不是最快的哈希算法。如果这一点成为了性能瓶颈,那么可以通过指定不同的哈希工具(实现了BuildHasher trait)来使用其它函数。