本文参考自深入分析 Java 中的中文编码问题

几种常见的编码格式(科普知识)

  • ASCII码 学过计算机的人都知道 ASCII 码,总共有 128 个,用一个字节的低 7 位表示,0~31 是控制字符如换行回车删除等;32~126 是打印字符,可以通过键盘输入并且能够显示出来。
  • ISO-8859-1 128 个字符显然是不够用的,于是 ISO 组织在 ASCII 码基础上又制定了一些列标准用来扩展 ASCII 编码,它们是 ISO-8859-1~ISO-8859-15,其中 ISO-8859-1 涵盖了大多数西欧语言字符,所有应用的最广泛。ISO-8859-1 仍然是单字节编码,它总共能表示 256 个字符。
  • GB2312 它的全称是《信息交换用汉字编码字符集 基本集》,它是双字节编码,总的编码范围是 A1-F7,其中从 A1-A9 是符号区,总共包含 682 个符号,从 B0-F7 是汉字区,包含 6763 个汉字。
  • GBK 全称叫《汉字内码扩展规范》,是国家技术监督局为 windows95 所制定的新的汉字内码规范,它的出现是为了扩展 GB2312,加入更多的汉字,它的编码范围是 8140~FEFE(去掉 XX7F)总共有 23940 个码位,它能表示 21003 个汉字,它的编码是和 GB2312 兼容的,也就是说用 GB2312 编码的汉字可以用 GBK 来解码,并且不会有乱码。
  • UTF-8 UTF-16统一采用两个字节表示一个字符,虽然在表示上非常简单方便,但是也有其缺点,有很大一部分字符用一个字节就可以表示的现在要两个字节表示,存储空间放大了一倍,在现在的网络带宽还非常有限的今天,这样会增大网络传输的流量,而且也没必要。而 UTF-8 采用了一种变长技术,每个编码区域有不同的字码长度。不同类型的字符可以是由 1~6 个字节组成。

    java需要编码的场景

    I/O 操作中存在的编码

    我们知道涉及到编码的地方一般都在字符到字节或者字节到字符的转换上,而需要这种转换的场景主要是在 I/O 的时候,这个 I/O 包括磁盘 I/O 和网络 I/O,关于网络 I/O 部分在后面将主要以 Web 应用为例介绍。下图是 Java 中处理 I/O 问题的接口: InputStream Reader 类是 Java 的 I/O 中读字符的父类,而 InputStream 类是读字节的父类,InputStreamReader 类就是关联字节到字符的桥梁,它负责在 I/O 过程中处理读取字节到字符的转换,而具体字节到字符的解码实现它由 StreamDecoder 去实现,在 StreamDecoder 解码过程中必须由用户指定 Charset 编码格式。值得注意的是如果你没有指定 Charset,将使用本地环境中的默认字符集,例如在中文环境中将使用 GBK 编码。 写的情况也是类似,字符的父类是 Writer,字节的父类是 OutputStream,通过 OutputStreamWriter 转换字符到字节。如下图所示: OutputStreamWriter

如下面一段代码, 实现了文件的读写功能

String file = "c:stream.txt";
String charset="utf-8";
FileOutputStream outputStream = new FileOutputStream(file);
OutputStreamWriter writer = new OutputStreamWriter(outputStream,charset);
try{
writer.write("中文字符");
	}finaly{
	writer.close();
}
//字节>字符
FileInputStream inputStream = new FileInputStream(file);
InputStreamReader reader = new InputStreamReader(inputStream,charset);
StringBuffer sb = new StringBuffer();
char[] buf = new char[64];
int count = 0;
try{
	while((count=reader.read(buf))!=-1){
		sb.append(buf,0,count);
	}
}finaly{
	reader.close();
}

内存操作中的编码

在 Java 开发中除了 I/O 涉及到编码外,最常用的应该就是在内存中进行字符到字节的数据类型的转换,Java 中用 String 表示字符串,所以 String 类就提供转换到字节的方法,也支持将字节转换为字符串的构造函数。如下代码示例:

String s = "这是一段中文字符串"; 
byte[] b = s.getBytes("UTF-8"); 
String n = new String(b,"UTF-8");

Charset 提供 encode 与 decode 分别对应 char[] 到 byte[] 的编码和 byte[] 到 char[] 的解码。如下代码所示:

Charset charset = Charset.forName("UTF-8"); 
ByteBuffer byteBuffer = charset.encode(string); 
CharBuffer charBuffer = charset.decode(byteBuffer);

String 编码

public static void encode() { 
       String name = "I am 君山"; 
       toHex(name.toCharArray()); 
       try { 
           byte[] iso8859 = name.getBytes("ISO-8859-1"); 
           toHex(iso8859); 
           byte[] gb2312 = name.getBytes("GB2312"); 
           toHex(gb2312); 
           byte[] gbk = name.getBytes("GBK"); 
           toHex(gbk); 
           byte[] utf16 = name.getBytes("UTF-16"); 
           toHex(utf16); 
           byte[] utf8 = name.getBytes("UTF-8"); 
           toHex(utf8); 
       } catch (UnsupportedEncodingException e) { 
           e.printStackTrace(); 
       } 
}

我们把 name 字符串按照前面说的几种编码格式进行编码转化成 byte 数组,然后以 16 进制输出,我们先看一下 Java 是如何进行编码的。 下面是 Java 中编码需要用到的类图 图 1. Java 编码类图 Java 编码类图