String不得不说的那些事
一、String、StringBuilder和StringBuffer的区别
1. String是字符串常量,StringBuilder和StringBuffer是字符串变量
String对象创建完成之后,如果对其更改,都是重新创建一个字符串对象,让引用变量重新指向其引用地址,而StringBuilder和StringBuffer都是可变的;
2. StringBuilder是线程不安全的,StringBuffer是线程安全的
StringBuffer相关方法添加了线程同步关键字synchronize关键字,故线程安全的,但效率低比StringBuilder低。
二、String为什么设计成不可变(immutable)
1. 字符串常量池的优化需要
字符串常量池针对String字符串使用的一种优化策略,创建字符串对象前,先检查字符串常量是否已经有该字符串(obj1.equal(obj2)),有直接返回字符串在字符串常量池的引用,如果String为可变的,这种优化策略则无效;
2. 允许String对象缓存HashCode
字符串的不变性保证了hashcode唯一性,不可变的hashcode可以被缓存而不用重新计算,提升了像使用String作为键值的hashmap的效率,这也侧面反映了hashmap为什么多数使用String作为键值的原因了;
3. 多线程使用安全性
字符串不可变,所以在多线程可以共享一个字符串实例,而不需要做额外的线程同步;
4. 类加载器需要
类加载器用到字符串,不可变性提供了安全性,以便类的正确加载;例如在加载java.sql.Connection类,如果这个值被改成myhacked.Connection,则会对数据库造成不可知的破坏;
5. 安全性
如果字符串是不可变的,则会引起很严重的安全问题;例如数据库的用户名和密码都是以字符串形式传入获得数据库的连接,socket编程中,主机名等都是以字符串形式传入,如果字符串可变,黑客可以很容易改变字符串对象的值,造成安全漏洞。
三、String直接创建对象(String s="abc")和intern()方法的区别
两者在创建字符串对象先去字符串常量池查找,如果有,直接返回该字符串的引用,没有则在字符串常量池创建并返回引用,看上去两者无差别,但是这样那么intern存在的意义为何?
测试代码 Test.java
String s1 = "ab";
String s2 = "c";
String s3 = "abc";
System.out.println(s3=="ab"+"c"); //true
System.out.println(s3==s1+s2); //false
System.out.println(s3==(s1+s2).intern()); //true
编译代码 Test.class
String s1 = "ab";
String s2 = "c";
String s3 = "abc";
System.out.println(s3 == "abc");
System.out.println(s3 == s1 + s2);
System.out.println(s3 == (s1 + s2).intern());
"ab"+"c";字符串拼接在编译期可以确定其值,进而可以在编译阶段确定该字符串是否存在于字符串常量池;但是s1+s2;字符串引用拼接需要在运行期才能得到结果,指望不上编译器的字符串常量池优化策略了,这时候intern方法作用便体现了,在运行期确定常量池是否有需要创建的字符串对象,如果有,返回其字符串常量池的引用。故得出的结论:String直接赋值和intern方法在字符串常量池的优化策略上,一个体现在编译期,一个则在运行期。
四、StringBuilder和"+"号的区别?
1. 拼接字符串常量
测试类
6 String s1 = "a" + "b" + "c";
7 String s2 = new StringBuilder().append("a").append("b").append("c").toString();
编译class
String s1 = "abc";
String s2 = "a" + "b" + "c";
字节件ByteCode
L0
LINENUMBER 6 L0
LDC "abc"
ASTORE 1
L1
LINENUMBER 7 L1
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
LDC "a"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
LDC "b"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
LDC "c"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
ASTORE 2
使用“+”进行字符串常量的拼接在编译时就已经完成,而使用 StringBuilder 进行字符串拼接需要在运行时完成。所以单纯的字符串常量拼接“+”的效率 应该高于 StringBuilder
2. 拼接字符串和引用
测试类
6 String s1 = "a";
7 String s2 = new StringBuilder().append(s1).append("b").append("c").toString();
8 String s3 = s1 + "b" + "c";
编译class
String s1 = "a";
String s2 = s1 + "b" + "c";
String s3 = s1 + "bc";
字节码ByteCode
L0
LINENUMBER 6 L0
LDC "a"
ASTORE 1
L1
LINENUMBER 7 L1
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
ALOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
LDC "b"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
LDC "c"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
ASTORE 2
L2
LINENUMBER 8 L2
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
ALOAD 1
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
LDC "bc"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
ASTORE 3
从字节码可以看出“+”拼接的字符串引用底层还是使用StringBuilder