编码和解码
平时开发的时候经常会遇到字符串编码和解码的问题,今天就初步探究下乱码问题,寻求解决乱码的方法。
概念
首先来分析下Java中的编码问题,文本在存储设备中是以字节的方式存储的,以特定的编码方案讲编码映射到字节数组。从存储设备中读取文本文件和写入文本就涉及到编码和解码。在JVM中,文本是以Unicode编码的形式存在的。下面先讲四个概念:
- 字符集合(Charater set): 是一组形状的集合,比如所有汉字的集合,它体现了字符的’’形状’’。
- 编码字符集(Coded character set): 是一组字符对应的编码(即数字),字符集中的每一个字符对应一个数字。在Java中每一个字符可以认为是一个16位的数字。
- 字符编码方案(Character-encoding schema): 讲字符编码映射到一个字节数组的方案,因为在磁盘中,所有信息都是以字节的方式存储的。因此Java中的16位编码必须转换成一个字节数组才能够存储。常见的编码方案有UTF-8、GBK等。
- 字符集(Charset):编码字符集和字符编码方案合起来称为字符集。
转换
编码
从JVM中的Unicode编码到字节数组,这个转换过程被称之为编码。转换的目的是为了存储或发送信息。同一个Unicode编码采用不同的字符集进行编码会得到不同的字节数组。编码的例子如下:
1 | //String的getBytes方法 |
解码
从字节数组到Unicode编码,这个转换过程被称之为解码。解码一般发生在需要从磁盘或者网络上得到的字节数组转换为字符串的场景。解码的时候一定要指定字符集,否则会使用默认的字符集进行解码。如果字符集错误,则会出现乱码。解码的例子如下:
1 | //使用String构造方法 |
默认字符集
Java的默认字符集有两种设置方法:
- 在执行Java程序的时候使用-Dfile.encoding参数指定
- 使用Properties类指定字符集。
两种方法同时使用的话,则在程序开始的时候使用参数指定的字符集,在执行Properties方法之后使用Properties指定的字符集。如果没有显式指定默认字符集,则使用操作系统默认的字符集。如果以上字符集无效,则会采用JDK中默认的字符集ISO-8859-1。
乱码与解决方案
乱码的原因
从上文可知,乱码的产生原因在于解码。采用了错误的字符集解码字节数组就会产生乱码。下面是个小例子:
1 | String before = "中国"; |
解决方案
在一般情况下我们可以得知字节数组的编码方案,在解码的时候指定正确的字符集就可以避免乱码。但有些时候我们并不能得知或者很难得知字节数组所采用的编码方案,那么按照常用的字符集进行解码的时候就很容易造成乱码。
在我们知道字节数组使用了哪几种编码方案之一的时候,我们可以判断得到的字符串在某个字符集下时候有效。日常开发中常见的字符编码方案有UTF-8、GBK、ISO-8859-1三种,其中ISO-8859-1是西欧标准,用这种编码方案编码中文等非拉丁语言的时候会得到值为63的一个字节。GBK编码中文采用的是两个字节,UTF-8采用的是变长编码方式,这两种可以采用以下方法判断具体是哪种编码方案。
1 | public static boolean isStringValidInCharset(String s, String charset) { |
上述方法只能简单的判断几种编码方案,下面介绍一种基于统计学的推断编码方案的工具cpdetector。cpdetector按照谁先返回非空探测结果,就以该结果为准的原则返回探测到的字符集编码。从理论上来说,只要字符串越长,返回结果正确的概率就越大。
1 | File file = new File(filePath); |