做开发也有很多年了,时常会遇到使用字符集的地方,有时候就会想,使用硬编码指定字符集也太不专业了,有没有字符集的常量呢?与字符集相关的类有一个特别明显的就是Charset,这个类就代表字符集,我们很少使用它的构造方法来创建字符集,它有一些获取Charset实例对象的静态方法,如下:
看方法名也很容易理解其中的含义,于是从这四个静态方法中进入源码查看,找到了一些字符字符集相关的常量,它们定义在sun.nio.cs包里面,里面有各种字符集类,如GBK、UTF_8、UTF_16、ISO_8859_1、Unicode等非常非常多的字符集类,应该是包含了所有的字符集了,正兴高采烈地使用它们时,发现一运行就报错了,大概意思是这些类是不允许使用的,晕!
一次偶然的机会,翻看Android中的Charset.defaultCharsets()的源码,发现了新大陆,如下:
public static Charset defaultCharset() {
// Android-changed: Use UTF_8 unconditionally.
synchronized (Charset.class) {
if (defaultCharset == null) {
defaultCharset = java.nio.charset.StandardCharsets.UTF_8;
}
return defaultCharset;
}
}
代码注释中有说明这是Android修改过的源码,与标准JDK的源码不一样了哦。
StandardCharsets,翻译过来就是标准字符集,但是没有GBK呀,气人,GBK不标准吗?如下:
经实验,这些常量可以使用。
在使用OkHttp的时候,也会涉及到字符集,所以我想OkHttp这么专业不可能用硬编码吧,于是进它源码里面找找他是用的什么样的字符集常量,关于字符集,在获取String的时候肯定是用到了的,于是可以从获取String的函数的源码入手:
response.body().string()
通过源码发现,在它的Util类里面封装了一些字符集常量,如下:
public static final Charset UTF_8 = Charset.forName("UTF-8");
public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
private static final Charset UTF_16_BE = Charset.forName("UTF-16BE");
private static final Charset UTF_16_LE = Charset.forName("UTF-16LE");
private static final Charset UTF_32_BE = Charset.forName("UTF-32BE");
private static final Charset UTF_32_LE = Charset.forName("UTF-32LE");
可惜,也没有GBK,哎!
后来开发用了Kotlin,Kotlin是个好东西,可以少写很多代码,比如file.readText()就可以把一个文件对象关联的文本内容全部读取出来,这里肯定也用到了字符集,点进去看源码,发现Kotlin用的是自带字符集常量,如下:
可惜,也没有GBK。
有如下三种字符集常量可以使用:
个人感觉使用Kotlin自带的会比较方便,它的类名Charsets比较短也比较好记而且类名很专业。通过读别人的源码,我们也可以学到,其实可以自己定义一个类,里面放一些字符集常量,不一定非要去JDK里找,这样的话我们自己定义,还可以增加GBK常量呢,说到这里,Kotlin有扩展属性功能,我们可以给Kotlin的Charsets类扩展一个GBK属性,这样就完美了,如下:
val Charsets.GBK: Charset
get() = Charset.forName("GBK")
fun main() {
println(Charsets.GBK)
}
这里不用担心每次调用Charsets.GBK时都会创建新的字符集对象,因为Charset.forName()函数会缓存使用到的字符集,每次会先从缓存中取,取不到才会去创建。
最后一点,有时候传参时,可能函数接收的字符集就是一个String,而不是Charset对象,怎么办呢?Charset对象有如下三个方法可以返回它的名字:
println(Charsets.GBK.displayName())
println(Charsets.GBK.name())
println(Charsets.GBK.toString())
输出都是:GBK
点源码进去看,发现toString返回的是name()函数,而name()函数和displayName()函数都是返回name属性,似乎没有区别,看看文档说明,如下:
displayName() // 返回此 charset 用于默认语言环境的可读名称。
name() // 返回此 charset 的规范名称。
似乎name()函数比较好,因为规范嘛,绝对是可用的,而displayName保不准哪天就改源码了,如GBK编码给你返回“汉字内码扩展规范”,根据文档描述返回默认语言环境的可读名称,如果在中国,很有可能 返回汉字的描述名称嘛!