如何在不手动指定编码的情况下在C#中获得字符串的一致字节表示形式?


2189

如何转换stringbyte[]在.NET(C#),而无需手动指定一个特定的编码?

我将对字符串进行加密。我可以加密而不进行转换,但是我仍然想知道为什么编码在这里起作用。

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


23
每个字符串都存储为字节数组,对吗?为什么我不能简单地拥有这些字节?
Agnel Kurian

135
编码将字符映射到字节的过程。例如,在ASCII中,字母“ A”映射到数字65。在不同的编码中,它可能不相同。但是,.NET框架中采用的高级字符串处理方法与此无关(在这种情况下除外)。
卢卡斯·琼斯

20
扮演魔鬼的拥护者:如果您想获取内存中字符串的字节(.NET使用它们)并以某种方式进行操作(即CRC32),并且从不希望将其解码回原始字符串...并不清楚为什么您会关心编码或如何选择要使用的编码。
格雷格,

78
令人惊讶的是没有人给过这个链接:joelonsoftware.com/articles/Unicode.html
Bevan

28
字符不是字节,字节不是字符。字符既是字体表的关键字,又是词法传统。字符串是一个字符序列。(单词,段落,句子和标题也有自己的词汇传统,这些文字传统证明了它们自己的类型定义的合理性-但我离题了)。像整数,浮点数以及其他所有内容一样,字符被编码为字节。曾经有一段时间,编码是一对一的简单:ASCII。但是,为了适应所有人类符号体系,一个字节的256个排列是不够的,因此设计了编码以选择性地使用更多字节。
乔治,

Answers:


1855

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

就像您提到的那样,您的目标很简单,就是“获取字符串存储在哪个字节中”
(并且,当然,能够从字节中重建字符串。)

对于这些目标,老实说,我明白为什么人们总是告诉您您需要编码。您当然不必为此担心编码。

只需这样做:

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

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

这种方法的其他好处:

字符串是否包含无效字符并不重要,因为您仍然可以获取数据并重建原始字符串!

因为您只是在看bytes,所以它的编码和解码都一样

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


247
什么是丑陋的这一个是,GetString并且GetBytes需要在系统上使用相同的字节顺序工作执行。因此,您不能使用它来获取要在其他地方转换为字符串的字节。因此,我很难想出一种想要使用它的情况。
CodesInChaos

72
@CodeInChaos:就像我说的那样,它的全部意思是如果您想在具有相同功能集的相同类型的系统上使用它。如果没有,那么您不应该使用它。
user541686

193
-1我保证有人(不了解字节和字符)会想要将其字符串转换为字节数组,他们会在Google上搜索并读取此答案,并且他们会做错事,因为几乎在所有情况下在这种情况下,编码相关的。
artbristol 2012年

401
@artbristol:如果他们不愿意阅读答案(或其他答案...),那么很抱歉,那么我没有更好的方法与他们交流。我通常选择回答OP,而不是去猜测其他人对我的回答会做些什么-OP有权知道,仅仅因为有人可能滥用刀子并不意味着我们需要隐藏世界上所有的刀子为我们自己。不过,如果您不同意也可以。
user541686 2012年

185
这个答案在很多层面上都是错误的,但最重要的是因为它是精巧的“您无需担心编码!”。GetBytes和GetString这2个方法是多余的,因为它们仅仅是Encoding.Unicode.GetBytes()和Encoding.Unicode.GetString()已经完成的工作的重新实现。语句“只要您的程序(或其他程序)不尝试解释字节”,从根本上来说也是有缺陷的,因为它们隐式表示字节应解释为Unicode。
David

1108

它取决于字符串的编码(ASCIIUTF-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)中的字符编码


14
但是,为什么要考虑编码?为什么我不能简单地获取字节而不必查看正在使用哪种编码?即使是必需的,String对象本身也不应该知道正在使用哪种编码,而只是转储内存中的内容吗?
Agnel Kurian

57
.NET字符串始终编码为Unicode。因此,请使用System.Text.Encoding.Unicode.GetBytes(); 获取.NET用来表示字符的字节集。但是,为什么要这样呢?我建议使用UTF-8,尤其是当大多数字符都位于西方拉丁语系中时。
AnthonyWJones

8
另外:字符串内部使用的确切字节无关紧要,如果检索它们的系统不处理该编码或将其处理为错误的编码。如果全部在.Net内,为什么还要转换为字节数组。否则,最好将编码明确化
Joel Coehoorn 2009年

11
@Joel,请谨慎使用System.Text.Encoding.Default,因为在运行的每台计算机上它可能都不同。因此,建议始终指定一种编码,例如UTF-8。
灰烬

25
除非您(或其他人)实际上打算解释数据,否则您不需要编码,而不是将其视为通用的“字节块”。对于压缩,加密等而言,担心编码毫无意义。请参阅我的答案,以了解一种无需担心编码的方法。(我可能以-1表示您不担心编码时会担心,但今天我的感觉并不特别刻薄::P)
user541686

285

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

如果不需要,不要重新发明轮子。


14
为了记录的目的,如果接受的答案发生更改,这是Mehrdad在当前时间和日期的答案。希望OP会重新审视并接受更好的解决方案。
Thomas Eding 2013年

7
原则System.Text.Encoding.Unicode上讲很好,但是编码应等同于Mehrdad的答案。
Jodrell 2014年

5
自原始答案以来,该问题已被编辑了无数次,因此,也许我的答案有点过时了。我从来没有打算像梅赫达德这样大声疾呼,而是要给出一个明智的做法。但是,您可能是对的。但是,原始问题中的短语“获取字符串已存储在哪个字节中”是非常不准确的。存放在哪里?在记忆中?在磁盘上?如果在内存中,System.Text.Encoding.Unicode.GetBytes可能会更精确。
Erik A. Brandstadmoen 2014年

7
@AMissico,您的建议是有问题的,除非您确定您的字符串与系统默认编码兼容(在系统默认旧版字符集中仅包含ASCII字符的字符串)。但是,OP在任何地方都没有指出。
弗雷德里克

5
@AMissico它可以使程序在不同的系统上给出不同的结果。那从来都不是一件好事。即使是用于进行哈希或其他操作(我假设这就是OP对“加密”的含义),相同的字符串仍应始终给出相同的哈希。
Nyerguds '16

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

2
您可以对所有这些操作使用相同的BinaryFormatter实例
Joel Coehoorn 2009年

3
很有意思。显然它将删除任何高替代Unicode字符。请参见[BinaryFormatter ] 上的文档

95

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

乔尔对此发表了文章:

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


6
我同意“ 1个字符可以用1个或多个字节表示”。无论字符串采用哪种编码,我都只希望这些字节。可以在内存中存储字符串的唯一方法是以字节为单位。偶数字符存储为1个或多个字节。我只想让他们了解字节。
Agnel Kurian

16
除非您(或其他人)实际上打算解释数据,否则您不需要编码,而不是将其视为通用的“字节块”。对于压缩,加密等而言,担心编码毫无意义。请参阅我的答案,以了解一种无需担心编码的方法。
user541686

9
@Mehrdad-完全可以,但是,正如我最初回答时所说的那样,原始问题并没有说明在将这些字节转换后,OP将对这些字节发生什么,对于以后的搜索者而言,有关的信息是相关的-这是Joel的答案所涵盖的内容非常好-并在答案中指出:只要您坚持.NET世界,并使用您的方法进行相互之间的转换,就很高兴。只要您走出这一步,编码就很重要。
Zhaph-Ben Duguid 2012年

一个代码点最多可以表示4个字节。(一个UTF-32代码单元,一个UTF-16代理对或4个字节的UTF-8。)UTF-8需要超过4个字节的值超出了Unicode的0x0..0x10FFFF范围。;-)
DevSolar

