记一个奇怪的编码转换问题,及探讨“错误: 编码GBK的不可映射字符” 的原因

Posted 10年 Java程序员,硬核人生!勇往直前,永不退缩!

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了记一个奇怪的编码转换问题,及探讨“错误: 编码GBK的不可映射字符” 的原因相关的知识,希望对你有一定的参考价值。

什么情况?

下面的一段简单代码,发现了奇怪的编码问题:

String docPath = "姝f枃";
// docPath = "正文"; // 注释1
System.out.println("default = " + docPath);

String docPath1 = new String(docPath.getBytes(), "GBK");
System.out.println("GBK = " + docPath1);
String docPath2 = new String(docPath.getBytes(), "UTF-8");
System.out.println("UTF-8 = " + docPath2);
String docPath3 = new String(docPath.getBytes(), "ISO-8859-1");
System.out.println("ISO-8859-1 = " + docPath3);

打印出来结果是?

default = 正文
GBK = 正文
UTF-8 = ????
ISO-8859-1 = ????

而把 注释1 打开,结果是:

default = ????
GBK = ????
UTF-8 = ????
ISO-8859-1 = ????

匪夷所思啊!  简直不可思议!!

 

查看文件编码:

 

 

 看起来编码是 ISO-8859-1 ?? (  经过后面的反复测试,发现, 这个高亮的选中行并不是 文件的实际编码!! ), 转换成utf-8, 结果显示:

 

 

 看起来当前文件编码是 GBK ? ———— 没错, 这个应该就是 文件的实际编码!!!

 

可是为什么 我明明 源码写的是 正文, 打印出来的是 “ 姝f枃 ” ?? 

 

难道是 console的显示编码的原因? console的显示编码  不可知,是不是 project encoding呢?

 

 

 

把Globa encoding 设置为GBK吧,结果还是一样的.. .

 

 

点击Reload Anyway , 结果,文件完全乱码了:

 

 这里说明一下 Reload 和 Convert 的区别, 测试发现, Reload  就相当于 重新用新的编码打散然后用项目编码格式编码,然后加载,然后覆盖;Convert 是直接用新编码转换然后覆盖;;

难道是IDEA的问题?

通过cmd 命令行执行,可以看到结果都是一样的:

 

 

  文件编码设置为GBK的时候, 出现了不可思议,如果文件编码设置是UTF-8, 则一切正常。。(同时可以看到 跟  -Dfile.encoding 参数是无关的!! 可以理解为  -Dfile.encoding 只在java 读取某些 外部 文件的时候的 默认编码?)

 

这就奇怪了。这说明class文件本身的内容就是那个了吧。跟IDEA 无关; 等等, 难道IDEA console 和 cmd 窗口的编码也是GBK?所以..

 

难道是。。。

 

新建一个文件TestEncoding.java,notepad++ 编辑, 把原来的内容拷贝过来,

import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.URLDecoder;
import java.util.Enumeration;

/**
 *
 * @author lk
 */
public class TestEncoding {

    public static void main(String[] args) throws  Exception {
//        System.out.println("args = " + args);
//        String localIp = getLocalIp();
//        System.out.println("localIp = " + localIp);

        String docPath = "姝f枃";
         docPath = "正文"; // 注释1
        testEncoding(docPath);
        System.out.println("正文 = " + docPath);
        System.out.println("姝f枃 = " + docPath);

    }

    private static void testEncoding(String docPath) throws UnsupportedEncodingException {
        System.out.println("默认 = " + docPath);

        // 测试默认-转换编码
        String docPath0 = new String(docPath.getBytes());
        System.out.println("默认 = " + docPath0);
        String docPath1 = new String(docPath.getBytes(), "GBK");
        System.out.println("默认 -> GBK = " + docPath1);
        String docPath2 = new String(docPath.getBytes(), "UTF-8");
        System.out.println("默认 -> UTF-8 = " + docPath2);
        String docPath3 = new String(docPath.getBytes(), "ISO-8859-1");
        System.out.println("默认 -> ISO-8859-1 = " + docPath3);

        // 测试转换编码
        String docPath00 = new String(docPath.getBytes("UTF-8"), "GBK");
        System.out.println(" -> UTF-8 -> GBK = " + docPath00);
        String docPath01 = new String(docPath.getBytes("UTF-8"),"UTF-8");
        System.out.println(" -> UTF-8 -> UTF-8 = " + docPath01);

        docPath00 = new String(docPath.getBytes("GBK"), "GBK");
        System.out.println(" -> GBK -> GBK = " + docPath00);
        docPath01 = new String(docPath.getBytes("GBK"),"UTF-8");
        System.out.println(" -> GBK -> UTF-8 = " + docPath01);
    }

}

 

 

编码设置为UTF-8 ,

 

 

 

 

javac编译,然后java执行一下,发现错误:

 

仔细检查发现,,正是 “姝f枃” 这几个字符,在utf-8 格式下是不能被解析的!! 替换为空格 “”就好了,但是 编译执行后发现 还是不对:

 

 

真是不可思议!  可见, 这个跟 是否是 IDEA 无关,这个是 编译之后的 class 文件本身内容的关系;

 

 

 

 有时候出现:

 

 

 哦, 错误: 编码GBK的不可映射字符。 ———— 连编译都不通过了!!

 

转换为GB2312,出现

 

 

 

点击“是”

 

 可见出现了乱码,特别注意到, 上面的页面的字样和 之前执行class 打印出来的 是一样的!

 

 

 

 

javac 同样有错:

  

 

郁闷了, 先把文件开始的那个文件内容复制,然后把Notepad++ 里的文件内容清空,然后编码转换为GB2312,然后复制文件内容; 

 

