如何在不手动指定编码的情况下在 C# 中获得字符串的一致字节表示形式?

How do I get a consistent byte representation of strings in C# without manually specifying an encoding?

提问人:Agnel Kurian 提问时间:1/23/2009 最后编辑:John SmithAgnel Kurian 更新时间:10/4/2022 访问量:1186731

问:

如何在 .NET (C#) 中将 a 转换为 a,而无需手动指定特定编码?stringbyte[]

我将加密字符串。我可以在不转换的情况下对其进行加密,但我仍然想知道为什么编码会在这里发挥作用。

另外,为什么要考虑编码?我不能简单地获取字符串存储在哪些字节中吗?为什么依赖于字符编码?

C# .NET 字符串 字符编码

评论

30赞 Agnel Kurian 1/23/2009
每个字符串都存储为字节数组,对吧?为什么我不能简单地拥有这些字节?
149赞 Lucas Jones 4/13/2009
编码将字符映射到字节的内容。例如,在 ASCII 中,字母“A”映射到数字 65。在不同的编码中,它可能不相同。但是,在 .NET Framework 中采用的字符串的高级方法使这在很大程度上无关紧要(本例除外)。
23赞 Greg 12/2/2009
扮演魔鬼的拥护者:如果你想获取内存中字符串的字节(因为 .NET 使用它们)并以某种方式操作它们(即 CRC32),并且从来没有想过将其解码回原始字符串......你为什么会关心编码或如何选择使用哪一种编码并不简单。
36赞 George 8/28/2014
char 不是字节,字节也不是 char。字符既是字体表的键,也是词法传统。字符串是字符序列(单词、段落、句子和标题也有它们自己的词汇传统,这些传统证明了它们自己的类型定义是合理的——但我离题了)。与整数、浮点数和其他所有内容一样,字符被编码为字节。曾经有一段时间,编码是一对一的简单:ASCII。然而,为了适应所有人类符号系统,一个字节的 256 个排列是不够的,并且设计了编码以选择性地使用更多字节。
10赞 Greg D 12/13/2014
四年后,我坚持我最初对这个问题的评论。它从根本上是有缺陷的,因为我们谈论的是字符串这一事实意味着解释。该字符串的编码是序列化协定的隐式部分,否则它只是一堆无意义的位。如果你想要无意义的位,为什么要从字符串中生成它们呢?只需写一堆 0 即可完成。

答:

20赞 gkrogers 1/23/2009 #1
byte[] strToByteArray(string str)
{
    System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
    return enc.GetBytes(str);
}

评论

1赞 Agnel Kurian 1/23/2009
但是,为什么要考虑编码呢?为什么我不能简单地获取字节而不必查看正在使用的编码?即使它是必需的,String 对象本身难道不应该知道正在使用什么编码并简单地转储内存中的内容吗?
6赞 JB King 1/24/2009
这并不总是有效。一些特殊字符可能会在使用这种方法时迷失方向,我发现这种方法很困难。
1144赞 bmotmans 1/23/2009 #2

这取决于字符串的编码(ASCIIUTF-8 等)。

例如:

byte[] b1 = System.Text.Encoding.UTF8.GetBytes (myString);
byte[] b2 = System.Text.Encoding.ASCII.GetBytes (myString);

编码很重要的一个小示例:

string pi = "\u03a0";
byte[] ascii = System.Text.Encoding.ASCII.GetBytes (pi);
byte[] utf8 = System.Text.Encoding.UTF8.GetBytes (pi);

Console.WriteLine (ascii.Length); //Will print 1
Console.WriteLine (utf8.Length); //Will print 2
Console.WriteLine (System.Text.Encoding.ASCII.GetString (ascii)); //Will print '?'

ASCII 根本无法处理特殊字符。

在内部,.NET Framework 使用 UTF-16 来表示字符串,因此,如果您只想获取 .NET 使用的确切字节,请使用 .System.Text.Encoding.Unicode.GetBytes (...)

有关更多信息,请参见 .NET Framework 中的字符编码 (MSDN)。

评论

16赞 Agnel Kurian 1/23/2009
但是,为什么要考虑编码呢?为什么我不能简单地获取字节而不必查看正在使用的编码?即使它是必需的,String 对象本身难道不应该知道正在使用什么编码并简单地转储内存中的内容吗?
67赞 AnthonyWJones 1/23/2009
.NET 字符串始终编码为 Unicode。所以使用 System.Text.Encoding.Unicode.GetBytes();获取 .NET 用于表示字符的字节集。但是,你为什么要这样做呢?我推荐 UTF-8,尤其是当大多数字符都在西方拉丁语集中时。
9赞 Joel Coehoorn 1/23/2009
另外:如果检索它们的系统不处理该编码或将其处理为错误的编码,则字符串内部使用的确切字节无关紧要。如果它都在 .Net 中,为什么要转换为字节数组。否则,最好明确编码
13赞 Ash 1/28/2010
@Joel,请小心 System.Text.Encoding.Default,因为它在运行它的每台计算机上可能不同。因此,建议始终指定编码,例如 UTF-8。
27赞 user541686 4/30/2012
除非您(或其他人)确实打算解释数据,而不是将其视为通用的“字节块”,否则您不需要编码。对于压缩、加密等,担心编码是没有意义的。请参阅我的答案,了解如何在不担心编码的情况下执行此操作。(我可能会给 -1 分,因为说你需要担心编码,而你不需要担心编码,但我今天并不觉得特别刻薄。 :P)
-2赞 cyberbobcat 1/23/2009 #3
// C# to convert a string to a byte array.
public static byte[] StrToByteArray(string str)
{
    System.Text.ASCIIEncoding  encoding=new System.Text.ASCIIEncoding();
    return encoding.GetBytes(str);
}


// C# to convert a byte array to a string.
byte [] dBytes = ...
string str;
System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
str = enc.GetString(dBytes);

评论

7赞 Jon Skeet 1/27/2009
1)由于使用ASCII作为编码,这将丢失数据。2) 创建新的 ASCIIEncoding 没有意义 - 只需使用 Encoding.ASCII 属性即可。
100赞 Zhaph - Ben Duguid 1/23/2009 #4

您需要考虑编码,因为 1 个字符可以由 1 个或多个字节(最多约 6 个)表示,并且不同的编码会以不同的方式处理这些字节。

乔尔对此发表了一篇帖子:

