码迷,mamicode.com
首页 > 编程语言 > 详细

【Java基础】Java中的char是否可以存储一个中文字符之理解字符字节以及编码集

时间:2016-04-29 15:32:24      阅读:372      评论:0      收藏:0      [点我收藏+]

标签:

Java中的一个char采用的是Unicode编码集,占用两个字节,而一个中文字符也是两个字节,因此Java中的char是可以表示一个中文字符的。

但是在C/C++中由于采用的字符编码集是ASCII,只有一个字节,因此是没办法表示一个中文字符的。

解答了上面的浅显易懂的问题之后,下面彻底理清楚字符 字节以及编码的原理。
其实关于编码以及字节的问题,在腾讯实习生一面的时候也问到过,当时搞不懂面试官为什么会问这个问题,现在想想,这个问题还是很考验一个人的思考以及钻研深度的,而且这个问题远远比自己想象的复杂。

字符编码

编码的三个阶段:

单字节字符 – ASCII
多字节字符 – ANSI
宽字节字符 – UNICODE

技术分享

字符 字节 字符串

技术分享

从第一个表和第二个表中可以看出来,任何字符再Unicode编码中都是两个字节进行标识的,而且世界上任何语言的字符的Unicode编码都是不一样的,因此不论计算机在什么样的语言环境下,都是可以正常显示字符的。但是ANSI每个字符长度却是不定的,这样一个字符该如何解释是和具体的计算机环境(具体的编码规则)有关的。

字符集与编码

各个国家和地区所制定的不同 ANSI 编码标准中,都只规定了各自语言所需的“字符”。比如:汉字标准(GB2312)中没有规定韩国语字符怎样存储。这些 ANSI 编码标准所规定的内容包含两层含义:

  1. 使用哪些字符。也就是说哪些汉字,字母和符号会被收入标准中。所包含“字符”的集合就叫做“字符集”
  2. 规定每个“字符”分别用一个字节还是多个字节存储,用哪些字节来存储,这个规定就叫做“编码”

各个国家和地区在制定编码标准的时候,“字符的集合”和“编码”一般都是同时制定的。因此,平常我们所说的“字符集”,比如:GB2312, GBK, JIS 等,除了有“字符的集合”这层含义外,同时也包含了“编码”的含义。

“UNICODE 字符集”包含了各种语言中使用到的所有“字符”。用来给 UNICODE 字符集编码的标准有很多种,比如:UTF-8, UTF-7, UTF-16, UnicodeLittle, UnicodeBig 等

“编码”的概念就是把“字符”转化成“字节”但是每一种编码对应着不同的转换规则,如果不遵循规则,后果就是乱码。

技术分享

针对语言的字符和字节

技术分享

就像开始所讨论的一个char是否可以表示一个中文字符的时候一样,如果只是把char看做一种类型的话,那是否可以表示中卫字符取决于语言。而按此处的以语言进行划分,就可以很容易看出两种主流语言中字符与字节的差异了 – 在C++中char被看做是字节而在Java中被看做是字符。

编码理解误区

技术分享

第一种误解,往往是导致乱码产生的原因
即采用每“一个字节”就是“一个字符”的转化方法,实际上也就等同于采用 iso-8859-1 进行转化。因此,我们常常使用 bytes = string.getBytes(“iso-8859-1”) 来进行逆向操作,先得到原始的“字节串”。然后再使用正确的 ANSI 编码,比如 string = new String(bytes, “GB2312”),来得到正确的“UNICODE 字符串”

第二种误解,往往导致本来容易纠正的乱码问题变得更复杂

非 UNICODE 程序中的字符串,都是以某种 ANSI 编码形式存在的。如果程序运行时的语言环境与开发时的语言环境不同,将会导致 ANSI 字符串的显示失败
比如,在日文环境下开发的非 UNICODE 的日文程序界面,拿到中文环境下运行时,界面上将显示乱码。如果这个日文程序界面改为采用 UNICODE 来记录字符串,那么当在中文环境下运行时,界面上将可以显示正常的日文。

乱码场景

一个web开发初学者必定会遇到的乱码问题及原理

当然这个问题在Android开发中也是经常遇到,尤其是在不注意设置UTF-8字符集的情况下,提交的都是GBK但是服务器却按ISO8859-1来解释

当页面中的表单提交字符串时,首先把字符串按照当前页面的编码,转化成字节串。然后再将每个字节转化成 “%XX” 的格式提交到 Web 服务器。比如,一个编码为 GB2312 的页面,提交 “中” 这个字符串时,提交给服务器的内容为 “%D6%D0”。

在服务器端,Web 服务器把收到的 “%D6%D0” 转化成 [0xD6, 0xD0] 两个字节,然后再根据 GB2312 编码规则得到 “中” 字。

