java中的字符集与编码

1,707 阅读6分钟

这是《Java与MySQL字符集与编码》 第三篇。本篇主要是从Java编码、内外码、string编码、io编码、jdbc 编码 这几个方面,讲一下Java相关的编码问题。这篇内容稍微有点多,耐心点~

1 系统编码

为什么要提系统编码呢? 因为这涉及到后面Java默认编码的问题。在Java 编译、运行时,如果没有设置编码,会默认读取系统编码。 Mac上查看编码是这么来操作的:

2 内码与外码,以及转换

引用个概念。

内码 & 外码
内码 :程序内部使用的字符编码,特别是某种语言实现其 char 或 String 类型在内存里用的内部编码。Java 的内码就是 UTF-16。

外码 :程序与外部交互时外部使用的字符编码。简单的来说就是除了内码都可以认为是“外码”(包括 class 文件的编码)。

如概念所述,Java的内码是UTF-16, 而外码则是需要根据不同的场景进行设置的。 数据到达Java之后,真正需要处理的时候,就需要转换成UTF-16内码进行了。包括下面的讨论也都是在讨论外码。

3 Java在跟编码相关的两个参数

java 中跟编码相关的系统参数有如下两个:

file.encoding
sun.jnu.encoding

我们这篇文章只讲一下 file.encoding 。

3.1 file.encoding 是什么?

文件编码。 Java源码中,有四个类调用了file.encoding 这个属性:

引用自 www.jianshu.com/p/7688863e3…

(1)java.nio.Charset.defaultCharset()

默认字符集是在 java 虚拟机启动时决定的,依赖于 java 虚拟机所在的操作系统的区域以及字符集。
从代码中可以看出,默认字符集就是从 file.encoding 这个属性中获取的。
此处的默认字符集会影响字符串、文件字符流读写等的默认编码。

(2) URLEncoder.encode(String) Web环境中最常遇到的编码使用。 (3)com.sun.org.apache.xml.internal.serializer.Encoding.getMimeEncodings(String) 影响对无编码设置的xml文件的读取 。 (4)javax.print.DocFlavor影响打印的编码。

从如上信息可以得到:

file.encoding 会影响无指定编码的字符串、读写文件、URL编码、打印等内容。

3.2 file.encoding 未指定时,默认编码是什么?

应用里面,在部署的时候如果指定了-Dfile.encoding=UTF-8类似的设定,那么后续的默认编解码就是用这个值,如果没有设定,则使用系统的编解码方案。

来段代码:

import java.nio.charset.Charset;

public class Main{
    public static void main(String[] args) {
        System.out.println("i am a 中国人");
        System.out.println(Charset.defaultCharset().name());
            System.out.println(System.getProperty("sun.jnu.encoding"));
            System.out.println(System.getProperty("file.encoding"));
    }
}

编译一下: javac Main.java 然后执行: java Main. 看下结果

stackoverflow上面关于默认编码 有个经典的回答,stackoverflow.com/questions/3…

大意是

在Java进程启动之后,默认字符集 就确定了,后续即使使用 System.setProperty("file.encoding", "UTF-8"); 修改,只会修改配置项值 Charset.defaultCharset()的值,但是实际解码的时候,还是使用了系统初始化的编解码方案,不会改变默认字符集编码。 如果需要指定读取文件内容的编码,需要通过字符流的构造器InputStreamReader(InputStream in, Charset cs)设置。

还是上面的代码,那我们在jvm启动时指定一下file.encoding 看下.

发现 Charset.defaultCharset().name() 已经变成了GBK.并且输出也乱码了。这是因为编码的时候使用了GBK,而输出到系统item窗口时我们是按照UTF-8解码的,所以就乱码了。

我们再试下通过system来改一下编码,看看是不是stackoverflow 上所说的那样。

import java.nio.charset.Charset;

public class Main{
    public static void main(String[] args) {
        System.setProperty("file.encoding", "UTF-8"); // 设置file.encoding 
        System.out.println("i am a 中国人");
        System.out.println(Charset.defaultCharset().name());
            System.out.println(System.getProperty("sun.jnu.encoding"));
            System.out.println(System.getProperty("file.encoding"));
    }
}

