Rust字符串类型全解析(最新推荐)
<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>1. 机器中的字符串</li><li>2. String 和 &str</li><li>3. Vec 和 &</li><li>4. str 系列</li><ul class="second_class_ul"><li>4.1. Box<str></li><li>4.2. Rc<str></li><li>4.3. Arc<str></li><li>4.4. Cow<str></li></ul><li>5. CStr 和 CString</li><ul class="second_class_ul"></ul><li>6. OsStr 和 OsString</li><ul class="second_class_ul"></ul><li>7. Path 和 PathBuf</li><ul class="second_class_ul"></ul><li>8. 总结</li><ul class="second_class_ul"></ul></ul></div><p>字符串是每种编程语言都绕不开的类型,</p><p>不过,在<code>Rust</code>中,你会看到远比其他语言更加丰富多样的字符串类型。</p>
<p>如下图:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202409/202409260901131.png" /></p>
<p>为什么<code>Rust</code>中需要这么多种表示字符串的类型呢?</p>
<p>初学<code>Rust</code>时,可能无法理解为什么要这样设计?为什么要给使用字符串带来这么多不必要的复杂性?</p>
<p>其实,<code>Rust</code>中对于字符串的设计,优先考虑的是<strong>安全</strong>,<strong>高效</strong>和<strong>灵活</strong>,</p>
<p>所以在易用性方面,感觉没有其他语言(比如python,golang)那么易于理解和掌握。</p>
<p>本文尝试解释<code>Rust</code>中的所有不同的字符串类型,以及它们各自的特点。</p>
<p>希望能让大家更好的理解<code>Rust</code>为了安全和发挥最大性能的同时,是如何处理字符串的。</p>
<p class="maodian"></p><h2>1. 机器中的字符串</h2>
<p>我们代码中的<strong>字符串</strong>或者<strong>数字</strong>,存储在机器中,都是<strong>二进制</strong>,也就是0和1组成的序列。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202409/202409260901132.png" /></p>
<p>程序将二进制数据转换为人类可读的字符串 需要两个关键信息:</p>
<ul><li>字符编码</li><li>字符串长度</li></ul>
<p>常见的编码有<code>ASCII</code>,<code>UTF-8</code>等等,编码就是二进制序列对应的字符,</p>
<p>比如,<code>ASCII</code>是<strong>8位二进制</strong>对应一个字符,所以它最多只能表示<code>256</code>种不同的字符。</p>
<p>而<code>UTF-8</code>可以使用<strong>8位~32位</strong>二进制来表示一个字符,这意味着它可以编码超过一百万个字符,</p>
<p>包括世界上的每种语言和各种表情符号等复杂字符。</p>
<p>通过<strong>字符编码</strong>,我们可以将二进制和字符互相转换,</p>
<p>再通过<strong>字符串长度</strong>信息,我们将内存中的二进制转换为字符串时,就能知道何时停止。</p>
<p><code>Rust</code>中的字符串,统一采用<code>UTF-8</code>编码,下面一一介绍各种字符串类型及其使用场景。</p>
<p class="maodian"></p><h2>2. String 和 &str</h2>
<p><code>String</code>和<code>&str</code>是<code>Rust</code>中使用最多的两种字符串类型,也是在使用中容易混淆的两种类型。</p>
<p><code>String</code>是分配在堆上的,可增长的UTF-8字符串,</p>
<p>它拥有底层的数据,并且在超出其定义的范围被自动清理释放。</p>
<div class="jb51code"><pre class="brush:plain;">let my_string = String::from("databook");
println!(
"pointer: {:p}, length: {}, capacity: {}",
&my_string,
my_string.len(),
my_string.capacity()
);</pre></div>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202409/202409260901133.png" /></p>
<p>对于一个<code>String</code>,主要部分有3个:</p>
<ul><li><code>Pointer</code>:指向堆内存中字符串的起始位置</li><li><code>Length</code>:有效字符串的长度</li><li><code>Capacity</code>:字符串<code>my_string</code>总共占用的空间</li></ul>
<p>注意这里<code>Length</code>和<code>Capacity</code>的区别,<code>Length</code>是<code>my_string</code>中有效字符的长度,也就是字符串实际的长度;</p>
<p><code>Capacity</code>表示系统为<code>my_string</code>分配的内存空间,一般来说,<code>Capacity >= Length</code>。</p>
<p>通常不需要直接处理<code>Capacity</code>,但它的存在对于编写高效且资源敏感的<code>Rust</code>代码时很重要。</p>
<p>特别是,当你知道即将向<code>String</code>添加大量内容时,可能会事先手动保留足够的<code>Capacity</code>以避免多次内存重新分配。</p>
<p><code>&str</code>则是一个字符串的切片,它表示一个连续的字符序列,</p>
<p>它是一个<strong>借用类型</strong>,并不拥有字符串数据,只包含指向切片开头的指针和切片长度。</p>
<div class="jb51code"><pre class="brush:plain;">let my_str: &str = "databook";
println!("pointer: {:p}, length: {}", &my_str, my_str.len());</pre></div>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202409/202409260901134.png" /></p>
<p>注意,<code>&str</code>没有<code>Capacity</code>方法,因为它只是一个借用,内容不可能增加。</p>
<p>最后,对于<code>String</code>和<code>&str</code>,使用时建议:</p>
<ul><li>在运行时动态创建或修改字符串数据时,请使用 <code>String</code></li><li>读取或分析字符串数据而不对其进行更改时,请使用 <code>&str</code></li></ul>
<p class="maodian"></p><h2>3. Vec 和 &</h2>
<p>这两种形式是将字符串表示位字节的形式,其中<code>Vec</code>是字节向量,<code>&</code>是字节切片。</p>
<p>它们只是将字符串中的各个字符转换成字节形式。</p>
<p><code>as_bytes</code>方法可将<code>&str</code>转换为<code>&</code>;</p>
<p><code>into_bytes</code>方法可将<code>String</code>转换为<code>Vec<u8></code>。</p>
<div class="jb51code"><pre class="brush:plain;">let my_str: &str = "databook";
let my_string = String::from("databook");
let s: & = my_str.as_bytes();
let ss: Vec<u8> = my_string.into_bytes();
println!("s: {:?}", s);
println!("ss: {:?}", ss);
/* 运行结果
s:
ss:
*/</pre></div>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202409/202409260901135.png" /></p>
<p>在UTF-8编码中,每个英文字母对应<strong>1个字节</strong>,而一个中文汉字对应<strong>3个字节</strong>。</p>
<div class="jb51code"><pre class="brush:plain;">let my_str: &str = "中文";
let my_string = String::from("中文");
let s: & = my_str.as_bytes();
let ss: Vec<u8> = my_string.into_bytes();
println!("s: {:?}", s);
println!("ss: {:?}", ss);
/* 运行结果
s:
ss:
*/</pre></div>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202409/202409260901136.png" /></p>
<p><code>Vec</code>和<code>&</code>以字节的形式存储字符串,不用关心字符串的具体编码,</p>
<p>这在网络中传输二进制文件或者数据包时非常有用,可以有效每次传输多少个字节。</p>
<p class="maodian"></p><h2>4. str 系列</h2>
<p><code>str</code>类型本身是不能直接使用的,因为它的大小在编译期无法确定,不符合<code>Rust</code>的安全规则。</p>
<p>但是,它可以与其他具有特殊用途的指针类型一起使用。</p>
<p class="maodian"></p><h3>4.1. Box<str></h3>
<p>如果需要一个字符串切片的所有权(<code>&str</code>是借用的,没有所有权),那么可以使用<code>Box</code>智能指针。</p>
<p>当你想要冻结字符串以防止进一步修改或通过删除额外容量来节省内存时,它非常有用。</p>
<p>比如,下面的代码,我们将一个<code>String</code>转换为<code>Box<str></code>,</p>
<p>这样,可以确保它不会在其他地方被修改,也可以删除它,因为<code>Box<str></code>拥有字符串的所有权。</p>
<div class="jb51code"><pre class="brush:plain;">let my_string = String::from("databook");
let my_box_str = my_string.into_boxed_str();
println!("{}", my_box_str);
// 这一步会报错,因为所有权已经转移
// 这是 Box<str> 和 &str 的区别
// println!("{}", my_string);</pre></div>
<p class="maodian"></p><h3>4.2. Rc<str></h3>
<p>当你想要在多个地方<strong>共享</strong>一个不可变的字符串的<strong>所有权</strong>,但是又不克隆实际的字符串数据时,</p>
<p>可以尝试使用<code>Rc<str></code>智能指针。</p>
<p>比如,我们有一个非常大的文本,想在多个地方使用,又不想复制多份占用内存,可以用<code>Rc<str></code>。</p>
<div class="jb51code"><pre class="brush:plain;">let my_str: &str = "very long text ....";
let rc_str1: Rc<str> = Rc::from(my_str);
let rc_str2 = Rc::clone(&rc_str1);
let rc_str3 = Rc::clone(&rc_str1);
println!("rc_str1: {}", rc_str1);
println!("rc_str2: {}", rc_str2);
println!("rc_str3: {}", rc_str3);
/* 运行结果
rc_str1: very long text ....
rc_str2: very long text ....
rc_str3: very long text ....
*/</pre></div>
<p>这样,在不实际克隆字符串数据的情况下,让多个变量拥有其所有权。</p>
<p class="maodian"></p><h3>4.3. Arc<str></h3>
<p><code>Arc<str></code>与<code>Rc<str></code>的功能类似,主要的区别在于<code>Arc<str></code>是线程安全的。</p>
<p>如果在多线程环境下,请使用<code>Arc<str></code>。</p>
<div class="jb51code"><pre class="brush:plain;">let my_str: &str = "very long text ....";
let arc_str: Arc<str> = Arc::from(my_str);
let mut threads = vec![];
let mut cnt = 0;
while cnt < 5 {
let s = Arc::clone(&arc_str);
let t = thread::spawn(move || {
println!("thread-{}: {}", cnt, s);
});
threads.push(t);
cnt += 1;
}
for t in threads {
t.join().unwrap();
}
/* 运行结果
thread-0: very long text ....
thread-3: very long text ....
thread-2: very long text ....
thread-1: very long text ....
thread-4: very long text ....
*/</pre></div>
<p>上面的代码中,在5个线程中共享了字符串数据。</p>
<p>上面<strong>运行结果</strong>中,线程顺序是不固定的,多执行几遍会有不一样的顺序。</p>
<p class="maodian"></p><h3>4.4. Cow<str></h3>
<p><code>Cow</code>是<code>Copy-on-Write</code>(写入时复制)的缩写,</p>
<p>当你需要实现一个功能,根据字符串的内容来决定是否需要修改它,使用<code>Cow</code>就很合适。</p>
<p>比如,过滤敏感词汇时,我们把敏感词汇替换成<code>xx</code>。</p>
<div class="jb51code"><pre class="brush:plain;">fn filter_words(input: &str) -> Cow<str> {
if input.contains("sb") {
let output = input.replace("sb", "xx");
return Cow::Owned(output);
}
Cow::Borrowed(input)
}</pre></div>
<p>当输入字符串<code>input</code>中含有敏感词<code>sb</code>时,会重新分配内存,生成新字符串;</p>
<p>否则直接使用原字符串,提高内存效率。</p>
<p class="maodian"></p><h2>5. CStr 和 CString</h2>
<p><code>CStr</code>和<code>CString</code>是与<strong>C语言</strong>交互时用于处理字符串的两种类型。</p>
<p><code>CStr</code>用于在<code>Rust</code>中安全地访问由<strong>C语言</strong>分配的字符串;</p>
<p>而<code>CString</code>用于在<code>Rust</code>中创建和管理可以安全传递给<strong>C语言</strong>函数的字符串。</p>
<p><strong>C风格</strong>的字符串与<code>Rust</code>中的字符串实现方式不一样,</p>
<p>比如,C语言中的字符串都是以<code>null</code>字符<code>\0</code>结尾的字节数组,这点就与<code>Rust</code>很不一样。</p>
<p>所以Rust单独封装了这两种类型(<code>CStr</code>和<code>CString</code>),可以安全的与C语言进行字符串交互,从而实现与现有的C语言库和API无缝集成。</p>
<p class="maodian"></p><h2>6. OsStr 和 OsString</h2>
<p><code>OsStr</code> 和 <code>OsString</code> 是用于处理与操作系统兼容的字符串类型。</p>
<p>主要用于需要与操作系统API进行交互的场景,这些API一般特定于平台的字符串编码(比如<code>Windows</code>上的<code>UTF-16</code>,以及大多数<code>Unix-like</code>系统上的<code>UTF-8)</code>。</p>
<p><code>OsStr</code> 和<code>OsString</code> 也相当于<code>str</code>和<code>String</code>的关系,所以<code>OsStr</code> 一般不直接在代码中使用,</p>
<p>使用比较多的是<code>&OsStr</code>和<code>OsString</code>。</p>
<p>这两个类型一般用于读取/写入操作系统环境变量或者与系统API交互时,帮助我们确保字符串以正确的格式传递。</p>
<p class="maodian"></p><h2>7. Path 和 PathBuf</h2>
<p>这两个类型看名字似乎和字符串关系不大,实际上它们是专门用来处理文件路径字符串的。</p>
<p>在不同的文件系统中,对于文件路径的格式,路径中允许使用的字符都不一样,比如,<code>windows</code>系统中文件路径甚至不区分大小写。</p>
<p>使用<code>Path</code> 和 <code>PathBuf</code>,我们编码时就不用分散精力去关心具体使用的是哪种文件系统。</p>
<p><code>Path</code>和<code>PathBuf</code>的主要区别在于可变性和所有权,</p>
<p>如果需要频繁读取和查询路径信息而不修改它,<code>Path</code>是一个好选择;</p>
<p>如果需要动态构建或修改路径内容,<code>PathBuf</code>则更加合适。</p>
<p class="maodian"></p><h2>8. 总结</h2>
<p>总之,<code>Rust</code>中字符串类型之所以多,是因为根据不同的用途对字符串类型做了分类。</p>
<p>这也是为了处理不同的应用场景时让程序发挥最大的性能,毕竟,<strong>安全</strong>和<strong>高性能</strong>一直是<code>Rust</code>最大的卖点。</p>
<p>到此这篇关于Rust字符串类型全解析(最新推荐)的文章就介绍到这了,更多相关Rust字符串类型内容请搜索琼殿技术社区以前的文章或继续浏览下面的相关文章希望大家以后多多支持琼殿技术社区!</p>
<div class="art_xg">
<b>您可能感兴趣的文章:</b><ul><li>Rust 利用 chrono 库实现日期和字符串互相转换的示例</li><li>Rust中字符串String集合的具有使用</li><li>Rust中字符串类型&str和String的使用</li><li>Rust中字符串类型String的46种常用方法分享</li><li>Rust动态调用字符串定义的Rhai函数方式</li></ul>
</div>
</div>
<!--endmain-->
頁:
[1]