Java中String和StringBuffer的区别是什么?

62

Java中String和StringBuffer有什么区别?

String是否有最大长度限制?


14
将来,您可以查看Javadocs以获取此类问题的答案。Java 6 javadocs: http://java.sun.com/javase/6/docs/api/ String: http://java.sun.com/javase/6/docs/api/java/lang/String.html StringBuffer: http://java.sun.com/javase/6/docs/api/java/lang/StringBuffer.html - Seth
15个回答

75

String 用于操作无法更改的字符字符串(只读且不可变)。

StringBuffer 用于表示可以修改的字符。

在性能方面,当执行字符串连接时,StringBuffer 更快。这是因为每次连接 String 时,由于它是不可变的,内部都会创建一个新对象。

您还可以使用 StringBuilder,它类似于 StringBuffer,但不是同步的。这两者的最大大小均为 Integer.MAX_VALUE(231 - 1 = 2,147,483,647)或最大堆大小除以2(请参见这里)。更多信息请参见此处


你能提供长度的来源吗? - Thomas Lötzer
2
@Thomashttp://java.sun.com/javase/6/docs/api/java/lang/Integer.html#MAX_VALUE 如果你在谷歌上输入2^31 - 1,你会得到2,137,483,647。 - Vivin Paliath
我是指为什么字符串的长度不能更长,更重要的是,为什么可以假设它们最多只能包含Integer.MAX_VALUE个字符,而不仅仅是2^16。正如我在另一个答案中提到的,字符串字面量只能是2^16,因为它们需要适合常量池。 - Thomas Lötzer
@Thomas,Java在内部使用int来表示字符数组中字符的长度。这是理论上的长度。 - Vivin Paliath
@Thomas:如果我的程序中有一个大小为2^17的字符串字面量,它是否可以在类文件中分解为两个大小为2^16的字符串字面量,然后在运行时进行拼接?而且JVM规范也可能会发生变化。Javadocs是合同。 - President James K. Polk

34

String是不可变的,即在创建时就不能改变。

StringBuffer(或其非同步的表兄弟StringBuilder)用于需要逐个构建字符串而不会在此过程中构造大量小String的性能开销的情况。

两者的最大长度均为Integer.MAX_VALUE,因为它们在内部存储为数组,而Java数组只有一个int用于其长度伪字段。

对于多次连接,StringBufferString的性能提高相当显著。如果运行以下测试代码,则会看到差异。在我古老的笔记本电脑上使用Java 6,我得到以下结果:

Concat with String took: 1781ms
Concat with StringBuffer took: 0ms
public class Concat
{
    public static String concatWithString()
    {
        String t = "Cat";
        for (int i=0; i<10000; i++)
        {
            t = t + "Dog";
        }
        return t;
    }
    public static String concatWithStringBuffer()
    {
        StringBuffer sb = new StringBuffer("Cat");
        for (int i=0; i<10000; i++)
        {
            sb.append("Dog");
        }
        return sb.toString();
    }
    public static void main(String[] args)
    {
        long start = System.currentTimeMillis();
        concatWithString();
        System.out.println("Concat with String took: " + (System.currentTimeMillis() - start) + "ms");
        start = System.currentTimeMillis();
        concatWithStringBuffer();
        System.out.println("Concat with StringBuffer took: " + (System.currentTimeMillis() - start) + "ms");
    }
}

24
String                                          StringBuffer

Immutable                                       Mutable
String s=new String("karthik");                StringBuffer sb=new StringBuffer("karthik")
s.concat("reddy");                             sb.append("reddy");
System.out.println(s);                         System.out.println(sb);
O/P:karthik                                    O/P:karthikreddy

--->once we created a String object            ---->once we created a StringBuffer object
we can't perform any changes in the existing  we can perform any changes in the existing
object.If we are trying to perform any        object.It is nothing but mutablity of 
changes with those changes a new object       of a StrongBuffer object
will be created.It is nothing but Immutability
of a String object

Use String--->If you require immutabilty
Use StringBuffer---->If you require mutable + threadsafety
Use StringBuilder--->If you require mutable + with out threadsafety

String s=new String("karthik");
--->here 2 objects will be created one is heap and the other is in stringconstantpool(scp) and s is always pointing to heap object

