当前位置:网站首页>【锟斤拷】的故事:谈谈汉字编码和常用字符集
【锟斤拷】的故事:谈谈汉字编码和常用字符集
2022-07-06 09:29:00 【若苗瞬】
文章目录
之前N篇都提到了汉字乱码,真是个长久困扰我们的问题。
无论开发语言,磁盘文件,数据库,网络传输都可能出现编码问题。
(一)编码
计算机本无码,它们只认0101的二进制(我们为了方便经常写作格式0xFF的16进制)。
所以要显示任何文字都需要进行编码,即使是英文字母。所以是人类创造了码。
废话尽量简短吧:
PS:内容和图来自百度和其它网站(能找到链接的都给出了)。
1.1 ASCII码
ASCII= American Standard Code for Information Interchange=美国信息交换标准码
单个字节表示一个字符,最高位为0,其它位的组合表示了各种英文字母与符号,比如:
最多: 0111 1111,7F
HEX:41 42 43 44 2C 31 32 33 34 —— ABCD,1234
在英语中,用128个符号编码便可以表示所有字母和符号,但是用来表示其他语言是不够的。
1.2 ASCII码的扩展
将最高位也使用起来,比如法语中的é的编码为130(二进制10000010)。
这样一来,这些欧洲国家使用的编码体系,可以表示最多256个符号。
最多: 1111 1111,FF
但是不同的国家有不同的字母,因此,哪怕它们都使用256个符号的编码方式,代表的字母却不一样。比如:
字节(130)在法语编码中代表了é,
在希伯来语编码中却代表了字母Gimel (ג),
在俄语编码中又会代表另一个符号。
但是不管怎样,所有这些编码方式中,0–127表示的符号是一样的,不一样的只是128–255的这一段。
PS:为了知道同样的编码到底表示的具体字符,我们必须知道这段文字的字符集。
1.3 汉字(包括其它文字)的多字节的编码
由于我们有国家标准的编码(GB)又有国际标准的编码(Unicode),所以中文相对复杂一些。
- GB2312编码:1981年5月1日发布的简体中文汉字编码国家标准。GB2312对汉字采用双字节编码,收录7445个图形字符,其中包括6763个汉字。
- BIG5编码:台湾地区繁体中文标准字符集,采用双字节编码,共收录13053个中文字,1984年实施。
- GBK编码:1995年12月发布的汉字编码国家标准,是对GB2312编码的扩充,对汉字采用双字节编码。GBK字符集共收录21003个汉字,包含国家标准GB13000-1中的全部中日韩汉字,和BIG5编码中的所有汉字。
- GB18030编码:2000年3月17日发布的汉字编码国家标准,是对GBK编码的扩充,覆盖中文、日文、朝鲜语和中国少数民族文字,其中收录27484个汉字。GB18030字符集采用单字节、双字节和四字节三种方式对字符编码。兼容GBK和GB2312字符集。
- Unicode编码:国际标准字符集,它将世界各种语言的每个字符定义一个唯一的编码,以满足跨语言、跨平台的文本信息转换。Unicode采用四字节为每个字符编码。
- UTF-8和UTF-16编码:Unicode编码的转换格式,可变长编码,相对于Unicode更节省空间。UTF-16的字节序有大尾序(big-endian)和小尾序(little-endian)之别。PS:UTF-8的汉字通常是三字节。
我们的国标编码(字符集)是这样发展的:
1.4 编码实例和测试
举个例子,四个汉字(参考网站)
【 我 〇 䶵 𬌗 】
- 我:常用字,各种字符集编码都有。
- 〇:早期的GB2312没有收录。
- 䶵:日文的汉字?参考链接,GBK没收录,GB18030是四字节,UTF-8是三字节
- 𬌗:牙齿咬合面,参考链接,GBK没收录,GB18030是四字节,UTF-8是四字节。
用Java测试一下,代码如下:
String aTestStr="中文我〇䶵𬌗abc";
{
System.out.print("UTF-8 :");
byte[] gb = aTestStr.getBytes(StandardCharsets.UTF_8);
for (byte b : gb)
System.out.printf("%#02x,", b);
System.out.println("\n"+new String(gb, StandardCharsets.UTF_8)+"\n");
}
{
System.out.print("GB18030:");
byte[] gb = aTestStr.getBytes("GB18030");
for (byte b : gb)
System.out.printf("%#02x,", b);
System.out.println("\n"+new String(gb, "GB18030")+"\n");
}
{
System.out.print("GBK :");
byte[] gb = aTestStr.getBytes("GBK");
for (byte b : gb)
System.out.printf("%#02x,", b);
System.out.println("\n"+new String(gb, "GBK")+"\n");
}
{
System.out.print("GB2312 :");
byte[] gb = aTestStr.getBytes("GB2312");
for (byte b : gb)
System.out.printf("%#02x,", b);
System.out.println("\n"+new String(gb, "GB2312")+"\n");
}
输出结果如下,和上面表格一致:
呃,稍微仔细点看吧,或者去掉前后无关的字。。。
UTF-8 :0xe4,0xb8,0xad,0xe6,0x96,0x87,0xe6,0x88,0x91,0xe3,0x80,0x87,0xe4,0xb6,0xb5,0xf0,0xac,0x8c,0x97,0x61,0x62,0x63,
中文我〇䶵𬌗abc
GB18030:0xd6,0xd0,0xce,0xc4,0xce,0xd2,0xa9,0x96,0x82,0x35,0x87,0x38,0x99,0x31,0xd2,0x39,0x61,0x62,0x63,
中文我〇䶵𬌗abc
GBK :0xd6,0xd0,0xce,0xc4,0xce,0xd2,0xa9,0x96,0x3f,0x3f,0x61,0x62,0x63,
中文我〇??abc
GB2312 :0xd6,0xd0,0xce,0xc4,0xce,0xd2,0x3f,0x3f,0x3f,0x61,0x62,0x63,
中文我???abc
(二)显示出现乱码的原因
2.1 超出编码范围
如上例,GBK,GB2312都有乱码,生僻的字显示成了?问号。
如果一段字符串的字节码中保存了所用字符集中没有的编码内容,
则显示时会产生看不懂的错乱符号和怪异字符,一般我们叫做乱码。
PS:之前遇到的:《Python写入文本文件时‘GBK’编码器无法编码字符‘\uXXYY‘》就是编码范围的问题。
文章中没有写正确,我也懒得改了,如上测试的结果,Java并不是指定GBK就高枕无忧,得GB18030啊!
2.2 编码UTF8的BOM
在Windows下可能某些UTF8的编码,前3位是Bit Order Mark(之前写错了),但其实UTF8是不需要字节顺序标识位的,所以唯一的作用就是表示这是一个UTF8的文件。
这不是一个很通用,大家都接受的设定(请自行了解BOM),比如Linux是不认BOM的。
如果无视BOM会导致读取时前面会出现有一点点乱码。
最好办法就是不要使用BOM,中文UTF8编码(有BOM)的数据例如下:
EF BB BF 41 42 43 31 32 33 2C E4 B8 AD E6 96 87 E6 B1 89 E5 AD 97
“ABC123,中文汉字”
2.3 不支持中文
比如操作系统不支持,没有安装中文字体。
即使内容编码是正确的,但系统不知道什么是GB18030,没有能显示GB18030的字体。就只能显示成乱码。
其实这种情况不能叫乱码,码是对的,但显示不了而已(一般都是方框框?)。
2.4 用错了编码
Bingo!
比起前面几种不太常见的原因,编程时用错了编码,才是引起乱码的最主要原因。
所谓用错,就是用一种编码,去读另一种编码的字节码内容。
最常见的:用GB系列编码方式读UTF8,用UTF8读GB系列。
PS:之前遇到的:《升级HBase2字符编码问题以及中文显示》属于用错了编码,
不过不是我主动写错的,而是String.getBytes没传递字符集参数,使用了系统默认字符集的问题。
Windows/Linux默认不一样,而Java在后续操作中(HBASE取出数据)对不指定的文本,都采用了UTF8处理。
下面不知道哪位大神整理的表格,遇到乱码时可以看看。
2.5 原始字节码错误
如果像上面提到的:读错后又将内容写入到了一个新的文本文件中,那么这个新的文本文件编码就错了。
文本原始的字节码已经错误了,无论你后来怎么读,显示都是错误的。
特别是【锟斤拷】这种,是不可恢复的错误。
(三)避免文件读写乱码
3.1 注意默认编码
- Java默认采用UTF8编码。
- Linux默认是UTF8编码。
- Windows默认是GB18030编码(大家都说GBK,但是GBK范围小一些,哎)
- 即使Windows下,IntelliJ IDEA的单元测试默认是UTF8编码(怎么测试和正式运行时不一样?)。
3.2 指定编码
- 打开,写入文本文件时,要指定一种编码,需要指定正确。
- 正确的编码无需转换,需转换的编码一定是前面已经错了。
3.3 不要过分依赖自动判断
两种情况:
- 内容太短,两种编码范围均在内。
- 文件太大,前面只有英文,难道需要读完10GB的文本来判断编码?
第二种情况比较好理解,
而第一种情况,内容中只有很少的汉字,如UTF8编码的 【跃跃】,【珊珊】:
跃(UTF8) = E8 B7 83
珊(UTF8) = E7 8F 8A
那么可爱的叠词就是:
跃跃(UTF8) = E8 B7 83,E8 B7 83
珊珊(UTF8) = E7 8F 8A,E7 8F 8A
如果我们2字节一组看:
跃跃(UTF8) = E8 B7 83,E8 B7 83 = E8 B7,83 E8,B7 83 = 璺冭穬(GB18030)
珊珊(UTF8) = E7 8F 8A,E7 8F 8A = E7 8F,8A E7,8F 8A = 鐝婄強(GB18030)
虽然不是常用字,但就能断定当作GB18030是错误的么?
可能跃跃和珊珊看起来太正常,
换个例子 【趃珋】 和 【瓒冪弸】 到底谁才对呢?
(四)延伸讨论:Oracle的字符集
注意Oracle大概是这样的:
- Oracle服务端即使是英文字符集比如ISO8859p1也可以存储中文。
- 只需要Oracle客户端与服务端字符集设置一致。
- 有一种编码除外:服务端AL32UTF8,客户端可设置简中ZHS16GBK / 繁中ZHT16BIG5。
- 当服务端AL32UTF8时,客户端不应该设置成AL32UTF8。
原理如上,但是还要小心有坑:
- 各种客户端软件工具对字符集处理不同。
- Java8不使用Oracle客户端字符集(蛤???)。
比如试过下面几种客户端工具(不代表所有版本!):
- TOAD:不按照NLS_LANG环境变量,无法设置字符集,只支持ZHS16GBK。
- PL/SQL:按照NLS_LANG环境变量,无法设置字符集,但导入数据到AL32UTF8时实际是ZHS16GBK编码,查询时可以同时正确展示AL32UTF8和ZHS16GBK的中文。
- Navicat:按照设置的字符集导入和查询,但导入时中文字符经常出错。
Java的用阿里巴巴的德鲁伊勉强解决了,可以参考之前遇到的问题。
这个:《Oracle数据库字符集为WE8ISO8859P1存储中文和Java读写展示》
以及:《Oracle数据库字符集为WE8ISO8859P1存储中文和客户端程序展示中文问题》
(四)延伸讨论:FTP的字符编码
我们开发FTP也很容易遇到乱码,但是成熟的FTP工具一般不会。
那是因为别人判断得仔细,会详细询问FTP支持的指令,包括命令编码方式等。
- 简单说如果FTP服务端用UTF8,那么不需要任何转换。
- 如果服务端不是UTF-8,则我们需要把我们的GBK字节码,强行转成服务端的编码(包括8859-1一类)。
名称相关的指令,list,put,get,凡是有目录名,文件名的地方都调用一下。
为啥GBK转8859-1,不是UTF8转8859-1呢,因为如果它是UTF8就已经支持中文了啊!!!
嗯,这是个逻辑问题……
部分代码如下:
public String FromServerEncodingString(String aOriString) throws Exception {
if (ftp.getControlEncoding().equalsIgnoreCase("UTF-8")) return aOriString.trim();
else return new String(aOriString.getBytes(ftp.getControlEncoding()), "GBK").trim();
}
public String ToServerEncodingString(String aOriString) throws Exception {
if (ftp.getControlEncoding().equalsIgnoreCase("UTF-8")) return aOriString.trim();
else return new String(aOriString.getBytes("GBK"), ftp.getControlEncoding());
}
不过好在SFTP也就是SSH协议,似乎都是统一的UTF8编码。
吐槽一下FTP真是一个松散的协议啊!!!
暂时到此,以后遇到新情况再说。
边栏推荐
- What is the difficulty of programming?
- Openwrt build Hello ipk
- China double brightening film (dbef) market trend report, technical dynamic innovation and market forecast
- Codeforces Round #799 (Div. 4)A~H
- 力扣:第81场双周赛
- js封装数组反转的方法--冯浩的博客
- Market trend report, technological innovation and market forecast of double door and multi door refrigerators in China
- Tert butyl hydroquinone (TBHQ) Industry Research Report - market status analysis and development prospect forecast
- QT style settings of qcobobox controls (rounded corners, drop-down boxes, up expansion, editable, internal layout, etc.)
- Useeffect, triggered when function components are mounted and unloaded
猜你喜欢
Codeforces Round #801 (Div. 2)A~C
Log statistics (double pointer)
860. Lemonade change
window11 conda安装pytorch过程中遇到的一些问题
Read and save zarr files
Li Kou - 298th weekly match
QT模拟鼠标事件,实现点击双击移动拖拽等
Share an example of running dash application in raspberry pie.
Kubernetes cluster deployment
使用jq实现全选 反选 和全不选-冯浩的博客
随机推荐
Acwing: Game 58 of the week
2078. Two houses with different colors and the farthest distance
Raspberry pie 4B installation opencv3.4.0
Base dice (dynamic programming + matrix fast power)
SF smart logistics Campus Technology Challenge (no T4)
Maximum product (greedy)
Useeffect, triggered when function components are mounted and unloaded
读取和保存zarr文件
生成随机密码/验证码
Codeforces Round #800 (Div. 2)AC
Click QT button to switch qlineedit focus (including code)
Installation and use of VMware Tools and open VM tools: solve the problems of incomplete screen and unable to transfer files of virtual machines
Effet d'utilisation, déclenché lorsque les composants de la fonction sont montés et déchargés
Opencv learning log 28 -- detect the red cup cover
(lightoj - 1369) answering queries (thinking)
Codeforces Round #802(Div. 2)A~D
Market trend report, technical innovation and market forecast of double-sided foam tape in China
1013. Divide the array into three parts equal to and
China double brightening film (dbef) market trend report, technical dynamic innovation and market forecast
使用jq实现全选 反选 和全不选-冯浩的博客