在C#中无需手动指定编码获取字符串一致字节表示的方法

在C#中无需手动指定编码获取字符串一致字节表示的方法

技术背景

在C#编程中,经常会遇到需要将字符串转换为字节数组,以及将字节数组转换回字符串的场景。然而,不同的字符编码(如ASCII、UTF - 8、UTF - 16等)会对字符串的字节表示产生影响。有时候,我们可能只希望简单地获取字符串存储的字节,而不进行任何编码转换,并且能够将这些字节重新构建回原来的字符串。

实现步骤

通用转换方法

如果不需要对字节进行解释,以下方法可以直接获取字符串的字节表示,并将字节数组还原为字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);
}

使用特定编码转换

如果需要考虑编码,.NET提供了方便的方法:

1
2
3
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);

不使用编码提及的获取方法

要避免提及编码,可以使用指针的方式获取字符串存储的实际字节:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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;
}

使用Span<T>的方法

从C# 7.2开始,可以使用Span<T>来获取字符串的底层内存表示:

1
byte[] bytes = "rubbish_香_string".AsSpan().AsBytes().ToArray();

将字节数组转换回字符串:

1
2
3
4
5
6
7
8
string s;
unsafe
{
fixed (char* f = &bytes.AsSpan().NonPortableCast<byte, char>().DangerousGetPinnableReference())
{
s = new string(f);
}
}

核心代码

通用转换方法

1
2
3
4
5
6
7
8
9
10
11
12
13
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);
}

使用特定编码转换

1
2
var bytes = System.Text.Encoding.UTF8.GetBytes(data);
var decoded = System.Text.Encoding.UTF8.GetString(bytes);

不使用编码提及的获取方法

1
2
3
4
5
6
7
8
9
10
11
12
unsafe byte[] GetRawBytes(String s)
{
if (s == null) return null;
var codeunitCount = s.Length;
var byteCount = codeunitCount * 2;
var bytes = new byte[byteCount];
fixed(void* pRaw = s)
{
Marshal.Copy((IntPtr)pRaw, bytes, 0, byteCount);
}
return bytes;
}

最佳实践

  • 无需解释字节时:如果只是简单地存储和恢复字符串,不涉及字节的解释和处理,使用通用转换方法(GetBytesGetString)可以避免编码带来的复杂性。
  • 需要考虑编码时:如果要将字符串传输到外部系统,或者需要与其他程序交互,建议使用特定的编码(如UTF - 8)进行转换,以确保数据的正确传输和处理。
  • 追求性能时:在性能敏感的场景中,可以使用Span<T>来获取字符串的底层内存表示,但要注意其使用的安全性。

常见问题

为什么需要考虑编码?

因为计算机只能处理字节,而字符串是由字符组成的。编码是将逻辑字符转换为物理字节的约定。不同的编码方式对字符的字节表示不同,例如ASCII只能处理英文字符,对于特殊字符和其他语言的字符需要更完整的编码(如UTF - 8、UTF - 16)。

不同编码转换是否会丢失信息?

是的,不同编码转换可能会丢失信息。例如,使用ASCII编码转换包含特殊字符的字符串时,无法处理的字符会被替换为?

GetBytesGetString方法的局限性是什么?

这两个方法只能用于相同系统上GetBytes方法的输出,不能用于任意字节数组。因为它们没有考虑编码信息,只是简单地将字符数组和字节数组进行复制。

使用指针获取字节的方法有什么问题?

使用指针获取字节的方法依赖于机器的字节序(大端序或小端序),并且是实现相关的。如果代码需要在不同的机器上运行,可能会出现问题。


在C#中无需手动指定编码获取字符串一致字节表示的方法
https://119291.xyz/posts/csharp-string-byte-representation-without-encoding/
作者
ww
发布于
2025年5月15日
许可协议