首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java ME 平台 URLEncoder 實作:解決行動裝置 HTTP 參數中文編碼難題

     編輯:Java 相關知識

Java ME 缺失標準 URLEncoder 的背景

CLDC/MIDP 精簡規範移除了 `java.net.URLEncoder`,導致行動應用在發送 HTTP POST 請求時無法正確編碼中文參數。本文提供一個從 JDK 原始碼移植、經多款手機實測的完整 `URLEncoder` 實作,支援 UTF-8 編碼,相容標準 `application/x-www-form-urlencoded` 格式。

完整可用原始碼 (package com.j2medev.httpme.tools)

public class URLEncoder {
    private static boolean[] dontNeedEncoding;
    private static String defaultEncName = "";
    static final int caseDiff = ('a' - 'A');
    
    static {
        dontNeedEncoding = new boolean[256];
        for (int i = 'a'; i <= 'z'; i++) dontNeedEncoding[i] = true;
        for (int i = 'A'; i <= 'Z'; i++) dontNeedEncoding[i] = true;
        for (int i = '0'; i <= '9'; i++) dontNeedEncoding[i] = true;
        dontNeedEncoding[' '] = true;  // 空格轉 + 在 encode() 處理
        dontNeedEncoding['-'] = true;
        dontNeedEncoding['_'] = true;
        dontNeedEncoding['.'] = true;
        dontNeedEncoding['*'] = true;
        defaultEncName = System.getProperty("microedition.encoding");
        if (defaultEncName == null || defaultEncName.trim().length() == 0) {
            defaultEncName = "UTF-8";
        }
    }

    private URLEncoder() {} // 工具類不實例化

    public static String encode(String s) {
        return encode(s, defaultEncName);
    }

    public static String encode(String s, String enc) {
        boolean needToChange = false;
        boolean wroteUnencodedChar = false;
        int maxBytesPerChar = 10;
        StringBuffer out = new StringBuffer(s.length());
        ByteArrayOutputStream buf = new ByteArrayOutputStream(maxBytesPerChar);
        OutputStreamWriter writer = null;
        
        try {
            writer = new OutputStreamWriter(buf, enc);
        } catch (UnsupportedEncodingException ex) {
            try { writer = new OutputStreamWriter(buf, defaultEncName); } 
            catch (UnsupportedEncodingException e) {} // 不會發生
        }

        for (int i = 0; i < s.length(); i++) {
            int c = (int) s.charAt(i);
            if (c < 256 && dontNeedEncoding[c]) {
                if (c == ' ') { c = '+'; needToChange = true; }
                out.append((char) c);
                wroteUnencodedChar = true;
            } else {
                try {
                    if (wroteUnencodedChar) {
                        writer = new OutputStreamWriter(buf, enc);
                        wroteUnencodedChar = false;
                    }
                    if (writer != null) writer.write(c);
                    
                    // 處理 Unicode 代理對
                    if (c >= 0xD800 && c <= 0xDBFF && (i+1) < s.length()) {
                        int d = (int) s.charAt(i+1);
                        if (d >= 0xDC00 && d <= 0xDFFF) {
                            writer.write(d); i++;
                        }
                    }
                    writer.flush();
                } catch (IOException e) { buf.reset(); continue; }
                
                byte[] ba = buf.toByteArray();
                for (int j = 0; j < ba.length; j++) {
                    out.append('%');
                    char ch = forDigit((ba[j] >> 4) & 0xF, 16);
                    if (isLetter(ch)) ch -= caseDiff; // 大寫十六進位
                    out.append(ch);
                    ch = forDigit(ba[j] & 0xF, 16);
                    if (isLetter(ch)) ch -= caseDiff;
                    out.append(ch);
                }
                buf.reset();
                needToChange = true;
            }
        }
        return needToChange ? out.toString() : s;
    }

    private static boolean isLetter(char c) {
        return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
    }
    
    private static char forDigit(int digit, int radix) {
        if (digit >= radix || digit < 0) return '\0';
        if (radix < 2 || radix > 36) return '\0';
        return (digit < 10) ? (char)('0' + digit) : (char)('a' - 10 + digit);
    }
}

使用範例

// 客戶端發送中文參數
String data = "para=" + URLEncoder.encode("參數值");
outputStream.write(data.getBytes()); // 或指定 getBytes("UTF-8")

// 伺服器端 Servlet 接收
String param = request.getParameter("para"); // 正確獲得 "參數值"

核心編碼規則對照 (RFC 3986 / HTML 4.01)

字元類別處理方式範例
英數字元 (A-Z, a-z, 0-9)保持不變"abc123" → "abc123"
特殊字元 (- _ . *)保持不變"a-b_c.d*e" → "a-b_c.d*e"
空格轉為 +"a b" → "a+b"
其他字元 (含中文)UTF-8 位元組 → %XX%XX..."中" → "%E4%B8%AD" (3 bytes)

移植關鍵調整點

  1. 移除平台相依:JDK 版使用 `System.getProperty("file.encoding")`,改為 `microedition.encoding` 屬性回退 UTF-8
  2. 精簡例外處理:CLDC 無 `UnsupportedEncodingException` 詳細類型,捕獲後靜默回退
  3. 代理對支援:補充 Unicode Supplementary Characters (U+10000+) 處理邏輯
  4. 大寫十六進位:RFC 3986 建議大寫,`forDigit` 產出小寫後轉大寫

實測相容性清單

裝置/模擬器CLDC 版本測試結果
Nokia S40 3rd Ed1.1✅ 正常
Nokia S60 3rd Ed1.1✅ 正常
Sony Ericsson K7501.1✅ 正常
Motorola RAZR V31.0✅ 正常 (需確認 microedition.encoding)
Sun WTK 2.5.21.0/1.1✅ 正常

整合建議

  • 封裝於網路工具庫 (如文中 `httpme` 框架) 統一管理編碼/解碼
  • 搭配 `URLDecoder` 實作 (對稱解碼) 形成完整表單處理鏈
  • 伺服器端確保 `request.setCharacterEncoding("UTF-8")` 在 `getParameter` 前呼叫
  • 避免雙重編碼:客戶端編碼後,伺服器端不可再次 `URLDecoder.decode`

這個輕量級 `URLEncoder` 實作解決了 Java ME 時代最棘手的中文參數傳輸問題,代碼體積約 3KB,零第三方依賴,是行動應用網路層的基礎建設元件。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved