如何将字节向量(u8)转换为字符串?

257

我正在尝试使用Rust编写简单的TCP/IP客户端,需要打印从服务器获得的缓冲区。

如何将Vec<u8>(或&[u8])转换为String

6个回答

266

将一个字节切片转换为字符串切片(假设采用UTF-8编码):

use std::str;

//
// pub fn from_utf8(v: &[u8]) -> Result<&str, Utf8Error>
//
// Assuming buf: &[u8]
//

fn main() {

    let buf = &[0x41u8, 0x41u8, 0x42u8];

    let s = match str::from_utf8(buf) {
        Ok(v) => v,
        Err(e) => panic!("Invalid UTF-8 sequence: {}", e),
    };

    println!("result: {}", s);
}

此转换是原地进行的,不需要分配内存。如果需要,您可以通过在字符串切片上调用.to_owned()方法来创建一个String字符串(还有其他可选项)。

如果您确定字节切片是有效的UTF-8,并且您不想产生有效性检查的开销,则该函数有一个不安全版本from_utf8_unchecked,它具有相同的行为但跳过了检查。

如果您需要一个String而不是&str,您也可以考虑使用String::from_utf8

转换函数的库引用:


你可能想要补充说明,这是因为Vec可以强制转换为切片。 - torkleyy
4
尽管使用 from_utf8 确实不会分配内存,但值得一提的是它需要扫描数据以验证 utf-8 的正确性。因此,这不是一个 O(1) 操作(一开始可能会误以为是)。 - Zargony

162

我偏爱String::from_utf8_lossy

fn main() {
    let buf = &[0x41u8, 0x41u8, 0x42u8];
    let s = String::from_utf8_lossy(buf);
    println!("result: {}", s);
}

它会将无效的UTF-8字节转换为�,因此不需要错误处理。当您不需要它时很好用,我几乎不需要它。您实际上从中获得一个String。这应该会使从服务器获取的内容更容易打印出来。

有时候您可能需要使用into_owned() 方法,因为它是写时克隆。


8
非常感谢您提供 into_owned() 的建议!这正是我要找的(它可以将字符串变为一个合适的 String 对象,例如可以作为方法的返回值)。 - Per Lundberg
1
� 是Unicode U+FFFD(UTF-8序列0xEF 0xBF 0xBD(八进制357 277 275)),“替换字符”。在某些文本编辑器中,可以通过正则表达式模式\x{FFFD}进行搜索。 - Peter Mortensen

95

如果您实际上有一个字节向量(Vec<u8>),并希望将其转换为String,最有效的方法是使用String::from_utf8重用分配:

fn main() {
    let bytes = vec![0x41, 0x42, 0x43];
    let s = String::from_utf8(bytes).expect("Found invalid UTF-8");
    println!("{}", s);
}

3
请注意,如@Bjorn Tipling所述,您可能会认为可以在此处使用String::from_utf8_lossy,那么您就不需要expect调用,但其输入是字节片(&'a [u8])。另一方面,也有from_utf8_unchecked。如果您确定字节片是有效的UTF-8,并且不希望承担转换开销,那么有一种不安全的版本[from_utf8_lossy] from_utf8_unchecked,它具有相同的行为但跳过了检查。 - James Ray
请注意,您可以使用&vec_of_bytes将其转换回字节切片,如from_utf8_lossy的示例所列。https://doc.rust-lang.org/std/string/struct.String.html#method.from_utf8_lossy - James Ray
@JamesRay,有没有一种方法可以在不重新分配内存的情况下获得“from_utf8_lossy”的行为?如果我从一个Vec开始,然后在将其转换为字符串之前对其进行引用,例如String::from_utf8_lossy(&my_vec),那么当我实际上不需要时,我将最终重新分配内存。 - Michael Dorst
哦,算了吧。from_utf8_lossy 返回的是 Cow<str>,而不是 String。如果没有无效字符,则不会重新分配内存,但如果有,则会重新分配。 - Michael Dorst

10
在我的情况下,我只需要将数字转换为字符串,而不是根据某种编码将数字转换为字母,因此我这样做了。
fn main() {
    let bytes = vec![0x41, 0x42, 0x43];
    let s = format!("{:?}", &bytes);
    println!("{}", s);
}

这是我尝试过的,但感觉可能有错或其他问题。也许我现在会坚持这个。 - MikeTheSapien

3
为了将可能包含非UTF-8字符/字节序列的Vec<u8>优化地转换为UTF-8 String,且不进行任何不必要的分配,您应该乐观地尝试调用String::from_utf8(),然后退而求其次使用String::from_utf8_lossy()
let buffer: Vec<u8> = ...;

let utf8_string = String::from_utf8(buffer)
    .map_err(|non_utf8| String::from_utf8_lossy(non_utf8.as_bytes()).into_owned())
    .unwrap();

其他答案中提出的方法即使在快乐情况下(向量中有有效的UTF-8数据)也会导致内存中有两个拥有的缓冲区:一个带有原始的u8字节,另一个是作为一个String拥有其字符的形式。相反,该方法将尝试消耗Vec并将其直接编组为Unicode字符串,仅在失败时会为包含失误解码的输出的新字符串分配空间。

-3

v.iter().map(|&c| char::from(c)).collect()


这真的是一个非常糟糕的方法。从多个方面来看都不好。使用被接受的答案会好得多。 - Chayim Friedman
当二进制向量中含有一些控制字符时,它可能不是一个有效的UTF-8编码。你只想将这些字节作为连续的字符而不是尝试解码或解释任何内容。这样做会慢一些吗?可能会——因为它需要进行内存分配。我建议你针对每种情况使用适当的工具。 - stach
我来自Python,那里使用包含任意字符的Unicode字符串非常普遍。不是UTF8编码的字节流,而是像'\u0001\u00FF'这样的Unicode字符串。为什么我们在Rust中不能有这样的字符串呢? - stach
如果这是Unicode,即使只是部分的,那么你的解决方案也无法工作。而在Rust中,与Python不同,String被保证完全是UTF-8编码的。如果你想要部分UTF-8编码的内容,可以将其保留为Vec<u8>。你可以使用像bstr这样的crate来对其进行各种字符串操作。 - Chayim Friedman
每个Unicode代码点从0x00到0xFF都有明确定义。根据定义,这种方法不会生成任何其他代码点。它甚至不涉及这些代码点/UTF-8的内部表示的概念。 - undefined
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接