在Java中,我应该何时优先选择String而不是StringBuilder呢?

10

我知道StringBuilder比String更被推荐,因为String会被保存在常量字符串池中,给它赋一个新值并不会覆盖之前的值。但是,StringBuilder是一个对象,可以覆盖它之前的值。

  • In what cases should I use String over StringBuilder vice versa.
  • Also If I have variables of type String in backing classes of Hibernate should I use StringBuilder? How?

    ...
    Result result = (Result) criteria.list().get(0);
    
    
    class Result{
       String name;
       String fname;
       ...
    }
    

只有少数字符串(字面字符串、字符串值常量表达式和使用intern()调用的字符串)将被保留在池中。你为什么暗示这是不好的?你所说的“覆盖先前值”是什么意思?将值分配给String变量与将值分配给StringBuilder变量相同。你是在谈论可变性与不可变性吗?你确定何时“优先”使用StringBuilder吗? - xehpuk
据我所知,当您为字符串分配一个新值时,它会删除对其先前值的引用并添加新值的引用,因此先前的值会留在内存中等待垃圾回收。而 StringBuilder 则将新值替换为先前的值。我不确定,这就是为什么在第一个要点中我包括了(反之亦然)。 - Daniel Newtown
5个回答

4
您应该使用String,因为String对象被缓存在对象池中,当您不对它们进行更改时,可能会提供更好的性能。
只有在持续连接String标记时,StringBuilder才有用,而这在规范化良好的数据库表中不应该是情况。
JVM执行各种优化,即使您使用连接操作,JVM也可能将该例程重写为StringBuilder版本。

还要注意,在许多情况下,可以并且应该避免使用串联操作,而不必通过中间的StringBuilder来实现。案例1 - "jdbc" - 使用绑定变量与字符串串联的比较;案例2 - "System.out" - 多次调用printprintln,或者链式操作System.out,如System.out.append("Hello ").append(name); - YoYo

2
我会在文本值固定时使用String。
当您创建较大的文本字符串时,请使用StringBuilder,例如:
final StringBuilder sb = new StringBuilder();
for(;;){
    sb.append("more text\n");//add text, StringBuilder's size will increase incrementally
    if(this.stop()){
         break;//stop loop
    }
}
final String result = sb.toString();// Create the final result to work with
System.out.println(result);//print result

