最近看到了一道题目:
正确答案:C
下面就这题的其他选项给出解析:
String
String不可变!先来看看String的源码:
public final class String implements Serializable, Comparable<String>, CharSequence {
private final byte[] value;
private final byte coder;
private int hash;
private static final long serialVersionUID = -6849794470754667710L;
.......}
1、String由final修饰,说明final不能被继承,不能被修改
2、String用来存储数据的是字节数组 byte[] ,同样也是由final修饰的。
3、虽然字节数组value是由final修饰,但是我们要清楚一个原则就是:String是引用型变量,要清楚在String的数组是放在堆中的,然后将栈中放的是数组在堆中的引用地址,而我们通常所用的就是一个指向堆中真实数组数据的一个引用地址,所以也称String为引用型对象。简而言之:引用地址不可变,但是地址指向的堆中的数组数据是可以改变的。一旦创建了一个String对象,就在内存中申请一片固定的地址空间存放数据,不管怎么变,地址是不会变的,但是地址所指向的空间真实存放的数据是可以改变的。再通俗点就是:你家的门牌号不会变,但是你家要住几口人是你说了算。
下面通过一个Demo来说明:
public class Main {
public static String strAppend(String str){
str += "String";
return str;
}
public static StringBuilder sbAppend(StringBuilder str){
return str.append("StringBuilder");
}
public static void testBuilder() {
String s1 = new String("ysy ");
String s2 = Main.strAppend(s1);
System.out.println("String不可改变");
System.out.println(s2.toString());
System.out.println(s1.toString());
System.out.println("StringBuilder可以改变:");
StringBuilder sb1 = new StringBuilder("string");
StringBuilder sb2 = Main.sbAppend(sb1);
System.out.println(sb2.toString());
System.out.println(sb1.toString());
}
public static void main(String[] args) {
testBuilder();
}
}
在这里能看出来String追加字符串后能新的字符串后,原来的字符串并没有发生改变。
但是StringBuilder修改后就改变了自身的值。
Sting不可变的意义何在:
- 字符串常量池的需要
- 允许String对象缓存HashCode
- 安全性
在这里可以参考一位大佬的博客为什么String要设计成不可变的? 说的很详细。
StringBuffer是线程安全
先来看下StringBuffer的源码:
public synchronized StringBuffer append(String str) {
this.toStringCache = null;
super.append(str);
return this;
}
注意下里面的关键字synchronize,线程安全。也可以通过下面一个Demo来例证:
先创建多个线程,然后在每一个线程中对一个变量进行自增多次,最后结果一定的话就说明这个是加了线程锁的,也就是线程安全的。
public class Main {
public static void main(String[] args) {
// testBuilder();
testBuffer();
}
public static void testBuffer(){
StringBuffer buffer = new StringBuffer();
for(int i = 0; i < 100; i++ ){
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j <1000; j++){
buffer.append("j");
}
}
}).start();
}
try {
Thread.sleep(100);
}catch (Exception e){
System.out.println(e.getMessage());
}
System.out.println("线程安全"+buffer.length());
}
}
在这里创建了100个线程,每个线程中对StringBuffer的对象append的一个字符,最后整个字符串的长度一定为100000,所以可以看出来StringBuffer的线程安全的。
StringBuilder线程不安全
同样将这个方法跟StringBuffer进行对比,结果是不一定的,能够看出StringBuilder是线程不安全的。
public static void testBuilder() {
StringBuilder builder = new StringBuilder();
for(int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for(int j = 0; j < 1000; j++) {
builder.append("a");
}
}
}).start();
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.print("线程不安全:"+builder.length());
}
String、StringBuffer、StringBuilder三者的速度
速度:String最慢,StringBuffer次之,StringBuilder最快。