一、创建字符串
//三种常用的构造字符串的方式 public static void main(String[] args) { String str1 = "hello"; String str2 = new String("world"); char[] chars = {'a','b','c'}; String str3 = new String(chars); }
注意: String是引用类型,内部并不存储字符串本身。通过查看String类的实现源码可以发现,字符串实际由两部分value数组、hash值组成,字符串实际保存在char类型的数组中:
二、字符串与常量池
📝以下程序输出的结果是什么?在此过程中创建了几个String对象?
public static void main(String[] args) { String str1 = "Hello"; String str2 = "Hello"; String str3 = new String("Hello"); System.out.println(str1==str2); System.out.println(str1==str3); }
分析: 双引号引起来的内容时存放在字符串常量池的,在直接赋值时,如果在字符串常量池中存在就直接返回常量池中字符串的引用,如果不存在则先在字符串常量池中创建一份。如果通过new创建字符串对象,同样会按照如上步骤检查常量池,只不过最后返回的是通过new创建的字符串对象。
最后我们说结论:此过程中str1会在常量池创建1
个字符串对象,str2创建0
个字符串对象,str3在堆区创建1
字符串个对象。且str1==str2
,str1!=str3
.
具体过程如下草图:
总结:
直接赋值产生1或0个字符串对象,使用newString()赋值时产生2或1字符串对象。赋值时先看字符串常量池,如果字符串常量池中没有,就在常量池中创建一个,如果有,前者直接赋值则直接引用,后者使用new String()在堆内存中还需创建一个实例对象(此时引用变量指向的是堆内存中创建的实例对象,而不是常量池中的实例对象)。
字符串常量池的作用:
“池” 是编程中的一种常见的, 重要的提升效率的方式。 对于字符串常量池来说,每次使用相同字面类型的常量时,Java会首先在字符串常量池中查找是否存在该常量的实例,如果存在则直接返回引用,避免重复创建新的实例,从而提高程序的运行速度并节省内存。
三、字符串的不可变性
1.String类在设计时就是不可改变的,String类实现描述中已经说明了。
2.所有涉及到可能修改字符串内容的操作都是创建一个新对象,改变的是新对象。
为什么 String 要设计成不可变的?
- 方便实现字符串对象池. 如果 String 可变, 那么对象池就需要考虑写时拷贝的问题了.
- 不可变对象是线程安全的.
- 不可变对象更方便缓存 hash code, 作为 key 时可以更高效的保存到 HashMap 中
四、字符串的拼接
上面我们说String
被设计成不可变类型,那么字符串的拼接该怎么解释呢?
public static void main(String[] args) { String str="hello"; str+="world"; System.out.println(str); }
我们将上面的代码进行编译,其实它的底层实现如下:
public static void main4(String[] args) { //上述代码的底层实现: String str = "hello"; StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(str); stringBuilder.append("abc"); str = stringBuilder.toString(); System.out.println(str); }
所以,每次字符串的拼接底层都会创建一个StringBuilder对象,最后通过toString再返回一个新的String对象,可以想象如果在一个循环中使用字符串的拼接,那么它的效率将会非常低。话说回来,上面提到了StringBuilder,他究竟是什么?下面我们详细介绍:
五、StringBuilder和StringBuffer
由于String的不可更改特性,为了方便字符串的修改,Java中又提供StringBuilder和StringBuffer类。与String类的不同就是,在这些类中的对字符串修改的方法都是直接对原字符串进行修改,最后返回的都是修改后的原字符串。
此外,StringBuffer和StringBulider方法都是一样的,区别是StringBuffer被synchronized(锁)修饰,(线程安全)用在多线程情况下。单线程下一般用StringBuilder,因为频繁的加锁和释放锁也是需要耗费系统资源的。
(1)String、StringBuffer、StringBuilder的区别
String的内容不可修改,StringBuffer与StringBuilder的内容可以修改。
StringBuffer与StringBuilder大部分功能是相似的。
StringBuffer采用同步处理,属于线程安全操作;而StringBuilder未采用同步处理,属于线程不安全操作
(2)三者之间的转换
- StringBuffer或StringBuilder转String:调用toString方法
- String转StringBuffer或StringBuilder:利用它们的构造方法或append()方法
经典例题: 在不考虑常量池之前是否存在的情况下,以下总共创建了多少个String
对象?
String str = new String("ab"); // 会创建多少个对象 String str = new String("a") + new String("b"); // 会创建多少个对象 //答案:2 5
解析:
对于代码 String str = new String(“ab”); 会创建两个String对象。首先,"ab"字面量会在字符串常量池中创建一个String对象,然后通过调用new String()构造函数创建第二个String对象。
对于代码 String str = new String(“a”) + new String(“b”); 会创建五个String对象。首先,字面量 “a” 和 “b” 分别会在字符串常量池中创建两个String对象。接着,通过 new String() 构造函数创建了另外两个String对象。最后,通过字符串拼接操作符 + 进行连接时,会创建一个新的String对象,其值为拼接结果 “ab”。