【问题标题】:charAt() or substring? Which is faster?charAt() 还是子字符串?哪个更快?
【发布时间】:2010-12-12 22:42:04
【问题描述】:

我想遍历字符串中的每个字符并将字符串的每个字符作为字符串传递给另一个函数。

String s = "abcdefg";
for(int i = 0; i < s.length(); i++){
    newFunction(s.substring(i, i+1));}

或

String s = "abcdefg";
for(int i = 0; i < s.length(); i++){
    newFunction(Character.toString(s.charAt(i)));}

最终结果需要是一个字符串。那么有什么更快或更高效的想法吗?

【问题讨论】:

    标签: java string performance character substring


    【解决方案1】:

    像往常一样:没关系,但如果您坚持花时间进行微优化,或者如果您真的想针对非常特殊的用例进行优化,试试这个:

    import org.junit.Assert;
    import org.junit.Test;
    
    public class StringCharTest {
    
        // Times:
        // 1. Initialization of "s" outside the loop
        // 2. Init of "s" inside the loop
        // 3. newFunction() actually checks the string length,
        // so the function will not be optimized away by the hotstop compiler
    
        @Test
        // Fastest: 237ms / 562ms / 2434ms
        public void testCacheStrings() throws Exception {
            // Cache all possible Char strings
            String[] char2string = new String[Character.MAX_VALUE];
            for (char i = Character.MIN_VALUE; i < Character.MAX_VALUE; i++) {
                char2string[i] = Character.toString(i);
            }
    
            for (int x = 0; x < 10000000; x++) {
                char[] s = "abcdefg".toCharArray();
                for (int i = 0; i < s.length; i++) {
                    newFunction(char2string[s[i]]);
                }
            }
        }
    
        @Test
        // Fast: 1687ms / 1725ms / 3382ms
        public void testCharToString() throws Exception {
            for (int x = 0; x < 10000000; x++) {
                String s = "abcdefg";
                for (int i = 0; i < s.length(); i++) {
                    // Fast: Creates new String objects, but does not copy an array
                    newFunction(Character.toString(s.charAt(i)));
                }
            }
        }
    
        @Test
        // Very fast: 1331 ms/ 1414ms / 3190ms
        public void testSubstring() throws Exception {
            for (int x = 0; x < 10000000; x++) {
                String s = "abcdefg";
                for (int i = 0; i < s.length(); i++) {
                    // The fastest! Reuses the internal char array
                    newFunction(s.substring(i, i + 1));
                }
            }
        }
    
        @Test
        // Slowest: 2525ms / 2961ms / 4703ms
        public void testNewString() throws Exception {
            char[] value = new char[1];
            for (int x = 0; x < 10000000; x++) {
                char[] s = "abcdefg".toCharArray();
                for (int i = 0; i < s.length; i++) {
                    value[0] = s[i];
                    // Slow! Copies the array
                    newFunction(new String(value));
                }
            }
        }
    
        private void newFunction(String string) {
            // Do something with the one-character string
            Assert.assertEquals(1, string.length());
        }
    
    }
    

    【讨论】:

    • 由于这将传递一个字符串,您需要在第一次测试中稍微更改您的测试。 {char[] s = "abcdefg".toCharArray();} 应该在循环内,甚至更好(为了防止 JVM 巧妙地优化,将整个循环和 .toCharArray() 放在单独的函数内)。衡量所有初始开销以及循环成本非常重要。特别是因为性能实际上可以根据字符串长度从一个到另一个倾斜。所以测试不同长度的刺也很重要。
    • 在循环内移动了“s”并添加了一个 assert() 以防止 JVM 优化 newFunction()。当然现在变慢了,但是相对测量值还是一样的。我的观点只是,如果确切知道问题,就有可能进行优化。关键不是改变某个操作使用哪个功能,而是在更高的层次上查看操作以获得改进,例如通过缓存
    • 请注意,从 Java 7u6 开始,子字符串变成了复制。见stackoverflow.com/questions/14161050/…
    【解决方案2】:

    答案是:it doesn't matter。

    分析您的代码。这是你的瓶颈吗?

    【讨论】:

    • 个人资料以什么方式?对于内存使用情况?
    【解决方案3】:

    newFunction 真的需要String 吗?如果你可以让newFunction 取一个char 并这样称呼它会更好:

    newFunction(s.charAt(i));
    

    这样,您可以避免创建临时 String 对象。

    回答您的问题:很难说哪个更有效。在这两个示例中,都必须创建一个仅包含一个字符的 String 对象。哪个更有效取决于在您的特定 Java 实现上究竟如何实现 String.substring(...) 和 Character.toString(...)。找出它的唯一方法是通过分析器运行程序并查看哪个版本使用更多 CPU 和/或更多内存。通常,您不必担心此类微优化 - 仅在您发现这是性能和/或内存问题的原因时才花时间进行此操作。

    【讨论】:

    • newFunction 确实需要一个字符串。除了单个字符,newFunction 还可以处理更长的字符串。它以同样的方式处理它们。我不想重载 newFunction 来接收一个字符,因为它在两种情况下都做同样的事情。
    • 我完全同意在开发中应该避免微优化,直到发现它是必要的。我还认为,作为一项学习练习,了解内存分配和其他“隐藏行为”非常重要。我个人厌倦了天真的程序员因为认为短 = 高性能而淘汰短代码,并且在不知不觉中使用了效率极低的算法。不学这个的人=懒惰。被这个固定的人=慢。有一个平衡需要达成。在我看来:)
    • @estacado:如果性能是您的驱动力(正如您的帖子所暗示的那样),请在正确的位置进行优化。重载新函数以避免字符串开销 - 可能是明智的选择,具体取决于基于 [char] 的版本的外观。围绕函数扭曲代码可能会更耗时、效率更低且难以维护。
    【解决方案4】:

    在你发布的两个 sn-ps 中,我不想说。我同意 Will 的观点,几乎可以肯定它与您的代码的整体性能无关 - 如果不是,您可以进行更改并自行确定使用硬件上的 JVM 对您的数据而言哪个是最快的。

    也就是说,如果您先将 String 转换为 char 数组,然后对数组执行迭代,那么第二个 sn-p 可能会更好。这样做只会执行一次字符串开销(转换为数组),而不是每次调用。此外,您可以然后将数组直接传递给带有一些索引的 String 构造函数,这比将数组的 char out 单独传递(然后变成一个字符数组)更有效):

    String s = "abcdefg";
    char[] chars = s.toCharArray();
    for(int i = 0; i < chars.length; i++) {
        newFunction(String.valueOf(chars, i, 1));
    }
    

    但是为了强调我的第一点,当您查看每次调用 String.charAt() 时实际上要避免的内容时 - 这是两个边界检查、一个(惰性)布尔 OR 和一个加法。这不会产生任何明显的差异。 String 构造函数也没有区别。

    本质上,这两个习惯用法在性能方面都很好(两者都没有立即明显低效),因此您不应该再花时间处理它们,除非分析器显示这会占用您应用程序的大量运行时间。即使这样,您也几乎可以肯定地通过在该领域重构您的支持代码来获得更多的性能提升(例如,让newFunction 获取整个字符串本身);在这一点上,java.lang.String 已经得到了很好的优化。

    【讨论】:

    • 当前 jvm 中的 substring 实际上使用原始字符数组作为后备存储,而您正在启动副本。所以我的直觉说 substring 实际上会更快,因为 memcpy 可能会更昂贵(取决于字符串的大小,越大越好)。
    【解决方案5】:

    我将首先使用 String.toCharArray() 从源字符串中获取底层 char[],然后继续调用 newFunction。

    但我确实同意 Jesper 的观点,即最好只处理字符并避免所有字符串函数...

    【讨论】:

    • 据我所知, String.charAt(i) 会进行该查找。将字符串复制到新数组(这是我理解 String.toCharArray() 要做的)引入了新的和不同的开销。重复传递对 charAt() 的字符串引用比先转换为本机数组慢吗?我怀疑这取决于字符串的长度...
    • 总有取舍 :) 只有 OP 才能真正说出什么更有效。
    【解决方案6】:

    Leetcode 似乎更喜欢子字符串选项here。

    这就是我解决这个问题的方法:

    class Solution {
    public int strStr(String haystack, String needle) {
        if(needle.length() == 0) {
            return 0;
        }
    
        if(haystack.length() == 0) {
            return -1;
        }
    
        for(int i=0; i<=haystack.length()-needle.length(); i++) {
            int count = 0;
            for(int j=0; j<needle.length(); j++) {
                if(haystack.charAt(i+j) == needle.charAt(j)) {
                    count++;
                }
            }
            if(count == needle.length()) {
                return i;
            }
        }
        return -1;
    }
    

    }

    这是他们给出的最佳解决方案:

    class Solution {
    public int strStr(String haystack, String needle) {
        int length;
        int n=needle.length();
        int h=haystack.length();
        if(n==0)
            return 0;
        // if(n==h)
        //     length = h;
        // else
            length = h-n;
        if(h==n && haystack.charAt(0)!=needle.charAt(0))
                return -1;
        for(int i=0; i<=length; i++){
            if(haystack.substring(i, i+needle.length()).equals(needle))
                return i;
        }
        return -1;
    }
    

    }

    老实说,我不明白为什么这很重要。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-05-09
      • 2011-05-14
      • 2014-05-16
      • 2011-03-26
      • 2012-06-12
      • 2010-09-29
      • 2011-03-14
      • 2012-07-15
      相关资源
      最近更新 更多