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

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

此外,为什么还要考虑编码?我不能简单地获取字符串存储的字节数吗?为什么依赖字符编码?


当前回答

嗯,我读过所有的答案,它们都是关于使用编码或关于删除未配对代理的序列化。

例如,如果字符串来自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);
}

其他回答

我不确定,但我认为字符串将其信息存储为一个Chars数组,这对字节来说效率很低。具体来说,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,但存储空间会很低。

随着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类的某些实现,而不是将数据作为字符串进行推理。

// 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);

为了证明Mehrrad的声音回答有效,他的方法甚至可以持久化未配对的代理字符(其中许多人对我的答案持反对态度,但每个人都有同样的错误,例如System.Text.Encoding.UTF8.GetBytes、System.Text.Encding.Unicode.GetBytes;例如,这些编码方法不能持久化高代理字符d800,而这些方法只是用值fffd替换高代理字符):

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.Encding.Unicode.GetBytes,它们只会用值fffd替换高代理项字符

每当这个问题发生变化时,我仍然在想一个序列化程序(无论是来自Microsoft还是来自第三方组件),它可以持久化字符串,即使它包含不成对的代理字符;我时不时地在谷歌上搜索这个:序列化不成对的代理角色.NET。这不会让我失眠,但偶尔有人评论我的答案,说它有缺陷,但他们的答案在不成对的替代角色方面同样有缺陷,这让我很烦。

Darn,Microsoft应该在BinaryFormatter中使用System.Buffer.BlockCopyツ

谢谢!

当被问及您打算如何处理字节时,您回答:

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

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

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

为了进行通信(无论是与完全不同的进程还是将来与相同的程序),您需要严格定义协议,以最大限度地减少使用协议或意外创建错误的难度。依赖于.NET的内部表示并不是一个严格的、明确的,甚至不能保证是一致的定义。标准编码是一个严格的定义,将来不会让您失望。

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

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

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

using System.Text;

// ...

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

正如您所看到的,与实现您自己的读写器方法相比,仅使用内置的编码对象实际上也是更少的代码。