0.常识
- Java的内存编码是Unicode,UTF-16
- Linux 默认ISO-8859-1
- Win32 默认GB2312
- 字符集charset:多个字符的集合。例如GB2312是中国国家标准的简体中文字符集。
- 字符编码(Character encoding):把字符集中的字符编码(映射)为特定的字节或字节序列的规则,以便文本在存储和在网络中传输。
0.1 常用字符集&字符编码
字符集 | 字符编码 | 描述 |
ASCII | ASCII | 0x00-0x7F, 单字节/一个字符 |
ISO-8859-1 | ISO-8859-1 | 0x00-0xFF扩展,单字节 |
GB2312 | GB2312 | 中文简体,双字节,兼容 |
GBK | GBK | 中文简繁,双字节 0x8140~0xFEFE,除xx7F |
Unicode | UTF-16 | 双字节,0000~FFFF |
Unicode | UTF-8 | 变长设计 |
Unicode (UTF-16) | UTF-8 | 字节 |
0000 - 007F | 0xxxxxxx | 1 |
0080 - 07FF | 110xxxxx 10xxxxxx | 2 |
0800 - FFFF | 1110xxxx 110xxxxx 10xxxxxx | 3 |
ASCII码为单字节,用7位二进制数表示,最高位为0,即00000000-01111111或0x00-0x7F。码表如下
Unicode对世界上大部分的文字系统进行了整理、编码,使得电脑可以用更为简单的方式来呈现和处理文字。
GB系列这一类的中文是双字节,单字节也支持(英文兼容)GBK码表
1.常见问题
1.1 中文乱码
此处讲的是Java编程过程中遇到的问题,由于Java的内存字符集是Unicode编码,0x4e00 - 0x9fa5范围对应的是中文,在0800 - FFFF第三个范围内,所以中文在
I/O,包含文件读写、数据库读写、网络IO等等,所有的C/S、B/S架构间的通信数据如果包含中文,当客户端和服务端的编解码字符集选用不统一的话都会导致乱码。
1.1.1 文件IO
用文件做byte[]字节流的中转,有重载方法指定字符编码或者使用系统编码,系统编码可以使用参数-D
void fileWriteAndRead(String wcharset, String rcharset) throws Exception {
String filename = "fileCharset.txt";
File file = new File(filename);
String content = "English 中文";
FileOutputStream out = new FileOutputStream(file);
OutputStreamWriter writer = new OutputStreamWriter(out, wcharset); //指定字符集
writer.write(content);
writer.close();
// 读取同一个文件,换字符集尝试各种效果
FileInputStream in = new FileInputStream(file);
InputStreamReader reader = new InputStreamReader(in, rcharset);
char[] buf = new char[64];
StringBuilder sb = new StringBuilder();
int readCount = 0;
while ((readCount = reader.read(buf)) > 0) {
sb.append(buf, 0, readCount);
}
reader.close();
System.out.println("write-charset:"+wcharset+"\t"+content+"\t"+"read-charset:"+rcharset+"\t"+sb.toString());
}
@Test
public void diffCharsets() throws Exception{
String[] charsets = {"ISO-8859-1","GBK","UTF-8"};
for (String wchar : charsets) {
for (String rchar : charsets) {
fileWriteAndRead(wchar, rchar);
}
}
}
由于写入使用的字符集和读取使用的字符集不一致,如从一个gbk编码的文本文件中用utf-8编码解析读取到的字节流会出现很多?
做个实验,使用左侧的字符集将String content = "English 中文";这个字符串写入文本,然后用表头的字符集解析,会出先怎样的乱码?
写入encode\读取decode | ISO-8859-1 | GBK | UTF-8 |
ISO-8859-1 单 | English ?? | English ?? | English ?? |
GBK 双 | English ÖÐÎÄ | English 中文 | English ���� |
UTF-8 变(1&3) | English ä¸æ | English 涓枃 | English 中文 |
GBK转换成UTF-8再转成GBK:锟斤拷锟斤拷锟斤拷锟叫癸拷锟斤拷
有表格可得这些字符集都是兼容ASCII字符集的 英文不会有乱码的问题,而中文如果编解码使用的字符编码不一样的话会导致乱码。
上述例子中,主要通过StreamEncoder进行
如果不指定String charset,会使用默认的编码,取用-D参数file.encoding指定的字符编码,如果lookup没有找到会使用UTF-8。
public static Charset defaultCharset() {
if (defaultCharset == null) {
synchronized (Charset.class) {
String csn = AccessController.doPrivileged(
new GetPropertyAction("file.encoding"));
Charset cs = lookup(csn);
if (cs != null)
defaultCharset = cs;
else
defaultCharset = forName("UTF-8");
}
}
return defaultCharset;
}
1.1.1 数据库读写
很多人有过类似经历,用mysql客户端连接上mysql服务,执行sql语句,无论是查询还是插入修改,如果sql语句中包含中文,极有可能出现乱码:查询出的数据,插入到表中的字段......
造成乱码的原因就是你ddl创建表结构的时候声明的字符集和你当前的客户端字符集、连接字符集不同,
解决方式
mysql>
set character_set_client=gbk;
set character_set_connection = gbk;
set character_set_results=gbk;
Java
jdbc_url=jdbc:mysql://ip:port/db_name?useUnicode=true&characterEncoding=gbk
1.1.2 网络IO
如http通信,最常见的是浏览器页面出现乱码,一般通过设置浏览器编码