目录
String 类
String 类是 类 类型,通过 String 对象的引用 表示和操作 字符串类型 的数据。
由双引号 " " 引起来的内容就是一个字符串常量,Java 编译器会自动将 字符串常量 转换为 String 对象进行处理。
下面是常见的 初始化 String 对象的 3种方式 (效果相同,日常开发推荐第一种方法):
// 方法1:使用常量字符串构造s1对象
String s1 = "hello";
System.out.println(s1);
// 方法2:直接newString一个s2对象
String s2 = new String("hello");
System.out.println(s2);
// 方法3:使用字符数组构造s3对象
char[] array = {'h', 'e', 'l', 'l', 'o'};
String s3 = new String(array);
System.out.println(s3);
String 的不可变性
我们所有对字符串的操作都是 产生新的字符串对象 而不是对 原来字符串对象的基础上进行替换或修改。
根本原因:String类中的 value 引用是被 private 修饰,在其他类中根本无法直接获取到这个对象的引用,所有的字符串都是存到字符数组里面的,而字符数组就是引用类型。因此,我们对一个字符串的所有改动都会产生一个新的对象,原来的字符串对象保持不变。
那 String 不可变的原因为什么不是被 final 修饰的呢?
final 修饰类表明该类不想被继承,final 修饰引用类型表明该引用变量不能引用其他对象,但是其引用对象中的内容是可以修改的。因此,如果只是被 final 修饰的 value 这个引用不能改变他所指向对象的引用,但是可以直接对对象进行修改。
我们通过调试下面这段代码证明 String 的不可变性:
public static void main(String[] args) {
String str = "hello";
str = str + "abc";
System.out.println(str);
}
“abc” 拼接完之后, value 中的引用发生了改变,说明 value 的引用所指向的对象已经不同于原来的 “hello” 字符串对象了。
观察字符串修改时的反汇编代码
比如还是下面这段代码:对一个字符串对象拼接另一个字符串
public static void main(String[] args) {
String str = "hello";
str = str + "abc";
System.out.println(str);
}
运行这个代码之后,然后找到该类的 .class文件所在的路径,通过终端查看该代码的反汇编,查看反汇编的目的是查看编译器是如何解读和处理这段代码的。
我们可以从反汇编代码中发现,它用到了一个 StringBuilder 的类,我们可以对照这个反汇编的注释重新对照着写这7步的 java代码:
最后发现两个代码的效果是一样的。
StringBuilder 的可变性(StringBuilder 相较于 String 的优势)
尽量避免直接对 String类型对象 进行修改,因为 String类是不能修改的,所有的修改都会创建新对象,效率非常低下。所以因为这个原因,编译器会优化我之前写的对 String类型对象 进行修改的低效率代码 。通过 StringBuilder 进行字符串修改时不会创建新的对象,只需在原来的 StringBuilder 对象的基础上进行修改即可,这就是 StringBuilder 相较于 String 的优势。
我们可以运行一段代码直观的感受 String 与 StringBuilder ,StringBuffer 中效率的差距:
public static void main(String[] args) {
//计算 通过10000次循环修改String对象的时间(毫秒)
long start = System.currentTimeMillis();
String s = "";
for(int i = 0; i < 10000; ++i){
s += i;
}
long end = System.currentTimeMillis();
System.out.println(end - start);
//计算 通过10000次循环修改StringBuffer对象的时间(毫秒)
start = System.currentTimeMillis();
StringBuffer sbf = new StringBuffer("");
for(int i = 0; i < 10000; ++i){
sbf.append(i);
}
end = System.currentTimeMillis();
System.out.println(end - start);
//计算 通过10000次循环修改StringBuilder对象的时间(毫秒)
start = System.currentTimeMillis();
StringBuilder sbd = new StringBuilder();
for(int i = 0; i < 10000; ++i){
sbd.append(i);
}
end = System.currentTimeMillis();
System.out.println(end - start);
}
StringBuilder 相较于 String 的优点:
在循环的情况下,字符串进行拼接,每次循环 String 都需要新建对象和销毁对象,而 StringBuilder 直接在该对象进行 append
下图是代码的运行结果:
StringBuilder 与 StringBuffer 的区别
- StringBuffer 是线程安全的类,也就是只能有一个线程同时调用 StringBuffer 中的方法,其他线程不能同时调用;
- StringBuilder 不是线程安全的类,所以可以有多个线程同时调用 StringBuilder 中的方法。
但 StringBuffer 每次上锁和解锁都需要耗费资源,在多线程的情况下,虽然很安全,但是效率比StringBuilder 低一些。
总结
-
String是不可变的类,因为类型对象的字符串不能在原来对象的基础上进行修改,而是会产生一个新的对象;
-
StringBuilder 和 StringBuffer 是可变的类,因为StringBuilder 和 StringBuffer类型对象的字符串可以直接在原来对象的基础上进行修改;
-
StringBuffer 在多线程的情况下更安全,StringBuilder 性能更高。
-
String,StringBuilder 和 StringBuffer 的应用场景:
– 如果在 单线程环境 下需要 频繁修改字符串,应该使用 StringBuilder;
– 如果在 多线程环境 下需要 保证线程安全,可以使用 StringBuffer;
– 如果 字符串 不需要 被修改,则可以使用 String。