文章目录
1 不可变的String
在 Java中,字符串属于对象,Java提供了 String类 来创建和操作字符串
通过查看 String源码,可以发现
- String类是
final类
,并且它的成员方法都默认为final方法
- String类是通过
char数组
来保存字符串的 - String类的 sub,concat,replace等操作,都不是在原有字符串上进行的,而是重新生成了一个新的字符串,即进行这些操作后,最原始的字符串并没有被改变
char[] helloArray = {'h','e','l','l','0'};
String helloString = new String(helloString);
String对象是不可变的
。
String类中每一个看起来会修改String值的方法,实际上都是创建了一个全新的String对象,以包含修改后的字符串的内容。
String str1 = "java";
String str2 = "java";
System.out.println(str1 == str2); // true
在代码中,可以创建同一个String对象的多个别名,而它们所指向的对象是相同的
,一直呆在一个单一的物理位置上,从未动过。
内存分析
String str = "hello"; str = str+"world";
;
初始String值为“hello”,然后在这个字符串后面加上新的字符串“world”,这个过程是需要重新在栈堆内存中开辟内存空间的,最终得到了“hello world”字符串也相应的需要开辟内存空间,这样短短的两个字符串,却需要开辟三次内存空间,不得不说这是对内存空间的极大浪费。
2 StringBuffer和StringBuilder
当对字符串进行修改的时候,需要使用 StringBuffer与 StringBuilder类。
和 String类不同的是,StringBuffer和StringBuilder类的对象能够被多次修改,并且不产生新的未使用的对象
通过查看源码发现,StringBuilder和StringBuffer类拥有的成员属性以及成员方法基本相同,区别是 StringBuffer类的成员方法前面多了一个关键字:synchronized。即 StringBuffer是线程安全的,但是 StringBuilder的访问速度更快,因此,一般用 StringBuilder.
注意:
String 可以赋空值,StringBuilder,StringBuffer不行,它们是对象,必须先 new,获得具体对象再使用
StringBuffer s = null; //结果警告:Null pointer access: The variable result can only be null at this location
StringBuffer s = new StringBuffer();//StringBuffer对象是一个空的对象
StringBuffer s = new StringBuffer(“abc”);//创建带有内容的StringBuffer对象,对象的内容就是字符串”
3 String,StringBuffer,StringBuilder三者的执行效率
一般情况下,StringBuilder > StringBuffer > String
不是所有情况下都这样
比如:String str = “hello”+“world”; 的效率,就比 StringBuilder sBuilder = new StringBuilder().append(“hello”).append(“world”); 要高
因此,这三个类各有利弊,应当根据不同的情况来进行选择使用
- 当字符串相加操作或者改动较少的情况下,建议使用 String str = “hello”;这种形式
- 当字符串相加操作较多的情况下,建议使用 StringBuilder,如果采用了多线程,则使用 StringBuffer
4 String的重载 “+”
在 Java中,
唯一被重载的运算符就是用于 String的 "+" 与"+="
除此之外,Java不允许程序员重载其他的运算符
public class StringTest {
String a = "abc";
String b = "mongo";
String info = a + b + 47;
}
String对象是不可变的,所以在上述的代码过程中可能会是这样工作的:
(1)"abc" + "mongo"
创建新的String对象abcmongo
;
(2)"abcmongo" + "47"
创建新的String对象abcmongo47
;
(3)引用info 指向最终生成的String。
但是这种方式会生成一大堆需要垃圾回收的中间对象,性能相当糟糕。
4.1 编译器的优化处理
通过反编译代码会发现,编译器自动引入了StringBuilder类
。
编译器创建了一个StringBuilder对象,并调用StringBuilder.append()方法,最后调用toString()生成结果,从而避免中间对象的性能损耗。
编译器优化String对象的连接,而下面这种情况会直接连接作为常量。
public class StringTest {
String info = "Andy" + "24" + "Developer";
}
4.2 编译器的优化是有限度的
性能较低的代码
public void implicitUseStringBuilder(String[] values) {
String result = "";
for (int i = 0 ; i < values.length; i ++) {
result += values[i];
}
System.out.println(result);
}
通过反编译发现,StringBuilder对象创建发生在循环之间
,也就是意味着有多少次循环就会创建多少个StringBuilder对象,这样明显性能较低。
性能较高的代码
public void explicitUseStringBuider(String[] values) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < values.length; i ++) {
result.append(values[i]);
}
}
StringBuilder的创建,位于循环之外,所以不会多次创建 StringBuilder
综上,循环体中需要尽量避免隐式或者显式创建StringBuilder。