Java几种常见的编码格式Word文档下载推荐.docx
《Java几种常见的编码格式Word文档下载推荐.docx》由会员分享,可在线阅读,更多相关《Java几种常见的编码格式Word文档下载推荐.docx(18页珍藏版)》请在冰豆网上搜索。
GB18030
全称是《信息交换用汉字编码字符集》,是我国的强制标准,它可能是单字节、双字节或者四字节编码,它的编码与GB2312编码兼容,这个虽然是国家标准,但是实际应用系统中使用的并不广泛。
UTF-16
说到UTF必须要提到Unicode(UniversalCode统一码),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问题的接口:
Reader类是Java的I/O中读字符的父类,而InputStream类是读字节的父类,InputStreamReader类就是关联字节到字符的桥梁,它负责在I/O过程中处理读取字节到字符的转换,而具体字节到字符的解码实现它由StreamDecoder去实现,在StreamDecoder解码过程中必须由用户指定Charset编码格式。
值得注意的是如果你没有指定Charset,将使用本地环境中的默认字符集,例如在中文环境中将使用GBK编码。
写的情况也是类似,字符的父类是Writer,字节的父类是OutputStream,通过OutputStreamWriter转换字符到字节。
如下图所示:
同样StreamEncoder类负责将字符编码成字节,编码格式和默认编码规则与解码是一致的。
如下面一段代码,实现了文件的读写功能:
Java代码
1.String
file
=
"
c:
/stream.txt"
;
2.String
charset
UTF-8"
3.//
写字符换转成字节流
4.FileOutputStream
outputStream
new
FileOutputStream(file);
5.OutputStreamWriter
writer
OutputStreamWriter(
6.outputStream,
charset);
7.try
{
8.
writer.write("
这是要保存的中文字符"
);
9.}
finally
10.
writer.close();
11.}
12.//
读取字节转换成字符
13.FileInputStream
inputStream
FileInputStream(file);
14.InputStreamReader
reader
InputStreamReader(
15.inputStream,
16.StringBuffer
buffer
StringBuffer();
17.char[]
buf
char[64];
18.int
count
0;
19.try
20.
while
((count
reader.read(buf))
!
-1)
21.
buffer.append(buffer,
0,
count);
22.
}
23.}
24.
reader.close();
25.}
Stringfile="
Stringcharset="
//写字符换转成字节流
FileOutputStreamoutputStream=newFileOutputStream(file);
OutputStreamWriterwriter=newOutputStreamWriter(
outputStream,charset);
try{
writer.write("
}finally{
writer.close();
}
//读取字节转换成字符
FileInputStreaminputStream=newFileInputStream(file);
InputStreamReaderreader=newInputStreamReader(
inputStream,charset);
StringBufferbuffer=newStringBuffer();
char[]buf=newchar[64];
intcount=0;
while((count=reader.read(buf))!
=-1){
buffer.append(buffer,0,count);
reader.close();
在我们的应用程序中涉及到I/O操作时只要注意指定统一的编解码Charset字符集,一般不会出现乱码问题,有些应用程序如果不注意指定字符编码,中文环境中取操作系统默认编码,如果编解码都在中文环境中,通常也没问题,但是还是强烈的不建议使用操作系统的默认编码,因为这样,你的应用程序的编码格式就和运行环境绑定起来了,在跨环境下很可能出现乱码问题。
内存中操作中的编码
在Java开发中除了I/O涉及到编码外,最常用的应该就是在内存中进行字符到字节的数据类型的转换,Java中用String表示字符串,所以String类就提供转换到字节的方法,也支持将字节转换为字符串的构造函数。
如下代码示例:
s
这是一段中文字符串"
2.
byte[]
b
s.getBytes("
3.
String
n
String(b,"
Strings="
byte[]b=s.getBytes("
Stringn=newString(b,"
另外一个是已经被被废弃的ByteToCharConverter和CharToByteConverter类,它们分别提供了convertAll方法可以实现byte[]和char[]的互转。
如下代码所示:
1.ByteToCharConverter
charConverter
ByteToCharConverter.getConverter("
2.char
c[]
charConverter.convertAll(byteArray);
3.CharToByteConverter
byteConverter
CharToByteConverter.getConverter("
4.byte[]
byteConverter.convertAll(c);
ByteToCharConvertercharConverter=ByteToCharConverter.getConverter("
charc[]=charConverter.convertAll(byteArray);
CharToByteConverterbyteConverter=CharToByteConverter.getConverter("
byte[]b=byteConverter.convertAll(c);
这两个类已经被Charset类取代,Charset提供encode与decode分别对应char[]到byte[]的编码和byte[]到char[]的解码。
1.Charset
Charset.forName("
ByteBuffer
byteBuffer
charset.encode(string);
CharBuffer
charBuffer
charset.decode(byteBuffer);
Charsetcharset=Charset.forName("
ByteBufferbyteBuffer=charset.encode(string);
CharBuffercharBuffer=charset.decode(byteBuffer);
编码与解码都在一个类中完成,通过forName设置编解码字符集,这样更容易统一编码格式,比ByteToCharConverter和CharToByteConverter类更方便。
Java中还有一个ByteBuffer类,它提供一种char和byte之间的软转换,它们之间转换不需要编码与解码,只是把一个16bit的char格式,拆分成为2个8bit的byte表示,它们的实际值并没有被修改,仅仅是数据的类型做了转换。
如下代码所以:
1.ByteBuffer
heapByteBuffer
ByteBuffer.allocate(1024);
heapByteBuffer.putChar(c);
ByteBufferheapByteBuffer=ByteBuffer.allocate(1024);
ByteBufferbyteBuffer=heapByteBuffer.putChar(c);
以上这些提供字符和字节之间的相互转换只要我们设置编解码格式统一一般都不会出现问题。
Java中如何编解码
前面介绍了几种常见的编码格式,这里将以实际例子介绍Java中如何实现编码及解码,下面我们以“Iam君山”这个字符串为例介绍Java中如何把它以ISO-8859-1、GB2312、GBK、UTF-16、UTF-8编码格式进行编码的。
1.public
static
void
encode()
name
I
am
君山"
toHex(name.toCharArray());
4.
try
5.
iso8859
name.getBytes("
ISO-8859-1"
6.
toHex(iso8859);
7.
gb2312
GB2312"
toHex(gb2312);
9.
gbk
GBK"
toHex(gbk);
11.
utf16
UTF-16"
12.
toHex(utf16);
13.
utf8
14.
toHex(utf8);
15.
catch
(UnsupportedEncodingException
e)
16.
e.printStackTrace();
17.
18.
publicstaticvoidencode(){
Stringname="
Iam君山"
toHex(name.toCharArray());
byte[]iso8859=name.getBytes("
toHex(iso8859);
byte[]gb2312=name.getBytes("
toHex(gb2312);
byte[]gbk=name.getBytes("
toHex(gbk);
byte[]utf16=name.getBytes("
toHex(utf16);
byte[]utf8=name.getBytes("
toHex(utf8);
}catch(UnsupportedEncodingExceptione){
e.printStackTrace();
我们把name字符串按照前面说的几种编码格式进行编码转化成byte数组,然后以16进制输出,我们先看一下Java是如何进行编码的。
下面是Java中编码需要用到的类图
图1.Java编码类图
首先根据指定的charsetName通过Charset.forName(charsetName)设置Charset类,然后根据Charset创建CharsetEncoder对象,再调用CharsetEncoder.encode对字符串进行编码,不同的编码类型都会对应到一个类中,实际的编码过程是在这些类中完成的。
下面是String.getBytes(charsetName)编码过程的时序图
图2.Java编码时序图
从上图可以看出根据charsetName找到Charset类,然后根据这个字符集编码生成CharsetEncoder,这个类是所有字符编码的父类,针对不同的字符编码集在其子类中定义了如何实现编码,有了CharsetEncoder对象后就可以调用encode方法去实现编码了。
这个是String.getBytes编码方法,其它的如StreamEncoder中也是类似的方式。
下面看看不同的字符集是如何将前面的字符串编码成byte数组的?
如字符串“Iam君山”的char数组为4920616d20541b5c71,下面把它按照不同的编码格式转化成相应的字节。
按照ISO-8859-1编码
字符串“Iam君山”用ISO-8859-1编码,下面是编码结果:
从上图看出7个char字符经过ISO-8859-1编码转变成7个byte数组,ISO-8859-1是单字节编码,中文“君山”被转化成值是3f的byte。
3f也就是“?
”字符,所以经常会出现中文变成“?
”很可能就是错误的使用了ISO-8859-1这个编码导致的。
中文字符经过ISO-8859-1编码会丢失信息,通常我们称之为“黑洞”,它会把不认识的字符吸收掉。
由于现在大部分基础的Java框架或系统默认的字符集编码都是ISO-8859-1,所以很容易出现乱码问题,后面将会分析不同的乱码形式是怎么出现的。
按照GB2312编码
字符串“Iam君山”用GB2312编码,下面是编码结果:
GB2312对应的Charset是sun.nio.cs.ext.EUC_CN而对应的CharsetDecoder编码类是sun.nio.cs.ext.DoubleByte,GB2312字符集有一个char到byte的码表,不同的字符编码就是查这个码表找到与每个字符的对应的字节,然后拼装成byte数组。
查表的规则如下:
1.c2b[c2bIndex[char
>
8]
+
(char
&
0xff)]
c2b[c2bIndex[char>
8]+(char&
0xff)]
如果查到的码位值大于oxff则是双字节,否则是单字节。
双字节高8位作为第一个字节,低8位作为第二个字节,如下代码所示:
1.if
(bb
0xff)
//
DoubleByte
if
(dl
-
dp
<
2)
return
CoderResult.OVERFLOW;
da[dp++]
(byte)
8);
bb;
else
SingleByte
1)
if(bb>
0xff){//DoubleByte
if(dl-dp<
2)
returnCoderResult.OVERFLOW;
da[dp++]=(byte)(bb>
8);