如何在.NET(C#)中将字符串转换为字节[]而不手动指定特定编码?
我要加密字符串。我可以在不进行转换的情况下对其进行加密,但我仍然想知道为什么编码会在这里发挥作用。
此外,为什么还要考虑编码?我不能简单地获取字符串存储的字节数吗?为什么依赖字符编码?
如何在.NET(C#)中将字符串转换为字节[]而不手动指定特定编码?
我要加密字符串。我可以在不进行转换的情况下对其进行加密,但我仍然想知道为什么编码会在这里发挥作用。
此外,为什么还要考虑编码?我不能简单地获取字符串存储的字节数吗?为什么依赖字符编码?
当前回答
这是一个流行的问题。重要的是要了解作者所问的问题,以及它与最常见的需求不同。为了防止在不需要的地方滥用代码,我首先回答了后者。
共同需求
每个字符串都有一个字符集和编码。将System.String对象转换为System.Byte数组时,仍有字符集和编码。对于大多数用途,您可以知道需要哪个字符集和编码,.NET使“复制并转换”变得简单。只需选择适当的encoding类即可。
// 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"
显然,转换不一定是无损的!
注意:对于System.String,源字符集是Unicode。
唯一令人困惑的是,.NET使用字符集的名称作为该字符集的一个特定编码的名称。编码。Unicode应称为Encoding.UTF16。
这就是大多数用法。如果这正是你所需要的,请停止阅读这里。如果您不了解编码是什么,请参阅有趣的Joel Spolsky文章。
特定需求
现在,作者提出的问题是,“每个字符串都存储为一个字节数组,对吗?为什么我不能简单地拥有这些字节?”
他不想改变信仰。
根据C#规范:
C#中的字符和字符串处理使用Unicode编码。字符类型表示UTF-16代码单元,字符串类型表示UTF-16代码单元序列。
因此,我们知道,如果我们请求空转换(即,从UTF-16到UTF-16),我们将得到所需的结果:
Encoding.Unicode.GetBytes(".NET String to byte array")
但为了避免提及编码,我们必须采用另一种方式。如果可以接受中间数据类型,则有一个概念上的快捷方式:
".NET String to byte array".ToCharArray()
这并不能为我们提供所需的数据类型,但Mehrad的答案显示了如何使用BlockCopy将此Char数组转换为Byte数组。然而,这将复制字符串两次!而且,它也显式地使用特定于编码的代码:数据类型System.Char。
获取存储字符串的实际字节的唯一方法是使用指针。fixed语句允许获取值的地址。根据C#规范:
[对于]字符串类型的表达式。。。初始值设定项计算字符串中第一个字符的地址。
为此,编译器使用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;
}
正如@CodesInChaus所指出的,结果取决于机器的端序。但问题的作者并不关心这一点。
其他回答
这是一个流行的问题。重要的是要了解作者所问的问题,以及它与最常见的需求不同。为了防止在不需要的地方滥用代码,我首先回答了后者。
共同需求
每个字符串都有一个字符集和编码。将System.String对象转换为System.Byte数组时,仍有字符集和编码。对于大多数用途,您可以知道需要哪个字符集和编码,.NET使“复制并转换”变得简单。只需选择适当的encoding类即可。
// 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"
显然,转换不一定是无损的!
注意:对于System.String,源字符集是Unicode。
唯一令人困惑的是,.NET使用字符集的名称作为该字符集的一个特定编码的名称。编码。Unicode应称为Encoding.UTF16。
这就是大多数用法。如果这正是你所需要的,请停止阅读这里。如果您不了解编码是什么,请参阅有趣的Joel Spolsky文章。
特定需求
现在,作者提出的问题是,“每个字符串都存储为一个字节数组,对吗?为什么我不能简单地拥有这些字节?”
他不想改变信仰。
根据C#规范:
C#中的字符和字符串处理使用Unicode编码。字符类型表示UTF-16代码单元,字符串类型表示UTF-16代码单元序列。
因此,我们知道,如果我们请求空转换(即,从UTF-16到UTF-16),我们将得到所需的结果:
Encoding.Unicode.GetBytes(".NET String to byte array")
但为了避免提及编码,我们必须采用另一种方式。如果可以接受中间数据类型,则有一个概念上的快捷方式:
".NET String to byte array".ToCharArray()
这并不能为我们提供所需的数据类型,但Mehrad的答案显示了如何使用BlockCopy将此Char数组转换为Byte数组。然而,这将复制字符串两次!而且,它也显式地使用特定于编码的代码:数据类型System.Char。
获取存储字符串的实际字节的唯一方法是使用指针。fixed语句允许获取值的地址。根据C#规范:
[对于]字符串类型的表达式。。。初始值设定项计算字符串中第一个字符的地址。
为此,编译器使用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;
}
正如@CodesInChaus所指出的,结果取决于机器的端序。但问题的作者并不关心这一点。
Use:
string text = "string";
byte[] array = System.Text.Encoding.UTF8.GetBytes(text);
结果是:
[0] = 115
[1] = 116
[2] = 114
[3] = 105
[4] = 110
[5] = 103
如何在.NET(C#)中将字符串转换为字节[]而不手动指定特定编码?
NET中的字符串将文本表示为UTF-16代码单元的序列,因此字节已经在UTF-16中的内存中编码。
Mehrad的回答
您可以使用Mehrad的答案,但它实际上使用了编码,因为字符是UTF-16。它调用ToCharArray,通过查看源代码创建一个char[]并将内存直接复制到它。然后,它将数据复制到同样分配的字节数组中。因此,在后台,它复制了两次底层字节,并分配了一个在调用后不使用的字符数组。
Tom Blodget的回答
Tom Blodget的答案比Mehrad快20-30%,因为它跳过了分配一个字符数组并将字节复制到其中的中间步骤,但它需要使用/safe选项进行编译。如果你绝对不想使用编码,我认为这是正确的方法。如果将加密登录放在固定块中,甚至不需要分配单独的字节数组并将字节复制到其中。
此外,为什么要考虑编码?我不能简单地获取字符串存储的字节数吗?为什么依赖字符编码?
因为这是正确的方法。字符串是一个抽象。
如果“字符串”包含无效字符,使用编码可能会给您带来麻烦,但这不应该发生。如果将数据输入到字符串中包含无效字符,则说明操作错误。您可能应该首先使用字节数组或Base64编码。
如果使用System.Text.Encoding.Unicode,代码将更具弹性。您不必担心运行代码的系统的端序。您不必担忧下一版本的CLR是否会使用不同的内部字符编码。
我认为问题不在于你为什么要担心编码,而是你为什么要忽略它而使用其他东西。编码旨在表示字节序列中字符串的抽象。System.Text.Encoding.Unicode将为您提供一个小端字节顺序编码,并将在现在和将来的每个系统上执行相同的编码。
最接近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的回答,因为他把你引向了“字符串对象的字节”的方向。但它依赖于实现,而且因为他在窥探内部,所以很难重建字符串的副本。
迈赫达德的回应是错误的,因为它在概念层面上具有误导性。您仍然有一个字节列表,已编码。他的特定解决方案允许保留未配对的代孕对象——这取决于实现。如果GetBytes默认以UTF-8格式返回字符串,他的特定解决方案将无法准确生成字符串的字节。
我已经改变了主意(Mehrad的解决方案)——这不是获取字符串的字节数;而是获取从字符串创建的字符数组的字节。无论编码如何,c#中的char数据类型都是固定大小。这允许生成一致长度的字节数组,并且允许基于字节数组的大小来再现字符数组。因此,如果编码是UTF-8,但每个字符都是6字节以容纳最大的utf8值,那么它仍然可以工作。所以事实上,字符的编码并不重要。
但是使用了转换——每个字符都放在一个固定大小的框中(c#的字符类型)。然而,这个表示是什么并不重要,从技术上来说,这是OP的答案。所以,如果你无论如何都要转换。。。为什么不“编码”?
嗯,我读过所有的答案,它们都是关于使用编码或关于删除未配对代理的序列化。
例如,如果字符串来自SQL Server,它是从存储例如密码哈希的字节数组构建的,这就很糟糕了。如果我们从中删除任何内容,它将存储一个无效的哈希,如果我们想将其存储在XML中,我们希望保持它的完整性(因为XML编写器会在它找到的任何未配对代理上删除一个异常)。
所以我在这种情况下使用了字节数组的Base64编码,但是在互联网上,只有一种解决方案是C#,而且它有bug,而且只有一种方法,所以我已经修复了bug并编写了返回过程。给你,未来的谷歌人:
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);
}