参考文献:Java中文处理学习笔记——Hello Unicode
http://www.chedong.com/tech/
1.字符集的准备知识:
ISO-8859-1 GB2312 BIG5 GBK GB18030 UNICODE 为什么会有这么多字符集编码方式?
假设一个字符就是棋盘上的一个棋子,有其固定的坐标,如果需要区别所有的字符,就需要有足够的棋格容纳不同的“字符”。
英文和欧洲其他语言的单字节字符集(SingleByte Charsets):
首先对于ISO-8859系列的字符集都想象成一个:2^8 = 16 * 16 = 256个格子的棋盘,这样所有的西文字符(英文)用这样一个16×16的坐标系就基本可以覆盖全了。而英文实际上只用其中小于128(/x80)的部分就够了。利用大于128部分的空间的不同定义规则形成了真对其他欧洲语言的扩展字符集:ISO-8859-2 ISO-8859-4等……
ISO-8859-1 | ISO-8859-7 | 其他语言 | ||||||||||||
|
|
|
GB2312 BIG5 SJIS等多字节字符集(MultiByte Charsets):
对于亚洲语言来说:汉字这么多,用这么一个256格的小棋盘肯定放不下,所以要区别成千上万的汉字解决办法就是用2个字节(坐标)来定位一个“字”在棋盘上的位置,将以上规则做一个扩展:
- 如果第1个字符是小于128(/x80)的仍和英文字符集编码方式保持兼容;
- 如果第1个字符是大于128(/x80)的,就当成是汉字的第1个字节,这个自己和后面紧跟的1个字节组成一个汉字;
其结果相当于在位于128以上的小棋格里每个小棋格又划分出了一个16×16的小棋盘。这样一个棋盘中的格子数(可能容纳的字符数)就变成了128 + 128 * 256。按照类似的方式有了简体中文的GB2312标准,繁体中文的BIG5字符集和日文的SJIS字符集等,GB2312字符集包含大约有六仟多个常用简体汉字。
简体中文 | 日文SJIS | 繁体中文 | ||||||||||||||||||||||||||||||
|
|
|
由此可以看出,所有这些从ASCII扩展式的编码方式中:英文部分都是兼容的,但扩展部分的编码方式是不兼容的,虽然很多字在3种体系中写法一致(比如“中文”这2个字)但在相应字符集中的坐标不一致,所以GB2312编写的页面用BIG5看就变得面目全非了。而且有时候经常在浏览其他非英语国家的页面时(比如包含有德语的人名时)经常出现奇怪的汉字,其实就是扩展位的编码冲突造成的。
我把GBK和GB18030理解成一个小UNICODE:GBK字符集是GB2312的扩展(K),GBK里大约有贰万玖仟多个字符,除了保持和GB2312兼容外,繁体中文字,甚至连日文的假名字符也能显示。而GB18030-2000则是一个更复杂的字符集,采用变长字节的编码方式,能够支持更多的字符。关于汉字的编码方式比较详细的定义规范可以参考:
http://www.unihan.com.cn/cjk/ana17.htm
ASCII(英文) ==> 西欧文字 ==> 东欧字符集(俄文,希腊语等) ==> 东亚字符集(GB2312 BIG5 SJIS等)==> 扩展字符集GBK GB18030这个发展过程基本上也反映了字符集标准的发展过程,但这么随着时间的推移,尤其是互联网让跨语言的信息的交互变得越来越多的时候,太多多针对本地语言的编码标准的出现导致一个应用程序的国际化变得成本非常高。尤其是你要编写一个同时包含法文和简体中文的文档,这时候一般都会想到要是用一个通用的字符集能够显示所有语言的所有文字就好了,而且这样做应用也能够比较方便的国际化,为了达到这个目标,即使应用牺牲一些空间和程序效率也是非常值得的。UNICODE就是这样一个通用的解决方案。
UNICODE双字节字符集
所以你可以把UNICODE想象成这样:让所有的字符(包括英文)都用2个字节(2个8位)表示,这样就有了一个2^(8*2) = 256 * 256 = 65536个格子的大棋盘。在这个棋盘中,这样中(简繁)日韩(还包括越南)文字作为CJK字符集都放在一定的区位内,为了减少重复,各种语言中写法一样的字共享一个“棋格”。详细的区位见附录A
Unicode:(DoubleByte Charsets)
西 | C中 | 其 | |
欧 | J日 | 它 | |
英 | K韩 | 语 | |
文 | 言 |
什么还要有UTF-8?毕竟互联网70%以上的信息仍然是英文。如果连英文都用2个字节存取(UCS-2),空间浪费不就太多了?所谓UTF-8就是这样一个为了提高英文存取效率的字符集转换格式:Unicode Transformation Form 8-bit form。用UTF-8,UNICODE的2字节字符用变长个(1-3个字节)表示:
- 对英文,仍然和ASCII一样用1个字节表示,这个字节的值小于128(/x80);
- 对其他语言的用一个值位于128-256之间的字节开始,再加后面紧跟的2个字节表示,一个字符一共是3个字节;
因此,在应用中程序处理过程中所有字符都是16位(双字节),但在存取转换成字节流时使用UTF-8格式转换,对于英文字符来说和原来用ASCII方式存取时相比大小仍然是一样的,而对中文来说和原来的GB2312编码方式相比,大小为:(3字节/2字节)=1.5倍
2.试验1:操作系统语言环境设置对Java应用缺省编码方式的影响
package test1;
import java.util.*;
import java.text.*;
public class Env
{
public static void main(String[] args)
{
System.out.println( "Hello, it's " + new Date() );
Locale list[] = DateFormat.getAvailableLocales();
System.out.println( "======System avialable locale:======" );
for( int i=0; i<list.length; i++ )
{
System.out.println( list[i].toString() + "/t" + list[i].getDisplayName() );
}
System.out.println( "======System property======" );
System.getProperties().list( System.out );
}
}
部分显示:
(1)
file.encoding=ISO-8859-1
user.language=en
user.language=en
(2)
file.encoding=GBK
user.language=zh
user.region=CN
(3)
file.encoding=GBK
user.language=zh
user.region=CN
JVM的缺省编码方式由系统的“本地语言环境”设置确定,和操作系统的类型无关。
2.试验2:Java的输入输出过程中的字节流到字符流的转换过程
/*
* 试验2:Java的输入输出过程中的字节流到字符流的转换过程
*/
package test2;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
public class HelloUnicode
{
public static void main(String[] args)
{
String hello = "Hello world 世界你好";
if( args.length>0 )
{
hello = args[0];
}
try
{
System.out.println( ">>>>testing1: Write hello world to files<<<<" );
System.out.println( "[test 1-1]: With system default encoding="
+ System.getProperty("file.encoding") + "/nstring="
+ hello + "/tlength=" + hello.length() );
printCharArray( hello );
writeFile( "hello.orig.html ", hello );
// 把字符串按GB2312解码
hello = new String( hello.getBytes(), "GB2312" );
System.out.println( "[test 1-2]: getBytes with platform default encoding and"
+ " decoding as gb2312/nString="
+ hello + "/tlength=" + hello.length() );
writeFile( "hello.gb2312.html", hello );
printCharArray( hello );
//把字符串按UTF8解码成字节流,并打印相应的字节
hello = new String( hello.getBytes("UTF8") );
System.out.println("[test 1-3]: convert string to UTF8/nstring="
+ hello + "/tlength=" + hello.length() );
writeFile( "hello.utf8.html", hello );
printCharArray( hello );
/*
* 试验2: 从试验1的输出文件中读取,并按照不同方式解码
*/
System.out.println( ">>>>>>testing2: reading and decoding from files<<<<<" );
//first file:encoding with system default
hello = readFile( "hello.orig.html" );
System.out.println(
"[test 2-1]: read hello.orig.html: decoding with system default encodeing/nstring="
+ hello + "/tlength=" + hello.length() );
printCharArray( hello );
//second file: decoding from GBK
hello = readFile( "hello.gb2312.html" );
hello = new String( hello.getBytes(), "GB2312" );
System.out.println(
"[test 2-2]: read hello.gb2312.html: decoding as GB2312/nstring="
+ hello +"/tlength=" + hello.length() );
printCharArray( hello );
//third file: decoding from UTF8
hello = readFile( "hello.utf8.html" );
hello = new String( hello.getBytes(), "UTF8" );
System.out.println(
"[test 2-3]: read hello.utf8.html: decoding as UTF8/nstring="
+ hello + "/tlength=" + hello.length() );
printCharArray( hello );
}catch( Exception e)
{
System.out.println( e.toString() );
}
}
public static void printCharArray( String inStr )
{
char [] myBuffer = inStr.toCharArray();
//list each Charactor in byte value, short value, and UnicodeBlock Mapping
for( int i=0; i<inStr.length(); i++ )
{
byte b = (byte)myBuffer[i];
short s = (short)myBuffer[i];
char c = (char)myBuffer[i];
String hexB = Integer.toHexString(b).toUpperCase();
String hexS = Integer.toHexString(s).toUpperCase();
StringBuffer sb = new StringBuffer();
//print char
sb.append( "char[" );
sb.append( i );
sb.append( "]='" );
sb.append( myBuffer[i] );
sb.append( "'/t" );
//byte value
sb.append( "byte=" );
sb.append( b );
sb.append( " //u" );
sb.append( hexB );
sb.append( '/t' );
// char value
sb.append( "char=" );
sb.append( c );
sb.append( " //u" );
sb.append( Integer.toHexString(c).toUpperCase() );
sb.append( '/t' );
//short value
sb.append( "short=" );
sb.append( s );
sb.append( " //u" );
sb.append( hexS );
sb.append( '/t' );
//Unicode Block
sb.append( Character.UnicodeBlock.of(myBuffer[i]) );
System.out.println( sb.toString() );
}
System.out.println();
}
private static void writeFile( String fileName, String content )
{
try
{
File tmpFile = new File( fileName );
if( tmpFile.exists() )
{
tmpFile.delete();
}
FileWriter fw = new FileWriter( fileName, true );
fw.write( content );
fw.close();
}catch( Exception e )
{
System.out.println( e.toString() );
}
}
private static String readFile( String fileName )
{
try
{
BufferedReader fr = new BufferedReader( new FileReader(fileName) );
StringBuffer out = new StringBuffer();
String thisLine = new String();
while( thisLine!=null )
{
thisLine = fr.readLine();
if( thisLine!=null )
{
out.append( thisLine );
}
}
fr.close();
return out.toString();
}catch( Exception e )
{
System.out.println( e.toString() );
return null;
}
}
}
部分显示:
char[0]='H' byte=72 /u48 char=H /u48 short=72 /u48 BASIC_LATIN
char[1]='e' byte=101 /u65 char=e /u65 short=101 /u65 BASIC_LATIN
char[2]='l' byte=108 /u6C char=l /u6C short=108 /u6C BASIC_LATIN
char[3]='l' byte=108 /u6C char=l /u6C short=108 /u6C BASIC_LATIN
char[4]='o' byte=111 /u6F char=o /u6F short=111 /u6F BASIC_LATIN
char[5]=' ' byte=32 /u20 char= /u20 short=32 /u20 BASIC_LATIN
char[12]='世' byte=22 /u16 char=世 /u4E16 short=19990 /u4E16 CJK_UNIFIED_IDEOGRAPHS
char[13]='界' byte=76 /u4C char=界 /u754C short=30028 /u754C CJK_UNIFIED_IDEOGRAPHS
char[14]='你' byte=96 /u60 char=你 /u4F60 short=20320 /u4F60 CJK_UNIFIED_IDEOGRAPHS
char[15]='好' byte=125 /u7D char=好 /u597D short=22909 /u597D CJK_UNIFIED_IDEOGRAPHS
试验2的一些结论:
- 所有的应用都是按照字节流=>字符流=>字节流方式进行的处理的:
byte_stream ==[input decoding]==> unicode_char_stream ==[output encoding]==> byte_stream; - 在Java字节流到字符流(或者反之)都是含有隐含的解码处理的(缺省是按照系统缺省编码方式);
- 最早的字节流解码过程从javac的代码编译就开始了;
- Java中的字符character存储单位是双字节的UNICODE;
3.转换代码
package test3;
import java.io.*;
import java.util.*;
public class gb2big5
{
static int iCharNum = 0;
public static void main(String[] args)
{
System.out.println("Input GB2312 file, output Big5 file.");
if (args.length != 2)
{
System.err.println("Usage: jview gb2big5 gbfile big5file");
System.exit(1);
String inputString = readInput(args[0]);
writeOutput(inputString, args[1]);
System.out.println("Number of Characters in file: " + iCharNum
+ ".");
}
}
static void writeOutput(String str, String strOutFile)
{
try
{
FileOutputStream fos = new FileOutputStream(strOutFile);
Writer out = new OutputStreamWriter(fos, "Big5");
out.write(str);
out.close();
} catch (IOException e)
{
e.printStackTrace();
e.printStackTrace();
}
}
static String readInput(String strInFile)
{
StringBuffer buffer = new StringBuffer();
try
{
FileInputStream fis = new FileInputStream(strInFile);
InputStreamReader isr = new InputStreamReader(fis, "GB2312");
Reader in = new BufferedReader(isr);
int ch;
while ((ch = in.read()) > -1)
{
iCharNum += 1;
buffer.append((char) ch);
}
in.close();
return buffer.toString();
} catch (IOException e)
{
e.printStackTrace();
return null;
}
}
}