澳门新葡亰平台游戏网站Java GBK 中文乱码问题分析

澳门新葡亰平台游戏网站 31

在io相关的操作中经常会现身乱码难点

三种司空见惯的编码格式 
为啥要编码 
不亮堂我们有未有想过三个题目,那就是干吗要编码?大家能还是不得不编码?要回答这些主题材料亟需求赶回Computer是何许表示大家人类能够通晓的记号的,这一个标志也正是大家人类利用的语言。由于人类的言语有太多,由此表示这个语言的标识太多,不能用Computer中三个宗旨的存款和储蓄单元——
byte
来表示,因此应当要透过拆分或一些翻译专门的职业,才具让Computer能明白。大家能够把Computer能够通晓的言语假定为Türkiye Cumhuriyeti语,其它语言要能够在微管理机中动用必得通过三次翻译,把它翻译成意大利共和国语。那几个翻译的进度正是编码。所以能够想像就算不是说斯拉维尼亚语的国家要可以运用Computer就必须要要由此编码。那看起来有些霸道,可是那就是现状,那也和我们国家现行反革命在大力推广普通话同样,希望其他国家都会说国语,现在别的的言语都翻译成粤语,大家得以把Computer中积存消息的矮小单位改成汉字,那样我们就不设有编码难点了。 
因而不问可以知道,编码的原故能够总括为: 
计算机中存款和储蓄信息的小小单元是三个字节即 8 个 bit,所以能代表的字符范围是
0~255 个 
人类要代表的标志太多,无法用八个字节来完全代表 
要解决这几个冲突必得必要叁个新的数据布局 char,从 char 到 byte 必得编码 
如何“翻译” 
接头了种种语言需求调换,经过翻译是必不可缺的,那又怎么着来翻译啊?总括中提拱了多种翻译方式,视若无睹的有
ASCII、ISO-8859-1、GB2312、GBK、UTF-8、UTF-16
等。它们都得以被看作为词典,它们规定了转会的规行矩步,根据这一个法规就足以让Computer科学的代表大家的字符。近日的编码格式超多,比方GB2312、GBK、UTF-8、UTF-16
这二种格式都能够表示二个中国字,那我们到底接受哪类编码格式来存款和储蓄汉字呢?那将要考虑到此外因素了,是累积空间最重要依然编码的效用主要。遵照那么些成分来不易选拔编码格式,上边简单介绍一下这两种编码格式。 
ASCII 码 
学过Computer的人都知晓 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
来解码,并且不会有乱码。 
GB18030 
齐全部都是《消息置换用汉字编码字符集》,是本国的强制标准,它只怕是单字节、双字节大概四字节编码,它的编码与
GB2312
编码包容,那些即使是国标,不过实际行使类别中应用的并不普及。 
UTF-16 
说起 UTF 必需求提到 Unicode(Universal Code 统一码),ISO
试图想成立一个簇新的超语言字典,世界上保有的言语都得以透过这本词典来相互翻译。总体上看这一个字典是多么的错综相连,关于
Unicode 的详细标准能够参照相应文书档案。Unicode 是 Java 和 XML
的底蕴,上面详细介绍 Unicode 在微微处理机中的存款和储蓄格局。 
UTF-16 具体定义了 Unicode 字符在微微处理器中存取方法。UTF-16
用五个字节来代表 Unicode
转变格式,那些是定长的代表方法,无论什么字符都得以用多个字节表示,五个字节是
16 个 bit,所以叫 UTF-16。UTF-16
表示字符极度便利,每三个字节表示二个字符,那个在字符串操作时就大大简化了操作,那也是
Java 以 UTF-16 作为内部存款和储蓄器的字符存款和储蓄格式的三个十分重要的原故。 
UTF-8 
UTF-16
统一接收多个字节表示贰个字符,纵然在表示上特别轻易方便,可是也是有其症结,有极大学一年级些字符用两个字节就能够代表的后天要两个字节表示,存款和储蓄空间放大了一倍,在明天的互联网带宽还丰富轻松的前不久,那样会增大网络传输的流量,并且也没供给。而
UTF-8
采取了一种变长技巧,每一种编码区域有例外的字码长度。不相同品类的字符能够是由
1~6 个字节组成。 
UTF-8 有以下编码法规: 
假诺贰个字节,最高位(第 8 位)为 0,表示那是三个 ASCII 字符(00 –
7F)。可以知道,全体 ASCII 编码已然是 UTF-8 了。 
假诺一个字节,以 11 最初,三回九转的 1
的个数暗指那个字符的字节数,譬如:110xxxxx 象征它是双字节 UTF-8
字符的首字节。 
假如贰个字节,以 10
开始,表示它不是首字节,需求向前查找能力博稳当前字符的首字节 
Java 中须求编码的场景 
前方描述了大面积的二种编码格式,下边将介绍 Java
中怎么样管理对编码的支撑,什么场面中需求编码。 
I/O 操作中留存的编码 
笔者们清楚涉及到编码的地点平时都在字符到字节或然字节到字符的转移上,而需求这种转移的景观紧要是在
I/O 的时候,这一个 I/O 包涵磁盘 I/O 和网络 I/O,关于互连网 I/O
部分在背后将根本以 Web 应用为例介绍。下图是 Java 中拍卖 I/O
难题的接口: 
澳门新葡亰平台游戏网站 1 
Reader 类是 Java 的 I/O 中读字符的父类,而 InputStream
类是读字节的父类,InputStreamReader 类正是关乎字节到字符的大桥,它担负在
I/O 进程中拍卖读取字节到字符的转移,而具体字节到字符的解码达成它由
StreamDecoder 去实现,在 StreamDecoder 解码进程中必须由顾客钦命 Charset
编码格式。值得注意的是一旦你从未点名
Charset,将使用本地蒙受中的暗许字符集,比方在中文景况准将使用 GBK
编码。 
写的图景也是相符,字符的父类是 Writer,字节的父类是 OutputStream,通过
OutputStreamWriter 调换字符到字节。如下图所示: 
澳门新葡亰平台游戏网站 2 
一致 StreamEncoder
类担当将字符编码成字节,编码格式和私下认可编码法则与解码是一律的。 
如上面一段代码,达成了文本的读写效用: 

编码难点间接烦懑着开辟职员,特别在 Java 中更为显然,因为 Java
是跨平台语言,不一样平台之间编码之间的切换相当多。本文将向你详细介绍 Java
中编码难题应运而生的根本原因,你将通晓到:Java
中时时遇上的二种编码格式的区别;Java
中偶尔必要编码的情景;现身普通话题目标原由分析;在开荒 Java web
程序时可能会存在编码的多少个地方,多个 HTTP
诉求怎么调节编码格式?如何避免现身汉语标题?

比方在一个txt文件中按GBK编码保存内容”淘!笔者欢快!”

Java代码  澳门新葡亰平台游戏网站 3

三种布衣蔬食的编码格式