每个软件开发人员绝对必须了解 Unicode 和字符集的绝对最低限度(没有任何借口!

评论

7赞 Agnel Kurian 1/23/2009
“1 个字符可以由 1 个或多个字节表示”我同意。我只想要这些字节,而不管字符串采用什么编码。字符串存储在内存中的唯一方法是以字节为单位。甚至字符也存储为 1 个或更多字节。我只是想得到他们的字节。
17赞 user541686 4/30/2012
除非您(或其他人)确实打算解释数据,而不是将其视为通用的“字节块”,否则您不需要编码。对于压缩、加密等,担心编码是没有意义的。请参阅我的答案,了解如何在不担心编码的情况下执行此操作。
10赞 Zhaph - Ben Duguid 4/30/2012
@Mehrdad - 完全,但正如我最初回答时所说,最初的问题并没有警告这些字节在转换后会发生什么 OP,对于未来的搜索者来说,相关的信息是相关的 - Joel 的回答很好地涵盖了这一点 - 正如您在答案中所说的那样:只要您坚持在 .NET 世界中, 并使用您的方法转换为/从,您很高兴。一旦你走出这个范围,编码就很重要了。
1赞 DevSolar 10/8/2018
一个码位最多可以用 4 个字节表示。(一个 UTF-32 代码单元、一个 UTF-16 代理项对或 4 个字节的 UTF-8。UTF-8 需要超过 4 个字节的值位于 0x0 之外。0x10FFFF Unicode 范围。;-)
11赞 Hans Passant 1/23/2009 #5

关键问题是字符串中的字形需要 32 位(字符代码为 16 位),但一个字节只有 8 位备用。除非您将自己限制为仅包含 ASCII 字符的字符串,否则不存在一对一映射。System.Text.Encoding 有很多方法可以将字符串映射到 byte[],您需要选择一种能够避免信息丢失的方法,并且当客户端需要将 byte[] 映射回字符串时,该方法易于客户端使用。

Utf8 是一种流行的编码,它紧凑且无损。

评论

3赞 Joel Mueller 1/24/2009
仅当大多数字符都采用英语 (ASCII) 字符集时,UTF-8 才是紧凑的。如果您有一长串中文字符,则该字符串的 UTF-16 编码将比 UTF-8 更紧凑。这是因为 UTF-8 使用一个字节来编码 ASCII,否则使用 3 个字节(或 4 个字节)。
8赞 Hans Passant 1/24/2009
真。但是,如果您熟悉处理中文文本,您怎么可能不知道编码呢?
14赞 Ed Marty 1/23/2009 #6

我不确定,但我认为字符串将其信息存储为字符数组,这对于字节来说效率低下。具体来说,Char 的定义是“表示 Unicode 字符”。

以以下示例为例:

String str = "asdf éß";
String str2 = "asdf gh";
EncodingInfo[] info =  Encoding.GetEncodings();
foreach (EncodingInfo enc in info)
{
    System.Console.WriteLine(enc.Name + " - " 
      + enc.GetEncoding().GetByteCount(str)
      + enc.GetEncoding().GetByteCount(str2));
}

请注意,Unicode 答案在两种情况下都是 14 个字节,而 UTF-8 答案对于第一个只有 9 个字节,对于第二个只有 7 个字节。

因此,如果您只想要字符串使用的字节,只需使用 ,但使用存储空间效率低下。Encoding.Unicode

评论

0赞 Christoph 2/14/2021
也就是说,如果你的字符串是英文的 - 是中文的,你最好用 UTF-16。
53赞 Joel Coehoorn 1/23/2009 #7

您的问题的第一部分(如何获取字节)已经由其他人回答:查看命名空间。System.Text.Encoding

我将解决您的后续问题:为什么需要选择编码?为什么你不能从字符串类本身得到它?

答案分为两部分。

首先,字符串类内部使用的字节并不重要,每当你认为它们这样做时,你都可能会引入一个错误。

如果你的程序完全在 .Net 世界中,那么你根本不需要担心获取字符串的字节数组,即使你通过网络发送数据也是如此。相反,请使用 .Net 序列化来担心传输数据。您不再担心实际的字节数:序列化格式化程序会为您完成这项工作。

另一方面,如果您将这些字节发送到无法保证会从 .Net 序列化流中提取数据的位置,该怎么办?在这种情况下,您确实需要担心编码,因为显然,这个外部系统很关心。因此,字符串使用的内部字节无关紧要:您需要选择一种编码,以便可以在接收端明确此编码,即使它与 .Net 内部使用的编码相同。

我知道在这种情况下,您可能更愿意在可能的情况下使用字符串变量在内存中存储的实际字节,这样可以节省一些创建字节流的工作。但是,我告诉你,与确保你的输出在另一端被理解,并保证你的编码必须明确相比,这并不重要。此外,如果您真的想匹配您的内部字节,您已经可以选择编码,并获得这些性能节省。Unicode

这就把我带到了第二部分......选择编码告诉 .Net 使用底层字节。您确实需要选择这种编码,因为当一些新奇的 Unicode-Plus 出现时,.Net 运行时需要自由使用这种更新、更好的编码模型,而不会破坏您的程序。但是,就目前(以及可预见的未来)而言,只需选择 Unicode 编码即可满足您的需求。Unicode

了解字符串必须重写为连线也很重要,即使使用匹配的编码,这至少也涉及位模式的一些转换。计算机需要考虑诸如大端与小端序、网络字节顺序、数据包化、会话信息等内容。

评论

11赞 Ash 1/28/2010
在 .NET 中,有些区域必须获取字符串的字节数组。许多 .NET Cryptrography 类都包含接受字节数组或流的方法,例如 ComputeHash()。您别无选择,只能先将字符串转换为字节数组(选择编码),然后选择性地将其包装在流中。但是,只要您选择一种编码(即 UTF8)并坚持下去,这就没有问题。
2赞 Karl Stephen 4/13/2022
当我不知道编码是关于什么的并且由于懒惰而拒绝学习时,我的心情和 OP 完全一样(只要给我字节......你的答案是第一个(在顶部)关心给出明确警告的答案。我很高兴在我的 PC 上编写和读取二进制文件......直到我不得不与 MAC/Linux 用户打交道、网络、将应用程序升级到最新的操作系统、更多地了解字节序、自定义编码(电子 ROM 和数据)。这一天 .Net 将用 4 个字节编码 Unicode,UTF8 最多 8 个字节。我学会了尽可能避免绕过本机方法的艰难方法。
0赞 Luaan 10/10/2022
@KarlStephen 在 ASCII 之外的世界里,我们已经有很多经验,三十年前将任何事物视为所谓的“纯文本”是多么愚蠢。哎呀,我的语言有八种不同的不兼容编码,你实际上很可能同时在同一台DOS机器上拥有这些编码。疯狂的是,仍然有一些备受瞩目的应用程序“不关心编码”——上次我在亚马逊上更改地址时,它仍然无法正确代表我的名字。2016-左右,我仍然必须替换特殊字符(这会更改名称)。
0赞 Karl Stephen 10/18/2022
@Luaan:我不认为这是愚蠢的,只是以忽视他人为代价而提高了效率。我说为什么不使用具有更标准化字符的 Unicode64(可能包括 covid 字符)?我仍然相信选择是关键:提供 unicode 扩展更新,但也要保持编码的活力。可悲的是,最近的操作系统往往会减少编码支持,尤其是在移动设备上。出于这个原因,我确实支持这个答案:对字符串数据进行编码并指定使用的编码,这是安全有效的方法。这确实意味着,每个开发人员都应该学习编码......还没有
0赞 Ben Voigt 6/3/2023
我只想提一下,有时忽略跨平台兼容性是正确的做法,主要是对于可以随时重建的缓存,需要一些计算费用。在这种情况下,性能是唯一的问题,因为如果没有性能,您还不如重建缓存。要遵循的例子......
121赞 Michael Buen 1/24/2009 #8
BinaryFormatter bf = new BinaryFormatter();
byte[] bytes;
MemoryStream ms = new MemoryStream();

string orig = "喂 Hello 谢谢 Thank You";
bf.Serialize(ms, orig);
ms.Seek(0, 0);
bytes = ms.ToArray();

MessageBox.Show("Original bytes Length: " + bytes.Length.ToString());

MessageBox.Show("Original string Length: " + orig.Length.ToString());

for (int i = 0; i < bytes.Length; ++i) bytes[i] ^= 168; // pseudo encrypt
for (int i = 0; i < bytes.Length; ++i) bytes[i] ^= 168; // pseudo decrypt

BinaryFormatter bfx = new BinaryFormatter();
MemoryStream msx = new MemoryStream();            
msx.Write(bytes, 0, bytes.Length);
msx.Seek(0, 0);
string sx = (string)bfx.Deserialize(msx);

MessageBox.Show("Still intact :" + sx);

MessageBox.Show("Deserialize string Length(still intact): " 
    + sx.Length.ToString());

BinaryFormatter bfy = new BinaryFormatter();
MemoryStream msy = new MemoryStream();
bfy.Serialize(msy, sx);
msy.Seek(0, 0);
byte[] bytesy = msy.ToArray();

MessageBox.Show("Deserialize bytes Length(still intact): " 
   + bytesy.Length.ToString());

评论

2赞 Joel Coehoorn 1/24/2009
您可以对所有这些操作使用相同的 BinaryFormatter 实例
3赞 11/19/2010
很有意思。显然,它会删除任何高代理项 Unicode 字符。请参阅有关 [BinaryFormatter]
1赞 harmonik 2/20/2009 #9

两种方式:

public static byte[] StrToByteArray(this string s)
{
    List<byte> value = new List<byte>();
    foreach (char c in s.ToCharArray())
        value.Add(c.ToByte());
    return value.ToArray();
}

public static byte[] StrToByteArray(this string s)
{
    s = s.Replace(" ", string.Empty);
    byte[] buffer = new byte[s.Length / 2];
    for (int i = 0; i < s.Length; i += 2)
        buffer[i / 2] = (byte)Convert.ToByte(s.Substring(i, 2), 16);
    return buffer;
}

我倾向于使用底部的比顶部更频繁,没有对它们的速度进行基准测试。

评论

4赞 Agnel Kurian 2/23/2009
多字节字符呢?
0赞 mg30rg 1/11/2018
@AgnelKurian Msdn 说:“此方法返回一个无符号字节值,该值表示传递给它的 Char 对象的数字代码。在 .NET Framework 中,Char 对象是一个 16 位值。这意味着该方法适用于返回 ASCII 字符范围或 Unicode C0 控件和基本拉丁语以及 C1 控件和拉丁语补充范围(从 U+0000 到 U+00FF)中的字符的数字代码。
25赞 Konamiman 7/16/2009 #10

另外,请解释为什么应该考虑编码。 我不能简单地获取字符串存储在哪些字节中吗? 为什么这种对编码的依赖性?!!!

因为没有“字符串的字节”这样的东西。

字符串(或更一般地说,文本)由字符组成:字母、数字和其他符号。就这样。然而,计算机对字符一无所知;它们只能处理字节。因此,如果要使用计算机存储或传输文本,则需要将字符转换为字节。你是怎么做到的?这就是编码出现的地方。

编码只不过是将逻辑字符转换为物理字节的约定。最简单和最著名的编码是 ASCII,如果你用英语写作,它就是你所需要的。对于其他语言,您将需要更完整的编码,因为任何Unicode风格都是当今最安全的选择。

因此,简而言之,试图“在不使用编码的情况下获取字符串的字节”与“在不使用任何语言的情况下编写文本”一样不可能。

顺便说一句,我强烈建议你(以及任何人,就此而言)阅读这一小段智慧: 绝对最低限度 每个软件开发人员绝对、肯定必须了解 Unicode 和字符集(没有借口!

评论

2赞 Agnel Kurian 7/16/2009
请允许我澄清一下:编码已用于将“hello world”转换为物理字节。由于字符串存储在我的计算机上,因此我确信它必须以字节为单位存储。我只想访问这些字节以将它们保存在磁盘上或出于任何其他原因。我不想解释这些字节。由于我不想解释这些字节,因此此时需要编码就像需要电话线来调用 printf 一样错位。
3赞 Konamiman 7/22/2009
但同样,除非使用编码,否则没有文本到物理字节转换的概念。当然,编译器以某种方式将字符串存储在内存中 - 但它只是使用内部编码,您(或编译器开发人员以外的任何人)都不知道。因此,无论您做什么,都需要编码才能从字符串中获取物理字节。
0赞 ollb 5/14/2011
@Agnel Kurian:当然,字符串在某处有一堆字节来存储其内容(UTF-16 是公平的)。但是有一个很好的理由阻止你访问它:字符串是不可变的,如果你能获得内部的byte[]数组,你也可以修改它。这会破坏不可变性,而不可变性至关重要,因为多个字符串可能共享相同的数据。使用 UTF-16 编码获取字符串可能只会将数据复制出来。
3赞 Agnel Kurian 5/14/2011
@Gnafoo,字节的副本就可以了。
9赞 Alessandro Annini 3/22/2010 #11

最快的方式

public static byte[] GetBytes(string text)
{
    return System.Text.ASCIIEncoding.UTF8.GetBytes(text);
}

编辑,正如 Makotosan 评论的那样,这是现在最好的方法:

Encoding.UTF8.GetBytes(text)

评论

8赞 Makotosan 2/18/2012
ASCIIE编码.....是不需要的。最好只使用 Encoding.UTF8.GetBytes(text)。
26赞 Gman 3/10/2011 #12

好吧,我已经阅读了所有答案,它们都是关于使用编码的,或者是关于删除未配对代理项的序列化。

例如,当字符串来自 SQL Server 时,情况很糟糕,因为 SQL Server 是从存储密码哈希的字节数组构建的。如果我们从中删除任何内容,它将存储一个无效的哈希值,如果我们想将其存储在 XML 中,我们希望保持它完好无损(因为 XML 编写器会在它找到的任何未配对代理项上删除异常)。

因此,在这种情况下,我使用字节数组的Base64编码,但是,嘿,在互联网上,C#中只有一种解决方案,而且它有错误并且只有一种方法,所以我修复了错误并写回了过程。来了,未来的谷歌员工:

public static byte[] StringToBytes(string str)
{
    byte[] data = new byte[str.Length * 2];
    for (int i = 0; i < str.Length; ++i)
    {
        char ch = str[i];
        data[i * 2] = (byte)(ch & 0xFF);
        data[i * 2 + 1] = (byte)((ch & 0xFF00) >> 8);
    }

    return data;
}

public static string StringFromBytes(byte[] arr)
{
    char[] ch = new char[arr.Length / 2];
    for (int i = 0; i < ch.Length; ++i)
    {
        ch[i] = (char)((int)arr[i * 2] + (((int)arr[i * 2 + 1]) << 8));
    }
    return new String(ch);
}

评论

0赞 Makotosan 2/10/2012
您所要做的就是使用内置转换器,而不是使用自定义方法将字节数组转换为 base64:Convert.ToBase64String(arr);
0赞 Gman 3/7/2012
@Makotosan谢谢你,但我确实用于 base64 转换。但是要获得初始值,我需要对包含二进制数据的数据库做一些事情(这是MSSQL将其返回给我的方式)。因此,上述函数用于 .Convert.ToBase64String(arr); byte[] (data) <-> string (serialized data to store in XML file)byte[] (data)StringString (binary data) <-> byte[] (easy accessible binary data)
42赞 Nathan 7/26/2011 #13

试试这个,代码少得多:

System.Text.Encoding.UTF8.GetBytes("TEST String");

评论

1赞 mg30rg 12/6/2017
那就试试这个,然后哭吧!它会起作用,但是System.Text.Encoding.UTF8.GetBytes("Árvíztűrő tükörfúrógép);System.Text.Encoding.UTF8.GetBytes("Árvíztűrő tükörfúrógép").Length != System.Text.Encoding.UTF8.GetBytes("Arvizturo tukorfurogep").Length"Árvíztűrő tükörfúrógép".Length == "Arvizturo tukorfurogep".Length
9赞 Vlad 2/25/2018
@mg30rg:为什么你觉得你的例子很奇怪?当然,在可变宽度编码中,并非所有字符都具有相同的字节长度。这是怎么回事?
0赞 Nyerguds 3/31/2020
@Vlad 不过,这里一个更有效的注释是,作为编码的 unicode 符号(因此,作为字节),包含自己的变音符号的字符将给出与拆分为添加到字符中的修饰符号的变音符号不同的结果。但是 iirc 在 .net 中有一些方法可以专门将它们分开,以允许获得一致的字节表示。
1赞 user1120193 1/2/2012 #14
bytes[] buffer = UnicodeEncoding.UTF8.GetBytes(string something); //for converting to UTF then get its bytes

bytes[] buffer = ASCIIEncoding.ASCII.GetBytes(string something); //for converting to ascii then get its bytes
305赞 Erik A. Brandstadmoen 4/30/2012 #15

公认的答案非常非常复杂。为此,请使用包含的 .NET 类:

const string data = "A string with international characters: Norwegian: ÆØÅæøå, Chinese: 喂 谢谢";
var bytes = System.Text.Encoding.UTF8.GetBytes(data);
var decoded = System.Text.Encoding.UTF8.GetString(bytes);

如果你没有必要,就不要重新发明轮子......

评论

17赞 Thomas Eding 9/28/2013
如果接受的答案被更改,出于记录目的,它是 Mehrdad 在当前时间和日期的答案。希望OP能够重新审视这个问题,并接受更好的解决方案。
9赞 Jodrell 11/25/2014
原则上很好,但是,编码应该等同于 Mehrdad 的答案。System.Text.Encoding.Unicode
7赞 Erik A. Brandstadmoen 11/26/2014
自原始答案以来,这个问题已经编辑了无数次,所以,也许我的答案有点过时了。我从来没想过要给出一个与Mehrdad的答案相当的答案,而是给出一个明智的方法。但是,你可能是对的。但是,原始问题中的短语“获取字符串已存储在哪些字节中”非常不准确。存储在哪里?在记忆中?在磁盘上?如果在内存中,可能会更精确。System.Text.Encoding.Unicode.GetBytes
8赞 Frédéric 4/7/2016
@AMissico,您的建议是错误的,除非您确定您的字符串与系统默认编码(系统默认旧字符集中仅包含 ASCII 字符的字符串)兼容。但OP中没有一处说明这一点。
6赞 Nyerguds 4/22/2016
@AMissico 不过,它可能会导致程序在不同的系统上给出不同的结果。这从来都不是一件好事。即使是为了制作哈希或其他东西(我认为这就是 OP 对“加密”的意思),相同的字符串仍然应该始终给出相同的哈希值。
1949赞 user541686 4/30/2012 #16

与这里的答案相反,如果不需要解释字节,则无需担心编码!

就像你提到的,你的目标很简单,就是“获取字符串存储在哪些字节中”。
(当然,能够从字节重新构造字符串。

对于这些目标,老实说,我不明白为什么人们总是告诉你你需要编码。您当然不需要为此担心编码。

只需这样做:

static byte[] GetBytes(string str)
{
    byte[] bytes = new byte[str.Length * sizeof(char)];
    System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
    return bytes;
}

// Do NOT use on arbitrary bytes; only use on GetBytes's output on the SAME system
static string GetString(byte[] bytes)
{
    char[] chars = new char[bytes.Length / sizeof(char)];
    System.Buffer.BlockCopy(bytes, 0, chars, 0, bytes.Length);
    return new string(chars);
}

只要你的程序(或其他程序)不尝试以某种方式解释字节,你显然没有提到你打算这样做,那么这种方法就没有错!担心编码只会让你的生活无缘无故地变得更加复杂。

这种方法的其他好处是:字符串是否包含无效字符并不重要,因为您仍然可以获取数据并重建原始字符串!

它的编码和解码方式是一样的,因为你只是在看字节

但是,如果您使用特定的编码,则会给您编码/解码无效字符带来麻烦。

评论

277赞 CodesInChaos 5/13/2012
这个丑陋的地方在于,需要在具有相同字节序的系统上执行才能工作。因此,您不能使用它来获取要在其他地方转换为字符串的字节。所以我很难想出一个我想使用它的情况。GetStringGetBytes
73赞 user541686 5/14/2012
@CodeInChaos:就像我说的,这样做的重点是如果你想在同一类型的系统上使用它,具有相同的功能集。如果没有,那么你不应该使用它。
219赞 artbristol 6/15/2012
-1 我保证有人(不了解字节与字符)会想要将他们的字符串转换为字节数组,他们会谷歌并阅读这个答案,他们会做错事,因为在几乎所有情况下,编码都是相关的。
437赞 user541686 6/15/2012
@artbristol:如果他们懒得读答案(或其他答案......),那么我很抱歉,那么我没有更好的方式与他们交流了。我通常选择回答 OP,而不是试图猜测其他人可能会用我的答案做什么——OP 有权知道,仅仅因为有人可能滥用一把刀并不意味着我们需要为自己隐藏世界上所有的刀。不过,如果你不同意,那也没关系。
205赞 David 7/11/2012
这个答案在很多层面上都是错误的,但最重要的是因为它的衰落,“你不需要担心编码!GetBytes 和 GetString 这两个方法都是多余的,因为它们只是 Encoding.Unicode.GetBytes() 和 Encoding.Unicode.GetString() 已经完成的重新实现。“只要你的程序(或其他程序)不尝试解释字节”这句话也存在根本性的缺陷,因为它们隐含地意味着字节应该被解释为Unicode。
47赞 5 revsMichael Buen #17

为了证明 Mehrdrad 的合理答案是有效的,他的方法甚至可以保留未配对的代理字符(其中许多人反对我的答案,但每个人都同样有罪,例如,;例如,这些编码方法不能保留高代理字符,而那些只是用值替换高代理字符):System.Text.Encoding.UTF8.GetBytesSystem.Text.Encoding.Unicode.GetBytesd800fffd

using System;

class Program
{     
    static void Main(string[] args)
    {
        string t = "爱虫";            
        string s = "Test\ud800Test"; 

        byte[] dumpToBytes = GetBytes(s);
        string getItBack = GetString(dumpToBytes);

        foreach (char item in getItBack)
        {
            Console.WriteLine("{0} {1}", item, ((ushort)item).ToString("x"));
        }    
    }

    static byte[] GetBytes(string str)
    {
        byte[] bytes = new byte[str.Length * sizeof(char)];
        System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
        return bytes;
    }

    static string GetString(byte[] bytes)
    {
        char[] chars = new char[bytes.Length / sizeof(char)];
        System.Buffer.BlockCopy(bytes, 0, chars, 0, bytes.Length);
        return new string(chars);
    }        
}

输出:

T 54
e 65
s 73
t 74
? d800
T 54
e 65
s 73
t 74

尝试使用 System.Text.Encoding.UTF8.GetBytes 或 System.Text.Encoding.Unicode.GetBytes,它们只会将高代理项字符替换为值 fffd

每次这个问题有动静时,我仍然在考虑一个序列化程序(无论是来自 Microsoft 还是来自 3rd 方组件),即使它包含不成对的代理字符,它也可以持久化字符串;我时不时地用谷歌搜索:序列化未配对代理字符 .NET。这不会让我失眠,但是当时不时有人评论我的答案有缺陷时,这有点烦人,但当涉及到未配对的代理字符时,他们的答案同样有缺陷。

的,Microsoft 应该刚刚在其 ツSystem.Buffer.BlockCopyBinaryFormatter

谢谢!

评论

4赞 dtanders 6/14/2012
代理项是否必须成对出现才能形成有效的码位?如果是这样的话,我就能理解为什么数据会被破坏。
2赞 Michael Buen 6/15/2012
@dtanders 是的,这也是我的想法,它们必须成对出现,如果你故意把它们放在字符串上并使它们不成对,就会发生不成对的代理字符。我不知道的是,为什么其他开发人员一直在喋喋不休地认为我们应该使用编码感知方法,因为他们认为序列化方法(我的答案,这是 3 年多来公认的答案)不能保持未配对的代理字符完好无损。但是他们忘了检查他们的编码感知解决方案是否也保留了未配对的代理字符,具有讽刺意味的是ツ
1赞 Michael Buen 6/15/2012
如果有一个内部使用的序列化库,那么所有编码倡导者的论点都将没有实际意义System.Buffer.BlockCopy
3赞 Trisped 11/12/2014
@MichaelBuen 在我看来,主要问题是你用粗体大字说某事无关紧要,而不是说这在他们的情况下无关紧要。因此,你鼓励那些看到你的答案的人犯基本的编程错误,这将在未来给其他人带来挫败感。未配对的代理项在字符串中无效。它不是一个 char 数组,因此将字符串转换为另一种格式会导致该字符出错是有道理的。如果要进行手动字符串操作,请按照建议使用 char[]。FFFD
4赞 supercat 11/13/2014
@dtanders:A 是 的不可变序列; .NET 始终允许从 any 构造对象,并将其内容导出到包含相同值的 a,即使原始对象包含未配对的代理项也是如此。System.StringCharStringChar[]Char[]Char[]
1赞 Avlin 10/11/2012 #18

使用 LINQ 编写简单代码

string s = "abc"
byte[] b = s.Select(e => (byte)e).ToArray();

编辑:如下所述,这不是一个好方法。

但你仍然可以用它来理解 LINQ 用更合适的编码:

string s = "abc"
byte[] b = s.Cast<byte>().ToArray();

评论

2赞 WynandB 10/25/2013
它几乎没有更快,更不用说最快的了。这当然是一个有趣的选择,但顺便说一句,它本质上与速度更快的相同。快速测试表明,性能至少提高了 79%。YMMV。Encoding.Default.GetBytes(s)Encoding.Default.GetBytes(s)
6赞 Hans Kesting 12/18/2013
尝试使用 .此代码不会崩溃,但会返回错误的结果(甚至更糟)。尝试转换为 而不是查看差异。shortbyte
3赞 Tommaso Belluzzo 1/15/2013 #19

这是我对转换的不安全实现:StringByte[]

public static unsafe Byte[] GetBytes(String s)
{
    Int32 length = s.Length * sizeof(Char);
    Byte[] bytes = new Byte[length];

    fixed (Char* pInput = s)
    fixed (Byte* pBytes = bytes)
    {
        Byte* source = (Byte*)pInput;
        Byte* destination = pBytes;

        if (length >= 16)
        {
            do
            {
                *((Int64*)destination) = *((Int64*)source);
                *((Int64*)(destination + 8)) = *((Int64*)(source + 8));

                source += 16;
                destination += 16;
            }
            while ((length -= 16) >= 16);
        }

        if (length > 0)
        {
            if ((length & 8) != 0)
            {
                *((Int64*)destination) = *((Int64*)source);

                source += 8;
                destination += 8;
            }

            if ((length & 4) != 0)
            {
                *((Int32*)destination) = *((Int32*)source);

                source += 4;
                destination += 4;
            }

            if ((length & 2) != 0)
            {
                *((Int16*)destination) = *((Int16*)source);

                source += 2;
                destination += 2;
            }

            if ((length & 1) != 0)
            {
                ++source;
                ++destination;

                destination[0] = source[0];
            }
        }
    }

    return bytes;
}

它比公认的 anwser 快得多,即使它没有那么优雅。 以下是我超过 10000000 次迭代的秒表基准测试:

[Second String: Length 20]
Buffer.BlockCopy: 746ms
Unsafe: 557ms

[Second String: Length 50]
Buffer.BlockCopy: 861ms
Unsafe: 753ms

[Third String: Length 100]
Buffer.BlockCopy: 1250ms
Unsafe: 1063ms

为了使用它,您必须在项目构建属性中勾选“允许不安全代码”。 根据 .NET Framework 3.5,此方法也可以用作 String 扩展:

public static unsafe class StringExtensions
{
    public static Byte[] ToByteArray(this String s)
    {
        // Method Code
    }
}

评论

0赞 Jon Hanna 1/6/2014
在 .NET 的 Itanium 版本上,值是 8 的倍数吗?因为否则,这将由于未对齐的读取而失败。RuntimeHelpers.OffsetToStringData
0赞 Jodrell 11/25/2014
调用不是更简单吗?stackoverflow.com/a/27124232/659190memcpy
-4赞 sagardhavale 1/23/2013 #20

代码如下:

// Input string.
const string input = "Dot Net Perls";

// Invoke GetBytes method.
// ... You can store this array as a field!
byte[] array = Encoding.ASCII.GetBytes(input);

// Loop through contents of the array.
foreach (byte element in array)
{
    Console.WriteLine("{0} = {1}", element, (char)element);
}
23赞 Shyam sundar shah 6/5/2013 #21

C# 将 a 转换为数组:stringbyte

public static byte[] StrToByteArray(string str)
{
   System.Text.UTF8Encoding  encoding=new System.Text.UTF8Encoding();
   return encoding.GetBytes(str);
}
6赞 Shyam sundar shah 9/2/2013 #22

您可以使用以下代码在 .NET 中将 a 转换为 astringbyte array

string s_unicode = "abcéabc";
byte[] utf8Bytes = System.Text.Encoding.UTF8.GetBytes(s_unicode);
-12赞 Thomas Eding 9/28/2013 #23

OP 的问题:“如何在 .NET (C#) 中将 a 转换为数组?[原文如此]stringbyte

您可以使用以下代码:

static byte[] ConvertString (string s) {
    return new byte[0];
}

好处是,编码无关紧要!哦,等等,这是一个生态......这只是微不足道的,而且损失很大。

评论

0赞 Lodewijk 4/28/2014
这不是皈依。这是一个新的字节数组。OP 真正需要的是指针和 memcpy。或者强制转换:byte[] b = (byte[]) s;。
2赞 Niki Romagnoli 10/14/2014
此外,这里甚至没有使用“s”。绝对不是解决方案。
10赞 mashet 10/22/2013 #24

用:

    string text = "string";
    byte[] array = System.Text.Encoding.UTF8.GetBytes(text);

结果是:

[0] = 115
[1] = 116
[2] = 114
[3] = 105
[4] = 110
[5] = 103

评论

0赞 Ferdz 8/30/2018
OP 特别要求不要指定编码...“无需手动指定特定编码”
105赞 Tom Blodget 12/2/2013 #25

这是一个流行的问题。重要的是要了解作者在问什么,并且它与最常见的需求不同。为了防止在不需要的地方滥用代码,我首先回答了后者。

共同需求

每个字符串都有一个字符集和编码。当您将对象转换为数组时,您仍然具有字符集和编码。对于大多数用法,你会知道需要哪种字符集和编码,而 .NET 使“通过转换进行复制”变得简单。只需选择合适的班级即可。System.StringSystem.ByteEncoding

// using System.Text;
Encoding.UTF8.GetBytes(".NET String to byte array")

转换可能需要处理目标字符集或编码不支持源中的字符的情况。您有一些选择:例外、替换或跳过。默认策略是替换“?”。

// using System.Text;
var text = Encoding.ASCII.GetString(Encoding.ASCII.GetBytes("You win €100")); 
                                                      // -> "You win ?100"

显然,转换不一定是无损的!

注意:源字符集为 Unicode。System.String

唯一令人困惑的是,.NET 使用字符集的名称作为该字符集的一个特定编码的名称。 应该称为 .Encoding.UnicodeEncoding.UTF16

对于大多数用法来说,就是这样。如果这是您需要的,请停止阅读此处。如果您不了解什么是编码,请参阅有趣的 Joel Spolsky 文章

特殊需求

现在,作者问的问题是,“每个字符串都存储为字节数组,对吧?为什么我不能简单地拥有这些字节?

他不想要任何转变。

C# 规范

C# 中的字符和字符串处理使用 Unicode 编码。字符 type 表示 UTF-16 代码单元,string 类型表示 UTF-16 代码单元序列。

所以,我们知道,如果我们要求 null 转换(即从 UTF-16 到 UTF-16),我们将得到所需的结果:

Encoding.Unicode.GetBytes(".NET String to byte array")

但为了避免提及编码,我们必须以另一种方式做到这一点。如果中间数据类型是可以接受的,则有一个概念快捷方式:

".NET String to byte array".ToCharArray()

这并不能让我们获得所需的数据类型,但 Mehrdad 的答案显示了如何使用 BlockCopy 将此 Char 数组转换为 Byte 数组。但是,这会将字符串复制两次!而且,它过于明确地使用特定于编码的代码: 数据类型 。System.Char

获取存储 String 的实际字节的唯一方法是使用指针。该语句允许获取值的地址。从 C# 规范:fixed

[对于] 字符串类型的表达式,...初始值设定项计算 字符串中第一个字符的地址。

为此,编译器编写代码时会使用 .因此,要获取原始字节,只需创建一个指向字符串的指针并复制所需的字节数。RuntimeHelpers.OffsetToStringData

// using System.Runtime.InteropServices
unsafe byte[] GetRawBytes(String s)
{
    if (s == null) return null;
    var codeunitCount = s.Length;
    /* We know that String is a sequence of UTF-16 code units 
       and such code units are 2 bytes */
    var byteCount = codeunitCount * 2; 
    var bytes = new byte[byteCount];
    fixed(void* pRaw = s)
    {
        Marshal.Copy((IntPtr)pRaw, bytes, 0, byteCount);
    }
    return bytes;
}

正如@CodesInChaos所指出的,结果取决于机器的字节序。但问题作者并不关心这一点。

评论

4赞 Tom Blodget 2/4/2014
@Jan 没错,但字符串长度已经给出了代码单元(不是代码点)的数量。
1赞 Jan Hettich 2/4/2014
感谢您指出这一点!来自 MSDN:“属性 [of] 返回此实例中的对象数,而不是 Unicode 字符数。因此,您的示例代码是正确的。LengthStringChar
1赞 Tom Blodget 11/13/2014
@supercat “char 类型表示 UTF-16 代码单元,string 类型表示 UTF-16 代码单元序列。”—_C# 5 Specification._ 虽然,是的,没有什么可以防止无效的 Unicode 字符串:new String(new []{'\uD800', '\u0030'})
1赞 supercat 11/14/2014
@TomBlodget:有趣的是,如果获取 的实例,提取 ,并将每个字节的结果字节打包成一个 [每个字符两个字节,MSB 优先],则调用生成的字符串将比调用 的实例甚至调用这些实例要快得多。鉴于此,我想知道为什么返回 a 而不是 ?Globalization.SortKeyKeyDataStringString.CompareOrdinalSortKey.CompareSortKeymemcmpKeyDataByte[]String
2赞 Martin Capodici 6/30/2015
唉,正确的答案,但为时已晚,永远不会有那么多的选票。由于 TL;DR的人会认为公认的答案是摇滚的。copyenpastit 并投票赞成它。
-1赞 WonderWorker 4/9/2014 #26

若要将字符串转换为 byte[],请使用以下解决方案:

string s = "abcdefghijklmnopqrstuvwxyz";
byte[] b = System.Text.UTF32Encoding.GetBytes(s);

我希望它有所帮助。

评论

2赞 Sebastian 4/13/2014
那不是解决方案!
1赞 Sebastian 4/13/2014
在您编辑之前,它是:这仅适用于 ASCII 字符。但该类型用于存储 UTF16 单元。现在,经过编辑,代码至少是正确的,但它因环境而异,因此几乎无用。恕我直言,Encoding.Default 只能用于与旧版 Windows“Ansi 代码页”代码进行交互。s.Select(e => (byte)e)char
0赞 WonderWorker 4/14/2014
好点子。您如何看待 byte[] b = new System.Text.UTF32Encoding()。GetBytes(s); ?
0赞 Sebastian 4/14/2014
使用,UTF8 同样可以。byte[] b = System.Text.UTF32Encoding.GetBytes(s);
2赞 Vijay Singh Rana 6/11/2014 #27

由于以下事实,可以通过几种不同的方式将字符串转换为字节数组:.NET 支持 Unicode,并且 Unicode 标准化了几种称为 UTF 的不同编码。它们具有不同长度的字节表示,但从这个意义上说是等价的,当一个字符串被编码时,它可以被编码回字符串,但如果字符串用一个 UTF 编码并在假设不同的 UTF 的情况下解码,如果可以搞砸。

此外,.NET 支持非 Unicode 编码,但它们在一般情况下无效(仅当在实际字符串中使用 Unicode 码位的有限子集(如 ASCII)时才有效)。在内部,.NET 支持 UTF-16,但对于流表示形式,通常使用 UTF-8。它也是互联网事实上的标准。

毫不奇怪,类支持将字符串序列化为字节数组和反序列化,这是一个抽象类;它的派生类支持具体编码:和四个 UTF(支持 UTF-16)System.Text.EncodingASCIIEncodingSystem.Text.UnicodeEncoding

参考此链接。

对于使用 序列化为字节数组。对于逆运算,请使用 .此函数返回一个字符数组,因此要获取字符串,请使用字符串构造函数。
参考此页面。
System.Text.Encoding.GetBytesSystem.Text.Encoding.GetCharsSystem.String(char[])

例:

string myString = //... some string

System.Text.Encoding encoding = System.Text.Encoding.UTF8; //or some other, but prefer some UTF is Unicode is used
byte[] bytes = encoding.GetBytes(myString);

//next lines are written in response to a follow-up questions:

myString = new string(encoding.GetChars(bytes));
byte[] bytes = encoding.GetBytes(myString);
myString = new string(encoding.GetChars(bytes));
byte[] bytes = encoding.GetBytes(myString);

//how many times shall I repeat it to show there is a round-trip? :-)
0赞 6 revs, 3 users 95%George #28

字符既是字体表的查找键,又是词汇传统,例如排序、大小写版本等。

因此,字符不是字节(8 位),字节也不是字符。特别是,一个字节的 256 种排列无法容纳某些书面语言中的数千个符号,更不用说所有语言了。因此,已经设计了各种字符编码方法。有些编码针对特定类别的语言(ASCII 编码);使用代码页的多种语言(扩展 ASCII);或者,雄心勃勃地,通过根据需要有选择地包含额外的字节,Unicode,所有语言。

在系统(如 .NET Framework)中,String 表示特定字符编码。在 .NET 中,此编码为 Unicode。由于框架默认读取和写入 Unicode,因此在 .NET 中通常不需要处理字符编码。

但是,通常,要将字符串从字节流加载到系统中,您需要知道源编码,以便正确解释并随后转换它(否则代码将被视为已经在系统的默认编码中,从而呈现乱码)。同样,当字符串写入外部源时,它将以特定的编码写入。

评论

2赞 Kevin 8/26/2017
Unicode 不是编码。Unicode 是字符到代码点的抽象映射。有多种对 Unicode 进行编码的方法;特别是,UTF-8 和 UTF-16 是最常见的。.NET 使用 UTF-16,但我不确定它是 UTF-16 LE 还是 UTF-16 BE。
0赞 verdy_p 9/8/2019
UTF-16 LE 或 UTF-16 BE 也不相关:字符串使用牢不可破的 16 位代码单元,没有任何解释。UTF-16BE 或 UTF-16 LE 可能仅在将字符串转换为字节数组或反之亦然时才相关,因为此时您将指定编码(在这种情况下,字符串必须首先是有效的 UTF-16,但字符串不必是有效的 UTF-16)。GetBytes() 不一定返回有效的 UTF-16 BE/LE,它使用简单的算术;返回的数组也不是有效的 UTF-8,而是任意字节。如果未指定编码,则结果中的字节顺序是特定于系统的。
0赞 verdy_p 9/8/2019
这也意味着字符串。UTF8.getBytes() 可能会从内容无效的 UTF-16 的任意字符串中抛出编码异常。在 C# 中,您可以选择要使用的编码器/解码器(编解码器)。您可能希望使用自己的编解码器,该编解码器将以不同的方式打包/解压缩字节,或者可能会静默删除未配对的代理项(如果编解码器尝试将字符串解释为 UTF-16),或者可能会删除高字节,或者通过 U+FFFD 替换/解释 UTF-16 中无效的代码单元。编解码器还可以使用数据压缩,或十六进制/base64 或转义...编解码器不仅限于 UTF8 编码。
0赞 verdy_p 9/8/2019
注意:我在这里自愿使用术语“编解码器”而不是“编码”,后者更具体且仅用于文本。C#,C,C++,Java,Javascript / ECMAscript / ActiveScript中的字符串不仅限于有效的文本:它们只是一种通用的存储结构,便于文本并被库(但不是全部)视为文本。因此,UTF 表单根本不会强制执行,除非在使用它们的特定 API(包括 UTF* 编码对象)中。是的,您可以将二进制程序或PNG图像存储在紧凑的不可变字符串中,而不是可变数组中,但是您可以将所有字符串I/O到文本通道
17赞 Jarvis Stark 9/9/2014 #29

您可以使用以下代码在字符串和字节数组之间进行转换。

string s = "Hello World";

// String to Byte[]

byte[] byte1 = System.Text.Encoding.Default.GetBytes(s);

// OR

byte[] byte2 = System.Text.ASCIIEncoding.Default.GetBytes(s);

// Byte[] to string

string str = System.Text.Encoding.UTF8.GetString(byte1);

评论

0赞 r.hamd 9/9/2015
VUP这个解决了我的问题( byte[] ff = ASCIIEncoding.ASCII.GetBytes(barcodetxt.文本);)
4赞 Jodrell 11/25/2014 #30

如果确实想要字符串基础字节的副本,可以使用如下所示的函数。但是,您不应该继续阅读以找出原因。

[DllImport(
        "msvcrt.dll",
        EntryPoint = "memcpy",
        CallingConvention = CallingConvention.Cdecl,
        SetLastError = false)]
private static extern unsafe void* UnsafeMemoryCopy(
    void* destination,
    void* source,
    uint count);

public static byte[] GetUnderlyingBytes(string source)
{
    var length = source.Length * sizeof(char);
    var result = new byte[length];
    unsafe
    {
        fixed (char* firstSourceChar = source)
        fixed (byte* firstDestination = result)
        {
            var firstSource = (byte*)firstSourceChar;
            UnsafeMemoryCopy(
                firstDestination,
                firstSource,
                (uint)length);
        }
    }

    return result;
}

这个函数将很快为你提供字符串底层字节的副本。您将以它们在系统上编码的任何方式获得这些字节。这种编码几乎可以肯定是 UTF-16LE,但这是您不必关心的实现细节。

只是打电话会更安全、更简单、更可靠

System.Text.Encoding.Unicode.GetBytes()

这很可能会给出相同的结果,更容易键入,并且字节将往返,以及 Unicode 中的字节表示形式,调用

System.Text.Encoding.Unicode.GetString()

评论

0赞 Ben Voigt 12/8/2020
正如在许多其他评论中提到的,/ 不会对所有 .NET 实例进行往返。Unicode.GetBytes()Unicode.GetString()string
0赞 Jodrell 12/9/2020
@BenVoigt,我调整了答案。这些天我会做一些不太特定于 Windows 的事情。
0赞 Ben Voigt 12/10/2020
您可以考虑避免 p/invoke,这适用于从指针复制到字节数组。stackoverflow.com/a/54453180/103167Marshal.Copy
0赞 Jodrell 12/10/2020
@BenVoigt甚至 stackoverflow.com/a/48195448/659190
-1赞 Piero Alberto 1/21/2015 #31

从到:byte[]string

        return BitConverter.ToString(bytes);
1赞 alireza amini 6/30/2015 #32

只需使用这个:

byte[] myByte= System.Text.ASCIIEncoding.Default.GetBytes(myString);

评论

2赞 mg30rg 1/11/2018
...并失去所有跳跃应对高于 127 的角色。在我的母语中,写“Árvíztűrő tükörfúrógép.”是完全有效的。 将返回无法检索的丢失信息。(我还没有提到亚洲语言,在那里你会失去所有字符。System.Text.ASCIIEncoding.Default.GetBytes("Árvíztűrő tükörfúrógép.").ToString();"Árvizturo tukörfurogép."
8赞 Gerard ONeill 8/19/2015 #33

最接近 OP 问题的方法是 Tom Blodget 的问题,它实际上进入了对象并提取了字节。我说最接近是因为它取决于 String 对象的实现。

"Can't I simply get what bytes the string has been stored in?"

当然,但这就是问题中根本错误出现的地方。String 是一个对象,它可能具有有趣的数据结构。我们已经知道它确实如此,因为它允许存储未配对的代理项。它可能会存储长度。它可能会保留一个指向每个“配对”代理项的指针,以便快速计数。等。所有这些额外的字节都不是字符数据的一部分。

您想要的是数组中每个字符的字节。这就是“编码”的用武之地。默认情况下,您将获得 UTF-16LE。如果你不关心字节本身,除了往返,那么你可以选择任何编码,包括“默认”,并在以后将其转换回来(假设相同的参数,例如默认编码是什么,代码点,错误修复,允许的东西,如不配对的代理项等。

但是,为什么要把“编码”留给魔法呢?为什么不指定编码,以便您知道将获得哪些字节?

"Why is there a dependency on character encodings?"

编码(在此上下文中)仅表示表示字符串的字节。不是字符串对象的字节。你想要字符串被存储的字节 -- 这是天真地提出问题的地方。您需要表示字符串的连续数组中的字符串字节,而不是字符串对象可能包含的所有其他二进制数据。

这意味着字符串的存储方式无关紧要。您希望将字符串“编码”为字节数组中的字节。

我喜欢 Tom Bloget 的回答,因为他把你带到了“字符串对象的字节”方向。不过,这取决于实现,并且由于他正在窥视内部结构,因此可能很难重构字符串的副本。

Mehrdad的回应是错误的,因为它在概念层面上具有误导性。您仍然有一个已编码的字节列表。他的特定解决方案允许保留未配对的代理项 - 这取决于实现。如果默认情况下以 UTF-8 格式返回字符串,他的特定解决方案将无法准确生成字符串的字节。GetBytes


我已经改变了主意(Mehrdad 的解决方案)——这不是获取字符串的字节;相反,它获取从字符串创建的字符数组的字节。无论编码如何,c# 中的 char 数据类型都是固定大小的。这允许生成一致长度的字节数组,并允许根据字节数组的大小复制字符数组。因此,如果编码是 UTF-8,但每个字符是 6 个字节以容纳最大的 utf8 值,它仍然有效。所以确实 -- 字符的编码并不重要。

但是使用了转换 - 每个字符都放在一个固定大小的框中(c# 的字符类型)。然而,这种表示是什么并不重要,从技术上讲,这是 OP 的答案。所以,如果你无论如何都要皈依......为什么不“编码”?

评论

0赞 Mojtaba Rezaeian 2/12/2016
UTF-8 或 UTF-16 甚至 UTF-32 都不支持这些字符,例如:& & 。所以你可能是错的,Mehrdad 的答案是安全的转换,而不考虑使用什么类型的编码。񩱠(Char) 55906(Char) 55655
0赞 Gerard ONeill 2/12/2016
Raymon,字符已经由一些 unicode 值表示——所有 unicode 值都可以用所有 utf 表示。对你在说什么有更长的解释吗?这两个值(或 3..)存在于什么字符编码中?
0赞 Mojtaba Rezaeian 2/12/2016
它们是无效字符,不受任何编码范围的支持。这并不意味着它们是 100% 无用的。将任何类型的字符串转换为其字节数组等效项的代码,无论编码如何,都不是错误的解决方案,并且在所需的场合有自己的用法。
1赞 Gerard ONeill 2/12/2016
好吧,那么我认为你不明白这个问题。我们知道它是一个符合 Unicode 的数组——事实上,因为它是 .net,我们知道它是 UTF-16。所以这些角色不会在那里存在。你也没有完全阅读我关于内部表述变化的评论。String 是一个对象,而不是编码的字节数组。所以我不同意你最后的说法。您需要将所有 unicode 字符串转换为任何 UTF 编码的代码。这正确地完成了您想要的操作。
0赞 Mojtaba Rezaeian 2/12/2016
对象是数据序列,最初是描述当前状态的对象的位序列。因此,编程语言中的每个数据都可以转换为字节数组(每个字节定义 8 位),因为您可能需要在内存中保留任何对象的某些状态。您可以在文件或内存中保存和保存字节序列,并在从磁盘读取后将其转换为整数、bigint、图像、Ascii 字符串、UTF-8 字符串、加密字符串或您自己定义的数据类型。所以你不能说对象是不同于字节序列的东西。
-5赞 IgnusFast 1/22/2016 #34

对于串行通信项目,我必须将字符串转换为字节数组 - 我必须处理 8 位字符,并且我无法找到使用框架转换器的方法,该方法既不添加两个字节条目,也不错误转换字节设置第八位。所以我做了以下工作,这很有效:

string message = "This is a message.";
byte[] bytes = new byte[message.Length];
for (int i = 0; i < message.Length; i++)
    bytes[i] = (byte)message[i];

评论

3赞 Mojtaba Rezaeian 2/12/2016
这种方式不安全,如果输入字符串包含 unicode 范围字符,您将丢失原始数据。
0赞 IgnusFast 2/7/2017
这是针对一个串行通信项目,无论如何都无法处理 unicode。当然,这是一个非常狭隘的案例。
0赞 Mojtaba Rezaeian 2/12/2016 #35

我编写了一个类似于公认的答案的Visual Basic扩展,但直接使用.NET内存和封送处理进行转换,并且它支持其他方法不支持的字符范围,例如或甚至(无效字符,如:&):UnicodeEncoding.UTF8.GetStringUnicodeEncoding.UTF32.GetStringMemoryStream and BinaryFormatter񩱠ChrW(55906)ChrW(55655)

<Extension> _
Public Function ToBytesMarshal(ByRef str As String) As Byte()
    Dim gch As GCHandle = GCHandle.Alloc(str, GCHandleType.Pinned)
    Dim handle As IntPtr = gch.AddrOfPinnedObject
    ToBytesMarshal = New Byte(str.Length * 2 - 1) {}
    Try
        For i As Integer = 0 To ToBytesMarshal.Length - 1
            ToBytesMarshal.SetValue(Marshal.ReadByte(IntPtr.Add(handle, i)), i)
        Next
    Finally
        gch.Free()
    End Try
End Function

<Extension> _
Public Function ToStringMarshal(ByRef arr As Byte()) As String
    Dim gch As GCHandle = GCHandle.Alloc(arr, GCHandleType.Pinned)
    Try
        ToStringMarshal = Marshal.PtrToStringAuto(gch.AddrOfPinnedObject)
    Finally
        gch.Free()
    End Try
End Function
2赞 NH. 11/9/2017 #36

这取决于你想要字节的用途

这是因为,正如 Tyler 所说,“字符串不是纯粹的数据。他们也有信息。在本例中,信息是创建字符串时假定的编码。

假设二进制数据(而不是文本)存储在字符串中

这是基于 OP 对他自己的问题的评论,如果我理解 OP 对用例的暗示,这是正确的问题。

将二进制数据存储在字符串中可能是错误的方法,因为上面提到了假设的编码!无论将二进制数据存储在一个(而不是更合适的数组)中的程序或库,在战斗开始之前就已经输掉了战斗。如果他们在 REST 请求/响应或任何必须传输字符串的内容中将字节发送给您,那么 Base64 将是正确的方法。stringbyte[]

如果文本字符串的编码未知

其他人都错误地回答了这个错误的问题。

如果字符串看起来不错,只需选择一种编码(最好是以 UTF 开头的编码),使用相应的函数,并告诉谁你把字节交给了你选择的编码。System.Text.Encoding.???.GetBytes()

17赞 John Rasch 1/11/2018 #37

随着 C# 7.2 发布的 Span<T> 的出现,将字符串的基础内存表示形式捕获到托管字节数组中的规范技术是:

byte[] bytes = "rubbish_\u9999_string".AsSpan().AsBytes().ToArray();

将其转换回来应该是一个不切实际的问题,因为这意味着您实际上是在以某种方式解释数据,但为了完整性:

string s;
unsafe
{
    fixed (char* f = &bytes.AsSpan().NonPortableCast<byte, char>().DangerousGetPinnableReference())
    {
        s = new string(f);
    }
}

这些名称和应该进一步说明您可能不应该这样做的论点。NonPortableCastDangerousGetPinnableReference

请注意,使用 Span<T> 需要安装 System.Memory NuGet 包

无论如何,实际的原始问题和后续评论暗示底层内存没有被“解释”(我认为这意味着除了需要按原样编写之外,没有修改或读取),这表明应该使用类的某些实现,而不是将数据推理为字符串。Stream

评论

0赞 Ben Voigt 12/8/2020
new string(f)是错误的,如果您想要往返所有字符串的希望,您至少需要使用接受显式长度的构造函数重载。
8赞 Jason Goemaat 7/3/2018 #38

如何在 .NET (C#) 中将字符串转换为 byte[],而无需手动指定特定编码?

.NET 中的字符串将文本表示为 UTF-16 代码单元的序列,因此字节已在内存中以 UTF-16 编码。

Mehrdad's Answer(梅尔达德)的回答

你可以使用 Mehrdad 的答案,但它实际上使用了编码,因为字符是 UTF-16。它调用 ToCharArray,后者查看源创建一个并将内存直接复制到其中。然后,它将数据复制到也分配的字节数组中。因此,在后台,它会复制底层字节两次,并分配一个在调用后未使用的 char 数组。char[]

Tom Blodget's Answer

Tom Blodget 的答案比 Mehrdad 快 20-30%,因为它跳过了分配 char 数组并将字节复制到其中的中间步骤,但它需要您使用该选项进行编译。如果你绝对不想使用编码,我认为这是要走的路。如果你把你的加密登录名放在块里,你甚至不需要分配一个单独的字节数组并将字节复制到它。/unsafefixed

另外,为什么要考虑编码?我不能简单地获取字符串存储在哪些字节中吗?为什么依赖于字符编码?

因为这是正确的方法。 是一个抽象。string

如果您的“字符串”包含无效字符,则使用编码可能会给您带来麻烦,但这不应该发生。如果您使用无效字符将数据输入到字符串中,则您做错了。您可能应该首先使用字节数组或 Base64 编码。

如果使用 ,您的代码将更具弹性。您不必担心运行代码的系统的字节序。您无需担心下一版本的 CLR 是否会使用不同的内部字符编码。System.Text.Encoding.Unicode

我认为问题不在于你为什么要担心编码,而在于你为什么要忽略它并使用其他东西。编码旨在表示字节序列中字符串的抽象。 将为您提供一个小的字节序顺序编码,并将在现在和将来的每个系统上执行相同的编码。System.Text.Encoding.Unicode

评论

0赞 verdy_p 9/7/2019
实际上,C# 中的字符串并不局限于 UTF-16。事实是,它包含一个 16 位代码单元的向量,但这些 16 位代码单元并不局限于有效的 UTF-16。但由于它们是 16 位,您需要编码(字节顺序)才能将它们转换为 8 位。然后,字符串可以存储非 Unicode 数据,包括二进制代码(例如位图图像)。它仅在 I/O 和进行此类解释的文本格式化程序中被解释为 UTF-16。
0赞 verdy_p 9/7/2019
因此,在 C# 字符串中,您可以安全地存储代码单元(如 0xFFFF 或 0xFFFE),即使它们是 UTF-16 中的非字符,并且可以在 0xDC00 中存储不后跟代码单元的独立0xD800。0xDFFF(即在 UTF-16 中无效的未配对代理项)。同样的注释也适用于 Javascript/ECMAscript 和 Java 中的字符串。
0赞 verdy_p 9/7/2019
使用“GetBytes”时,当然不会指定编码,而是假定字节顺序,以获取字符串中本地存储的每个代码单元的特定两个字节。当你从字节构建一个新字符串时,你还需要一个转换器,不一定是 UTF-8 到 UTF-16,你可以在高字节中插入额外的 0,或者将两个字节(MSB 第一或 LSB 第一顺序)打包在同一个 16 位代码单元中。然后,字符串是 16 位整数数组的紧凑形式。与“字符”的关系是另一个问题,在 C# 中,它们不是实际类型,因为它们仍然表示为字符串
3赞 jpmc26 9/11/2019 #39

当被问到你打算用这些字节做什么时,你回答说

我要加密它。我可以在不转换的情况下对其进行加密,但我仍然想知道为什么编码会在这里发挥作用。只要给我字节就是我说的。

无论您打算通过网络发送此加密数据,稍后将其加载回内存,还是将其流式传输到另一个进程,您显然都打算在某个时候对其进行解密。在这种情况下,答案是您正在定义通信协议。不应根据编程语言及其相关运行时的实现细节来定义通信协议。这有几个原因:

  • 您可能需要与以不同语言或运行时实现的进程进行通信。(例如,这可能包括在另一台计算机上运行的服务器或将字符串发送到 JavaScript 浏览器客户端。
  • 该程序将来可能会以不同的语言或运行时重新实现。
  • .NET 实现可能会更改字符串的内部表示形式。你可能认为这听起来很牵强,但这实际上发生在 Java 9 中,以减少内存使用。.NET 没有理由不效仿。Skeet 认为,UTF-16 在今天可能不是最佳选择,因为表情符号和其他 Unicode 块也需要超过 2 个字节来表示,这增加了内部表示在未来发生变化的可能性。

对于通信(无论是使用完全不同的过程还是将来使用相同的程序),您需要严格定义您的协议,以最大程度地减少使用它或意外创建错误的难度。根据。NET的内部表示不是一个严格、清晰,甚至不能保证一致的定义。标准编码是一个严格的定义,将来不会让您失望。

换言之,如果不指定编码,就无法满足一致性要求。

如果您发现您的进程执行得更好,因为 .NET 在内部或出于任何其他原因使用它,您当然可以选择直接使用 UTF-16,但您需要显式选择该编码并在代码中显式执行这些转换,而不是依赖于 .NET的内部实现。

因此,选择一种编码并使用它:

using System.Text;

// ...

Encoding.Unicode.GetBytes("abc"); # UTF-16 little endian
Encoding.UTF8.GetBytes("abc")

正如你所看到的,与实现你自己的读取器/写入器方法相比,仅使用内置编码对象实际上也要少一些代码。

2赞 Chris Hutchinson 8/4/2020 #40

如果将 .NET CoreSystem.Memory 用于 .NET Framework,则可以通过 Span<T> 和 Memory<T> 获得非常有效的封送处理机制,该机制可以有效地将字符串内存重新解释为字节范围。一旦你有了字节的跨度,你就可以自由地封送回另一种类型,或者将这个跨度复制到数组中进行序列化。

总结一下其他人所说的话:

  • 存储此类序列化的表示形式对系统字节序、编译器优化以及对正在执行的 .NET 运行时中字符串的内部表示形式的更改很敏感。
    • 避免长期储存
    • 避免在其他环境中反序列化或解释字符串
      • 这包括其他计算机、处理器体系结构、.NET 运行时、容器等。
      • 这包括比较、格式化、加密、字符串操作、本地化、字符转换等。
    • 避免对字符编码做出假设
      • 在实践中,默认编码往往是 UTF-16LE,但编译器/运行时可以选择任何内部表示形式

实现

public static class MarshalExtensions
{
   public static ReadOnlySpan<byte> AsBytes(this string value) => MemoryMarshal.AsBytes(value.AsSpan());
   public static string AsString(this ReadOnlySpan<byte> value) => new string(MemoryMarshal.Cast<byte, char>(value));
}

static void Main(string[] args)
{
    string str1 = "你好,世界";
    ReadOnlySpan<byte> span = str1.AsBytes();
    string str2 = span.AsString();

    byte[] bytes = span.ToArray();

    Debug.Assert(bytes.Length > 0);
    Debug.Assert(str1 == str2);
}

Furthur 洞察

在 C++ 中,这大致相当于 reinterpret_cast,而 C 这大致相当于对系统词类型 (char) 的强制转换。

在最新版本的 .NET Core 运行时 (CoreCLR) 中,对跨度的操作可有效地调用编译器内部函数和各种优化,这些优化有时可以消除边界检查,从而在保持内存安全的同时实现卓越的性能,前提是内存是由 CLR 分配的,并且跨度不是从非托管内存分配器的指针派生的。

警告

这使用 CLR 支持的机制,该机制从字符串返回 ReadOnlySpan<char>;此外,此范围不一定包含完整的内部字符串布局。ReadOnlySpan<T> 表示如果需要执行变更,则必须创建副本,因为字符串是不可变的。

评论

1赞 Chris Hutchinson 8/4/2020
一些评论:尽管似乎是流行的观点,但这种机制的一个完全有效的用例是运行时加密:提取字节表示,加密字节,并将加密的有效负载保存在内存中。这最大限度地减少了编码开销,并且只要它没有被序列化并传输到另一个环境,就不会因解释语义或内部表示而遭受任何特定于编码的问题。为此目的使用 SecureString 是有争议的,并且担心垃圾回收,但除此之外,前提似乎是合理的。
0赞 Chris Hutchinson 8/4/2020
CoreCLR 至少有一个建议是引入更紧凑的内部表示形式:github.com/dotnet/runtime/issues/6612
2赞 Michel Diemer 9/27/2022 #41

计算机只理解原始二进制数据,原始位。 一位是二进制数字:0 或 1。 一个 8 位数字是一个字节。一个字节是介于 0 和 255 之间的数字。

ASCII 是将数字转换为字符的表。 介于 0 和 31 之间的数字是控件:制表符、换行符等。 介于 32 和 126 之间的数字是可打印字符: 字母 A,数字 1,% 符号,下划线 _

因此,使用 ASCII,有 33 个控制字符和 95 个可打印字符。

ASCII 是当今最常用的字符编码。 Unicode 表的第一个条目是 ASCII,并且与 ASCII 字符集匹配。

ASCII 是一个 7 位字符集。介于 0 和 127 之间的数字。 使用 8 位,我们可以达到 255。

ASCII 最常见的替代方案是 EBCDIC,它与 ASCII 不兼容,今天仍然存在于 IBM 计算机和数据库中。

1 字节,所以 8 位数字是当今计算机科学中最常见的单位。1 字节是介于 0 和 255 之间的数字。

ASCII 定义了 0 到 127 之间的每个数字的含义。

与 128 到 255 之间的数字关联的字符取决于所使用的字符编码。现在使用的两种广泛使用的字符编码是 windows1252 和 UTF-8。

在 windows1252 中,对应于 € 符号的数字是 128。 1 字节:[A0]。 在 Unicode 数据库中,€ 符号是数字 8364。

现在我给你数字8364。拖曳字节:[20,AC]。 在 UTF-8 中,欧元符号是数字 14844588。三个字节:[E282AC]。

现在我给你们一些原始数据。假设 20AC。 是两个 windows1252 字符:£ 还是一个 Unicode € 符号?

我给你一些更多的原始数据。E282交流。 好吧,82 是 windows1252 中的未分配字符,所以它可能不是 windows1252。 它可以是 macRoman “'Ç ̈” 或 OEM 437 “ßéó” 或 UTF-8 “€” 符号。

可以根据字符编码的特征和统计数据来猜测原始字节流的编码,但没有可靠的方法可以做到这一点。 在 UTF-8 中,介于 128 和 255 之间的数字本身是无效的。 é 在某些语言(法语)中很常见,因此,如果您看到许多值为 E9 的字节被字母包围,则可能是 windows1252 编码字符串,即表示 é 字符的 E9 字节。

当您有一个表示字符串的原始字节流时,最好知道匹配的编码,而不是试图猜测。

下面是曾经被广泛使用的各种编码中的一个原始字节的屏幕截图。

one byte character shown in various encodings

评论

0赞 Michel Diemer 9/27/2022
C++用 Windows Visual Studio Code 编写的程序,计划使其在 Linux 上运行,GitHub 上的源代码:github.com/micheldiemer/Test-Terminal/blob/main/Cout01/...