天天看點

Java編碼及網絡傳輸中的編碼問題

Java編碼及網絡傳輸中的編碼問題

2012-01-13 12:21 cherishLC cherishLC的部落格 我要評論(0) 字号:T | T

Java編碼及網絡傳輸中的編碼問題

本文主要講作者對FTP搜尋中遇到編碼的問題進行了研究,主要分為三部分:表單資料的編碼、網址的編碼、亂碼恢複。詳細請看下文。

AD:

近來試着FTP搜尋,遇到編碼問題,研究了下。

Java内部的String為Unicode編碼,每個字元占兩個位元組。

Java編解碼方法如下:

  1. String str = "hi好啊me";  
  2. byte[] gbkBytes=str.getBytes("GBK");//将String的Unicode編碼轉為GBK編碼,輸出到位元組中  
  3. String string=new String(gbkBytes,"GBK");//gbkBytes中的位元組流以GBK方案解碼成Unicode形式的Java字元串 

1、表單資料的編碼

現在的問題是,在網絡中,不知道用戶端發過來的位元組流的編碼方案(發送前浏覽器會對資料編碼!!!各個浏覽器還不一樣!!!)

解決方案如下:

Java編碼及網絡傳輸中的編碼問題

當然URLEncoder.encode(str, "utf-8")和URLDecoder.decode(strReceive,"utf-8")方法中的編碼方案要一緻。

2、網址的編碼

但以上方法隻适合表單資料的送出;對于URL則不行!!!原因是URLEncoder把'/'也編碼了,浏覽器發送時報錯!!!那麼,隻要http://IP/子目錄把http://IP/這部分原封不動(當然這部分不要有中文),之後的資料以'/'分割後分段編碼即可。

