乐趣区

小师妹学JavaIO之文件编码和字符集Unicode

[toc]

小师妹学 JavaIO 之: 文件编码和字符集 Unicode

简介

小师妹一时兴起,使用了一项从来都没用过的新技能,没想却出现了一个无法解决的问题。把大象装进冰箱到底有几步?乱码的问题又是怎么解决的?快来跟 F 师兄一起看看吧。

更多精彩内容且看:

  • 区块链从入门到放弃系列教程 - 涵盖密码学, 超级账本, 以太坊,Libra, 比特币等持续更新
  • Spring Boot 2.X 系列教程: 七天从无到有掌握 Spring Boot- 持续更新
  • Spring 5.X 系列教程: 满足你对 Spring5 的一切想象 - 持续更新
  • java 程序员从小工到专家成神之路(2020 版)- 持续更新中, 附详细文章教程

使用 Properties 读取文件

这天,小师妹心情很愉悦,吹着口哨唱着歌,标准的 45 度俯视让人好不自在。

小师妹呀,什么事情这么高兴,说出来让师兄也沾点喜庆?

小师妹:F 师兄,最新我发现了一种新型的读取文件的方法,很好用的,就跟 map 一样:

public void usePropertiesFile() throws IOException {Properties configProp = new Properties();
        InputStream in = this.getClass().getClassLoader().getResourceAsStream("www.flydean.com.properties");
        configProp.load(in);
        log.info(configProp.getProperty("name"));
        configProp.setProperty("name", "www.flydean.com");
        log.info(configProp.getProperty("name"));
    }

F 师兄你看,我使用了 Properties 来读取文件,文件里面的内容是 key=value 形式的,在做配置文件使用的时候非常恰当。我是从 Spring 项目中的 properties 配置文件中得到的灵感,才发现原来 java 还有一个专门读取属性文件的类 Properties。

小师妹现在都会抢答了,果然青出于蓝。

乱码初现

小师妹你做得非常好,就这样触类旁通,很快 java 就要尽归你手了,后面的什么 scala,go,JS 等估计也统统不在话下。再过几年你就可以升任架构师,公司技术在你的带领之下一定会蒸蒸日上。

做为师兄,最大的责任就是给小师妹以鼓励和信心,给她描绘美好的未来,什么出任 CEO,赢取高富帅等全都不在话下。听说有个专业的词汇来描述这个过程叫做:画饼。

小师妹有点心虚:可是 F 师兄,我还有点小小的问题没有解决,有点中文的小小乱码 ….

我深有体会的点点头:马赛克是阻碍人类进步的绊脚石 … 哦,不是马赛克,是文件乱码,要想弄清楚这个问题,还要从那个字符集和文件编码讲起。

字符集和文件编码

在很久很久以前,师兄我都还没有出生的时候,西方世界出现了一种叫做计算机的高科技产品。

初代计算机只能做些简单的算数运算,还要使用人工打孔的程序才能运行,不过随着时间的推移,计算机的体积越来越小,计算能力越来越强,打孔已经不存在了,编程了人工编写的计算机语言。

一切都在变化,唯有一件事情没有变化。这件事件就是计算机和编程语言只流传在西方。而西方日常交流使用 26 个字母加有限的标点符号就够了。

最初的计算机存储可以是非常昂贵的,我们用一个字节也就是 8bit 来存储所有能够用到的字符,除了最开始的 1bit 不用以外,总共有 128 中选择,装 26 个小写 +26 个大写字母和其他的一些标点符号之类的完全够用了。

这就是最初的 ASCII 编码,也叫做美国信息交换标准代码(American Standard Code for Information Interchange)。

后面计算机传到了全球,人们才发现好像之前的 ASCII 编码不够用了,比如中文中常用的汉字就有 4 千多个,怎么办呢?

没关系,将 ASCII 编码本地化,叫做 ANSI 编码。1 个字节不够用就用 2 个字节嘛,路是人走出来的,编码也是为人来服务的。于是产生了各种如 GB2312, BIG5, JIS 等各自的编码标准。这些编码虽然与 ASCII 编码但是相互之间缺并不兼容。

这严重的影响了国际化的进程,这样还怎么去实现同一个地球,同一片家园的梦想?

