一、字符集
规定了某个文字对应的二进制数字存放方式(编码)和某串二进制数值代表了哪个文字(解码)的转换关系。对应到真实生活中,字符集就是对某种语言的称呼。例如:英语,汉语,日语。
Unicode是为了统一ASCII(仅英文字母)GBK(中文)等而发明的,就是要把地球上所有的语言的符号,都用统一的字符集来表示,一个编码真正做到了唯一。
不过,Unicode只是一个符号集,只规定了符号的 二进制代码,却没有规定这个二进制代码应该如何存储
二、字符编码
对于一个字符集来说要正确编码转码一个字符需要三个关键元素:字库表(character
repertoire)、编码字符集(coded character
set)、字符编码(character
encoding form)。
- 字库表:所有的单词,(为了能够涵盖世界上所有的字符)
- 编码字符集:单词在字库的哪里,索引id
- 字符编码:把序号转换成另外一种存储格式,(真正用的上的字符相对整个字库表来说比例非常低),是字符集的实现方式,因为计算机只能看懂bytes。我们需要一种用bytes来表示Unicode的方法这样才可以存储和传播他们。
UTF-8就是字符编码,变长。UTF-8是Unicode的实现方式之一
i) 好处一是节省流量。
ii) 好处二是ASCII编码实际上可以被看成是UTF-8编码的一部分,所以,大量只支持ASCII编码的历史遗留软件可以在UTF-8编码下继续工作。
iii) UTF-8编码能表示的字符数量远超GBK。
三、 Window笔记本
Windows 记事本里面的4个选项:
- ANSI,俗称“默认编码”,不是确定的一种编码,是Windows提出来的一种解决方案,会根据不同的语言版本选择。对于英文文件,是ASCII编码,对于简体中文是GB2312,繁体中文是Big5,日文是JIS
- Unicode, 这个选项用的是little endian
- Unicode big endian
- UTF-8
四、JAVA的内部编码
既然计算机在内存中统一使用的是unicode编码,JAVA自然也是如此了。
Java 内部处理字符使用的字序方式是 Unicode,这是一种通行全球的编码方式。 Unicode 另有一种储存或传输的格式,叫做 UTF-8。UTF-8 的格式在编码英文时,只需要 8 位,但是中文则是 24 位,所以中文字出现比例高的地方还是使用 UTF-16 比较节省空间。Java 的 Class File(也就是 bytecode)中有一字段叫做常数区(Constant Pool),一律使用 UTF-8 为字符编码。
五、 JAVA 的I/O 编码
虽然 Java 内部完整地使用 Unicode,但是你所使用的操作系统可不见得。
Java 现行的 IO 一律使用 Stream 的方式,相关的类别都放在 java.io 中。
输出 binary 的使用 OutputStream 的子类别,输入 binary 的资料使用 InputStream 的子类别;
输出文字的使用 Writer 的子类别,输入文字的资料使用 Reader 的子类别。
-
InputStream/OutputStream 与 Reader/Writer的区别
InputStream/OutputStream 会原封不动地传送资料,但是Reader/Writer 会将资料当作文字对待,所以 Reader/Writer 在「必要时」会把(文字)资料转码(会根据操作系统的默认编码方式进行转码)。 -
什么时候转码?:
Java 的 Stream(包括 Reader 和 Writer)是可以互相串接的。当 Reader 的资料来源是另一个 Reader 时,不转码,当 Reader 的资料来源是一个 InputStream 时,就会转码。当 Writer 的资料去处是另一个 Writer 时,不转码,当 Writer 的资料去处是一个 OutputStream 时,就会转码。 -
由什么码转成什么码?
这是可以指定的。
因为转码只发生在 Reader/InputStream 的交界处与 Writer/OutputStream 的交界处,所以正是由 InputStreamReader 和 OutputStreamWriter 此二类别负责,下面两个 constructor 的第二个参数,正是用来指定转码的方式。
new InputStreamReader(new FileInputStream(inputFilePath), encoding)
如果你清楚地知道你要读写的档案(或资料来源 / 去处)是采用某种编码方式,你也可以主动指定编码方式。但是,请记得抓取可能导致的 UnsupportedEncodingException,并务必处理之,不可对此例外置之不理,因为该 JRE 有可能没有附上此种编码表(也有可能你的编码名称给错)。 -
保持现场 ,保持资料的完整性
如果你不知道你的 I/O 资料来源或去处是用何种编码方式,那么你最好不要用 Reader 和 Writer,而应该直接用 InputStream 和 OutputStream,因为与其被 Reader 和 Writer 胡乱编码之后造成信息遗失或错乱,不如保持资料的完整不变,留待以后进一步解读。 -
建议
在我们的应用程序中涉及到 I/O 操作时只要注意指定统一的编解码 Charset 字符集,一般不会出现乱码问题,有些应用程序如果不注意指定字符编码,中文环境中取操作系统默认编码,如果编解码都在中文环境中,通常也没问题,但是还是强烈的不建议使用操作系统的默认编码,因为这样,你的应用程序的编码格式就和运行环境绑定起来了,在跨环境下很可能出现乱码问题。
六、 内存操作中的编码
Java中,String 类就提供转换到字节的方法,也支持将字节转换为字符串的构造函数。
Charset 提供 encode 与 decode 分别对应 char[] 到 byte[] 的编码和 byte[] 到 char[] 的解码。
参考: http://blog.csdn.net/mazhimazh/article/details/19327421
把一个字符串“中”赋给 String 类的一个对象 str,这个字符串“中”是按照操作系统默认编码方式进行编码,在中文 windows 系统中通常是“GBK”,“中”在GBK编码中是0xD6D0,在将该字符赋给str时,Java会对该字符串进行编码转换,即将GBK编码方式的“中”转换成Unicode编码方式的“中”,Unicode编码方式“中”的编码是0x4E2D,所以str在程序运行期间在内存中的二进制表示成16进制就是0x4E2D。
七、 造成MySQL乱码的一个原因
- 存入和取出时对应环节的编码不一致
我们把存入阶段的三次编解码使用的字符集编号为C1,C2,C3;取出时的三个字符集依次编号为C1’,C2’,C3’。那么存入的时候bash C1用的是UTF-8编码,取出的时候,C1'我们却使用了windows终端(默认是GBK编码),那么结果几乎一定是乱码。又或者存入MySQL的时候set names utf8(C2),而取出的时候却使用了set names gbk(C2'),那么结果也必然是乱码 - 单个流程中三步的编码不一致
即上面任意一个流程同一方向的三步中,只要两步或者两部以上的编码有不一致就有可能出现编解码错误。如果差异的两个字符集之间无法进行无损编码转换,那么就一定会出现乱码。