浅谈Java中String、StringBuilder、StringBuffer的联系与区别
String是Java 开发工作者使用得最频繁的类之一,了解并学习String、StringBuilder、StringBuffer这几个类,分析它们的异同点以及了解各个类适用的场景,相信都有利于日常编码与求职面试。
本文目录大纲
一.你了解String类吗?
二.String、StringBuffer、StringBuilder的联系
三.深入理解String、StringBuffer、StringBuilder
一.你了解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;
/**
* Class String is special cased within the Serialization Stream Protocol.
*/
private static final ObjectStreamField[] serialPersistentFields =
new ObjectStreamField[0];
......
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > value.length) {
throw new StringIndexOutOfBoundsException(endIndex);
}
int subLen = endIndex - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return ((beginIndex == 0) && (endIndex == value.length)) ? 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);
}
......
}
通过以上代码可以看出:
- String类是final类,意味着String类不能被继承,并且它的成员方法都默认为final方法
- String类是通过char数组保存字符串的
- 由成员方法可看出,对字符串的操作都不是在原有的字符串上进行的,而是重新生成了一个新的字符串对象,也就是说: “对String对象的任何改变都不影响到原对象,相关的任何操作都会生成新的对象”
二.String、StringBuffer、StringBuilder的联系
查看相关API可以发现,String、StringBuffer、StringBuilder都实现了CharSequence接口,内部使用char数组保存字符
- String:字符串常量,是不可改变的量,即创建后就不能在修改了
- StringBuffer:字符串变量(线程安全),是可变字符串序列
- StringBuilder:字符串变量(线程不安全),是可变字符串序列
因为String是不可改变的对象,因此在每次对String类型进行改变的时候其实都等同于生成了一个新的String对象,任何将指针指向新的String对象,所以在经常改变内容的字符串最好不要用String,因为每次生成对象都会对系统性能产生影响,当内存中无引用对象多了以后,JVM的GC会进行工作,影响系统效率。
因此,在字符串不经常变化的场景中可以使用String类,例如常量的声明,少量的变量运算;在频繁进行字符串运算(如拼接、替换、删除等),并且运行在多线程环境中,则可以考虑使用StringBuffer,若是单线程的环境,则可以考虑使用StringBuilder。
三.深入理解String、StringBuffer、StringBuilder
- String str = “hello world” 和 String str = new String(“hello world”)的区别
观察下列代码
public class Main() {
public static void main(String[] args) {
String str1 = "hello world";
String str2 = new String("hello world");
String str3 = "hello world";
String str4 = new String("hello world");
System.out.println(str1==str2);
System.out.println(str1==str3);
System.out.println(str2==str4);
}
}
输出结果:
false
true
false
之所以会出现这样的结果,这是因为JVM 的内存机制中,在class文件中有一部分存储编译期间生成的字面常量以及符号引用,在编译运行期间对应着方法区的运行时常量池。
因此在上述代码中,String str1="hello world"和String str3="hello world"都在编译期间生成了字面常量和符号引用,运行期间字面常量"hello world"被存储在运行时常量池;通过这种方式将String跟引用绑定的话,JVM执行引擎会先在运行时常量池查找是否存在系统的字面常量;否则在运行时常量池开辟一个空间来存储该字面常量,并将引用指向该字面常量。
总所周知,通过new关键字来生成对象是在堆区进行的,而在堆区进行对象生成的过程是不会去检测该对象是否已经存在的。因此通过new来创建对象,创建出的一定是不同的对象,即使字符串的内容是相同的。
- 关于equal和==的区别
观察下列代码
public class Main() {
public static void main(String[] args) {
String str1 = "hello world";
String str2 = new String("hello world");
String str3 = "hello world";
String str4 = new String("hello world");
System.out.println(str1.equals(str2));
System.out.println(str1.equals(str3));
System.out.println(str2.equals(str4));
}
}
输出结果:
true
true
true
之所以会出现这样的结果,是因为用于比较两个对象的时候,用来检测是否两个引用指向了同一块内存
equals()是Object的方法,默认情况下,它与一样,比较地址;然而String类重写了这个方法,它的equals()通过比较对象的value值,判断两个对象是否相等
- String、StringBuilder、StringBuffer三者的执行效率
一般情况下String、StringBuilder、StringBuffer三者的执行效率:StringBuilder>StringBuffer>String