String s="karthik"; 
--->In this case only one object will be created in scp and s is always pointing to that object only

1
真的吗?String s=new String("karthik"); 这会创建两个对象吗? - Asif Mushtaq

10

String是一个不可变的类。这意味着一旦你实例化了一个字符串实例,例如:

String str1 = "hello";

内存中的对象无法更改。相反,您将需要创建一个新实例,复制旧字符串并追加任何其他内容,如以下示例:

String str1 = "hello";
str1 = str1 + " world!";

这里实际上发生的是我们并没有更新现有的str1对象...我们重新分配了新的内存空间,将"hello"数据复制并在末尾添加了" world!",然后将str1引用设置为指向这个新的内存空间。所以在底层看起来更像是这样的:

String str1 = "hello";
String str2 = str1 + " world!";
str1 = str2;
因此,如果频繁地进行这种“复制+在内存中移动东西”的过程,特别是递归地进行时,它会非常昂贵。 当您面临反复执行某些操作的情况时,请使用StringBuilder。它是可变的,并且可以将字符串附加到当前字符串的末尾,因为它由一个可增长的数组支持(不确定是否实际使用了该数据结构,可能是列表)。

4

API:

一个线程安全可变的字符序列。StringBuffer类似于String,但可以被修改。在任何时刻,它都包含一些特定的字符序列,但是通过某些方法调用,序列的长度和内容可以被更改。


2

通过在append操作后打印String/StringBuffer对象的哈希码,也可以证明每次都会重新创建一个新的String对象,而不是使用相同的String对象。

public class MutableImmutable {

/**
 * @param args
 */
public static void main(String[] args) {
    System.out.println("String is immutable");
    String s = "test";
    System.out.println(s+"::"+s.hashCode());
    for (int i = 0; i < 10; i++) {
        s += "tre";
        System.out.println(s+"::"+s.hashCode());
    }

    System.out.println("String Buffer is mutable");

    StringBuffer strBuf = new StringBuffer("test");
    System.out.println(strBuf+"::"+strBuf.hashCode());
    for (int i = 0; i < 10; i++) {
        strBuf.append("tre");
        System.out.println(strBuf+"::"+strBuf.hashCode());
    }

 }

}

输出: 它打印对象值以及它的哈希码。
    String is immutable
    test::3556498
    testtre::-1422435371
    testtretre::-1624680014
    testtretretre::-855723339
    testtretretretre::2071992018
    testtretretretretre::-555654763
    testtretretretretretre::-706970638
    testtretretretretretretre::1157458037
    testtretretretretretretretre::1835043090
    testtretretretretretretretretre::1425065813
    testtretretretretretretretretretre::-1615970766
    String Buffer is mutable
    test::28117098
    testtre::28117098
    testtretre::28117098
    testtretretre::28117098
    testtretretretre::28117098
    testtretretretretre::28117098
    testtretretretretretre::28117098
    testtretretretretretretre::28117098
    testtretretretretretretretre::28117098
    testtretretretretretretretretre::28117098
    testtretretretretretretretretretre::28117098

2
我找到了Reggie Hutcherso关于比较String和StringBuffer性能的有趣答案。 来源: http://www.javaworld.com/javaworld/jw-03-2000/jw-0324-javaperf.html Java提供了StringBuffer和String类,其中String类用于操作不能更改的字符字符串。简单地说,String类型的对象是只读且不可变的。StringBuffer类用于表示可以修改的字符。
这两个类之间的显着性能差异在于,在执行简单的连接操作时,StringBuffer比String更快。在字符串操作代码中,字符字符串通常进行连接。使用String类,连接通常按以下方式执行:
 String str = new String ("Stanford  ");
 str += "Lost!!";

如果你使用StringBuffer来进行相同的字符串连接,你需要编写类似以下的代码:
 StringBuffer str = new StringBuffer ("Stanford ");
 str.append("Lost!!");