于是国际组织出手了,制定了 UNICODE 字符集,为所有语言的所有字符都定义了一个唯一的编码,unicode 的字符集是从 U +0000 到 U +10FFFF 这么多个编码。

小师妹:F 师兄,那么 unicode 和我平时听说的 UTF-8,UTF-16,UTF-32 有什么关系呢?

我笑着问小师妹:小师妹,把大象装进冰箱有几步?

小师妹:F 师兄,脑筋急转弯的故事,已经不适合我了,大象装进冰箱有三步,第一打开冰箱,第二把大象装进去,第三关上冰箱,完事了。

小师妹呀,作为一个有文化的中国人,要真正的承担起民族复兴,科技进步的大任,你的想法是很错误的,不能光想口号,要有实际的可操作性的方案才行,要不然我们什么时候才能够打造秦芯,唐芯和明芯呢?

师兄说的对,可是这跟 unicode 有什么关系呢?

unicode 字符集最后是要存储到文件或者内存里面的,那怎么存呢?使用固定的 1 个字节,2 个字节还是用边长的字节呢?根据编码方式的不同,可以分为 UTF-8,UTF-16,UTF-32 等多种编码方式。

其中 UTF- 8 是一种变长的编码方案,它使用 1 - 6 个字节来存储。UTF-16 使用 2 个或者 4 个字节来存储,JDK9 之后的 String 的底层编码方式变成了两种:LATIN1 和 UTF16。

而 UTF-32 是使用 4 个字节来存储。这三种编码方式中,只有 UTF- 8 是兼容 ASCII 的,这也是为什么国际上 UTF- 8 编码方式比较通用的原因(毕竟计算机技术都是西方人搞出来的)。

解决 Properties 中的乱码

小师妹,要解决你 Properties 中的乱码问题很简单,Reader 基本上都有一个 Charsets 的参数,通过这个参数可以传入要读取的编码方式,我们把 UTF- 8 传进去就行了:

public void usePropertiesWithUTF8() throws IOException{Properties configProp = new Properties();
        InputStream in = this.getClass().getClassLoader().getResourceAsStream("www.flydean.com.properties");
        InputStreamReader inputStreamReader= new InputStreamReader(in, StandardCharsets.UTF_8);
        configProp.load(inputStreamReader);
        log.info(configProp.getProperty("name"));
        configProp.setProperty("name", "www.flydean.com");
        log.info(configProp.getProperty("name"));
    }

上面的代码中,我们使用 InputStreamReader 封装了 InputStream,最终解决了中文乱码的问题。

真. 终极解决办法

小师妹又有问题了:F 师兄,这样做是因为我们知道文件的编码方式是 UTF-8,如果不知道该怎么办呢?是选 UTF-8,UTF-16 还是 UTF-32 呢?

小师妹问的问题越来越刁钻了,还好这个问题我也有准备。

接下来介绍我们的终极解决办法,我们将各种编码的字符最后都转换成 unicode 字符集存到 properties 文件中,再读取的时候是不是就没有编码的问题了?

转换需要用到 JDK 自带的工具:

 native2ascii -encoding utf-8 file/src/main/resources/www.flydean.com.properties.utf8 file/src/main/resources/www.flydean.com.properties.cn

上面的命令将 utf- 8 的编码转成了 unicode。

转换前:

site=www.flydean.com
name= 程序那些事 

转换后:

site=www.flydean.com
name=\u7a0b\u5e8f\u90a3\u4e9b\u4e8b

再运行下测试代码:

public void usePropertiesFileWithTransfer() throws IOException {Properties configProp = new Properties();
        InputStream in = this.getClass().getClassLoader().getResourceAsStream("www.flydean.com.properties.cn");
        configProp.load(in);
        log.info(configProp.getProperty("name"));
        configProp.setProperty("name", "www.flydean.com");
        log.info(configProp.getProperty("name"));
    }

输出正确的结果。

如果要做国际化支持,也是这样做的。

总结

千辛万苦终于解决了小师妹的问题,F 师兄要休息一下。

本文的例子 https://github.com/ddean2009/learn-java-io-nio

本文作者:flydean 程序那些事

本文链接:http://www.flydean.com/io-charsets-properties/

本文来源:flydean 的博客

欢迎关注我的公众号: 程序那些事,更多精彩等着您!

退出移动版