前言
工作时有一个需求需要拉取Amazon生成的报告,一开始拉取看似是正常的,查看日志发现有一些文件拉取成功但是插入数据库失败,报错:Incorrect string value
网上搜出来的解决方法都是将 utf8 变为utf8mb4 原因是utf8字符集只占三个字节,而utf8mb4 占四个字节,说白了就是字节导致的无法正常表示字符。
但是设计数据库之初就使用了 utf8mb4 所以肯定不是这个原因,查了一圈也没找到个办法。最后把那个报错的字符找出来发现根本不在ascii码表里,进一步发现这个字符的编码方式并不是平时常用的编码方式,但是可以知道这个字符的一个单字节的字符,如果想让他正常显示和插入数据库,那么我们只需要按照utf8 的标准强行转为符合规范的 双字节即可。
UTF8 规定
UTF 8规定当字符占的位数为n,n > 1时,高位的字节前n位都为1,第n+1位为0,然后后面的字节的前两位都是10.然后剩余的位用Unicode值来填充,高位补0。格式就是下面这样:
一个字节: 0xxxxxxx
两个字节: 110xxxxx 10xxxxxx
三个字节: 1110xxxx 10xxxxxx 10xxxxxx
四个字节: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
单字节有8位,那么我们只需要构造一个 110 xxxxx 10 xxxxxx ,将八位从低到高填充即可。
func asciiFmtUtf8(c uint8) []byte {
return []byte{0xC0 | (c >> 6), 0x80 | (c & 0x3f)}
}
注意: 这里只能转化128 ~ 255 的字符集单字节变为双字节,因为127 以内的只需要7位即可表示 转为 utf8 的话应该一个字节就可以表示了,不需要两个字节。
func repairInvalidUtf8(s string) string {
res := make([]byte, 0, len([]byte(s)))
for i, r := range s {
if r == 0xfffd {
bb := []byte(s[i : i+1])
if len(bb) == 0 {
continue
}
res = append(res, asciiFmtUtf8(uint8(bb[0]))...)
} else {
res = append(res, []byte(string(r))...)
}
}
return string(res)
}
转