【问题标题】:How to speed up a loop that does string manipulation in Java? [closed]如何加快在 Java 中进行字符串操作的循环? [关闭]
【发布时间】:2016-08-29 02:29:21
【问题描述】:

我有一个在循环中构建字符串的程序,但我的程序太慢了。现在运行Oblig1Test.oppgave7 大约需要 600 毫秒。可以做些什么来加快速度?

Oblig1.toString:

public static String toString(int[] a, char v, char h, String mellomrom)
{
    String s ="";

    s += v;

    if(a.length != 0)
    {
        for(int i = 0; i < a.length-1; i++)
        {
                s += a[i] + mellomrom; 
        }

        s += a[a.length-1];
    }

    s += h;

    return s;
}

Oblig1Test:

public static int oppgave7()
{
   int[] b = new int[20000];
   long tid = System.currentTimeMillis();
   Oblig1.toString(b,' ',' '," ");
   tid = System.currentTimeMillis() - tid;

  if (tid > 40)
  {
    System.out.println("Oppgave 7: Metoden "
      + "er for ineffektiv. Må forbedres!");
  }
}

public static void main(String[] args) throws IOException
{
   oppgave7();
}

【问题讨论】:

  • 使用 StringBuilder 构建你的字符串
  • 我在这里并没有真正得到反对意见:OP 有一个精确的问题,并制造了复制所需的一切。
  • 这个问题似乎是题外话,因为它属于Code Review

标签: java string


【解决方案1】:

当您的代码中的缓慢操作是多个字符串的串联时,您可能会通过使用StringBuilder 获得很多收益。

通过将您的 toString 方法更改为

public static String toString(int[] a, char v, char h, String mellomrom){
    StringBuilder sb = new StringBuilder();
    sb.append(v);
    if(a.length != 0){
        for(int i = 0; i < a.length-1; i++){
                sb.append(a[i]).append(mellomrom); 
        }
        sb.append(a[a.length-1]);
    }
    sb.append(h);
    return sb.toString();
}

我能够在我的计算机上从 493 毫秒到 22 毫秒。

【讨论】:

【解决方案2】:

使用StringBuilder

public static String toString(int[] a, char v, char h, String mellomrom) {
    StringBuilder sb = new StringBuilder();
    sb.append(v);
    for(int i = 0 ; i < a.length - 1 ; ++i) {
        sb.append(a[i]).append(mellomrom);
    }
    if(a.length != 0) {
        sb.append(a[a.length-1]);
    }
    sb.append(h);
    return sb.toString();
}

【讨论】:

    【解决方案3】:

    首先,变量的单字符名称?坏小子! :) 给事物起有意义的名字,它不需要任何成本。

    其次,Java 中的字符串是不可变的。声明

    String concatedString = concatedString + secondString
    

    不是“将 secondString 的值附加到 concatedString”,而是“创建一个新的 String,即 concatedString 和 secondString 的值,丢弃 concatedString 并使 concatedString 引用引用新字符串”。换句话说,每次您使用 + 连接字符串时,您都在隐式创建一个新的 String 对象。

    在循环中创建对象非常昂贵。

    Java 提供了一组可变字符串,例如 StringBuilder 和 StringBuffer(后者是线程安全的,但性能较差)。请改用其中之一。

    【讨论】:

    • 请注意,这不一定是正确的(至少在今天如此)。您的示例在当前状态下使用 Java 1.6+,将使用 StringBuilder 为您自动重写,您无需执行任何操作。然而,在 OP 的情况下,javac 只会优化他的部分功能,他应该更明确地使用它自己使用单个 StringBuilder 实例。
    • 我不知道自动创建字符串缓冲区,我必须记住这一点。但鉴于您提到的警告,依赖它可能是不明智的。
    • 不,但总的来说,除了特别密集的字符串构建案例外,我倾向于不再对使用 StringBuilders 大惊小怪。因为我现在大部分时间只针对 Java 6+ 平台,所以我不太担心,或者我重写我的 toString() 以创建 javac 能够自己完成大部分工作并获得两全其美:不错的可读性和不错的速度,尽管不是最好的。我建议您使用javap 查看一些简单示例的字节码。很有趣。
    • 看到这个conversation with Heinz Kabutz。请注意,其中一些可能已经过时,但这会告诉您要查找的内容。
    【解决方案4】:

    是的,这是一个常见问题。

    Java 中的字符串是不可变的,这意味着一旦构造了 String 对象,就不能以任何方式对其进行修改,并且所有对它的操作都是通过复制内容来完成的。

    在您的特定情况下,当您执行s += vs += a[i] + mellomrom 时,它实际上将s 字符串的内容复制到新的StringBuilder,连接各个部分,然后从中生成String StringBuilder。所以,

    s += v
    

    变成

    s = new StringBuilder(s).append(v).toString();
    

    s += a[i] + mellomrom
    

    变成

    s = new StringBuilder(s).append(a[i]).append(mellomrom).toString();
    

    这在循环中完成时会造成严重的性能灾难(构造两个新对象,复制内容两次)。要在循环中构造字符串,请使用您自己的 StringBuilder 实例,这样您就只有一个实例,而不必在每次迭代时将其转换为 String

    public static String toString(int[] a, char v, char h, String mellomrom)
    {
        StringBuilder sb = new StringBuilder();
        sb.append(v);
    
        if(a.length != 0)
        {
            for(int i = 0; i < a.length-1; i++)
            {
                sb.append(a[i]).append(mellomrom);
            }
            sb.append(a[a.length-1]);
        }
        sb.append(h);
        return sb.toString();
    }
    

    【讨论】:

    • 在他的例子中,他甚至可以从一开始就设置 StringBuilder 的确切容量,就像我们现在要使用的大小和分隔符一样。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-18
    • 2013-06-26
    • 2010-09-20
    • 1970-01-01
    相关资源
    最近更新 更多