UTF-8编码规则,及对包含汉字的字符串截取指定字节数

  1. 面试题之——对包含汉字的字符串截取指定字节数
    1. 第一种方法
    2. 第二种方法
    3. 第三种方法
  2. 参考

UTF-8是一种变长字节编码方式。对于某一个字符的UTF-8编码,如果只有一个字节则其最高二进制位为0;如果是多字节,其第一个字节从最高位开始,连续的二进制位值为1的个数决定了其编码的位数,其余各字节均以10开头。UTF-8最多可用到6个字节。
如表:

1字节 0xxxxxxx
2字节 110xxxxx 10xxxxxx
3字节 1110xxxx 10xxxxxx 10xxxxxx
4字节 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

UTF-8使用一至六个字节为每个字符编码(尽管如此,2003年11月UTF-8被RFC 3629重新规范,只能使用原来Unicode定义的区域,U+0000到U+10FFFF,也就是说最多四个字节

为了更好的理解后面的实际应用,我们这里简单的介绍下UTF-8的编码实现方法。即UTF-8的物理存储和Unicode序号的转换关系。
UTF-8编码为变长编码。最小编码单位(code unit)为一个字节。一个字节的前1-3个bit为描述性部分,后面为实际序号部分。

  • 如果一个字节的第一位为0,那么代表当前字符为单字节字符,占用一个字节的空间。0之后的所有部分(7个bit)代表在Unicode中的序号。
  • 如果一个字节以110开头,那么代表当前字符为双字节字符,占用2个字节的空间。110之后的所有部分(7个bit)代表在Unicode中的序号。且第二个字节以10开头
  • 如果一个字节以1110开头,那么代表当前字符为三字节字符,占用3个字节的空间。110之后的所有部分(7个bit)代表在Unicode中的序号。且第二、第三个字节以10开头
  • 如果一个字节以10开头,那么代表当前字节为多字节字符的第二个字节。10之后的所有部分(6个bit)代表在Unicode中的序号。

具体每个字节的特征可见上方,其中x代表序号部分,把各个字节中的所有x部分拼接在一起就组成了在Unicode字库中的序号

我们分别看三个从一个字节到三个字节的UTF-8编码例子:

实际字符 在Unicode字库序号的十六进制 在Unicode字库序号的二进制 UTF-8编码后的二进制 UTF-8编码后的十六进制
$ 0024 010 0100 0010 0100 24
¢ 00A2 000 1010 0010 1100 0010 1010 0010 C2 A2
20AC 0010 0000 1010 1100 1110 0010 1000 0010 1010 1100 E2 82 AC

得出以下规律:

  • 4个字节以上的的UTF-8十六进制编码一定是以F开头的
  • 3个字节的UTF-8十六进制编码一定是以E开头的
  • 2个字节的UTF-8十六进制编码一定是以CD开头的
  • 1个字节的UTF-8十六进制编码一定是以比8小的数字开头的

面试题之——对包含汉字的字符串截取指定字节数

题目:编写一个截取字符串的函数,输入为一个字符串和字节数, 输出为按字节截取的字符串,但要保证汉字不被截取半个,如"我ABC",4,应该截取"我AB",输入"我ABC汉DEF",6, 应该输出"我ABC",而不是"我ABC+汉的半个"。
我们都知道在计算机中,存储一个汉字需要至少两个字节。例如:gbk和gb2312都是用两个字节存储一个汉字,而UTF-8是用三个字节存储一个汉字。

第一种方法

适用字符集:UTF-8,gb2312,gbk
思路:依次截取字符串的每个字符,根据字符编码获取其字节数temp_len,total用以记录每次截取字符后及之前截取字符字节数之和,然后判断total是否小于等于所需要截取字节长度(length),如果小于说明还没超过所需要截取字节长度,那么截取字符的长度(len)+1,如果total>length说明已经超出所需要截取的字节长度,此时的len就是所需要截取原字符串的长度

/**
 * @param input  需要截取的字符串
 * @param length 需要截取的字节数
 * @param encoding 字符的编码格式
 * @return 截取后的字符串
 * @throws UnsupportedEncodingException
 */
public static String subString(String input, int length, String encoding)
        throws UnsupportedEncodingException {
    byte[] buf = input.getBytes(encoding);
    int characterNum = input.length();
    System.out.println("字符编码:" +  encoding +",字符串的字符个数:"
        + characterNum + ", 字节长度为:" + buf.length);
    //截取到当前字符时的字节数
    int total = 0;
    //应当截取到的字符的长度
    int len = 0;
    for(int i=0; i<characterNum; i++) {
        String temp = input.substring(i, i+1);
        int temp_len = temp.getBytes(encoding).length;
        total += temp_len;
        if(total<=length){
            len++;
        }
    }
    return input.substring(0, len);
}

第二种方法

适用字符集:gb2312,gbk
gbk汉字编码是两个字节为一个字符,高字节是小于0的(原因我也不清楚),设置一标志变量bChineseFirstHalf为false,如果获取到的字节小于0且bChineseFirstHalf为false,说明这是汉字的前半个字节,则bChineseFirstHalf设置为true,否则,num(最终需要截取字符串长度)加1。一直循环到字节数i等于需要截取的字节数length

public static String subStr(String input, int length, String encoding)
        throws UnsupportedEncodingException {
    int num = 0;
    byte[] buf = input.getBytes(encoding);
    System.out.println("字符编码:" +  encoding +",字符串的字符个数:"
        + input.length() + ", 字节长度为:" + buf.length);
    boolean bChineseFirstHalf = false;
    for (int i = 0; i < length; i++) {
        if (buf[i] < 0 && !bChineseFirstHalf) {
            bChineseFirstHalf = true;
        } else {
            num++;
            bChineseFirstHalf = false;
        }
    }
    return input.substring(0, num);
}

第三种方法

适用字符集:utf-8
第三种方法想到了,使用上一篇文章中的 UTF8Utils 类。

public static String subString1(String input, int length, String encoding)
        throws UnsupportedEncodingException {
    byte[] buf = input.getBytes(encoding);
    String hex = UTF8Utils.bytesToHex(buf);
    String firstChar = null;
    //应当截取到的字符的长度
    int len = 0;
    wai : for(int i=0; i<length; ) {
        firstChar = hex.substring(0, 1);
        for (int j = 0;j<UTF8Utils.byteMap.get(firstChar);j++){
            i++;
            if(i>length){
                break wai;
            }
        }
        hex = hex.substring(UTF8Utils.hexMap.get(firstChar), hex.length());
        len++;
    }
    return input.substring(0, len);
}

参考

十分钟搞清字符集和字符编码
面试题之——对包含汉字的字符串截取指定字节数


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 bin07280@qq.com

文章标题:UTF-8编码规则,及对包含汉字的字符串截取指定字节数

文章字数:1.6k

本文作者:Bin

发布时间:2016-05-27, 14:26:33

最后更新:2019-08-06, 00:58:17

原始链接:http://coolview.github.io/2016/05/27/UTF-8%E7%BC%96%E7%A0%81%E8%A7%84%E5%88%99/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录