如何在.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所指出的,结果取决于机器的端序。但问题的作者并不关心这一点。
其他回答
代码如下:
// 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);
}
这取决于你想要的字节
这是因为,正如泰勒所恰当地说的,“字符串不是纯数据。它们也有信息。”在这种情况下,信息是创建字符串时假定的编码。
假设您有二进制数据(而不是文本)存储在字符串中
这是基于OP对自己问题的评论,如果我理解OP在用例中的提示,这是正确的问题。
由于上面提到的假设编码,将二进制数据存储在字符串中可能是错误的方法!无论哪个程序或库将二进制数据存储在字符串中(而不是更合适的byte[]数组),在战斗开始之前就已经失败了。如果他们以REST请求/响应或任何必须传输字符串的方式向您发送字节,Base64将是正确的方法。
如果您有一个编码未知的文本字符串
其他人都错误地回答了这个错误的问题。
如果字符串看起来不错,只需选择一个编码(最好是以UTF开头的编码),使用相应的System.Text.encoding。???。GetBytes()函数,并告诉给谁您选择了哪种编码。
如果您真的想要一个字符串的基本字节的副本,可以使用下面这样的函数。然而,你不应该继续阅读以找出原因。
[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()
嗯,我读过所有的答案,它们都是关于使用编码或关于删除未配对代理的序列化。
例如,如果字符串来自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);
}
随着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);
}
}
NonPortableCast和DangerousGetPinnableReference这两个名称应该进一步证明您可能不应该这样做。
注意,使用Span<T>需要安装System.Memory NuGet包。
无论如何,实际的原始问题和后续评论暗示底层内存没有被“解释”(我假设这意味着没有修改或读取,超出了按原样编写的需要),这表明应该使用Stream类的某些实现,而不是将数据作为字符串进行推理。