JVM String方法实现

时间:2014-06-09 13:54:59

标签: java string methods jvm implementation

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次迭代
将“b”替换为“a.b.c”中的“x” 结果:“a.x.c”

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

像Java 7拆分方法这样的代码经过大量优化,以尽可能避免模式编译/正则表达式处理:

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);
}

遵循替换方法的逻辑:

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在某些方法上提供了快速路径方法,而不是其他方法。

1 个答案:

答案 0 :(得分:7)

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

通常,Java团队非常重视核心库的性能,并使用各种实际数据维护大量内部基准。我从来没有遇到过正则表达式处理成为瓶颈的情况。我的常设建议是从编写最简单的可正常代码开始,甚至不要考虑重写Java内置函数,直到分析证明它是瓶颈,并且你已经耗尽了所有其他优化途径。

关于您的最新编辑 - 首先,我不会将split方法描述为经过大量优化。它处理了一个非常常见的特殊情况,并且保证不会遭受上面针对天真字符串匹配算法描述的差的最坏情况复杂性 - 分裂单个字符,文字标记。

很可能同样的特殊情况可以针对replace进行优化,并会提供一些可衡量的改进。但是看看实现这种简单优化所花费的时间 - 大约50行代码。这些代码行是有代价的,特别是当它们成为Java库中最广泛使用的类的一部分时。成本有多种形式:

  • 资源 - 一些开发人员必须花费时间编写,测试,记录和维护Java语言的50行代码。
  • 风险 - 在初步测试过程中,有50个微妙漏洞的机会。
  • 复杂性 - 任何希望了解该方法如何工作的开发人员需要额外的50行代码,现在必须花时间阅读和理解。

现在你的问题归结为&#34;为什么这一种方法被优化以处理特殊情况,而不是另一种?&#34;甚至更普遍的&#34;为什么这个特殊功能没有实现?&#34;除了原作者之外,没有人能够明确地回答这个问题,但答案几乎总是要么对该功能没有足够的需求,要么认为使用该功能所带来的好处不值得添加它的成本。