7

我正在学习 Java,我想知道在这里修改字符串的最佳方法是什么(为了提高性能和学习 Java 中的首选方法)。假设您正在遍历一个字符串并检查每个字符/对字符串中的该索引执行一些操作。

我是使用StringBuilder该类,还是将字符串转换为 char 数组,进行修改,然后将 char 数组转换回字符串?

示例StringBuilder

StringBuilder newString = new StringBuilder(oldString);
for (int i = 0; i < oldString.length() ; i++) {
    newString.setCharAt(i, 'X');    
}

字符数组转换示例:

char[] newStringArray = oldString.toCharArray();
for (int i = 0; i < oldString.length() ; i++) {
    myNameChars[i] = 'X';    
}    
myString = String.valueOf(newStringArray);

每种不同方式的优缺点是什么?

我认为这StringBuilder会更有效,因为每次更新索引时转换为 char 数组都会复制数组。

4

4 回答 4

4

我说做任何最可读/可维护的事情,直到您知道字符串“修改”正在减慢您的速度。对我来说,这是最易读的:

Sting s = "foo";
s += "bar";
s += "baz";

如果这太慢,我会使用StringBuilder. 您可能想将此与StringBuffer. 如果性能很重要而同步不重要,StringBuilder应该更快。如果需要同步,则应使用StringBuffer.

同样重要的是要知道这些字符串没有被修改。在java中,Strings是不可变的。


这都是特定于上下文的。如果您优化此代码并且它没有产生明显的差异(通常是这种情况),那么您只是想得比您需要的时间更长,并且您可能使您的代码更难以理解。在需要时进行优化,而不是因为可以。在您这样做之前,请确保您正在优化的代码是导致性能问题的原因。

于 2013-10-09T18:16:43.057 回答
1

每种不同方式的优点/缺点是什么。我认为 StringBuilder 会更有效率,因为每次更新索引时转换为 char 数组都会生成数组的副本。

如所写,第二个示例中的代码将仅创建两个数组:一个在您调用时创建toCharArray(),另一个在您调用时创建String.valueOf()String将数据存储在 char[] 数组中)。您正在执行的元素操作不应触发任何对象分配。当您读取或写入元素时,不会对数组进行复制。

如果您要进行任何类型的String操作,推荐的做法是使用StringBuilder. 如果您正在编写对性能非常敏感的代码,并且您的转换不会改变字符串的长度,那么直接操作数组可能是值得的。但是由于您正在学习 Java 作为一门新语言,我猜您不是在高频交易或任何其他延迟至关重要的环境中工作。因此,您最好使用StringBuilder.

如果您正在执行任何可能产生与原始字符串长度不同的字符串的转换,您几乎肯定应该使用StringBuilder; 它将根据需要调整其内部缓冲区的大小。

在相关的说明中,如果您正在执行简单的字符串连接(例如,s = "a" + someObject + "c"),编译器实际上会将这些操作转换为一系列StringBuilder.append()调用,因此您可以自由使用任何您认为更美观的方法。我个人更喜欢+运营商。但是,如果您要跨多个语句构建字符串,则应该创建一个StringBuilder.

例如:

public String toString() {
    return "{field1 =" + this.field1 + 
           ",  field2 =" + this.field2 + 
           ...
           ",  field50 =" + this.field50 + "}";
}

在这里,我们有一个包含许多串联的单一长表达式。您无需担心手动优化它,因为编译器将使用单个并重复StringBuilder调用它。append()

String s = ...;
if (someCondition) {
    s += someValue;
}
s += additionalValue;
return s;

在这里,您最终会在StringBuilders幕后创建两个,但除非这是延迟关键应用程序中非常热的代码路径,否则真的不值得担心。给定类似的代码,但有更多单独的连接,可能值得优化。如果您知道字符串可能非常大,也是如此。但不要只是猜测——测量!在尝试修复之前证明存在性能问题。 (注意:这只是“微优化”的一般规则;显式使用 a 很少有缺点StringBuilder。但不要认为它会产生可衡量的差异:如果你担心它,你应该实际测量。)

String s = "";
for (final Object item : items) {
    s += item + "\n";
}

在这里,我们对每个循环迭代执行单独的连接操作,这意味着每次循环StringBuilder都会分配一个新的。在这种情况下,可能值得使用单个StringBuilder,因为您可能不知道集合有多大。我会认为这是“在优化规则之前证明存在性能问题”的一个例外:如果操作有可能根据输入的复杂性爆炸,请谨慎行事。

于 2013-10-09T18:28:30.163 回答
1

哪个选项表现最好并不是一个简单的问题。

我使用Caliper做了一个基准测试

                RUNTIME (NS)
array           88
builder         126
builderTillEnd  76
concat          3435

基准方法:

public static String array(String input)
{
    char[] result = input.toCharArray(); // COPYING
    for (int i = 0; i < input.length(); i++)
    {
        result[i] = 'X';
    }
    return String.valueOf(result); // COPYING
}

public static String builder(String input)
{
    StringBuilder result = new StringBuilder(input); // COPYING
    for (int i = 0; i < input.length(); i++)
    {
        result.setCharAt(i, 'X');
    }
    return result.toString(); // COPYING
}

public static StringBuilder builderTillEnd(String input)
{
    StringBuilder result = new StringBuilder(input); // COPYING
    for (int i = 0; i < input.length(); i++)
    {
        result.setCharAt(i, 'X');
    }
    return result;
}

public static String concat(String input)
{
    String result = "";
    for (int i = 0; i < input.length(); i++) 
    {
        result += 'X'; // terrible COPYING, COPYING, COPYING... same as:
                       // result = new StringBuilder(result).append('X').toString();
    }
    return result;
}

评论

  1. 如果我们想修改一个字符串,我们必须至少复制该输入字符串的 1 个副本,因为 Java 中的字符串是不可变的。

  2. java.lang.StringBuilder延伸java.lang.AbstractStringBuilderStringBuilder.setCharAt()继承自AbstractStringBuilder并看起来像这样:

    public void setCharAt(int index, char ch) {
        if ((index < 0) || (index >= count))
            throw new StringIndexOutOfBoundsException(index);
        value[index] = ch;
    }
    

    AbstractStringBuilder内部使用最简单的 char 数组:char value[]. 因此,result[i] = 'X'与 非常相似result.setCharAt(i, 'X'),但是第二个将调用多态方法(可能被 JVM 内联)并检查边界if,因此会慢一些。

结论

  1. 如果您可以一直操作StringBuilder到最后(您不需要 String 返回) - 就这样做。这是首选方式,也是最快的方式。简直是最好的。

  2. 如果你最后想要 String并且这是你程序的瓶颈,那么你可以考虑使用 char 数组。在基准 char 数组中,比StringBuilder. 确保在优化前后正确测量程序的执行时间,因为无法保证这 25%。

  3. +永远不要用or连接循环中的字符串+=,除非你真的知道你在做什么。通常最好使用显式StringBuilderappend().

于 2013-10-09T18:41:54.447 回答
0

我更喜欢使用StringBuilder修改原始字符串的类。

对于字符串操作,我喜欢StringUtil类。您需要获取 Apache 公共依赖项才能使用它

于 2013-10-09T18:21:11.590 回答