前言

字符编码,这本不属于IO的内容,但字节流之后写的应该是字符流,既然是字符流,那就涉及一个"字符编码的"问题,考虑到字符编码不仅仅是在IO这块,Java中很多场景都涉及到这个概念,因此这边文章就专门详细写一下字符编码,具体的网上有很多,但本文目的是尽量讲清楚各种编码方式的作用,个人认为,不求、也没有必要对字符编码理解地多么深入。

字符集和字符编码

第一个概念就是字符集和字符编码之间的区别:

1、字符集(charset)

字符集指的是一个系统支持的所有抽象字符的集合。字符是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等,常见的字符集有ASCII字符集、GB2312字符集、BIG5字符集、GB18030字符集、Unicode字符集等。

2、字符编码(encoding)

计算机要准确处理各种字符集文字,就要进行字符编码,以便计算机能够识别和存储各种文字。因此字符编码就是讲符号转换为计算机可以接受的数字系统的数,称为数字代码。

ASCII码

计算机里面只有数字0和1(严格说连0和1都没有,只有开和关,无非是用0和1表示开关的状态罢了),在计算机软件里的一切都是用数字标识的额,屏幕上显示的一个一个字符也是数字。最初使用的计算机在美国,用到的字符很少,因此每一个字符都用一个数字表示,一个字节所能表示的数字反内卫足以容纳所有这些字符。实际上表示这些字符的数字的字节最高位都是0,也就是说这些数字都在0~127之间,如字符a对应97,字符b对应数字98,这种字符与数字的对应编码固定下来之后,这套编码规则被称为ASCII码(美国标准信息交换码)。一张简单的ASCII码表如图:

从表中可以看出ASCII码分为两部分:

1、0~31是控制字符,如换行、回车、删除等

2、32~126是打印字符,可以通过键盘输入并且能够显示出来

GB2312和GBK

随着计算机在其它国家的普及,许多国家把本地字符集引入了计算机,大大扩展了计算机中字符的范围。一个字节所能表示的范围不足以容纳中文字符(看看上面的ASCII码表就知道了),中国大陆将每一个中文字符都用两个字节表示,原有的ASCII码字符的编码保持不变。

为了将一个中文字符与两个ASCII码字符相区别,中文字符的每个字节最高位为1,中国大陆为每一个中文字符都指定了一个对应的数字,并于1980年制定了一套《信息技术 中文编码字符集》,这套规范就是GB2312。GB2312是双字节编码,总的编码范围是A1~F7,其实A1~A9是富豪区,总共包含682个符号;B0~F7是汉字区,总共包含6763个汉字。

GBK是在1995年制定的后续标准,全称为《汉字内码扩展规范》,是国家技术监督局为Windows 95所制定的新的汉字内码规范。GBK的出现是为了扩展GBK2312,并加入更多的汉字。GBK的编码范围是8140~FEFE(去掉XX7F),总共有23940个码位,能表示21003个汉字,它的编码是和GB2312兼容的,也就是说用GB2312编码的汉字可以用GBK来解码,并且不会有乱码问题。GBK还是现如今中文Windows操作系统的系统默认编码。

Unicode

在一个国家的本地化系统中出现的一个字符,通过电子邮件传送到另外一个国家的本地化系统中,看到的就不是那个原始字符了,而是另外那个国家的一个字符或乱码,因为计算机里面并没有真正的字符,字符都是以数字的形式存在的,通过邮件传送一个字符,实际上传送的是这个字符对应的字符编码,同一个数字在不同的国家和地区代表的很可能是不同的符号。

为了解决各个国家和地区之间各自使用不同的本地化字符编码带来的不便,人们将全世界所有的符号进行了统一编码,称之为Unicode(统一码、万国码)。所有字符不再区分国家和地区,都是人类共有的符号,如"中"字在Unicode中不再是GBK中的D6D0,而是在任何地方都是4e2d,如果所有的计算机系统都使用这种编码方式,那么4e2d这个字在任何地方都代表汉字中的"中"。Unicode编码的字符都占用两个字节的大小,也就是说全世界所有字符个数不会超过65536个。

