面试官能够调查的内容
假如继续深入,面试官能够从各种不同的视点调查,比如能够:
- 经过 String 和相关类,调查根本的线程安全规划与完结,各种根底编程实践。
- 调查 JVM 目标缓存机制的理解以及如何杰出地运用。
- 调查 JVM 优化 Java 代码的一些技巧。
- String 相关类的演进,比如 Java 9 中完结的巨大变化。…
String
String、StringBuilder、StringBuffer的差异
-
虚拟机的特别处理
- String类是Java里面一个比较特别的类,由于String类运用的十分频频,所以虚拟机保护了一个字符串常量池,String类能够像根本类型那样,以字面量的办法创立字符串,也能够运用new的办法创立一个字符串目标。假如字面量的办法,会先从常量池中获取,假如常量池没有的话,再去创立一个字符串目标,并将字符串的引证保存在字符串常量池里。
- StringBuilder、StringBuffer就是一般的类。
-
可变性
- String类是不可变的,满意了不可变类的界说(具体见下一个问题),对String进行拼接、或许trim()办法去掉左右空格时,都会发生一个新的字符串目标。
- StringBuilder和StringBuffer都是可变的,都继承AbstractStringBuilder类,和String类相同,底层也是运用字符数组来保存字符串,不过没有运用final和private关键字润饰,而且供给了修正字符串的办法。比如:append、insert等,都是直接在原目标上进行操作。
-
线程安全性
- String目标不可变,线程操作某个String目标,并不能修正其内容,所以多线程的状况下,不需要做同步,就能获取到正确的结果,所以是线程安全的(参考文章:为什么String类不可变,就是线程安全的?)
- StringBuffer中操作字符串的办法都加了同步锁,也是线程安全的
- StringBuilder没有对操作字符串的办法加同步锁,所以对错线程安全的
-
性能
- String目标每次进行改动的时分,都会生成一个新的目标,所以比较合适于字符串少量操作的状况,不然会在堆区生成很多的字符串
- StringBuffer由于加了同步锁,所以性能上比StringBuilder稍差一些。在单线程操作很多字符串的状况,比较合适运用StringBuilder;多线程操作很多字符串比较合适运用StringBuffer。
String为什么不可变
String类满意了不可变类的界说。
- 用final关键润饰,所以String类不会被继承,避免了子类破坏String类的不可变性
- 保存字符串的数组用final润饰而且是私有的,而且String类没有供给修正这个字符串的办法,所以外部类也无法拜访和修正。
不可变类仅仅其实例不能被修正的类。每个实例中包括的一切信息都有必要在创立该实例的时分就供给,而且在目标的整个生命周期内固定不变。为了使类不可变,要遵从下面五条规矩:
1. 不要供给任何会修正目标状态的办法。
2. 保证类不会被扩展。一般的做法是让这个类成为 final的,避免子类化,破坏该类的不可变行为。
3. 使一切的域都是 final 的。
4. 使一切的域都成为私有的。避免客户端取得拜访被域引证的可变目标的权限,并避免客户端直接修正这些目标。
5. 保证关于任何可变性组件的互斥拜访。 假如类具有指向可变目标的域,则有必要保证该类的客户端无法取得指向这些目标的引证。
Java 9 为何要将 String的底层完结由char[]改成了byte[] ?
新版的 String 其实支撑两个编码方案:Latin-1 和 UTF-16。假如字符串中包括的汉字没有超越 Latin-1 可表示范围内的字符,那就会运用 Latin-1 作为编码方案。Latin-1 编码方案下,
byte
占一个字节(8 位),char
占用 2 个字节(16),byte
相较char
节约一半的内存空间。JDK 官方就说了绝大部分字符串目标只包括 Latin-1 可表示的字符。
假如字符串中包括的汉字超越 Latin-1 可表示范围内的字符,
byte
和char
所占用的空间是相同的。这是官方的介绍:openjdk.java.net/jeps/254 。
字符串拼接,用+ 仍是StringBuilder
Java 言语自身并不支撑运算符重载(?),“+”和“+=”是专门为 String 类重载过的运算符,也是 Java 中仅有的两个重载过的运算符。
String str1 = "he";
String str2 = "llo";
String str3 = "world";
String str4 = str1 + str2 + str3;
上面的代码对应的字节码如下:
能够看出,字符串目标经过“+”的字符串拼接办法,实际上是经过 StringBuilder
调用 append()
办法完结的,拼接完结之后调用 toString()
得到一个 String
目标 。
不过,在for循环内运用“+”进行字符串的拼接的话,存在比较显着的缺点:StringBuilder
目标是在循环内部被创立的,这意味着每循环一次就会创立一个StringBuilder
目标。编译器不会创立单个 StringBuilder
以复用,会导致创立过多的 StringBuilder
目标。
不过,运用 “+” 进行字符串拼接会发生很多的临时目标的问题在 JDK9 中得到了解决。在 JDK9 傍边,字符串相加 “+” 改为了用动态办法
makeConcatWithConstants()
来完结,而不是很多的StringBuilder
了。这个改进是 JDK9 的 JEP 280open in new window
字符串常量池和intern办法
详见文章:JVM:字符串常量池
String 类型的变量和常量做“+”运算时发生了什么。
String str1 = "str";
String str2 = "ing";
String str3 = "str" + "ing"; //常量池中的目标
String str4 = str1 + str2; //在堆上创立的新的目标
String str5 = "string"; //常量池中的目标
System.out.println(str3 == str4);//false
System.out.println(str3 == str5);//true
System.out.println(str4 == str5);//false
关于编译期能够确定值的字符串,也就是常量字符串 ,jvm 会将其存入字符串常量池。而且,字符串常量拼接得到的字符串常量在编译阶段就已经被存放字符串常量池,这个得益于编译器的优化。