为什么这个问题在Stack Overflow上如此热门?
如果你曾经用C#编程,并遇到需要将字符串转换为字节数组的情况,你肯定问过自己:“为什么我必须指定编码?字符串不就是字符串吗!”这个问题在Stack Overflow上获得了数千次浏览并非偶然。它触及了一个根本性的误解:我们如何看待文本与计算机如何看待文本之间的差异。
在本文中,我们不仅会提供现成的代码,还会深入探讨为什么Encoding.UTF8.GetBytes("hello")不是权宜之计,而是唯一正确的方法。你将了解如何在C#中获取字符串的字节表示而不会丢失数据,以及“自动编码选择”背后隐藏的秘密。
关于“无编码字符串”的迷思
许多初学者认为C#中的字符串只是一系列字节。事实并非如此。在内部,.NET中的string以UTF-16(双字节字符)存储。当你写下"Привет"时,它在内存中占用12个字节(6个字符 × 2字节)。但如果你尝试将此字符串发送到文件、网络或数据库,你几乎总是需要另一种格式——最常见的是UTF-8。
尝试“无需编码”获取字节,就像问:“如何将一本书从俄语翻译成英语,而不指定语言?”这是不可能的。编码是将字符转换为字节的规则。没有编码,你只会得到一团乱码。
不应该怎么做(典型错误)
string text = "Hello, мир!";byte[] wrongBytes = System.Text.Encoding.Default.GetBytes(text); // 糟糕!这段代码使用了系统默认编码(在俄语Windows上通常是Windows-1251)。如果你在另一台机器或Linux上运行此代码,你会得到不同的字节。你的应用程序将变得不可预测。
正确方法:显式指定编码
获取一致的字符串字节表示的唯一可靠方法是显式指定编码。最流行且推荐的选择是UTF-8:
string text = "Hello, мир!";byte[] utf8Bytes = System.Text.Encoding.UTF8.GetBytes(text);// 结果:[72, 101, 108, 108, 111, 44, 32, 208, 188, 208, 184, 209, 128, 33]这段代码在任何平台上都会产生相同的结果。为什么?因为UTF-8是一个国际标准,在任何地方都工作一致。
其他编码选项(何时需要)
- ASCII — 仅限拉丁字符,丢弃所有大于127的字符。
- Unicode (UTF-16LE) — C#的内部格式,但存储效率不高。
- Windows-1251 — 遗留编码,仅用于与旧系统兼容。
对于99%的现代任务,请使用Encoding.UTF8。
如果我想“不指定编码”呢?有技巧吗?
从技术上讲,你可以使用BitConverter.ToString()或带有指针的不安全代码,但这会给你内存中存储的原始UTF-16字节。由于以下几个原因,这是一个坏主意:
- 结果依赖于平台(小端序 vs 大端序)。
- 对于拉丁字符,你会得到多余的零字节(每个字符占用2字节)。
- 当.NET版本更改时,内部表示可能会改变(可能性很小,但有可能)。
危险代码示例,请勿模仿:
unsafe{ string text = "Hello"; fixed (char* ptr = text) { byte* bytePtr = (byte*)ptr; // 处理原始UTF-16字节 }}这虽然有效,但违反了“如果你不了解后果,就不要触碰内部机制”的原则。