然后javac, java 一切正常了。

 

然后编码格式强制转换为Utf-8, 出现下面的情况:

 

 

 

然后javac编译, java运行,发现结果跟 在IDEA中发现的一样的奇怪、不可思议; 可见,刚刚的转换过程,出现了问题, 应该来说,不能直接这么转换!!

 

然后呢,再转换为GB2312,

 

一切又正常了!!

难道是我电脑问题? windows 默认编码是GBK,所以? 拿到linux 虚拟机上运行一遍,发现结果是一样的。

 

再仔细想想,貌似 文件编码是GBK的时候能够打印 源码所见的 正确的结果, 文件编码设置是UTF-8的时候,就一定会出现奇怪现象... 

 

为什么?

为此,我能想到的唯一的 解释是:编译之后的 class 文件本身内容不同,把不同看起来相同的 源码,用不同的编码格式保存,然后编译,然后对比class, 果然发现不同:

 

 ++++++++++++++++++++ ++++++++++++++++++++ ++++++++++++++++++++ 补充 start

后面研究了下 javac 命令,发现其实它是有一个  -encoding 参数的!!  之前一直没怎么注意!! 其实 上面奇怪现象的原因, 就是这个! javac 的默认编码参数是 系统编码, 所以... 

C:\\Users\\admin\\test>javac
用法: javac <options> <source files>
其中, 可能的选项包括:
  -g                         生成所有调试信息
  -g:none                    不生成任何调试信息
  -g:{lines,vars,source}     只生成某些调试信息
  -nowarn                    不生成任何警告
  -verbose                   输出有关编译器正在执行的操作的消息
  -deprecation               输出使用已过时的 API 的源位置
  -classpath <路径>            指定查找用户类文件和注释处理程序的位置
  -cp <路径>                   指定查找用户类文件和注释处理程序的位置
  -sourcepath <路径>           指定查找输入源文件的位置
  -bootclasspath <路径>        覆盖引导类文件的位置
  -extdirs <目录>              覆盖所安装扩展的位置
  -endorseddirs <目录>         覆盖签名的标准路径的位置
  -proc:{none,only}          控制是否执行注释处理和/或编译。
  -processor <class1>[,<class2>,<class3>...] 要运行的注释处理程序的名称; 绕过默认的搜索进程
  -processorpath <路径>        指定查找注释处理程序的位置
  -parameters                生成元数据以用于方法参数的反射
  -d <目录>                    指定放置生成的类文件的位置
  -s <目录>                    指定放置生成的源文件的位置
  -h <目录>                    指定放置生成的本机标头文件的位置
  -implicit:{none,class}     指定是否为隐式引用文件生成类文件
  -encoding <编码>             指定源文件使用的字符编码
  -source <发行版>              提供与指定发行版的源兼容性
  -target <发行版>              生成特定 VM 版本的类文件
  -profile <配置文件>            请确保使用的 API 在指定的配置文件中可用
  -version                   版本信息
  -help                      输出标准选项的提要
  -A关键字[=值]                  传递给注释处理程序的选项
  -X                         输出非标准选项的提要
  -J<标记>                     直接将 <标记> 传递给运行时系统
  -Werror                    出现警告时终止编译
  @<文件名>                     从文件读取选项和文件名

 

测试发现, 只要 javac 的 -encoding参数 和java 源码文件的编码格式一致, 那么就不会出现奇怪现象了!!

(原来如此,这么简单的原因没注意到,瞎jb忙 ... )

 

可是,为什么 IDEA中看到的GBK 编码的源码, 执行结果也是有乱码?  我感觉,可能文件都没有重新编译过吧。 可以把所有class 文件清除了然后再执行一遍。

 

 直接修改上图的 Project Encoding 是不会引起重新编译的! Global Encoding 作用不太清楚, 这个的设置, 不管它设置gbk 还是utf-8 ,结果都是 正确的。。

 

另外, 观察到IDEA 中也是可以设置javac 参数的:

 

 

 这个设置 确实有生效;

 

 ++++++++++++++++++++ ++++++++++++++++++++ ++++++++++++++++++++ 补充 end

 

 

 

怎么办?

总之,

 javac 的 -encoding参数 是相当关键的, 默认是系统编码,windows 就是 GBK!  所以当我们设置java 源码文件编码为utf-8,然后编译的时候 需要注意 设置 javac 的  -encoding参数 !!

1 可见不是可得!! 有些java源码文件,其中的字符看起来 是正确编码,其实那个是 用其他编码格式编码的结果! 但是为什么 它在源码中显示正确呢? 那大概是因为 两种编码格式比如 UTF-8和GBK 有一定的交集, 编码reload或convert之后 ,呈现了相同的展现,但其实 内容是不同的!—— 这TM 确实有些不好理解啊!换言之, 源码的字样 到了 class 中会发生一些变化! 两者不是相同的!! 所以打印出来的  会不同!

2 编码转换的时候要小心,特别是 reload,很容易出问题的! 不能直接这么转换。如果用Notepad++ 转换文件编码格式, 出现了 “无法恢复警告” 那么就不能转;同样, 用IDEA reload 也要小心。

3 还是 class 内容的不同;

 

以上是关于记一个奇怪的编码转换问题,及探讨“错误: 编码GBK的不可映射字符” 的原因的主要内容,如果未能解决你的问题,请参考以下文章

信道编码技术及电子系统工程应用的探讨论文

使用python编辑html,但lxml将漂亮的html实体转换为奇怪的编码

记一个C系语言中数值类型变量隐式转换的小坑

记一个C系语言中数值类型变量隐式转换的小坑

记一个C系语言中数值类型变量隐式转换的小坑

很奇怪,谁知道这是啥编码???