字节数组解Unicode字符串

时间:2023-01-11 13:10:05

这段时间遇到一个问题是需要遇到一个BinaryReader(里面可能塞了各种数据),需要从当前位置读取一个字符串,当然可能可能包括各种符号,可以理解成要读一个byte[]截取一个字符串,如果我们不加过滤的一律都塞到串,然后解码,结果很容易出现乱码。

虽然提供这个字节流的官方中声明了“字符串以Unicode编码”的,但是由于以前对编码知识了解并并不多,所以看到这个声明的时候,我开始是忽略的!o(╯□╰)o在不了解详细存储格式的情况下请教同事, 建议我一个个byte都打印出来!这样对照我的输入和byte流的输出,对比我才发现了,用Unicode编码这个很关键的一点!

首先对于Unicode编码而言,是两个字节存放一个字符便于规范的解析,即汉字占两个字节,ASCII码也占两个字节!ASCII字符的话高位存0,低位存实际字符,对应到字节数组中就是第一个字节存0,第二个字节存字符了。

看字节流搞清楚了这些,也才能开始从字节流中读出当前需要的字符串,比如我需要读的字符串满足ASCII字符或中文字符,下面的实现demo就可以解析出满足条件的字符串。
其中,在isChinese()方法中,我同时判定高低位字节是否同时满足一个Unicode汉字,想扩展的话可以在里面补充其他语言的,这样就可以截取出多语言字符串了。
其中如下的demo中testBytes作为测试输入,每个字符成对出现,最后一个字符刻意放了一个非Unicode字符的,截取到此处停止,最后附上源码:

using UnityEngine;
using System.Text;
using System.Collections.Generic;

public class ParseBinaryStr
{

void Start()
{
byte[] testBytes = {
0,100,
102,47,
101,135,
103,44,
0,13};

getCHStr(testBytes);
}

private string getCHStr(byte[] data, int startIndex = 0)
{
List<byte> bytelist = new List<byte>();
for (int index = startIndex; index < data.Length;)
{
if (index >= data.Length)
break;

byte byte1 = data[index++];
if (byte1 == 0) //byte=0是ASCII码表中的空字符
{
byte byte2 = data[index++];
if (byte2 != 0) //高地位都为0的话就真的 没有字符
{
bytelist.Add(byte2);
bytelist.Add(0);
}
else
{
break;
}
}
else
{
byte byte2 = data[index++];

if (byte2 != 0)
{
if (byte1 != 0 && byte2 != 0 && !isChinese(byte2, byte1))
{
break;
}
else
{
bytelist.Add(byte2);
bytelist.Add(byte1);
}
}
else
{
break;
}
}
}
byte[] res = new byte[bytelist.Count];
for (int index = 0; index < bytelist.Count; index++)
res[index] = bytelist[index];

string str = new string(Encoding.Unicode.GetChars(res));

Debug.Log("截取字符串=" + str);

return str;
}

//中文字符的区间"[\u4E00-\u9FBF]"
private bool isChinese(byte item1, byte item2)
{
return item1 >= 0x00 && item1 <= 0xBF &&
item2 >= 0x4e && item2 <= 0x9f;
}
}

ps:Debug.Log()暴露了我的Unitylog

最后附上一个小建议,看到一个byte[]分不清Unicode高地位的时候,一个小办法是先找一个字符串有Unicode解码工具得到Unicode编码串,

点个小工具就不错!
比如”张”对应Unicode编码串是\u5f20,分别讲十六进制的5f,20转为十进制就直到存到byte[]中的结果应当是什么了。

public void PrintEncodeString()
{
String str111 = "张";
byte[] testbyte = System.Text.Encoding.Unicode.GetBytes(str111);
for (int i = 0; i < testbyte.Length; i++)
{
Debug.Log("testbyte[" + i + "] =" + testbyte[i]);
}
}

看清楚一个字的高低字节分别怎么存的,就不容易搞混了。^_^

最后补充学习编码的知识,特别推荐这篇blog,写的各编码介绍非常详细!
字符编码介绍

其中下面这段很好的解释了Unicode万用码,中间码,Unicode是个好孩子!

不同 ANSI 编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段 ANSI 编码的文本中。一个很大的缺点是,同一个编码值,在不同的编码体系里代表着不同的字。这样就容易造成混乱。导致了unicode码的诞生。 其中每个语言下的ANSI编码,都有一套一对一的编码转换器,Unicode变成所有编码转换的中间介质。所有的编码都有一个转换器可以转换到Unicode,而Unicode也可以转换到其他所有的编码。

此外下面专门针对中文编码GBK的说明中,

GB 18030,全称:国家标准GB 18030-2005《信息技术 中文编码字符集》,是*现时最新的内码字集,是GB 18030-2000《信息技术 信息交换用汉字编码字符集 基本集的扩充》的修订版。与GB 2312完全兼容,与GBK基本兼容,支持GB 13000及Unicode的全部统一汉字,共收录汉字70244个。GB 18030主要有以下特点
与UTF-8相同,采用多字节编码,每个字可以由1个、2个或4个字节组成。
编码空间庞大,最多可定义161万个字符。
支持中国国内少数民族的文字,不需要动用造字区。
汉字收录范围包含繁体汉字以及日韩汉字
中国的程序员们看到这一系列汉字编码的标准是好的,于是通称他们叫做 “DBCS”(Double Byte Charecter Set 双字节字符集)。在DBCS系列标准里,最大的特点是两字节长的汉字字符和一字节长的英文字符并存于同一套编码方案里,因此他们写的程序为了支持中文处 理,必须要注意字串里的每一个字节的值,如果这个值是大于127的,那么就认为一个双字节字符集里的字符出现了。那时候凡是受过加持,会编程的计算机僧侣 们都要每天念下面这个咒语数百遍:
“一个汉字算两个英文字符!一个汉字算两个英文字符……”

看到这一段我想笑了,如果你拿到一段数据并不告知你编码方式的话,这样下去是很折磨人的,除非你查看详细数据。虽然看上去变长存储可以节省空间,我觉得自己单用的时候可以这么考虑,通用的数据还是Unicode好。
最后结合我自己的尝试补充,除Unicode编码外的其他编码发方式不能相互编码解码,可能解出乱码,但是其他编码转到Unicode编码是没有问题的。