请选择 进入手机版 | 继续访问电脑版

UBAINS

 找回密码
 立即注册
搜索
热搜: UBAINS
查看: 334|回复: 0

[学习笔记] 字符编码

[复制链接]

7

主题

7

帖子

335

积分

版主

Rank: 7Rank: 7Rank: 7

积分
335
QQ
发表于 2020-10-28 18:38:19 | 显示全部楼层 |阅读模式
本帖最后由 pidtfork 于 2020-10-28 18:43 编辑

字符编码几个常见问题


1.         为什么两个中控使用了同样的编码,在两台中控间发送中文会乱码?

2.         中控系统发送和接收什么编码?

3.         在GEdit编辑器保存的中文以什么编码保存?

4.         怎么发送和接收中文?

5.         怎么发送和显示utf8、gbk等编码?

6.         调试助手发送中文为什么显示乱码?


Unicode码

在解决这些问题之前,不得不先来了解一下Unicode码。

•Unicode是什么?

维基百科上是这样描述的

Unicode,中文又称万国码、国际码、统一码、单一码,是计算机科学领域里的一项业界标准。
它对世界上大部分的文字系统进行了整理、编码,使得电脑可以用更为简单的方式来呈现和处理文字。
Unicode伴随着通用字符集的标准而发展,同时也以书本的形式[1]对外发表。Unicode至今仍在不断增修,每个新版本都加入更多新的字符。目前最新的版本为2020年3月公布的13.0.0[2],已经收录超过13万个字符(第十万个字符在2005年获采纳)。
Unicode涵盖的资料除了视觉上的字形、编码方法、标准的字符编码外,还包含了字符特性,如大小写字母。
Unicode的发展由非营利机构统一码联盟负责,该机构致力于让Unicode方案取代既有的字符编码方案。因为既有的方案往往空间非常有限,亦不适用于多语环境。
Unicode备受认可,并广泛地应用于电脑软件的国际化与本地化过程。有很多新科技,如可扩展置标语言(Extensible Markup Language,简称:XML)、Java编程语言以及现代的操作系统,都采用Unicode编码。


我们用人话来理解一下

Unicode预将全世界所有的字符包含在一个集合里,计算机只要支持这一个字符集,就能显示所有的字符,再也不会有乱码了。我们可以想象一下这是一个超级大数组,每一位存一个字符,这样就可以通过一个数值来取一个唯一的字符了,

虽然这样比喻并不严谨,但是意思差不多就是用一个明确的值来表示一个字符。

所以文档编码转换可以是这样的例如一个GBK编码文档转换为UTF8,可以先将GBK解码为Unicode码再按照UTF8进行编码,当然实际中也可以按照两种编码格式的对应关系直接转码而不需要中间态。

这么多符号,Unicode不是一次性定义的,而是分区定义。0号平面也称为基本多文种平面 Basic Multilingual Plane,简称BMP,取值范围为U+0000 - U+FFFF,所有最常见的字符都放在这个平面,

这是Unicode最先定义和公布的一个平面。记住这个,这个最早的BMP 0号平面下面会讲。剩下的字符都放在辅助平面(缩写SMP),码点范围从U+010000一直到U+10FFFF。


Unicode和UTF-8、UTF-16、UTF-32区别


从上面解释中我们看到Unicode只规定了每个字符的码点(值),到底用什么样的字节序表示这个码点,就涉及到编码方法。这就是UTF-8、UTF-16、UTF-32干的事情了,Unicode这么强大为什么不直接用Unicode来传输,

我们来举个栗子,例如你的系统收到了这样一串Unicode码,你要还原成字符正确的是什么?

41 42 41 42

答案可以按照单个字节定义是:ABAB。如果按照双字节来定义就是:䅂䅂

甚至可以是:AB䅂、A䉁B、等多种方式,显然这不是一个好的传输方式,但是也不是不能用不是,所以你还是可以Unicode码直接跨系统传输。

解决这个问题其实很简单就是固定每个字符长度,例如每个字符固定用4个字节编码,这样传输后解码时候就按照4的倍数来解码,每4个字节当成一个字符,这就是UTF-32编码,

用utf32来表示一个字符A的十六进制值是这样的 00 00 00 41,这相对于ASCII编码来的存储同样的信息大了四倍,显然这太浪费存储了,于是有了UTF-8这种编码方法。

针对Unicode传输讲的问题,uft8将一个字节 00 的最高位留出来用来表示可以是多个字节组合成一个字符的 如这个字符 „ 的十六进制是 C2 84,

