7

我经常需要对大型字符串集合或数组进行 url 编码或解码。除了遍历它们并使用静态 URLDecoder.decode(string, "UTF-8") 之外,是否有任何库可以使这种类型的操作更高效?

一位同事坚持认为,使用静态方法就地解码字符串不是线程安全的。为什么会这样?

4

4 回答 4

8

JDK URLDecoder 没有有效地实现。最值得注意的是,它在内部依赖于 StringBuffer(这在 URLDecoder 的情况下不必要地引入了同步)。Apache commons 提供URLCodec,但据报道它在性能方面也存在类似问题,但我还没有证实在最新版本中仍然存在这种情况。

Mark A. Ziesemer 不久前写了一篇关于 URLDecoder 的问题和性能的文章。他记录了一些错误报告并最终编写了一个完整的替代品。因为是这样,所以我会在这里引用一些关键摘录,但你应该在这里阅读整个源文章:http: //blogger.ziesemer.com/2009/05/improving-url-coder-performance-java.html

精选报价:

Java 在 java.net.URLEncoder 和 java.net.URLDecoder 中提供了此功能的默认实现。不幸的是,由于 API 的编写方式以及实现中的细节,它并不是性能最好的。sun.com 上已提交了许多与 URLEncoder 相关的性能相关错误。

还有一个替代方案:来自 Apache Commons Codec 的 org.apache.commons.codec.net.URLCodec。(Commons Codec 还为 Base64 编码提供了一个有用的实现。)不幸的是,Commons 的 URLCodec 与 Java 的 URLEncoder/URLDecoder 存在一些相同的问题。

...

对 JDK 和 Commons 的建议:

在构造任何“缓冲区”类时,例如 ByteArrayOutputStream、CharArrayWriter、StringBuilder 或 StringBuffer,估计并传入估计的容量。JDK 的 URLEncoder 当前为其 StringBuffer 执行此操作,但也应为其 CharArrayWriter 实例执行此操作。Common 的 URLCodec 应该为其 ByteArrayOutputStream 实例执行此操作。如果类的默认缓冲区大小太小,它们可能必须通过复制到新的、更大的缓冲区来调整大小——这并不是一个“便宜”的操作。如果类的默认缓冲区大小太大,可能会不必要地浪费内存。

两种实现都依赖于字符集,但只接受它们作为它们的字符串名称。Charset 为名称查找提供了一个简单而小型的缓存 - 仅存储最后使用的 2 个字符集。不应依赖这一点,并且出于其他互操作性原因,两者都应该接受 Charset 实例。

两种实现都只处理固定大小的输入和输出。JDK 的 URLEncoder 仅适用于 String 实例。Commons 的 URLCodec 也基于字符串,但也适用于 byte[] 数组。这是一个设计级别的约束,它本质上阻止了对较大或可变长度输入的有效处理。相反,应该支持“支持流”的接口,例如 CharSequence、Appendable 和 java.nio 的 ByteBuffer 和 CharBuffer 的 Buffer 实现。

...

请注意,com.ziesemer.utils.urlCodec 的速度是 JDK URLEncoder 的 3 倍以上,是 JDK URLDecoder 的 1.5 倍以上。(JDK 的 URLDecoder 比 URLEncoder 快,因此没有太大的改进空间。)

我认为您的同事建议 URLDecode 不是线程安全的是错误的。这里的其他答案详细解释。

编辑 [2012-07-03] - 根据 OP 稍后发表的评论

不确定您是否在寻找更多想法?您是正确的,如果您打算将列表作为原子集合进行操作,那么您将必须同步对列表的所有访问,包括您的方法之外的引用。但是,如果您对可能与原始列表不同的返回列表内容感到满意,那么对集合中可能被其他线程修改的“批量”字符串进行操作的蛮力方法可能如下所示:

/**
 * @param origList will be copied by this method so that origList can continue
 *                 to be read/write by other threads. 
 * @return list containing  decoded strings for each entry that was 
           in origList at time of copy.
 */
public List<String> decodeListOfStringSafely(List<String> origList)
        throws UnsupportedEncodingException {
    List<String> snapshotList = new ArrayList<String>(origList);
    List<String> newList  = new ArrayList<String>(); 

    for (String urlStr : snapshotList) {
      String decodedUrlStr  = URLDecoder.decode(urlStr, "UTF8");
          newList.add(decodedUrlStr);
    }

    return newList;
}

如果这没有帮助,那么我仍然不确定你在追求什么,你会更好地创建一个新的、更简洁的问题。如果这是您要问的问题,那么请小心,因为出于多种原因,此示例脱离上下文并不是一个好主意。

于 2012-05-01T18:13:28.953 回答
0

静态函数实际上从来没有真正需要线程安全(或者它是设计失败)。如果甚至不访问类中的静态变量,则尤其如此。

我建议使用您之前使用的函数,并遍历集合

于 2012-05-01T18:10:12.043 回答
0

Apache 有URLCodec可用于编码解码。

如果您的静态方法仅适用于局部变量或最终初始化变量,那么它是完全线程安全的。

由于参数存在于堆栈上并且它们是完全线程安全的,因此最终常量是不可变的,因此无法更改。

以下代码是完全线程安全的:

public static String encodeMyValue(String value){
  // do encoding here
}

如果最终变量是可变的,则应小心,这意味着您无法重新分配它,但您可以更改其内部表示(属性)。

于 2012-05-01T18:06:02.170 回答
0

基本上没有应用于静态方法或实例方法或构造函数的神奇线程安全。除非应用同步,否则它们都可以同时在多个线程上调用。如果他们不获取或更改任何共享数据,他们通常是安全的 - 如果他们确实访问共享数据,您需要更加小心。

因此,在您的情况下,您可以在此 urldecoding 或 encoding 之上编写同步方法,您可以通过该方法在外部强制执行线程安全。

于 2012-05-01T18:28:01.250 回答