多语言字符集系列文章-- 第三篇 IRIS对多语言字符集的支持和常见问题
3.1 InterSystems IRIS内码与多语言支持
3.1.1 InterSystems IRIS内码与字符集转换
InterSystems IRIS的内码是UTF-16,因此它支持Unicode多语言字符集。对于其它字符编码,例如UTF-8、GB18030、Big 5,它会自动进行转码。例如对于简体中文,它将GB18030输入数据转换为UTF-16,或者将UTF-16数据转换为GB18030输出。
因为GB18030兼容GB2312,所以InterSystems IRIS也支持GB2312。
注意:在第一篇介绍过,GB18030与GBK并不完全兼容,因此InterSystems IRIS理论上不支持GBK。
InterSystems IRIS如何做字符集编码系统间的转换?它使用2种方式:
对于可以通过算法转换的,例如UTF-8与UTF-16的相互转换,InterSystems IRIS会使用算法转换;
对于无法通过算法转换的,例如与UTF-16的相互转换,InterSystems IRIS会使用I/O转换表转换,它其实是两个映射关系表,一个用于特定字符集转UTF-16、另一个用于UTF-16转特定字符集。
注意,大家可能会注意到,GB18030的I/O转换表里并没有所有的GB18030编码,这是为什么呢?回顾一下第一篇关于GB18030的介绍,它提到了GB18030编码中一部分与Unicode编码非连续相关,而另一部分与Unicode编码连续相关。在InterSystems IRIS的GB18030的I/O转换表,其实只有与Unicode编码非连续相关的那部分编码,就是需要通过对照表关联的部分;而连续相关的那部分,是通过算法转换的,因此不在I/O转换表里。这么做的目的是降低把GB18030这么大的代码表全部加载到内存里的硬件成本。
3.1.2 字符集相关的命令、函数和方法
InterSystems IRIS提供了一系列与字符集相关的命令、函数和方法。它们可以帮助我们理解InterSystems IRIS的字符集处理逻辑、帮忙在字符集相关问题出现时进行排错。
这里以汉字为例介绍这些命令、函数和方法。汉字的UTF-16编码的16进制值是9451、GB18030的16进制值是E861。
- ZZDUMP:查看变量16进制编码的命令
例如:我们看一下变量x的16进制值
这里,可以看到变量x保存的是汉字,它的16进制的值是9451。9451就是汉字的UTF-16(Unicode)16进制码。
- $ZCONVERT($ZCVT):转码函数
如果需要通过代码控制不同字符集编码间的转换,可以使用$ZCVT函数。例如,我们把上面变量x的字符转码为GB18030编码:
这个例子中,传递给$ZCVT的第一个参数是变量x;第二个参数是“O”,意思是将x的字符编码转码为目标码;第三个参数是要转换的目标编码,“GB18030”。现在变量y的值就是汉字的GB18030编码。
同样,你可以把变量y的GB18030编码再转为IRIS内码UTF-16。这时,第二个参数要设置为“I”,意思是设置源编码;而第三个参数还是“GB18030”:
现在变量z仍是汉字的UTF-16编码。
- $ASCII、$WASCII :将字符转为10进制UTF-16码
这两个系统函数将所有字符转为其10进制的UTF-16码。名字有点迷惑,它们并不是获得字符的ASCII码,当然对于ASCII字符,ASCII码和UTF-16码是一样的。
其中$ASCII可以将UCS基本平面的字符转为UTF-16,也就是双字节UTF-16编码。例如汉字的就是这样的:
但那些超过双字节UTF-16的汉字就需要用$WASCII来获取其UTF-16的值了。例如汉字“”,它的UTF-16编码是4字节的D85FDFF9,获取它的UTF-16值,就需要用$WASCII($WA)。当然,也可以全用$WASCII来获取所有字符的UTF-16编码。
- $CHAR、$WCHAR: 将10进制UTF-16码转为字符
这两个系统函数将10进制的UTF-16码转为字符。例如:
同样,$CHAR可以转双字节的UTF-16;$WCHAR可以转双字节或四字节的UTF-16。
- $ZHEX:将16进制数转10进制数、10进制数转16进制数
上面有些函数处理16进制数、一些处理10进制数,可以用函数$ZHEX做10进制和16进制数的转换。例如:
USER>w $ZHEX("E861")
59489
大家可能对这个函数比较迷惑:只有一个参数,那么怎么知道它是10进制数还是16进制数呢?例如9451,既可以表达一个10进制数,也可以表达一个16进制数。$ZHEX是这么判断的:是以字符串传入的参数(加了双引号的)就是16进制,而以数字传入的参数(没加双引号的)就是10进制。因此会得到这样的结果:
USER>w $ZHEX(9451)
24EB
USER>w $ZHEX("9451")
37969
3.2 常见字符集相关问题
既然InterSystems IRIS支持GB18030-2005,那么还会有中文字符集相关问题吗?还是会有,因为InterSystems IRIS不仅是一个数据库,它的互操作功能会和各种数据源和数据目标连接,接收和发送各种编码的数据。如果数据源/数据目标所声明使用的字符集与其实际使用的字符集不同,或者未声明使用的字符集并且实际字符集和IRIS默认的字符集不符,就会造成问题。这也是绝大多数的InterSystems IRIS相关字符集问题的根源。
所以在InterSystems IRIS中遇到字符乱码问题时,首先应该看一下数据声明的字符集是什么,实际收到数据的字符集是什么。数据声明的字符集信息,可以看上一篇《各种技术对字符集使用的声明》的介绍,而实际收到数据的字符集可以使用Wireshark等工具检查。
下面列举一些常见的字符集相关的问题处理方式:
3.2.1 文件
对于正确编码的文件,InterSystems IRIS都可以正常处理。但如果文件声明的字符集编码与实际的编码不同时,可以使用$ZCVT进行转码。
如果在使用文件(File)或FTP适配器,这2个适配器都会CHARSET设置项,可以使用这个设置项做InterSystems IRIS内码(UTF-16)和真实需要的字符编码间自动做相互转换。
InterSystems IRIS提供了好几个字符文件的操作类:%Library.File、%Library.FileCharacterStream、%Stream.FileCharacter。在使用它们创建文件时,需要注意指明文字字符集,否则默认为ANSI,从而造成生僻字异常。
InterSystems IRIS不同的字符文件操作类,对于字符集的声明稍有差异,如下:
对于%Library.File,在执行Open方法时设置字符集,例如设置为UTF8
Set tFile = ##class(%File).%New("c:\temp\test.txt")
Do tFile.Open("WSN:/IOTABLE=""UTF8""")
对于%Library.FileCharacterStream,设置其TranslateTable属性,或调用TranslateTableSet方法设置
Set tStream=##class(%FileCharacterStream).%New() Set tStream.Filename="c:\temp\test.txt" Set tStream.TranslateTable="UTF8"
对于%Stream.FileCharacter,设置其TranslateTable属性,或调用TranslateTableSet方法设置
Set tStream = ##class(%Stream.FileCharacter).%New()
Set tST=tStream.LinkToFile("C:\temp\test.txt")
Set tStream.TranslateTable="UTF8"
另外,还要考虑BOM问题,有些文本编辑器打开没有BOM的UTF-8文件时会显示乱码、而另外一些有BOM反而显示乱码。
对于BOM,%Library.File无法在文件头插入BOM,%Library.FileCharacterStream、%Stream.FileCharacter可以通过设置其BOM属性,例如设置字符集为UTF8,且设置BOM为UTF8的EF BB BF(10进制239 187 191):
Set tStream = ##class(%Stream.FileCharacter).%New()
Set st= tStream.LinkToFile("C:\temp\test.txt")
Set tStream.TranslateTable="UTF8"
Set tStream.BOM=$char(239,187,191)
Do tStream.Write($C(8224))
Do tStream.%Save()
从性能和功能角度,推荐使用%Stream.FileCharacter和%Library.FileCharacterStream,而不是%Library.File。
3.2.2 数据库
通过InterSystems IRIS连接到第三方数据库进行查询或更新数据操作而出现乱码是比较典型的字符集问题。
当遇到问题时,尤其是Oracle数据库时,首先检查一下数据源的内码设置。如果是其内码设置问题,那么通常需要通过$ZCVT来转码。
如果不是内码设置问题,查一下InterSystems IRIS使用什么方式连接到对方数据库:ODBC还是JDBC。如果是JDBC,看看连接字符串中的字符集设置是否正确;如果是ODBC,看看是不是生僻字乱码,如果是可能需要用$ZCVT转码,否则要检查ODBC驱动是否正确,尤其是是否在使用其ODBC3.5的驱动。
3.2.3 HTTP
遇到HTTP相关的字符乱码,首先查一下HTTP头Content-Type的参数charset设置。如果对方没有设置或设置不正确,且对方不能修稿,可以使用$ZCVT转码。
对于使用HTTP适配器的IRIS业务服务,可以通过其CHARSET设置项来配置字符集,并通过"FORCE CHARSET" 设置项强制跳过HTTP头Content-Type的参数charset设置。
3.2.4 SOAP/REST传递的XML字符流/字符串
首先检查下encoding属性的设置。如果它没有设置或设置错误,且不能修改,可以使用$ZCVT转码。
3.2.5 HL7 V2消息
HL7 V2是医疗行业常用的消息协议,InterSystems IRIS提供了大量的工具和特性以支持HL7 V2消息。
如果是HL7 V2传输中出现的中文乱码问题,首先查一下MSH-18的设置。如果数据源没有设置或设置错了,都可以使用InterSystems IRIS数据转换能力先将其修正为正确的值。当然有可能接收目标系统不按MSH-18设置的字符集解码数据,这时,可以按目标系统要求的字符集来编码数据,而忽略MSH-18。
InterSystems IRIS提供了很多工具,可以查看HL7 V2消息或文件的内容, 例如HL7查看器、消息追踪。InterSystems IRIS会按MSH-18设置的值进行数据的解码,当MSH-18值缺失时,默认值是Latin1。如果是因为MSH-18缺失而造成中文显示为乱码,且HL7 V2消息源无法修改时,可以考虑修改EnsLib.HL7.Util.IOFraming的DefCharEncoding(字符集编码默认值)为消息源使用的字符集。
注意,这种乱码情况不会影响HL7消息的接受接收和发送,仅影响显示。
3.2.6 其它
可能还会碰到一些的中文乱码其实是显示问题,尤其是那些仅生僻字显示有问题的情况。
出现这类显示问题的原因是并不是所有的操作系统和IT工具都支持完整的汉字字符集或汉字字形。
例如,常见的字符终端工具仅支持GB2312,所以对GB2312字符集之外的汉字就会显示为一个特定的符号,看起来就像乱码。
而MacOS的Safari浏览器、Pages文稿和很多应用都不能显示一些GB18030字符集的生僻字,例如:
这种情况下,数据本身没有问题,只是显示不正常而已,通常不需要担心。
这就是InterSystems IRIS多语言字符集的支持的主要内容内容。后面我会持续将客户遇到的与字符集相关的典型问题持续更新到文章的末尾,供大家参考。
附录1: 列表类型的中文数据显示乱码?
最近一个客户问了一个中文显示乱码问题。经过查看,发现乱码字段/属性的类型是list of %String,而且使用逻辑模式查看数据。换成ODBC模式或显示模式就没有问题了。原因是列表元素间是有特殊控制字符的,不同显示模式处理列表类型的数据方式不同,详见 显示模式 。对于列表类型的属性/字段,建议通过STORAGEDEFAULT="array" 将其映射为SQL子表,从而更容易通过SQL查看和操作数据,同时对象和global的访问不产生任何影响。例如:
Property MyList as list of %String (STORAGEDEFAULT="array");