代碼如下:

  1. /**  
  2.  * 對{@link URLEncoder#encode(String, String)}的封裝,但不編碼'/'字元,對其他字元分段編碼  
  3.  *   
  4.  * @param str  
  5.  *            要編碼的URL  
  6.  * @param encoding  
  7.  *            編碼格式  
  8.  * @return 字元串以字元'/'隔開,對每一段單獨編碼以encoding編碼格式編碼  
  9.  * @version: 2012_01_10  
  10.  *           <p>  
  11.  *           注意:未考慮':',如直接對http://編解碼,會産生錯誤!!!請在使用前将其分離出來,可以使用  
  12.  *           {@link #encodeURLAfterHost(String, String)}方法解決此問題  
  13.  *           <p>  
  14.  *           注意:對字元/一起編碼,導緻URL請求異常!!  
  15.  */ 
  16. public static String encodeURL(String str, String encoding) {  
  17.     final char splitter = '/';  
  18.     try {  
  19.         StringBuilder sb = new StringBuilder(2 * str.length());  
  20.         int start = 0;  
  21.         for (int i = 0; i < str.length(); i++) {  
  22.             if (str.charAt(i) == splitter) {  
  23.                 sb.append(URLEncoder.encode(str.substring(start, i),  
  24.                         encoding));  
  25.                 sb.append(splitter);  
  26.                 start = i + 1;  
  27.             }  
  28.         }  
  29.         if (start < str.length())  
  30.             sb.append(URLEncoder.encode(str.substring(start), encoding));  
  31.         return sb.toString();  
  32.     } catch (UnsupportedEncodingException e) {  
  33.         e.printStackTrace();  
  34.     }  
  35.     return null;  
  36. }  
  37. /**  
  38.  * 對IP位址後的URL通過'/'分割後進行分段編碼.  
  39.  * <p>  
  40.  * 對{@link URLEncoder#encode(String, String)}  
  41.  * 的封裝,但不編碼'/'字元,也不編碼網站部分(如ftp://a.b.c.d/部分,檢測方法為對三個'/'字元的檢測,且要求前兩個連續),  
  42.  * 對其他字元分段編碼  
  43.  *   
  44.  * @param str  
  45.  *            要編碼的URL  
  46.  * @param encoding  
  47.  *            編碼格式  
  48.  * @return IP位址後字元串以字元'/'隔開,對每一段單獨編碼以encoding編碼格式編碼,其他部分不變  
  49.  * @version: 2012_01_10  
  50.  *           <p>  
  51.  *           注意:對字元/一起編碼,導緻URL請求異常!!  
  52.  */ 
  53. public static String encodeURLAfterHost(String str, String encoding) {  
  54.     final char splitter = '/';  
  55.     int index = str.indexOf(splitter);//第一個'/'的位置  
  56.     index++;//移到下一位置!!  
  57.     if (index < str.length() && str.charAt(index) == splitter) {//檢測第一個'/'之後是否還是'/',如ftp://  
  58.         index++;//從下一個開始  
  59.         index = str.indexOf(splitter, index);//第三個'/';如ftp://anonymous:[email protected]:219.223.168.20/中的最後一個'/'  
  60.         if (index > 0) {  
  61.             return str.substring(0, index + 1)  
  62.                     + encodeURL(str.substring(index + 1), encoding);//如ftp://anonymous:[email protected]:219.223.168.20/天空  
  63.         } else 
  64.             return str;//如ftp://anonymous:[email protected]:219.223.168.20  
  65.     }  
  66.     return encodeURL(str, encoding);  
  67. }  
  68. /**  
  69.  * 對IP位址後的URL通過'/'分割後進行分段編碼.  
  70.  * 此方法與{@link #decodeURLAfterHost(String, String)}配對使用  
  71.  * @param str  
  72.  *            要解碼的URL  
  73.  * @param encoding  
  74.  *            str的編碼格式  
  75.  * @return IP位址後字元串以字元'/'隔開,對每一段單獨解碼以encoding編碼格式解碼,其他部分不變  
  76.  * @version: 2012_01_10  
  77.  *   
  78.  *           <p>  
  79.  *           注意:對字元/一起解碼,将導緻URL請求異常!!  
  80.  */ 
  81. public static String decodeURLAfterHost(String str, String encoding) {  
  82.     final char splitter = '/';  
  83.     int index = str.indexOf(splitter);//第一個'/'的位置  
  84.     index++;//移到下一位置!!  
  85.     if (index < str.length() && str.charAt(index) == splitter) {//檢測第一個'/'之後是否還是'/',如ftp://  
  86.         index++;//從下一個開始  
  87.         index = str.indexOf(splitter, index);//第三個'/';如ftp://anonymous:[email protected]:219.223.168.20/中的最後一個'/'  
  88.         if (index > 0) {  
  89.             return str.substring(0, index + 1)  
  90.                     + decodeURL(str.substring(index + 1), encoding);//如ftp://anonymous:[email protected]:219.223.168.20/天空  
  91.         } else 
  92.             return str;//如ftp://anonymous:[email protected]:219.223.168.20  
  93.     }  
  94.     return decodeURL(str, encoding);  
  95. }  
  96. /**  
  97.  * 此方法與{@link #encodeURL(String, String)}配對使用  
  98.  * <p>  
  99.  * 對{@link URLDecoder#decode(String, String)}的封裝,但不解碼'/'字元,對其他字元分段解碼  
  100.  *   
  101.  * @param str  
  102.  *            要解碼的URL  
  103.  * @param encoding  
  104.  *            str的編碼格式  
  105.  * @return 字元串以字元'/'隔開,對每一段單獨編碼以encoding編碼格式解碼  
  106.  * @version: 2012_01_10  
  107.  *   
  108.  *           <p>  
  109.  *           注意:對字元/一起編碼,導緻URL請求異常!!  
  110.  */ 
  111. public static String decodeURL(String str, String encoding) {  
  112.     final char splitter = '/';  
  113.     try {  
  114.         StringBuilder sb = new StringBuilder(str.length());  
  115.         int start = 0;  
  116.         for (int i = 0; i < str.length(); i++) {  
  117.             if (str.charAt(i) == splitter) {  
  118.                 sb.append(URLDecoder.decode(str.substring(start, i),  
  119.                         encoding));  
  120.                 sb.append(splitter);  
  121.                 start = i + 1;  
  122.             }  
  123.         }  
  124.         if (start < str.length())  
  125.             sb.append(URLDecoder.decode(str.substring(start), encoding));  
  126.         return sb.toString();  
  127.     } catch (UnsupportedEncodingException e) {  
  128.         e.printStackTrace();  
  129.     }  
  130.     return null;  

3、亂碼了還能恢複?

問題如下:

Java編碼及網絡傳輸中的編碼問題

貌似圖中的utf-8改成iso8859-1是可以的,utf-8在字元串中有中文時不行(但英文部分仍可正确解析)!!!畢竟GBK的位元組流對于utf-8可能是無效的,碰到無效的字元怎麼解析,是否可逆那可不好說啊。

測試代碼如下:

  1. package tests;  
  2. import java.io.UnsupportedEncodingException;  
  3. import java.net.URLEncoder;  
  4. /**  
  5.  * @author LC  
  6.  * @version: 2012_01_12  
  7.  */ 
  8. public class TestEncoding {  
  9.     static String utf8 = "utf-8";  
  10.     static String iso = "iso-8859-1";  
  11.     static String gbk = "GBK";  
  12.     public static void main(String[] args) throws UnsupportedEncodingException {  
  13.         String str = "hi好啊me";  
  14.         //      System.out.println("?的十六進制為:3F");  
  15.         //      System.err  
  16.         //              .println("出現中文時,如果編碼方案不支援中文,每個字元都會被替換為?的對應編碼!(如在iso-8859-1中)");  
  17.         System.out.println("原始字元串:\t\t\t\t\t\t" + str);  
  18.         String utf8_encoded = URLEncoder.encode(str, "utf-8");  
  19.         System.out.println("用URLEncoder.encode()方法,并用UTF-8編碼後:\t\t" + utf8_encoded);  
  20.         String gbk_encoded = URLEncoder.encode(str, "GBK");  
  21.         System.out.println("用URLEncoder.encode()方法,并用GBK編碼後:\t\t" + gbk_encoded);  
  22.         testEncoding(str, utf8, gbk);  
  23.         testEncoding(str, gbk, utf8);  
  24.         testEncoding(str, gbk, iso);  
  25.         printBytesInDifferentEncoding(str);  
  26.         printBytesInDifferentEncoding(utf8_encoded);  
  27.         printBytesInDifferentEncoding(gbk_encoded);  
  28.     }  
  29.     /**  
  30.      * 測試用錯誤的編碼方案解碼後再編碼,是否對原始資料有影響  
  31.      *   
  32.      * @param str  
  33.      *            輸入字元串,Java的String類型即可  
  34.      * @param encodingTrue  
  35.      *            編碼方案1,用于模拟原始資料的編碼  
  36.      * @param encondingMidian  
  37.      *            編碼方案2,用于模拟中間的編碼方案  
  38.      * @throws UnsupportedEncodingException  
  39.      */ 
  40.     public static void testEncoding(String str, String encodingTrue,  
  41.             String encondingMidian) throws UnsupportedEncodingException {  
  42.         System.out.println();  
  43.         System.out  
  44.                 .printf("%s編碼的位元組資料->用%s解碼并轉為Unicode編碼的JavaString->用%s解碼變為位元組流->讀入Java(用%s解碼)後變為Java的String\n",  
  45.                         encodingTrue, encondingMidian, encondingMidian,  
  46.                         encodingTrue);  
  47.         System.out.println("原始字元串:\t\t" + str);  
  48.         byte[] trueEncodingBytes = str.getBytes(encodingTrue);  
  49.         System.out.println("原始位元組流:\t\t" + bytesToHexString(trueEncodingBytes)  
  50.                 + "\t\t//即用" + encodingTrue + "編碼後的位元組流");  
  51.         String encodeUseMedianEncoding = new String(trueEncodingBytes,  
  52.                 encondingMidian);  
  53.         System.out.println("中間字元串:\t\t" + encodeUseMedianEncoding + "\t\t//即用" 
  54.                 + encondingMidian + "解碼原始位元組流後的字元串");  
  55.         byte[] midianBytes = encodeUseMedianEncoding.getBytes("Unicode");  
  56.         System.out.println("中間位元組流:\t\t" + bytesToHexString(midianBytes)  
  57.                 + "\t\t//即中間字元串對應的Unicode位元組流(和Java記憶體資料一緻)");  
  58.         byte[] redecodedBytes = encodeUseMedianEncoding  
  59.                 .getBytes(encondingMidian);  
  60.         System.out.println("解碼位元組流:\t\t" + bytesToHexString(redecodedBytes)  
  61.                 + "\t\t//即用" + encodingTrue + "解碼中間字元串(流)後的字元串");  
  62.         String restored = new String(redecodedBytes, encodingTrue);  
  63.         System.out.println("解碼字元串:\t\t" + restored + "\t\t和原始資料相同?  " 
  64.                 + restored.endsWith(str));  
  65.     }  
  66.     /**  
  67.      * 将字元串分别編碼為GBK、UTF-8、iso-8859-1的位元組流并輸出  
  68.      *   
  69.      * @param str  
  70.      * @throws UnsupportedEncodingException  
  71.      */ 
  72.     public static void printBytesInDifferentEncoding(String str)  
  73.             throws UnsupportedEncodingException {  
  74.         System.out.println("");  
  75.         System.out.println("原始String:\t\t" + str + "\t\t長度為:" + str.length());  
  76.         String unicodeBytes = bytesToHexString(str.getBytes("unicode"));  
  77.         System.out.println("Unicode bytes:\t\t" + unicodeBytes);  
  78.         String gbkBytes = bytesToHexString(str.getBytes("GBK"));  
  79.         System.out.println("GBK bytes:\t\t" + gbkBytes);  
  80.         String utf8Bytes = bytesToHexString(str.getBytes("utf-8"));  
  81.         System.out.println("UTF-8 bytes:\t\t" + utf8Bytes);  
  82.         String iso8859Bytes = bytesToHexString(str.getBytes("iso-8859-1"));  
  83.         System.out.println("iso8859-1 bytes:\t" + iso8859Bytes + "\t\t長度為:" 
  84.                 + iso8859Bytes.length() / 3);  
  85.         System.out.println("可見Unicode在之前加了兩個位元組FE FF,之後則每個字元兩位元組");  
  86.     }  
  87.     /**  
  88.      * 将該數組轉的每個byte轉為兩位的16進制字元,中間用空格隔開  
  89.      *   
  90.      * @param bytes  
  91.      *            要轉換的byte序列  
  92.      * @return 轉換後的字元串  
  93.      */ 
  94.     public static final String bytesToHexString(byte[] bytes) {  
  95.         StringBuilder sb = new StringBuilder(bytes.length * 2);  
  96.         for (int i = 0; i < bytes.length; i++) {  
  97.             String hex = Integer.toHexString(bytes[i] & 0xff);// &0xff是byte小于0時會高位補1,要改回0  
  98.             if (hex.length() == 1)  
  99.                 sb.append('0');  
  100.             sb.append(hex);  
  101.             sb.append(" ");  
  102.         }  
  103.         return sb.toString().toUpperCase();  
  104.     }  

繼續閱讀