博客原文 ----> https://www.yanzhaochang.top/read/11
String 字符串总结
String被声明为final,因此它不可被继承,是常量,不可变的。
a) 不可变的好处
1. 可以缓存hash值
因为String和hash值经常被使用,例如Stirng用作HashMap的key。不可变的特性还可以保证hash值不可变,因此只需要进行一次计算,这也是一种性能的优化手段。
2. String Pool的需要
字符串常量池(String pool String 保留池 String 保留池)是Java堆中一个特殊的储存区域。
创建对象时,假如此字符串已经存在于常量池中,就会从String Pool中取得引用,不会再创建。只有String是不可变的,才可能使用String Pool。
例:
3 安全性
String 被许多的Java类库用来当作参数,例如:网络的连接地址、文件路径Path、反射中使用的String类型的参数,如果String不是固定不变的,将会引入各种安全隐患。
b) 字符串缓冲池
JVM为了提升性能和减少内存的开销,避免字符串的重复创建,在内存中会单独开辟出一块单独的区域,用来储存字符串对象,这块内存区域被称为字符串缓冲池。
String s1 = "test";
String s2 = "test";
String s3 = new String("test");
String s4 = new String("test");
String s5 = new String("test2");
System.out.print(s1==s2);
System.out.print(s1==s3);
System.out.print(s2==s3);
System.out.print(s3==s4);
结果:
true false false true
解析:
1 对于s1,在创建字符串的时候,JVM虚拟机会先去缓冲池中查找是否存在"test"这个对象
如果不存在,就在缓冲池中创建"test"这个对象,然后及那个"test"对象的引用地址返回给字符串常量str,str就会指向"test"字符串对象
如果存在,则不创建任何对象,直接将池中"test"的地址返回,赋值给字符串常量,共创建了1个对象。
2 对于s2 ,在创建时,同样会先查询缓冲池,因为缓存池中存在,所以直接将池中的地址返回,赋值给字符串常量,共创建了0个对象。
3 对于s3,在创建时,同样会先查询缓冲池,因为存在test对象,所以不必创建,然后test对象作为构造函数的对象,在内存中(非缓存池)又创建了一个新的字符串对象,共创建了1个对象。
4 s4 的创建过程同s3
5 在创建时,同样会先查询缓冲池,缓存池中不存在test2对象,先创建一个test2字符串对象,然后再以test2为构造函数参数创建一个s5对象,共创建了2个对象。
String str1="java";
String str2="blog";
String s_1=str1+str2;
String s_2=str1+ "blog";
String s_3="java"+"blog";
System.out.print(s_1 == s_2);
System.out.print(s_1 == s_3);
System.out.println(s_2 == s_3);
System.out.print(s_1=="javablog");
System.out.print(s_2=="javablog");
System.out.println(s_3=="javablog");
结果:
false false false
false false true
解析:
1. s_1 创建时,+运算符会先在堆中创建两个对象str1、 str2,会在缓冲池直接复制给这两个值,然后运行时创建一个StringBuilder并用str1 的字符串完成初始化,然后调用append方法合并str2的字符串,最后使用toString方法完成类型转换,形成新对象引用给s_1,共创建了3个对象。
2. s_2 创建时,不放入字符串池中,在堆中先创建对象str1,然后再查找缓冲池,存在"java"对象,直接引用,,然后运行时创建一个StringBuilder并用str1 的字符串完成初始化,然后调用append方法合并"blog"。最后使用toString方法完成类型转换,形成新对象引用给s_1,共创建2个对象。
3. s_3 创建时,会直接查询缓冲池,存在,则赋值,不存在再创建,所以此过程共创建1个对象。
总结:
1. ""引号创建的字符串会在字符串池中。
2. new 创建的会先查看池中有相同的值的字符串
如果有,则拷贝一份到堆中,然后返回堆中的地址;
如果没有,则直接在堆中创建一份,然后返回堆中的地址(此时不需要在堆中复制到池中,否则,会浪费池的空间)
c) String StringBuffer StringBuilder
1 区别
a) String:不可变字符;
StringBuffer:可变字符序列、效率低、线程安全(操作方法上加synchronized);
StringBuilder:可变字符序列、效率高、线程不安全;
b)String可以赋予空值,后两个不行
2 总结
a) 操作少量数据使用String;
b) 多线程下操作大量数据使用StringBuffer;
c) 单线程下操作大量数据使用StringBuilder(推荐使用);
3 速度排序:String < StringBuffer < StringBuilder
String s = "aaa";
long l1 = System.currentTimeMillis();
for(int i = 0;i<100000;i++) {
s+=i;
}
long l2 = System.currentTimeMillis();
System.out.println(l2-l1);
s="aaa";
StringBuffer s2 = new StringBuffer(s);
long l3 = System.currentTimeMillis();
for(int i = 0;i<10000000;i++) {
s2.append(i);
}
long l4 = System.currentTimeMillis();
System.out.println(l4-l3);
s="aaa";
StringBuilder s3 = new StringBuilder(s);
long l5 = System.currentTimeMillis();
for(int i = 0;i<10000000;i++) {
s3.append(i);
}
long l6 = System.currentTimeMillis();
System.out.println(l6-l5);
结果:
27951
433
310
结论:
可以看出,在StringBuffer 和StringBuilder 是String拼接次数的100的情况下,还快100倍。
原因在每次String对于字符串拼接的时候都会创建一个StringBuilder 使用append方法拼接完之后,再用toString()方法再转换成String类。执行次数越多,创建和销毁的对象就越多。