UTF-8编码规则,及对包含汉字的字符串截取指定字节数
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十六进制编码一定是以
C
或D
开头的 - 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" 转载请保留原文链接及作者。