89

这是一个受欢迎的问题。重要的是要了解问题作者的要求,并且该要求与最常见的要求有所不同。为了防止在不需要的地方滥用该代码,我已经在后面的第一个回答。

共同需求

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

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

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

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

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

这并不能为我们提供所需的数据类型,但是Mehrdad的答案显示了如何使用BlockCopy将此Char数组转换为Byte数组。但是,这会将字符串复制两次!而且,它也明确使用编码特定的代码:datatype System.Char

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

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

为此,编译器使用编写代码跳过字符串对象的其他部分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 codeunits 
       and such codeunits 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所指出的,结果取决于计算机的字节序。但是问题作者对此并不关心。


3
@Jan是正确的,但是字符串长度已经给出了代码单位的数量(不是代码点)。
Tom Blodget 2014年

1
感谢您指出了这一点!从MSDN:“ Length[of属性String]返回Char此实例中的对象数,而不是Unicode字符数。” 因此,您的示例代码正确无误。
Jan Hettich 2014年

1
@supercat“ char类型表示一个UTF-16代码单元,而字符串类型表示一个UTF-16代码单元序列。” —_ C#5规范。_尽管,是的,没有什么可以防止无效的Unicode字符串:new String(new []{'\uD800', '\u0030'})
Tom Blodget 2014年

