Rust:String

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 的首要目标:

  1. 完全兼容 ASCII:老文件不用改
  2. 避免字节序问题:不分大小端
  3. 自同步:在流中可定位字符边界

因此 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描述一种字符串模式匹配,类似于正则表达式,但没有正则功能那么丰富,常见的情况下,接受四种格式:

  1. &str:匹配固定字符串
rust 复制代码
let s = "hello world";

// 替换所有 "world" 为 "Rust"
let new_s = s.replace("world", "Rust");

assert_eq!(new_s, "hello Rust");
  1. char:匹配单个字符
rust 复制代码
let s = "a-b-c-d";

// 替换所有 '-' 为 '_'
let new_s = s.replace('-', "_");

assert_eq!(new_s, "a_b_c_d");
  1. FnMut(char) -> bool:符合闭包条件的字符
rust 复制代码
let s = "abc123xyz";

// 替换所有数字为 "#"
let new_s = s.replace(char::is_numeric, "#");

assert_eq!(new_s, "abc###xyz");

Pattern可以传入一个闭包,只要闭包返回true,就认为这个字符匹配。

  1. 字符数组切片(如 &[' ', '\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的所有权,此时Stringdrop就不会在离开左右域时调用,那么这个堆区的内存就永远不会回收,手动造成内存泄露。

造成内存泄露有什么好处?这就要提到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>

相比于直接fromfrom_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 编码)从左到右进行:

  1. 如果某个位置的字节不同,则以该字节的大小决定顺序
  2. 如果前缀完全相同,则较短的字符串排在前面

示例:

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的额外约束。

他们的返回值MatchIndicesRMatchIndices都是迭代器,迭代器的输出类型是 (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");

  • DerefString 自动解引用为 &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");

相关推荐
许彰午1 小时前
35_Java设计模式之工厂模式
java·开发语言·设计模式
程序猿阿伟1 小时前
《Chrome非必要服务的精细化关闭指南》
前端·chrome·php
凡人叶枫1 小时前
Effective C++ 条款32:确定你的 public 继承塑模出 is-a(是一种)关系
java·linux·开发语言·c++·嵌入式开发
belong_my_offer1 小时前
理解前端函数
前端
小杨互联网1 小时前
Jar反编译逆向2.0教程实战
java·jar·java反编译·jar反编译·java逆向·源码还原
爱码少年1 小时前
Spring Boot 文件上传下载完整指南:从基础到高级实践
java·spring boot
沐土Arvin1 小时前
中国省市区json数据
前端
Flittly1 小时前
【AgentScope Java新手村系列】(7)子Agent编排
java·spring boot·笔记·spring·ai
狗哥哥1 小时前
统一下载网关技术方案
前端·架构