`

转:Java字符串与字符集的基本概念

    博客分类:
  • java
阅读更多
转:http://blog.csdn.net/darxin/article/details/5079242
Java String是Java API中最常用的类,本文和大家谈谈String类的内部原理,同时描述ISO-8859-1字符集在字符串处理中的独特用处。

Java字符串的内部编码
String类内部管理着一个char类型的数组,Java API是这样描述char基本类型的:
char 数据类型(和 Character 对象封装的值)基于原始的 Unicode 规范,将字符定义为固定宽度的 16 位实体。
这一点我们可以通过下面的语句加以证实:

[java] view plaincopy
System.out.println(Character.SIZE); // 结果为16 



根据String类的构造方法,我们可以这样定义字符串:

[java] view plaincopy
String str = "abc"; 



上句代码等效于:

[java] view plaincopy
char data[] = {'a', 'b', 'c'}; 
String str = new String(data); 



同时,char基本类型与byte、short、int、long一样可以用数值表示。所以上述代码等效于:

[java] view plaincopy
char data[] = {0x61, 0x62, 0x63}; 
String str = new String(data); 



需要注意的是,char基本类型始终使用16位即两个字节表示一个字符,即使这个字符的Unicode值小于0xFF(如ASCII码)。对于Unicode值大于0xFF的字符,如“中国”二字的Unicode编码分别为/u4E2D和/u56FD,我们可以这样创建:

[java] view plaincopy
char data[] = {'/u4E2D', '/u56FD'}; 
String str = new String(data); 



当然也可以这样创建:

[java] view plaincopy
char data[] = {0x4E2D, 0x56FD}; 
String str = new String(data); 



通过上面的描述可以明确两个要点:

字符串对象中的每一个元素始终占据两个字节长度,一个或两个元素(增补字符占据两个元素)表示一个字符。
字符串对象中的每一个元素都使用Unicode字符集进行编码。

字符集(Charset)与字节化字符数据
在出现 Unicode 规范之前,计算机在处理字符串的问题上经历过ASCII和ANSI编码两个阶段,在ASCII时代,计算机只能处理英文数字以及几个基本符号,当时使用的是单字节字符集(SBCS)。
各国为了能在计算机上处理本国的文字,制订了相应的国家标准,规定了各自的ANSI编码。如中文简体使用GBK标准;中文繁体使用BIG5标准;日文使用Shift_JIS标准。在ANSI编码时代,计算机使用多字节字符集(MBCS)处理文字。如“中国ABC”,在GB2312标准中,“中国”两个字符分别使用两个字节表示,而“ABC”三个英文字符又分别使用一个字节表示。各国文字的ANSI编码互不通用,不能使用一种ANSI编码表达多个国家的文字。
为了文字交流的顺畅,也就是说为了达到在一个文本当中既可以有中文简体字存在,也可以有中文繁体字存在的目的。国际组织根据各国语言的特点,使用两个字节的数据量将大部分国家的文字信息整合到一个字符集中,这就是Unicode编码,也称万国码。关于Unicode编码的特点,前文已经描述,那就是使用双字节字符集(DBCS)处理文字。


在Java中,使用字节数组保存不同字符集的字符值,如使用ASCII字符集保存“abc”的方法如下:

[java] view plaincopy
// 使用ASCII编码的“abc” 
byte[] ascBytes = {(byte)0x61, (byte)0x62, (byte)0x63}; 



使用GBK字符集保存“中国”二字的方法如下:

[java] view plaincopy
// 使用GBK编码的“中国” 
byte[] gbkBytes = {(byte)0xD6, (byte)0xD0, (byte)0xB9, (byte)0xFA}; 



当然我们也可以使用Unicode字符集保存“中国”二字。如:

[java] view plaincopy
// 使用Unicode编码的“中国” 
byte[] unicodeBytes = {(byte)0x4E, (byte)0x2D, (byte)0x56, (byte)0xFD}; 



一个特殊的字符集UTF-8是与Unicode规范对应的多字节表示的字符集。如“中”字的UTF-8编码为“0xE4, 0xB8, 0xAD”三个字节。



在这里,将这些与具体字符集相对应的字节化的数据流称为字节化字符数据,与字符串对象形成鲜明对照的是,字符串对象的最小单位是两个字节而字节化字符数据的最小单位则是一个字节。由此我们可以明确另外两个要点:

字节化字符数据中的每一个元素始终占据一个字节长度,一个或多个元素表示一个字符。
字节化字符数据必须与一个字符集相对应。

字节化字符数据与字符串对象的互换
在Java程序运行过程中,字符串对象始终以Unicode编码方式保存在内存中,但将字符串对象保存到持久化资源(文件或数据库)或将其通过网络传输时,通常是以字节化字符数据的方式进行处理。这样就要求Java API必须提供两者互换的功能。事实上这一功能在String类及Charset类中已经提供。
一方面我们可以利用String类的getBytes()方法返回不同字符集的字节化字符数据,其本质是从Unicode字符集编码向其它字符集编码转换的过程。例如:

[java] view plaincopy
public static void main(String[] args) { 
 
    String str = "中国"; 
    printBytes("中国的UNICODE编码:", str.getBytes(Charset.forName("unicode"))); 
    printBytes("中国的GBK编码:", str.getBytes(Charset.forName("GBK"))); 
    printBytes("中国的UTF-8编码:", str.getBytes(Charset.forName("UTF-8"))); 

 
public static void printBytes(String title, byte[] data) { 
    System.out.println(title); 
    for (byte b : data) { 
        System.out.print("0x" + toHexString(b) + " "); 
    } 
    System.out.println();    

 
public static String toHexString(byte value) { 
    String tmp = Integer.toHexString(value & 0xFF); 
    if (tmp.length() == 1) { 
        tmp = "0" + tmp; 
    } 
 
    return tmp.toUpperCase(); 




上例的输出结果为:

中国的UNICODE编码:
0xFE 0xFF 0x4E 0x2D 0x56 0xFD
中国的GBK编码:
0xD6 0xD0 0xB9 0xFA
中国的UTF-8编码:
0xE4 0xB8 0xAD 0xE5 0x9B 0xBD

需要注意的是,从字符串对象中取出的Unicode编码的字节化字符数据时,其开始部分存在一个BOM(ByteOrderMark),一般情况下,该BOM值为“0xFE 0xFF”,即大端字节序(BIG_ENDIAN)。如果BOM值为“0xFF 0xFE”则为小端字节序(LITTLE_ENDIAN)。



另一方面也可以利用String类的构造方法根据不同字符集的字节化字符数据产生一个字符串对象,其本质是从其它字符集编码向Unicode字符集编码转换的过程。例如:

[java] view plaincopy
byte[] unicodeBytes = {(byte)0x4E, (byte)0x2D, (byte)0x56, (byte)0xFD}; 
System.out.println(new String(unicodeBytes, Charset.forName("unicode"))); 
 
byte[] gbkBytes = {(byte)0xD6, (byte)0xD0, (byte)0xB9, (byte)0xFA}; 
System.out.println(new String(gbkBytes, Charset.forName("GBK"))); 
         
byte[] utf8Bytes = {(byte)0xE4, (byte)0xB8, (byte)0xAD, (byte)0xE5, (byte)0x9B, (byte)0xBD}; 
System.out.println(new String(utf8Bytes, Charset.forName("UTF-8"))); 



上例三个输出语句均输出“中国”二字。

上述两种转换过程,特别是Unicode字符集编码向其它字符集编码的转换过程中会出现转换失败的现象。转换失败时该Unicode码自动用0x3F代替。例如:

[java] view plaincopy
public static void main(String[] args) { 
 
    String str = "中国"; 
    printBytes("中国的BIG5编码:", str.getBytes(Charset.forName("BIG5"))); 

 
public static void printBytes(String title, byte[] data) { 
    // 同上例 




上例的输出结果为:

中国的BIG5编码:
0xA4 0xA4 0x3F



其中“国”由于没有繁体中文BIG5字符集对应的编码值,所以会用0x3F表示。

特殊的字符集(ISO-8859-1)
ISO-8859-1是单字节字符集,是ASCII字符集的补充。通常情况下使用ISO-8859-1字符集进行字符串对象与字节化字符数据的互换操作与前述完全一致。例如:

[java] view plaincopy
public static void main(String[] args) { 
    // 字符串“abc” 
    byte[] bytes = { (byte) 0x61, (byte) 0x62, (byte) 0x63 }; 
    String str = new String(bytes, Charset.forName("ISO-8859-1")); 
    printBytes("ISO-8859-1编码:", str.getBytes(Charset.forName("ISO-8859-1"))); 
    printBytes("UNICODE编码:", str.getBytes(Charset.forName("UNICODE")));         

 
public static void printBytes(String title, byte[] data) { 
    // 同上 



上例的输出结果为:

ISO-8859-1编码:
0x61 0x62 0x63
UNICODE编码:
0xFE 0xFF 0x00 0x61 0x00 0x62 0x00 0x63

通过此例可以看出,从ISO-8859-1字符集转换成Unicode字符集的过程是将字节化字符数据中的每个一个byte类型元素直接保存成一个char类型元素。也就是说下面的代码:

[java] view plaincopy
byte[] bytes = { (byte) 0x61, (byte) 0x62, (byte) 0x63 }; 
String str = new String(bytes, Charset.forName("ISO-8859-1")); 



等效于:

[java] view plaincopy
byte[] bytes = { (byte) 0x61, (byte) 0x62, (byte) 0x63 }; 
char[] tmp = new char[bytes.length]; 
for (int i=0; i<bytes.length; i++) { 
    tmp[i] = (char)bytes[i]; 

String str = new String(tmp); 



需要注意的是,ISO-8859-1到Unicode的转换过程是对编码值为0x00 - 0xFF之间都有效的一种转换。在ISO-8859-1字符集中,0x00-0x1F、0x7F、0x80-0x9F没有定义。我们可以使用其中几个无效编码进行测试:

[java] view plaincopy
public static void main(String[] args) { 
    // 无效的ISO-8859-1编码 
    byte[] bytes = { (byte) 0x00, (byte) 0x1A, (byte) 0x7F, (byte) 0x93 }; 
    String str = new String(bytes, Charset.forName("ISO-8859-1")); 
    printBytes("ISO-8859-1编码:", str.getBytes(Charset.forName("ISO-8859-1"))); 
    printBytes("UNICODE编码:", str.getBytes(Charset.forName("UNICODE")));         

 
public static void printBytes(String title, byte[] data) { 
    // 同上 




上例的输出结果为:

ISO-8859-1编码:
0x00 0x1A 0x7F 0x93
UNICODE编码:
0xFE 0xFF 0x00 0x00 0x00 0x1A 0x00 0x7F 0x00 0x93

根据这一特点,我们可以总结出最后一个要点:

利用ISO-8859-1字符集,我们可以将任何一个字节数组无损保存到字符串对象中。

也就是说,可以利用这一特点将字节化字符数据的原始字节数据(而不是经过Unicode字符集转换之后的数据)直接保存在字符串对象中。反之也可以从一个经过ISO-8859-1编码的字符串对象中取出原始字节数据。例如:

[java] view plaincopy
public static void main(String[] args) { 
    byte[] utf8Bytes = {(byte)0xE4, (byte)0xB8, (byte)0xAD, (byte)0xE5, (byte)0x9B, (byte)0xBD}; 
    printBytes("原始字节流:" , utf8Bytes); 
     
    // ISO-8859-1编码过程 
    // 保存原始字节数据流(不经过Unicode编码)到字符串对象 
    String isoStr = new String(utf8Bytes, Charset.forName("ISO-8859-1")); 
     
    // ISO-8859-1解码过程 
    // 从字符串对象中取得与utf8Bytes内容完全相等的原始字节数据流 
    byte[] tmp = isoStr.getBytes(Charset.forName("ISO-8859-1")); 
    printBytes("转换字节流:" , tmp); 

 
public static void printBytes(String title, byte[] data) { 
    // 同上 



上例的输出结果为:

原始字节流:
0xE4 0xB8 0xAD 0xE5 0x9B 0xBD
转换字节流:
0xE4 0xB8 0xAD 0xE5 0x9B 0xBD

这种通过字符串对象保存原始字节数据的方法被很多地方所使用。最常见的就是Java WEB应用中Web服务器对来自于服务器的表单数据的处理,关于这方面的详细说明请参考 如何解决Java WEB应用中的乱码问题。
分享到:
评论

相关推荐

    JAVA程序设计习题集

    习题集内容覆盖面广,包括:Java言的基本常识、基本语法、面向对象的基本概念、数组、字符串、异常处理、文件和数据流、图形用户界面设计、小应用程序、线程、编程规范、网络程序设计、多媒体民图形学程序设计以及...

    Java程序设计习题集下载

    习题集内容覆盖面广,包括:Java言的基本常识、基本语法、面向对象的基本概念、数组、字符串、异常处理、文件和数据流、图形用户界面设计、小应用程序、线程、编程规范、网络程序设计、多媒体民图形学程序设计以及...

    125集专攻JAVA基础 JAVA零基础入门学习视频教程 动力节点JAVA视频教程.txt

    北京动力节点-Java编程零基础教程-091-Java基本语法-控制语句-switch语句-字符串作为比较对象.avi 北京动力节点-Java编程零基础教程-092-Java基本语法-控制语句-switch语句-举例1.avi 北京动力节点-Java编程零...

    JAVA入门1.2.3:一个老鸟的JAVA学习心得 PART1(共3个)

    9.2.6 在字符串中查找子字符串或字符 226 9.2.7 替换字符串中的内容 226 9.2.8 String对象——磐石刻字 227 9.3 String类的最佳拍档——StringBuffer类 227 9.3.1 StringBuffer:专业操纵字符 228 9.3.2 String...

    Java入门1·2·3:一个老鸟的Java学习心得.PART3(共3个)

    9.2.6 在字符串中查找子字符串或字符 226 9.2.7 替换字符串中的内容 226 9.2.8 String对象——磐石刻字 227 9.3 String类的最佳拍档——StringBuffer类 227 9.3.1 StringBuffer:专业操纵字符 228 9.3.2 String...

    Java重点知识总结

    字符串(常量、与字符常量的区别、连接运算、例2.18) 。。。。。。。 。。。。 第9章 流的概念 字节流的类的作用、类层次 字符流的类的作用、类层次 随机文件存取类作用 第10章 URL、URLConnection类支持的协议 TCP...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part2

     17.2.3 字符串模式匹配  17.2.4 逻辑运算  17.2.5 集合运算  17.3 小结  17.4 思考题 第18章 Hibernate的检索方式(下)  18.1 连接查询  18.1.1 默认情况下关联级别的运行时检索策略  18.1.2 迫切左外连接...

    java初学者必看

    5.2.1 与字符串的连接 5.2.2 与其他数据类型的连接 5.3 String字符串操作 5.3.1 基本操作 5.3.2 比较 5.3.3 转化 5.3.4 查找 5.3.5 截取拆分 5.3.6 替换或修改 5.4 StringBuffer类操作 5.4.1 基本操作 ...

    JAVA面试题最全集

    使用StringBuffer类与String类进行字符串连接时有何区别? 57.调用Thread类的destroy()方法有什么后果? 58.多线程,用什么关键字修饰同步方法?stop()和suspend()方法为何不推荐使用? 59.使用socket建立客户端...

    java8集合源码分析-LearningNotes:Java笔记

    、、基本概念、面向对象、基本数据类型与运算、字符串与数组、异常处理、Object 通用方法 数据结构 & 源码分析:ArrayList、Vector、LinkedList、HashMap、ConcurrentHashMap、HashSet、LinkedHashSet and ...

    Java开发热门常问面试题目资料解答合集40个资料左右.zip

    Java中的String是可变字符串还是不可变字符串? 答:String是不可变字符串,一旦创建就不可更改。 Java中的四种访问修饰符分别是什么,并描述其作用? 答:四种访问修饰符分别是public、protected、default、...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part4

     17.2.3 字符串模式匹配  17.2.4 逻辑运算  17.2.5 集合运算  17.3 小结  17.4 思考题 第18章 Hibernate的检索方式(下)  18.1 连接查询  18.1.1 默认情况下关联级别的运行时检索策略  18.1.2 迫切左外连接...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part3

     17.2.3 字符串模式匹配  17.2.4 逻辑运算  17.2.5 集合运算  17.3 小结  17.4 思考题 第18章 Hibernate的检索方式(下)  18.1 连接查询  18.1.1 默认情况下关联级别的运行时检索策略  18.1.2 迫切左外连接...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part1.rar

     17.2.3 字符串模式匹配  17.2.4 逻辑运算  17.2.5 集合运算  17.3 小结  17.4 思考题 第18章 Hibernate的检索方式(下)  18.1 连接查询  18.1.1 默认情况下关联级别的运行时检索策略  18.1.2 迫切左外连接...

    史上最全Java面试题目大集合

    JAVA平台提供了两个类:String和StringBuffer,它们可以储存和操作字符串,即包含多个字符的字符数据。这个String类提供了数值不可改变的字符串。而这个StringBuffer类提供的字符串进行修改。当你知道字符数据要改变...

    数据结构与算法经典问题解析-Java语言描述

    本书强调问题及其分析,而非理论阐述,共分为21章,讲述了基本概念、递归和回溯、链表、栈、队列、树、优先队列和堆、并查集DAT、图算法、排序、查找、选择算法(中位数)、符号表、散列、字符串算法、算法设计技术...

    java基础案例与开发详解案例源码全

    9.1.3 字符串对象修改228 9.1.4 类型转换230 9.2 StringBuffer类的使用231 9.3 StringBuilder类的使用233 9.4 日期类简介234 9.5 Java语言国际化时间获取与计算238 9.6 Random类和Math类240 9.7 本章习题243 第10章 ...

    疯狂JAVA讲义

    9.6.4 使用MessageFormat处理包含占位符的字符串 349 9.6.5 使用类文件代替资源文件 350 9.6.6 使用NumberFormat格式化数字 350 9.6.7 使用DateFormat格式化日期 352 9.7 本章小结 355 本章练习 355 第10章 ...

Global site tag (gtag.js) - Google Analytics