java中的字符编码简介
一、有关编码的基础知识
1、计算机存储的容量
位(比特) bit 最小的单元
字节 byte 机器语言的单位(计算机存储设备容量的最基本单位)
1byte = 8bits 8个二进制位为1个字节
1KB = 1024byte
1MB = 1024KB
1GB = 1024MB
1TB = 1024GB
字节:只有字节才有地址的概念,对一种计算机的存储设备以字节为单位赋予的地址称为字节编址。一个存储单元可以存储一个字节。
例如:一个1KB的存储器,他有1024个存储单元,他的编号从0-1023
2、进制
二进制:采用0和1两个数码来表示的数。进位规则是“逢二进一”
八进制:采用0-7八个数字计数,进位规则是“逢八进一”
十进制:采用0-9十个数字计数,进位规则是“逢十进一”
十六进制:采用0-9,A-F(10-15),进位规则是“逢十六进一”
/**
* 进制转换
*/
public class jinzhiTest {
public static void main(String[] args) {
int i = 22;
//二进制 10110 = 1*2^4+0*2^3+1*2^2+1*2^1+0*2^0=22
System.out.println(Integer.toBinaryString(i)); //10110
//八进制 26 = 2*8^1+6*8^0 = 22
System.out.println(Integer.toOctalString(i)); //26
//十六进制 16 = 1*16^1+6*16^0 = 22
System.out.println(Integer.toHexString(i)); //16
}
}
3、其他相关概念
字符:是各种文字和符号的总称,包括各个国家的文字、标点符号、图形符号、数字等。
字符集:字符集是多个符号的集合,每个字符集包含的字符个数不同。
字符编码:字符集只是规定了有哪些字符,而最终决定采用哪些字符,每一个字符用多少个字节表示等问题,则是由编码来决定的。计算机要准确的处理各个字符集文字,需要进行字符编码,以便计算机能够识别和存储各种文字。(通过字符编码可以把字符转化成字节)
二、常见的字符集编码介绍
1、ASCII字符集
1.1、定义
ASCII 字符集:美国信息互换标准代码。是基于罗马字母表的一套电脑编码系统,主要显示英语和一些西欧语言,是现今最通用的单字节编码系统。ASCII字符集只有256个字符,用0-255之间的数字来表示。(使用7位单元的字符集01111111-->256)
1.2、包含内容
控制字符(回车键、退格、换行键等)
可显示字符(英文大小写、阿拉伯数字、西文符号)
扩展字符集(表格符号、计算符号、希腊字符、拉丁符号)
1.3、编码方式
第0-31号及127号是控制字符或通讯专用字符
第32-126号是字符,其中48-57号是0-9是个阿拉伯数字,65-90号为26个大写英文字母,97-122号为26个英文小写字母,其余一些为标点符号,运算符号等。
2、Unicode字符集
2.1、定义
国际标准码,融合了多种文字。所有文字都用两个字节来表示,Java 语言使用的就是unicode) University multiple-object coded character set(通用多八位编码字符集),支持世界上超过650种语言的国际字符。Unicode允许在同一服务器上混合使用不同语言,它为每种语言的每个字符设定了统一并且唯一的二进制编码,以满足跨平台,跨语言进行文本转换,处理的要求。[ASCII只有256个字符集,这对于许多亚洲和东方语言来说,远远不够,为此Unicode应运而生]
2.2、编码方式
Unicode标准始终使用十六进制数字,固定使用2个字节来表示一个字符,共可以表示65536个字符。而且书写时在前面加上前缀“U+”,例如A的编码是0041,则书写成“U+0041”。
Unicode编码系统分为编码和实现。编码指的是:每个字符对于的Unicode码是唯一确定的,Unicode用两个字节表示一个字符。这样理论上可以表示65536(2的16次方)个字符。Unicode用数字0-0x10FFFF来映射这些字符,最多可以容纳1114112个字符。而新版的Unicode定义了16个辅助平面,至少需要占据21位编码空间(比3字节少,但是辅助平面仍然占用4个字节编码空间,与UCS-4保持一致)。UCS-4是一个更大的尚未完全填充的31位字符集。
签名BOM:
*!* BOM 是 Byte Order Mark 的缩写(字节顺序标记),是编码方案里用于标识编码的标准标记。
*!* ANSI 文件没有 BOM
*!* UTF-8 文件的 BOM 为:EF BB BF,不过 UTF-8 文件可以有 BOM,也可以没有 BOM
*!* Unicode big endian 文件的 BOM 为:FE FF
*!* Unicode little endian 文件的 BOM 为:FF FE
Unicode规范中定义,每一个文件的最前面分别加入一个表示编码顺序(BOM)的字符,这个字符的名字叫做"零宽度非换行空格"(ZERO WIDTH NO-BREAK SPACE),用FEFF表示。这正好是两个字节,而且FF比FE大1。
如果一个文本文件的头两个字节是FE FF,就表示该文件采用大头方式;如果头两个字节是FF FE,就表示该文件采用小头方式。
软件一般采用三种方式来决定文本的字符集和编码: 检测文件头标识,提示用户选择,根据一定的规则猜测。最标准的途径是检测文本最开头的几个字节,开头字节 Charset/encoding:
EF BB BF UTF-8
FF FE UTF-16/UCS-2, little endian
FE FF UTF-16/UCS-2, big endian
FF FE 00 00 UTF-32/UCS-4, little endian.
00 00 FE FF UTF-32/UCS-4, big-endian.
例如:
以汉字"严"为例,Unicode码是4E25,需要用两个字节存储,一个字节是4E,另一个字节是25。存储的时候,4E在前,25在后,就是Big endian(最低位地址存放高位字节,可称高位优先,内存从最低地址开始按顺序存放(高数位数字先写)。最高位字节放最前面。)方式;25在前,4E在后,就是Little endian(最低位地址存放低位字节,可称低位优先,内存从最低地址开始按顺序存放(低数位数字先写)。最低位字节放最前面。)方式。
Unicode的实现方式称为Unicode转换格式(Unicode Transformation Format,简称UTF)。因为实际操作中,我们因为一些特殊的要求(提高性能、节约空间等),对Unicode会有很多种实现方式。例如:如果一个仅包含基本7位的ASCII码字符,对于Unicode来说每个字符都使用了两个字节的Unicode编码传输,那么其第一个字节的8位始终为0,这就造成了比较大的浪费,对于这种情况,可以使用UTF-8编码。这是一种变长的编码,占它将基本的7位的ASCII字符仍用7位编码表示,占用一个字节(首位补0),而遇到与其他Unicode字符混合的情况,将按照一定的算法转换,每个字符使用1-3个字节编码,并利用首位为0或1进行识别。这样对以7位ASCII字符为主的西文文档就大大的节省了编码的长度。
2.3、实现方式
UTF8 :
是Unicode 其中的一个使用方式。 UTF-8使用可变长度字节来存储Unicode字符,如ASCII字母还是采用一个字符来存储,希腊字母等采用2个字符来存储,而常用的汉字要使用3字节,辅助平面字符(少量不常用的)则使用4字节。
UTF-8的编码规则很简单,只有二条:
a)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。
b)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。
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
-------------------|-------------------------------------------------
以汉字"严"为例:
已知"严"的unicode是4E25(100111000100101),根据上表,可以发现4E25处在第三行的范围内(0000 0800-0000 FFFF),因此"严"的UTF-8编码需要三个字节,即格式是"1110xxxx 10xxxxxx 10xxxxxx"。然后,从"严"的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,"严"的UTF-8编码是"11100100 10111000 10100101",转换成十六进制就是E4B8A5。
UTF-16:
使用一个或两个未分配的16位代码单元的序列对Unicode代码点进行编码,其中大部分汉字采用2个字节编码,少量汉字采用4个字节编码
UTF-32:
将每一个Unicode代码点表示为相同值的32位整数。 使用4个字节为每个字符编码。占用空间一般是其他编码的2-4倍。
3、GB2312 字符集
3.1、定义
信息交换用汉字编码字符集。是中国标准的简体中文字符集,它所收录的汉字已经覆盖99.75%的使用频率,在中国大陆和新加坡广泛使用。1980年制定的。
3.2、包含内容
GB2312收录了简化汉字及一般字符,序号,数字,拉丁字母,日文假名,希腊字母,俄文字母,汉语拼音符号,汉语注音字母,共7445个图形字符。其中包括6763个汉字,一级汉字3755个,二级汉字3008个。
3.3、编码方式
GB2312对所收汉字进行了“分区”处理,每区含有94个汉字或者符号,这种表示方法也叫做“区位码”。 它是用双字节表示的,前面的字节为第一字节,又称“高字节”,后面的为第二字节,“低字节”。 高位字节,把01-87区的区号加上0xA0(相当于数字160);低位字节把01-94区的区号加上0xA0(相当于数字160)。 举个简单的小例子:第一个汉字——“啊”,它的区号为16,位号01,则区位码是1601。则高字节位:16+0xA0=0xB0;低字节位:01+0xA0=0xA1,所以“啊”的汉字处理编码为0xB0A1。
Windows 中的代码页(Code Page)是 CP936 。
4、GBK字符集
4.1、定义
GBK是GB2312字符集的扩展(K)(中国的中文编码表升级,融合了更多的中文文字符号。),它收录了21886个符号,它分为汉字区和图形符号区,汉字区包括21003个字符。GBK字符集主要扩展了繁体中文字的支持。 GBK 作为对 GB2312 的扩展,在现在的 Windows 系统中仍然使用代码页 CP936 表示。
扩展:内码和code page(代码页)
UNICODE 是用两个字节来表示为一个字符,他总共可以组合出65535不同的字符,这大概已经可以覆盖世界上所有文化的符号。如果还不够也没有关系,ISO已经准备了UCS-4方案,说简单了就是四个字节来表示一个字符,这样我们就可以组合出21亿个不同的字符。如果Unicode在全世界人民使用计算机之前发明时就统一了,代码页就没有必要存在。
内码:是指操作系统内部的字符编码。早期操作系统的内码是与语言相关的,从Windows NT 开始,MS 趁机把它们的操作系统改了一遍,把所有的核心代码都改成了用 UNICODE 方式工作的版本。现在的Windows 在内部统一使用Unicode(这样在内核上可以支持全世界所有的语言文字),然后用代码页适应各种语言。微软一般将缺省代码页指定的编码说成是内码,在特殊的场合也会说自己的内码是Unicode,例如在 GB18030问题的处理上。
Codepage(代码页):就是各国的文字编码和Unicode之间的映射表。例如GBK的code page是CP936 ,BIG5的code page是CP950,GB2312的code page是CP20936。微软也为GB18030定义了code page:CP54936。但是由于GB18030有一部分4字节编码,而Windows的代码页只支持单字节和双字节编码,所以这个code page是无法真正使用的。
Windows应用程序使用的代码页由windows区域(Locale)设置来定义。系统Locale决定代码页,用户Locale决定数字、货币、时间和日期的格式。
常见的Code Page:
代码页Code Page | 对应的字符集Character Set |
Code Page 932(cp932) | Japanese |
Code Page 936(cp936) | GBK – Simplified Chinese |
Code Page 949 | Korean |
Code Page 950 | BIG5 – Traditional Chinese |
Code Page 1200 | UTF-16LE Unicode little-endian |
Code Page 1201 | UTF-16BE Unicode big-endian |
Code Page 65000 | UTF-7 Unicode |
Code Page 65001 | UTF-8 Unicode |
ANSI Code Page | 微软自己定义了一系列的Code Page |
我们在另存记事本文件时,常常看到默认存的是ANSI编码,ANSI是微软自定义的一系列代码页。(如:GBK、GB312、Big5、Shift_JIS等这些编码方式统称为ANSI编码)在简体中文Window操作系统中,ANSI编码代表GBK,在繁体中文操作系统中,ANSI代表Big5编码,在日文操作系统中,ANSI代表Shift_JIS。
例如:在记事本中输入 “联通” 两个字
保持后,再次打开发现乱码了。记事本默认保持的是ANSI字符编码。在简体中文模式下ANSI默认使用的是GBK编码。GBK编码下联通对应的编码是:
联-0xc1aa 二进制排列:1100 0001 1010 1010
通-0xcda8 二进制排列:1100 1101 1010 1000
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的第二个模板,保持的时候保持的是GBK,当我们打开记事本时,记事本误把该文件当做UTF8编码的文件去解析,即:GBK-转为-UTF8,所以打开就乱码了。
但是用UltraEdit打开,看到的是[jͨ]。
这是因为其实这是因为GB2312编码与UTF8编码产生了编码冲撞的原因。UltraEdit检测到头部符合UTF-8,到误把文件以UTF-8格式读入,读入后对于联字来说:1100 0001 1010 1010去掉红色部分:00001 101010
再对齐补0。就得到:0000 0000 0110 1010。转换成编码:0x006a。经过utf-8解析后0x006a对应的Unicode字符是j。同理对于联字来说,转换成编码:0x0368。
public class Test {
public static void main(String[] args) throws UnsupportedEncodingException {
//GBK编码 Charset只能处理双字节编码
Charset gb = Charset.forName("GBK");
ByteBuffer gbbu1 = gb.encode("联");
System.out.println(Integer.toHexString(gbbu1.getChar())); //claa
ByteBuffer gbbu2 = gb.encode("通");
System.out.println(Integer.toHexString(gbbu2.getChar())); //cda8
Charset cs1 = Charset.forName("GBK");
ByteBuffer buffer1 = cs1.encode("联通");
//以utf-8编码去解析 GBK编码文字
Charset cs2 = Charset.forName("utf-8");
CharBuffer jm1 = cs2.decode(buffer1);
System.out.println("UTF-8解析 GBK:"+jm1.toString()); //�� ͨ
//类似UltraEdit打开后解析
System.out.println("\u006a\u0368"); //jͨ
}
}
5、BIG5字符集
5.1、定义
又称大五码,由台湾五家软件公司创立。因为当时台湾没有一个标准的字符集,而且GB2312又没有收录繁体字,所以才推出了BIG5。
5.2、包含内容
BIG5字符集共收录了13053个中文字,该字符集在台湾使用。但是没有考虑到社会上流通的人名,地方用字,方言用字,化学及生物科等用字,没有包含日文平假名及片假字母。
5.3、编码方式
BIG5也采用双字节存储方法,以两个字节编码一个字。高位字节的编码范围是0xA1-0xF9,低位字节的编码范围是0xA1-0xFE。
6、GB18030字符集
6.1、定义
GB18030字符集标准解决汉字,日文假名,朝鲜语和中国少数民族文字组成的大字符集计算机编码问题。
6.2、包含内容
该标准的字符总编码空间超过150万个编码位,收录了27484个汉字,覆盖中文,日文,朝鲜语和中国少数民族文字。满足中国大陆,香港,台湾,日本和韩国等东南亚地区信息交换多文种,大字量,多用途,统一编码格式的要求。
6.3、编码方式
GB8030标准采用单字节,双字节和四字节三种方式对字符编码。 单字节部分使用0x00-0x7F码(对应于ASCII码的相应码); 双字节部分,首字节码从0x81-0xFE,尾字节码分别是0x40-0x7E和0x80-0xFE。四字节部分采用0x30-0x39作为双字节编码扩充的后缀,这样扩充的四字节编码,其范围是0x81308130-0x0xFE39FE39,其中第一,三个字节编码位均为0x81-0xFE,第二,四个为0x30-0x39。GB18030 在 Windows 中的代码页是 CP54936。
7、ISO-8859-1:拉丁文字母表1号
用一个字节的8位表示。属于单字节编码,应用于英文系列,最多表示的字符范围是0-255,无法表示中文字符。很多协议上,默认使用的是该编码。
public class IOTest {
public static void main(String[] args) throws UnsupportedEncodingException {
//java中的char字符,采用的是Unicode编码
System.out.println(Integer.toHexString('黄')); //9ec4
System.out.println("\u9ec4"); //书写格式 前面加u
// 中 字的Unicode编码为4e2d
System.out.println(Integer.toHexString('中')); //4e2d
// 中 字的GB2312编码为d6d0 getChar() 只能处理两个字节的编码规则
Charset gb = Charset.forName("GB2312");
ByteBuffer gbbu = gb.encode("中");
System.out.println(Integer.toHexString(gbbu.getChar())); //d6d0
String str = "中";
byte[] utf8Bytes = str.getBytes("utf-8");
System.out.println("uft8:" + bytesToHexString(utf8Bytes));//e4b8ad
//注:前面的feff就表示该文件采用大头方式存储
String str1 = "中";
byte[] utf8Bytes1 = str1.getBytes("utf-16");
System.out.println("utf-16:" + bytesToHexString(utf8Bytes1));//feff e42d
String str2 = "中";
byte[] utf8Bytes2 = str2.getBytes("Unicode");
System.out.println("Unicode:" + bytesToHexString(utf8Bytes2));//feff e42d
}
public static String bytesToHexString(byte[] src){
StringBuilder stringBuilder = new StringBuilder();
if (src == null || src.length <= 0) {
return null;
}
for (int i = 0; i < src.length; i++) {
int v = src[i] & 0xFF; // 之所以用byte和0xff相与,是因为int是32位,与0xff相与后就舍弃前面的24位,只保留后8位
String hv = Integer.toHexString(v);
if (hv.length() < 2) { // 不足两位要补0
stringBuilder.append(0);
}
stringBuilder.append(hv);
}
return stringBuilder.toString();
}
}
为什么会出现乱码?
public class IoTest2 {
public static void main(String[] args) throws UnsupportedEncodingException {
//GBK解析GBK
Charset cs1 = Charset.forName("GBK");
ByteBuffer buffer1 = cs1.encode("号");
System.out.println("GBK解析GBK:"+cs1.decode(buffer1)); //GBK:号
//GB2312 解析 GBK
ByteBuffer buffer2 = cs1.encode("号");
Charset cs2 = Charset.forName("GB2312");
CharBuffer jm2 = cs2.decode(buffer2);
System.out.println("GB2312 解析 GBK:"+jm2.toString());//GB2312:号
//BIG5 解析 GBK
ByteBuffer buffer3 = cs1.encode("号"); //GBK
Charset cs3 = Charset.forName("BIG5");
CharBuffer jm3 = cs3.decode(buffer3);
//虽然找到了,但是字符编码并不同
System.out.println("BIG5 解析 GBK:"+jm3.toString()); //BIG5:瘍
/**
* GBK解析 UTF-8 :e58f在GBK中的编码对应的是鍙字,GBK是双字节编码,
* 由于位数差异,多出来一个字节在GBK中无法找到对应的编码
*/
Charset cs4 = Charset.forName("UTF-8");
ByteBuffer buffer4 = cs4.encode("号");
System.out.println(bytesToHexString(buffer4.array())); //e58fb7
CharBuffer jm4 = cs1.decode(buffer4);
System.out.println("GBK 解析 UTF-8:"+jm4.toString());//鍙�
Charset cs5 = Charset.forName("GBK");
ByteBuffer buffer5 = cs5.encode("号");
System.out.println(bytesToHexString(buffer5.array())); //bac5
CharBuffer jm5 = cs4.decode(buffer5);
//找不到对应的编码只有两位所以出现两个,中文在UTF-8中占三个字符�
System.out.println("UTF-8 解析 GBK :"+jm5.toString());//��
}
public static String bytesToHexString(byte[] src){
StringBuilder stringBuilder = new StringBuilder();
if (src == null || src.length <= 0) {
return null;
}
for (int i = 0; i < src.length; i++) {
int v = src[i] & 0xFF; // 之所以用byte和0xff相与,是因为int是32位,与0xff相与后就舍弃前面的24位,只保留后8位
String hv = Integer.toHexString(v);
if (hv.length() < 2) { // 不足两位要补0
stringBuilder.append(0);
}
stringBuilder.append(hv);
}
return stringBuilder.toString();
}
}
字节长度:
public class CodeLength{
public static void main(String[] args) throws UnsupportedEncodingException {
System.out.println("深".getBytes("GBK").length);//2
System.out.println("深".getBytes("UTF-8").length);//3
System.out.println("深".getBytes("unicode").length);//4
System.out.println("深".getBytes("ISO8859-1").length);//1
System.out.println("深".getBytes().length); //3
Charset cs1 = Charset.forName("Unicode");
ByteBuffer buffer1 = cs1.encode("深");
System.out.println(bytesToHexString(buffer1.array()));
//feff6df100
}
public static String bytesToHexString(byte[] src){
StringBuilder stringBuilder = new StringBuilder();
if (src == null || src.length <= 0) {
return null;
}
for (int i = 0; i < src.length; i++) {
int v = src[i] & 0xFF; // 之所以用byte和0xff相与,是因为int是32位,与0xff相与后就舍弃前面的24位,只保留后8位
String hv = Integer.toHexString(v);
if (hv.length() < 2) { // 不足两位要补0
stringBuilder.append(0);
}
stringBuilder.append(hv);
}
return stringBuilder.toString();
}
}
GBK编码:一个中文占2个字节
UTF-8编码:一个中文占3个字节
Unicode编码:一个中文占4个字节,Unicode本身是采用两个字节编码的,在java中直接使用Unicode转码时会按照UTF-16BE的方式拆分,由于UTF-16分为UTF-16LE/UTF-16BE,直接使用无法区分是大端还是小端,所以会加上一个额外的字节序BOM头。开头两个字节FE FF-(BE),开头两个字节FF FE-(LE)刚好占据了两个字节。所以会显示占据4个字节长度
ISO8859-1编码:单字节编码,对于中文无法解析,就算解析也只能取其一个字节编码
"深".getBytes()也返回的是3,这是因为文件格式默认设置的UTF-8编码。按照默认编码获取的字节长度为3