根据以下链接中的内容总结而成
http://cenalulu.github.io/linux/character-encoding/
http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html
http://blog.itpub.net/55022/viewspace-713901
unicode http://www.chi2ko.com/tool/CJK.htm
http://ff.163.com/newflyff/gbk-list/
字符编码解码
字符集合(Character set):是一组形状的集合,例如所有汉字的集合,发明于公元前,发明者是仓颉。它体现了字符的“形状”,它与计算机、编码等无关。
编码字符集(Coded character set):是一组字符对应的编码(即数字),为字符集合中的每一个字符给予一个数字。例如最早的编码字符集ASCII,发明于1967年。再例如Java使用的unicode,发明于1994年(持续更新中)。由于编码字符集为每一个字符赋予一个数字,因此在java内部,字符可以认为就是一个16位的数字,因此以下方式都可以给字符赋值:
char c=‘中’
char c =0x4e2d
char c=20013
字符编码方案(Character-encoding schema):将字符编码(数字)映射到一个字节数组的方案,因为在磁盘里,所有信息都是以字节的方式存储的。因此Java的16位字符必须转换为一个字节数组才能够存储。例如UTF-8字符编码方案,它可以将一个字符转换为1、2、3或者4个字节。
一般认为,编码字符集和字符编码方案合起来被称之为字符集(Charset),这是一个术语,要和前面的字符集合(Character set)区分开。
即
字符集合(Character set):是一组形状的集合,一般存储于字库中。
编码字符集(Coded character set):是一组字符对应的编码(即数字),为字符集合中的每一个字符给予一个数字。
字符编码方案(Character-encoding schema):将字符编码(数字)映射到一个字节数组的方案。
字符集(Charset):是编码字符集和字符编码方案的组合。
UTF-8编码简介
为了更好的理解后面的实际应用,我们这里简单的介绍下UTF-8的编码实现方法。即UTF-8的物理存储和Unicode序号的转换关系。 UTF-8编码为变长编码。最小编码单位(code unit
)为一个字节。一个字节的前1-3个bit为描述性部分,后面为实际序号部分。
如果一个字节的第一位为0,那么代表当前字符为单字节字符,占用一个字节的空间。0之后的所有部分(7个bit)代表在Unicode中的序号。
如果一个字节以110开头,那么代表当前字符为双字节字符,占用2个字节的空间。110之后的所有部分(5个bit)加上后一个字节的除10外的部分(6个bit)代表在Unicode中的序号。且第二个字节以10开头
如果一个字节以1110开头,那么代表当前字符为三字节字符,占用3个字节的空间。110之后的所有部分(5个bit)加上后两个字节的除10外的部分(12个bit)代表在Unicode中的序号。且第二、第三个字节以10开头
如果一个字节以10开头,那么代表当前字节为多字节字符的第二个字节。10之后的所有部分(6个bit)和之前的部分一同组成在Unicode中的序号。
具体每个字节的特征可见下表,其中x
代表序号部分,把各个字节中的所有x
部分拼接在一起就组成了在Unicode字库中的序号
Byte 1 | Byte 2 | Byte3 |
---|---|---|
0xxx xxxx | ||
110x xxxx | 10xx xxxx | |
1110 xxxx | 10xx xxxx | 10xx xxxx |
我们分别看三个从一个字节到三个字节的UTF-8编码例子:
实际字符 | 在Unicode字库序号的十六进制 | 在Unicode字库序号的二进制 | UTF-8编码后的二进制 | UTF-8编码后的十六进制 |
---|---|---|---|---|
$ | 0024 | 010 0100 | 0010 0100 | 24 |
¢ | 00A2 | 000 1010 0010 | 1100 0010 1010 0010 | C2 A2 |
€ | 20AC | 0010 0000 1010 1100 | 1110 0010 1000 0010 1010 1100 | E2 82 AC |
从以上的简单介绍中得出以下规律:
3个字节的UTF-8十六进制编码一定是以
E
开头的2个字节的UTF-8十六进制编码一定是以
C
或D
开头的1个字节的UTF-8十六进制编码一定是以比
8
小的数字开头的
Unicode符号范围 (十六进制) | UTF-8编码方式(二进制) |
---|---|
0000 0000-0000 007F | 0xxxxxxx |
0000 0080-0000 07FF | 110xxxxx 10xxxxxx |
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
跟据上表,解读 UTF-8 编码非常简单。如果一个字节的第一位是0,则这个字节单独就是一个字符;如果第一位是1,则连续有多少个1,就表示当前字符占用多少个字节。UTF-8 就是在互联网上使用最广的一种 Unicode 的实现方式。其他实现方式还包括 UTF-16(字符用两个字节或四个字节表示)和 UTF-32(字符用四个字节表示),不过在互联网上基本不用。这里的关系是,UTF-8 是 Unicode 的实现方式之一。
Unicode的全称是“Universal Multiple-Octet Coded Character Set”,通用多字节编码字符集,简写为UCS。 Unicode规定了一组字符对应的编码。恰好这组字符就是全人类目前所有的字符。 UCS-2是指用两个字节对应一个字符的编码字符集;UCS-4则是指用四个字节对应一个字符的编码字符集。你可以认为,目前为止Unicode有两个具体的编码字符集,UCS-2和UCS-4。 Unicode是某种编码字符集(目前包括UCS-2和UCS-4两种),而UTF则是字符编码方案,就是将字符编码(数字)映射到一个字节数组的方案。UTF中的U是指Unicode,也就是将Unicode编码映射到字节数组的方案。目前UTF包括UTF-7、UTF-8、UTF-16和UTF-32,后面的数字代表转换时最小的位数。例如UTF-8就是用几个8位二进制数来代表一个Unicode编码。而UTF-16就是用几个16位二进制数来代表一个Unicode编码。
Big Endian和Little Endian是字节序字节序就是数据在内存中存放的顺序,多于一个字节的数据在内存中存放时有两种选择,即Big Endian和Little Endian。 Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。 Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。 Big Endian和Little Endian和芯片类型以及操作系统都有关系。但是由于Java是平台无关的,所以Java被设计为Big Endian的。但是当Java中的字符进行编码时,就要注意其字节序了。 例如UTF-16字符编码方案就分为UTF-16BE和UTF-16LE。Unicode 规范定义,每一个文件的最前面分别加入一个表示编码顺序的字符,这个字符的名字叫做"零宽度非换行空格"(zero width no-break space),用FEFF表示。这正好是两个字节,而且FF比FE大1。如果一个文本文件的头两个字节是FE FF,就表示该文件采用大头方式;如果头两个字节是FF FE,就表示该文件采用小头方式。
GBK
GBK是一个字符集GBK同时包含编码字符集和字符编码方案。GBK的中文编码是双字节来表示的,英文编码是用ASCII码表示的,用单字节表示。但GBK编码表中也有英文字符的双字节表示形式,所以英文字母可以有2中GBK表示方式。为区分中文,将其最高位都定成1。英文单字节最高位都为0。当用GBK解码时,若高字节最高位为0,则用ASCII码表解码;若高字节最高位为1,则用GBK编码表解码GBK编码是GB2312编码的超集,向下完全兼容GB2312,同时GBK收录了Unicode基本多文种平面中的所有CJK汉字。同 GB2312一样,GBK也支持希腊字母、日文假名字母、俄语字母等字符,但不支持韩语中的表音字符(非汉字字符)。GBK还收录了GB2312不包含的汉字部首符号、竖排标点符号等字符。
GBK的整体编码范围是为0x8140-0xFEFE,不包括低字节是0×7F的组合。高字节范围是0×81-0xFE,低字节范围是0x40-7E和0x80-0xFE。
低字节是0x40-0x7E的GBK字符有一定特殊性,因为这些字符占用了ASCII码的位置,这样会给一些系统带来麻烦。 有些系统中用0x40-0x7E中的字符(如“|”)做特殊符号,在定位这些符号时又没有判断这些符号是不是属于某个 GBK字符的低字节,这样就会造成错误判断。在支持GB2312的环境下就不存在这个问题。需要注意的是支持GBK的环境中小于0x80的某个字节未必就是ASCII符号;另外就是最好选用小于0×40的ASCII符号做一些特殊符号,这样就可以快速定位,且不用担心是某个汉字的另一半。Big5编码中也存在相应问题
GBK、GB2312等与UTF8之间都必须通过Unicode编码才能相互转换:
GBK、GB2312--Unicode--UTF8
UTF8--Unicode--GBK、GB2312
GBK,GB2312以及Unicode都既是字符集,也是编码方式,而UTF-8只是编码方式,并不是字符集GBK编码中英文字符只占一个字节
例:严
1)ANSI:文件的编码就是两个字节D1 CF,这正是严的 GB2312 编码,这也暗示 GB2312 是采用大头方式存储的。
2)Unicode:编码是四个字节FF FE 25 4E,其中FF FE表明是小头方式存储,真正的编码是4E25。
3)Unicode big endian:编码是四个字节FE FF 4E 25,其中FE FF表明是大头方式存储。
4)UTF-8:编码是六个字节EF BB BF E4 B8 A5,前三个字节EF BB BF表示这是UTF-8编码,后三个E4B8A5就是严的具体编码,它的存储顺序与编码顺序是一致的。
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.Properties;
public class codeText {
//从数字到字节数组—编码
private static byte[] encoding1(String str, String charset) throws UnsupportedEncodingException {
return str.getBytes(charset);
}
private static byte[] encoding2(String str, String charset) {
Charset cset = Charset.forName(charset);
ByteBuffer byteBuffer = cset.encode(str);
byte[] bytes = new byte[byteBuffer.remaining()];
byteBuffer.get(bytes);
return bytes;
}
//从字节数组到数字—解码
private static String decoding1(byte[] bytes,String charset) throws UnsupportedEncodingException {
String str = new String(bytes, charset);
return str;
}
private static String decoding2(byte[] bytes, String charset) {
Charset cset = Charset.forName(charset);
ByteBuffer buffer = ByteBuffer.wrap(bytes);
CharBuffer charBuffer = cset.decode(buffer);
return charBuffer.toString();
}
/**
Java的默认字符集,可以在两个地方设定,一是执行java程序时使用-Dfile.encoding参数指定,例如-Dfile.encoding=UTF-8就指定默认字符集是UTF-8。二是在程序执行时使用Properties进行指定,如下:
注意,这两种方法如果同时使用,则程序开始时使用参数指定的字符集,在Properties方法后使用Properties指定的字符集。
如果这两种方法都没有使用,则使用操作系统默认的字符集。例如中文版windows 7的默认字符集是GBK。
默认字符集的优先级如下:
1.程序执行时使用Properties指定的字符集;
2.java命令的-Dfile.encoding参数指定的字符集;
3.操作系统默认的字符集;
4.JDK中默认的字符集,我跟踪了JDK1.8的源代码,发现其默认字符集指定为ISO-8859-1。
*/
private static void setEncoding(String charset) {
Properties properties = System.getProperties();
// properties.put("file.encoding",charset);
System.out.println(properties.get("file.encoding"));
}
private static String getEncoding() {
Properties properties = System.getProperties();
System.out.println(properties.get("file.encoding"));
return properties.get("file.encoding").toString();
}
//JDK支持的字符集
private static void printAvailableCharsets() {
Map<String ,Charset> map = Charset.availableCharsets();
System.out.println("the available Charsets supported by jdk:"+map.size());
for (Map.Entry<String, Charset> entry :
map.entrySet()) {
System.out.println(entry.getKey());
}
}
private static void generateGrabledCode() throws UnsupportedEncodingException {
String str = "中国";
byte[] bytes = str.getBytes("UTF-8");
str = new String(bytes, "GBK");
System.out.println(str);
}
public static void main(String[] args){
// printAvailableCharsets();
//getEncoding();
/**
中 unicode:4e2d; GBK:d6d0
默认UTF-8:11100100 10111000 10101101
UTF-8: 11100100 10111000 10101101
Unicode: 11111111111111111111111111111110 11111111111111111111111111111111 1001110 101101 ;fe ff表示Big Endian大头方式存储
GBK:11010110 11010000
*/
String str = "中";
try {
byte[] bytes = str.getBytes();
for(byte b:bytes){
System.out.print(Integer.toBinaryString(b).substring(24)+" ");
}
System.out.println();
byte[] bytesUTF = str.getBytes("UTF-8");
for(byte b:bytesUTF){
System.out.print(Integer.toBinaryString(b).substring(24)+" ");
}
System.out.println();
byte[] bytesUnicode = str.getBytes("Unicode");
for(byte b:bytesUnicode){
System.out.print(Integer.toBinaryString(b)+" ");
}
System.out.println();
byte[] bytesGBK = str.getBytes("GBK");
for(byte b:bytesGBK){
System.out.print(Integer.toBinaryString(b).substring(24)+" ");
}
System.out.println();
str = new String(bytesGBK, "GBK");
System.out.println(str);
}catch (UnsupportedEncodingException e){
e.printStackTrace();
}
}
}