什么情況?
下面的一段簡單代碼,發現了奇怪的編碼問題:
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
怎么辦?
總之,
0 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 內容的不同;