【问题标题】:Java equivalent to JavaScript's encodeURIComponent that produces identical output?Java 等效于产生相同输出的 JavaScript 的 encodeURIComponent?
【发布时间】:2010-10-11 01:36:45
【问题描述】:

我一直在试验各种 Java 代码,试图想出一些东西来编码包含引号、空格和“外来”Unicode 字符的字符串,并产生与 JavaScript 的 encodeURIComponent 函数相同的输出。

我的折磨测试字符串是:"A" B ± "

如果我在 Firebug 中输入以下 JavaScript 语句:

encodeURIComponent('"A" B ± "');

——然后​​我得到:

"%22A%22%20B%20%C2%B1%20%22"

这是我的小测试 Java 程序:

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

public class EncodingTest
{
  public static void main(String[] args) throws UnsupportedEncodingException
  {
    String s = "\"A\" B ± \"";
    System.out.println("URLEncoder.encode returns "
      + URLEncoder.encode(s, "UTF-8"));

    System.out.println("getBytes returns "
      + new String(s.getBytes("UTF-8"), "ISO-8859-1"));
  }
}

——这个程序输出:

URLEncoder.encode 返回 %22A%22+B+%C2%B1+%22
getBytes 返回 "A" B ± "

关闭,但没有雪茄!使用 Java 对 UTF-8 字符串进行编码以使其产生与 JavaScript 的 encodeURIComponent 相同的输出的最佳方法是什么?

编辑:我正在使用 Java 1.4,很快就会迁移到 Java 5。

