如何在C#中获得字符串的一致字节表示而无需手动指定编码?

如何在. NET(C#)中将string转换为byte[]而无需手动指定特定编码?

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

还有,为什么要考虑编码?我不能简单地获取字符串存储在哪个字节中吗?为什么要依赖字符编码?

1180535 次浏览
byte[] strToByteArray(string str){System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();return enc.GetBytes(str);}

这取决于字符串的编码(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 1Console.WriteLine (utf8.Length); //Will print 2Console.WriteLine (System.Text.Encoding.ASCII.GetString (ascii)); //Will print '?'

ASCII根本不适合处理特殊字符。

在内部,. NET框架使用UTF-16来表示字符串,因此如果您只是想获取. NET使用的确切字节,请使用System.Text.Encoding.Unicode.GetBytes (...)

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

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

您需要考虑编码,因为1个字符可以由1或多个字节(最多约6个)表示,不同的编码将以不同的方式处理这些字节。

乔尔对此有一个帖子:

每个软件开发人员绝对,肯定必须了解Unicode和字符集(没有借口!)

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

Utf8是一种流行的编码,它紧凑而不会丢失。

我不确定,但我认为字符串将其信息存储为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,但它会降低存储空间的效率。

你的问题的第一部分(如何获取字节)已经被其他人回答了:查看System.Text.Encoding命名空间。

我会解决你的后续问题:为什么你需要选择一种编码?为什么你不能从字符串类本身得到它?

答案分两部分。

首先,字符串类不重要内部使用的字节,每当您假设它们使用时,您可能会引入bug。

如果您的程序完全在. Net世界中,那么您根本不需要担心获取字符串的字节数组,即使您要通过网络发送数据。相反,使用. Net Serialization来担心传输数据。您不再担心实际字节:Serialization格式化程序会为您做这件事。

另一方面,如果你将这些字节发送到某个你不能保证会从. Net序列化流中提取数据的地方怎么办?在这种情况下,你绝对需要担心编码,因为显然,这个外部系统关心。所以,字符串使用的内部字节无关紧要:你需要选择一种编码,这样你就可以在接收端明确地说明这种编码,即使它与. Net内部使用的编码相同。

我理解在这种情况下,你可能更喜欢在可能的情况下使用字符串变量存储在内存中的实际字节,这样可能会节省创建字节流的工作。然而,我告诉你,与确保你的输出在另一端被理解相比,这并不重要,并保证你必须明确地使用你的编码。此外,如果你真的想匹配你的内部字节,你已经可以选择Unicode编码,并获得性能节省。

这就把我带到了第二部分……选择Unicode编码<强>是告诉. Net来使用底层字节。你确实需要选择这种编码,因为当一些新奇的Unicode-Plus出现时。Net运行时需要可以自由地使用这种更新、更好的编码模型,而不会破坏你的程序。但是,就目前(以及可预见的未来)而言,仅仅选择Unicode编码就可以满足你的需求。

同样重要的是要理解你的字符串必须重写为线,这至少涉及到位模式即使你使用匹配的编码的一些转换。计算机需要考虑诸如Big vs Little Endian、网络字节顺序、分组、会话信息等。

BinaryFormatter 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 encryptfor (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());

两种方式:

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

而且,

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

我倾向于使用底部比顶部更频繁,没有对它们进行速度基准测试。

请解释为什么要考虑编码。我不能简单地得到字符串存储在哪个字节中吗?为什么要依赖编码?!!!

因为没有“字符串的字节”这样的东西。

字符串(或更一般地说,文本)由字符组成:字母、数字和其他符号。仅此而已。然而,计算机对字符一无所知;它们只能处理字节。因此,如果你想使用计算机存储或传输文本,你需要将字符转换为字节。你是如何做到的?编码就出现在这里。

编码只不过是将逻辑字符转换为物理字节的约定。最简单和最著名的编码是ASCII,如果您用英语编写,它就是您所需要的全部。对于其他语言,您将需要更完整的编码,任何Unicode风格都是当今最安全的选择。

因此,简而言之,试图“在不使用编码的情况下获取字符串的字节”与“不使用任何语言编写文本”一样不可能。

顺便说一句,我强烈建议你(和任何人,就此而言)阅读这一小段智慧:每个软件开发人员绝对、肯定必须了解Unicode和字符集的绝对最低限度(没有借口!)

最快的方法

public static byte[] GetBytes(string text){return System.Text.ASCIIEncoding.UTF8.GetBytes(text);}

编辑正如Makotosan评论的那样,这是现在最好的方法:

Encoding.UTF8.GetBytes(text)

好吧,我已经阅读了所有答案,它们是关于使用编码或关于丢弃未配对代理的序列化。

例如,当字符串来自SQL服务器时,它是从存储密码哈希的字节数组构建的,这就很糟糕了。如果我们从它中删除任何内容,它会存储一个无效的哈希,如果我们想将其存储在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);}

