記一個奇怪的編碼轉換問題,及探討“錯誤: 編碼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 內容的不同;

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM