初探JDK源码之默认字符集

本文首发于个人微信公众号《andyqian》,期待你的关注!

前言

   今天我们以String类中的getBytes()方法为例,来看一看JDK源码,getBytes()方法在帮助文档中是 这样写的:

Encodes this String into a sequence of bytes using the platform's default charset, storing the result into a new byte array.

意思是:使用平台的默认字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。

恩,那我们就来对不同平台的默认字符集这个问题。一探究竟。

初探源码

  1. 首先我们进入getBytes()源码中:
public byte[] getBytes() {
  return StringCoding.encode(value, 0, value.length);
    }

该方法中直接返回,StringCoding.encode(value,0,value.length),那就再点击进去看看。代码如下:

static byte[] encode(char[] ca, int off, int len) {
        String csn = Charset.defaultCharset().name();
        try {
            // use charset name encode() variant which provides caching.
            return encode(csn, ca, off, len);
        } catch (UnsupportedEncodingException x) {
            warnUnsupportedCharset(csn);
        }
        try {
            return encode("ISO-8859-1", ca, off, len);
        } catch (UnsupportedEncodingException x) {
            // If this code is hit during VM initialization, MessageUtils is
            // the only way we will be able to get any kind of error message.
            MessageUtils.err("ISO-8859-1 charset not available: "
                             + x.toString());
            // If we can not find ISO-8859-1 (a required encoding) then things
            // are seriously wrong with the installation.
            System.exit(1);
            return null;
        }
    }

在这里,我们看到了,在上述方法中,通过 Charset.defaultCharset().getName() 获取系统默认的字符集。那我们就再点击进去看看,代码如下:

public static Charset defaultCharset() {
        if (defaultCharset == null) {
            synchronized (Charset.class) {
                String csn = AccessController.doPrivileged(
                    new GetPropertyAction("file.encoding"));
                Charset cs = lookup(csn);
                if (cs != null)
                    defaultCharset = cs;
                else
                    defaultCharset = forName("UTF-8");
            }
        }
        return defaultCharset;
    }

其实,在上述代码中,我们最关心的是这一行代码:

String csn = AccessController.doPrivileged(
                    new GetPropertyAction("file.encoding"));

点击进去后。如下所示:

public class GetPropertyAction implements PrivilegedAction<String> {
    private String theProp;
    private String defaultVal;

    public GetPropertyAction(String var1) {
        this.theProp = var1;
    }

    public GetPropertyAction(String var1, String var2) {
        this.theProp = var1;
        this.defaultVal = var2;
    }

    public String run() {
        String var1 = System.getProperty(this.theProp);
        return var1 == null?this.defaultVal:var1;
    }

在这里,我们已经看到了熟悉的代码:

System.getProperty(this.theProp);

到此,我们就可以在不同的平台做实验了。
此次实验的平台有:

  1. Linux平台
    系统: Ubuntu 14.04 LTS (中文环境)

  2. Windows平台
    系统: Windows 7 (中文环境)

不同平台的默认字符集实验

经测试: 在windows中通过CMD命令直接运行的获取的编码为:
测试JDK版本: java version "1.7.0_79"
步骤:
windows编码

D:\testpdfPath>javac Test.java

D:\testpdfPath>java Test
GBK

Linux (会话编码为UTF-8)编码(默认为UTF-8):

[andy@andyqian  /tmp]
$ javac Test.java

[andy@andyqian  /tmp]
$ java Test
UTF-8

初探JDK源码之默认字符集_第1张图片

Linux (会话编码为GBK)编码:

[andy@andyqian  /tmp]
$ ls
Test.java

[andy@andyqian /tmp]
$ javac Test.java

[andy@andyqian  /tmp]
$ java Test
GBK

以上试验表明:
Windows中文环境下,默认编码为:GBK。
Linux系统中文环境下,默认编码为: UTF-8。

不同的系统 file.encoding 的表现是不一样的。到此,我们已经查看了getBytes()中的默认字符集源码。

实验代码:

上述试验代码,非常简单,如下所述,有兴趣试验的朋友,可以新建一个Java类,命名为Test.java,复制到其中即可。

/**
 * author: andy
 * date: 17-11-24
 * blog: www.andyqian.com
 * version: 0.0.1
 * description:
 */
public class Test {