试试这个,代码少得多:

System.Text.Encoding.UTF8.GetBytes("TEST String");
bytes[] buffer = UnicodeEncoding.UTF8.GetBytes(string something); //for converting to UTF then get its bytes
bytes[] buffer = ASCIIEncoding.ASCII.GetBytes(string something); //for converting to ascii then get its bytes

公认的答案非常非常复杂。使用包含的。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);

不要重新发明轮子如果你不需要…

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

就像你提到的,你的目标很简单,就是"获取字符串存储在哪个字节中"
(当然,能够从字节重建字符串。)

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

只需这样做:

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

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

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

它将被编码和解码一样,因为你是只是看着字节

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

为了证明Mehrdrad的声音回答有效,他的方法甚至可以持久化不成对代理字符(其中许多人反对我的答案,但每个人都同样有罪,例如System.Text.Encoding.UTF8.GetBytesSystem.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 54e 65s 73t 74? d800T 54e 65s 73t 74

尝试使用System.Text.Encoding.UTF8. GetBytes系统。文本。编码。Unicode。获取字节,它们只会将高代理字符替换为值fffd

每次这个问题发生变化时,我仍然在想一个序列化器(无论是来自微软还是来自第三方组件),即使它包含未配对的代理字符,它也可以持久化字符串;我不时地在谷歌上搜索这个:序列化不成对的代理字符。NET。这并没有让我失眠,但是当不时有人评论我的答案有缺陷时,这有点烦人,然而,当涉及到未配对的代理字符时,他们的答案同样有缺陷。

该死,微软应该在其BinaryFormatter中使用System.Buffer.BlockCopy

谢谢!

使用LINQ的简单代码

string s = "abc"byte[] b = s.Select(e => (byte)e).ToArray();

编辑:正如下面的评论,这不是一个好方法。

但您仍然可以使用它通过更合适的编码来理解LINQ:

string s = "abc"byte[] b = s.Cast<byte>().ToArray();

这是我对StringByte[]转换的不安全实现:

public static unsafe Byte[] GetBytes(String s){Int32 length = s.Length * sizeof(Char);Byte[] bytes = new Byte[length];
fixed (Char* pInput = s)fixed (Byte* pBytes = bytes){Byte* source = (Byte*)pInput;Byte* destination = pBytes;
if (length >= 16){do{*((Int64*)destination) = *((Int64*)source);*((Int64*)(destination + 8)) = *((Int64*)(source + 8));
source += 16;destination += 16;}while ((length -= 16) >= 16);}
if (length > 0){if ((length & 8) != 0){*((Int64*)destination) = *((Int64*)source);
source += 8;destination += 8;}
if ((length & 4) != 0){*((Int32*)destination) = *((Int32*)source);
source += 4;destination += 4;}
if ((length & 2) != 0){*((Int16*)destination) = *((Int16*)source);
source += 2;destination += 2;}
if ((length & 1) != 0){++source;++destination;
destination[0] = source[0];}}}
return bytes;}

它比公认的答案快得多,即使不像它那么优雅。以下是我在10000000次迭代中的秒表基准:

[Second String: Length 20]Buffer.BlockCopy: 746msUnsafe: 557ms
[Second String: Length 50]Buffer.BlockCopy: 861msUnsafe: 753ms
[Third String: Length 100]Buffer.BlockCopy: 1250msUnsafe: 1063ms

要使用它,您必须在项目构建属性中勾选“允许不安全代码”。根据。NET Framework 3.5,此方法也可以用作字符串扩展:

public static unsafe class StringExtensions{public static Byte[] ToByteArray(this String s){// Method Code}}

以下是代码:

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

C#将string转换为byte数组:

public static byte[] StrToByteArray(string str){System.Text.UTF8Encoding  encoding=new System.Text.UTF8Encoding();return encoding.GetBytes(str);}

您可以使用以下代码将string转换为byte array。NET

string s_unicode = "abcéabc";byte[] utf8Bytes = System.Text.Encoding.UTF8.GetBytes(s_unicode);

OP的问题:“如何在. NET(C#)中将string转换为byte数组?”[原文如此]

您可以使用以下代码:

static byte[] ConvertString (string s) {return new byte[0];}

作为一个好处,编码并不重要!哦,等等,这是一个ecoding……它只是琐碎和高度有损的。

用途:

    string text = "string";byte[] array = System.Text.Encoding.UTF8.GetBytes(text);

结果是:

[0] = 115[1] = 116[2] = 114[3] = 105[4] = 110[5] = 103

这是一个很受欢迎的问题,理解作者在问什么很重要,而且这个问题和最常见的需求是不同的。为了防止在不需要的地方滥用代码,我先回答了后者。

共同需要

每个字符串都有字符集和编码。当您将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编码。字符type表示一个UTF-16代码单元,字符串类型表示一个UTF-16代码单元序列。

因此,我们知道如果我们要求null转换(即从UTF-16到UTF-16),我们将获得所需的结果:

Encoding.Unicode.GetBytes(".NET String to byte array")

但是为了避免提到编码,我们必须用另一种方式来做。如果可以接受中间数据类型,则有一个概念上的快捷方式:

".NET String to byte array".ToCharArray()

这并没有让我们得到所需的数据类型,但梅尔达德的回答展示了如何使用区块复制将此Char数组转换为Byte数组。然而,这会复制字符串两次!而且,它太显式地使用了特定于编码的代码:数据类型System.Char

获取字符串存储的实际字节的唯一方法是使用指针。fixed语句允许获取值的地址。从C#规范:

[对于]字符串类型的表达式,…初始化器计算字符串中第一个字符的地址。

为此,编译器使用RuntimeHelpers.OffsetToStringData编写跳过字符串对象其他部分的代码。因此,要获取原始字节,只需创建一个指向字符串的指针并复制所需的字节数。

// using System.Runtime.InteropServicesunsafe byte[] GetRawBytes(String s){if (s == null) return null;var codeunitCount = s.Length;/* We know that String is a sequence of UTF-16 code unitsand 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所指出的,结果取决于机器的连续性。但问题作者并不关心这一点。

要将字符串转换为byte[],请使用以下解决方案:

string s = "abcdefghijklmnopqrstuvwxyz";byte[] b = System.Text.UTF32Encoding.GetBytes(s);

希望有帮助。

由于以下事实,字符串可以通过几种不同的方式转换为字节数组:. NET支持Unicode,Unicode标准化了几种称为UTF的差异编码。它们具有不同长度的字节表示,但在这个意义上是等价的,当字符串被编码时,它可以被编码回字符串,但是如果字符串用一个UTF编码并在不同UTF的假设下解码,如果可以搞砸。

此外,. NET支持非Unicode编码,但它们在一般情况下无效(只有在实际字符串中使用Unicode代码点的有限子集时才有效,例如ASCII)。在内部,. NET支持UTF-16,但对于流表示,通常使用UTF-8。它也是Internet的标准事实。

毫不奇怪,类System.Text.Encoding支持字符串序列化为字节数组和反序列化,它是一个抽象类;它的派生类支持具体编码:ASCIIEncoding和四个UTF(System.Text.UnicodeEncoding支持UTF-16)

参考这个链接。

对于使用System.Text.Encoding.GetBytes序列化到字节数组。对于逆操作,请使用System.Text.Encoding.GetChars。此函数返回一个字符数组,因此要获取字符串,请使用字符串构造函数System.String(char[])
参考此页。

示例:

string myString = //... some string
System.Text.Encoding encoding = System.Text.Encoding.UTF8; //or some other, but prefer some UTF is Unicode is usedbyte[] bytes = encoding.GetBytes(myString);
//next lines are written in response to a follow-up questions:
myString = new string(encoding.GetChars(bytes));byte[] bytes = encoding.GetBytes(myString);myString = new string(encoding.GetChars(bytes));byte[] bytes = encoding.GetBytes(myString);
//how many times shall I repeat it to show there is a round-trip? :-)

字符既是字体表的查找键,也是词法传统,例如排序、大小写版本等。

因此,字符不是字节(8位),字节也不是字符。特别是,一个字节的256种排列无法容纳某些书面语言中的数千个符号,更不用说所有语言了。因此,设计了各种编码字符的方法。有些编码特定类别的语言(ASCII编码);使用代码页的多种语言(扩展ASCII);或者,雄心勃勃地,通过选择性地包括所需的额外字节来实现所有语言,Unicode。

在系统中,例如。NET框架,字符串意味着特定的字符编码。在。NET中,这种编码是Unicode。由于框架默认读取和写入Unicode,因此在。NET中通常不需要处理字符编码。

但是,一般来说,要从字节流将字符串加载到系统中,您需要知道源编码,以便正确解释并随后翻译它(否则代码将被视为已经处于系统的默认编码中,从而呈现胡言乱语)。类似地,当字符串写入外部源时,它将以特定的编码写入。

您可以使用以下代码在字符串和字节数组之间进行转换。

string s = "Hello World";
// String to Byte[]
byte[] byte1 = System.Text.Encoding.Default.GetBytes(s);
// OR
byte[] byte2 = System.Text.ASCIIEncoding.Default.GetBytes(s);
// Byte[] to string
string str = System.Text.Encoding.UTF8.GetString(byte1);

如果你真的想要一个字符串的底层字节的副本,你可以使用如下的函数。但是,你不应该请继续阅读以找出原因。

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

byte[]string

        return BitConverter.ToString(bytes);

简单地使用这个:

byte[] myByte= System.Text.ASCIIEncoding.Default.GetBytes(myString);

最接近OP问题的方法是Tom Blodget,它实际上进入对象并提取字节。我说最接近是因为它取决于字符串对象的实现。

"Can't I simply get what bytes the string has been stored in?"

当然,但这就是问题的根本错误所在。String是一个可能具有有趣数据结构的对象。我们已经知道它有,因为它允许存储未配对的代理。它可能存储长度。它可能会保留一个指向每个“配对”代理的指针,允许快速计数。等等。所有这些额外的字节都不是字符数据的一部分。

你需要的是数组中每个字符的字节。这就是“编码”的用武之地。默认情况下,你将获得UTF-16LE。如果你不关心字节本身,除了往返,那么你可以选择任何编码,包括“默认”,并在以后将其转换回来(假设相同的参数,例如默认编码是什么,代码点,bug修复,允许的东西,例如未配对的代理等。

但是为什么把“编码”留给魔法呢?为什么不指定编码,这样你就知道你会得到什么字节?

"Why is there a dependency on character encodings?"

编码(在这种情况下)仅仅意味着表示您的字符串的字节。不是字符串对象的字节。您想要存储字符串的字节-这就是问题被天真地问到的地方。您想要表示字符串的连续数组中的字符串字节,而不是字符串对象可能包含的所有其他二进制数据。

这意味着字符串的存储方式无关紧要。您希望将字符串“编码”为字节数组中的字节。

我喜欢Tom Bloget的回答,因为他把你带到了“字符串对象的字节”的方向。但这取决于实现,因为他在偷看内部,可能很难重建字符串的副本。

Mehrda的回应是错误的,因为它在概念层面上是误导的。你仍然有一个字节列表,编码。他的特定解决方案允许保留未配对的代理-这取决于实现。如果GetBytes默认以UTF-8返回字符串,他的特定解决方案将无法准确生成字符串的字节。


我改变了主意(Mehrhead的解决方案)-这不是获取字符串的字节;而是获取从字符串创建的字符数组的字节。不管编码如何,c#中的char数据类型是固定大小的。这允许生成一致长度的字节数组,并允许根据字节数组的大小复制字符数组。因此,如果编码是UTF-8,但每个char是6个字节以容纳最大的utf8值,它仍然可以工作。所以实际上-字符的编码并不重要。

但是使用了转换-每个字符都放置在一个固定大小的框中(c#的字符类型)。然而,该表示形式是什么并不重要,这在技术上是OP的答案。所以-如果您无论如何都要转换…为什么不'编码'?

我必须将字符串转换为串行通信项目的字节数组-我必须处理8位字符,并且我无法找到使用框架转换器的方法来做到既不添加两字节条目也不错误翻译设置为第8位的字节。所以我做了以下工作:

string message = "This is a message.";byte[] bytes = new byte[message.Length];for (int i = 0; i < message.Length; i++)bytes[i] = (byte)message[i];

我已经写了一个Visual Basic扩展类似于接受的答案,但直接使用. NET内存和编组进行转换,它支持其他方法不支持的字符范围,如UnicodeEncoding.UTF8.GetStringUnicodeEncoding.UTF32.GetString甚至MemoryStream and BinaryFormatter(无效字符,如:񩱠):

<Extension> _Public Function ToBytesMarshal(ByRef str As String) As Byte()Dim gch As GCHandle = GCHandle.Alloc(str, GCHandleType.Pinned)Dim handle As IntPtr = gch.AddrOfPinnedObjectToBytesMarshal = New Byte(str.Length * 2 - 1) {}TryFor i As Integer = 0 To ToBytesMarshal.Length - 1ToBytesMarshal.SetValue(Marshal.ReadByte(IntPtr.Add(handle, i)), i)NextFinallygch.Free()End TryEnd Function
<Extension> _Public Function ToStringMarshal(ByRef arr As Byte()) As StringDim gch As GCHandle = GCHandle.Alloc(arr, GCHandleType.Pinned)TryToStringMarshal = Marshal.PtrToStringAuto(gch.AddrOfPinnedObject)Finallygch.Free()End TryEnd Function

这取决于你想要什么字节

这是因为,正如Tyler恰当地所说,“字符串不是纯数据。它们也有信息。”在这种情况下,信息是创建字符串时假设的编码。

假设您将二进制数据(而不是文本)存储在字符串中

这是基于OP对他自己问题的评论,如果我理解OP对用例的提示,这是正确的问题。

由于上面提到的假设编码,将二进制数据存储在字符串中可能是错误的方法!无论是什么程序或库将二进制数据存储在string(而不是更合适的byte[]数组)中,在战斗开始之前就已经输掉了。如果他们以REST请求/响应或必须传输字符串的方式将字节发送给你,base64将是正确的方法。

如果您有一个未知编码的文本字符串

其他人都错误地回答了这个错误的问题。

如果字符串看起来很好,只需选择一种编码(最好是以UTF开头的编码),使用相应的System.Text.Encoding.???.GetBytes()函数,并告诉您将字节提供给您选择的编码的任何人。

随着C#7.2发布的#0的出现,将字符串的底层内存表示捕获到托管字节数组中的规范技术是:

byte[] bytes = "rubbish_\u9999_string".AsSpan().AsBytes().ToArray();

将其转换回应该是一个非启动程序,因为这意味着您实际上以某种方式解释数据,但为了完整性:

string s;unsafe{fixed (char* f = &bytes.AsSpan().NonPortableCast<byte, char>().DangerousGetPinnableReference()){s = new string(f);}}

名称NonPortableCastDangerousGetPinnableReference应该进一步证明您可能不应该这样做。

请注意,使用Span<T>需要安装System. Memory NuGet包

无论如何,实际原始问题和后续评论暗示底层内存没有被“解释”(我假设这意味着没有修改或读取超出按原样编写的需要),这表明应该使用Stream类的某些实现,而不是将数据推理为字符串。

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

NET中的字符串将文本表示为UTF-16代码单元的序列,因此字节已经以UTF-16编码在内存中。

梅尔达的答案

你可以使用梅尔达德的回答,但它实际上使用了编码,因为字符是UTF-16。它调用ToCharArray,查看的来源会创建一个char[]并将内存直接复制到它。然后它将数据复制到也分配的字节数组。因此,它在幕后复制底层字节两次并分配一个调用后未使用的char数组。

Tom Blodget的回答

Tom Blodget的回答比Mehrda快20-30%,因为它跳过了分配char数组并将字节复制到它的中间步骤,但它需要你使用/unsafe选项编译。如果你绝对不想使用编码,我认为这是要走的路。如果你把加密登录放在fixed块中,你甚至不需要分配一个单独的字节数组并将字节复制到它。

另外,为什么要考虑编码?我不能简单地获取字符串存储在哪个字节中吗?为什么要依赖字符编码?

因为这是正确的方法。string是一个抽象。

如果字符串含有无效字符,使用编码可能会给您带来麻烦,但这不应该发生。如果您将数据输入含有无效字符的字符串,您就做错了。您可能应该使用字节数组或Base64编码开始。

如果您使用System.Text.Encoding.Unicode,您的代码将更有弹性。您不必担心您的代码将在哪个系统上运行。您无需担心CLR的下一个版本是否会使用不同的内部字符编码。

我认为问题不在于你为什么要担心编码,而是你为什么要忽略它并使用其他东西。编码旨在表示字节序列中字符串的抽象。System.Text.Encoding.Unicode会给你一点字节顺序编码,并将在现在和未来的每个系统上执行相同的操作。

当被问及你打算用这些字节做什么时,你回应

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

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

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

对于通信(无论是使用完全不同的进程还是将来使用相同的程序),您都需要定义您的协议严格,以最大限度地减少使用它或意外创建错误的难度。取决于. NET的内部表示不是严格的,清晰的,甚至不能保证是一致的定义。标准编码是一个严格的定义,将来不会让你失望。

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

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

所以选择一种编码并使用它:

using System.Text;
// ...
Encoding.Unicode.GetBytes("abc"); # UTF-16 little endianEncoding.UTF8.GetBytes("abc")

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

如果您对. NET Framework使用. NET Core系统内存,则可以通过Span内存使用非常有效的封送处理机制,它可以有效地将字符串内存重新解释为字节跨度。一旦您有了字节跨度,您就可以自由地列表回另一种类型,或者将跨度复制到数组中进行序列化。

总结一下别人说的话:

  • 存储这种序列化的表示对系统字节顺序、编译器优化以及执行中字符串的内部表示的更改很敏感。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);}

FurthurInsight

在C++这大致相当于reinterpret_cast,C大致相当于转换为系统的单词类型(char)。

在最新版本的. NET Core运行时(CoreCLR)中,对跨度的操作有效地调用编译器内在函数和各种优化,这些优化有时可以消除边界检查,从而在保持内存安全的同时实现卓越的性能,假设您的内存是由CLR分配的,并且跨度不是从非托管内存分配器的指针派生的。

警告

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

计算机只理解原始的二进制数据,原始位。一位是二进制数字:0或1。一个8位的数字是一个字节。一个字节是0到255之间的数字。

ASCII是一个将数字转换为字符的表。0到31之间的数字是控件:tab、new line和其他。32到126之间的数字是可打印字符:字母a,数字1,%符号,下划线_

因此,对于ASCII,有33个控制字符和95个可打印字符。

ASCII是当今最常用的字符编码。Unicode表的第一个条目是ASCII并匹配ASCII字符集。

ASCII是一个7位字符集。数字在0到127之间。有了8位,我们可以提高到255。

ASCII最常见的替代品是EBCDIC,它与ASCII不兼容,今天仍然存在于IBM计算机和数据库中。

1字节,所以8位数字是当今计算机科学中最常见的单位。1字节是0到255之间的数字。

ASCII定义了0到127之间每个数字的含义。

与128到255之间的数字关联的字符取决于所使用的字符编码。目前使用的两种广泛使用的字符编码是windows1252和UTF-8。

在Windows1252中,欧元符号对应的数字是128。1字节:[A0]。在Unicode数据库中,欧元符号是8364。

现在我给你数字8364。两个字节:[20, AC]。在UTF-8中,欧元符号是数字14844588。三个字节:[E282AC]。

现在我给你一些原始数据。假设20AC。是两个窗口1252个字符:£还是一个Unicode欧元符号?

我给你一些原始数据。E282AC。那么,82是一个未赋值的字符,所以它可能不是Windows1252。它可以是macRoman的“'”或OEM 437“”或UTF-8“欧元”符号。

可以根据字符编码的特征和统计数据猜测原始字节流的编码,但没有可靠的方法可以做到这一点。128到255之间的数字本身在UTF-8中无效。é在某些语言(法语)中很常见,所以如果你看到许多字节的值E9被字母包围,它可能是一个windows 1252编码字符串,E9字节代表é字符。

当您有一个表示字符串的原始字节流时,最好知道匹配的编码,而不是试图猜测。

下面是曾经广泛使用的各种编码中的一个原始字节的屏幕截图。

以各种编码显示的一个字节字符