在.NET (C#)中,如何将string
转换为byte[]
而不手动指定特定的编码?
我要对这个字符串进行加密。我可以不进行转换就进行加密,但我仍然想知道为什么编码在这里起作用。
此外,为什么需要考虑编码?我不能简单地获取字符串存储在哪些字节中吗?为什么存在字符编码的依赖关系?
在.NET (C#)中,如何将string
转换为byte[]
而不手动指定特定的编码?
我要对这个字符串进行加密。我可以不进行转换就进行加密,但我仍然想知道为什么编码在这里起作用。
此外,为什么需要考虑编码?我不能简单地获取字符串存储在哪些字节中吗?为什么存在字符编码的依赖关系?
与此处的答案相反,如果字节不需要被解释,你就无需担心编码!
就像您提到的那样,您的目标很简单,就是“获取字符串存储在哪些字节中”,
(当然,也要能够从这些字节重新构建字符串。)
对于这些目标,我真的不明白为什么人们一直告诉您需要使用编码。对于这一点,您确实不需要担心编码。
只需执行以下操作即可:
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);
}
只要您的程序(或其他程序)不尝试以某种方式“解释”这些字节,而您显然没有提到您打算这样做,那么这种方法就是完全正确的!担心编码只会为您增加更多不必要的复杂性。此方法的另一个好处是:无论字符串是否包含无效字符,都不会有问题,因为您仍然可以获取数据并重新构建原始字符串!
它将被编码和解码,因为您只是查看字节。
如果使用了特定的编码,则在对无效字符进行编码/解码时将会出现问题。
GetString
和 GetBytes
需要在相同字节顺序(endianness)的系统上执行才能正常工作。所以你不能使用它来获取想要转换为字符串的字节,然后在其他地方使用。因此,我很难想出什么情况下会想要使用它。 - CodesInChaos例如:
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)。
接受的答案非常复杂。使用包含的.NET类进行此操作:
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);
如果不必要,就不要重复造轮子...
System.Text.Encoding.Unicode
才能与 Mehrdad 的答案等效。 - JodrellSystem.Text.Encoding.Unicode.GetBytes
可能会更精确。 - Erik A. BrandstadmoenBinaryFormatter bf = new BinaryFormatter();
byte[] bytes;
MemoryStream ms = new MemoryStream();
string orig = "喂 Hello 谢谢 Thank You";
bf.Serialize(ms, orig);
ms.Seek(0, 0);
bytes = ms.ToArray();
MessageBox.Show("Original bytes Length: " + bytes.Length.ToString());
MessageBox.Show("Original string Length: " + orig.Length.ToString());
for (int i = 0; i < bytes.Length; ++i) bytes[i] ^= 168; // pseudo encrypt
for (int i = 0; i < bytes.Length; ++i) bytes[i] ^= 168; // pseudo decrypt
BinaryFormatter bfx = new BinaryFormatter();
MemoryStream msx = new MemoryStream();
msx.Write(bytes, 0, bytes.Length);
msx.Seek(0, 0);
string sx = (string)bfx.Deserialize(msx);
MessageBox.Show("Still intact :" + sx);
MessageBox.Show("Deserialize string Length(still intact): "
+ sx.Length.ToString());
BinaryFormatter bfy = new BinaryFormatter();
MemoryStream msy = new MemoryStream();
bfy.Serialize(msy, sx);
msy.Seek(0, 0);
byte[] bytesy = msy.ToArray();
MessageBox.Show("Deserialize bytes Length(still intact): "
+ bytesy.Length.ToString());
这是一个常见的问题。重要的是要理解问题作者所询问的内容,它与最常见的需求不同。为了防止在不需要的情况下滥用代码,我首先回答了后者。
每个字符串都有字符集和编码。当您将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使用字符集的名称来命名该字符集的一个特定编码。应将Encoding.Unicode
称为Encoding.UTF16
。
对于大多数用途,就是这样了。如果您需要的是这个,请停止阅读。如果您不理解编码是什么,请参见有趣的Joel Spolsky文章。
现在,问题的作者问道:“每个字符串都存储为字节数组,对吧?我为什么不能简单地获取这些字节?”
他不想进行任何转换。
根据C#规范:
C#中的字符和字符串处理使用Unicode编码。char类型表示UTF-16代码单元,string类型表示UTF-16代码单元序列。
因此,我们知道如果我们请求空转换(即从UTF-16到UTF-16),我们将得到所需的结果:
Encoding.Unicode.GetBytes(".NET String to byte array")
但是为了避免提及编码,我们必须另寻他法。如果可以接受中间数据类型,那么有一个概念上的捷径:
".NET String to byte array".ToCharArray()
这不能得到我们想要的数据类型,但是Mehrdad's answer显示了如何使用BlockCopy将此Char数组转换为Byte数组。但是,这将字符串复制两次!而且,它还显式使用编码特定代码:数据类型System.Char
。
获取String存储的实际字节的唯一方法是使用指针。 fixed
语句允许取地址。来自C#规范:
对于string类型的表达式,...初始值计算字符串中第一个字符的地址。
为此,编译器编写了代码,跳过字符串对象的其他部分,使用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;
}
正如 @CodesInChaos 指出的那样,结果取决于机器的字节序。但问题作者并不关心这个。
Length
属性[的String
]返回该实例中Char
对象的数量,而不是Unicode字符的数量。”因此,您的示例代码编写正确。 - Jan Hettichcodeunitcount * 2
是代码单元的正确字节数。该代码根本不计算码点。 - Tom Blodgetfixed
或 unsafe
代码,也可以这样做:var gch = GCHandle.Alloc("foo", GCHandleType.Pinned); var arr = new byte[sizeof(char) * ((string)gch.Target).Length]; Marshal.Copy(gch.AddrOfPinnedObject(), arr, 0, arr.Length); gch.Free();
- user541686在处理 IT 技术相关内容时,需要考虑编码问题。因为一个字符可能由 1 个或多个字节(最多约6个)表示,而不同的编码方式会以不同的方式处理这些字节。
Joel 在他的博客中谈到了这个问题:
System.Text.Encoding
命名空间。这使我想到了第二部分...选择Unicode
编码就是告诉 .Net 使用底层的字节。你确实需要选择这个编码,因为当某种新奇的 Unicode-Plus 出现时,.Net 运行时需要自由地使用这种更新、更好的编码模型而不会破坏您的程序。但是,目前(和可预见的未来),只需选择 Unicode 编码即可获得所需的结果。
另外,重要的是要理解,即使您使用匹配的编码,您的字符串也必须被重新编写成二进制数据,并且这至少涉及一些位模式的转换。计算机需要考虑到一些问题,如大端和小端、网络字节顺序、数据封装、会话信息等。
仅为证明Mehrdrad的声音answer有效,他的方法甚至可以持久化unpaired surrogate characters(许多人曾对我的回答提出过反对意见,但所有人都同样有罪,例如System.Text.Encoding.UTF8.GetBytes
、System.Text.Encoding.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
BinaryFormatter
中使用 System.Buffer.BlockCopy
ツSystem.Buffer.BlockCopy
,那么所有编码倡导者的争论都将毫无意义。 - Michael BuenSystem.String
是由不可变的字符序列组成;.NET一直允许从任何Char[]
构造一个String
对象,并将其内容导出到一个包含相同值的Char[]
中,即使原始的Char[]
中包含不配对的代理项。 - supercat试试这个,代码更少:
System.Text.Encoding.UTF8.GetBytes("TEST String");
System.Text.Encoding.UTF8.GetBytes("Árvíztűrő tükörfúrógép");
,并且哭泣!它会工作,但是 System.Text.Encoding.UTF8.GetBytes("Árvíztűrő tükörfúrógép").Length != System.Text.Encoding.UTF8.GetBytes("Arvizturo tukorfurogep").Length
而 "Árvíztűrő tükörfúrógép".Length == "Arvizturo tukorfurogep".Length
。 - mg30rg我已经阅读了所有答案,它们都是关于使用编码或者一个关于序列化删除无法配对的代理项。
当字符串来自存储例如密码哈希的字节数组的地方(比如来自SQL Server),如果我们删除任何内容,它将会存储一个无效的哈希值。如果我们想要将其存储在XML中,我们需要将其保持完整(因为XML writer在发现任何未配对的代理项时都会抛出异常)。
因此,在这种情况下,我使用字节数组的Base64编码。但是在互联网上,只有一种C#的解决方案,并且其中存在错误,并且只有一种方法,所以我修复了该错误并编写了回传过程。这里是给未来的Googlers:
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);
}
Convert.ToBase64String(arr);
来进行 base64 转换 byte[] (data) <-> string (serialized data to store in XML file)
。但是为了获取初始的 byte[] (data)
,我需要对包含二进制数据的 String
进行一些操作(这是 MSSQL 返回给我的方式)。因此,上面的函数是用于 String (binary data) <-> byte[] (easy accessible binary data)
。 - Gman