【问题讨论】:

    标签: java javascript unicode utf-8


    【解决方案1】:

    这是我最后想出的课程:

    import java.io.UnsupportedEncodingException;
    import java.net.URLDecoder;
    import java.net.URLEncoder;
    
    /**
     * Utility class for JavaScript compatible UTF-8 encoding and decoding.
     * 
     * @see http://stackoverflow.com/questions/607176/java-equivalent-to-javascripts-encodeuricomponent-that-produces-identical-output
     * @author John Topley 
     */
    public class EncodingUtil
    {
      /**
       * Decodes the passed UTF-8 String using an algorithm that's compatible with
       * JavaScript's <code>decodeURIComponent</code> function. Returns
       * <code>null</code> if the String is <code>null</code>.
       *
       * @param s The UTF-8 encoded String to be decoded
       * @return the decoded String
       */
      public static String decodeURIComponent(String s)
      {
        if (s == null)
        {
          return null;
        }
    
        String result = null;
    
        try
        {
          result = URLDecoder.decode(s, "UTF-8");
        }
    
        // This exception should never occur.
        catch (UnsupportedEncodingException e)
        {
          result = s;  
        }
    
        return result;
      }
    
      /**
       * Encodes the passed String as UTF-8 using an algorithm that's compatible
       * with JavaScript's <code>encodeURIComponent</code> function. Returns
       * <code>null</code> if the String is <code>null</code>.
       * 
       * @param s The String to be encoded
       * @return the encoded String
       */
      public static String encodeURIComponent(String s)
      {
        String result = null;
    
        try
        {
          result = URLEncoder.encode(s, "UTF-8")
                             .replaceAll("\\+", "%20")
                             .replaceAll("\\%21", "!")
                             .replaceAll("\\%27", "'")
                             .replaceAll("\\%28", "(")
                             .replaceAll("\\%29", ")")
                             .replaceAll("\\%7E", "~");
        }
    
        // This exception should never occur.
        catch (UnsupportedEncodingException e)
        {
          result = s;
        }
    
        return result;
      }  
    
      /**
       * Private constructor to prevent this class from being instantiated.
       */
      private EncodingUtil()
      {
        super();
      }
    }
    

    【讨论】:

    • 添加小费。在Android 4.4我发现我们还需要替换%0A这意味着Android输入中的返回键,否则会导致js崩溃。
    • @Aloong 替换"%0A" 是什么意思?什么角色会被替换?只是空字符串""
    • 没有必要使用replaceAll,简单的replace也有同样的效果。不需要在正则表达式中转义%,所以不要写\\%,而是写%。如果“这个异常永远不会发生”,宁可抛出一个Error 或至少一个IllegalStateException,但不要默默地做一些错误的事情。
    【解决方案2】:

    查看实现差异,我看到:

    MDC on encodeURIComponent():

    • 文字字符(正则表达式表示):[-a-zA-Z0-9._*~'()!]

    Java 1.5.0 documentation on URLEncoder:

    • 文字字符(正则表达式表示):[-a-zA-Z0-9._*]
    • 空格字符" " 转换为加号"+"

    所以基本上,为了得到想要的结果,使用URLEncoder.encode(s, "UTF-8") 然后做一些后期处理:

    • 将所有出现的"+" 替换为"%20"
    • 将所有出现的 "%xx" 代表任何 [~'()!] 替换回它们的字面对应部分

    【讨论】:

    • 我希望你用一些简单的语言写了“将所有出现的“%xx”代表任何 [~'()!] 替换回它们的字面对应部分”。 :(我的小脑袋无法理解.......
    • @Shailendra [~'()!] 表示 "~""'""("")""!"。 :) 不过,我也建议学习正则表达式基础知识。 (我也没有对此进行扩展,因为至少有两个其他答案显示了各自的 Java 代码。)
    • 将所有出现的"+" 替换为"%20" 具有潜在的破坏性,因为"+" 是URI 路径中的合法字符(尽管不在查询字符串中)。例如“a+b c”应编码为"a+b%20c";此解决方案会将其转换为"a%20b%20c"。相反,请使用new URI(null, null, value, null).getRawPath()
    • @ChrisNitchie 这不是问题的重点。问题是“Java 等效于产生相同输出的 JavaScript 的 encodeURIComponent?”,而不是 “通用 Java encode-URI-component 函数?”.
    • @ChrisNitchie a+b c 使用 java 的 URLEncoder 编码为 a%2Bb+c 并使用 js 的 encodeURIComponent 编码为 a%2Bb%20c
    【解决方案3】:

    使用 Java 6 附带的 javascript 引擎:

    import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; public class Wow { public static void main(String[] args) throws Exception { ScriptEngineManager factory = new ScriptEngineManager(); ScriptEngine engine = factory.getEngineByName("JavaScript"); engine.eval("print(encodeURIComponent('\"A\" B ± \"'))"); } }

    输出:%22A%22%20B%20%c2%b1%20%22

    情况不同,但更接近你想要的。

    【讨论】:

    • 啊,对不起......我应该在问题中提到我正在使用 Java 1.4 很快迁移到 Java 5!
    • 如果 javascript 是唯一的解决方案,你可以试试 Rhino,但仅仅解决这个小问题就太过分了。
    • 即使他使用的是 Java 6,我认为这个解决方案也太棒了。我不认为他在寻找一种直接调用 javascript 方法的方法,只是一种模拟它的方法。
    • 也许吧。我认为最简单的解决方案是编写自己的转义函数,如果你找不到任何对你有用的东西。只需从 StringEscapeUtils 类 (Jakarta Commons Lang) 复制一些方法,然后根据您的需要重新实现它。
    • 这确实有效,如果您不担心性能......我认为它很好。
    【解决方案4】:

    我使用java.net.URI#getRawPath(),例如

    String s = "a+b c.html";
    String fixed = new URI(null, null, s, null).getRawPath();
    

    fixed 的值将是 a+b%20c.html,这是您想要的。

    URLEncoder.encode() 的输出进行后处理将消除假定在URI 中的所有优点。例如

    URLEncoder.encode("a+b c.html").replaceAll("\\+", "%20");
    

    会给你a%20b%20c.html,它会被解释为a b c.html

    【讨论】:

    • 在认为这应该是最好的答案之后,我在实践中尝试了几个文件名,但至少有两个失败了,一个是西里尔字符。所以,不,这显然还没有经过足够好的测试。
    • 不适用于http://a+b c.html等字符串,会报错
    【解决方案5】:

    我想出了我自己版本的encodeURIComponent,因为发布的解决方案有一个问题,如果字符串中存在+,应该编码,它将转换为空格。

    这是我的课:

    import java.io.UnsupportedEncodingException;
    import java.util.BitSet;
    
    public final class EscapeUtils
    {
        /** used for the encodeURIComponent function */
        private static final BitSet dontNeedEncoding;
    
        static
        {
            dontNeedEncoding = new BitSet(256);
    
            // a-z
            for (int i = 97; i <= 122; ++i)
            {
                dontNeedEncoding.set(i);
            }
            // A-Z
            for (int i = 65; i <= 90; ++i)
            {
                dontNeedEncoding.set(i);
            }
            // 0-9
            for (int i = 48; i <= 57; ++i)
            {
                dontNeedEncoding.set(i);
            }
    
            // '()*
            for (int i = 39; i <= 42; ++i)
            {
                dontNeedEncoding.set(i);
            }
            dontNeedEncoding.set(33); // !
            dontNeedEncoding.set(45); // -
            dontNeedEncoding.set(46); // .
            dontNeedEncoding.set(95); // _
            dontNeedEncoding.set(126); // ~
        }
    
        /**
         * A Utility class should not be instantiated.
         */
        private EscapeUtils()
        {
    
        }
    
        /**
         * Escapes all characters except the following: alphabetic, decimal digits, - _ . ! ~ * ' ( )
         * 
         * @param input
         *            A component of a URI
         * @return the escaped URI component
         */
        public static String encodeURIComponent(String input)
        {
            if (input == null)
            {
                return input;
            }
    
            StringBuilder filtered = new StringBuilder(input.length());
            char c;
            for (int i = 0; i < input.length(); ++i)
            {
                c = input.charAt(i);
                if (dontNeedEncoding.get(c))
                {
                    filtered.append(c);
                }
                else
                {
                    final byte[] b = charToBytesUTF(c);
    
                    for (int j = 0; j < b.length; ++j)
                    {
                        filtered.append('%');
                        filtered.append("0123456789ABCDEF".charAt(b[j] >> 4 & 0xF));
                        filtered.append("0123456789ABCDEF".charAt(b[j] & 0xF));
                    }
                }
            }
            return filtered.toString();
        }
    
        private static byte[] charToBytesUTF(char c)
        {
            try
            {
                return new String(new char[] { c }).getBytes("UTF-8");
            }
            catch (UnsupportedEncodingException e)
            {
                return new byte[] { (byte) c };
            }
        }
    }
    

    【讨论】:

    • 感谢您提供好的解决方案!其他人看起来完全......效率低下,IMO。如果在今天的硬件上没有 BitSet,也许会更好。或者 0...127 的两个硬编码长整数。
    • URLEncoder.encode("+", "UTF-8"); 产生"%2B",这是正确的 URL 编码,所以您的解决方案是,我的歉意,完全没有必要。为什么URLEncoder.encode 不把空格变成%20 是我无法理解的。
    【解决方案6】:

    我想出了另一个实现,记录在 http://blog.sangupta.com/2010/05/encodeuricomponent-and.html。该实现还可以处理 Unicode 字节。

    【讨论】:

      【解决方案7】:

      对我来说这很有效:

      import org.apache.http.client.utils.URIBuilder;
      
      String encodedString = new URIBuilder()
        .setParameter("i", stringToEncode)
        .build()
        .getRawQuery() // output: i=encodedString
        .substring(2);
      

      或使用不同的 UriBuilder

      import javax.ws.rs.core.UriBuilder;
      
      String encodedString = UriBuilder.fromPath("")
        .queryParam("i", stringToEncode)
        .toString()   // output: ?i=encodedString
        .substring(3);
      

      在我看来,使用标准库比手动后期处理更好。 @Chris 的回答看起来也不错,但它不适用于网址,例如“http://a+b c.html”

      【讨论】:

      • 使用标准库很好... ...除非你是中间件,并且依赖于不同版本的标准库,然后任何使用你的代码的人都必须摆弄依赖关系,然后希望没有任何问题......
      • 如果此解决方案可行,那就太好了,但它的行为方式与请求 encodeURIComponent 不同。 encodeURIComponent 返回 ?&amp; 结果 %3F%26%20,但您的建议返回 %3F%26+。我知道这在其他问题和答案中被多次提及,但应该在这里提及,以免人们盲目相信它。
      【解决方案8】:

      我已经成功使用了 java.net.URI 类,如下所示:

      public static String uriEncode(String string) {
          String result = string;
          if (null != string) {
              try {
                  String scheme = null;
                  String ssp = string;
                  int es = string.indexOf(':');
                  if (es > 0) {
                      scheme = string.substring(0, es);
                      ssp = string.substring(es + 1);
                  }
                  result = (new URI(scheme, ssp, null)).toString();
              } catch (URISyntaxException usex) {
                  // ignore and use string that has syntax error
              }
          }
          return result;
      }
      

      【讨论】:

      • 不,这种方法并不完全成功,但相对还可以。你仍然有问题。例如,基数 #java 将编码为 %23 javascript 不会对其进行编码。请参阅:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… Javascript 不使用空格。 A-Z a-z 0-9 ; , / ? :@ & = + $ - _ 。 ! ~ * ' ( ) # 并且对于其中一些 java 将 espace。
      • 使用以下表达式进行 UNIT 测试是一件好事:''' String charactersJavascriptDoesNotEspace = "A-Za-z0-9;,/?:@&=+$-_.!~ *'()#"; ''' 红衣主教是唯一的异常值。因此,修复上面的算法以使其与 javascript 兼容是微不足道的。
      【解决方案9】:

      这是一个简单的例子 Ravi Wallau 的解决方案:

      public String buildSafeURL(String partialURL, String documentName)
              throws ScriptException {
          ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
          ScriptEngine scriptEngine = scriptEngineManager
                  .getEngineByName("JavaScript");
      
          String urlSafeDocumentName = String.valueOf(scriptEngine
                  .eval("encodeURIComponent('" + documentName + "')"));
          String safeURL = partialURL + urlSafeDocumentName;
      
          return safeURL;
      }
      
      public static void main(String[] args) {
          EncodeURIComponentDemo demo = new EncodeURIComponentDemo();
          String partialURL = "https://www.website.com/document/";
          String documentName = "Tom & Jerry Manuscript.pdf";
      
          try {
              System.out.println(demo.buildSafeURL(partialURL, documentName));
          } catch (ScriptException se) {
              se.printStackTrace();
          }
      }
      

      输出: https://www.website.com/document/Tom%20%26%20Jerry%20Manuscript.pdf

      它还回答了 Loren Shqipognja 在 cmets 中关于如何将字符串变量传递给 encodeURIComponent() 的悬而未决的问题。 scriptEngine.eval()方法返回一个Object,所以可以通过String.valueOf()等方法转换成String。

      【讨论】:

        【解决方案10】:

        这是我正在使用的:

        private static final String HEX = "0123456789ABCDEF";
        
        public static String encodeURIComponent(String str) {
            if (str == null) return null;
        
            byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
            StringBuilder builder = new StringBuilder(bytes.length);
        
            for (byte c : bytes) {
                if (c >= 'a' ? c <= 'z' || c == '~' :
                    c >= 'A' ? c <= 'Z' || c == '_' :
                    c >= '0' ? c <= '9' :  c == '-' || c == '.')
                    builder.append((char)c);
                else
                    builder.append('%')
                           .append(HEX.charAt(c >> 4 & 0xf))
                           .append(HEX.charAt(c & 0xf));
            }
        
            return builder.toString();
        }
        

        它通过根据RFC 3986 对不是未保留字符的每个字符进行百分比编码来超越 Javascript。


        这是相反的转换:

        public static String decodeURIComponent(String str) {
            if (str == null) return null;
        
            int length = str.length();
            byte[] bytes = new byte[length / 3];
            StringBuilder builder = new StringBuilder(length);
        
            for (int i = 0; i < length; ) {
                char c = str.charAt(i);
                if (c != '%') {
                    builder.append(c);
                    i += 1;
                } else {
                    int j = 0;
                    do {
                        char h = str.charAt(i + 1);
                        char l = str.charAt(i + 2);
                        i += 3;
        
                        h -= '0';
                        if (h >= 10) {
                            h |= ' ';
                            h -= 'a' - '0';
                            if (h >= 6) throw new IllegalArgumentException();
                            h += 10;
                        }
        
                        l -= '0';
                        if (l >= 10) {
                            l |= ' ';
                            l -= 'a' - '0';
                            if (l >= 6) throw new IllegalArgumentException();
                            l += 10;
                        }
        
                        bytes[j++] = (byte)(h << 4 | l);
                        if (i >= length) break;
                        c = str.charAt(i);
                    } while (c == '%');
                    builder.append(new String(bytes, 0, j, UTF_8));
                }
            }
        
            return builder.toString();
        }
        

        【讨论】:

          【解决方案11】:

          我用过 String encodedUrl = new URI(null, url, null).toASCIIString(); 对网址进行编码。 要在url 中的现有参数之后添加参数,我使用UriComponentsBuilder

          【讨论】:

          【解决方案12】:

          我从 google-http-java-client 库中找到了 PercentEscaper 类,它可以很容易地用于实现 encodeURIComponent。

          PercentEscaper from google-http-java-client javadoc google-http-java-client home

          【讨论】:

            【解决方案13】:

            Guava 库有 PercentEscaper:

            Escaper percentEscaper = new PercentEscaper("-_.*", false);

            “-_.*”是安全字符

            false 表示 PercentEscaper 用 '%20' 转义空间,而不是 '+'

            【讨论】:

              猜你喜欢
              • 2011-02-20
              • 2010-09-10
              • 1970-01-01
              • 2020-04-25
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多