导读
rust作为一门强类型语言,除了基础类型,用的最多的就是字符串类型了,rust提供了多种方法:String、&str、&'static str和 FastStr。ok,用法相信大部分人都会,但是他们内存分配在堆上还是栈上呢? 谁的性能最好呢?
本文将详细介绍这些字符串类型及其用法,帮助您更好地理解和掌握 Rust 字符串处理。
用法
String
Rust 中 String
的常见用法包括:
- 初始化:使用
String::new()
创建一个空的字符串或者使用字符串字面量直接初始化一个String
。
let mut s = String::new();
let s = String::from("Hello, world!");
- 拼接字符串:使用
+
运算符或format!
宏来拼接字符串。
let s1 = String::from("Hello");
let s2 = String::from(", world!");
let s3 = s1 + &s2; // 注意 s1 被移动到 s3 中
println!("{}", s3); // 输出 "Hello, world!"
let s4 = format!("{}{}", s1, s2); // 使用 format! 宏拼接字符串
println!("{}", s4); // 输出 "Hello, world!"
- 提取出固定位置的字符:使用
chars()
方法和索引来提取字符串中的字符。
let s = String::from("Hello");
let char_at_index = s.chars().nth(1); // 获取索引位置为 1 的字符
match char_at_index {
Some(c) => println!("{}", c), // 输出 "e"
None => println!("Index out of range"),
}
- 截断字符串:使用
split_off()
方法来截断字符串,将剩余部分移动到一个新的String
中。
let mut s = String::from("Hello, world!");
let new_string = s.split_off(5); // 将索引位置 5 后面的部分截断
println!("{}", s); // 输出 "Hello"
println!("{}", new_string); // 输出 ", world!"
- 字符串追加:使用
push_str()
方法将一个字符串追加到另一个字符串的末尾。
let mut s = String::from("Hello");
let s2 = ", world!";
s.push_str(s2);
println!("{}", s); // 输出 "Hello, world!"
&str
Rust 中 str
的常见用法包括:
- 初始化:使用字符串字面量直接初始化一个
str
。
let s: &str = "Hello, world!";
- 拼接字符串:使用
+
运算符或format!
宏来拼接字符串。
let s1: &str = "Hello";
let s2: &str = ", world!";
let s3: String = [s1, s2].concat(); // 使用 `concat()` 方法拼接字符串
println!("{}", s3); // 输出 "Hello, world!"
let s4: String = format!("{}{}", s1, s2); // 使用 format! 宏拼接字符串
println!("{}", s4); // 输出 "Hello, world!"
- 提取出固定位置的字符:使用索引来提取字符串中的字符。
let s: &str = "Hello";
let char_at_index: Option<char> = s.chars().nth(1); // 获取索引位置为 1 的字符
match char_at_index {
Some(c) => println!("{}", c), // 输出 "e"
None => println!("Index out of range"),
}
- 截断字符串:使用切片操作来截断字符串,获取指定范围内的子字符串。
let s: &str = "Hello, world!";
let new_str: &str = &s[7..12]; // 获取索引位置 7 到 11 的子字符串
println!("{}", new_str); // 输出 "world"
- 字符串追加:使用
to_owned()
方法将一个str
转换为String
类型,并使用push_str()
方法将一个字符串追加到另一个字符串的末尾。
let mut s: String = String::from("Hello");
let s2: &str = ", world!";
s.push_str(s2);
println!("{}", s); // 输出 "Hello, world!"
需要注意的是 str
类型在 Rust 中是不可变的。
&'static str
Rust 中 &'static str
的常见用法包括:
- 初始化:使用字符串字面量直接初始化一个
&'static str
。
let s: &'static str = "Hello, world!";
- 拼接字符串:使用
+
运算符或format!
宏来拼接字符串。
let s1: &'static str = "Hello";
let s2: &'static str = ", world!";
let s3: String = [s1, s2].concat(); // 使用 `concat()` 方法拼接字符串
println!("{}", s3); // 输出 "Hello, world!"
let s4: String = format!("{}{}", s1, s2); // 使用 format! 宏拼接字符串
println!("{}", s4); // 输出 "Hello, world!"
- 提取出固定位置的字符:使用索引来提取字符串中的字符。
let s: &'static str = "Hello";
let char_at_index: Option<char> = s.chars().nth(1); // 获取索引位置为 1 的字符
match char_at_index {
Some(c) => println!("{}", c), // 输出 "e"
None => println!("Index out of range"),
}
- 截断字符串:使用切片操作来截断字符串,获取指定范围内的子字符串。
let s: &'static str = "Hello, world!";
let new_str: &'static str = &s[7..12]; // 获取索引位置 7 到 11 的子字符串
println!("{}", new_str); // 输出 "world"
- 字符串追加:使用
to_string()
方法将一个&'static str
转换为String
类型,并使用push_str()
方法将一个字符串追加到另一个字符串的末尾。
let mut s: String = "Hello".to_string();
let s2: &'static str = ", world!";
s.push_str(s2);
println!("{}", s); // 输出 "Hello, world!"
这些是 &'static str
常见的用法示例。 &'static str
类型表示一个静态字符串,它是不可变的,并且具有 'static
生命周期。
FastStr
FastStr 是一个只读的字符串包装器。你可以将它看作是一个拥有所有权的 &str 类型。FastStr 使用了三种变体:&'static str
、Arc<String>
和 StackString
,并自动选择最佳的存储方式;还进行了优化的字符串克隆和字符串比较。
String
、Arc<String>
、Cow
、&'static str
这些都可以直接 into
到 FastStr
;Bytes
、BytesMut
、Vec<u8>
可以直接用 unsafe
方式转到 FastStr
,这些方法都可以避免数据拷贝。如果是普通的 &str
,那么可以用 FastStr::new(s)
创建出 FastStr
(会有一次内存分配和拷贝),但是之后在 FastStr
使用中的 clone
就是零开销了。
use fast_str::FastStr;
// 编译期常量
const EMPTY_STR: FastStr = FastStr::new();
const STATIC_STR: FastStr = FastStr::from_static("💙❤");
// 直接从&str转
let str1: FastStr = "🍷 wink".into();
let str2 = FastStr::from_ref("😆 Happy");
// 使用”+“连接
let str3: FastStr = str1 + str2;
// clone的复杂度是O(1),几乎是零成本
let str4: FastStr = str3.clone();
// 从String转入faststr
let from_string = FastStr::from_string(String::from("hello world"));
let from_faststr = String::from(from_string);
那么他们的区别呢?
对比
内存分配
- String:String是Rust中的可变字符串类型,它在堆上分配内存。当我们使用String::new()创建一个新的空字符串时,它会分配一块足够容纳字符串内容的内存,并将字符串的长度和容量都设置为0。当我们向String添加字符或字符串时,它会动态地调整内存大小,以适应新的内容。当String不再使用时,它会自动释放分配的内存。
- &str:&str是Rust中的不可变字符串切片类型,它是一种引用类型,不负责内存分配。&str可以引用不同的字符串数据,例如String、静态字符串字面量或其他字符串切片。&str本身只包含一个指向字符串数据的指针和一个长度字段,它不占用额外的内存。&str的生命周期通常由引用它的作用域来决定,不需要手动释放内存。
- &'static str:&'static str是Rust中的静态字符串切片类型,它与&str的区别在于它的生命周期被指定为’static,表示该字符串切片的生命周期是整个程序的运行时间。&'static str通常用于存储静态字符串字面量,例如在代码中直接写出的字符串。与&str一样,&'static str只包含一个指向字符串数据的指针和一个长度字段,不占用额外的内存。
- FastStr:FastStr是一个自定义的字符串类型,它是一个较为底层的实现。FastStr的内存分配方式取决于具体的实现方式,它可以是在堆上分配内存,也可以是使用静态内存池等方式。FastStr通常用于需要高性能的场景,它对字符串的操作更加灵活,但也需要更多的手动管理和维护。
总结来说,String在堆上分配内存,&str和&'static str是引用类型,不负责内存分配,而FastStr的内存分配方式取决于具体的实现方式。根据具体的需求和场景,选择适合的字符串类型可以更好地管理内存和提高性能。
性能
- String:String是可变的堆分配字符串类型。由于它在堆上分配内存,并且支持动态调整大小,因此它可以容纳任意长度的字符串。这使得String非常灵活,但也会导致额外的内存分配和释放开销。在频繁修改字符串内容或拼接大量字符串时,可能会产生较高的性能开销。
- &str:&str是不可变的字符串切片类型,通常是通过引用其他字符串数据创建的。它不负责内存分配,只包含一个指向字符串数据的指针和长度字段。由于不需要额外的内存分配和释放,因此&str在性能方面比String更高效。另外,&str还可以直接访问字符串的部分内容,而无需复制整个字符串。
- &'static str:&'static str是静态字符串切片类型,它的生命周期被指定为’static,表示整个程序的运行时间。它通常用于存储静态字符串字面量,例如在代码中直接写出的字符串。由于静态字符串切片的生命周期很长,因此在性能方面与普通的&str相似。
- FastStr:FastStr是一个自定义的字符串类型,它的性能取决于具体的实现方式。FastStr通常用于需要高性能的场景,它可能采用特定的内存分配策略,例如静态内存池。由于FastStr的实现可以针对具体的使用场景进行优化,因此在某些情况下,它可能比标准库中的String和&str更高效。
总的来说,性能对比取决于具体的使用场景和操作。在大多数情况下,使用&str和&'static str可以获得较好的性能,尤其是在处理大量字符串时。String在需要频繁修改字符串内容或进行字符串拼接的情况下可能会产生较高的性能开销。而FastStr则根据具体的实现方式和使用场景而异,可能在某些特定场景下提供更高的性能。因此,在选择字符串类型时,需要根据具体需求和场景进行权衡和选择。