Java編碼及網絡傳輸中的編碼問題
2012-01-13 12:21 cherishLC cherishLC的部落格 我要評論(0) 字号:T | T
本文主要講作者對FTP搜尋中遇到編碼的問題進行了研究,主要分為三部分:表單資料的編碼、網址的編碼、亂碼恢複。詳細請看下文。
AD:
近來試着FTP搜尋,遇到編碼問題,研究了下。
Java内部的String為Unicode編碼,每個字元占兩個位元組。
Java編解碼方法如下:
- String str = "hi好啊me";
- byte[] gbkBytes=str.getBytes("GBK");//将String的Unicode編碼轉為GBK編碼,輸出到位元組中
- String string=new String(gbkBytes,"GBK");//gbkBytes中的位元組流以GBK方案解碼成Unicode形式的Java字元串
1、表單資料的編碼
現在的問題是,在網絡中,不知道用戶端發過來的位元組流的編碼方案(發送前浏覽器會對資料編碼!!!各個浏覽器還不一樣!!!)
解決方案如下:
當然URLEncoder.encode(str, "utf-8")和URLDecoder.decode(strReceive,"utf-8")方法中的編碼方案要一緻。
2、網址的編碼
但以上方法隻适合表單資料的送出;對于URL則不行!!!原因是URLEncoder把'/'也編碼了,浏覽器發送時報錯!!!那麼,隻要http://IP/子目錄把http://IP/這部分原封不動(當然這部分不要有中文),之後的資料以'/'分割後分段編碼即可。
代碼如下:
- /**
- * 對{@link URLEncoder#encode(String, String)}的封裝,但不編碼'/'字元,對其他字元分段編碼
- *
- * @param str
- * 要編碼的URL
- * @param encoding
- * 編碼格式
- * @return 字元串以字元'/'隔開,對每一段單獨編碼以encoding編碼格式編碼
- * @version: 2012_01_10
- * <p>
- * 注意:未考慮':',如直接對http://編解碼,會産生錯誤!!!請在使用前将其分離出來,可以使用
- * {@link #encodeURLAfterHost(String, String)}方法解決此問題
- * <p>
- * 注意:對字元/一起編碼,導緻URL請求異常!!
- */
- public static String encodeURL(String str, String encoding) {
- final char splitter = '/';
- try {
- StringBuilder sb = new StringBuilder(2 * str.length());
- int start = 0;
- for (int i = 0; i < str.length(); i++) {
- if (str.charAt(i) == splitter) {
- sb.append(URLEncoder.encode(str.substring(start, i),
- encoding));
- sb.append(splitter);
- start = i + 1;
- }
- }
- if (start < str.length())
- sb.append(URLEncoder.encode(str.substring(start), encoding));
- return sb.toString();
- } catch (UnsupportedEncodingException e) {
- e.printStackTrace();
- }
- return null;
- }
- /**
- * 對IP位址後的URL通過'/'分割後進行分段編碼.
- * <p>
- * 對{@link URLEncoder#encode(String, String)}
- * 的封裝,但不編碼'/'字元,也不編碼網站部分(如ftp://a.b.c.d/部分,檢測方法為對三個'/'字元的檢測,且要求前兩個連續),
- * 對其他字元分段編碼
- *
- * @param str
- * 要編碼的URL
- * @param encoding
- * 編碼格式
- * @return IP位址後字元串以字元'/'隔開,對每一段單獨編碼以encoding編碼格式編碼,其他部分不變
- * @version: 2012_01_10
- * <p>
- * 注意:對字元/一起編碼,導緻URL請求異常!!
- */
- public static String encodeURLAfterHost(String str, String encoding) {
- final char splitter = '/';
- int index = str.indexOf(splitter);//第一個'/'的位置
- index++;//移到下一位置!!
- if (index < str.length() && str.charAt(index) == splitter) {//檢測第一個'/'之後是否還是'/',如ftp://
- index++;//從下一個開始
- index = str.indexOf(splitter, index);//第三個'/';如ftp://anonymous:[email protected]:219.223.168.20/中的最後一個'/'
- if (index > 0) {
- return str.substring(0, index + 1)
- + encodeURL(str.substring(index + 1), encoding);//如ftp://anonymous:[email protected]:219.223.168.20/天空
- } else
- return str;//如ftp://anonymous:[email protected]:219.223.168.20
- }
- return encodeURL(str, encoding);
- }
- /**
- * 對IP位址後的URL通過'/'分割後進行分段編碼.
- * 此方法與{@link #decodeURLAfterHost(String, String)}配對使用
- * @param str
- * 要解碼的URL
- * @param encoding
- * str的編碼格式
- * @return IP位址後字元串以字元'/'隔開,對每一段單獨解碼以encoding編碼格式解碼,其他部分不變
- * @version: 2012_01_10
- *
- * <p>
- * 注意:對字元/一起解碼,将導緻URL請求異常!!
- */
- public static String decodeURLAfterHost(String str, String encoding) {
- final char splitter = '/';
- int index = str.indexOf(splitter);//第一個'/'的位置
- index++;//移到下一位置!!
- if (index < str.length() && str.charAt(index) == splitter) {//檢測第一個'/'之後是否還是'/',如ftp://
- index++;//從下一個開始
- index = str.indexOf(splitter, index);//第三個'/';如ftp://anonymous:[email protected]:219.223.168.20/中的最後一個'/'
- if (index > 0) {
- return str.substring(0, index + 1)
- + decodeURL(str.substring(index + 1), encoding);//如ftp://anonymous:[email protected]:219.223.168.20/天空
- } else
- return str;//如ftp://anonymous:[email protected]:219.223.168.20
- }
- return decodeURL(str, encoding);
- }
- /**
- * 此方法與{@link #encodeURL(String, String)}配對使用
- * <p>
- * 對{@link URLDecoder#decode(String, String)}的封裝,但不解碼'/'字元,對其他字元分段解碼
- *
- * @param str
- * 要解碼的URL
- * @param encoding
- * str的編碼格式
- * @return 字元串以字元'/'隔開,對每一段單獨編碼以encoding編碼格式解碼
- * @version: 2012_01_10
- *
- * <p>
- * 注意:對字元/一起編碼,導緻URL請求異常!!
- */
- public static String decodeURL(String str, String encoding) {
- final char splitter = '/';
- try {
- StringBuilder sb = new StringBuilder(str.length());
- int start = 0;
- for (int i = 0; i < str.length(); i++) {
- if (str.charAt(i) == splitter) {
- sb.append(URLDecoder.decode(str.substring(start, i),
- encoding));
- sb.append(splitter);
- start = i + 1;
- }
- }
- if (start < str.length())
- sb.append(URLDecoder.decode(str.substring(start), encoding));
- return sb.toString();
- } catch (UnsupportedEncodingException e) {
- e.printStackTrace();
- }
- return null;
- }
3、亂碼了還能恢複?
問題如下:
貌似圖中的utf-8改成iso8859-1是可以的,utf-8在字元串中有中文時不行(但英文部分仍可正确解析)!!!畢竟GBK的位元組流對于utf-8可能是無效的,碰到無效的字元怎麼解析,是否可逆那可不好說啊。
測試代碼如下:
- package tests;
- import java.io.UnsupportedEncodingException;
- import java.net.URLEncoder;
- /**
- * @author LC
- * @version: 2012_01_12
- */
- public class TestEncoding {
- static String utf8 = "utf-8";
- static String iso = "iso-8859-1";
- static String gbk = "GBK";
- public static void main(String[] args) throws UnsupportedEncodingException {
- String str = "hi好啊me";
- // System.out.println("?的十六進制為:3F");
- // System.err
- // .println("出現中文時,如果編碼方案不支援中文,每個字元都會被替換為?的對應編碼!(如在iso-8859-1中)");
- System.out.println("原始字元串:\t\t\t\t\t\t" + str);
- String utf8_encoded = URLEncoder.encode(str, "utf-8");
- System.out.println("用URLEncoder.encode()方法,并用UTF-8編碼後:\t\t" + utf8_encoded);
- String gbk_encoded = URLEncoder.encode(str, "GBK");
- System.out.println("用URLEncoder.encode()方法,并用GBK編碼後:\t\t" + gbk_encoded);
- testEncoding(str, utf8, gbk);
- testEncoding(str, gbk, utf8);
- testEncoding(str, gbk, iso);
- printBytesInDifferentEncoding(str);
- printBytesInDifferentEncoding(utf8_encoded);
- printBytesInDifferentEncoding(gbk_encoded);
- }
- /**
- * 測試用錯誤的編碼方案解碼後再編碼,是否對原始資料有影響
- *
- * @param str
- * 輸入字元串,Java的String類型即可
- * @param encodingTrue
- * 編碼方案1,用于模拟原始資料的編碼
- * @param encondingMidian
- * 編碼方案2,用于模拟中間的編碼方案
- * @throws UnsupportedEncodingException
- */
- public static void testEncoding(String str, String encodingTrue,
- String encondingMidian) throws UnsupportedEncodingException {
- System.out.println();
- System.out
- .printf("%s編碼的位元組資料->用%s解碼并轉為Unicode編碼的JavaString->用%s解碼變為位元組流->讀入Java(用%s解碼)後變為Java的String\n",
- encodingTrue, encondingMidian, encondingMidian,
- encodingTrue);
- System.out.println("原始字元串:\t\t" + str);
- byte[] trueEncodingBytes = str.getBytes(encodingTrue);
- System.out.println("原始位元組流:\t\t" + bytesToHexString(trueEncodingBytes)
- + "\t\t//即用" + encodingTrue + "編碼後的位元組流");
- String encodeUseMedianEncoding = new String(trueEncodingBytes,
- encondingMidian);
- System.out.println("中間字元串:\t\t" + encodeUseMedianEncoding + "\t\t//即用"
- + encondingMidian + "解碼原始位元組流後的字元串");
- byte[] midianBytes = encodeUseMedianEncoding.getBytes("Unicode");
- System.out.println("中間位元組流:\t\t" + bytesToHexString(midianBytes)
- + "\t\t//即中間字元串對應的Unicode位元組流(和Java記憶體資料一緻)");
- byte[] redecodedBytes = encodeUseMedianEncoding
- .getBytes(encondingMidian);
- System.out.println("解碼位元組流:\t\t" + bytesToHexString(redecodedBytes)
- + "\t\t//即用" + encodingTrue + "解碼中間字元串(流)後的字元串");
- String restored = new String(redecodedBytes, encodingTrue);
- System.out.println("解碼字元串:\t\t" + restored + "\t\t和原始資料相同? "
- + restored.endsWith(str));
- }
- /**
- * 将字元串分别編碼為GBK、UTF-8、iso-8859-1的位元組流并輸出
- *
- * @param str
- * @throws UnsupportedEncodingException
- */
- public static void printBytesInDifferentEncoding(String str)
- throws UnsupportedEncodingException {
- System.out.println("");
- System.out.println("原始String:\t\t" + str + "\t\t長度為:" + str.length());
- String unicodeBytes = bytesToHexString(str.getBytes("unicode"));
- System.out.println("Unicode bytes:\t\t" + unicodeBytes);
- String gbkBytes = bytesToHexString(str.getBytes("GBK"));
- System.out.println("GBK bytes:\t\t" + gbkBytes);
- String utf8Bytes = bytesToHexString(str.getBytes("utf-8"));
- System.out.println("UTF-8 bytes:\t\t" + utf8Bytes);
- String iso8859Bytes = bytesToHexString(str.getBytes("iso-8859-1"));
- System.out.println("iso8859-1 bytes:\t" + iso8859Bytes + "\t\t長度為:"
- + iso8859Bytes.length() / 3);
- System.out.println("可見Unicode在之前加了兩個位元組FE FF,之後則每個字元兩位元組");
- }
- /**
- * 将該數組轉的每個byte轉為兩位的16進制字元,中間用空格隔開
- *
- * @param bytes
- * 要轉換的byte序列
- * @return 轉換後的字元串
- */
- public static final String bytesToHexString(byte[] bytes) {
- StringBuilder sb = new StringBuilder(bytes.length * 2);
- for (int i = 0; i < bytes.length; i++) {
- String hex = Integer.toHexString(bytes[i] & 0xff);// &0xff是byte小于0時會高位補1,要改回0
- if (hex.length() == 1)
- sb.append('0');
- sb.append(hex);
- sb.append(" ");
- }
- return sb.toString().toUpperCase();
- }
- }