String 对象一旦创建,值是不能修改的。
String底层是用数组来存值的。如果需要频繁修改字符串,则使用StringBuffer。
String 和 StringBuffer的耗时
public static void main(String[] args) {
long st = System.currentTimeMillis();
String str = "";
for(int i=0;i<50000;i++){
str += i;
}
long et = System.currentTimeMillis();
System.out.println(et-st);
long st2 = System.currentTimeMillis();
StringBuffer sb = new StringBuffer();
for(int j=0;j<50000;j++){
sb.append(j);
}
long et2 = System.currentTimeMillis();
System.out.println(et2-st2);
}
结果:String -> 1807,StringBuffer -> 6.
StringBuffer 初始默认长度16。
当我们创建一个StringBuffer对象时,长度为super(str.length()+16),创建了一个字符串缓冲区。
StringBuilder 和 StringBuffer 是一对兄弟,因为它们拥有同一个父类 AbstractStringBuilder,同时实现的接口也是完全一样,都实现了 java.io.Serializable, CharSequence 两个接口。
区别
StringBuffer 添加了 synchronized 关键字修饰(线程安全),而 StringBuilder 没有。
因为StringBuffer是线程安全的,所以效率通没有StringBuilder高。
比较StringBuffer 和 StringBuffer的耗时
public static void main(String[] args) {
for(int i=0;i<100000;i++){
test t =new test();
}
System.out.println("⬆️⬆️⬆️⬆️⬆️ warm up code ⬆️⬆️⬆️⬆️⬆️");
System.out.println("===============================");
long st = System.currentTimeMillis();
StringBuilder s1 = new StringBuilder();
for(int i=0;i<500000;i++){
s1.append(i);
}
long et = System.currentTimeMillis();
System.out.println("StringBuilder run Times: "+(et-st));
System.out.println("===============================");
long st2 = System.currentTimeMillis();
StringBuffer s2 = new StringBuffer();
for(int j=0;j<500000;j++){
s2.append(j);
}
long et2 = System.currentTimeMillis();
System.out.println("StringBuffer run Times: "+(et2-st2));
}
结果:
⬆️⬆️⬆️⬆️⬆️ warm up code ⬆️⬆️⬆️⬆️⬆️
===============================
StringBuilder run Times: 39
===============================
StringBuffer run Times: 52
在比较两者效率的时候,最开始发现StringBuffer有时会比StringBuilder运行得快。后来发现这里有一个概念叫虚拟机预热(warm up the JVM)。
JVM 预热是指什么?
类加载过程完毕后,所有需要的类会进入 JVM cache (native code) ,这样就可以被快速的实时访问。
当应用的第一个请求到来,会触发逻辑相关类的第一次加载,此过程会有一定的耗时,会影响第一次调用的实时响应。这主要是因为JVM的懒加载及JIT机制。因此对于低延迟应用,必须采用特定的策略来处理第一次的预加载逻辑,以保障第一次的请求的快速响应。此过程,我们称之为 JVM 的预热。
所以当我预热JVM之后,得到的结果就相对比较准确了。
StringBuilder为什么线程不安全?
public static void main(String[] args) {
StringBuilder s1 = new StringBuilder();
for(int i=0;i<100;i++){
new Thread(new Runnable() {
@Override
public void run() {
for(int j=0;j<1000;j++){
s1.append("a");
}
}
}).start();
}
try {
Thread.sleep(100);
System.out.println(s1.length());
} catch (InterruptedException e) {
e.printStackTrace();
}
当我们开启100个线程的时候,发现结果98642
比100000小。
高频面试题
1、StringBuilder 的效率一定比 String 更高吗?
我们通常会说 StringBuilder 效率要比 String 高,严谨一点这句话不完全对,虽然大部分情况下使用 StringBuilder 效率更高,但在某些特定情况下不一定是这样,比如下面这段代码:
String str = "Hello"+"World";
StringBuilder stringBuilder = new StringBuilder("Hello");
stringBuilder.append("World");
此时,使用 String 创建 “HelloWorld” 的效率要高于使用 StringBuilder 创建 “HelloWorld”,这是为什么呢?
因为 String 对象的直接相加,JVM 会自动对其进行优化,也就是说 “Hello”+“World” 在编译期间会自动优化为 “HelloWorld”,直接一次性创建完成,所以效率肯定要高于 StringBuffer 的 append 拼接。
但是需要注意的是如果是这样的代码:
String str1 = "Hello";
String str2 = "World";
String str3 = str1+str2;
对于这种间接相加的操作,效率要比直接相加低,因为在编译器不会对引用变量进行优化。
2、下面代码的运行结果是?
String str1 = "Hello World";
String str2 = "Hello"+" World";
System.out.println(str1 == str2);
true
,因为 “Hello”+" World" 在编译期间会被 JVM 自动优化成 “Hello World”,是一个字符串常量,所以和 str1 引用相同。
3、下面代码的运行结果是?
String str1 = "Hello World";
String str2 = "Hello";
String str3 = str2 + " World";
System.out.println(str1 == str3);
false
,JVM 只有在 String 对象直接拼接的时候才会进行优化,如果是对变量进行拼接则不会优化,所以 str2 + " World" 并不会直接优化成字符串常量 “Hello World”,同时这种间接拼接的结果是存放在堆内存中的,所以 str1 和 str3 的引用肯定不同。
4、String str = new String(“Hello World”) 创建了几个对象?
这是很常见的一道面试题,大部分的答案都是 2 个,“Hello World” 是一个,另一个是指向字符串的变量 str,其实是不准确的。
因为代码的执行过程和类的加载过程是有区别的,如果只看运行期间,这段代码只创建了 1 个对象,new 只调用了一次,即在堆上创建的 “Hello World” 对象。
而在类加载的过程中,创建了 2 个对象,一个是字符串字面量 “Hello World” 在字符串常量池中所对应的实例,另一个是通过 new String(“Hello World”) 在堆中创建并初始化,内容与 “Hello World” 相同的实例。
所以在回答这道题的时候,可以先问清楚面试官是在代码执行过程中,还是在类加载过程中。这道题目如果换做是 String str = new String(“Hello World”) 涉及到几个对象,那么答案就是 2 个。
5、String、StringBuffer、StringBuilder 有什么区别?
1、String 一旦创建不可变,如果修改即创建新的对象,StringBuffer 和 StringBuilder 可变,修改之后引用不变。
2、String 对象直接拼接效率高,但是如果执行的是间接拼接,效率很低,而 StringBuffer 和 StringBuilder 的效率更高,同时 StringBuilder 的效率高于 StringBuffer。
3、StringBuffer 的方法是线程安全的,StringBuilder 是线程不安全的,在考虑线程安全的情况下,应该使用 StringBuffer。
6、下面代码的运行结果是?
public static void main(String[] args) {
String str = "Hello";
test(str);
System.out.println(str);
}
public static void test(String str){
str+="World";
}
Hello
,因为 String 是不可变的,传入 test 方法的参数相当于 str 的一个副本,所以方法内只是修改了副本,str 本身的值没有发生变化。
7、下面代码的运行结果是?
public static void main(String[] args) {
StringBuffer str = new StringBuffer("Hello");
test(str);
System.out.println(str);
}
public static void test(StringBuffer str){
str.append(" World");
}
Hello World
,因为 StringBuffer 是可变类型,传入 test 方法的参数就是 str 的引用,所以方法内修改的就是 str 本身。
参考文章:
How to Warm Up the JVM
每日一读