编译 javac Main.java 运行时指定 java -Dfile.encoding=GBK Main

我们发现,虽然程序的第一行就是设定编码为UTF-8,但是实际的编码还是GBK,这一点跟stackoverflow上一致。 而Charset.defaultCharset 也是gbk , 跟启动时指定的参数一致,这一点跟stackoverflow 上不一致。我用的java version "1.8.0_201" ,也可能跟我的版本有关系,这个暂时就不纠结了。

3.3 指定file.encoding 会产生什么影响?

file.encoding 对string的影响,我们在第4部分讲string 时进行验证。 file.encoding对读写文件内容的影响,我们这篇文章中就先不讲了,可以直接参考 www.jianshu.com/p/7688863e3… 这篇文章。

4 Java 中string 编码问题

灵魂三问 + 长度问题 + file.encoding 对string 的影响。

4.1 string 是什么

string 本身就是一个char数组,那么要表示成字节数组,就得涉及编码问题。 下面我们看几个关于string字符编码的知识点。

4.2 string 从哪儿来, 到哪儿去

我们先看一段代码,里面涉及到几种不同编码

看下输出:

这段代码中主要阐述string是通过字节数组 + 字符编码来生成的。里面讲了三种编码。由于系统默认的编码是UTF-8, 其实上面一段代码底层是这样来操作的【这就是file.encoding 对无编码string的影响】。

通过getBytes 获取了字符串的字节数组,然后按照UTF-8编码重新定义一个新的string用于输出,肯定只有utf-8 的能正常数据。

我们改一下new String 的编码,再来看下:

结果都正常了:

4.3 string 的长度

关于string的长度,我们最常用的一个函数是string.length(), 你真的懂它的真实用途吗? 先来一个demo吧

 try {
            String s = new String("iam中国人");
            System.out.println("========s======= " + s);
            System.out.println("length: " + s.length());
            System.out.println("codePointCount: " + s.codePointCount(0, s.length()));
            String s2 = "\uD834\uDD1E";
            System.out.println("========s2======= " +s2);
            System.out.println("length: " + s2.length());
            System.out.println("codePointCount: " + s2.codePointCount(0, s2.length()));
            String s3 = "\uD83D\uDE02";
            System.out.println("========s3======= " +s3);
            System.out.println("length:" + s3.length());
            System.out.println("codePointCount: " + s3.codePointCount(0, s3.length()));

        } catch (Exception e) {
            System.out.println(e);
        }

结果是这样的:

从结果我们可以看出,length 跟 codePointCount 返回的结果有时相同,有时不同。 那我先看下二者的区别。 length 是返回代码单元的个数。这就是为什么s1返回了6 ,而s2跟s3 返回了2 而不是1. 因为音乐符号跟表情虽然是一个符号,但是其码元是2.

而codePointCount 是码点的个数,一个字符在字符集中肯定只对应一个码点,所以就是结果中的6、1、1.

5 Java中的IO编码问题

如前文所述,Java中跟字符编码比较相关的,除了string就是IO了,这篇文章我们先不讲,等后续有时间再细讲。如果感兴趣可以直接看 xiaogd.net/category/字符… 相关的文章。

6 spring boot 中jdbc编码设置

上面讲完了Java中对字符集、编码的介绍,那这部分为我们下一节讲MySQL编码做个铺垫。 Java里面跟MySQL交互,主要涉及编码的地方就是jdbc 。spring 中涉及JDBC的编码是

这部分我们就先不写demo了,在下一篇文章中,讲解MySQL编码时,会写详细的demo。

7 总结

本文主要讲解了Java中的编码问题(IO的没讲),file.encoding 的知识,重点讲了string的编码问题。最后提到了jdbc的编码问题,为我们下一节的MySQL编码问题做个铺垫。

8 参考文献

Java中的编码: www.colabug.com/2020/0218/7…

java运行时参数file.encoding和sun.jnu.encoding详解: www.jianshu.com/p/7688863e3…

字符集编码相关系列: xiaogd.net/category/字符…