Rust:String
编码
Unicode
字符Char是信息展示的基本单位,比如字母 "A"、汉字 "你"、表情符号"😊"。 计算机内部存储的不是字符,而是数字。因此我们需要一张对照表。
Unicode 是一张全球统一的字符表,给每个字符一个唯一的码位Code Point。
示例:
| 字符 | Unicode码位 | 十六进制表示 |
|---|---|---|
| A | 65 | 0041 |
| 中 | 20013 | 4E2D |
| 😊 | 128522 | 1F60A |
注意这时还没有谈怎么存,只是定义了编号
Unicode 的码位可能很大,比如 1F60A 超过十万。但是0041又很小,超出u8的存储范围,用更多的字节去存又亏了。
我们得定义一种可变长度字节编码规则,才能把这些码位变成计算机能存进文件或网络的字节序列。
这些规则就称作 UTF(Unicode Transformation Format)。
常见的有:
- UTF‑8:以 8 位(1 字节)为单位,变长(1--4 字节编码一个字符)
- UTF‑16:以 16 位为基本单元,变长(2 或 4 字节)
- UTF‑32:定长,每个字符4字节(32位)
UTF-8
UTF‑8 是一种变长编码,不同字符根据码位范围使用不同字节数量进行表示。码位小的字符用较少字节,节省空间。码位大的字符用更多字节,确保表达范围足够 。
UTF‑8 的首要目标:
- 完全兼容 ASCII:老文件不用改
- 避免字节序问题:不分大小端
- 自同步:在流中可定位字符边界
因此 UTF‑8 是一种既高效又稳健的编码方式。
UTF‑8 根据 Unicode 码点的二进制范围,划分为 1 至 4 字节的几种格式。
| 字节数 | Unicode 码位范围 | 二进制格式 |
|---|---|---|
| 1 字节 | 0000 -- 007F | 0xxxxxxx |
| 2 字节 | 0080 -- 07FF | 110xxxxx 10xxxxxx |
| 3 字节 | 0800 -- FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
| 4 字节 | 10000 -- 10FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
规则说明:
- 首字节:指明这是几字节的序列,通过前缀位数1的个数判断
- 后续字节 :都以
10开头,标志写明"我是后续部分" - 真实的码位二进制按顺序分配到
x位置上
以"中"字(4E2D)为例。
0x4E2D,在范围 0x0800 ~ 0xFFFF,所以是 3 字节编码。因此第一个字节的开头是1110,前三个1表示这个字符总共占 3 字节。0x4E2D的二进制为100111000101101,将这个二进制填入空缺位置:
rust
1110xxxx 10xxxxxx 10xxxxxx
11100100 10111000 10101101
得到二进制:11100100 10111000 10101101,对应的十六进制:E4 B8 AD 。
因此"中"对应的编码就是E4 B8 AD。
而String默认存储的就是utf-8的变长编码序列,char则直接存储Unicode码值。
结构
String的结构非常简单:
rust
pub struct String {
vec: Vec<u8>,
}
它本质就是对Vec<u8>进行了一层包装,一个一个字节地存储utf-8编码后的字符。
关于基本信息的相关方法:
len:获取字符串长度,以字节为单位
rust
const fn len(&self) -> usize
capacity:获取字符串容量,以字节为单位
rust
const fn capacity(&self) -> usize
本博客中的很多方法,都在前一章Vec内部详细讲过了,这些方法只会简单地列举出来,只有String中比较特殊的方法,才会深入讲解。
增删查改
push:在字符串尾部插入一个字符
rust
fn push(&mut self, ch: char)
示例:
rust
let mut s = String::from("abc");
s.push('1');
s.push('2');
s.push('3');
assert_eq!("abc123", s);
push_str:在字符串尾部插入一个字符串
rust
fn push_str(&mut self, string: &str)
示例:
rust
let mut s = String::from("foo");
s.push_str("bar");
assert_eq!("foobar", s);
insert:在指定索引插入一个字符
rust
fn insert(&mut self, idx: usize, ch: char)
索引必须处于utf-8字符边界。
示例:
rust
let mut s = String::with_capacity(3);
s.insert(0, 'f');
s.insert(1, 'o');
s.insert(2, 'o');
assert_eq!("foo", s);
insert_str:在指定索引插入一个字符串
rust
fn insert_str(&mut self, idx: usize, string: &str)
索引必须处于utf-8字符边界。
示例:
rust
let mut s = String::from("bar");
s.insert_str(0, "foo");
assert_eq!("foobar", s);
pop:删除并返回数组尾部的字符
rust
fn pop(&mut self) -> Option<char>
示例:
rust
let mut s = String::from("abč");
assert_eq!(s.pop(), Some('č'));
assert_eq!(s.pop(), Some('b'));
assert_eq!(s.pop(), Some('a'));
assert_eq!(s.pop(), None);
remove:删除并返回指定索引的字符
rust
fn remove(&mut self, idx: usize) -> char
索引必须处于utf-8字符边界。
示例:
rust
let mut s = String::from("abç");
assert_eq!(s.remove(0), 'a');
assert_eq!(s.remove(1), 'ç');
assert_eq!(s.remove(0), 'b');
retain:保留满足条件的字符
rust
fn retain<F>(&mut self, f: F)
where
F: FnMut(char) -> bool,
示例:
rust
let mut s = String::from("abc123");
s.retain(|c| c.is_alphabetic());
assert_eq!(s, "abc");
clear:清空字符串
rust
fn clear(&mut self)
示例:
rust
let mut s = String::from("hello");
s.clear();
assert_eq!(s, "");
split_off:在指定索引处分割字符串
rust
fn split_off(&mut self, at: usize) -> String
索引必须处于 utf-8 字符边界。
示例:
rust
let mut s = String::from("hello world");
let t = s.split_off(5);
assert_eq!(s, "hello");
assert_eq!(t, " world");
drain:移除并返回指定范围的字符迭代器
rust
fn drain<R>(&mut self, range: R) -> Drain<'_>
where
R: RangeBounds<usize>,
范围必须处于 utf-8 字符边界。
示例:
rust
let mut s = String::from("abcde");
let drained: String = s.drain(2..4).collect();
assert_eq!(s, "abe");
assert_eq!(drained, "cd");
replace:替换符合模式的字符串
rust
fn replace<P>(&self, from: P, to: &str) -> String
where
P: Pattern,
在这里,from要求满足约束Pattern,这个Pattern描述一种字符串模式匹配,类似于正则表达式,但没有正则功能那么丰富,常见的情况下,接受四种格式:
&str:匹配固定字符串
rust
let s = "hello world";
// 替换所有 "world" 为 "Rust"
let new_s = s.replace("world", "Rust");
assert_eq!(new_s, "hello Rust");
char:匹配单个字符
rust
let s = "a-b-c-d";
// 替换所有 '-' 为 '_'
let new_s = s.replace('-', "_");
assert_eq!(new_s, "a_b_c_d");
FnMut(char) -> bool:符合闭包条件的字符
rust
let s = "abc123xyz";
// 替换所有数字为 "#"
let new_s = s.replace(char::is_numeric, "#");
assert_eq!(new_s, "abc###xyz");
Pattern可以传入一个闭包,只要闭包返回true,就认为这个字符匹配。
- 字符数组切片(如
&[' ', '\t'][..])
如果说想要匹配多个字符,那么可以将多个字符放在一个数组中,向Pattern传入数组切片,只要匹配到数组内任意一个字符,就视为匹配成功。
rust
let s = "a b\tc";
// 替换空格和制表符为 "_"
let new_s = s.replace(&[' ', '\t'][..], "_");
assert_eq!(new_s, "a_b_c");
这种方案常用与对多个分隔符进行匹配。
replace_range:移除字符串中指定的范围,并用给定的字符串替换
rust
fn replace_range<R>(&mut self, range: R, replace_with: &str)
where
R: RangeBounds<usize>,
替换的字符串长度不需要与被移除的范围长度相同。
起始和终止索引必须处于utf-8字符边界。
示例:
rust
let mut s = String::from("α is alpha, β is beta");
let beta_offset = s.find('β').unwrap_or(s.len());
// 将 β 之前的部分替换为新的字符串
s.replace_range(..beta_offset, "Α is capital alpha; ");
assert_eq!(s, "Α is capital alpha; β is beta");
内存分配
| 方法 | 签名 | 功能说明 |
|---|---|---|
new |
fn new() -> String |
创建一个空字符串,长度和容量均为 0 |
with_capacity |
fn with_capacity(capacity: usize) -> String |
创建一个指定初始容量的空字符串,减少扩容次数 |
reserve |
fn reserve(&mut self, additional: usize) |
为字符串预留额外的容量,避免频繁扩容 |
shrink_to_fit |
fn shrink_to_fit(&mut self) |
收缩字符串容量,使其与当前长度匹配 |
resize |
fn resize(&mut self, new_len: usize, c: char) |
调整字符串长度,若变长则填充指定字符,若变短则截断 |
truncate |
fn truncate(&mut self, new_len: usize) |
截断字符串到指定长度,索引必须在 UTF-8 字符边界 |
以上相关的容量方法,都在Vec讲解过了,这里不在额外讲解。
leak:消耗并泄漏这个String,返回一个指向其内容的可变引用&'a mut str。
rust
fn leak<'a>(self) -> &'a mut str
String内部的Vec<u8>,本质是通过一个指向堆区的指针访问到的数据。当String离开生命周期,就会调用drop,基于RAII机制就会回收堆区的内存。
而leak方法,就是显式地消耗掉String的所有权,此时String的drop就不会在离开左右域时调用,那么这个堆区的内存就永远不会回收,手动造成内存泄露。
造成内存泄露有什么好处?这就要提到leak的返回值,它返回了一个&'a mut str,也就是一个原生字符串的借用。你可以通过这个借用访问到堆区的数据,相当于把一个String转换为了一个&str,并且这个字符串的生命周期变成了'static,整个程序存活期间都存在。
转换与构造
From<&str>:从字符串切片构造一个新的String
rust
impl From<&str> for String
示例:
rust
let s = String::from("hello");
assert_eq!(s, "hello");
From<char>:从单个字符构造一个新的String
rust
impl From<char> for String
示例:
rust
let s: String = String::from('R');
assert_eq!(s, "R");
FromStr:支持"abc".parse::<String>()语法
rust
impl FromStr for String
示例:
rust
let s: String = "hello".parse().unwrap();
assert_eq!(s, "hello");
From<Vec<u8>>:从字节向量直接构造String,要求内容是合法 UTF-8
rust
impl From<Vec<u8>> for String
如果不是合法的 UTF-8 序列,那么会导致panic。
示例:
rust
let bytes = vec![104, 101, 108, 108, 111]; // "hello"
let s: String = String::from(bytes);
assert_eq!(s, "hello");
from_utf8:尝试从 UTF-8 编码的字节向量构造String
rust
pub fn from_utf8(v: Vec<u8>) -> Result<String, FromUtf8Error>
相比于直接from,from_utf8返回的是一个Result,如果不是合法的UTF-8 序列,返回的就是Err,而不会panic,更安全一些。
示例:
rust
let bytes = vec![104, 101, 108, 108, 111];
let s = String::from_utf8(bytes).unwrap();
assert_eq!(s, "hello");
into_bytes:消耗String,返回底层的字节向量
rust
pub fn into_bytes(self) -> Vec<u8>
示例:
rust
let s = String::from("abc");
let bytes = s.into_bytes();
assert_eq!(bytes, vec![97, 98, 99]);
as_bytes:返回字符串的字节切片
rust
pub fn as_bytes(&self) -> &[u8]
示例:
rust
let s = String::from("abc");
let bytes = s.as_bytes();
assert_eq!(bytes, &[97, 98, 99]);
二元关系
判等:PartialEq / Eq
String 和 &str 都实现了 PartialEq / Eq。逐字节比较两个字符串的内容是否完全一致,即 UTF-8 编码序列相同。
示例:
rust
let a = String::from("hello");
let b = String::from("hello");
let c = String::from("world");
assert!(a == b); // 内容完全相同
assert!(a != c); // 内容不同
字典序:PartialOrd / Ord
Rust 中的字符串比较遵循字典序。
比较时会逐字节(UTF-8 编码)从左到右进行:
- 如果某个位置的字节不同,则以该字节的大小决定顺序
- 如果前缀完全相同,则较短的字符串排在前面
示例:
rust
let a = String::from("apple");
let b = String::from("banana");
let c = String::from("app");
assert!(a < b); // "apple" 在字典序上小于 "banana"
assert!(c < a); // "app" 是 "apple" 的前缀,较短的排前
查找与匹配
contains:判断字符串是否包含某个模式
rust
pub fn contains<P>(&self, pat: P) -> bool
where
P: Pattern,
这里的P必须实现Pattern,可以传入之前的四种参数。
示例:
rust
let bananas = "bananas";
assert!(bananas.contains("nana"));
assert!(!bananas.contains("apples"));
starts_with/ends_with:判断字符串是否以某个模式开头或结尾
rust
pub fn starts_with<P>(&self, pat: P) -> bool
where
P: Pattern,
fn ends_with<P>(&self, pat: P) -> bool
where
P: Pattern,
<P as Pattern>::Searcher<'a>: for<'a> ReverseSearcher<'a>,
这里同样P必须实现Pattern,可以传入之前的四种参数。
对于 ends_with来说,它通过<P as Pattern>::Searcher<'a>指定了内部的关联类型,要求关联类型必须实现 ReverseSearcher,实现了这个表示可以反过来检索。
而之前的四种参数中,比如&str就是一个借用。搜索器需要用这个借用的生命周期去构造,为了保证不论&str是什么生命周期,这个搜索器都可以正常构造,那么使用高阶生命周期表示搜索器内部的'a对所有生命周期都成立。
示例:
rust
let s = String::from("hello world");
assert!(s.starts_with("hello"));
assert!(s.ends_with("world"));
find:查找模式在字符串中的第一个匹配位置
rust
fn find<P>(&self, pat: P) -> Option<usize>
where
P: Pattern,
示例:
rust
let s = "Löwe 老虎 Léopard Gepardi";
assert_eq!(s.find('L'), Some(0));
assert_eq!(s.find('é'), Some(14));
assert_eq!(s.find("pard"), Some(17));
match_indices/rmatch_indices:返回所有匹配模式的索引及匹配内容
rust
fn match_indices<P>(&self, pat: P) -> MatchIndices<'_, P> ⓘ
where
P: Pattern,
fn rmatch_indices<P>(&self, pat: P) -> RMatchIndices<'_, P> ⓘ
where
P: Pattern,
<P as Pattern>::Searcher<'a>: for<'a> ReverseSearcher<'a>,
其中 rmatch_indices 是反向查找的版本,因此和之前一样有一层对Searcher的额外约束。
他们的返回值MatchIndices和RMatchIndices都是迭代器,迭代器的输出类型是 (usize, &str),即索引以及匹配上的字符串。
示例:
rust
let v: Vec<_> = "abcXXXabcYYYabc".match_indices("abc").collect();
assert_eq!(v, [(0, "abc"), (6, "abc"), (12, "abc")]);
let v: Vec<_> = "1abcabc2".match_indices("abc").collect();
assert_eq!(v, [(1, "abc"), (4, "abc")]);
let v: Vec<_> = "ababa".match_indices("aba").collect();
assert_eq!(v, [(0, "aba")]); // only the first `aba`
let v: Vec<_> = "abcXXXabcYYYabc".rmatch_indices("abc").collect();
assert_eq!(v, [(12, "abc"), (6, "abc"), (0, "abc")]);
let v: Vec<_> = "1abcabc2".rmatch_indices("abc").collect();
assert_eq!(v, [(4, "abc"), (1, "abc")]);
let v: Vec<_> = "ababa".rmatch_indices("aba").collect();
assert_eq!(v, [(2, "aba")]); // only the last `aba`
split:按模式分割字符串,返回迭代器
rust
fn split<P>(&self, pat: P) -> Split<'_, P> ⓘ
where
P: Pattern,
示例:
rust
let v: Vec<&str> = "Mary had a little lamb".split(' ').collect();
assert_eq!(v, ["Mary", "had", "a", "little", "lamb"]);
let v: Vec<&str> = "".split('X').collect();
assert_eq!(v, [""]);
let v: Vec<&str> = "lionXXtigerXleopard".split('X').collect();
assert_eq!(v, ["lion", "", "tiger", "leopard"]);
let v: Vec<&str> = "lion::tiger::leopard".split("::").collect();
assert_eq!(v, ["lion", "tiger", "leopard"]);
迭代器
chars:返回字符串的字符迭代器
rust
fn chars(&self) -> Chars<'_>
示例:
rust
let s = "hello";
let mut iter = s.chars();
assert_eq!(iter.next(), Some('h'));
assert_eq!(iter.next(), Some('e'));
lines:按行分割字符串,返回迭代器
rust
fn lines(&self) -> Lines<'_>
示例:
rust
let s = "foo\nbar\nbaz";
let mut iter = s.lines();
assert_eq!(iter.next(), Some("foo"));
assert_eq!(iter.next(), Some("bar"));
assert_eq!(iter.next(), Some("baz"));
bytes:返回字符串的字节迭代器
rust
fn bytes(&self) -> Bytes<'_>
示例:
rust
let s = "abc";
let mut iter = s.bytes();
assert_eq!(iter.next(), Some(97));
assert_eq!(iter.next(), Some(98));
assert_eq!(iter.next(), Some(99));
Trait 实现
Add<&str>:字符串拼接,返回新的String
rust
impl Add<&str> for String
示例:
rust
let s = String::from("foo") + "bar";
assert_eq!(s, "foobar");
AddAssign<&str>:字符串拼接并赋值
rust
impl AddAssign<&str> for String
示例:
rust
let mut s = String::from("foo");
s += "bar";
assert_eq!(s, "foobar");
AsRef<str>:获取内部&str借用
rust
impl AsRef<str> for String
示例:
rust
fn takes_str_ref(s: impl AsRef<str>) {
assert_eq!(s.as_ref(), "hello");
}
let s = String::from("hello");
takes_str_ref(s);
AsMut<str>:获取内部&mut str借用
rust
impl AsMut<str> for String
示例:
rust
fn make_uppercase(s: &mut impl AsMut<str>) {
let r = s.as_mut();
r.make_ascii_uppercase();
}
let mut s = String::from("hello");
make_uppercase(&mut s);
assert_eq!(s, "HELLO");
ToString:将类型转换为String
rust
impl ToString for str
示例:
rust
let s: String = "hello".to_string();
assert_eq!(s, "hello");
Deref:String自动解引用为&str
rust
impl Deref for String {
type Target = str;
fn deref(&self) -> &str { ... }
}
示例:
rust
let s = String::from("hello");
let slice: &str = &s; // 自动 Deref
assert_eq!(slice, "hello");