1
@TomBlodget:有趣的是,如果使用的实例Globalization.SortKey,提取KeyData,然后将每个字节的结果打包成String[每个字符两个字节,MSB首先 ],则调用String.CompareOrdinal结果字符串将比调用或SortKey.Compare实例快得多。SortKey甚至调用memcmp这些实例。鉴于此,我想知道为什么KeyData返回a Byte[]而不是a String
超级猫

1
las,正确的答案,但是太晚了,将永远不会有被接受的票数那么多。由于TL; DR,人们会认为接受的答案很困难。复制并对其进行投票。
Martin Capodici

46

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

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

答案分为两部分。

首先,字符串类内部使用的字节无关紧要,并且每当您假设它们出现时,就很可能引入错误。

如果您的程序完全位于.Net世界之内,那么即使您正在通过网络发送数据,也不必担心完全为字符串获取字节数组。而是使用.Net序列化来担心传输数据。您不必再担心实际的字节数了:序列化格式化程序会为您完成此操作。

另一方面,如果您无法保证将这些字节发送到某个地方,将会从.Net序列化流中提取数据怎么办?在这种情况下,您确实确实需要担心编码,因为显然此外部系统在乎。同样,字符串使用的内部字节也没关系:您需要选择一种编码,这样就可以在接收端清楚地知道此编码,即使它是.Net内部使用的相同编码也是如此。

我了解在这种情况下,您可能更愿意在可能的情况下使用字符串变量存储在内存中的实际字节,这样可能会节省创建字节流的工作。但是,我告诉您,与确保另一端可以理解您的输出,并确保您的编码必须是明确的相比,这并不重要。此外,如果您确实想匹配内部字节,则只需选择Unicode编码即可节省性能。

这使我进入第二部分...选择Unicode编码告诉.Net使用基础字节。您确实需要选择这种编码,因为当出现一些新的Unicode-Plus时,.Net运行时需要自由使用这种更新更好的编码模型,而不会破坏程序。但是,就目前而言(以及可预见的未来),仅选择Unicode编码即可满足您的需求。

理解必须将字符串重写为电线也很重要,即使使用匹配的编码,这也至少涉及位模式的一些翻译。计算机需要考虑诸如Big vs Little Endian,网络字节顺序,数据包化,会话信息等问题。


9
.NET中的某些区域确实需要获取字符串的字节数组。许多.NET密码学类都包含诸如ComputeHash()之类的接受字节数组或流的方法。您别无选择,只能先将字符串转换为字节数组(选择编码),然后有选择地将其包装在流中。但是,只要您选择编码(即UTF8),就不会有任何问题。
Ash Ash

44

只是为了证明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 54
e 65
s 73
t 74
? d800
T 54
e 65
s 73
t 74

尝试使用System.Text.Encoding.UTF8.GetBytesSystem.Text.Encoding.Unicode.GetBytes,它们只会将高替代字符替换为值fffd

