• gbk与utf8自动识别


    1. 背景

    在通过geotools读取shp文件到postgis数据库的时候,首先要名称当前shp文件的编码格式。如果编码格式没有搞清楚,那么导入数据库的记录就会是乱码。对于shp文件编码格式的定义,在他的文件集中有个以cpg结尾的文件,这个文件记录了shp文件的编码格式。然而,不幸的是不是所有的shp文件集包含这个文件。此时,我们就需要通过另外一种途径判断字符串的编码格式。

    2. 上代码

    我们先把解决方式展现出来,如果只仅仅为了解决问题,那直接拷贝走就可以,但是如果想一探究竟的话,可以继续往下看。需要注意的是,这个方法在概率上并不是完全正确,因为可能出现极端情况而影响结果的准确性。

    /**
      * 判断字符串的编码格式
      * @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";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    大体思路就是对一个字符串分别用utf-8与GBK的方式读取,如果哪个字符短,那就属于哪个编码格式。那么下面我们就论证一下这个说法。

    3. UTF-8编码

    3.1 UTF-8编码概述

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    通过上图我们可以知道,0xxxxxxx是占用一个字节,那么他最大代表0~127(000000000 ~ 011111111)。这个也就是ASCII编码区,即utf-8完全兼容ASCII编码。我们知道,一个汉子的3个字节,所以对应1110xxxx 10xxxxxx 10xxxxxx

    3.2 UTF-8编码规则

    通过上面分析,我们知道汉子的编码为3个字节。有效字节为画x的部分,也就是两个字节(4 + 6 + 6)的长度。

    3.3 UTF-8编码举例

    3.3.1 三个字节举例

    我们以“河”为例,进行说明。

    • 确认Unicode编码

      通过查询Unicode对照表,可以知道河的Unicode码为:6cb3,网上转换工具一大片,大家可以自行查找。

    • 转换为二进制数

      6c b3
      0110 1100 1011 0011
      
      • 1
      • 2
    • 转换为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
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

    3.3.2 两个字节举例

    我们以“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);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

    3.3.3 非法UTF-8举例

    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]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在输出结果中,我们发下出现一个乱码一个数组[-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]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    @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]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    结论:

    • 如果以110或者1110等开头的话,他会吸收后面以10开头的字节,作为一个错误编码
    • 如果以10开头,则按照一个错误编码。

    4. GBK编码

    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版。

    4.1 GBK编码规则

    当使用GB2312编码标准时,给定一串字符编码,按照字节进行检测,首先检测每个字节的大小,如果字节值小于0x7F(字节首位为0),就用ASCII标准解码,如果遇到一个大于0x7F(字节首位为1)的字节,就把该字节和它后面一个字节连在一起用GBK标准进行解码,然后从第三个字节开始继续遍历检测。

    4.2 举例

    我们还以"河"为例,说明他的编码方式:

    5. 检测说明

    我们在回到刚开始的问题,主要是按照另外一种情况解码,那么长度必定大于等于当前字符串长度。

    5.1 GBK以UTF-8解码

    我们知道GBK编码后是其长度的二倍。按照正常情况下,utf-8的长度应该是len(gbk) * 2 / 3,这样看来长度比GBK短。但是前提合法。UTF-8有非常严格的编码规则,很多情况下,就会出现每个字符生成一个错误字符,这样看来长度应该是GBK的二倍。

    5.2 UTF-8以GBK解码

    这个很好理解,UTF-8字节长度是其3倍,所以如果正常情况下,GBK的长度应该是UTF8 * 3 / 2,正常情况下都比utf8长。

    当然,如果全部是英文,上述方法是不能判断编码格式的。

  • 相关阅读:
    「运维有小邓」EventLog Analyzer实时告警通知
    windows版:TensorRT安装教程
    司空见惯 - 体彩中奖交多少税
    如何修改模型颜色
    GitLab 502问题解决方案
    Element封装Table和Pagination
    宁夏果蔬系统
    shell 多线程
    基于SpringBoot的在线拍卖系统
    Vue.js核心技术解析与uni-app跨平台实战开发学习笔记 第4章 Vue.js动画 4.1 Vue单组动画
  • 原文地址:https://blog.csdn.net/oYinHeZhiGuang/article/details/125900211