在 Tomcat 服务器中,request.getParameter() 得到乱码时,常常是因为前面提到的“误解一”造成的。默认情况下,当提交 “%D6%D0” 给 Tomcat 服务器时,request.getParameter() 将返回 [0x00D6, 0x00D0] 两个 UNICODE 字符,而不是返回一个 “中” 字符。因此需要使用 bytes = string.getBytes(“iso-8859-1”) 得到原始的字节串,再用 string = new String(bytes, “GB2312”) 重新得到正确的字符串 “中”

数据库中字符串类型数据的存储

这个乱码场景也比较容易遇到。举个最简单的例子是当字段的值含有中文的时候,如果存的时候与取的时候没有显式的指定编码集,一般都会乱码。比如存的时候按GBK或者GB2312这种默认字符集去存储,但是解析显示的时候却默认的时候8859-1,自然就产生了乱码。一个比较好的习惯当然是在配置环境的时候都设置编码集为UTF-8。

Unicode和UTF-X之间的关系

Unicode的实现方式不同于编码方式。一个字符的Unicode编码是确定的。但是在实际传输过程中,由于不同系统平台的设计不一定一致,以及出于节省空间的目的,对Unicode编码的实现方式有所不同。Unicode的实现方式称为Unicode转换格式(Unicode Transformation Format,简称为UTF)

例如,如果一个仅包含基本7位ASCII字符的Unicode文件,如果每个字符都使用2字节的原Unicode编码传输,其第一字节的8位始终为0。这就造成了比较大的浪费。对于这种情况,可以使用UTF-8编码,这是一种变长编码,它将基本7位ASCII字符仍用7位编码表示,占用一个字节(首位补0)。而遇到与其他Unicode字符混合的情况,将按一定算法转换,每个字符使用1-3个字节编码,并利用首位为0或1进行识别。这样对以7位ASCII字符为主的西文文档就大幅节省了编码长度(具体方案参见UTF-8)。类似的,对未来会出现的需要4个字节的辅助平面字符和其他UCS-4扩充字符,2字节编码的UTF-16也需要通过一定的算法进行转换。

UTF-8就是在互联网上使用最广的一种Unicode的实现方式。其他实现方式还包括UTF-16(字符用两个字节或四个字节表示)和UTF-32(字符用四个字节表示),不过在互联网上基本不用.

UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。


    public class CharTest {

    public static void main(String[] args) throws UnsupportedEncodingException {

        String str = "hello中国";
        int byteLen = str.getBytes("utf-8").length;
        int strLen = str.length(); 
        System.out.println(System.getProperty("file.encoding"));
        System.out.println("byteLen : " + byteLen);
        System.out.println("strLen  : " + strLen);
    }
    }

分别将上述的str.getBytes(“utf-8”)设置为gbk utf-8 utf-16 utf-32就可以看出每种编码方式中,一个中文字符所占用的字节数是不一样的。英文字符所占字节数也是稍微不同的。

总结如下:
在 GB 2312 编码或 GBK 编码中,一个英文字母字符存储需要1个字节,一个汉字字符存储需要2个字节。
在UTF-8编码中,一个英文字母字符存储需要1个字节,一个汉字字符储存需要3到4个字节。
在UTF-16编码中,一个英文字母字符存储需要2个字节,一个汉字字符储存需要3到4个字节(Unicode扩展区的一些汉字存储需要4个字节)。
在UTF-32编码中,世界上任何字符的存储都需要4个字节。

UTF-8的编码规则:

  1. 对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。
  2. 对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码

Notepad++中进行编码格式(ANSI,Unicode,Unicode big endian 和 UTF-8)的设置,然后使用UltraEdit进行16进制格式查看,就可以看出不同编码方式下,同一个中文字符的编码不同,字节数也不同。涉及到大小端问题还会导致顺序的不同。后面的参考链接二中会有详细的解释
技术分享

UTF-8 BOM和无BOM的区别
BOM(byte order mark)是为 UTF-16 和 UTF-32 准备的,用于标记字节序(byte order)
还是一样的,按上述的方式在notpad++中设置编码格式然后在ultraedit中使用16进制进行查看就会发现默认utf-8的时候直接是
技术分享
无bom的时候是:
技术分享

一般还是直接使用无bom的编码

大小端的问题

既然涉及到了字节,自然就涉及到大小端的问题。
在计算机系统中,以字节为基本存储单位的,每个地址单元都对应着一个字节,一个字节为8bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如果将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。

大小端定义:
1) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
2) Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。

网络传输一般采用大端序,也被称之为网络字节序,或网络序。IP协议中定义大端序为网络字节序。Berkeley套接字定义了一组转换函数,用于16和32bit整数在网络序和本机字节序之间的转换。htonl,htons用于本机序转换到网络序;ntohl,ntohs用于网络序转换到本机序。
关于网络字节序,Unix高级网络编程中有详细的解释

参考链接:
https://zh.wikipedia.org/wiki/Unicode
http://www.regexlab.com/zh/encoding.htm
http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html
http://blog.csdn.net/ce123/article/details/6971544

【Java基础】Java中的char是否可以存储一个中文字符之理解字符字节以及编码集

标签:

原文地址:http://blog.csdn.net/sugar_z_/article/details/51276984

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!