区别
- String (JDK1.0) : 不可变字符序列 底层使用char[]存储
- StringBuffer (JDK1.0) : 可变字符序列、效率低、线程安全 底层使用char[]存储
- StringBuilder (JDK1.5) : 可变字符序列、效率好、线程不安全 底层使用char[]存储
注意:作为参数传递的话,方法内部String不会改变其值,StringBuffer和StringBuilder会改变其值。
源码分析:
String str=new String(); -----------底层 : char[] value=new char[0]
String str=new String(“abc”);-----------底层:char[] value=new char[]{‘a’,‘b’,‘c’}
StringBuffer str=new StringBuffer();---------底层 char[] value=new char[16] 创建一个长度为16的数组
StringBuffer str=new StringBuffer(“abc”);---------底层 char[] value=new char[“abc”.length+16] 创建一个长度为"abc".length+16的数组
扩容问题: 如果要添加的数据底层数组盛不下了,那就需要扩容底层的数组。
默认情况下,扩容为原来容量的2倍 + 2,同时将原有数组中的元素复制到新的数组中。
开发建议:
- 一般开发中,字符串如果不经常变化,优先使用String,如少量的字符串拼接操作,常量声明等。
- 在不存在线程安全问题的场景下(单线程),如果需要操作大量字符串,可以使用StringBuilder,避免使用 常量+变量来拼接字符串 如 Sting str=“abc”,str=str+“def”,这样会导致大量副本字符串对象再内存中,降低效率,影响程序性能
- 在线程不安全的情况下,如有大量字符串需要操作,应使用StringBuffer
- 如果能预知大小的话最好在new StringBuffer 或者StringBuilder 的时候设置好capacity,避免多次扩容的开销。扩容要抛弃原有数组,还要进行数组拷贝创建新的数组。减少内存操作,提高效率
String类详解
String类的特性:
- String类: 代表字符串。java程序中的所有字符串字面值(如“abc”)都做为此类的实例实现
- String是一个final类,代表不可变的字符序列
- 字符串是常量,用双引号引起来表示。它们的值在创建之后不能更改。
- String对象的字符内容是存储在一个字符数组value[]中的。
/**
* 1.String类声明为final,不可继承
* 2.String实现了Serializable接口:表示字符串支持序列化
* 实现了Comparable接口:表示String可以比较大小
* 3.String内部定义了final char [] value 用于存储 字符串数据
* 4.String不可变性
* 体现:1.当对字符串重新赋值时,需要重写指定内存区域赋值,不能使用原有的value进行赋值
* 2.当对现有的字符串进行连接操作,也需要重新指定内存区域赋值,不能使用原有的value进行赋值
* 3.当调用String的replace()方法修改指定字符串或字符时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值
*
* 5.通过字面量赋值(区别与new),此时字符串是存在常量池而不是堆内存
* 6.字符串常量池中是不会存储相同内容的字符串的(内容相同,其实是一个地址)
*/
String不可变性体现 :
@Test
public void test01(){
//字面量赋值 存在常量池
String s1="abc";
String s2="abc";
//比较s1和s2的地址 true
System.out.println(s1==s2);
s2="hello";
//abc
System.out.println(s1);
//hello
System.out.println(s2);
//这里其实用的还是s1的内存
String s3="abc";
//开辟了新内存
s3+="def";
// abcdef
System.out.println(s3);
//abc
System.out.println(s1);
//用的和s1一样的内存
String s4="abc";
//开辟新内存
String s5 = s4.replace('a', 'm');
//mbc
System.out.println(s5);
//abc
System.out.println(s4);
}
内存图: