在通过geotools读取shp文件到postgis数据库的时候,首先要名称当前shp文件的编码格式。如果编码格式没有搞清楚,那么导入数据库的记录就会是乱码。对于shp文件编码格式的定义,在他的文件集中有个以cpg结尾的文件,这个文件记录了shp文件的编码格式。然而,不幸的是不是所有的shp文件集包含这个文件。此时,我们就需要通过另外一种途径判断字符串的编码格式。
我们先把解决方式展现出来,如果只仅仅为了解决问题,那直接拷贝走就可以,但是如果想一探究竟的话,可以继续往下看。需要注意的是,这个方法在概率上并不是完全正确,因为可能出现极端情况而影响结果的准确性。
/**
* 判断字符串的编码格式
* @param str 需要判断的字符串
* @return 判断的编码格式,如果返回null,则不能判断编码
*/
private String checkCharSet(String str) {
try {
int lenUtf8 = new String(str.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8).length();
int lenGbk = new String(str.getBytes(StandardCharsets.ISO_8859_1), "GBK").length();
//字符串不包含中文,不能判断是哪一种编码集
if (lenGbk == lenUtf8) {
return null;
}
return lenUtf8 > lenGbk ? "GBK" : "UTF-8";
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return "UTF-8";
}
大体思路就是对一个字符串分别用utf-8与GBK的方式读取,如果哪个字符短,那就属于哪个编码格式。那么下面我们就论证一下这个说法。
UTF-8是一种变长字节编码方式。对于某一个字符的UTF-8编码,如果只有一个字节则其最高二进制位为0;如果是多字节,其第一个字节从最高位开始,连续的二进制位值为1的个数决定了其编码的位数,其余各字节均以10开头。UTF-8最多可用到6个字节。
0xxxxxxx
110xxxxx 10xxxxxx
1110xxxx 10xxxxxx 10xxxxxx
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
通过上图我们可以知道,0xxxxxxx是占用一个字节,那么他最大代表0~127(000000000 ~ 011111111)。这个也就是ASCII编码区,即utf-8完全兼容ASCII编码。我们知道,一个汉子的3个字节,所以对应1110xxxx 10xxxxxx 10xxxxxx。
通过上面分析,我们知道汉子的编码为3个字节。有效字节为画x的部分,也就是两个字节(4 + 6 + 6)的长度。
我们以“河”为例,进行说明。
确认Unicode编码
通过查询Unicode对照表,可以知道河的Unicode码为:6cb3,网上转换工具一大片,大家可以自行查找。
转换为二进制数
6c b3
0110 1100 1011 0011
转换为utf-8编码
我们知道,对于一个三个字节的utf-8码(1110 xxxx 10xx xxxx 10xx xxxx),第一个字节剩余四个bit,第二个和第三个字节剩余六个bit,所以最终转换的utf-8码为:
1110 0110 10110010 10110011
验证
我们通过java代码工具做一个验证。
@Test
public void utf8test(){
String msg = "河";
byte[] bytes = msg.getBytes(StandardCharsets.UTF_8);
System.out.println(Arrays.toString(bytes));
}
//[-26, -78, -77]
// -26:1110 0110
// -78:1011 0010
// -77:1011 0011
我们以“h”为例,进行说明。h虽然属于ascii码定义范畴,但是他在Unicode也定义了相应的编码。这个案例我们采用反向的方式进行,也就是解码。
查找h对应的Unicode码,04BB。
将04bb转换为二进制为:100 1011 1011
转换为utf-8的编码:
11010010 10101011
将两个二进制字节转换为有符号整数,为[-46,-69]
执行程序,验证输出
@Test
public void utf8(){
byte[] bytes = {-46,-69};
String s = new String(bytes, StandardCharsets.UTF_8);
System.out.println(s);
}
utf-8具有严格的编码规范,如果不按照这个规则来编码回事什么情况,我们就做一个实验。
我们就以11101001 10101001举例。我们知道,以1110开头的utf-8需要三个字节。而此时提供了两个字节,看看他输出什么结果?
@Test
public void validUtf8(){
byte b1 = (byte)Integer.parseInt("11101001", 2);
byte b2 = (byte)Integer.parseInt("10101001", 2);
byte[] bytes = {b1,b2};
String s = new String(bytes, StandardCharsets.UTF_8);
System.out.println(s);
System.out.println(Arrays.toString(s.getBytes()));
}
//�
//[-17, -65, -67]
在输出结果中,我们发下出现一个乱码一个数组[-17, -65, -67],这个数组就是针对不合法的UTF-8的标识。
需要注意的是,这里只有一个字符。抓换为二进制为:11101111 10111111 10111101
我们再以11101001 11101001举例:
@Test
public void validUtf8(){
byte b1 = (byte)Integer.parseInt("11101001", 2);
byte b2 = (byte)Integer.parseInt("11101001", 2);
byte[] bytes = {b1,b2};
String s = new String(bytes, StandardCharsets.UTF_8);
System.out.println(s);
System.out.println(Arrays.toString(s.getBytes()));
}
//��
//[-17, -65, -67, -17, -65, -67]
@Test
public void validUtf8(){
byte b1 = (byte)Integer.parseInt("10101001", 2);
byte b2 = (byte)Integer.parseInt("11101001", 2);
byte[] bytes = {b1,b2};
String s = new String(bytes, StandardCharsets.UTF_8);
System.out.println(s);
System.out.println(Arrays.toString(s.getBytes()));
}
//��
//[-17, -65, -67, -17, -65, -67]
结论:
- 如果以110或者1110等开头的话,他会吸收后面以10开头的字节,作为一个错误编码
- 如果以10开头,则按照一个错误编码。
GBK编码,是对GB2312编码的扩展,因此完全兼容GB2312-80标准。GBK编码依然采用双字节编码方案,其编码范围:8140-FEFE,剔除xx7F码位,共23940个码位。共收录汉字和图形符号21886个,其中汉字(包括部首和构件)21003个,图形符号883个。GBK编码支持国际标准ISO/IEC10646-1和国家标准GB13000-1中的全部中日韩汉字,并包含了BIG5编码中的所有汉字。GBK编码方案于1995年12月15日正式发布,这一版的GBK规范为1.0版。
当使用GB2312编码标准时,给定一串字符编码,按照字节进行检测,首先检测每个字节的大小,如果字节值小于0x7F(字节首位为0),就用ASCII标准解码,如果遇到一个大于0x7F(字节首位为1)的字节,就把该字节和它后面一个字节连在一起用GBK标准进行解码,然后从第三个字节开始继续遍历检测。
我们还以"河"为例,说明他的编码方式:
1011 1010 1101 0011我们在回到刚开始的问题,主要是按照另外一种情况解码,那么长度必定大于等于当前字符串长度。
我们知道GBK编码后是其长度的二倍。按照正常情况下,utf-8的长度应该是len(gbk) * 2 / 3,这样看来长度比GBK短。但是前提合法。UTF-8有非常严格的编码规则,很多情况下,就会出现每个字符生成一个错误字符,这样看来长度应该是GBK的二倍。
这个很好理解,UTF-8字节长度是其3倍,所以如果正常情况下,GBK的长度应该是UTF8 * 3 / 2,正常情况下都比utf8长。
当然,如果全部是英文,上述方法是不能判断编码格式的。