开发人员通常认为上面的第一个例子更有效,因为他们认为使用append方法进行连接的第二个例子比使用+运算符连接两个String对象的第一个例子更昂贵。
+运算符看起来很无辜,但生成的代码会产生一些意外。实际上,使用StringBuffer进行连接可以产生比使用String更快的代码。要发现这种情况的原因,我们必须检查两个示例生成的字节码。使用String的示例的字节码如下:
0 new #7 <Class java.lang.String>
3 dup 
4 ldc #2 <String "Stanford ">
6 invokespecial #12 <Method java.lang.String(java.lang.String)>
9 astore_1
10 new #8 <Class java.lang.StringBuffer>
13 dup
14 aload_1
15 invokestatic #23 <Method java.lang.String valueOf(java.lang.Object)>
18 invokespecial #13 <Method java.lang.StringBuffer(java.lang.String)>
21 ldc #1 <String "Lost!!">
23 invokevirtual #15 <Method java.lang.StringBuffer append(java.lang.String)>
26 invokevirtual #22 <Method java.lang.String toString()>
29 astore_1

位置0到9处的字节码用于执行第一行代码,即:
 String str = new String("Stanford ");

然后,位置在10到29的字节码被执行以进行连接操作:
 str += "Lost!!";

这里变得有趣了。拼接生成的字节码会创建一个StringBuffer对象,然后调用它的append方法:临时的StringBuffer对象在位置10创建,它的append方法在位置23被调用。由于String类是不可变的,必须使用StringBuffer进行拼接。
在对StringBuffer对象执行拼接后,它必须转换回String。这是通过在位置26调用toString方法来完成的。该方法从临时的StringBuffer对象创建一个新的String对象。创建这个临时的StringBuffer对象并将其转换回String对象非常昂贵。
总之,上面的两行代码会创建三个对象:
  1. 位置0处的一个String对象
  2. 位置10处的一个StringBuffer对象
  3. 位置26处的一个String对象
现在,让我们看一下使用StringBuffer的示例所生成的字节码。
0 new #8 <Class java.lang.StringBuffer>
3 dup
4 ldc #2 <String "Stanford ">
6 invokespecial #13 <Method java.lang.StringBuffer(java.lang.String)>
9 astore_1
10 aload_1 
11 ldc #1 <String "Lost!!">
13 invokevirtual #15 <Method java.lang.StringBuffer append(java.lang.String)>
16 pop

代码第一行的字节码从位置0到9被执行:
 StringBuffer str = new StringBuffer("Stanford ");

地址为10到16的字节码随后将被执行以进行连接操作:
 str.append("Lost!!");

请注意,与第一个例子一样,这段代码调用了StringBuffer对象的append方法。然而,与第一个例子不同的是,没有必要创建临时的StringBuffer对象,然后将其转换为String对象。这段代码只创建了一个对象,即位于0位置的StringBuffer。
总之,StringBuffer连接明显比String连接快得多。显然,在可能的情况下应该使用StringBuffer进行此类操作。如果需要使用String类的功能,请考虑使用StringBuffer进行连接,然后执行一次转换为String。

2
选择使用String还是StringBuffer/Builder不应该是任何人关心的问题... JVM是一个JIT环境 -> 为什么JIT不能执行适当的优化呢? - Mathew

2

StringBuffer 用于将多个字符串创建为一个字符串,例如当您想要在循环中追加字符串的部分时。

如果只有一个线程访问 StringBuffer,则应该使用 StringBuilder 而不是 StringBuffer,因为 StringBuilder 不同步,因此更快。

据我所知,作为一种语言,Java 中没有字符串大小的上限,但 JVM 可能有上限。


字符串有一个 toCharArray() 方法,根据 javadoc,它必须返回字符串的全部内容。 - Simon Nickerson
@Simon Nickerson 谢谢,那么上限就是不超过Integer.MAX_VALUE。 - Thomas Lötzer

1
一个 StringBuffer 或者它年轻且更快的兄弟 StringBuilder 在需要进行大量字符串拼接时更受青睐。
string += newString;

或者等价地

string = string + newString;

因为上述结构隐式地每次都会创建一个新的字符串,这将导致巨大的性能下降。在内部,StringBuffer / StringBuilder 最好与动态可扩展的 List<Character> 进行比较。


1
String is immutable. 

为什么?请在这里查看。
StringBuffer is not. It is thread safe. 

更多类似于何时使用哪个概念等问题可以通过this进行了解。

希望这能有所帮助。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接