为什么在将整数转换为 ASCII 然后再转换回整数后得到不同的值?
Why do I get a different value after turning an integer into ASCII and then back to an integer?
为什么当我将 INT 值转换为字节和 ASCII 并返回时,我得到另一个值?
示例:
var asciiStr = new string(Encoding.ASCII.GetChars(BitConverter.GetBytes(2000)));
var intVal = BitConverter.ToInt32(Encoding.ASCII.GetBytes(asciiStr), 0);
Console.WriteLine(intVal);
// Result: 1855
ASCII 仅为 7 位 - 不支持 127
以上的代码点。不支持的字符被转换为 ?
每 the docs on Encoding.ASCII
:
The ASCIIEncoding object that is returned by this property might not have the appropriate behavior for your app. It uses replacement fallback to replace each string that it cannot encode and each byte that it cannot decode with a question mark ("?") character.
所以2000
十进制=D0 07 00 00
十六进制(小端)=[unsupported character] [BEL character] [NUL character] [NUL character]
=? [BEL character] [NUL character] [NUL character]
=3F 07 00 00
十六进制(小端)=1855
十进制。
字符串编码(ASCII、UTF8、SHIFT_JIS 等)旨在将人类语言归类为二进制(字节)形式。它不是为存储任意二进制数据而设计的,例如整数的二进制形式。
虽然您的二进制数据将被解释为字符串,但某些信息将会丢失,这意味着在一般情况下以这种方式存储二进制数据将失败。您可以使用以下代码查看失败的地方:
for (int i = 0; i < 255; ++i)
{
var byteData = new byte[] { (byte)i };
var stringData = System.Text.Encoding.ASCII.GetString(byteData);
var encodedAsBytes = System.Text.Encoding.ASCII.GetBytes(stringData);
Console.WriteLine("{0} vs {1}", i, (int)encodedAsBytes[0]);
}
如您所见,它开始时很好,因为所有字符代码都对应于 ASCII 字符,但是一旦我们得到数字(即 128 及以上),我们开始需要超过 7 位来存储二进制值。此时它不再被正确解码,我们开始看到 63 返回而不是输入值。
最终您将遇到使用任何字符串编码对二进制数据进行编码的问题。您需要选择一种专门用于将二进制数据存储为字符串的编码方法。
两种流行的方法是:
- Hexadecimal
- Base64 使用 ToBase64String and FromBase64String
十六进制示例(使用十六进制方法here):
int initialValue = 2000;
Console.WriteLine(initialValue);
// Convert from int to bytes and then to hex
byte[] bytesValue = BitConverter.GetBytes(initialValue);
string stringValue = ByteArrayToString(bytesValue);
Console.WriteLine("As hex: {0}", stringValue); // outputs D0070000
// Convert form hex to bytes and then to int
byte[] decodedBytesValue = StringToByteArray(stringValue);
int intValue = BitConverter.ToInt32(decodedBytesValue, 0);
Console.WriteLine(intValue);
Base64 示例:
int initialValue = 2000;
Console.WriteLine(initialValue);
// Convert from int to bytes and then to base64
byte[] bytesValue = BitConverter.GetBytes(initialValue);
string stringValue = Convert.ToBase64String(bytesValue);
Console.WriteLine("As base64: {0}", stringValue); // outputs 0AcAAA==
// Convert form base64 to bytes and then to int
byte[] decodedBytesValue = Convert.FromBase64String(stringValue);
int intValue = BitConverter.ToInt32(decodedBytesValue, 0);
Console.WriteLine(intValue);
P.S。如果您只是想将整数转换为字符串(例如“2000”),那么您可以简单地使用 .ToString()
:
int initialValue = 2000;
string stringValue = initialValue.ToString();
TL;DR:一切都很好。但你是 character replacement.
的受害者
我们从 2000
开始。首先,我们承认这个数字可以用十六进制表示为 0x000007d0
.
BitConverter.GetBytes
BitConverter.GetBytes(2000)
是一个4字节的数组,因为2000是一个32位的整型字面量。所以 32 位整数表示,在小端(最低有效字节在前),由以下字节序列给出 { 0xd0, 0x07, 0x00, 0x00 }
。在十进制中,这些相同的字节是 { 208, 7, 0, 0 }
Encoding.ASCII.GetChars
呃哦!问题。 这就是事情可能发生意外转变的地方。
您要求系统将这些字节解释为 ASCII-encoded 数据。问题是 ASCII 使用 0-127 的代码。值为 208 (0xd0
) 的字节不对应于任何可由 ASCII 编码的字符。那么实际发生了什么?
解码 ASCII 时,如果遇到一个超出 0-127 范围的字节,则它 decodes that byte to a replacement character 并移动到下一个字节。这个替换字符是一个问号?
。因此,您从 Encoding.ASCII.GetChars 返回的 4 个字符是 ?
、BEL(铃)、NUL(空)和NUL(空)。
BEL
是代码为 7 的字符的 ASCII 名称,传统上在有能力的终端上显示时会发出哔声。 NUL(代码 0)是一个空字符,传统上用于表示字符串的结尾。
新字符串
现在您可以从该字符数组创建一个字符串。在 C# 中,字符串完全能够在字符串主体中表示 NUL 字符,因此您的字符串中将包含两个 NUL 字符。它们可以用 "[=19=]"
在 C# 字符串文字中表示,以防您想自己尝试。表示您拥有的字符串的 C# 字符串文字将是 "?\a[=20=][=20=]"
您知道 BEL 字符可以用 escape sequence \a
表示吗?很多人不知道。
Encoding.ASCII.GetBytes
现在你开始逆行。您的字符串完全由 ASCII 范围内的字符组成。问号的编码是代码 63 (0x3F)。 BEL 为 7,NUL 为 0。因此字节数为 { 0x3f, 0x07, 0x00, 0x00 }
。惊讶吗?好吧,您现在正在编码一个问号,之前您提供了一个无法用 ASCII 编码表示的 208 (0xd0) 字节。
BitConverter.ToInt32
将这四个字节转换回 32 位整数得到整数 0x0000073f
,十进制形式为 1855
。
为什么当我将 INT 值转换为字节和 ASCII 并返回时,我得到另一个值?
示例:
var asciiStr = new string(Encoding.ASCII.GetChars(BitConverter.GetBytes(2000)));
var intVal = BitConverter.ToInt32(Encoding.ASCII.GetBytes(asciiStr), 0);
Console.WriteLine(intVal);
// Result: 1855
ASCII 仅为 7 位 - 不支持 127
以上的代码点。不支持的字符被转换为 ?
每 the docs on Encoding.ASCII
:
The ASCIIEncoding object that is returned by this property might not have the appropriate behavior for your app. It uses replacement fallback to replace each string that it cannot encode and each byte that it cannot decode with a question mark ("?") character.
所以2000
十进制=D0 07 00 00
十六进制(小端)=[unsupported character] [BEL character] [NUL character] [NUL character]
=? [BEL character] [NUL character] [NUL character]
=3F 07 00 00
十六进制(小端)=1855
十进制。
字符串编码(ASCII、UTF8、SHIFT_JIS 等)旨在将人类语言归类为二进制(字节)形式。它不是为存储任意二进制数据而设计的,例如整数的二进制形式。
虽然您的二进制数据将被解释为字符串,但某些信息将会丢失,这意味着在一般情况下以这种方式存储二进制数据将失败。您可以使用以下代码查看失败的地方:
for (int i = 0; i < 255; ++i)
{
var byteData = new byte[] { (byte)i };
var stringData = System.Text.Encoding.ASCII.GetString(byteData);
var encodedAsBytes = System.Text.Encoding.ASCII.GetBytes(stringData);
Console.WriteLine("{0} vs {1}", i, (int)encodedAsBytes[0]);
}
如您所见,它开始时很好,因为所有字符代码都对应于 ASCII 字符,但是一旦我们得到数字(即 128 及以上),我们开始需要超过 7 位来存储二进制值。此时它不再被正确解码,我们开始看到 63 返回而不是输入值。
最终您将遇到使用任何字符串编码对二进制数据进行编码的问题。您需要选择一种专门用于将二进制数据存储为字符串的编码方法。
两种流行的方法是:
- Hexadecimal
- Base64 使用 ToBase64String and FromBase64String
十六进制示例(使用十六进制方法here):
int initialValue = 2000;
Console.WriteLine(initialValue);
// Convert from int to bytes and then to hex
byte[] bytesValue = BitConverter.GetBytes(initialValue);
string stringValue = ByteArrayToString(bytesValue);
Console.WriteLine("As hex: {0}", stringValue); // outputs D0070000
// Convert form hex to bytes and then to int
byte[] decodedBytesValue = StringToByteArray(stringValue);
int intValue = BitConverter.ToInt32(decodedBytesValue, 0);
Console.WriteLine(intValue);
Base64 示例:
int initialValue = 2000;
Console.WriteLine(initialValue);
// Convert from int to bytes and then to base64
byte[] bytesValue = BitConverter.GetBytes(initialValue);
string stringValue = Convert.ToBase64String(bytesValue);
Console.WriteLine("As base64: {0}", stringValue); // outputs 0AcAAA==
// Convert form base64 to bytes and then to int
byte[] decodedBytesValue = Convert.FromBase64String(stringValue);
int intValue = BitConverter.ToInt32(decodedBytesValue, 0);
Console.WriteLine(intValue);
P.S。如果您只是想将整数转换为字符串(例如“2000”),那么您可以简单地使用 .ToString()
:
int initialValue = 2000;
string stringValue = initialValue.ToString();
TL;DR:一切都很好。但你是 character replacement.
的受害者我们从 2000
开始。首先,我们承认这个数字可以用十六进制表示为 0x000007d0
.
BitConverter.GetBytes
BitConverter.GetBytes(2000)
是一个4字节的数组,因为2000是一个32位的整型字面量。所以 32 位整数表示,在小端(最低有效字节在前),由以下字节序列给出 { 0xd0, 0x07, 0x00, 0x00 }
。在十进制中,这些相同的字节是 { 208, 7, 0, 0 }
Encoding.ASCII.GetChars
呃哦!问题。 这就是事情可能发生意外转变的地方。
您要求系统将这些字节解释为 ASCII-encoded 数据。问题是 ASCII 使用 0-127 的代码。值为 208 (0xd0
) 的字节不对应于任何可由 ASCII 编码的字符。那么实际发生了什么?
解码 ASCII 时,如果遇到一个超出 0-127 范围的字节,则它 decodes that byte to a replacement character 并移动到下一个字节。这个替换字符是一个问号?
。因此,您从 Encoding.ASCII.GetChars 返回的 4 个字符是 ?
、BEL(铃)、NUL(空)和NUL(空)。
BEL
是代码为 7 的字符的 ASCII 名称,传统上在有能力的终端上显示时会发出哔声。 NUL(代码 0)是一个空字符,传统上用于表示字符串的结尾。
新字符串
现在您可以从该字符数组创建一个字符串。在 C# 中,字符串完全能够在字符串主体中表示 NUL 字符,因此您的字符串中将包含两个 NUL 字符。它们可以用 "[=19=]"
在 C# 字符串文字中表示,以防您想自己尝试。表示您拥有的字符串的 C# 字符串文字将是 "?\a[=20=][=20=]"
您知道 BEL 字符可以用 escape sequence \a
表示吗?很多人不知道。
Encoding.ASCII.GetBytes
现在你开始逆行。您的字符串完全由 ASCII 范围内的字符组成。问号的编码是代码 63 (0x3F)。 BEL 为 7,NUL 为 0。因此字节数为 { 0x3f, 0x07, 0x00, 0x00 }
。惊讶吗?好吧,您现在正在编码一个问号,之前您提供了一个无法用 ASCII 编码表示的 208 (0xd0) 字节。
BitConverter.ToInt32
将这四个字节转换回 32 位整数得到整数 0x0000073f
,十进制形式为 1855
。