UTF8编码的原理及白名单过滤utf8mb4(Caused by: java.sql.BatchUpdateException: Incorrect string value)

2022-05-12 09:22:17 浏览数 (1)

这几天遇到Mysql数据落库报编码错误:

代码语言:javascript复制
Caused by: java.sql.BatchUpdateException: Incorrect string value: 'xF0x9Fx98x8A',...' for column 'statement_text' at row 1

Caused by: java.sql.BatchUpdateException: Incorrect string value: 'xF0xA0x81x81%'...' for column 'statement_sample' at row 1

网上提供了大部分的解决方法是修改数据库配置,但是数据库如果使用连接池,无法保证其他连接时不指定utf-8,所以避免不了其他连接污染连接池。这里给出另一种解决方法,过滤掉特殊字符。

1 UTF-8编码分段

UTF-8(8-bit Unicode Transformation Format)是一种针对Unicode的可变长度字符编码,也是一种前缀码。它可以用来表示Unicode标准中的任何字符,且其编码中的第一个字节仍与ASCII兼容,这使得原来处理ASCII字符的软件无须或只须做少部分修改,即可继续使用。因此,它逐渐成为电子邮件、网页及其他存储或发送文字优先采用的编码。

1.0 符号查询方法

http://www.fileformat.info/info/unicode/char/xxxxx/index.htm

替换xxxx为需要查询的字符16进制编码

例如emoji的SMILING FACE的编码为1f60a:查询地址 字符a的编码为61:查询地址

1.1 Ascii

128个US-ASCII字符只需一个字节编码(Unicode范围由U 0000至U 007F)

例如

十六进制(JAVA)

图形

“u0060”

`

“u0061”

a

“u0062”

b

“u0063”

c

“u0064”

d

“u0065”

e

1.2 拉丁文等

带有附加符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文及它拿字母则需要两个字节编码(Unicode范围由U 0080至U 07FF)。

十六进制(JAVA)

图形

查询连接

“u0550”

Ր

link

“u0450”

ѐ

link

1.3 中文等

其他基本多文种平面(BMP)中的字符(这包含了大部分常用字,如大部分的汉字)使用三个字节编码(Unicode范围由U 0800至U FFFF)。

十六进制(JAVA)

图形

查询连接

“u9AD8”

link

“u738B”

link

1.4 其他

其他极少使用的Unicode 辅助平面的字符使用四至六字节编码(Unicode范围由U 10000至U 1FFFFF使用四字节,Unicode范围由U 200000至U 3FFFFFF使用五字节,Unicode范围由U 4000000至U 7FFFFFFF使用六字节)。

十六进制(JAVA)

图形

查询连接

“uD83DuDE0A”

?

link

“uD83DuDE0F”

?

link

2 UTF-8编码字节含义

  • 对于UTF-8编码中的任意字节B,如果B的第一位为0,则B独立的表示一个字符(ASCII码);
  • 如果B的第一位为1,第二位为0,则B为一个多字节字符中的一个字节(非ASCII字符);
  • 如果B的前两位为1,第三位为0,则B为两个字节表示的字符中的第一个字节;
  • 如果B的前三位为1,第四位为0,则B为三个字节表示的字符中的第一个字节;
  • 如果B的前四位为1,第五位为0,则B为四个字节表示的字符中的第一个字节;

因此,对UTF-8编码中的任意字节,根据第一位,可判断是否为ASCII字符;根据前二位,可判断该字节是否为一个字符编码的第一个字节;根据前四位(如果前两位均为1),可确定该字节为字符编码的第一个字节,并且可判断对应的字符由几个字节表示;根据前五位(如果前四位为1),可判断编码是否有错误或数据传输过程中是否有错误。

码点的位数

码点起值

码点终值

字节序列

Byte 1

Byte 2

Byte 3

Byte 4

Byte 5

Byte 6

7

U 0000

U 007F

1

0xxxxxxx

11

U 0080

U 07FF

2

110xxxxx

10xxxxxx

16

U 0800

U FFFF

3

1110xxxx

10xxxxxx

10xxxxxx

21

U 10000

U 1FFFFF

4

11110xxx

10xxxxxx

10xxxxxx

10xxxxxx

26

U 200000

U 3FFFFFF

5

111110xx

10xxxxxx

10xxxxxx

10xxxxxx

10xxxxxx

31

U 4000000

U 7FFFFFFF

6

1111110x

10xxxxxx

10xxxxxx

10xxxxxx

10xxxxxx

10xxxxxx

  • 在ASCII码的范围,用一个字节表示,超出ASCII码的范围就用字节表示,这就形成了我们上面看到的UTF-8的表示方法,好处是当UNICODE文件中只有ASCII码时,存储的文件都为一个字节,所以就是普通的ASCII文件无异,读取的时候也是如此,所以能与以前的ASCII文件兼容。
  • 大于ASCII码的,就会由上面的第一字节的前几位表示该unicode字符的长度,比如110xxxxx前三位的二进制表示告诉我们这是个2BYTE的UNICODE字符;1110xxxx是个三位的UNICODE字符,依此类推;xxx的位置由字符编码数的二进制表示的位填入。越靠右的x具有越少的特殊意义。只用最短的那个足够表达一个字符编码数的多字节串。注意在多字节串中,第一个字节的开头"1"的数目就是整个串中字节的数目。

3 Java过滤4字长UTF-8编码字符(保留3字长字符)

  • 如上述1.1,1.2,1.3中提到,三字长编码保存了大部分常规字符,使用白名单保留这部份字符可以满足一般业务需求,过滤掉特殊字符串(解决MYSQL特殊字符无法插入的问题)。
  • 4字长的UTF-8字符就是Unicode SMP(辅助平面)中的字符, 也就是Unicode编码大于U FFFF的字符, 所以我们只需要获取字符串中各个字符的code point,当code point 大于FFFF时(或者直接使用Character.isSupplementaryCodePoint来判断),过滤掉即可。

示例代码如下:

代码语言:javascript复制
    @Test
    public void filterUtf8mb4Test() {
        String s = "a中uD83DuDD11a中";
        log.info(filterUtf8mb4(s));
    }

    public static String filterUtf8mb4(String str) {
        final int LAST_BMP = 0xFFFF;
        StringBuilder sb = new StringBuilder(str.length());
        for (int i = 0; i < str.length(); i  ) {
            int codePoint = str.codePointAt(i);
            if (codePoint < LAST_BMP) {
                sb.appendCodePoint(codePoint);
            } else {
                i  ;
            }
        }
        return sb.toString();
    }

输出结果为:

代码语言:javascript复制
a中a中

4 一些笔记

不想重启方案

执行之前在当前会话执行下面二选一

代码语言:javascript复制
set character_set_client = utf8mb4;

SET NAMES utf8mb4;

服务端修改方案记录

(要改全,改完了重启)

代码语言:javascript复制
[client] 
default-character-set = utf8mb4
[mysqld]
character-set-server = utf8mb4 
collation-server = utf8mb4_unicode_ci
[mysql] 
default-character-set = utf8mb4

参考官方文档 mysql域含义 参数 字符集

SQL修改字符集

代码语言:javascript复制
ALTER DATABASE database_name CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
ALTER TABLE table_name CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE table_name CHANGE column_name column_name VARCHAR(length) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

5 引用

字符集资料出处 https://zh.wikipedia.org/wiki/UTF-8 http://www.fileformat.info https://www.cnblogs.com/chrischennx/p/6623610.html 修改数据库的方法请参考 https://blog.csdn.net/hzw19920329/article/details/55670782

0 人点赞