当然Unicode只包含65536个字符就想包含全世界所有的字符是远远不够的,所以Unicode提供了字符平面映射,链接地址上就是Wiki百科对于字符平面映射的解读。另外要提一点的是,Unicode是Java和XML的基础。

UTF-8和UTF-16

Unicode是一种字符集标准,而具体该标准应该如何应用到计算机中,则是另一个话题了,常用的Unicode编码方式有两种:

1、UTF-16。两个字节表示Unicode转换格式,这是定长的表示方法。也就是说不管什么字符都可以使用两个字节表示,两个字节是16Bit,所以叫做UTF-16。UTF-16编码非常方便,每两个字节表示一个字符,这个在字符串操作时大大简化了操作。

2、UTF-8。UTF-16统一采用了两个字节表示一个字符,虽然在表示上非常简单,但是很大一部分字符用一个字节表示就够了,现在需要两个字节,存储空间放大了一倍。UTF-8就采取了一种变长技术,每个编码区域有不同的字码长度,不同类型的字符可以是由1~6个字节组成。

两种编码方式比较,相对来说,UTF-16的编码效率较高,从字符到字节的相互转换可以更简单,进行字符串操作也更好,它更适合在本地磁盘和内存之间使用,可以进行字符和字节之间的快速切换。但是UTF-16并不适合在网络之间传输,因为网络传输易损坏字节流,一旦字节流损坏将很难恢复,所以相比较而言UTF-8更适合网络传输。另外UTF-8对ASCII字符采用单字节存储,单个字符损坏也不会影响后面的其他字符,在编码效率上介于GBK和UTF-16之间,所以,UTF-8在编码效率和编码安全性上做了平衡,是理想的中文编码方式。

Java与字符编码

Java中的字符使用的都是Unicode字符集,编码方式为UTF-16,Java技术在通过Unicode保证跨平台特性的前提下也支持了全扩展的本地平台字符集,而显示输出和键盘输入都是采用的本地编码。因此,免不了二者的转化问题。

看一个很简单的例子:

public static void main(String[] args) throws Exception
{
// 这里将字符串通过getBytes()方法,编码成GB2312
byte b[] = "大家一起来学习Java语言".getBytes("GB2312");
File file = new File("D:/Files/encoding.txt");
OutputStream out = new FileOutputStream(file);
out.write(b);
out.close();
}

看一下文件中是什么:

正常输出,无编码问题,但是如果这样:

public static void main(String[] args) throws Exception
{
// 这里将字符串通过getBytes()方法,编码成GB2312
byte b[] = "大家一起来学习Java语言".getBytes("ISO8859-1");
File file = new File("D:/Files/encoding.txt");
OutputStream out = new FileOutputStream(file);
out.write(b);
out.close();
}

再看一下文件中是什么:

乱码问题就出现了,通过上述操作的完整过程分析一下原因。

要再次说明的是,Java中的String都是Unicode字符集的。Java中的各个类,对于英文字符的支持都非常好,可以正常地写入文件中,但对于中文字符就未必了。从Java源代码到输入文件正确的内容,要经过"Java源代码->Java字节码->虚拟机->文件"几个步骤,在上述过程中的每一步都必须正确地处理汉字的编码,才能够使最终有我们期望的结果。

"Java源代码->Java字节码",标准的Java编译器Javac使用的字符集是系统默认的字符集,比如在中文Windows操作系统上就是GBK(上面GBK的部分已经说明过了),而在Linux操作系统上就是ISO8859-1,所以大家会发现Linux操作系统上编译的类中源文件中的中文字符都出现了问题,解决办法就是在编译的时候添加encoding参数,这样才能够与平台无关,用法是:javac -encoding GBK。

"Java字节码->虚拟机->文件",Java运行环境(JRE)分英文版和国际版,但只有国际版才支持非英文字符。Java开发工具包(JDK)肯定支持多国字符,但并非所有的计算机用户都安装了JDK。很多操作系统应用软件为了能够更好地支持Java,都内嵌了JRE的国际版本,为支持自己多国字符提供了方便。

