Java中的String,StringBuilder,StringBuffer三者的区别
Java中的String,StringBuilder,StringBuffer三者的区别
下面从源码角度深入分析下三者
String
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
value hash count都是private,并且没有提供setValue,setOffset和setCount等公共方法来修改这些值,所以在String类的外部无法修改String。也就是说一旦初始化就不能修改, 并且在String类的外部不能访问这三个成员。
value数组还是final的,不可继承,不可更改。这也印证了String是不可变对象。
再看看String的一些方法
public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsExceptio(beginIndex);
}
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true);
}
...
其实都不是对原字符串操作,而是new一个新的字符串,原字符串并没有改变。
疑惑
String a = "123";
System.out.println(a);
a = "234";
System.out.println(a);
output:
123
234
实际上,a值的改变,是重新创建了一个String对象,它的值为"234"。
所以对一个String类型的变量的修改,其实是重新创建了一个String对象,改变了其引用的指向。
String"+"
String a = "cc";
String b = "ff";
String c = "aa" + "bb " + a + "dd" + b;
实际过程
StringBuilder builder = new StringBuilder();
builder.append("aa").append("bb").append(a).append("dd").append(b)
c = builder.toString()
字符串常量池
我们知道字符串的分配和其他对象分配一样,是需要消耗高昂的时间和空间的,而且字符串我们使用的非常多。JVM为了提高性能和减少内存的开销,在实例化字符串的时候进行了一些优化:使用字符串常量池。每当我们创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。由于String字符串的不可变性我们可以十分肯定常量池中一定不存在两个相同的字符串(这点对理解上面至关重要)。
Java中的常量池,实际上分为两种形态:静态常量池和运行时常量池。
所谓静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。
而运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。
来看下面的程序:
String a = "chenssy";
String b = "chenssy";
a、b和字面上的chenssy都是指向JVM字符串常量池中的"chenssy"对象,他们指向同一个对象。
String c = new String("chenssy");
new关键字一定会产生一个对象chenssy(注意这个chenssy和上面的chenssy不同),同时这个对象是存储在堆中。所以上面应该产生了两个对象:保存在栈中的c和保存堆中chenssy。但是在Java中根本就不存在两个完全一模一样的字符串对象。故堆中的chenssy应该是引用字符串常量池中chenssy。所以c、chenssy、池chenssy的关系应该是:c—>chenssy—>池chenssy。整个关系如下:
通过上面的图我们可以非常清晰的认识他们之间的关系。所以我们修改内存中的值,他变化的是所有。
StringBuilder
可变对象,提供以下方法:
append();
delete();
deleteCharAt();
replace();
insert();
reverse();
toString();
...
StringBuffer
StringBuffer 与 StringBuilder 中的方法和功能完全是等价的。在StringBuilder提供的方法基础上添加了synchronize关键字,实现线程安全
比较
- 都是final类,不允许被继承
- String长度不可变,StringBuilder和StringBuffer可变
- StringBuffer线程安全
- StringBufilder拥有更好的性能
- String、StringBuilder、StringBuffer三者的执行效率:StringBuilder > StringBuffer > String 当然这个是相对的,当字符串相加操作或者改动较少的情况下,建议使用 String。当字符串相加操作较多的情况下,建议使用StringBuilder,如果采用了多线程,则使用StringBuffer。