Java中的String、StringBuilder、StringBuffer类
前言
字符串类型是我们开发时使用比较多的数据类型了,下面浅谈一下Java的几个字符串类:String、StringBuilder、StringBuffer。三种方式本质上都是对基本数据类型char进行操作的。
String、StringBuilder和StringBuffer都是Java中用于处理字符串的类,它们有一些共同点和区别。
- String:String是不可变的字符串类,一旦创建就不能被修改。每次对String进行修改操作,都会创建一个新的String对象。这种特性使得String在多线程环境下是线程安全的。但是频繁的字符串拼接操作会导致大量的临时对象创建,对性能有一定的影响。
- StringBuilder:StringBuilder是可变的字符串类,可以进行字符串的修改操作。StringBuilder的操作是非线程安全的,适用于单线程环境下的字符串拼接操作。由于StringBuilder不会创建新的对象,所以在频繁的字符串拼接操作中,性能比String要好。
- StringBuffer:StringBuffer也是可变的字符串类,和StringBuilder类似,可以进行字符串的修改操作。不同的是,StringBuffer的操作是线程安全的,适用于多线程环境下的字符串拼接操作。由于StringBuffer会创建新的对象,所以在频繁的字符串拼接操作中,性能比StringBuilder要差。
一、String类
Java中的String类是一个不可变的字符串类,它是由字符序列组成的。一旦创建了String对象,就不能修改它的值。如果对String对象进行修改操作,实际上是创建了一个新的String对象。
- Java 中规定所有用双引号括号起来的,皆为String对象且不可改变。
- 用双引号括号起来的String对象放在堆区的字符串常量池中,当需要使用时将指针指向常量池的字符。
我们看下面的面试题来了解String对象:
- 请分析以下字符串对象是否改变,并且说明详细原因。
public class StringDemo {
public static void main(String[] args) {
String s = "string1";
s = "string2";
/*
字符串对象是不会被改变的,咋一眼看上去s对象被改变了
,但实际上"string1"和"string2"是不同的对象,s仅仅
是被重新赋值了而已。分别打印s的地址我们就能发现,s的
地址已经改变了。
// 类似于
Student stu = new Student("小明");
stu = new Student("小王");
*/
}
}
- 请分析以下输出结果,并说明原因。
public class StringDemo {
public static void main(String[] args) {
String s1 = "string";
String s2 = "string";
System.out.println(s1 == s2);
// 输出true
// 首先明确:== 比较的是值。
// String类型是引用数据类型,所以s1、s2其实是一个地址值。
// s1、s2同时指向"string"对象的地址(字符串常量池是复用的),所以结果输出true。
}
}
public class StringDemo {
public static void main(String[] args) {
String s1 = "string";
String s2 = new String("string");
System.out.println(s1 == s2);
// 输出false
// s1指向字符串常量池的"string"对象
// s2则是指向堆区的"string"对象,因为new String("string")操作在堆区非常量池创建对象,且总是比直接用双引号创建方式多一次创建对象的操作。
// new String("string")首先检查字符串常量池有没有"string"对象,没有则创建,之后再将字符串常量池"string"对象的值复制再创建一个新的String对象。
}
}
public class StringDemo {
public static void main(String[] args) {
String s1 = "string";
String s2 = "str";
String s3 = s2 + "ing";
System.out.println(s1 == s3);
// 输出false
// s1指向字符串常量池的"string"对象
// 我们知道对象是不能直接使用操作符的,"+"号被String类重载了,当遇到 + 号时,String类会使用StringBuilder/StringBuffer进行字符串的构造。
// 变量s2 + 字符串常量"ing"时,String类会创建新的对象于堆区的其他区域中,所以s3则是指向堆区的"string"对象。
// 所以 s1 != s3
}
}
public class StringDemo {
public static void main(String[] args) {
String s1 = "string";
String s2 = "str" + "i" + "ng";
System.out.println(s1 == s2);
// 输出true
// s1指向字符串常量池的"string"对象
// 对象是不能直接使用操作符的,"+"号被String类重载了,当遇到 + 号时,String类会使用StringBuilder/StringBuffer进行字符串的构造。
// "str" + "i" + "ng" 由于都是字符串常量对象,所以直接引用字符串常量池,所以s3指向字符串常量池的"string"对象。
// 所以 s1 == s3 ,注意与上一个demo的区别。
}
}
在业务中,我们普遍认为字符串内容相等即字符串相等,不关心地址问题,所以在进行字符串比较的时候一般不会使用==进行比较,String类提供了equal()方法进行字符串内容的比较。且推荐使用双引号方式进行初始化,效率比 new String()要高。
String对象遍历:
public class StringDemo {
public static void main(String[] args) {
// 方式一:使用charAt()方法
String str = "Hello";
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
// 对字符进行处理
}
// 方式二:将String转换为字符数组
String str = "Hello";
char[] charArray = str.toCharArray();
for (char c : charArray) {
// 对字符进行处理
}
}
}
推荐将String转换为字符数组再遍历。 显然,使用charAt()进行遍历每一轮循坏都要调用一次length()与charAt(i)方法,而方法的调用是需要时间开销的,而方式二则是以空间换时间的方式进行遍历,显然效率是优于使用charAt()进行遍历的。
二、StringBuilder类
StringBuilder类是Java中可变的字符串类,它可以进行字符串的修改操作。与String类不同,StringBuilder对象的值可以被修改,而不会创建新的对象。
StringBuilder类提供了许多用于操作字符串的方法,例如追加字符串、插入字符串、删除字符等。它还可以通过链式调用方法来进行多个操作。
面试考点:StringBuilder的扩容机制
public class StringBuilder{
public static void main(String args[]){
// 设置StringBuilder初始化容量为10
StringBuilder sb1 = new StringBuilder(10);
System.out.println(sb1.capacity()); // 输出10
// 可以添加超过容量的字符,StringBuilder会自动扩容
sb1.append("StringBuilder StringBuilder StringBuilder StringBuilder");
System.out.println(sb1.capacity()); // 输出34
// 不设置初始化容量
StringBuilder sb2 = new StringBuilder();
System.out.println(sb2.capacity()); // 输出16,不设置系统默认为16。
sb2.append("StringBuilder StringBuilder StringBuilder StringBuilder");
System.out.println(sb2.capacity()); // 输出34
// 扩容规则:字符大于16则 将容量变为(16+1)*2 ,大于 (16+1)*2 = 34 则 将容量变为(34+1)*2 = 70...
// 字符大于容量将 容量扩为原本的(n+1)* 2
}
}
同时StringBuilder提供toString()方法转化为String对象。
由于StringBuilder对象的可变性,它在单线程环境下比String类更高效。在频繁的字符串拼接操作中,使用StringBuilder可以避免创建大量的临时对象,提高性能。
然而,需要注意的是,StringBuilder类是线程不安全的。如果在多线程环境下使用StringBuilder进行字符串操作,可能会导致线程安全问题。如果需要在多线程环境下进行字符串操作,应该使用线程安全的StringBuffer类。
总之,StringBuilder类是Java中可变的字符串类,适用于单线程环境下的字符串拼接操作,可以提高性能。
三、StringBuffer类
StringBuffer类是Java中可变的字符串类,它和StringBuilder类类似,可以进行字符串的修改操作。和StringBuilder不同的是,StringBuffer类的操作是线程安全的。
由于StringBuffer对象的可变性和线程安全性,它适用于多线程环境下的字符串操作。在多个线程同时访问和修改同一个StringBuffer对象时,不会出现线程安全问题。
StringBuffer类提供了许多用于操作字符串的方法,例如追加字符串、插入字符串、删除字符等。它也可以通过链式调用方法来进行多个操作。
面试考点:StringBuffer如何实现的线程安全
- 通过查看源码,可以知道,StringBuffer的基本方法上都有synchronized关键字,即加上了同步锁,实现线程同步,解决线程安全问题。
- 方法级同步锁极大的影响了效率,所以StringBuffer的效率是低于StringBuilder的。
此外,由于StringBuffer会创建新的对象,所以在频繁的字符串拼接操作中,性能比StringBuilder要差。如果在单线程环境下进行字符串拼接操作,推荐使用StringBuilder;如果在多线程环境下进行字符串拼接操作,推荐使用StringBuffer。
总之,StringBuffer类是Java中可变的、线程安全的字符串类,适用于多线程环境下的字符串操作。
总结
综上所述,
- 如果在单线程环境下进行字符串拼接操作,推荐使用StringBuilder;
- 如果在多线程环境下进行字符串拼接操作,推荐使用StringBuffer;
- 如果不需要修改字符串,只是进行字符串的读取操作,可以使用String;