    public static void main(String[] args){
        System.out.println(System.getProperty("file.encoding"));
    }
}

这里需要注意的是: 直接复制到IDEA中,获取的结果可能会受idea影响。这也我直接使用原始命令来编译的原因。

SSH 远程编码:

  这里说个题外话,SSH本地机器编码会影响远程机器当前会话的编码。怎么说呢? 我们继续做实验。

机器准备:
一台编码为 en_US.UTF-8 编码的机器。
一台编码为 zh_GBK 编码的机器。

备注: 两台相同编码的机器也可以,修改一台机器的编码即可。

首先,我们通过Xshell直接来连接远程机器(UTF-8)通过 locale命令查看系统编码如下:

[andy@andyqian  /tmp/test]
$ locale
LANG=en_US.UTF-8
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_PAPER="en_US.UTF-8"
LC_NAME="en_US.UTF-8"
LC_ADDRESS="en_US.UTF-8"
LC_TELEPHONE="en_US.UTF-8"
LC_MEASUREMENT="en_US.UTF-8"
LC_IDENTIFICATION="en_US.UTF-8"
LC_ALL=

注意此时的系统编码为: UTF-8。

  1. 紧接着,我们先查看本地机器(GBK)的编码,
[andy@andyqian02  /home/andyqian]
$ locale
LANG=zh_CN.GBK
LC_CTYPE="zh_CN.GBK"
LC_NUMERIC="zh_CN.GBK"
LC_TIME="zh_CN.GBK"
LC_COLLATE="zh_CN.GBK"
LC_MONETARY="zh_CN.GBK"
LC_MESSAGES="zh_CN.GBK"
LC_PAPER="zh_CN.GBK"
LC_NAME="zh_CN.GBK"
LC_ADDRESS="zh_CN.GBK"
LC_TELEPHONE="zh_CN.GBK"
LC_MEASUREMENT="zh_CN.GBK"
LC_IDENTIFICATION="zh_CN.GBK"
LC_ALL=

再通过本地主机(GBK),ssh连接到 远程主机机器(UTF-8)上,再通过locale命令查看系统编码如下:

[andy@andyqian02  /home/andyqian]
$ ssh [email protected] 
[email protected]'s password:

登录后,查看编码:

[andy@andyqian01  /home/andyqian]
$ locale
LANG=zh_CN.GBK
LC_CTYPE="zh_CN.GBK"
LC_NUMERIC="zh_CN.GBK"
LC_TIME="zh_CN.GBK"
LC_COLLATE="zh_CN.GBK"
LC_MONETARY="zh_CN.GBK"
LC_MESSAGES="zh_CN.GBK"
LC_PAPER="zh_CN.GBK"
LC_NAME="zh_CN.GBK"
LC_ADDRESS="zh_CN.GBK"
LC_TELEPHONE="zh_CN.GBK"
LC_MEASUREMENT="zh_CN.GBK"
LC_IDENTIFICATION="zh_CN.GBK"
LC_ALL=

备注: 192.168.1.1 请替换成自己的主机地址

注意此时远程主机当前会话的编码已经变成了 zh_CN.GBK

此时通过:

System.getProperty("file.encoding")

获取到的编码为: GBK

这里再次证明。系统的默认编码,在不同场景下,表现形式也不一样,很容易造成乱码。我就吃过这样的亏。泪奔...

小结

  上面以getBytes()这个常用的方法,一步一步查看JDK源码,也动手实验了,不同系统的表现形式。其实想表达的是,多动手实验,会发现非常有意思,印象也会深刻很多。收获也会不一样。

精简版:

  1. 要指定特定编码,不用系统默认编码。避免不同系统默认编码不一致,导致乱码。

  2. 多动手试验。

最后: 祝大家周末愉快!


初探JDK源码之默认字符集_第2张图片

扫码关注,一起进步

个人博客: http://www.andyqian.com

你可能感兴趣的:(Java开发,Java,源码,编码)