Java编码及网络传输中的编码问题

Java编码及网络传输中的编码问题

 近来试着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:tmp@g.cn:219.223.168.20/中的最后一个'/' 
  60.         if (index > 0) {  
  61.             return str.substring(0, index + 1)  
  62.                     + encodeURL(str.substring(index + 1), encoding);//如ftp://anonymous:tmp@g.cn:219.223.168.20/天空 
  63.         } else 
  64.             return str;//如ftp://anonymous:tmp@g.cn: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:tmp@g.cn:219.223.168.20/中的最后一个'/' 
  88.         if (index > 0) {  
  89.             return str.substring(0, index + 1)  
  90.                     + decodeURL(str.substring(index + 1), encoding);//如ftp://anonymous:tmp@g.cn:219.223.168.20/天空 
  91.         } else 
  92.             return str;//如ftp://anonymous:tmp@g.cn: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;  
  131. }

 3、乱码了还能恢复?

  问题如下:

Java编码及网络传输中的编码问题

  貌似图中的utf-8改成iso8859-1是可以的,utf-8在字符串中有中文时不行(但英文部分仍可正确解析)!!!毕竟GBK的字节流对于utf-8可能是无效的,碰到无效的字符怎么解析,是否可逆那可不好说啊。

  测试代码如下:

  1. package tests;  
  2.  
  3. import java.io.UnsupportedEncodingException;  
  4. import java.net.URLEncoder;  
  5.  
  6. /**  
  7.  * @author LC  
  8.  * @version: 2012_01_12  
  9.  */ 
  10. public class TestEncoding {  
  11.     static String utf8 = "utf-8";  
  12.     static String iso = "iso-8859-1";  
  13.     static String gbk = "GBK";  
  14.  
  15.     public static void main(String[] args) throws UnsupportedEncodingException {  
  16.         String str = "hi好啊me";  
  17.         //      System.out.println("?的十六进制为:3F");  
  18.         //      System.err  
  19.         //              .println("出现中文时,如果编码方案不支持中文,每个字符都会被替换为?的对应编码!(如在iso-8859-1中)");  
  20.         System.out.println("原始字符串:\t\t\t\t\t\t" + str);  
  21.         String utf8_encoded = URLEncoder.encode(str, "utf-8");  
  22.         System.out.println("用URLEncoder.encode()方法,并用UTF-8编码后:\t\t" + utf8_encoded);  
  23.         String gbk_encoded = URLEncoder.encode(str, "GBK");  
  24.         System.out.println("用URLEncoder.encode()方法,并用GBK编码后:\t\t" + gbk_encoded);  
  25.         testEncoding(str, utf8, gbk);  
  26.         testEncoding(str, gbk, utf8);  
  27.         testEncoding(str, gbk, iso);  
  28.         printBytesInDifferentEncoding(str);  
  29.         printBytesInDifferentEncoding(utf8_encoded);  
  30.         printBytesInDifferentEncoding(gbk_encoded);  
  31.     }  
  32.  
  33.     /**  
  34.      * 测试用错误的编码方案解码后再编码,是否对原始数据有影响  
  35.      *   
  36.      * @param str  
  37.      *            输入字符串,Java的String类型即可  
  38.      * @param encodingTrue  
  39.      *            编码方案1,用于模拟原始数据的编码  
  40.      * @param encondingMidian  
  41.      *            编码方案2,用于模拟中间的编码方案  
  42.      * @throws UnsupportedEncodingException  
  43.      */ 
  44.     public static void testEncoding(String str, String encodingTrue,  
  45.             String encondingMidian) throws UnsupportedEncodingException {  
  46.         System.out.println();  
  47.         System.out  
  48.                 .printf("%s编码的字节数据->用%s解码并转为Unicode编码的JavaString->用%s解码变为字节流->读入Java(用%s解码)后变为Java的String\n",  
  49.                         encodingTrue, encondingMidian, encondingMidian,  
  50.                         encodingTrue);  
  51.         System.out.println("原始字符串:\t\t" + str);  
  52.         byte[] trueEncodingBytes = str.getBytes(encodingTrue);  
  53.         System.out.println("原始字节流:\t\t" + bytesToHexString(trueEncodingBytes)  
  54.                 + "\t\t//即用" + encodingTrue + "编码后的字节流");  
  55.         String encodeUseMedianEncoding = new String(trueEncodingBytes,  
  56.                 encondingMidian);  
  57.         System.out.println("中间字符串:\t\t" + encodeUseMedianEncoding + "\t\t//即用" 
  58.                 + encondingMidian + "解码原始字节流后的字符串");  
  59.         byte[] midianBytes = encodeUseMedianEncoding.getBytes("Unicode");  
  60.         System.out.println("中间字节流:\t\t" + bytesToHexString(midianBytes)  
  61.                 + "\t\t//即中间字符串对应的Unicode字节流(和Java内存数据一致)");  
  62.         byte[] redecodedBytes = encodeUseMedianEncoding  
  63.                 .getBytes(encondingMidian);  
  64.         System.out.println("解码字节流:\t\t" + bytesToHexString(redecodedBytes)  
  65.                 + "\t\t//即用" + encodingTrue + "解码中间字符串(流)后的字符串");  
  66.         String restored = new String(redecodedBytes, encodingTrue);  
  67.         System.out.println("解码字符串:\t\t" + restored + "\t\t和原始数据相同?  " 
  68.                 + restored.endsWith(str));  
  69.     }  
  70.  
  71.     /**  
  72.      * 将字符串分别编码为GBK、UTF-8、iso-8859-1的字节流并输出  
  73.      *   
  74.      * @param str  
  75.      * @throws UnsupportedEncodingException  
  76.      */ 
  77.     public static void printBytesInDifferentEncoding(String str)  
  78.             throws UnsupportedEncodingException {  
  79.         System.out.println("");  
  80.         System.out.println("原始String:\t\t" + str + "\t\t长度为:" + str.length());  
  81.         String unicodeBytes = bytesToHexString(str.getBytes("unicode"));  
  82.         System.out.println("Unicode bytes:\t\t" + unicodeBytes);  
  83.         String gbkBytes = bytesToHexString(str.getBytes("GBK"));  
  84.         System.out.println("GBK bytes:\t\t" + gbkBytes);  
  85.         String utf8Bytes = bytesToHexString(str.getBytes("utf-8"));  
  86.         System.out.println("UTF-8 bytes:\t\t" + utf8Bytes);  
  87.         String iso8859Bytes = bytesToHexString(str.getBytes("iso-8859-1"));  
  88.         System.out.println("iso8859-1 bytes:\t" + iso8859Bytes + "\t\t长度为:" 
  89.                 + iso8859Bytes.length() / 3);  
  90.         System.out.println("可见Unicode在之前加了两个字节FE FF,之后则每个字符两字节");  
  91.     }  
  92.  
  93.     /**  
  94.      * 将该数组转的每个byte转为两位的16进制字符,中间用空格隔开  
  95.      *   
  96.      * @param bytes  
  97.      *            要转换的byte序列  
  98.      * @return 转换后的字符串  
  99.      */ 
  100.     public static final String bytesToHexString(byte[] bytes) {  
  101.         StringBuilder sb = new StringBuilder(bytes.length * 2);  
  102.         for (int i = 0; i < bytes.length; i++) {  
  103.             String hex = Integer.toHexString(bytes[i] & 0xff);// &0xff是byte小于0时会高位补1,要改回0  
  104.             if (hex.length() == 1)  
  105.                 sb.append('0');  
  106.             sb.append(hex);  
  107.             sb.append(" ");  
  108.         }  
  109.         return sb.toString().toUpperCase();  
  110.     }  
  111. }

本文出自seven的测试人生公众号最新内容请见作者的GitHub页:http://qaseven.github.io/
上一篇:vim 创建Python脚本时候自动补全解释器和编码方法


下一篇:场景体验报告——冬季实战营第三期