MySQL之特殊字符(表情)的存储以及读取乱码问题
特殊字符的乱码问题是程序开发以及软件应用中很常见的问题,根据不同场景也有不同的原因。本篇仅记录特定情境下遇到的问题与解决方法,MySQL存储带有表情等特殊字符的不规则数据的应用场景。
1. 从讲故事开始
1.1 字与字节与字符
- 字节(Byte)是计算机存储容量基本单位,8个二进制位组成1个字节。
- 字是计算机一次运算能处理的位数,如32位机器中字=4字节
- 字符是一种符号,肉眼可见的汉字,字母等
字符与字节的关系:一般情况下,一个标准英文字母或数字占一个字节位置,一个标准汉字占二个字节位置表示,在不同的编码方式下一个字符占的字节不太一样。
1.2 不同编码集
- ASCII(ISO-8859-1)是鼻祖,最简单的方式
- B2312、GBK、GB18030,GBK系列(递增向下兼容)是双字节中文编码方式,包含全部中文字符。不论中、英文字符均使用双字节来表示
- UTF-8编码则是用以解决国际上字符的一种多字节编码,它对英文使用8位(即一个字节),中文使用24位(三个字节)来编码
- Unicode是统一编码,它建立了一个全世界统一的码表。世界上的所有文字,在这张码表中都是唯一的。UTF-8, UTF-16, UTF-32 就是 Unicode 不同的实现。
2. 特殊字符(表情)的存储
Emoji 是一种特殊的 Unicode 编码,常见于 ios 和 android 手机上.utf8编码最大字符长度为 3 字节,无法解析Emoji。
MySQL在5.5.3之后增加了这个utf8mb4的编码,专门用来兼容四字节的unicode
- 首先存储建表时,要采用字段级或者表级utf8mb4编码格式。
CHARSET=utf8mb4
- 含有Emoji的字段存储为BLOB大字段类型
utf8mb4解决了某些Emoji无法解析(一个字符占四个字节)的问题,还有一些特殊Emoji字符其实是两个字符(下图),要使用BLOB才能存储,尝试过longtext类型也无法存储
3. 特殊字符(表情)的读取
由于字段类型是BLOB,对应的Java实体类类型应该为byte[],若依旧采用String类型,出现了Emoji乱码的问题,由于持久化框架采用mybatis框架。则解决思路是在mybatis里读取时,利用byte[]转化。
在mapper.xml中的字段里,配置处理的工具类
package xx.xx.xx.xx.util;
import java.io.ByteArrayInputStream;
import java.io.UnsupportedEncodingException;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
/**
* 自定义typehandler,解决mybatis存储blob字段后,出现乱码的问题
* 在mapper.xml中配置:
* 将result标签中的jdbcType属性改为typeHandler属性即可,如下
* <result typeHandler="xx.xx.xx.xx.util.ConvertBlobTypeHandler"/>
*/
public class ConvertBlobTypeHandler extends BaseTypeHandler<String> {
//###指定字符集
private static final String DEFAULT_CHARSET = "utf-8";
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
ByteArrayInputStream bis;
try {
//###把String转化成byte流
bis = new ByteArrayInputStream(parameter.getBytes(DEFAULT_CHARSET));
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Blob Encoding Error!");
}
ps.setBinaryStream(i, bis, parameter.length());
}
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
Blob blob = rs.getBlob(columnName);
byte[] returnValue = null;
if (null != blob) {
returnValue = blob.getBytes(1, (int) blob.length());
}
try {
//###把byte转化成string
return new String(returnValue, DEFAULT_CHARSET);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Blob Encoding Error!");
}
}
@Override
public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
Blob blob = cs.getBlob(columnIndex);
byte[] returnValue = null;
if (null != blob) {
returnValue = blob.getBytes(1, (int) blob.length());
}
try {
return new String(returnValue, DEFAULT_CHARSET);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Blob Encoding Error!");
}
}
@Override
public String getNullableResult(ResultSet arg0, int arg1) throws SQLException {
return null;
}
}