1. String
不可变
查看String
源码如下:public final class String implements java.io.Serializable, Comparable<String>, CharSequence{ /** The value is used for character storage. */ private final char value[]; /** The offset is the first index of the storage that is used. */ private final int offset; /** The count is the number of characters in the String. */ private final int count; /** 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; ...... }
由源码可知,String中存储数据的数组被关键字
final
修饰,因此是不可变的运算和操作
创建对象
创建对象有两种方式://方式一 String str = "abc"; //方式二 String str = new String("abc");
两种方式都会在栈中创建一个字符串变量
str
,但它们的内存分配方式是不同的。
我们可以通过如下代码直观看出两种方式的不同String str1 = "abc"; String str2 = "abc"; String str3 = new String("abc"); String str4 = new String("abc"); System.out.println(str1 == str2); //true System.out.println(str3 == str4); //false
为了理解这部分内容,最好先了解一下Java中的内存分配机制,可参考此篇博客:【后端面经-Java】JVM内存分区详解
总之,简单来说,内存主要分为栈、堆、方法区等部分,栈中存放局部变量,堆中存放对象实例和数组,方法区中存放类信息和常量等,常量池
一开始均在方法区中,后来运行时常量池
转移到堆中,下文均按照这种内存分配模型来讨论。
下图展示了两种创建方式下的内存情况:方式一:
- 在栈中创建一个变量之后,需要指向具体的值,首先会在常量池中查找
abc
,如果找到,则指向这个字符串,如果没有找到,在运行时常量池
中创建这一字符串,然后指向它。 - 因此,
str1
和str2
指向的是同一个字符串,即同一个内存单元,所以str1 == str2
为true
- 在栈中创建一个变量之后,需要指向具体的值,首先会在常量池中查找
方式二
- 在栈中创建一个变量之后,在堆中构造一个新的字符串对象,然后指向它。
- 因此,
str3
和str4
指向的是两个不同的内存单元,所以str3 == str4
为false
"+"运算
- 每次"+"运算虽然看似很简便,实际上需要创建一个新的String对象来接收结果,而作为运算数的String对象依然存在于堆中,成为垃圾占用堆空间,需要Java垃圾回收机制进行处理。(关于Java垃圾回收机制,可参考此篇博文:【后端面经-Java】JVM垃圾回收机制)
- 这种操作是非常低效的,且造成了大量的内存占用,因此在实际开发中,应尽量避免使用"+"运算符来进行字符串拼接,而应该使用
StringBuffer
或StringBuilder
来进行字符串拼接。
substring() && replace() && concat()
- 这些操作的一个特点就是:创建新的String对象承接结果,而原来的String对象依然存在于堆中。
适用场景
适用于字符串不需修改的场景。
2. StringBuffer
可变
- StringBuffer源代码中数组是可变长度的。
线程安全
- 在类定义过程中,适用
synchronized
关键字,保证线程安全。 - 线程安全与否是
StringBuffer
和StringBuilder
的重要区别之一。
- 在类定义过程中,适用
运算和操作
append()
:在字符串末尾添加新字符串;insert()
:在指定位置插入新字符串;toString()
:将StringBuffer
转换为String
;
适用场景
多线程,字符串需要频繁修改
3. StringBuilder
可变
- 和
StringBuffer
一样,StringBuilder
源代码中数组是可变长度的。
- 和
线程不安全
- 并没有使用
synchronized
关键字,因此线程不安全。 - 因为线程不安全,不需要考虑线程安全的处理,所以
StringBuilder
的性能比StringBuffer
略高。
- 并没有使用
适用场景:
- 单线程,字符串需要频繁修改
4. 性能提升
- 为了提升性能,避免在字符串需要修改的场景下使用
String
类; - 在初始定义时预先估计字符串的长度,对
StringBuilder
和StringBuffer
进行初始化,避免频繁扩容,提升性能。
StringBuilder sb = new StringBuilder(100);
StringBuffer sb = new StringBuffer(100);
``
## 5. 总结和比较
下图是对三者进行的比较:
![](https://cdn.jsdelivr.net/gh/cyl173/Imagebed/String相关类对比.png)
## 面试模拟
> Q:简单介绍一下String和StringBuilder的区别
> A:首先,String定义的字符串是不可变的,使用拼接函数或者操作符将会创建一个新的String对象,性能不高;而StringBuilder定义的字符串可变,且线程不安全使得其具有较好的性能,在字符串需要频繁修改的场景下,使用StringBuilder会更好。
## 参考资料
1. [Java String、StringBuffer 和 StringBuilder 的区别](https://www.runoob.com/w3cnote/java-different-of-string-stringbuffer-stringbuilder.html)
2. [Java中String和StringBuilder的区别](https://www.techiedelight.com/zh/difference-between-string-stringbuilder-java/)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。