当解码到84时发现 &80 最高位为1时就知道这是个多字节编码需要和前面的字节一起解码,当然也可能是和后面的字节一起,这就是涉及到编码大小端的问题了。

所以utf8是一个可变长度的编码协议,兼容了ASCII码的前面127位,同时常用字符编码字节比较短,所以很流行用这个编码了。

Unicode的实现方式称为Unicode转换格式(UnicodeTransformation Format,简称为UTF)。


•UTF-16


UTF-16编码介于UTF-32与UTF-8之间,同时结合了定长和变长两种编码方法的特点。

它的编码规则很简单:基本平面的字符占用2个字节(这里的基本平面就是上面讲的0号平面,最早最先定义,最常用的字符都在这里了),辅助平面的字符占用4个字节。

也就是说,UTF-16的编码长度要么是2个字节(U+0000到U+FFFF),要么是4个字节(U+010000到U+10FFFF)。


JavaScript使用哪一种编码?

JavaScript语言采用Unicode字符集,但是只支持一种编码方法。

这种编码既不是UTF-16,也不是UTF-8,更不是UTF-32。上面那些编码方法,JavaScript都不用。

JavaScript用的是UCS-2!

UCS-2编码

互联网还没出现的年代,曾经有两个团队,不约而同想搞统一字符集。一个是1988年成立的Unicode团队,另一个是1989年成立的UCS团队。等到他们发现了对方的存在,很快就达成一致:世界上不需要两套统一字符集。

1991年10月,两个团队决定合并字符集。也就是说,从今以后只发布一套字符集,就是Unicode,并且修订此前发布的字符集,UCS的码点将与Unicode完全一致。

UCS的开发进度快于Unicode,1990年就公布了第一套编码方法UCS-2,使用2个字节表示已经有码点的字符。(那个时候只有一个平面,就是基本平面,所以2个字节就够用了。)UTF-16编码迟至1996年7月才公布,

明确宣布是UCS-2的超集,即基本平面字符沿用UCS-2编码,辅助平面字符定义了4个字节的表示方法。

两者的关系简单说,就是UTF-16取代了UCS-2,或者说UCS-2整合进了UTF-16。所以现在只有UTF-16没有UCS-2。



JavaScript的诞生背景

那么,为什么JavaScript不选择更高级的UTF-16,而用了已经被淘汰的UCS-2呢?

答案很简单:非不想也,是不能也。因为在JavaScript语言出现的时候,还没有UTF-16编码。

1995年5月,Brendan Eich用了10天设计了JavaScript语言;10月,第一个解释引擎问世;次年11月,Netscape正式向ECMA提交语言标准(整个过程详见《JavaScript诞生记》)。

对比UTF-16的发布时间(1996年7月),就会明白Netscape公司那时没有其他选择,只有UCS-2一种编码方法可用!

由于JavaScript只能处理UCS-2编码,造成所有字符在这门语言中都是2个字节,如果是4个字节的字符,会当作两个双字节的字符处理。JavaScript的字符函数都受到这一点的影响,无法返回正确结果。下文中我们就用utf16泛指UCS-2。


中控发送数据时的处理

因为中控使用的是JavaScript这里使用的是UCS-2即utf-16编码子集,所有字符都必须是两个字节。但是ascii是单字节的。

所以这样就有矛盾了,如果中控通信都采用了utf-16的编码,中控发送英文字符时就会有问题,例如发送字符 "A" 十六进制 0x41用调试助手接收十六进制查看会变成 00 41,但是实际上我们收到的是 41 并没有 00 在前面,

这这是因为中控串口或者网口发送的数据的时候做了一个 base64encode 编码,&了一个0xFF 导致高位的数据丢失了,这也是Unicode码值小于256 即 0xFF 的值可以正常发送。

中文这种使用了两个字符标的丢失了高位数据。所以串口、网络发送字符串刚刚好是ASCII的原因。这又引入了一个新问题?

为什么发送的时候要进行 base64编码,接收时候要进行base64解码,是不是蛋疼!

其实是为了将js中运行的值传输给驱动层,因为js是需要操作硬件,为了方便在js和驱动层中交换数据,所以使用了base64编码,

Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法。可以避免在不同语言中产生编码问题。

因为去掉了数据高位,虽然ASCII码数据处理没有问题但是也导致中控间互相发送数据时不处理的话接收就会是乱码。

//7D59 是 “好”对应的utf-16编码

vardata_s = getTcpString(data);   //转换接收的数据格式

setButtonText("AAAAA",data_s);

按钮AAAAA显示}Y ,这里 } 的ascii的十六进制值就是 7D , Y 的ascii的十六进制值就是 59,其实是把一个完整的utf16编码字符拆分了两个值