问题就出"Java源代码->Java字节码上",这是由于JDK设置环境变量引起的。用程序看一下JDK环境变量:

public static void main(String[] args)
{
System.getProperties().list(System.out);
}

看一下输出的全部信息,有点长:

 -- listing properties --
java.runtime.name=Java(TM) SE Runtime Environment
sun.boot.library.path=E:\MyEclipse10\Common\binary\com.sun....
java.vm.version=11.3-b02
java.vm.vendor=Sun Microsystems Inc.
java.vendor.url=http://java.sun.com/
path.separator=;
java.vm.name=Java HotSpot(TM) 64-Bit Server VM
file.encoding.pkg=sun.io
user.country=CN
sun.java.launcher=SUN_STANDARD
sun.os.patch.level=
java.vm.specification.name=Java Virtual Machine Specification
user.dir=F:\代码\MyEclipse\TestIO
java.runtime.version=1.6.0_13-b03
java.awt.graphicsenv=sun.awt.Win32GraphicsEnvironment
java.endorsed.dirs=E:\MyEclipse10\Common\binary\com.sun....
os.arch=amd64
java.io.tmpdir=C:\Users\dell1\AppData\Local\Temp\
line.separator= java.vm.specification.vendor=Sun Microsystems Inc.
user.variant=
os.name=Windows Vista
sun.jnu.encoding=GBK
java.library.path=E:\MyEclipse10\Common\binary\com.sun....
java.specification.name=Java Platform API Specification
java.class.version=50.0
sun.management.compiler=HotSpot 64-Bit Server Compiler
os.version=6.2
user.home=C:\Users\dell1
user.timezone=
java.awt.printerjob=sun.awt.windows.WPrinterJob
file.encoding=GBK
java.specification.version=1.6
user.name=dell1
java.class.path=F:\代码\MyEclipse\TestIO\bin
java.vm.specification.version=1.0
sun.arch.data.model=64
java.home=E:\MyEclipse10\Common\binary\com.sun....
java.specification.vendor=Sun Microsystems Inc.
user.language=zh
awt.toolkit=sun.awt.windows.WToolkit
java.vm.info=mixed mode
java.version=1.6.0_13
java.ext.dirs=E:\MyEclipse10\Common\binary\com.sun....
sun.boot.class.path=E:\MyEclipse10\Common\binary\com.sun....
java.vendor=Sun Microsystems Inc.
file.separator=\
java.vendor.url.bug=http://java.sun.com/cgi-bin/bugreport...
sun.cpu.endian=little
sun.io.unicode.encoding=UnicodeLittle
sun.desktop=windows
sun.cpu.isalist=amd64

注意一下34行,表明了JDK使用的是GBK字符集(GBK是GB2312上的扩展,所以用GB2312字符集当然是没有问题的),这意味着Java对String的操作,都做了Unicode到GBK的转换。既然JDK用的GBK编码,那么用ISO8859-1字符集显示GBK编码出来的中文当然是有问题的。

这只是一个例子,在我们的应用程序中涉及I/O操作时,一般只要注意指定统一的编解码Charset集,就不会出现乱码问题。对有些应用程序如果不注意指定字符编码,则在中文环境中会使用操作系统的默认编码。如果编解码都在中文环境中,通常也没有问题,但还是强烈建议不要使用操作系统的默认编码,因为这样会使你的应用程序的编码格式和运行时环境绑定起来,这样在跨环境时很可能出现乱码问题。

