String#indexOf:为什么用 JDK 代码比较 1500 万个字符串比我的代码快?

时间:2021-02-17 16:18:41

标签: java jvm

答案可能存在于某处,但我找不到。我是从我正在创建的算法中提出这个问题的。本质上是一个 .contains(String s1, String s2),如果 s1 包含 s2,则返回 true,忽略希腊语/英语字符差异。例如,字符串 'nai, course' 包含字符串 'ναι'。但是,这与我的问题无关。

contains()String 方法使用 naive approach,我的算法也使用相同的方法。 contains() 的本质是用正确的参数调用 static 中存在的 indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex) java.lang.String.class

当我对我的算法进行不同类型的基准测试和测试时,我删除了所有希腊语 - 英语逻辑,以查看它仅使用英语字符串的表现有多快。而且速度更慢。大约比来自 JDK 的 s1.contains(s2) 慢 2 倍。

因此,我花时间将这个 indexOf 方法复制并粘贴到我的类中,并以 JDK 为字符串调用它的相同方式调用了 1500 万次。

类如下:

public class TestIndex {
    public static int fromJdk;
    public static int fromMe;

    public static void main(String[] args) {
        String s1 = "papapatita";
        String s2 = "patita";
        final char[] s1Arr = s1.toCharArray();
        int s1Length = s1Arr.length;

        final char[] s2Arr = s2.toCharArray();
        int s2Length = s2Arr.length;

        long be4 = System.nanoTime();
        for (int i = 0; i < 15_000_000; i++) {
            fromJdk = s1.indexOf(s2);
            //          fromMe = indexOf(s1Arr, 0, s1Length, s2Arr, 0, s2Length, 0);
        }
        long after = System.nanoTime();
        System.out.println((after - be4) / 1000000.0);
    }

    static int indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset,
            int targetCount, int fromIndex) {
        if (fromIndex >= sourceCount) {
            return (targetCount == 0 ? sourceCount : -1);
        }
        if (fromIndex < 0) {
            fromIndex = 0;
        }
        if (targetCount == 0) {
            return fromIndex;
        }

        char first = target[targetOffset];
        int max = sourceOffset + (sourceCount - targetCount);

        for (int i = sourceOffset + fromIndex; i <= max; i++) {
            /* Look for first character. */
            if (source[i] != first) {
                while (++i <= max && source[i] != first)
                    ;
            }

            /* Found first character, now look at the rest of v2 */
            if (i <= max) {
                int j = i + 1;
                int end = j + targetCount - 1;
                for (int k = targetOffset + 1; j < end && source[j] == target[k]; j++, k++)
                    ;

                if (j == end) {
                    /* Found whole string. */
                    return i - sourceOffset;
                }
            }
        }
        return -1;
    }
}

虽然 fromJdk = s1.indexOf(s2); 没有注释,但结果是(在我的机器上)大约 150 毫秒。如果我对此行进行注释并保留 fromMe = indexOf(s1Arr, 0, s1Length, s2Arr, 0, s2Length, 0); 不加注释,我应该得到相同的结果。结果几乎是两倍。大约 300 毫秒。

那么,问题是为什么? indexOf 方法与它在 JDK 中的存在完全一样。我知道在 Java 中测量性能是一件很难的事情(可能是 JMH)?但是差别很大。来自 JDK 的 indexOf 是否在幕后得到特殊处理?

如果某处有答案,请指点我。

1 个答案:

答案 0 :(得分:4)

因为 String.indexOf() 是一个 intrinsic method,所以 JDK 调用的是本机实现,而您的调用是 Java 实现。

JVM 实际上并不执行您看到的 Java 代码,它知道可以用更高效的版本替换它。当您复制代码时,它会通过常规的 JIT 编译,从而降低效率。只是 JVM 为提高性能所做的 dozens of tricks 之一,开发人员通常甚至没有意识到。