【问题标题】:JVM String methods implementationJVM 字符串方法实现
【发布时间】:2014-07-30 01:59:12
【问题描述】:

String 类有一些我不明白为什么要这样实现的方法... replace 就是其中之一。

public String replace(CharSequence target, CharSequence replacement) {
    return Pattern.compile(target.toString(), Pattern.LITERAL).matcher(
            this).replaceAll(Matcher.quoteReplacement(replacement.toString()));
}

与更简单、更高效(快速!)的方法相比,是否有一些显着优势?

public static String replace(String string, String searchFor, String replaceWith) {

    StringBuilder result=new StringBuilder();

    int index=0;
    int beginIndex=0;
    while((index=string.indexOf(searchFor, index))!=-1){
        result.append(string.substring(beginIndex, index)+replaceWith);
        index+=searchFor.length();
        beginIndex=index;
    }
    result.append(string.substring(beginIndex, string.length()));

    return result.toString();

}

Java 7 的统计数据:
1,000,000 次迭代
在“a.b.c”中将“b”替换为“x”
结果:“a.x.c”

时代:
string.replace: 485ms
string.replaceAll:490ms
优化替换 = 180ms

Java 7 split 方法等代码经过大量优化,尽可能避免模式编译/正则表达式处理:

public String[] split(String regex, int limit) {
    /* fastpath if the regex is a
     (1)one-char String and this character is not one of the
        RegEx's meta characters ".$|()[{^?*+\\", or
     (2)two-char String and the first char is the backslash and
        the second is not the ascii digit or ascii letter.
     */
    char ch = 0;
    if (((regex.value.length == 1 &&
         ".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||
         (regex.length() == 2 &&
          regex.charAt(0) == '\\' &&
          (((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 &&
          ((ch-'a')|('z'-ch)) < 0 &&
          ((ch-'A')|('Z'-ch)) < 0)) &&
        (ch < Character.MIN_HIGH_SURROGATE ||
         ch > Character.MAX_LOW_SURROGATE))
    {
        int off = 0;
        int next = 0;
        boolean limited = limit > 0;
        ArrayList<String> list = new ArrayList<>();
        while ((next = indexOf(ch, off)) != -1) {
            if (!limited || list.size() < limit - 1) {
                list.add(substring(off, next));
                off = next + 1;
            } else {    // last one
                //assert (list.size() == limit - 1);
                list.add(substring(off, value.length));
                off = value.length;
                break;
            }
        }
        // If no match was found, return this
        if (off == 0)
            return new String[]{this};

        // Add remaining segment
        if (!limited || list.size() < limit)
            list.add(substring(off, value.length));

        // Construct result
        int resultSize = list.size();
        if (limit == 0)
            while (resultSize > 0 && list.get(resultSize - 1).length() == 0)
                resultSize--;
        String[] result = new String[resultSize];
        return list.subList(0, resultSize).toArray(result);
    }
    return Pattern.compile(regex).split(this, limit);
}

遵循replace方法的逻辑:

public String replaceAll(String regex, String replacement) {
    return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}

拆分实现应该是:

public String[] split(String regex, int limit) {
    return Pattern.compile(regex).split(this, limit);
}

性能损失与替换方法上的损失相差不远。出于某种原因,Oracle 在某些方法上提供了 fastpath 方法,而不是在其他方法上。

【问题讨论】:

  • "Java原生方法实现的原因是什么?"
  • 他们的replace() 使用他们的replaceAll()。那里有什么问题?为什么要复制代码进行替换?
  • replace方法做高效replaceAll(即不涉及regex)
  • @HenryKeiter - 但replace 不进行正则表达式处理。
  • 我的猜测是,在某一时刻,他们正在重新设计所有毛茸茸的 String 函数以解决一些变化(可能在用 CharSequence 替换 String 时),并且使用通用代码更容易/更安全可能而不是自定义编码每个功能。

标签: java string methods jvm implementation


【解决方案1】:

您确定您提出的方法确实比String 类使用的基于正则表达式的方法更快吗?不仅适用于您自己的测试输入,还适用于程序可能抛出的每个可能的输入?它依赖String.indexOf 进行子字符串匹配,这本身就是一个幼稚的实现,会受到最坏情况下的性能的影响。 Pattern 完全有可能实现了更复杂的匹配算法,例如 KMP,以避免冗余比较。

一般来说,Java 团队非常重视核心库的性能,并使用大量真实数据维护大量内部基准。我从未遇到过正则表达式处理成为瓶颈的情况。我的长期建议是从编写最简单且能正常工作的代码开始,甚至不要开始考虑重写 Java 内置函数,直到分析证明这是一个瓶颈,并且您已经用尽了所有其他优化途径。

关于您的最新编辑 - 首先,我不会将 split 方法描述为高度优化。它处理一种恰好非常常见的特殊情况,并且保证不会遭受上述简单字符串匹配算法所描述的糟糕的最坏情况复杂性——即拆分为单个字符的文字标记。

很可能相同的特殊情况可以针对replace 进行优化,并提供一些可衡量的改进。但是看看实现这个简单优化需要什么——大约 50 行代码。这些代码行是有代价的,尤其是当它们可能是 Java 库中使用最广泛的类的一部分时。成本有多种形式:

  • 资源 - 这是一些开发人员在 Java 语言的整个生命周期中必须花费时间编写、测试、记录和维护的 50 行代码。
  • 风险 - 这是 50 次通过初始测试的细微错误的机会。
  • 复杂性 - 这是任何想要了解该方法如何工作的开发人员现在必须花时间阅读和理解的 50 行额外代码。

您的问题现在归结为“为什么优化了一种方法以处理特殊情况,而没有优化另一种方法?”或者更笼统地说“为什么没有实现这个特定的功能?”除了原作者之外,没有人能明确回答这个问题,但答案几乎总是要么对该功能没有足够的需求,要么认为拥有该功能所带来的好处不值得添加它的成本。

【讨论】:

  • 我已经测试了很多场景(大字符串,许多替换点等)并且差异是一致的,但正如我之前所说,在大多数情况下性能成本不会相关。但它仍然让我感到困惑,因为我从 SPLIT 之类的方法中查看代码,并且它们经过优化以避免在不必要时进行模式编译/正则表达式处理......
  • @marcolopes 请查看我编辑的答案 - 现在您的问题非常不同,因为它包含了与 split 方法的比较。
  • 您的回答是决定性的。我会接受的。我相信问题标题的更改是有序的。
猜你喜欢
  • 1970-01-01
  • 2011-05-30
  • 2014-09-28
  • 1970-01-01
  • 2016-08-02
  • 1970-01-01
  • 2013-10-12
  • 2016-09-03
  • 1970-01-01
相关资源
最近更新 更多