每当这个问题出现动静时,我仍在考虑一个序列化程序(无论是来自Microsoft还是来自第三方的组件),即使其中包含不成对的替代字符,该序列化程序也可以保留字符串;我不时在Google上搜索:序列化未配对的代理字符.NET。这不会让我失去任何睡眠,但是每时每刻都有人在烦恼我,我的回答是有缺陷的,但是对于不成对的代理角色,他们的回答同样有缺陷。

哎呀,微软应该System.Buffer.BlockCopy在它中使用BinaryFormatter

谢谢!


3
代理不必成对出现以形成有效的代码点吗?如果是这样,我可以理解为什么数据会被破坏。
dtanders 2012年

1
@dtanders是的,这也是我的想法,它们必须成对出现,如果您故意将它们放在字符串上并使它们不成对,则会出现未成对的替代字符。我不知道的是为什么其他开发人员坚持要求我们改用编码感知方法,因为他们认为序列化方法(我的回答是被接受三年以上的答案)并没有使未成对的问题成为现实。完整的替代字符。但是他们忘了检查自己的编码感知解决方案是否也不会保留未配对的代理字符,具有讽刺意味的是
Michael Buen 2012年

如果有一个System.Buffer.BlockCopy内部使用的序列化库,那么所有倡导编码的人的论据都将是徒劳的
Michael Buen 2012年

2
@MichaelBuen在我看来,主要的问题是,您用粗体大写的字母表示无所谓,而不是说在他们的情况下无所谓。结果,您鼓励那些看您答案的人犯一些基本的编程错误,这些错误将来会导致其他人感到沮丧。未配对的代理在字符串中无效。它不是一个char数组,因此将字符串转换为另一种格式会导致FFFD该字符出错是有意义的。如果要执行手动字符串操作,建议使用char []。
2014年

2
@dtanders:A System.String是的不可变序列Char;.NET始终允许String从任何对象构造对象,Char[]并将其内容导出到Char[]包含相同值的,即使原始对象Char[]包含不成对的替代对象。
2014年

41

试试看,更少的代码:

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

