Agn*_*ian 2121 .net c# string character-encoding
我如何转换string为byte[]在.NET(C#),而无需手动指定一个特定的编码?
我要加密字符串.我可以在不转换的情况下加密它,但我仍然想知道为什么编码在这里发挥作用.
另外,为什么要考虑编码?我不能简单地得到字符串存储的字节数吗?为什么依赖于字符编码?
Meh*_*dad 1822
就像你提到的那样,你的目标只是"获取字符串存储的字节数".
(当然,能够从字节重新构造字符串.)
只需这样做:
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);
}
Run Code Online (Sandbox Code Playgroud)
只要你的程序(或其他程序)不试图以某种方式解释字节,你显然没有提到你打算这样做,那么这种方法没有任何问题!担心编码只会让你的生活更加复杂,没有真正的理由.
它将被编码和解码相同,因为您只是查看字节.
但是,如果您使用了特定的编码,那么编码/解码无效字符会给您带来麻烦.
bmo*_*ans 1094
例如:
byte[] b1 = System.Text.Encoding.UTF8.GetBytes (myString);
byte[] b2 = System.Text.Encoding.ASCII.GetBytes (myString);
Run Code Online (Sandbox Code Playgroud)
一个小样本为什么编码很重要:
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 '?'
Run Code Online (Sandbox Code Playgroud)
ASCII根本没有配备处理特殊字符.
在内部,.NET框架使用UTF-16来表示字符串,因此如果您只想获取.NET使用的确切字节,请使用System.Text.Encoding.Unicode.GetBytes (...).
有关详细信息,请参阅.NET Framework中的字符编码(MSDN).
Eri*_*oen 275
接受的答案非常非常复杂.使用包含的.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);
Run Code Online (Sandbox Code Playgroud)
如果你不需要,不要重新发明轮子......
Mic*_*uen 111
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());
Run Code Online (Sandbox Code Playgroud)
Zha*_*uid 92
您需要考虑编码,因为1个字符可以由1个或更多字节(最多约6个)表示,不同的编码将以不同的方式处理这些字节.
乔尔有一个帖子:
Tom*_*get 86
这是一个很受欢迎的问题.重要的是要理解作者提出的问题,并且它与最常见的需求不同.为了阻止在不需要的地方滥用代码,我先回答了后面的问题.
每个字符串都有一个字符集和编码.将System.String对象转换为数组时,System.Byte仍然具有字符集和编码.对于大多数用法,您可以知道需要哪个字符集和编码,.NET可以简化"使用转换进行复制".只需选择合适的Encoding班级.
// using System.Text;
Encoding.UTF8.GetBytes(".NET String to byte array")
Run Code Online (Sandbox Code Playgroud)
转换可能需要处理目标字符集或编码不支持源中的字符的情况.您有一些选择:例外,替换或跳过.默认策略是替换"?".
// using System.Text;
var text = Encoding.ASCII.GetString(Encoding.ASCII.GetBytes("You win €100"));
// -> "You win ?100"
Run Code Online (Sandbox Code Playgroud)
显然,转换不一定是无损的!
注意:对于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")
Run Code Online (Sandbox Code Playgroud)
但为了避免提及编码,我们必须采取另一种方式.如果中间数据类型是可接受的,则有一个概念性的快捷方式:
".NET String to byte array".ToCharArray()
Run Code Online (Sandbox Code Playgroud)
这并没有让我们得到所需的数据类型,但Mehrdad的答案显示了如何使用BlockCopy将此Char数组转换为Byte数组.但是,这会复制字符串两次!而且,它也明确使用特定于编码的代码:数据类型System.Char.
获取存储String的实际字节的唯一方法是使用指针.该fixed语句允许获取值的地址.来自C#规范:
[For] 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 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;
}
Run Code Online (Sandbox Code Playgroud)
正如@CodesInChaos指出的那样,结果取决于机器的字节顺序.但问题的作者并不关心这一点.
Joe*_*orn 44
您的问题的第一部分(如何获取字节)已被其他人回答:查看System.Text.Encoding命名空间.
我将解决您的后续问题:为什么需要选择编码?为什么你不能从字符串类本身那里得到它?
答案分为两部分.
首先,字符串类内部使用的字节无关紧要,无论何时你认为它们都可能引入了一个bug.
如果您的程序完全在.Net世界中,那么即使您通过网络发送数据,也不必担心为字符串获取字节数组.相反,使用.Net Serialization来担心传输数据.您不再担心实际的字节:序列化格式化程序会为您执行此操作.
另一方面,如果您将这些字节发送到您无法保证的某个位置会从.Net序列化流中提取数据怎么办?在这种情况下,你肯定需要担心编码,因为显然这个外部系统关心.同样,字符串使用的内部字节无关紧要:您需要选择一个编码,以便您可以在接收端明确表示此编码,即使它与.Net内部使用的编码相同.
我知道在这种情况下你可能更喜欢在可能的情况下使用字符串变量存储在字节变量中的实际字节,并且可能会节省一些创建字节流的工作.但是,我把它告诉你,与确保在另一端理解你的输出相比,它并不重要,并保证你必须明确你的编码.此外,如果您确实希望匹配内部字节,则可以只选择Unicode编码,从而节省性能.
这使我想到的第二部分...采摘Unicode编码是告诉.NET来使用底层的字节.你确实需要选择这种编码,因为当出现一些新奇的Unicode-Plus时,.Net运行时需要免费使用这种更新,更好的编码模型而不会破坏你的程序.但是,目前(以及可预见的未来),只需选择Unicode编码即可获得所需内容.
理解你的字符串必须重写为连线也很重要,即使你使用匹配的编码,也至少需要对位模式进行一些翻译.计算机需要考虑Big vs Little Endian,网络字节顺序,打包,会话信息等.
Mic*_*uen 43
只是为了证明Mehrdrad的合理答案是有效的,他的方法甚至可以坚持不成对的代理人物(许多人反对我的答案,但其中每个人都同样有罪,例如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);
}
}
Run Code Online (Sandbox Code Playgroud)
输出:
T 54
e 65
s 73
t 74
? d800
T 54
e 65
s 73
t 74
Run Code Online (Sandbox Code Playgroud)
尝试使用System.Text.Encoding.UTF8.GetBytes或System.Text.Encoding.Unicode.GetBytes,它们只会用值fffd替换高代理字符
每当这个问题发生变化时,我仍然会想到一个序列化器(无论是来自Microsoft还是来自第三方组件),它可以持久化字符串,即使它包含不成对的代理字符; 我不时地谷歌这个:序列化不成对的代理人物.NET.这并没有让我失去任何睡眠,但是当有人评论我的答案是否存在缺陷时,它会有点烦人,但是当涉及到不成对的代理角色时,他们的答案同样存在缺陷.
Darn,微软应该刚刚使用System.Buffer.BlockCopy它的BinaryFormatterツ
谢谢!
Nat*_*han 39
试试这个,少了很多代码:
System.Text.Encoding.UTF8.GetBytes("TEST String");
Run Code Online (Sandbox Code Playgroud)
Gma*_*man 25
好吧,我已经阅读了所有的答案,他们是关于使用编码或一个关于序列化,删除不成对的代理人.
例如,字符串来自SQL Server,它是从存储密码哈希的字节数组构建的,这很糟糕.如果我们从中删除任何东西,它将存储一个无效的哈希,如果我们想将它存储在XML中,我们希望保持原样(因为XML编写器在它找到的任何未配对的代理上删除了一个例外).
所以我在这种情况下使用字节数组的Base64编码,但是,在互联网上只有一个解决方案,在C#中,它有bug,只有一种方式,所以我修复了bug并写回来了程序.在这里,未来的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);
}
Run Code Online (Sandbox Code Playgroud)
Kon*_*man 23
还请解释为什么要考虑编码.我不能简单地得到字符串存储的字节数吗?为什么这种依赖编码?!!!
因为没有"字符串的字节"之类的东西.
字符串(或更一般地说,文本)由字符组成:字母,数字和其他符号.就这样.然而,计算机对字符一无所知; 他们只能处理字节.因此,如果要使用计算机存储或传输文本,则需要将字符转换为字节.你是怎样做的?这是编码到达现场的地方.
编码只不过是将逻辑字符转换为物理字节的惯例.最简单和最知名的编码是ASCII,如果你用英文写作,它就是你所需要的.对于其他语言,您将需要更完整的编码,任何Unicode风格都是当今最安全的选择.
因此,简而言之,尝试"在不使用编码的情况下获取字符串的字节数"与"不使用任何语言编写文本"一样不可能.
顺便说一下,我强烈建议你(以及任何人,就此而言)阅读这一小小的智慧:绝对最低每个软件开发人员绝对必须知道Unicode和字符集(没有借口!)
Shy*_*hah 22
C#将a转换string为byte数组:
public static byte[] StrToByteArray(string str)
{
System.Text.UTF8Encoding encoding=new System.Text.UTF8Encoding();
return encoding.GetBytes(str);
}
Run Code Online (Sandbox Code Playgroud)
gkr*_*ers 17
byte[] strToByteArray(string str)
{
System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
return enc.GetBytes(str);
}
Run Code Online (Sandbox Code Playgroud)
Jar*_*ark 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);
Run Code Online (Sandbox Code Playgroud)
Joh*_*sch 15
随着Span<T>C#7.2 的发布,将字符串的底层内存表示捕获到托管字节数组的规范技术是:
byte[] bytes = "rubbish_\u9999_string".AsSpan().AsBytes().ToArray();
Run Code Online (Sandbox Code Playgroud)
将其转换回来应该是非首发的,因为这意味着您实际上是以某种方式解释数据,但为了完整起见:
string s;
unsafe
{
fixed (char* f = &bytes.AsSpan().NonPortableCast<byte, char>().DangerousGetPinnableReference())
{
s = new string(f);
}
}
Run Code Online (Sandbox Code Playgroud)
名称,NonPortableCast并DangerousGetPinnableReference应进一步说你可能不应该这样做的论点.
请注意,使用时Span<T>需要安装System.Memory NuGet包.
无论如何,实际的原始问题和后续注释意味着底层内存没有被"解释"(我假设意味着没有修改或读取超出需要按原样写入),表明Stream该类的一些实现应该使用而不是推理数据作为字符串.
Ed *_*rty 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));
}
Run Code Online (Sandbox Code Playgroud)
请注意,Unicode答案在两个实例中都是14个字节,而UTF-8答案第一个只有9个字节,第二个只有7个字节.
因此,如果您只想使用字符串使用的字节,只需使用Encoding.Unicode,但使用存储空间效率低下.
Han*_*ant 11
关键问题是字符串中的字形占用32位(字符代码为16位),但一个字节只有8位备用.除非您将自己限制为仅包含ASCII字符的字符串,否则不存在一对一映射.System.Text.Encoding有很多方法可以将字符串映射到byte [],你需要选择一个避免信息丢失的方法,当你需要将byte []映射回字符串时,客户端很容易使用它.
Utf8是一种流行的编码,它紧凑而且没有损耗.
使用:
string text = "string";
byte[] array = System.Text.Encoding.UTF8.GetBytes(text);
Run Code Online (Sandbox Code Playgroud)
结果是:
[0] = 115
[1] = 116
[2] = 114
[3] = 105
[4] = 110
[5] = 103
Run Code Online (Sandbox Code Playgroud)
最快的方式
public static byte[] GetBytes(string text)
{
return System.Text.ASCIIEncoding.UTF8.GetBytes(text);
}
Run Code Online (Sandbox Code Playgroud)
编辑 Makotosan评论说这是现在最好的方法:
Encoding.UTF8.GetBytes(text)
Run Code Online (Sandbox Code Playgroud)
如何在不手动指定特定编码的情况下将字符串转换为.NET(C#)中的byte []?
.NET中的字符串将文本表示为UTF-16代码单元序列,因此字节在UTF-16中已经在内存中进行编码.
Mehrdad的回答
你可以使用Mehrdad的答案,但它确实使用编码,因为字符是UTF-16.它调用ToCharArray,查看源创建char[]并直接复制内存.然后它将数据复制到也分配的字节数组.所以发动机罩下它是复制底层字节两次并分配该呼叫后不使用字符数组.
Tom Blodget的回答
Tom Blodget的答案比Mehrdad快20-30%,因为它跳过了分配char数组并将字节复制到它的中间步骤,但它需要你使用该/unsafe选项进行编译.如果你绝对不想使用编码,我认为这是要走的路.如果将加密登录放在fixed块中,则甚至不需要分配单独的字节数组并将字节复制到其中.
另外,为什么要考虑编码?我不能简单地得到字符串存储的字节数吗?为什么依赖于字符编码?
因为这是正确的方法. string是一种抽象.
如果您的"字符串"包含无效字符,则使用编码可能会给您带来麻烦,但这不应该发生.如果您使用无效字符将数据输入到字符串中,那么您做错了.您可能应该使用字节数组或Base64编码开始.
如果您使用System.Text.Encoding.Unicode,您的代码将更具弹性.您不必担心代码将运行的系统的字节顺序.如果下一版本的CLR将使用不同的内部字符编码,则无需担心.
我认为问题不在于你为什么要担心编码,而是为什么要忽略它并使用别的东西.编码旨在表示字节序列中字符串的抽象. System.Text.Encoding.Unicode将为您提供一个小端字节顺序编码,并将在现在和将来的每个系统上执行相同的操作.
最接近OP问题的方法是Tom Blodget,它实际上是进入对象并提取字节.我说最接近因为它取决于String对象的实现.
"Can't I simply get what bytes the string has been stored in?"
Run Code Online (Sandbox Code Playgroud)
当然,但这就是问题中出现根本错误的地方.String是一个可能具有有趣数据结构的对象.我们已经知道它确实存在,因为它允许存储未配对的代理.它可能存储长度.它可能会指向每个"配对"代理,以便快速计数.等等.所有这些额外字节都不是字符数据的一部分.
你想要的是数组中每个字符的字节.这就是'编码'的用武之地.默认情况下,你会得到UTF-16LE.如果您不关心除了往返之外的字节本身,那么您可以选择任何编码,包括'default',并稍后将其转换回来(假设相同的参数,例如默认编码,代码点,错误修复) ,允许的事情,如未成对的代理人等.
但为什么要让'编码'达到魔法?为什么不指定编码,以便您知道要获得的字节数?
"Why is there a dependency on character encodings?"
Run Code Online (Sandbox Code Playgroud)
编码(在此上下文中)仅表示表示字符串的字节.不是字符串对象的字节.你想要存储字符串的字节 - 这就是问题被天真地问的地方.您希望字符串的字节在表示字符串的连续数组中,而不是字符串对象可能包含的所有其他二进制数据.
这意味着如何存储字符串是无关紧要的.您希望在字节数组中将字符串"Encoded"转换为字节.
我喜欢Tom Bloget的答案,因为他带你走向'字符串对象的'字节'方向.虽然它依赖于实现,并且因为他正在窥视内部,所以可能很难重新构建字符串的副本.
Mehrdad的回答是错误的,因为它在概念层面上具有误导性.您仍然有一个编码的字节列表.他的特殊解决方案允许保留未配对的代理 - 这取决于实现.如果GetBytes默认情况下以UTF-8返回字符串,则他的特定解决方案不会准确生成字符串的字节.
我已经改变了主意(Mehrdad的解决方案) - 这不是获取字符串的字节; 而是获取从字符串创建的字符数组的字节.无论编码如何,c#中的char数据类型都是固定大小.这允许产生一致的长度字节数组,并且它允许基于字节数组的大小来再现字符数组.因此,如果编码是UTF-8,但每个char是6个字节以容纳最大的utf8值,它仍然可以工作.确实如此 - 角色的编码并不重要.
但是使用了转换 - 每个字符都放在一个固定大小的框中(c#的字符类型).然而,这种表示无关紧要,这在技术上是OP的答案.所以 - 无论如何你要转换......为什么不'编码'?
您可以使用下面的代码的转换string到byte array.NET中
string s_unicode = "abcéabc";
byte[] utf8Bytes = System.Text.Encoding.UTF8.GetBytes(s_unicode);
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1141720 次 |
| 最近记录: |