Java IO4:字符编码的更多相关文章

  1. JAVA的字符编码及问题

    web开发时,字符编码及有时候也会是一个麻烦的问题,没有经验的话,肯定不知道怎么解决,有一定的经验的话,那还是比较简单的.以下,是我学习过程中总结出来的几种字符编码级问题和其解决的方法 1.文档乱码, ...

  2. Java基础——字符编码

    一.ASII 美国(国家)信息交换标准(代)码. 计算机中只有数字,一切都是用数字表示,屏幕上显示的一个一个的字符也不例外. 一个字节可表示的数字为0-255,足以显示键盘上的所有的字符 例如. a ...

  3. java基础---->java中字符编码问题(一)

    这里面对java中的字符编码做一个总结,毕竟在项目中会经常遇到这个问题.爱不爱都可以,我怎样都依你,连借口我都帮你寻. 文件的编码格式 一.关于中文的二进制字节问题 public static Str ...

  4. JAVA支持字符编码读取文件

    文件操作,在java中很常用,对于存在特定编码的文件,则需要根据字符编码进行读取,要不容易出现乱码 /** * 读取文件 * @param filePath 文件路径 */ public static ...

  5. Java之字符编码和字符集

    什么是字符编码 计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的数字.英文.标点符号.汉字等字符是二进制数转换之后的结果.按照某种规则,将字符存储到计算机中,称为编码 .反之,将存储在计算 ...

  6. Java:字符编码

    常用的字符编码 UFT-8 ISO-8859-1 GBK/GBK2312

  7. Java中字符编码和字符串所占字节数 .

    首 先,java中的一个char是2个字节.java采用unicode,2个字节来表示一个字符,这点与C语言中不同,C语言中采用ASCII,在大多数 系统中,一个char通常占1个字节,但是在0~12 ...

  8. 基础篇:JAVA资源之IO、字符编码、URL和Spring.Resource

    目录 1 JAVA.IO字节流 2 JAVA.IO字符流 3 乱码问题和字符流 4 字符集和字符编码的概念区分 5 URI概念的简单介绍 6 URL概念及与URL的区别 7 Spring.Resour ...

  9. 深入理解Python的字符编码

    原文:http://lukejin.iteye.com/blog/598303 在处理中文的时候,我们有时候会碰到中文乱码的问题. 究其根本原因是正确的字节序列按照错误的编码方式解码成字符 或者正确的 ...

随机推荐

  1. Keepalived 配置实例

    Keepalived 是一款轻量级HA集群应用,它的设计初衷是为了做LVS集群的HA,即探测LVS健康情况,从而进行主备切换,不仅如此,还能够探测LVS代理的后端主机的健康状况,动态修改LVS转发规则 ...

  2. sql报句柄无效。 (异常来自 HRESULT:0x80070006 (E_HANDLE))

    是由于数据库连接资源被耗尽或者用完没被释放导致的. 我在字符串中加了启用连接池好了. 如果错误信息为:sql 无效操作.连接被关闭 也是这个问题导致的.

  3. javascript:void(0)

    这是不是一个设计缺陷呢 void(0)这种用法巧妙利用void关键字的特性返回undefined(且没有副作用).因为不是关键字,比如直接使用undefined,内容可能被改写. 再来看为啥使用0,而 ...

  4. JAVA中protected的作用

    JAVA中protected的作用   1.public:public表明该数据成员.成员函数是对所有用户开放的,所有用户都可以直接进行调用 2.private:private表示私有,私有的意思就是 ...

  5. maven认证信息

    上传至本地文件夹 <distributionManagement>         <repository>             <id>test</id ...

  6. hibernate配置 sqlserver 数据库自动增长

    <id  name="Id" type="integer"> <column name="userid" > < ...

  7. Spring 学习笔记 2. 尚硅谷_佟刚_Spring_IOC&DI概述

    1,远古时代 这里讲述的IOC的演变历史,举一个例子,假如需要生成HTML和PDF格式的报表,以前的开发方式就是有个报表服务类需要使用报表生成器 它需要和其他三个都关联,它既需要知道接口类型,也需要知 ...

  8. jquery 监听input输入值事件

    <html> <head> <title></title> <script type="text/javascript" sr ...

  9. mac命令行常用

    1. 寻找文件命令 find . -name "*.txt" //.代表当前路径,意思是找到所有txt文件 2. ps -A | grep mysql //可以检查到mysql的进 ...

  10. windows 7下qtcreator里QWT文件的pro配置

    http://blog.chinaunix.net/uid-20717410-id-272331.html 把编译好的qwt的include文件夹下面 所有的.h文件 复制到qt目录下 然后在pro里 ...