使用StringBuffer代替StringBuilder来处理同步值,关于StringBuilder和StringBuffer的区别请参见https://dev59.com/dnRC5IYBdhLWcg3wROzk#355092
JavaDoc:StringBuffer(http://docs.oracle.com/javase/7/docs/api/java/lang/StringBuffer.html):
线程安全的、可变的字符序列。字符串缓冲区类似于字符串,但可以被修改。在任何时刻,它包含某个特定的字符序列,但序列的长度和内容可以通过某些方法调用进行更改。
JavaDoc:StringBuilder(http://docs.oracle.com/javase/7/docs/api/java/lang/StringBuilder.html):
一个可变的字符序列。此类提供了与StringBuffer兼容的API,但不保证同步。该类被设计用作在单个线程中使用字符串缓冲区(通常情况下)的替代品。在可能的情况下,建议使用此类代替StringBuffer,因为在大多数实现中它会更快。
JavaDoc:String(http://docs.oracle.com/javase/7/docs/api/java/lang/String.html):
String类表示字符字符串。 Java程序中的所有字符串文字,例如“ abc”,都是作为此类的实例实现的。字符串是恒定的;创建后它们的值不能更改。字符串缓冲区支持可变字符串。因为String对象是不可变的,所以它们可以共享。
基本上,您将使用String来表示文本的常量(不可变)。

那么这是否意味着我的Hibernate代码是正确的? - Daniel Newtown
我没有看到错误...但这并不意味着错误不在其他地方。 - Danielson
我没有点踩,但我会避免使用StringBuilder。使事情线程安全需要仔细的实现考虑,而不是盲目地为所有API寻求线程安全的实现。在许多情况下,即使在多线程上下文中,也不总是需要保护代码的所有部分。像StringBuilder和一些Collection实现这样的线程安全实现会增加大量同步开销。 - YoYo
我不会在不同的线程中使用字符串构建,因为这听起来太容易出错了。而且我想不到任何必须要这样做的原因,但或许其他人会有不同的看法。 - Danielson

2

一个简单的经验法则(String是代表字符字符串的类型。StringBuilder是可变字符流)

使用String来表示文本值。根据定义,Java提供了字符串值的池化,因此可以为您提供一些空间优化。在处理文件批处理期间应用程序需要处理数百万个文本值的情况下,请考虑此功能。例如:

  String str1 = "Test";
  String str2 = "Test";

这里,str1 == str2(相同引用)

此外,+ 运算符在 String 类中被重载,用于从不同类型构造字符串。当构造小字符串时可以使用它(内部使用 StringBuilder 完成,所以不必担心)- 但是在循环时不要使用。

只有在使用不同类型的小片段构建目标字符串时 - 尤其是在循环内部 - 才使用 StringBuilder(或旧版的 StringBuffer),这将帮助您避免将不必要的字符串片段放入字符串池中。

   StringBuilder s1 = new StringBuilder("test");
   StringBuilder s2 = new StringBuilder("test");

在这里,s1 != s2。

此外,我认为没有办法可以操纵StringBuilder / Buffer的编码 - 与此同时,String允许这样做。

编辑:表示Hibernate实体: 始终使用String来表示类中的文本类型。出于上述原因,这应该像肌肉记忆一样自然而然地发生。例如,对于原始类型,如intfloatchar等,对于文本类型,使用String。仅使用构建器构建字符串,而不是表示类型,除非这是某种奇怪的要求。


1
请参考类似于https://dev59.com/dHVD5IYBdhLWcg3wGXlI的帖子。 - ring bearer

1

当你需要对字符字符串进行大量修改时,应该使用java.lang.StringBuilder类。我们知道String对象是不可变的,因此如果选择对String对象进行大量操作,将会在String池中留下许多废弃的String对象。(即使在现在拥有吉比特级别的RAM的时代,浪费宝贵的内存来处理废弃的String池对象也不是一个好主意。)另一方面,StringBuilder类型的对象可以被反复修改,而不会留下大量废弃的String对象。

String x = "abc";
x.concat("def");
System.out.println("x = " + x); // output is "x = abc"


StringBuffer sb = new StringBuffer("abc");
sb.append("def");
System.out.println("sb = " + sb); // output is "sb = abcdef"

1
字符串操作不会在“字符串池”中留下对象。有一组唯一的字符串,但只有当您使用intern()方法或者它们来自源代码中的字符串字面量时,它们才会进入该池中。而且,首先打印一个StringBuffer会*将其转换为String*,因此这并没有帮助。 - Erwin Bolwidt

1

字符串是不可变对象,一旦创建就无法更改。作为字符串创建的对象存储在常量字符串池中。 Java中的每个不可变对象都是线程安全的,这意味着字符串也是线程安全的。 字符串不能同时被两个线程使用。一旦分配了字符串,就无法更改。

String  demo = "Test1" ;

上述对象存储在常量字符串池中,其值无法被修改。

demo="Test2" ;

在常量池中创建了一个名为“Test2”的新字符串,并由demo变量引用。

StringBuilder对象是可变的,我们可以对存储在对象中的值进行更改。这实际上意味着,如果使用StringBuilder对象而不是String对象执行诸如append之类的字符串操作,则效率会更高。

StringBuilder demo2= new StringBuilder("Test1");

上述对象也存储在堆中,其值可以被修改。

demo2=new StringBuilder("Test1"); 

上述语句是正确的,因为它修改了StringBuilder中允许的值。

因此,在需要频繁更新/修改字符串的情况下,应该使用stringBuilder。


1
默认情况下,作为字符串创建的对象不会存储在“常量字符串池”中。只有当您使用intern()方法或者它们来自源代码中的字符串字面值时,它们才会出现在那里。new StringBuilder("Test1")也将字符串"Test1"存储在“常量字符串池”中,因为"Test1"是一个字符串,所以从使用StringBuilder/Buffer方面来看没有任何好处。 - Erwin Bolwidt
如果字符串是使用new初始化的,那么intern就会出现在其中,然后它将移动到池中。 - Abhijit Bashetti
不,使用“new”构造的字符串不会移动到池中。只有调用“intern()”或在类中有一个字符串字面量才能实现这一点。 - Erwin Bolwidt
抱歉回复简短。是的,我同意你的说法。我的意思是,当一个字符串被用new初始化后,如果你调用intern方法,它会被移动到池中。Intern方法会将字符串添加到池中,如果它们之前不存在的话... - Abhijit Bashetti

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