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

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

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


当前回答

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

Utf8是一种流行的编码方式,它紧凑而无损耗。

其他回答

如果您真的想要一个字符串的基本字节的副本,可以使用下面这样的函数。然而,你不应该继续阅读以找出原因。

[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()

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

正如您提到的,您的目标很简单,就是“获取字符串存储的字节数”。(当然,还要能够根据字节重新构造字符串。)

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

只需执行以下操作:

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

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

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

它将以相同的方式进行编码和解码,因为您只是在查看字节。

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

两种方式:

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();
}

And,

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;
}

我倾向于使用最下面的一个比最上面的一个更频繁,还没有对它们的速度进行基准测试。

如果您使用的是.NET Core或System.Memory for.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核心运行时(CoreCLR)中,如果您的内存是由CLR分配的,并且跨段不是从非托管内存分配器的指针派生的,则跨段上的操作可以有效地调用编译器内部函数和各种优化,这些优化有时可以消除边界检查,从而在保持内存安全的同时提高性能。

注意事项

这使用CLR支持的机制,该机制从字符串返回ReadOnlyPan<char>;此外,此跨度不一定包含完整的内部字符串布局。ReadOnlySpan<T>意味着如果需要执行变异,必须创建一个副本,因为字符串是不可变的。

这取决于字符串的编码(ASCII、UTF-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框架使用UTF-16表示字符串,因此,如果您只想获得.NET使用的确切字节,请使用System.Text.Encoding.Unicode.GetBytes(…)。

有关详细信息,请参阅.NET Framework(MSDN)中的字符编码。