rust字符与字符串:String和&str的异同
在编程的世界里,字符串处理是绕不开的话题。而在 Rust 中,字符串有两大核心类型:String
和 &str
。初次接触 Rust 的开发者,常常会对这两者感到困惑。今天,让我们深入探讨它们的异同,以便更精准地在项目中使用它们。
一、初识String和&str
(一)什么是String
String
是 Rust 中的字符串类型,它是一个可变的、拥有的、 UTF-8编码的字符串。这意味着你可以自由地修改它,比如追加字符或截取子串。这种类型适合需要频繁修改内容的场景。
举个例子,我们创建一个 String
:
let mut s = String::new();
s.push_str("hello");
s.push_str(" world");
println!("{}", s); // 输出 "hello world"
这里,我们先创建了一个空的 String
。接着,通过 push_str
方法追加内容。这表明 String
是可变的,能够根据需要动态调整大小。
(二)什么是&str
&str
是字符串切片,它是一个不可变的、借用的、 UTF-8编码的字符串片段。&str
通常用于表示一段已有的字符串数据,它的大小在编译时就已确定。这种类型适合只读的场景。例如,字面量字符串就是 &str
类型:
let s: &str = "hello world";
这里的 "hello world"
就是一个字符串字面量,它的类型是 &str
。我们无法直接修改 s
的内容,因为它是不可变的。
(三)mermaid总结
罗马数字列表:
I. String
适合需要频繁修改的场景。
II. &str
适合只读的场景。
二、String和&str的内存布局
(一)String的内存结构
String
在内存中是一个动态分配的数据结构,它包含三个部分:
部分 | 说明 |
---|---|
指针 | 指向堆上实际存储的字符串数据的起始位置 |
长度 | 表示当前字符串的长度 |
容量 | 表示分配的内存容量,用于容纳未来的扩展 |
let mut s = String::from("hello");
s.push_str(" world");
当我们在 s
后追加 " world"
时,String
会自动在堆上分配更多内存。如果当前容量不足,它会分配一个新的更大的内存块,将原有数据复制过去,然后释放旧的内存。这种动态扩容机制使得 String
能够灵活应对内容的变化。
(二)&str的内存结构
&str
是一个指向已有字符串数据的切片,它包含两个部分:
部分 | 说明 |
---|---|
指针 | 指向字符串数据的起始位置 |
长度 | 表示切片的长度 |
let s = "hello world";
let part = &s[0..5];
这里,part
是一个 &str
,它指向 s
的前五个字符。由于 &str
不拥有数据,它的大小是固定的,由编译器确定。
(三)mermaid总结
罗马数字列表:
III. String
的动态扩容机制使其能够灵活应对内容变化。
IV. &str
的固定大小使其在只读场景下效率更高。
三、String和&str的转换
(一)从&str创建String
可以通过 String::from
或 to_string
方法将 &str
转换为 String
:
let s1 = "hello";
let s2 = String::from(s1);
let s3 = s1.to_string();
String::from
和 to_string
方法都会在堆上为字符串分配内存,并将 &str
的内容复制过去。此时,s2
和 s3
是独立拥有的 String
。
(二)从String获取&str
可以通过 &
操作符将 String
转换为 &str
:
let s = String::from("hello world");
let s_slice: &str = &s;
这里,s_slice
是一个指向 s
内容的 &str
。由于 &str
不拥有数据,它的生命周期不能超过原始的 String
。
(三)mermaid总结
罗马数字列表:
V. 从 &str
创建 String
会复制数据。
VI. 从 String
获取 &str
不会复制数据,只是创建一个视图。
四、String和&str的操作
(一)String的操作
1. 追加内容
可以通过 push_str
方法向 String
追加内容:
let mut s = String::from("hello");
s.push_str(" world");
println!("{}", s); // 输出 "hello world"
push_str
方法将内容追加到 String
的末尾。如果当前容量不足,String
会自动扩容。
2. 合并字符串
可以通过 +
运算符合并两个 String
:
let s1 = String::from("hello");
let s2 = String::from(" world");
let s3 = s1 + &s2;
这里,s3
是合并后的 String
,而 s1
被转移给了 s3
,不能再使用。
(二)&str的操作
1. 截取子串
可以通过切片语法从 &str
中截取子串:
let s = "hello world";
let sub = &s[0..5];
println!("{}", sub); // 输出 "hello"
这里,sub
是一个指向 s
前五个字符的 &str
。
2. 拼接字符串
可以通过 format!
宏拼接多个 &str
:
let s1 = "hello";
let s2 = "world";
let s3 = format!("{} {}", s1, s2);
format!
宏会创建一个新的 String
,并将 s1
和 s2
的内容复制进去。
(三)mermaid总结
罗马数字列表:
VII. String
的操作会修改其内容。
VIII. &str
的操作不会修改原始数据。
五、性能对比
(一)内存分配
String
需要在堆上动态分配内存,这涉及到内存分配和可能的扩容操作。频繁的分配和扩容可能会带来性能开销。
而 &str
是栈上存储的指针和长度,它的大小是固定的,访问速度快。
(二)数据复制
从 &str
创建 String
会复制数据,这在大数据量时可能会消耗较多时间。
从 String
获取 &str
不会复制数据,只是创建一个视图。
(三)mermaid总结
罗马数字列表:
IX. String
的动态分配和扩容可能带来性能开销。
X. &str
的固定大小使其访问速度快。
六、实例分析
(一)实例一:字符串拼接
1. 使用String
let mut s = String::new();
for i in 0..10 {
s.push_str(&i.to_string());
}
这里的字符串拼接效率较低,因为每次循环都会触发 String
的追加操作,可能导致多次扩容。
2. 使用&str和format!
let mut s = String::new();
for i in 0..10 {
s = format!("{}{}", s, i);
}
这种写法性能更差,因为每次 format!
都会创建一个新的 String
,并将旧数据复制过去。
3. 更好的方法
let mut s = String::with_capacity(10);
for i in 0..10 {
s.push_str(&i.to_string());
}
通过预先设置 String
的容量,可以避免多次扩容,提高性能。
(二)实例二:字符串截取
let s = "hello world";
let sub = &s[0..5];
这里,我们从 &str
中截取子串,不会复制数据,效率很高。
(三)mermaid总结
罗马数字列表:
XI. 预设 String
容量可以避免多次扩容。
XII. &str
的截取操作效率高,无数据复制。
七、总结
在 Rust 中,String
和 &str
各有特点:
特性 | String | &str |
---|---|---|
可变性 | 可变 | 不可变 |
生命周期 | 拥有数据 | 借用数据 |
内存布局 | 堆分配,包含指针、长度和容量 | 栈存储指针和长度 |
性能 | 动态操作可能带来开销 | 访问速度快,无复制 |
在开发中,根据场景选择合适的类型:
- 需要频繁修改时,使用
String
- 只需读取时,优先使用
&str
- 点赞
- 收藏
- 关注作者
评论(0)