String、StringBuffer与StringBuilder的区别?
可变性:
- String对象有final修饰,是不可变的。
- StringBuffer和StringBuilder是可变字符序列。提供了诸多的修改字符串方法。
线程安全性:
- String对象有final修饰,是不可变的。线程安全。
- StringBuilder是线程不安全的
- StringBuffer使用了同步锁来保证线程安全。
适用的情景:
- 操作少量的数据: 适用 String
- 单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder
- 多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer
String s="abc";
StringBuffer s5=new StringBuffer(s);
Java 9 为何要将 String 的底层实现由 char[] 改成了 byte[] ?
节省内存空间
新版的 String 其实支持两个编码方案:Latin-1 和 UTF-16。如果字符串中包含的汉字没有超过 Latin-1 可表示范围内的字符,那就会使用 Latin-1 作为编码方案。Latin-1 编码方案下,byte 占一个字节(8 位),char 占用 2 个字节(16),byte 相较 char 节省一半的内存空间。
字符串常量池
1.创建的意义
- 因为新创建一个字符串,耗费高昂的时间与空间代价,而字符串又会频繁重复使用,因此创建一个字符串常量池来存储之前使用过的字符串,如果有重复的字符串,则直接复用,不再消耗资源创建新的字符串。
2.字符串创建的方式与原理
字符串的创建分为以下三种:
- 直接赋值
- new String()
- intern方法
①:直接赋值
这种方式创建的字符串对象,只会在常量池中。返回的也只是字符串常量池中的对象引用!
String s = "aaa"; // s指向常量池中的引用
步骤如下:
-
因为有aaa这个字面量,在创建对象s的时候,JVM会先去常量池中通过 equals(key) 方法,判断是否有相同的对象
-
如果有,则直接返回该对象在常量池中的引用
-
如果没有,则会在常量池中创建一个新对象,再返回常量池中aaa的对象引用。
②:new String()
这种方式会保证字符串常量池和堆中都有这个对象,最后返回堆内存中的对象引用!
String s1 = new String("aaa"); // s1指向内存中的对象引用
看到new关键字,一定是在堆里面开辟了一个小空间。
步骤如下:
-
同上,看到有"aaa"这个字面量,就会先去字符串常量池中检查是否存在字符串"aaa"
-
如果不存在,先在字符串常量池里创建一个字符串对象"aaa";再去堆内存中创建一个字符串对象"aaa"
-
如果存在,就直接去堆内存中创建一个字符串对象"aaa"
-
无论存不存在,都只返回堆内存中的字符串对象"aaa"的引用
③:intern()方法
String s1 = new String("aaa");
String s2 = s1.intern();
System.out.println(s1 == s2); //false
intern方法是一个 native (本地)的方法,其作用是将指定的字符串对象的引用保存在字符串常量池中,当调用 intern方法时:
- 如果字符串常量池中已经包含一个等于"aaa"的字符串(用equals(oject)方法确定),则返回字符串常量池中的字符串"aaa"。
- 如果字符串常量池中没有"aaa"这样一个字符串,则会将intern返回的引用指向当前字符串 s1,也就说会返回堆中的"aaa"
// 在堆中创建字符串对象”Java“
// 将字符串对象”Java“的引用保存在字符串常量池中
String s1 = "Java";
// 直接返回字符串常量池中字符串对象”Java“对应的引用
String s2 = s1.intern();
// 会在堆中在单独创建一个字符串对象
String s3 = new String("Java");
// 直接返回字符串常量池中字符串对象”Java“对应的引用
String s4 = s3.intern();
// s1 和 s2 指向的是堆中的同一个对象
System.out.println(s1 == s2); // true
// s3 和 s4 指向的是堆中不同的对象
System.out.println(s3 == s4); // false
// s1 和 s4 指向的是堆中的同一个对象
System.out.println(s1 == s4); //true
注意:在jdk1.6版本及以前,如果字符串常量池中没有"aaa" 这样一个字符串 ,还需要将"aaa" 复制到字符串常量池里,然后返回字符串常量池中的这个新创建的字符串"aaa"。
代码:堆栈方法区存储字符串
示例
字符串对象的创建
面试题:String str4 = new String(“abc”) 创建多少个对象?
1、在常量池中查找是否有“abc”对象。
1)有则返回对应的引用实例;
2)没有则创建对应的实例对象。
2、在堆中 new 一个 String(“abc”) 对象。
3、将对象地址赋值给str4,创建一个引用。
所以,常量池中没有“abc”字面量则创建两个对象,否则创建一个对象,以及创建一个引用。
更细节的可以参考该博文:https://blog.csdn.net/weixin_43180461/article/details/97401075
这里附几张图
String s1 = new String(“abc”);这句话创建了几个字符串对象?
会创建 1 或 2 个字符串对象。
- 如果字符串常量池中不存在字符串对象“abc”的引用,那么它会在堆上创建两个字符串对象,其中一个字符串对象的引用会被保存在字符串常量池中。
- 如果字符串常量池中已存在字符串对象“abc”的引用,则只会在堆中创建 1 个字符串对象“abc”。
String类型的变量和常量做"+"运算时发生了什么
String str1 = "str";
String str2 = "ing";
String str3 = "str" + "ing";
String str4 = str1 + str2;
String str5 = "string";
System.out.println(str3 == str4);//false
System.out.println(str3 == str5);//true
System.out.println(str4 == str5);//false
- 对于编译期可以确定值的字符串,也就是常量字符串 ,jvm 会将其存入字符串常量池。并且,字符串常量拼接得到的字符串常量在编译阶段就已经被存放字符串常量池,这个得益于编译器的优化
被 final 关键字修饰之后的 String 会被编译器当做常量来处理,编译器在程序编译期就可以确定它的值,其效果就相当于访问常量如果 ,编译器在运行时才能知道其确切值的话,就无法对其优化
final String str1 = "str";
final String str2 = "ing";
// 下面两个表达式其实是等价的
String c = "str" + "ing";// 常量池中的对象
String d = str1 + str2; // 常量池中的对象
System.out.println(c == d);// true
参考文章:https://blog.csdn.net/qq_45076180/article/details/115082348、
参考文章:https://blog.csdn.net/qq_45076180/article/details/115082348
参考文章:https://blog.csdn.net/weixin_42073629/article/details/116378067
参考文章:https://javaguide.cn/java/basis/java-basic-questions-02.html#string-s1-new-string-abc-%E8%BF%99%E5%8F%A5%E8%AF%9D%E5%88%9B%E5%BB%BA%E4%BA%86%E5%87%A0%E4%B8%AA%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%AF%B9%E8%B1%A1