初探JDK源码之默认字符集

Posted andyqian

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了初探JDK源码之默认字符集相关的知识,希望对你有一定的参考价值。

前言  

今天我们以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 数组中。

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

初探源码

首先我们进入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 (中文环境)

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

测试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

如图所示:

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命令查看系统编码如下:

  1. [andy@andyqian02  /home/andyqian]
    $ ssh andyqian@192.168.1.1 
    andyqian@192.168.1.1's password:

登录后,查看编码:

  1. [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=


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

zh_CN.GBK

此时通过:

System.getProperty("file.encoding")

获取到的编码为: GBK

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

小结

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

精简版:

  1. 有关编码的方法一定要指定特定编码,不要使用系统默认编码。避免不同系统默认编码不一致,导致乱码。

  2. 多动手试验。


最后: 祝大家周末愉快!

以上是关于初探JDK源码之默认字符集的主要内容,如果未能解决你的问题,请参考以下文章

初探JSP与LEeclipse

JDK源码阅读之 HashMap

1.4JDK源码阅读之AbstractStringBuilder

MySQL初探之Windows版字符问题

iOS开发底层之alloc原理初探

0009JDK源码分析之拓展字符集