String 字符串常量
StringBuffer 字符串变量(线程安全)
StringBuilder 字符串变量(非线程安全)
简要的说, String 类型和 StringBuffer 类型的主要性能区别其实在于 String 是不可变的对象, 因此在每次对 String 类型进行改变的时候其实都等同于生成了一个新的 String 对象,然后将指针指向新的 String 对象,所以经常改变内容的字符串最好不要用 String ,因为每次生成对象都会对系统性能产生影响,特别当内存中无引用对象多了以后, JVM 的 GC 就会开始工作,那速度是一定会相当慢的。
而如果是使用 StringBuffer 类则结果就不一样了,每次结果都会对 StringBuffer 对象本身进行操作,而不是生成新的对象,再改变对象引用。所以在一般情况下我们推荐使用 StringBuffer,特别是字符串对象经常改变的情况下。而在某些特别情况下,String 对象的字符串拼接其实是被 JVM 解释成了 StringBuffer 对象的拼接,所以这些时候 String 对象的速度并不会比 StringBuffer 对象慢,而特别是以下的字符串对象生成中, String 效率是远要比 StringBuffer 快的:
String S1 = “This is only a” + “ simple” + “ test”;
StringBuffer Sb = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”);
你会很惊讶的发现,生成 String S1 对象的速度简直太快了,而这个时候 StringBuffer 居然速度上根本一点都不占优势。其实这是 JVM 的一个把戏,在 JVM 眼里,这个
String S1 = “This is only a” + “ simple” + “test”; 其实就是:
String S1 = “This is only a simple test”; 所以当然不需要太多的时间了。但大家这里要注意的是,如果你的字符串是来自另外的 String 对象的话,速度就没那么快了,譬如:
String S2 = “This is only a”;
String S3 = “ simple”;
String S4 = “ test”;
String S1 = S2 +S3 + S4;
这时候 JVM 会规规矩矩的按照原来的方式去做
在大部分情况下 StringBuffer > String
Java.lang.StringBuffer线程安全的可变字符序列。一个类似于 String 的字符串缓冲区,但不能修改。虽然在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用可以改变该序列的长度和内容。
可将字符串缓冲区安全地用于多个线程。可以在必要时对这些方法进行同步,因此任意特定实例上的所有操作就好像是以串行顺序发生的,该顺序与所涉及的每个线程进行的方法调用顺序一致。
StringBuffer 上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。append 方法始终将这些字符添加到缓冲区的末端;而 insert 方法则在指定的点添加字符。
例如,如果 z 引用一个当前内容是“start”的字符串缓冲区对象,则此方法调用 z.append("le") 会使字符串缓冲区包含“startle”,而 z.insert(4, "le") 将更改字符串缓冲区,使之包含“starlet”。
在大部分情况下 StringBuilder > StringBuffer
java.lang.StringBuilder一个可变的字符序列是5.0新增的。此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。两者的方法基本相同。
1.String s = new String("abc");创建了几个String对象。
答案是2个,一个是字符串字面常数,在字符串池 中。一个是new出来的字符串对象,在堆中。(如果常量池原来有“abc”,那么只创建一个对象放在堆。如果没有,那么还要在常量池中新创建一个“abc”。)
2. String s1 = "abc";
String s2 = "a";
String s3 = s2 + "bc";
String s4 = "a" + "bc";
String s5 = s3.intern();
请问s1==s3是true还是false,s1==s4是false还是true。s1==s5呢?
此题注意两点,在表达式 String s3 = s2 + "bc" 中s2是一个变量,所以s3是运行时才能计算出来的字符串,是new的,在堆中不在字符串池中。如果把String s2 = "a" 改为 Final String s2 = "a",此时s2为常量变量,s1==s3.
s4是通过常量表达式计算出来的,他等同于字符串字面常数,在字符串池 中。所以,s1!=s3,s1==s4。
再看s5,s5是s3放到字符串池里面返回的对像,所以s1==s5。这里新手要注意的是,s3.intern()方法,是返回字符串在池中的引用,并不会改变s3这个变量的引用,就是s3还是指向堆中的那个"abc",并没有因调用了intern()方法而改变,实际上也不可能改变。
intern()方法:String类的intern方法,返回一个值相同的String对象,但是这个对象就像一个字符串字面常数一样,意思就是,他也到字符串池(常量池)里面去了。
那么,到底哪些String对象在池中,哪些在堆中?请看Java语言规范。
Literal strings within the same class (§8) in the same package (§7) represent references to the same String object (§4.3.1).
Literal strings within different classes in the same package represent references to the same String object.
Literal strings within different classes in different packages likewise represent references to the same String object.
Strings computed by constant expressions (§15.28) are computed at compile time and then treated as if they were literals.
Strings computed by concatenation at run time are newly created and therefore distinct.
The result of explicitly interning a computed string is the same string as any pre-existing literal string with the same contents.
以上6句话就是权威且全面的解释了。我依次解释下。
首先解释下什么是字符串字面常数 (String Literals),字面常数(Literals)就是你写在源代码里面的值,比如说int i = 6; 6就是一个整数形字面常数。String s = "abc"; “abc”就是一个字符串字面常数。Java中,所有的字符串字面常数 都放在上文提到的 字符串池 里面,是可以共享的,就是说,String s1 = "abc"; String s2 = "abc"; s1,s2都引用的同一个字符串对象,而且这个对象在 字符串池 里面,因此s1==s2。另外,字符串字面常数 是什么时候实例化并放到 字符串池 里面去的呢?答案是Load Class的时候(Java Spec 12.5)。
下面看那6句话。
前面三句基本废话,想绕口令一样,意思就是说任何类任何包,值相同的字符串字面常数(String Literals)都引用同一个对象。
第四句是说,通过常量表达式 (constant expressions)计算出来的字符串,也算字符串字面常数,就是说他们也在字符串池 中。什么是常量表达式 (constant expressions)待会说,这个很重要。
注意第五句话,在程序运行时通过连接(+)计算出来的字符串对象,是新创建的,他们不是字面常数,就算他们值相同,他们也不在字符串池 里面,他们在堆内存空间里,因此引用的对象各不相同。
最后一句话也很重要,String类的intern方法,返回一个值相同的String对象,但是这个对象就像一个字符串字面常数一样,意思就是,他也到字符串池 里面去了。
好,现在我们回到前文没有说清楚的一个问题,到底什么算常量表达式 (constant expressions)。上面提到了两种非常量表达式,new 和变量相加。至于什么算常量表达式 (constant expressions),请看Java语言规范。
ConstantExpression: Expression
A compile-time constant expression is an expression denoting a value of primitive type or a String that does not complete abruptly and is composed using only the following:
Literals of primitive type and literals of type String
Casts to primitive types and casts to type String
The unary operators +, -, ~, and ! (but not ++ or --)
The multiplicative operators *, /, and %
The additive operators + and -
The shift operators <<, >>, and >>>
The relational operators <, <=, >, and >= (but not instanceof)
The equality operators == and !=
The bitwise and logical operators &, ^, and |
The conditional-and operator && and the conditional-or operator ||
The ternary conditional operator ? :
Parenthesized expressions whose contained expression is a constant expression.
Simple names that refer to constant variables
Qualified names of the form TypeName . Identifier that refer to constant variables
Compile-time constant expressions are used in case labels in switch statements and have a special significance for assignment conversion. Compile-time constants of type String are always "interned" so as to share unique instances, using the method String.intern.
A compile-time constant expression is always treated as FP-strict, even if it occurs in a context where a non-constant expression would not be considered to be FP-strict.
上面总结起来就是编译时能确定的,就算,运行时才能确定的,就不算。以下为例子
String s = 1 + "23";//算,符合第二条Casts to primitive types and casts to type String
String s = (2 > 1) + "" ;//算,意味着s=="true",且这个“true”已经放到字符串池里面去了。
String s = (o instanceof Object) + "";//不算,instanceof这个操作符决定了不算。s=="true",但这个"true"对象在堆中。
留意下面的情况。
final String s2 = "a";
String s3 = s2 + "bc";//算
注意现在的s2+"bc"也算一个常量表达式,理由是那个列表里面的最后两条,s2是一个常量变量(constant variables),问题又来了,什么是常量变量?规范里也说了,被final修饰,并且通过常量表达式初始化的变量,就是常量变量。变量s2被final修饰,他通过常量表达式"a"初始化,所以s2是一个常量变量,所以s3引用的"abc",也在字符串池里面咯,明白了吧。
再举个反例:
final String s2 = getA();//s2不是常量变量,但是s2引用的"a"其实还是在常量池中,这两点不矛盾
public String getA(){return "a"}
String s3 = s2 + "bc";//此时s3不算常量表达式,因为s2不是常量变量
这是时候的s2,就不是常量变量了哦,因为getA()不是一个常量表达式。