澳门新葡亰平台游戏网站 4

  1. String file = “c:/stream.txt”;   
  2. String charset = “UTF-8”;   
  3. // 写字符换转成字节流  
  4. FileOutputStream outputStream = new FileOutputStream(file);   
  5. OutputStreamWriter writer = new OutputStreamWriter(   
  6. outputStream, charset);   
  7. try {   
  8.    writer.write(“那是要封存的普通话字符”卡塔尔;   
  9. } finally {   
  10.    writer.close();   
  11. 澳门新葡亰平台游戏网站 ,}   
  12. // 读取字节转变来字符  
  13. FileInputStream inputStream = new FileInputStream(file);   
  14. InputStreamReader reader = new InputStreamReader(   
  15. inputStream, charset);   
  16. StringBuffer buffer = new StringBuffer();   
  17. char[] buf = new char[64];   
  18. int count = 0;   
  19. try {   
  20.    while ((count = reader.read(buf)) != -1) {   
  21.        buffer.append(buffer, 0, count);   
  22.    }   
  23. } finally {   
  24.    reader.close();   
  25. }   
为什么要编码

不了然大家有未有想过二个难点,这正是怎么要编码?大家能还是不得不编码?要回答这一个标题不得不要回来Computer是何许表示我们人类能够知情的号子的,那几个标识也正是大家人类接收的言语。由于人类的语言有太多,因此表示这几个语言的符号太多,不能够用Computer中二个主导的存款和储蓄单元——
byte
来表示,因此必定要经过拆分或一些翻译职业,能力让计算机能精通。大家得以把计算机能够明白的言语假定为法语,别的语言要能够在计算机中行使必得通过三回翻译,把它翻译成俄语。那一个翻译的历程便是编码。所以能够想像借使不是说越南语的国家要能够使用Computer就务须求经过编码。这看起来有一些霸道,不过那正是现状,那也和我们国家现行反革命在大力推广汉语雷同,希望其余国家都会说汉语,今后此外的言语都翻译成人中学文,大家能够把计算机中存款和储蓄新闻的一丝一毫单位改成汉字,这样大家就官样文章编码难题了。
因而不问可以见到,编码的从头至尾的经过能够计算为:
微电脑中蕴藏音信的细小单元是三个字节即 8 个 bit,所以能代表的字符范围是
0~255 个
人类要表示的标记太多,不能够用贰个字节来完全代表
要消除那几个冲突必得要求叁个新的数据构造 char,从 char 到 byte 必需编码

然后用RandomAccessFile类读取并打字与印刷一行。

在大家的应用程序中涉嫌到 I/O 操作时只要注意钦赐统一的编解码 Charset
字符集,平时不会冒出乱码难题,有个别应用程序借使不在乎内定字符编码,汉语意况中取操作系统默许编码,假诺编解码都在普通话蒙受中,平日也没难题,然而照旧不言自明的不提出使用操作系统的默许编码,因为这样,你的应用程序的编码格式就和运作境况绑定起来了,在跨际遇下不小概现身乱码难题。 
内部存款和储蓄器中操作中的编码 
在 Java 开采中除去 I/O
涉及到编码外,最常用的应当便是在内部存款和储蓄器中开展字符到字节的数据类型的转移,Java
中用 String 表示字符串,所以 String
类就提供转变来字节的点子,也支撑将字节调换为字符串的布局函数。如下代码示例: 

如何“翻译”

知晓了各类语言要求交换,经过翻译是必备的,那又怎么来翻译啊?总结中提拱了二种翻译情势,常见的有
ASCII、ISO-8859-1、GB2312、GBK、UTF-8、UTF-16
等。它们都能够被看作为辞典,它们规定了转账的中规中矩,根据这一个准则就足以让Computer科学的象征大家的字符。最近的编码格式比较多,举例GB2312、GBK、UTF-8、UTF-16
这三种格式都足以表示壹当中夏族民共和国字,那我们到底选用哪一类编码格式来囤积汉字呢?那就要构思到任何因素了,是积存空间最主要如故编码的功能主要。依据这个要向来科学选取编码格式,下边简要介绍一下这两种编码格式。

  • ASCII 码

学过Computer的人都晓得 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 来解码,并且不会有乱码。

  • GB18030

齐全都以《音讯置换用汉字编码字符集》,是本国的劫持规范,它恐怕是单字节、双字节要么四字节编码,它的编码与
GB2312 编码宽容,那几个就算是国标,可是实际上使用种类中接收的并不普及。

  • UTF-16

谈到 UTF 一定要提到 Unicode(Universal Code 统一码),ISO
试图想成立三个崭新的超语言词典,世界上拥有的言语都能够由此那本词典来互相翻译。简单的讲这一个辞典是何等的复杂性,关于
Unicode 的详尽标准能够参见相应文书档案。Unicode 是 Java 和 XML
的底工,下边详细介绍 Unicode 在微型机中的存款和储蓄格局。
UTF-16 具体定义了 Unicode 字符在微机中存取方法。UTF-16
用八个字节来表示 Unicode
转变格式,那些是定长的表示方法,无论什么字符都能够用多个字节表示,七个字节是
16 个 bit,所以叫 UTF-16。UTF-16
表示字符特别便于,每多个字节表示一个字符,那几个在字符串操作时就大大简化了操作,那也是
Java 以 UTF-16 作为内部存款和储蓄器的字符存储格式的二个很关键的缘故。

  • UTF-8

UTF-16
统一行使八个字节表示叁个字符,即使在象征上特别轻巧方便,可是也可以有其症结,有比极大片段字符用一个字节就能够象征的前天要五个字节表示,存款和储蓄空间放大了一倍,在现行反革命的网络带宽还非常常有限的前天,那样会叠合互联网传输的流量,何况也没必要。而
UTF-8
接纳了一种变长才干,每一个编码区域有不一样的字码长度。不一样门类的字符能够是由
1~6 个字节组成。
UTF-8 有以下编码法规:
一旦多少个字节,最高位(第 8 位)为 0,表示那是四个 ASCII 字符(00 –
7F)。可以知道,全数 ASCII 编码已然是 UTF-8 了。
假诺一个字节,以 11 最初,接二连三的 1
的个数暗意这一个字符的字节数,比方:110xxxxx 意味着它是双字节 UTF-8
字符的首字节。
纵然三个字节,以 10
早先,表示它不是首字节,供给向前查找才干赢伏贴前字符的首字节

回页首

RandomAccessFile raf = new RandomAccessFile("D://1.txt","r");
System.out.print(raf.readLine());

Java代码  澳门新葡亰平台游戏网站 5

Java 中要求编码的场合

眼下描述了坐视不救的二种编码格式,上面将介绍 Java
中如哪个地方理对编码的支撑,什么场面中供给编码。

打字与印刷结果呈现乱码:

  1. String s = “那是一段普通话字符串”;   
  2.  byte[] b = s.getBytes(“UTF-8”);   
  3.  String n = new String(b,”UTF-8″);   
I/O 操作中存在的编码

我们掌握涉及到编码之处相像都在字符到字节恐怕字节到字符的转移上,而急需这种转移的现象重即使在
I/O 的时候,那几个 I/O 包罗磁盘 I/O 和网络 I/O,关于互联网 I/O
部分在前面将根本以 Web 应用为例介绍。下图是 Java 中管理 I/O 难点的接口:

澳门新葡亰平台游戏网站 6

Figure xxx. Requires a heading

Reader 类是 Java 的 I/O 中读字符的父类,而 InputStream
类是读字节的父类,InputStreamReader 类便是涉嫌字节到字符的桥梁,它担任在
I/O 进度中处理读取字节到字符的调换,而实际字节到字符的解码实现它由
StreamDecoder 去贯彻,在 StreamDecoder 解码进程中必得由顾客钦定 Charset
编码格式。值得注意的是假诺您未有一些名
Charset,将接收本地情状中的暗中认可字符集,比如在国语情况司令员使用 GBK
编码。
写的状态也是看似,字符的父类是 Writer,字节的父类是 OutputStream,通过
OutputStreamWriter 转换字符到字节。如下图所示:

澳门新葡亰平台游戏网站 7

Figure xxx. Requires a heading

平等 StreamEncoder
类担负将字符编码成字节,编码格式和私下认可编码法则与解码是千人一面的。
如上边一段代码,完毕了文本的读写功用:

清单 1.I/O 涉嫌的编码示例
String file = “c:/stream.txt”; String charset = “UTF-8”; //
写字符换转成字节流 FileOutputStream outputStream = new
FileOutputStream(fileState of Qatar; OutputStreamWriter writer = new
OutputStreamWriter( outputStream, charset卡塔尔(قطر‎; try {
writer.write(“那是要保存的华语字符”State of Qatar; } finally { writer.close(卡塔尔(قطر‎; } //
读取字节转变来字符 FileInputStream inputStream = new
FileInputStream(file卡塔尔(قطر‎; InputStreamReader reader = new InputStreamReader(
inputStream, charsetState of Qatar; StringBuffer buffer = new StringBuffer(卡塔尔国;
char[] buf = new char[64]; int count = 0; try { while ((count =
reader.read(buf)) != -1) { buffer.append(buffer, 0, count); } } finally
{ reader.close(); }

在大家的应用程序中提到到 I/O 操作时只要注意钦赐统一的编解码 Charset
字符集,日常不会产出乱码难点,有些应用程序要是不留意钦赐字符编码,汉语情况中取操作系统默许编码,倘若编解码都在中文意况中,日常也没难点,不过依旧总的来说的不建议利用操作系统的默许编码,因为如此,你的应用程序的编码格式就和平运动作条件绑定起来了,在跨境况下很恐怕现身乱码难题。
内部存款和储蓄器中操作中的编码
在 Java 开垦中除去 I/O
涉及到编码外,最常用的应当就是在内部存款和储蓄器中进行字符到字节的数据类型的转移,Java
中用 String 表示字符串,所以 String
类就提供转变来字节的形式,也支撑将字节调换为字符串的构造函数。如下代码示例:
String s = “那是一段中文字符串”; byte[] b = s.getBytes(“UTF-8″);
String n = new String(b,”UTF-8”);

其余二个是现已被被撇下的 ByteToCharConverter 和 CharToByteConverter
类,它们分别提供了 convertAll 方法能够达成 byte[] 和 char[]
的互转。如下代码所示:
ByteToCharConverter charConverter =
ByteToCharConverter.getConverter(“UTF-8”); char c[] =
charConverter.convertAll(byteArray); CharToByteConverter byteConverter =
CharToByteConverter.getConverter(“UTF-8”); byte[] b =
byteConverter.convertAll(c);

那八个类已经被 Charset 类取代,Charset 提供 encode 与 decode 分别对应
char[] 到 byte[] 的编码和 byte[] 到 char[]
的解码。如下代码所示:
Charset charset = Charset.forName(“UTF-8”); ByteBuffer byteBuffer =
charset.encode(string); CharBuffer charBuffer =
charset.decode(byteBuffer);

编码与解码都在三个类中做到,通过 forName
设置编解码字符集,那样更易于统一编码格式,比 ByteToCharConverter 和
CharToByteConverter 类更有益于。
Java 中还会有二个 ByteBuffer 类,它提供一种 char 和 byte
之间的软转变,它们之间转移无需编码与解码,只是把贰个 16bit 的 char
格式,拆分成为 2 个 8bit 的 byte
表示,它们的实际值并不曾被改变,仅仅是数据的等级次序做了转移。如下代码所以:
ByteBuffer heapByteBuffer = ByteBuffer.allocate(1024); ByteBuffer
byteBuffer = heapByteBuffer.putChar(c);

如上那个提供字符和字节之间的相互影响转变只要大家设置编解码格式统一平常都不会不能自已难题。

回页首

澳门新葡亰平台游戏网站 8

别的三个是一度被被丢弃的 ByteToCharConverter 和 CharToByteConverter
类,它们各自提供了 convertAll 方法能够兑现 byte[] 和 char[]
的互转。如下代码所示: 

Java 中怎么着编解码

前方介绍了三种不足为道的编码格式,这里将以实际例子介绍 Java
中怎么样贯彻编码及解码,下边大家以“I am 君山”这一个字符串为例介绍 Java
中如何把它以 ISO-8859-1、GB2312、GBK、UTF-16、UTF-8
编码格式举行编码的。
清单 2.String 编码

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 编码类图
![图 1. Java 编码类图](http://upload-images.jianshu.io/upload_images/1353074-54e5a4dd71448213.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)首先根据指定的 charsetName 通过 Charset.forName(charsetName) 设置 Charset 类,然后根据 Charset 创建 CharsetEncoder 对象,再调用 CharsetEncoder.encode 对字符串进行编码,不同的编码类型都会对应到一个类中,实际的编码过程是在这些类中完成的。下面是 String. getBytes(charsetName) 编码过程的时序图
图 2.Java 编码时序图
![图 2.Java 编码时序图](http://upload-images.jianshu.io/upload_images/1353074-6d3ab72f384f076d.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)从上图可以看出根据 charsetName 找到 Charset 类,然后根据这个字符集编码生成 CharsetEncoder,这个类是所有字符编码的父类,针对不同的字符编码集在其子类中定义了如何实现编码,有了 CharsetEncoder 对象后就可以调用 encode 方法去实现编码了。这个是 String.getBytes 编码方法,其它的如 StreamEncoder 中也是类似的方式。下面看看不同的字符集是如何将前面的字符串编码成 byte 数组的?
如字符串“I am 君山”的 char 数组为 49 20 61 6d 20 541b 5c71,下面把它按照不同的编码格式转化成相应的字节。
按照 ISO-8859-1 编码
字符串“I am 君山”用 ISO-8859-1 编码,下面是编码结果:
![Figure xxx. Requires a heading](http://upload-images.jianshu.io/upload_images/1353074-7a0132326aaeac15.gif?imageMogr2/auto-orient/strip)从上图看出 7 个 char 字符经过 ISO-8859-1 编码转变成 7 个 byte 数组,ISO-8859-1 是单字节编码,中文“君山”被转化成值是 3f 的 byte。3f 也就是“?”字符,所以经常会出现中文变成“?”很可能就是错误的使用了 ISO-8859-1 这个编码导致的。中文字符经过 ISO-8859-1 编码会丢失信息,通常我们称之为“黑洞”,它会把不认识的字符吸收掉。由于现在大部分基础的 Java 框架或系统默认的字符集编码都是 ISO-8859-1,所以很容易出现乱码问题,后面将会分析不同的乱码形式是怎么出现的。

按照 GB2312 编码
字符串“I am 君山”用 GB2312 编码,下面是编码结果:
![Figure xxx. Requires a heading](http://upload-images.jianshu.io/upload_images/1353074-bcda8d9da8857731.gif?imageMogr2/auto-orient/strip)GB2312 对应的 Charset 是 sun.nio.cs.ext. EUC_CN 而对应的 CharsetDecoder 编码类是 sun.nio.cs.ext. DoubleByte,GB2312 字符集有一个 char 到 byte 的码表,不同的字符编码就是查这个码表找到与每个字符的对应的字节,然后拼装成 byte 数组。查表的规则如下:
c2b[c2bIndex[char >> 8] + (char & 0xff)]

如果查到的码位值大于 oxff 则是双字节,否则是单字节。双字节高 8 位作为第一个字节,低 8 位作为第二个字节,如下代码所示:
if (bb > 0xff) { // DoubleByte if (dl - dp < 2) return CoderResult.OVERFLOW; da[dp++] = (byte) (bb >> 8); da[dp++] = (byte) bb; } else { // SingleByte if (dl - dp < 1) return CoderResult.OVERFLOW; da[dp++] = (byte) bb; }

从上图可以看出前 5 个字符经过编码后仍然是 5 个字节,而汉字被编码成双字节,在第一节中介绍到 GB2312 只支持 6763 个汉字,所以并不是所有汉字都能够用 GB2312 编码。

按照 GBK 编码
字符串“I am 君山”用 GBK 编码,下面是编码结果:
![Figure xxx. Requires a heading](http://upload-images.jianshu.io/upload_images/1353074-c96197f80c8a4f0e.gif?imageMogr2/auto-orient/strip)你可能已经发现上图与 GB2312 编码的结果是一样的,没错 GBK 与 GB2312 编码结果是一样的,由此可以得出 GBK 编码是兼容 GB2312 编码的,它们的编码算法也是一样的。不同的是它们的码表长度不一样,GBK 包含的汉字字符更多。所以只要是经过 GB2312 编码的汉字都可以用 GBK 进行解码,反过来则不然。

按照 UTF-16 编码
字符串“I am 君山”用 UTF-16 编码,下面是编码结果:
![Figure xxx. Requires a heading](http://upload-images.jianshu.io/upload_images/1353074-abcaac4c2357ba8a.gif?imageMogr2/auto-orient/strip)用 UTF-16 编码将 char 数组放大了一倍,单字节范围内的字符,在高位补 0 变成两个字节,中文字符也变成两个字节。从 UTF-16 编码规则来看,仅仅将字符的高位和地位进行拆分变成两个字节。特点是编码效率非常高,规则很简单,由于不同处理器对 2 字节处理方式不同,Big-endian(高位字节在前,低位字节在后)或 Little-endian(低位字节在前,高位字节在后)编码,所以在对一串字符串进行编码是需要指明到底是 Big-endian 还是 Little-endian,所以前面有两个字节用来保存 BYTE_ORDER_MARK 值,UTF-16 是用定长 16 位(2 字节)来表示的 UCS-2 或 Unicode 转换格式,通过代理对来访问 BMP 之外的字符编码。

按照 UTF-8 编码
字符串“I am 君山”用 UTF-8 编码,下面是编码结果:
![Figure xxx. Requires a heading](http://upload-images.jianshu.io/upload_images/1353074-e9b58f5eb5a11183.gif?imageMogr2/auto-orient/strip)UTF-16 虽然编码效率很高,但是对单字节范围内字符也放大了一倍,这无形也浪费了存储空间,另外 UTF-16 采用顺序编码,不能对单个字符的编码值进行校验,如果中间的一个字符码值损坏,后面的所有码值都将受影响。而 UTF-8 这些问题都不存在,UTF-8 对单字节范围内字符仍然用一个字节表示,对汉字采用三个字节表示。它的编码规则如下:

清单 3.UTF-8 编码代码片段
private CoderResult encodeArrayLoop(CharBuffer src, ByteBuffer dst){ char[] sa = src.array(); int sp = src.arrayOffset() + src.position(); int sl = src.arrayOffset() + src.limit(); byte[] da = dst.array(); int dp = dst.arrayOffset() + dst.position(); int dl = dst.arrayOffset() + dst.limit(); int dlASCII = dp + Math.min(sl - sp, dl - dp); // ASCII only loop while (dp < dlASCII && sa[sp] < 'u0080') da[dp++] = (byte) sa[sp++]; while (sp < sl) { char c = sa[sp]; if (c < 0x80) { // Have at most seven bits if (dp >= dl) return overflow(src, sp, dst, dp); da[dp++] = (byte)c; } else if (c < 0x800) { // 2 bytes, 11 bits if (dl - dp < 2) return overflow(src, sp, dst, dp); da[dp++] = (byte)(0xc0 | (c >> 6)); da[dp++] = (byte)(0x80 | (c & 0x3f)); } else if (Character.isSurrogate(c)) { // Have a surrogate pair if (sgp == null) sgp = new Surrogate.Parser(); int uc = sgp.parse(c, sa, sp, sl); if (uc < 0) { updatePositions(src, sp, dst, dp); return sgp.error(); } if (dl - dp < 4) return overflow(src, sp, dst, dp); da[dp++] = (byte)(0xf0 | ((uc >> 18))); da[dp++] = (byte)(0x80 | ((uc >> 12) & 0x3f)); da[dp++] = (byte)(0x80 | ((uc >> 6) & 0x3f)); da[dp++] = (byte)(0x80 | (uc & 0x3f)); sp++; // 2 chars } else { // 3 bytes, 16 bits if (dl - dp < 3) return overflow(src, sp, dst, dp); da[dp++] = (byte)(0xe0 | ((c >> 12))); da[dp++] = (byte)(0x80 | ((c >> 6) & 0x3f)); da[dp++] = (byte)(0x80 | (c & 0x3f)); } sp++; } updatePositions(src, sp, dst, dp); return CoderResult.UNDERFLOW; }

UTF-8 编码与 GBK 和 GB2312 不同,不用查码表,所以在编码效率上 UTF-8 的效率会更好,所以在存储中文字符时 UTF-8 编码比较理想。
几种编码格式的比较
对中文字符后面四种编码格式都能处理,GB2312 与 GBK 编码规则类似,但是 GBK 范围更大,它能处理所有汉字字符,所以 GB2312 与 GBK 比较应该选择 GBK。UTF-16 与 UTF-8 都是处理 Unicode 编码,它们的编码规则不太相同,相对来说 UTF-16 编码效率最高,字符到字节相互转换更简单,进行字符串操作也更好。它适合在本地磁盘和内存之间使用,可以进行字符和字节之间快速切换,如 Java 的内存编码就是采用 UTF-16 编码。但是它不适合在网络之间传输,因为网络传输容易损坏字节流,一旦字节流损坏将很难恢复,想比较而言 UTF-8 更适合网络传输,对 ASCII 字符采用单字节存储,另外单个字符损坏也不会影响后面其它字符,在编码效率上介于 GBK 和 UTF-16 之间,所以 UTF-8 在编码效率上和编码安全性上做了平衡,是理想的中文编码方式。

[回页首](http://www.ibm.com/developerworks/cn/java/j-lo-chinesecoding/#ibm-pcon)
Java Web 涉及到的编码
对于使用中文来说,有 I/O 的地方就会涉及到编码,前面已经提到了 I/O 操作会引起编码,而大部分 I/O 引起的乱码都是网络 I/O,因为现在几乎所有的应用程序都涉及到网络操作,而数据经过网络传输都是以字节为单位的,所以所有的数据都必须能够被序列化为字节。在 Java 中数据被序列化必须继承 Serializable 接口。
这里有一个问题,你是否认真考虑过一段文本它的实际大小应该怎么计算,我曾经碰到过一个问题:就是要想办法压缩 Cookie 大小,减少网络传输量,当时有选择不同的压缩算法,发现压缩后字符数是减少了,但是并没有减少字节数。所谓的压缩只是将多个单字节字符通过编码转变成一个多字节字符。减少的是 String.length(),而并没有减少最终的字节数。例如将“ab”两个字符通过某种编码转变成一个奇怪的字符,虽然字符数从两个变成一个,但是如果采用 UTF-8 编码这个奇怪的字符最后经过编码可能又会变成三个或更多的字节。同样的道理比如整型数字 1234567 如果当成字符来存储,采用 UTF-8 来编码占用 7 个 byte,采用 UTF-16 编码将会占用 14 个 byte,但是把它当成 int 型数字来存储只需要 4 个 byte 来存储。所以看一段文本的大小,看字符本身的长度是没有意义的,即使是一样的字符采用不同的编码最终存储的大小也会不同,所以从字符到字节一定要看编码类型。
另外一个问题,你是否考虑过,当我们在电脑中某个文本编辑器里输入某个汉字时,它到底是怎么表示的?我们知道,计算机里所有的信息都是以 01 表示的,那么一个汉字,它到底是多少个 0 和 1 呢?我们能够看到的汉字都是以字符形式出现的,例如在 Java 中“淘宝”两个字符,它在计算机中的数值 10 进制是 28120 和 23453,16 进制是 6bd8 和 5d9d,也就是这两个字符是由这两个数字唯一表示的。Java 中一个 char 是 16 个 bit 相当于两个字节,所以两个汉字用 char 表示在内存中占用相当于四个字节的空间。
这两个问题搞清楚后,我们看一下 Java Web 中那些地方可能会存在编码转换?
用户从浏览器端发起一个 HTTP 请求,需要存在编码的地方是 URL、Cookie、Parameter。服务器端接受到 HTTP 请求后要解析 HTTP 协议,其中 URI、Cookie 和 POST 表单参数需要解码,服务器端可能还需要读取数据库中的数据,本地或网络中其它地方的文本文件,这些数据都可能存在编码问题,当 Servlet 处理完所有请求的数据后,需要将这些数据再编码通过 Socket 发送到用户请求的浏览器里,再经过浏览器解码成为文本。这些过程如下图所示:
图 3. 一次 HTTP 请求的编码示例([查看大图](http://www.ibm.com/developerworks/cn/java/j-lo-chinesecoding/image021-lg.png))
![图 3. 一次 HTTP 请求的编码示例](http://upload-images.jianshu.io/upload_images/1353074-5cc89d7aabacf5e5.gif?imageMogr2/auto-orient/strip)如上图所示一次 HTTP 请求设计到很多地方需要编解码,它们编解码的规则是什么?下面将会重点阐述一下:
URL 的编解码
用户提交一个 URL,这个 URL 中可能存在中文,因此需要编码,如何对这个 URL 进行编码?根据什么规则来编码?有如何来解码?如下图一个 URL:
图 4.URL 的几个组成部分
![图 4.URL 的几个组成部分](http://upload-images.jianshu.io/upload_images/1353074-ba5727338a198b9c.gif?imageMogr2/auto-orient/strip)上图中以 Tomcat 作为 Servlet Engine 为例,它们分别对应到下面这些配置文件中:
Port 对应在 Tomcat 的 <Connector port="8080"/> 中配置,而 Context Path 在 <Context path="/examples"/> 中配置,Servlet Path 在 Web 应用的 web.xml 中的
<servlet-mapping> <servlet-name>junshanExample</servlet-name> <url-pattern>/servlets/servlet/*</url-pattern> </servlet-mapping>

<url-pattern> 中配置,PathInfo 是我们请求的具体的 Servlet,QueryString 是要传递的参数,注意这里是在浏览器里直接输入 URL 所以是通过 Get 方法请求的,如果是 POST 方法请求的话,QueryString 将通过表单方式提交到服务器端,这个将在后面再介绍。
上图中 PathInfo 和 QueryString 出现了中文,当我们在浏览器中直接输入这个 URL 时,在浏览器端和服务端会如何编码和解析这个 URL 呢?为了验证浏览器是怎么编码 URL 的我们选择 FireFox 浏览器并通过 HTTPFox 插件观察我们请求的 URL 的实际的内容,以下是 URL:HTTP://localhost:8080/examples/servlets/servlet/ 君山 ?author= 君山在中文 FireFox3.6.12 的测试结果
图 5. HTTPFox 的测试结果
![图 5. HTTPFox 的测试结果](http://upload-images.jianshu.io/upload_images/1353074-ec1ec9a1afcb20ad.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)君山的编码结果分别是:e5 90 9b e5 b1 b1,be fd c9 bd,查阅上一届的编码可知,PathInfo 是 UTF-8 编码而 QueryString 是经过 GBK 编码,至于为什么会有“%”?查阅 URL 的编码规范 RFC3986 可知浏览器编码 URL 是将非 ASCII 字符按照某种编码格式编码成 16 进制数字然后将每个 16 进制表示的字节前加上“%”,所以最终的 URL 就成了上图的格式了。
默认情况下中文 IE 最终的编码结果也是一样的,不过 IE 浏览器可以修改 URL 的编码格式在选项 -> 高级 -> 国际里面的发送 UTF-8 URL 选项可以取消。
从上面测试结果可知浏览器对 PathInfo 和 QueryString 的编码是不一样的,不同浏览器对 PathInfo 也可能不一样,这就对服务器的解码造成很大的困难,下面我们以 Tomcat 为例看一下,Tomcat 接受到这个 URL 是如何解码的。
解析请求的 URL 是在 org.apache.coyote.HTTP11.InternalInputBuffer 的 parseRequestLine 方法中,这个方法把传过来的 URL 的 byte[] 设置到 org.apache.coyote.Request 的相应的属性中。这里的 URL 仍然是 byte 格式,转成 char 是在 org.apache.catalina.connector.CoyoteAdapter 的 convertURI 方法中完成的:
protected void convertURI(MessageBytes uri, Request request) throws Exception { ByteChunk bc = uri.getByteChunk(); int length = bc.getLength(); CharChunk cc = uri.getCharChunk(); cc.allocate(length, -1); String enc = connector.getURIEncoding(); if (enc != null) { B2CConverter conv = request.getURIConverter(); try { if (conv == null) { conv = new B2CConverter(enc); request.setURIConverter(conv); } } catch (IOException e) {...} if (conv != null) { try { conv.convert(bc, cc, cc.getBuffer().length - cc.getEnd()); uri.setChars(cc.getBuffer(), cc.getStart(), cc.getLength()); return; } catch (IOException e) {...} } } // Default encoding: fast conversion byte[] bbuf = bc.getBuffer(); char[] cbuf = cc.getBuffer(); int start = bc.getStart(); for (int i = 0; i < length; i++) { cbuf[i] = (char) (bbuf[i + start] & 0xff); } uri.setChars(cbuf, 0, length); }

从上面的代码中可以知道对 URL 的 URI 部分进行解码的字符集是在 connector 的 <Connector URIEncoding=”UTF-8”/> 中定义的,如果没有定义,那么将以默认编码 ISO-8859-1 解析。所以如果有中文 URL 时最好把 URIEncoding 设置成 UTF-8 编码。
QueryString 又如何解析? GET 方式 HTTP 请求的 QueryString 与 POST 方式 HTTP 请求的表单参数都是作为 Parameters 保存,都是通过 request.getParameter 获取参数值。对它们的解码是在 request.getParameter 方法第一次被调用时进行的。request.getParameter 方法被调用时将会调用 org.apache.catalina.connector.Request 的 parseParameters 方法。这个方法将会对 GET 和 POST 方式传递的参数进行解码,但是它们的解码字符集有可能不一样。POST 表单的解码将在后面介绍,QueryString 的解码字符集是在哪定义的呢?它本身是通过 HTTP 的 Header 传到服务端的,并且也在 URL 中,是否和 URI 的解码字符集一样呢?从前面浏览器对 PathInfo 和 QueryString 的编码采取不同的编码格式不同可以猜测到解码字符集肯定也不会是一致的。的确是这样 QueryString 的解码字符集要么是 Header 中 ContentType 中定义的 Charset 要么就是默认的 ISO-8859-1,要使用 ContentType 中定义的编码就要设置 connector 的 <Connector URIEncoding=”UTF-8” useBodyEncodingForURI=”true”/> 中的 useBodyEncodingForURI 设置为 true。这个配置项的名字有点让人产生混淆,它并不是对整个 URI 都采用 BodyEncoding 进行解码而仅仅是对 QueryString 使用 BodyEncoding 解码,这一点还要特别注意。
从上面的 URL 编码和解码过程来看,比较复杂,而且编码和解码并不是我们在应用程序中能完全控制的,所以在我们的应用程序中应该尽量避免在 URL 中使用非 ASCII 字符,不然很可能会碰到乱码问题,当然在我们的服务器端最好设置 <Connector/> 中的 URIEncoding 和 useBodyEncodingForURI 两个参数。
HTTP Header 的编解码
当客户端发起一个 HTTP 请求除了上面的 URL 外还可能会在 Header 中传递其它参数如 Cookie、redirectPath 等,这些用户设置的值很可能也会存在编码问题,Tomcat 对它们又是怎么解码的呢?
对 Header 中的项进行解码也是在调用 request.getHeader 是进行的,如果请求的 Header 项没有解码则调用 MessageBytes 的 toString 方法,这个方法将从 byte 到 char 的转化使用的默认编码也是 ISO-8859-1,而我们也不能设置 Header 的其它解码格式,所以如果你设置 Header 中有非 ASCII 字符解码肯定会有乱码。
我们在添加 Header 时也是同样的道理,不要在 Header 中传递非 ASCII 字符,如果一定要传递的话,我们可以先将这些字符用 org.apache.catalina.util.URLEncoder 编码然后再添加到 Header 中,这样在浏览器到服务器的传递过程中就不会丢失信息了,如果我们要访问这些项时再按照相应的字符集解码就好了。
POST 表单的编解码
在前面提到了 POST 表单提交的参数的解码是在第一次调用 request.getParameter 发生的,POST 表单参数传递方式与 QueryString 不同,它是通过 HTTP 的 BODY 传递到服务端的。当我们在页面上点击 submit 按钮时浏览器首先将根据 ContentType 的 Charset 编码格式对表单填的参数进行编码然后提交到服务器端,在服务器端同样也是用 ContentType 中字符集进行解码。所以通过 POST 表单提交的参数一般不会出现问题,而且这个字符集编码是我们自己设置的,可以通过 request.setCharacterEncoding(charset) 来设置。
另外针对 multipart/form-data 类型的参数,也就是上传的文件编码同样也是使用 ContentType 定义的字符集编码,值得注意的地方是上传文件是用字节流的方式传输到服务器的本地临时目录,这个过程并没有涉及到字符编码,而真正编码是在将文件内容添加到 parameters 中,如果用这个编码不能编码时将会用默认编码 ISO-8859-1 来编码。
HTTP BODY 的编解码
当用户请求的资源已经成功获取后,这些内容将通过 Response 返回给客户端浏览器,这个过程先要经过编码再到浏览器进行解码。这个过程的编解码字符集可以通过 response.setCharacterEncoding 来设置,它将会覆盖 request.getCharacterEncoding 的值,并且通过 Header 的 Content-Type 返回客户端,浏览器接受到返回的 socket 流时将通过 Content-Type 的 charset 来解码,如果返回的 HTTP Header 中 Content-Type 没有设置 charset,那么浏览器将根据 Html 的 <meta HTTP-equiv="Content-Type" content="text/html; charset=GBK" /> 中的 charset 来解码。如果也没有定义的话,那么浏览器将使用默认的编码来解码。
其它需要编码的地方
除了 URL 和参数编码问题外,在服务端还有很多地方可能存在编码,如可能需要读取 xml、velocity 模版引擎、JSP 或者从数据库读取数据等。
xml 文件可以通过设置头来制定编码格式
<?xml version="1.0" encoding="UTF-8"?>

Velocity 模版设置编码格式:
services.VelocityService.input.encoding=UTF-8

JSP 设置编码格式:
<%@page contentType="text/html; charset=UTF-8"%>

访问数据库都是通过客户端 JDBC 驱动来完成,用 JDBC 来存取数据要和数据的内置编码保持一致,可以通过设置 JDBC URL 来制定如 MySQL:url="jdbc:mysql://localhost:3306/DB?useUnicode=true&characterEncoding=GBK"。 

[回页首](http://www.ibm.com/developerworks/cn/java/j-lo-chinesecoding/#ibm-pcon)
常见问题分析
在了解了 Java Web 中可能需要编码的地方后,下面看一下,当我们碰到一些乱码时,应该怎么处理这些问题?出现乱码问题唯一的原因都是在 char 到 byte 或 byte 到 char 转换中编码和解码的字符集不一致导致的,由于往往一次操作涉及到多次编解码,所以出现乱码时很难查找到底是哪个环节出现了问题,下面就几种常见的现象进行分析。
中文变成了看不懂的字符
例如,字符串“淘!我喜欢!”变成了“Ì Ô £ ¡Î Ò Ï²»¶ £ ¡”编码过程如下图所示
![Figure xxx. Requires a heading](http://upload-images.jianshu.io/upload_images/1353074-094a1b82207deb56.gif?imageMogr2/auto-orient/strip)字符串在解码时所用的字符集与编码字符集不一致导致汉字变成了看不懂的乱码,而且是一个汉字字符变成两个乱码字符。

一个汉字变成一个问号
例如,字符串“淘!我喜欢!”变成了“??????”编码过程如下图所示
![Figure xxx. Requires a heading](http://upload-images.jianshu.io/upload_images/1353074-c99544f8b243936b.gif?imageMogr2/auto-orient/strip)将中文和中文符号经过不支持中文的 ISO-8859-1 编码后,所有字符变成了“?”,这是因为用 ISO-8859-1 进行编解码时遇到不在码值范围内的字符时统一用 3f 表示,这也就是通常所说的“黑洞”,所有 ISO-8859-1 不认识的字符都变成了“?”。

一个汉字变成两个问号
例如,字符串“淘!我喜欢!”变成了“????????????”编码过程如下图所示
![Figure xxx. Requires a heading](http://upload-images.jianshu.io/upload_images/1353074-fe773d27b9119422.gif?imageMogr2/auto-orient/strip)这种情况比较复杂,中文经过多次编码,但是其中有一次编码或者解码不对仍然会出现中文字符变成“?”现象,出现这种情况要仔细查看中间的编码环节,找出出现编码错误的地方。

一种不正常的正确编码
还有一种情况是在我们通过 request.getParameter 获取参数值时,当我们直接调用
String value = request.getParameter(name);

会出现乱码,但是如果用下面的方式
String value = String(request.getParameter(name).getBytes(" ISO-8859-1"), "GBK"); 

解析时取得的 value 会是正确的汉字字符,这种情况是怎么造成的呢?
看下如所示:
![Figure xxx. Requires a heading](http://upload-images.jianshu.io/upload_images/1353074-8d1cfc3d23d9a990.gif?imageMogr2/auto-orient/strip)这种情况是这样的,ISO-8859-1 字符集的编码范围是 0000-00FF,正好和一个字节的编码范围相对应。这种特性保证了使用 ISO-8859-1 进行编码和解码可以保持编码数值“不变”。虽然中文字符在经过网络传输时,被错误地“拆”成了两个欧洲字符,但由于输出时也是用 ISO-8859-1,结果被“拆”开的中文字的两半又被合并在一起,从而又刚好组成了一个正确的汉字。虽然最终能取得正确的汉字,但是还是不建议用这种不正常的方式取得参数值,因为这中间增加了一次额外的编码与解码,这种情况出现乱码时因为 Tomcat 的配置文件中 useBodyEncodingForURI 配置项没有设置为”true”,从而造成第一次解析式用 ISO-8859-1 来解析才造成乱码的。

[回页首](http://www.ibm.com/developerworks/cn/java/j-lo-chinesecoding/#ibm-pcon)

总结
本文首先总结了几种常见编码格式的区别,然后介绍了支持中文的几种编码格式,并比较了它们的使用场景。接着介绍了 Java 那些地方会涉及到编码问题,已经 Java 中如何对编码的支持。并以网络 I/O 为例重点介绍了 HTTP 请求中的存在编码的地方,以及 Tomcat 对 HTTP 协议的解析,最后分析了我们平常遇到的乱码问题出现的原因。
综上所述,要解决中文问题,首先要搞清楚哪些地方会引起字符到字节的编码以及字节到字符的解码,最常见的地方就是读取会存储数据到磁盘,或者数据要经过网络传输。然后针对这些地方搞清楚操作这些数据的框架的或系统是如何控制编码的,正确设置编码格式,避免使用软件默认的或者是操作系统平台默认的编码格式。

在网络查询到插手相关编码解码操作后得以消除该难点

Java代码  澳门新葡亰平台游戏网站 9

RandomAccessFile raf = new RandomAccessFile("D://1.txt","r");
System.out.print(new String(raf.readLine().getBytes("ISO-8859-1"),"gbk"));
  1. ByteToCharConverter charConverter = ByteToCharConverter.getConverter(“UTF-8”);   
  2. char c[] = charConverter.convertAll(byteArray);   
  3. CharToByteConverter byteConverter = CharToByteConverter.getConverter(“UTF-8”);   
  4. byte[] b = byteConverter.convertAll(c);   

澳门新葡亰平台游戏网站 10

那三个类已经被 Charset 类代替,Charset 提供 encode 与 decode 分别对应
char[] 到 byte[] 的编码和 byte[] 到 char[]
的解码。如下代码所示: 

问题:

Java代码  澳门新葡亰平台游戏网站 11

在这里个进程中发出了怎么?

  1. Charset charset = Charset.forName(“UTF-8”);   
  2.  ByteBuffer byteBuffer = charset.encode(string);   
  3.  CharBuffer charBuffer = charset.decode(byteBuffer);   

要解答这么些主题材料首先要明了编码和平解决码的定义以至发生的来头:

编码与解码都在八个类中成功,通过 forName
设置编解码字符集,那样更易于统一编码格式,比 ByteToCharConverter 和
CharToByteConverter 类更便利。 
Java 中还会有多个 ByteBuffer 类,它提供一种 char 和 byte
之间的软调换,它们之间转移不供给编码与解码,只是把二个 16bit 的 char
格式,拆分成为 2 个 8bit 的 byte
表示,它们的实际值并从未被涂改,仅仅是数额的体系做了转移。如下代码所以: 

为什么要编码

Java代码  澳门新葡亰平台游戏网站 12

不清楚我们有未有想过八个标题,那就是为啥要编码?大家能或一定要编码?要回答那几个主题材料务要求重回计算机是何许表示大家人类可以领悟的符号的,这几个标识约等于我们人类采纳的语言。由于人类的语言有太多,由此表示那个语言的暗记太多,不可能用Computer中三个宗旨的存款和储蓄单元——
byte
来代表,因而一定要通过拆分或一些翻译专业,才干让计算机能分晓。大家能够把计算机能够领略的言语假定为土耳其语,其它语言要力所能致在计算机中使用必得通过一遍翻译,把它翻译成西班牙语。这几个翻译的历程正是编码。所以能够想像若是否说法文的国度要能力所能达到选取计算机就一定要通过编码。那看起来有个别霸道,但是那便是现
状,那也和咱们国家以往在大力推广普通话同样,希望任何国家都会说中文,今后其它的语言都翻译成中文,我们得以把Computer中存款和储蓄音讯的小不点儿单位改成汉字,这样
大家就不设有编码难点了。

  1. ByteBuffer heapByteBuffer = ByteBuffer.allocate(1024);   
  2.  ByteBuffer byteBuffer = heapByteBuffer.putChar(c);   

由此一句话来讲,编码的原故可以总计为:

以上这一个提供字符和字节之间的人机联作调换只要大家设置编解码格式统一平日都不会并发难题。 
Java 中怎么着编解码 
前方介绍了二种普及的编码格式,这里将以实际例子介绍 Java
中怎样兑现编码及解码,下边大家以“I am 君山”那些字符串为例介绍 Java
中怎样把它以 ISO-8859-1、GB2312、GBK、UTF-16、UTF-8
编码格式进行编码的。 

  1. 微型机中蕴藏信息的超小单元是一个字节即 8 个
    bit,所以能表示的字符范围是 0~255 个。

  2. 人类要代表的标识太多,无法用二个字节来完全意味着。

  3. 要解决那个冲突必须需求叁个新的数据布局 char,从 char 到 byte
    必得编码。

Java代码  澳门新葡亰平台游戏网站 13

名词解释:

  1. public static void encode() {   
  2.         String name = “I am 君山”;   
  3.         toHex(name.toCharArray());   
  4.         try {   
  5.             byte[] iso8859 = name.getBytes(“ISO-8859-1”);   
  6.             toHex(iso8859);   
  7.             byte[] gb2312 = name.getBytes(“GB2312”);   
  8.             toHex(gb2312);   
  9.             byte[] gbk = name.getBytes(“GBK”);   
  10.             toHex(gbk);   
  11.             byte[] utf16 = name.getBytes(“UTF-16”);   
  12.             toHex(utf16);   
  13.             byte[] utf8 = name.getBytes(“UTF-8”);   
  14.             toHex(utf8);   
  15.         } catch (UnsupportedEncodingException e) {   
  16.             e.printStackTrace();   
  17.         }   
  18.  }   

解码:将byte数组转为char数组。

我们把 name 字符串依照后面说的两种编码格式实行编码转形成 byte
数组,然后以 16 进制输出,大家先看一下 Java 是怎么样进展编码的。 
上面是 Java 中编码须求利用的类图 

编码:将char数组转为byte数组。

图 1. Java 编码类图 
澳门新葡亰平台游戏网站 14 
首先依照内定的 charsetName 通过 Charset.forName(charsetNameState of Qatar 设置
Charset 类,然后依据 Charset 创设 CharsetEncoder 对象,再调用
CharsetEncoder.encode
对字符串实行编码,差异的编码类型都会对应到叁个类中,实际的编码进度是在这里些类中成就的。上面是
String. getBytes(charsetName卡塔尔国 编码进程的时序图 

微微型机存款和储蓄的主干单位是byte,但张开四个文件时文件编辑器已经做理解码的劳作。

图 2.Java 编码时序图 
澳门新葡亰平台游戏网站 15 
从上航海用体育地方能够看见依据 charsetName 找到 Charset
类,然后依照这些字符集编码生成
CharsetEncoder,那个类是全体字符编码的父类,针对差异的字符编码集在其子类中定义了什么样达成编码,有了
CharsetEncoder 对象后就能够调用 encode 方法去落到实处编码了。那个是
String.getBytes 编码方法,其余的如 StreamEncoder
中也是近乎的章程。上边看看分化的字符集是怎么将眼下的字符串编码成 byte
数组的? 
如字符串“I am 君山”的 char 数组为 49 20 61 6d 20 541b
5c71,下边把它遵照分化的编码格式转形成相应的字节。 
按照 ISO-8859-1 编码 
字符串“I am 君山”用 ISO-8859-1 编码,下边是编码结果: 
澳门新葡亰平台游戏网站 16 
从上海体育场面看出 7 个 char 字符经过 ISO-8859-1 编码转换成 7 个 byte
数组,ISO-8859-1 是单字节编码,中文“君山”被转变成值是 3f 的 byte。3f
也正是“?”字符,所以平时会不由自主中文形成“?”很大概正是谬误的使用了
ISO-8859-1 这几个编码引致的。汉语字符经过 ISO-8859-1
编码会扬弃音信,经常大家称为“黑洞”,它会把不认得的字符摄取掉。由于将来繁多功底的
Java 框架或连串暗中同意的字符集编码都以ISO-8859-1,所以相当的轻便现身乱码难点,前边将会深入分析不一样的乱码格局是怎么现身的。 
按照 GB2312 编码 
字符串“I am 君山”用 GB2312 编码,下边是编码结果: 
澳门新葡亰平台游戏网站 17 
GB2312 对应的 Charset 是 sun.nio.cs.ext. EUC_CN 而相应的 CharsetDecoder
编码类是 sun.nio.cs.ext. DoubleByte,GB2312 字符集有二个 char 到 byte
的码表,不一样的字符编码就是查这几个码表找到与各类字符的对应的字节,然后拼装成
byte 数组。查表的家有家规如下: 

以下为解码进度描述

Java代码  澳门新葡亰平台游戏网站 18

澳门新葡亰平台游戏网站 19

  1. c2b[c2bIndex[char >> 8] + (char & 0xff)]   

文本实际存款和储蓄的原委是(以下为 16 进制):

一经查到的码位值大于 oxff 则是双字节,不然是单字节。双字节高 8
位作为第三个字节,低 8 位作为第一个字节,如下代码所示: 

澳门新葡亰平台游戏网站 20

Java代码  澳门新葡亰平台游戏网站 21

张开文件后看见的内容为

  1. if (bb > 0xff) {    // DoubleByte   
  2.             if (dl – dp < 2)   
  3.                 return CoderResult.OVERFLOW;   
  4.             da[dp++] = (byte) (bb >> 8);   
  5.             da[dp++] = (byte) bb;   
  6.  } else {                      // SingleByte   
  7.             if (dl – dp < 1)   
  8.                 return CoderResult.OVERFLOW;   
  9.             da[dp++] = (byte) bb;   
  10.  }   

澳门新葡亰平台游戏网站 22

从上图能够见见前 5 个字符经过编码后仍是 5
个字节,而汉字被编码成双字节,在率先节中介绍到 GB2312 只帮助 67六二十个汉字,所以并非颇具汉字都能够用 GB2312 编码。 
按照 GBK 编码 
字符串“I am 君山”用 GBK 编码,上边是编码结果: 
澳门新葡亰平台游戏网站 23 
你也许曾经意识上航海用图书馆与 GB2312 编码的结果是同等的,没错 GBK 与 GB2312
编码结果是平等的,由此能够吸取 GBK 编码是非常 GB2312
编码的,它们的编码算法也是一模一样的。差别的是它们的码表长度不平等,GBK
包含的方块字字符越来越多。所以只假若因而 GB2312 编码的汉字都能够用 GBK
进行解码,反过来则不然。 
按照 UTF-16 编码 
字符串“I am 君山”用 UTF-16 编码,下边是编码结果: 
澳门新葡亰平台游戏网站 24 
用 UTF-16 编码将 char 数组放大了一倍,单字节范围内的字符,在高位补 0
产生几个字节,汉语字符也化为多少个字节。从 UTF-16
编码法规来看,仅仅将字符的要职和地点实行拆分产生八个字节。特点是编码成效相当的高,法则很简短,由于分裂计算机对
2 字节管理方式分裂,Big-endian(高位字节在前,低位字节在后)或
Little-endian(低位字节在前,高位字节在后)编码,所以在对一串字符串实行编码是索要指明到底是
Big-endian 照旧 Little-endian,所在此以前面有八个字节用来保存
BYTE_ORDER_MA奥迪Q5K 值,UTF-16 是用定长 16 位(2 字节)来代表的 UCS-2 或
Unicode 转变格式,通过代办对来访问 BMP 之外的字符编码。 
按照 UTF-8 编码 
字符串“I am 君山”用 UTF-8 编码,上边是编码结果: 
澳门新葡亰平台游戏网站 25 
UTF-16
就算编码效用相当的高,不过对单字节范围内字符也推广了一倍,那无形也浪费了蕴藏空间,其余UTF-16
选择顺序编码,不能够对单个字符的编码值进行校验,若是中间的四个字符码值损坏,后边的装有码值都将受影响。而
UTF-8 那么些主题素材都海市蜃楼,UTF-8
对单字节范围内字符依然用叁个字节表示,对汉字使用几个字节表示。它的编码法规如下: 

急需详细表明以下代码的管理进程

Java代码  澳门新葡亰平台游戏网站 26

RandomAccessFile raf= new RandomAccessFile("D://1.txt","r");
System.out.print(raf.readLine());
  1. private CoderResult encodeArrayLoop(CharBuffer src,   
  2.  ByteBuffer dst){   
  3.             char[] sa = src.array();   
  4.             int sp = src.arrayOffset() + src.position();   
  5.             int sl = src.arrayOffset() + src.limit();   
  6.             byte[] da = dst.array();   
  7.             int dp = dst.arrayOffset() + dst.position();   
  8.             int dl = dst.arrayOffset() + dst.limit();   
  9.             int dlASCII = dp + Math.min(sl – sp, dl – dp);   
  10.             // ASCII only loop   
  11.             while (dp < dlASCII && sa[sp] < ‘u0080’)   
  12.                 da[dp++] = (byte) sa[sp++];   
  13.             while (sp < sl) {   
  14.                 char c = sa[sp];   
  15.                 if (c < 0x80) {   
  16.                     // Have at most seven bits   
  17.                     if (dp >= dl)   
  18.                         return overflow(src, sp, dst, dp);   
  19.                     da[dp++] = (byte)c;   
  20.                 } else if (c < 0x800) {   
  21.                     // 2 bytes, 11 bits   
  22.                     if (dl – dp < 2)   
  23.                         return overflow(src, sp, dst, dp);   
  24.                     da[dp++] = (byte)(0xc0 | (c >> 6));   
  25.                     da[dp++] = (byte)(0x80 | (c & 0x3f));   
  26.                 } else if (Character.isSurrogate(c)) {   
  27.                     // Have a surrogate pair   
  28.                     if (sgp == null)   
  29.                         sgp = new Surrogate.Parser();   
  30.                     int uc = sgp.parse(c, sa, sp, sl);   
  31.                     if (uc < 0) {   
  32.                         updatePositions(src, sp, dst, dp);   
  33.                         return sgp.error();   
  34.                     }   
  35.                     if (dl – dp < 4)   
  36.                         return overflow(src, sp, dst, dp);   
  37.                     da[dp++] = (byte)(0xf0 | ((uc >> 18)));   
  38.                     da[dp++] = (byte)(0x80 | ((uc >> 12) & 0x3f));   
  39.                     da[dp++] = (byte)(0x80 | ((uc >>  6) & 0x3f));   
  40.                     da[dp++] = (byte)(0x80 | (uc & 0x3f));   
  41.                     sp++;  // 2 chars   
  42.                 } else {   
  43.                     // 3 bytes, 16 bits   
  44.                     if (dl – dp < 3)   
  45.                         return overflow(src, sp, dst, dp);   
  46.                     da[dp++] = (byte)(0xe0 | ((c >> 12)));   
  47.                     da[dp++] = (byte)(0x80 | ((c >>  6) & 0x3f));   
  48.                     da[dp++] = (byte)(0x80 | (c & 0x3f));   
  49.                 }   
  50.                 sp++;   
  51.             }   
  52.             updatePositions(src, sp, dst, dp);   
  53.             return CoderResult.UNDERFLOW;   
  54.  }   

率先看一下java.io.RandomAccessFile#readLine方法的源码

UTF-8 编码与 GBK 和 GB2312 不相同,不用查码表,所以在编码功用上 UTF-8
的频率会越来越好,所以在仓库储存中文字符时 UTF-8 编码比较非凡。 
三种编码格式的可比 
对汉语字符后边多种编码格式都能管理,GB2312 与 GBK 编码准绳相近,可是 GBK
范围越来越大,它能管理全部汉字字符,所以 GB2312 与 GBK 比较应该选用GBK。UTF-16 与 UTF-8 都是管理 Unicode
编码,它们的编码准绳不太雷同,相对来讲 UTF-16
编码效能最高,字符到字节相互转改动简短,举办字符串操作也越来越好。它切合在地点磁盘和内存之间利用,能够进行字符和字节之间异常的快切换,如
Java 的内部存储器编码正是使用 UTF-16
编码。可是它不相符在网络之间传输,因为网络传输轻便损坏字节流,一旦字节流损坏将很难恢复生机,想相比较而言UTF-8 更相符互连网传输,对 ASCII
字符采取单字节存款和储蓄,此外单个字符损坏也不会耳熏目染后面此外字符,在编码作用上介于
GBK 和 UTF-16 之间,所以 UTF-8
在编码效能上和编码安全性上做了平衡,是上佳的中文编码格局。 
Java Web 涉及到的编码 
对于使用中文来说,有 I/O 的地点就能涉及到编码,前边已经涉及了 I/O
操作会唤起编码,而大大多 I/O 引起的乱码都以互联网I/O,因为今后大概全体的应用程序都涉嫌到网络操作,而数据通过互联网传输都以以字节为单位的,所以具有的数据都必需能够被种类化为字节。在
Java 中数据被连串化必得世襲 塞里alizable 接口。 
这里有叁个主题素材,你是还是不是认真思索过一段文本它的实在尺寸应该怎么总计,笔者早已蒙受过七个标题:就是要想艺术压缩
Cookie
大小,收缩网络传输量,当时有选拔不一样的压缩算法,开掘压缩后字符数是压缩了,不过并从未减掉字节数。所谓的压缩只是将几个单字节字符通过编码调换成多个多字节字符。减弱的是
String.length(卡塔尔国,而并不曾滑坡最后的字节数。举例将“ab”八个字符通过某种编码调换成三个出人意料的字符,即使字符数从三个变为二个,可是一旦利用
UTF-8
编码这些意外的字符最终经过编码恐怕又会成为多少个或越来越多的字节。相同的道理例如整型数字
1234567 如若真是字符来存款和储蓄,采取 UTF-8 来编码占用 7 个 byte,选取 UTF-16
编码将会吞噬 14 个 byte,可是把它当成 int 型数字来存款和储蓄只须求 4 个 byte
来积累。所以看一段文本的深浅,看字符本身的长短是从未意思的,纵然是均等的字符接受差别的编码最后存款和储蓄的大小也会不一样,所以从字符到字节必看编码类型。 
别的叁个主题素材,你是或不是考虑过,当大家在Computer中有个别文本编辑器里输入有些汉字时,它到底是怎么表示的?大家知道,Computer里存有的音信都以以
01 表示的,那么二个汉字,它到底是有个别个 0 和 1
呢?大家能够看见的方块字都以以字符方式现身的,举例在 Java
中“天猫”七个字符,它在微管理机中的数值 10 进制是 28120 和 23453,16 进制是
6bd8 和 5d9d,也正是那五个字符是由那多少个数字独一代表的。Java 中一个 char
是 16 个 bit 相当于多少个字节,所以多少个汉字用 char
表示在内部存款和储蓄器中攻下也正是多少个字节的长空。 
那八个难题搞精晓后,大家看一下 Java Web
中那三个地点只怕会存在编码调换? 
顾客从浏览器端发起四个 HTTP 乞请,须要存在编码的地点是
UMuranoL、Cookie、Parameter。服务器端接收到 HTTP 央浼后要分析 HTTP 左券,当中UEscortI、库克ie 和 POST
表单参数须求解码,服务器端可能还亟需读取数据库中的数据,本地或互联网中其余地点的文本文件,那么些多少都恐怕存在编码难题,当
Servlet 管理完全数央浼的数额后,必要将这个数量再编码通过 Socket
发送到客户诉求的浏览器里,再经过浏览器解码成为文本。这么些进程如下图所示: 
澳门新葡亰平台游戏网站 27 
如上图所示一回 HTTP
须要设计到非常多地点供给编解码,它们编解码的法则是何等?上边将会首要阐释一下: 
U奥迪Q5L 的编解码 
顾客提交二个 U大切诺基L,那么些 ULX570L 中或然存在汉语,因而需求编码,怎样对这些 UENVISIONL
实行编码?根据什么法规来编码?有哪些来解码?如下图一个 U昂科拉L: 

public final String readLine() throws IOException {
        StringBuffer input = new StringBuffer();
        int c = -1;
        boolean eol = false;
        while (!eol) {
            switch (c = read()) {
            case -1:
            case '/n':
                eol = true;
                break;
            case '/r':
                eol = true;
                long cur = getFilePointer();
                if ((read()) != '/n') {
                    seek(cur);
                }
                break;
            default:
                input.append((char)c);
                break;
            }
        }
        if ((c == -1) && (input.length() == 0)) {
            return null;
        }
        return input.toString();
    }

图 4.UHavalL 的多少个组成都部队分 
澳门新葡亰平台游戏网站 28 
上海体育场合中以 汤姆cat 作为 Servlet Engine
为例,它们分别对应到上边这个布置文件中: 
Port 对应在 Tomcat 的 <Connector port=”8080″/> 中配置,而 Context
Path 在 <Context path=”/examples”/> 中配置,Servlet Path 在 Web
应用的 web.xml 中的 
<servlet-mapping> 
        <servlet-name>junshanExample</servlet-name> 
        <url-pattern>/servlets/servlet/*</url-pattern> 
</servlet-mapping> 

关键关注read(卡塔尔部分和(char卡塔尔国c,read(卡塔尔国是二个本土方法,功效是从文件中读取七个byte字节。

<url-pattern> 中配置,PathInfo 是我们诉求的现实性的
Servlet,QueryString 是要传送的参数,注意这里是在浏览器里直接输入 UTiguanL
所以是透过 Get 方法需要的,要是是 POST 方法央浼的话,QueryString
将通过表单情势交给到服务器端,那几个将要末端再介绍。 
上海教室中 PathInfo 和 QueryString 现身了汉语,当大家在浏览器中一贯输入这个UTucsonL 时,在浏览器端和劳务端会怎样编码和深入分析那么些 U福特ExplorerL
呢?为了印证浏览器是怎么编码 U牧马人L 的大家选用 FireFox 浏览器并经过 HTTPFox插件阅览大家伏乞的 U奥迪Q3L 的其实的内容,以下是
ULANDL:HTTP://localhost:8080/examples/servlets/servlet/ 君山 ?author=
君山在中文言 Fire福克斯3.6.12 的测量试验结果 
澳门新葡亰平台游戏网站 29 

澳门新葡亰平台游戏网站 30

(char卡塔尔(قطر‎c是将变量c从byte类型转变为char类型,那是三个解码操作。

 

标题:此处是以什么样格式进行解码?

转帖表达出处:

解码格式是ISO-8859-1

 

raf.readLine(卡塔尔(قطر‎的管理进度如下

澳门新葡亰平台游戏网站 31

那么

new String(raf.readLine().getBytes("ISO-8859-1"),"gbk")

那行代码做了什么样

率先readLine(卡塔尔国按行一字节一字节地读取文件中的数据,何况按ISO-8859-1开展解码拼成char数组,然后getBytes(“ISO-8859-1″卡塔尔(قطر‎对拼成后的char数组按ISO-8859-1拓宽编码获取byte数组,最终new
String(string,”gbk”卡塔尔对编码后的byte数组用gbk格式举行解码成char数组。

参谋资料: 深刻解析 Java
中的中文编码难点

遗留难点:

1 、怎么样防止再次转码。

2 、RandomAccessFile的readLine (卡塔尔国 功能超低,怎么着进步作用。

You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图