//   我    爱    你    中    国     对应的Unicode编码十六进制为
//  6211  7231 4f60  4e2d  56fd
setButtonText("AAAAA","\u6211\u7231\u4f60\u4e2d\u56fd");

如果这样写会发现显示是按钮上也会显示中文 我爱你中国 因为这个没有进行base64encode编码,pad端也是js环境直接正确解析了Unicode编码

还有\u 转义是告诉系统后面跟着是Unicode码 和 \x 转义定义的是十六进制一样


中控间传输中文 utf-16编码

根据上面的问题,我们自己动手写一个支持传输utf-16编码的方法

先将中文的Unicode值转成十六进制字符串,变成单字节(因为字符串是ascii码取消了高位不影响)再在前面加上特定的字符串做标识,接收方将标识转换下,这样就能完成中控间传输中文了。

另一台中控收到的字符串后先检查一下有没有 U 字符串标识的,有的话全替换成转义的 \u

  1. function setUnicodeString(text){
  2.     varstr = "",temp= "";
  3.     for(vari = 0; i < text.length; i++) {
  4.        temp = text.charCodeAt(i);
  5.         if(temp <= 255) {
  6.            str += "U00"+temp.toString(16);
  7.         }
  8.         else
  9.            str += "U"+temp.toString(16);
  10.         }
  11.     returnstr;
  12. }

  13. function getUnicodeString(text) {
  14.     varreg = new RegExp("U[0-9,a-f,A-F]{4}","g");
  15.     returntext.replace(reg,function(data){
  16.         return String.fromCharCode("0x"+data.substr(1,4));
  17.     });
  18. }
复制代码



我们可以在端口发送前使用setUnicodeString方法对数据进行编码,接收到数据先用getUnicodeString进行解码,这样我们就可以在两个中控将传输中文了,当然实际使用中我们并不这样使用,这只是一个例子来帮助我们理解

中控中常用编码


实际使用中我们通过下面方法来设置和转换编码,text 为待转换的数据字符串,发送的时候以某种编码发送,接收的时候以某种编码接收即可。如果是ASCII码通信就无所谓了,可以不用设置字符编码。


//将utf16转为gbk编码
setU2G(text);

//将utf8转为gbk编码
setU82G(text);

//将gbk转utf8编码
setG2U(text);

//将utf16转为gbk编码
setGBK(text);

//将utf16转utf8编码
setUTF8(text);

//将utf8转为utf16
utf8to16(text);

//将utf6转为utf8
utf16to8(text);

//将gbk转utf16编码
utf8to16(setG2U(text));



问题解答
1.         为什么两个中控使用了同样的编码,在两台中控间发送中文会乱码?
              因为base64编码丢弃了高位,解码时又不能两个一起解码;
2.         中控系统发送和接收什么编码?
              按照效果可以理解为ASCII编码发送接收;
3.         在GEdit编辑器保存的中文以什么编码保存?
              文档为Unicode码存储;因为JavaScript语言特性,只能存储基本平面的字符。
4.         怎么发送和接收中文?
              使用setGBK、setUTF8、setG2U等方法处理接收发送字符编码;
5.         怎么发送和显示utf8、gbk等编码?
              同上
6.         调试助手发送中文为什么显示乱码?
              通常调试助手为GBK编码,中控数据发送时先进行setGBK转码操作


拓展知识


UTF-16编码分 UTF-16LE和UTF-16BE,不同之处在于每个2byte组里面的顺序是反过来的,即0xD852和0xDF62改成0x52D8和0x62DF就是utf16le编码。至于为什么会有这么蛋疼的区分,那是操作系统的遗留问题,就像window的CRLF和unix的LF一样。UCS-2 属于UTF-16LE;



BOM


文件的一个元数据叫BOM(byte-order mark)了,BOM位于文件二进制流的最前方,标识当前文件的编码格式。
UTF16LE的BOM为FF FE,UTF16BE的BOM为FE FF,UTF8的BOM为EF BB BF,可以通过十六进制查看器查看有BOM标识的文件信息。可以通过NotePad++转换BOM信息,中控读取FTP文件时读取到BOM信息的,如果想要处理BOM信息可以通过前缀处理。


高级模式
B Color Image Link Quote Code Smilies |上传

本版积分规则

Archiver|手机版|小黑屋|BBS.UBAINSYUN.COM

GMT+8, 2021-6-20 14:07 , Processed in 0.042878 second(s), 20 queries .

Powered by UBAINS! X3.4

© 2001-2017 UBAINS Inc.

快速回复 返回顶部 返回列表