9
@ mg30rg:您为什么认为您的例子很奇怪?当然,在可变宽度编码中,并非所有字符都具有相同的字节长度。它出什么问题了?
弗拉德(

@Vlad在这里,更有效的注释是,作为编码的unicode符号(即字节),包含自己的变音符号的字符所产生的结果与变音符拆分成添加到字符上的修饰符符号的结果不同。但是iirc .net中有一些方法专门将这些方法拆分开,以允许获得一致的字节表示形式。
Nyerguds

25

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

例如,当字符串来自SQL Server时,这是很糟糕的,SQL Server是从存储例如密码哈希的字节数组构建的。如果我们从中删除任何内容,它将存储一个无效的哈希,并且如果我们要将其存储在XML中,我们希望将其保持不变(因为XML编写器会在发现的任何未配对代理中删除异常)。

因此,在这种情况下,我使用字节数组的Base64编码,但是嘿,在Internet上,C#仅对此提供一种解决方案,并且其中包含错误,并且只是一种方法,因此,我已修复了该错误并将其写回程序。未来的Google员工,您好!

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

除了使用自定义方法将字节数组转换为base64之外,您所要做的只是使用内置转换器:Convert.ToBase64String(arr);
Makotosan

@Makotosan谢谢,但是我确实使用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 2012年

23

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

因为没有“字符串的字节”之类的东西。

字符串(或更笼统地说是文本)由字符组成:字母,数字和其他符号。就这样。但是计算机对字符一无所知。他们只能处理字节。因此,如果要使用计算机存储或传输文本,则需要将字符转换为字节。你是怎样做的?这是编码出现的地方。

编码不过是将逻辑字符转换为物理字节的约定。最简单和最广为人知的编码是ASCII,这是您用英语书写时所需要的。对于其他语言,您将需要更完整的编码,因为任何一种Unicode风格都是当今最安全的选择。

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

顺便说一句,我强烈建议您(和其他任何人)阅读这一小知识:每个软件开发人员绝对绝对要完全了解Unicode和字符集(没有任何借口!)


2
请允许我澄清一下:已使用一种编码将“ hello world”转换为物理字节。由于该字符串存储在我的计算机上,因此我确定它必须以字节存储。我只想访问这些字节以将其保存在磁盘上或出于任何其他原因。我不想解释这些字节。由于我不想解释这些字节,因此此时对编码的需求与要求电话线调用printf一样放错了位置。
Agnel Kurian

3
但是同样,除非您使用编码,否则没有文本到物理字节转换的概念。当然,编译器以某种方式将字符串存储在内存中-但它只是使用内部编码,您(或除编译器开发人员以外的任何人)都不知道。因此,无论您做什么,都需要一种编码来从字符串中获取物理字节。
Konamiman

@Agnel Kurian:的确,一个字符串在其内容存储某处有一束字节(UTF-16 afair)。但是有一个很好的理由阻止您访问它:字符串是不可变的,并且如果您可以获取内部byte []数组,则也可以对其进行修改。这打破了不变性,这是至关重要的,因为多个字符串可能共享同一数据。使用UTF-16编码获取字符串可能只会将数据复制出来。
ollb 2011年

2
@Gnafoo,可以复制字节。
Agnel Kurian

22

C#将a转换stringbyte数组:

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

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

但是,为什么要考虑编码?为什么我不能简单地获取字节而不必查看正在使用哪种编码?即使是必需的,String对象本身也不应该知道正在使用哪种编码,而只是转储内存中的内容吗?
Agnel Kurian

5
这并不总是有效。我发现很难用这种方法迷失一些特殊字符。
JB金王

17

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

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

VUP这一解决了我的问题(byte [] ff = ASCIIEncoding.ASCII.GetBytes(barcodetxt.Text);)
r.hamd

16

随着Span<T>C#7.2 的发布,将字符串的基础内存表示捕获到托管字节数组中的规范技术为:

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该类已实现某些实现应该完全代替将数据作为字符串进行推理。


13

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


10

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

Utf8是一种流行的编码,它紧凑且无损。


3
仅当大多数字符都使用英语(ASCII)字符集时,UTF-8才会紧凑。如果您有较长的汉字字符串,则该字符串的UTF-16编码比UTF-8更为紧凑。这是因为UTF-8使用一个字节编码ASCII,否则使用3(或4)。
Joel Mueller

7
真正。但是,如果您熟悉处理中文文本,又怎么不知道编码呢?
汉斯·帕桑

9

采用:

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

结果是:

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

OP明确要求不指定编码...“无需手动指定特定编码”
Ferdz

8

最快的方法

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

编辑 诚诚评论这是最好的方法:

Encoding.UTF8.GetBytes(text)

8
不需要ASCIIEncoding.....。仅使用Encoding.UTF8.GetBytes(text)是首选。
Makotosan

8

在不手动指定特定编码的情况下,如何在.NET(C#)中将字符串转换为byte []?

.NET中的字符串将文本表示为一系列UTF-16代码单元,因此这些字节已经在UTF-16的内存中进行了编码。

梅尔达德的答案

您可以使用Mehrdad的answer,但实际上使用的是编码,因为chars是UTF-16。它调用ToCharArray,后者查看源代码创建一个char[],并将内存直接复制到它。然后它将数据复制到也分配的字节数组中。因此,在幕后它将复制基础字节两次,并分配一个在调用后未使用的char数组。

汤姆·布洛杰特的答案

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

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

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

如果您有带有无效字符的“字符串”,那么使用编码可能会给您带来麻烦,但这不应该发生。如果您要使用无效字符将数据输入字符串,则说明这样做是错误的。首先,您可能应该使用字节数组或Base64编码。

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

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


实际上,C#中的字符串不仅限于UTF-16。确实,它包含一个由16位代码单元组成的向量,但是这些16位代码单元不限于有效的UTF-16。但是由于它们是16位的,因此需要一种编码(字节顺序)才能将它们转换为8位。然后,字符串可以存储非Unicode数据,包括二进制代码(例如,位图图像)。仅在进行此类解释的I / O和文本格式化程序中,它才被解释为UTF-16。
verdy_p

因此,在C#字符串中,即使它们不是UTF-16中的非字符,也可以安全地存储0xFFFF或0xFFFE之类的代码单元,并且可以在0xDC00..0xDFFF中存储一个孤立的0xD800,而不是一个代码单元(即未配对的替代品(在UTF-16中无效)。同样的说明适用于Javascript / ECMAscript和Java中的字符串。
verdy_p

当然,当您使用“ GetBytes”时,您无需指定编码,但是您会假设以字节顺序获取特定字符串中本地存储在字符串中的每个代码单元的两个字节。从字节构建新字符串时,还需要一个转换器,不必是UTF-8到UTF-16的转换器,您可以在高字节中插入额外的0,或将两个字节打包(以MSB优先或LSB优先)。相同的16位代码单元 然后,将字符串压缩为16位整数数组的形式。与“字符”的关系是另一个问题,在C#中它们不是实际类型,因为它们仍表示为字符串
verdy_p

7

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

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

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

您想要的是数组中每个字符的字节。这就是“编码”的来源。默认情况下,您将获得UTF-16LE。如果除了往返行程外,您不关心字节本身,则可以选择包括“默认”在内的任何编码,然后稍后再转换回去(假设使用相同的参数,例如默认编码是什么,代码点,错误修复,允许的东西,例如不成对的代理人等。

但是,为什么要把“编码”交给魔术呢?为什么不指定编码,以使您知道要获取的字节?

"Why is there a dependency on character encodings?"

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

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

我喜欢Tom Bloget的答案,因为他将您带向了“字符串对象的字节”方向。但是,它依赖于实现,并且因为他正在查看内部结构,所以可能很难重新构造字符串的副本。

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


我已经改变了主意(Mehrdad的解决方案)-这不是得到字符串的字节;而是获取从字符串创建的字符数组的字节。不管编码如何,c#中的char数据类型都是固定大小。这样可以产生长度一致的字节数组,并允许根据字节数组的大小来复制字符数组。因此,如果编码为UTF-8,但每个字符为6个字节以容纳最大的utf8值,它将仍然有效。所以确实-字符的编码无关紧要。

但是使用了转换-每个字符都放入一个固定大小的框(C#的字符类型)中。但是,该表示形式无关紧要,从技术上讲,这是OP的答案。所以-如果您仍要进行转换...为什么不“编码”?


这些字符不支持由UTF-8或UTF-16或甚至UTF-32为exapmle: &񩱠&。(Char) 55906 (Char) 55655因此,您可能是错的,Mehrdad的答案是安全的转换,无需考虑使用哪种编码类型。
Mojtaba Rezaeian

Raymon,字符已经由某些unicode值表示-所有unicode值都可以由所有utf表示。您在说什么有更长的解释?这两个值(或3 ..)存在什么字符编码?
Gerard ONeill '02

它们是无效字符,任何编码范围都不支持。这并不意味着它们100%无用。不管编码如何,将任何类型的字符串转换为等效的字节数组的代码根本不是错误的解决方案,并且在所需的场合有其自己的用法。
Mojtaba Rezaeian

1
好的,那我认为您不了解这个问题。我们知道它是一个符合unicode的数组-实际上,因为它是.net,所以我们知道它是UTF-16。因此这些字符将不存在。您也没有完全阅读我关于内部表示形式更改的评论。字符串是一个对象,而不是编码的字节数组。因此,我不同意您的最后一句话。您希望代码将所有unicode字符串转换为任何UTF编码。正确地做到了您想要的。
Gerard ONeill

对象是数据序列,最初是描述对象当前状态的位序列。因此,编程语言中的每个数据都可以转换为字节数组(每个字节定义8位),因为您可能需要在内存中保留任何对象的某些状态。您可以从磁盘中读取字节后,在文件或内存中保存并保存字节序列,并将其转换为整数,bigint,图像,Ascii字符串,UTF-8字符串,加密的字符串或您自己定义的数据类型。因此,您不能说对象不同于字节序列。
Mojtaba Rezaeian

6

您可以使用下面的代码转换stringbyte array在.NET

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

3

如果您确实想要一个字符串的基础字节的副本,则可以使用下面的函数。但是,您不应该继续阅读以找出原因。

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

这很可能会产生相同的结果,更易于键入,并且字节始终会与调用进行往返

System.Text.Encoding.Unicode.GetString()

3

这里是我的不安全落实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;
}

它比公认的anwser的速度要快得多,即使它并不那么优雅。这是我的秒表基准测试,经过10000000次迭代:

[Second String: Length 20]
Buffer.BlockCopy: 746ms
Unsafe: 557ms

[Second String: Length 50]
Buffer.BlockCopy: 861ms
Unsafe: 753ms

[Third String: Length 100]
Buffer.BlockCopy: 1250ms
Unsafe: 1063ms

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

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

RuntimeHelpers.OffsetToStringData.NET的Itanium版本是否为8的倍数?因为否则将由于未对齐的读取而失败。
乔恩·汉娜

调用会不会更简单memcpystackoverflow.com/a/27124232/659190
Jodrell,2014年

2

只需使用以下命令:

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

2
...并以127以上的高分跳过所有字符。用我的母语,写“Árvíztűrőtükörfúrógép”是完全有效的。System.Text.ASCIIEncoding.Default.GetBytes("Árvíztűrő tükörfúrógép.").ToString();将返回"Árvizturo tukörfurogép."无法检索的丢失信息。(而且我还没有提到您会丢失所有字符的亚洲语言。)
mg30rg

2

由于以下事实,可以用几种不同的方式将字符串转换为字节数组:.NET支持Unicode,并且Unicode标准化了几种不同的编码,称为UTF。它们具有不同的字节表示长度,但在某种意义上是等效的,即在对字符串进行编码时,可以将其编码回字符串,但是如果可以使用一个UTF对字符串进行编码并在假定使用不同UTF的情况下对其进行解码,则可以拧紧起来

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

毫不奇怪,类System.Text.Encoding是抽象类,支持将字符串序列化为字节数组并反序列化。其派生类支持具体的编码:ASCIIEncoding和四个System.Text.UnicodeEncodingUTF (支持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 used
byte[] 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? :-)

2

这取决于您想要的字节

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

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

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

由于上述假定的编码,将二进制数据存储在字符串中可能是错误的方法!无论哪种程序或库将二进制数据存储在一个string(而不是一个byte[]更合适的数组中),在开始之前就已经失败了。如果他们通过REST请求/响应或必须传输字符串的任何内容向您发送字节,Base64将是正确的方法。

如果您的文字字符串编码未知

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

如果字符串按原样看起来不错,则只需选择一种编码(最好是一种以UTF开头的编码),然后使用相应的System.Text.Encoding.???.GetBytes()函数,然后告诉谁将字节提供给您选择的编码。


2

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

我要加密它。我可以加密而不进行转换,但是我仍然想知道为什么编码在这里起作用。只要给我字节就是我所说的。

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

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

为了进行通信(使用完全不同的过程或将来使用相同的程序进行通信),您需要严格定义协议,以最大程度地减少使用它或意外创建错误的难度。依赖.NET的内部表示形式不是严格,清晰的甚至保证是一致的定义。标准编码严格的定义,将来不会令您失望。

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

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

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

using System.Text;

// ...

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

如您所见,实际上,仅使用内置的编码对象比实现自己的读取器/写入器方法要少。


1

两种方式:

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

我倾向于使用底部比顶部更多的频率,而没有为它们设定速度基准。


4
那多字节字符呢?
Agnel Kurian

c.ToByte()是私有的:S
Khodor

@AgnelKurian Msdn 说: “此方法返回一个无符号字节值,该值表示传递给它的Char对象的数字代码。在.NET Framework中,Char对象是一个16位值。这意味着该方法适合于返回ASCII字符范围或Unicode C0控件和基本Latin以及C1控件和Latin-1补码范围(从U + 0000到U + 00FF)中的字符的数字代码。”
mg30rg

1
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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.