最近要使用中科院计算所的关键词工具NLPIR,用java调用,在windows下测试后放到linux下跑,就发现会有乱码。
windows下默认是GBK,linux下是utf-8,因此在意料之中(尽管最后发现其实不是平台的问题)。
除此之外对于编码问题,一直不清楚,不知道这是工具问题还是平台问题,因此楼主一点一点排查,学到了一些编码的知识,备忘一下~
设置linux下的中文编码包
首先,查看linux设置的默认编码方式,看看是不是因为缺少中文编码包。
[root]# locale
locale是什么? 引自 http://hi.baidu.com/dd_taiyangxue/item/84b85007814bdcd51ff04652
一、locale的五脏六腑
1、 语言符号及其分类(LC_CTYPE)
2、 数字(LC_NUMERIC)
3、 比较和排序习惯(LC_COLLATE)
4、 时间显示格式(LC_TIME)
5、 货币单位(LC_MONETARY)
6、 信息主要是提示信息,错误信息, 状态信息, 标题, 标签, 按钮和菜单等(LC_MESSAGES)
7、 姓名书写方式(LC_NAME)
8、 地址书写方式(LC_ADDRESS)
9、 电话号码书写方式(LC_TELEPHONE)
10、度量衡表达方式(LC_MEASUREMENT)
11、默认纸张尺寸大小(LC_PAPER)
12、对locale自身包含信息的概述(LC_IDENTIFICATION)。二、理解locale的设置
设定locale就是设定12大类的locale分类属性,即 12个LC_*。除了这12个变量可以设定以外,为了简便起见,还有两个变量:LC_ALL和LANG。
它们之间有一个优先级的关系:LC_ALL > LC_* > LANG
可以这么说,LC_ALL是最上级设定或者强制设定,而LANG是默认设定值。
查看当前编码设置,发现当前的设置是en_US.UTF-8。
因此先通过apt-get install locales命令安装locales包。
[root]# apt-get install locales
安装完成locales包后,系统会自动进行locale配置,你只要选择所需的locale,可以多选。最后指定一个系统默认的locale。这样系统就会帮你自动生成相应的locale和配置好系统的locale。但是通过export LC_ALL=zh_CN.UTF-8发现还是报错找不到包。
因此增加新的locale包。
[root]# dpkg-reconfigure locales
ok之后出现选择列表,上下方向键找到zh_CN.UTF-8, zh_CN.GBK等中文编码包,空格选中,回车ok,就开始下载。此时系统中有了中文编码包。
再设置系统locale。
[root]# export LC_ALL=zh_CN.UTF-8
查看locale。
[root]# locale
发现当前的locale是zh_CN.UTF-8。系统遇到中文就不会出现乱码了。
在linux下跑NLPIR为什么还是会有乱码呢?只能是使用工具时没有注意编码。
NLPIR的编码设置
NLPIR的编码设置在NLPIR_init里面。有两个版本。
一种只有2个参数:NLPIR_init(bytes[] text,int encoding)。第2个参数为0时表示GBK编码,为1时表示UTF-8编码。
一种有3个参数:NLPIR_init(bytes[] text, int encoding,int liscense)。第3个参数是为了商用我不用管。但是encoding的意义我没弄清出是怎么对映的(文档里有说UTF8_CODE等,但是这个枚举量在java里是失效的),设置成1时好像还是不是UTF-8,我猜这个版本的是不支持UTF-8的。
(希望NLPIR工具的开发者注意要写得一手好文档啊!!!也不写清楚encoding具体的对应关系)
不幸的是,我在linux里面跑的是第二个版本的,因此不清楚这个encoding参数应该怎么设置才是utf-8。怎么办呢?
这时我发现java与平台无关在编码上的体现了。
java编码格式的总结。引自 http://cai555.iteye.com/blog/661191
java中的String永远都是unicode编码的,以它作为中间结果转化成各种不同的编码格式,比如:
String str = "中文";
byte[] utf8b = str.getBytes("UTF-8");
byte[] gbkb = str.getBytes("GBK");// 没有乱码
System.out.println(new String(utf8b, "UTF-8"));// 没有乱码
System.out.println(new String(gbkb, "GBK"));// 有乱码
System.out.println(new String(gbkb, "UTF-8"));
// 有乱码
System.out.println(new String(utf8b, "GBK"));byte数组utf8b和gbkb是不一样的,是转化成各自编码格式后的二进制数组,而new String(utf8b, "UTF-8")与new String(gbkb, "GBK")是一样的都是以Unicode编码保存。
我们可以来分析一下:
1. 当执行以下时:
- byte[] utf8b = str.getBytes("UTF-8");
jvm实际上是做了这样的转化 UNICODE => UTF-8,就是将Jvm内存中的unicode编码二进制码转化成UTF-8格式的二进制码然后赋值给byte[] utf8b 。这个转化的过程我们不用管,jvm会根据一个编码格式对照表来转化。
2. 当执行后面代码:
- new String(utf8b, "UTF-8")
实际上第二个参数"UTF-8"告诉jvm:“当前utf8b的编码格式是"UTF-8",你就以这个格式转化成unicode吧!”。也就是将utf8b转化成unicode再存入Jvm的内存,utf8=>unicode。(这个参数应该是为了告诉jvm使用“UTF-8”的编码格式对照表来转化)
因此我用工具处理即便是gb2312,也能通过Java的String的unicode编码进行中间过渡。
byte[] nativeBytes = testNLP.NLPIR_GetKeyWords(fileContent.getBytes("gb2312"), 50, true); String nativeStr = new String(nativeBytes,0,nativeBytes.length,"GB2312");
这时nativeStr是unicode编码,输出的时候在不同平台会自动转换。
唉,兜了一个大圈子,不过终于了解编码转换应该怎么处理了。