Java String相关问题
一、String 的不可变特性
String 可能是 Java 中最特殊的一个数据类型。
- 和基本数据类型的封装类型一样,具备不可变性;
- 声明方式像基本数据类型,但其实是引用类型;
- 作为引用类型,又不一定直接放在堆里,有可能放在字符串池(String Pool)中;
String
的不可变特性体现在哪些方面呢?
首先,String
类是一个 final
类,它不能被继承:
public final class String implements Serializable, Comparable<String>, CharSequence {
/** 用来存储字符 */
private final char[] value;
......
}
其次,它的内部真正用来存储字符的 value
数组也是一个 final
类型的,不能被修改指向。
此外,String
类中所有外部能调用的操作,凡是返回String
对象的操作方法都是返回一个新的String
实例对象:
public String subString( int begin ) {
......
return (beginIndex == 0) ? this : new String(value, begin, subLen);
}
也就是说,不管你怎么操作,最开始的String
对象一定是不变的,你得到的所有String
新实例都是新生成的(当然新生成的也是不可变的),而不会去修改原本的对象。
所以,总结一下String
的不可变特性主要体现在两个方面:
- 不可变的类(包括数据成员)
- 不可变的实例对象
二、String 为什么要设计成不可变的?
- 使用习惯需要:
String fileName = "demo.jar";
String dirPath = “disk/code/";
String filePath = dirPath + fileName;
String
实例的使用,主要是对字面量的使用,更接近基本数据类型。
- 计算需要:加快字符串处理速度
public final class String implements Serializable, Comparable<String>, CharSequence {
/** 缓存字符串的hash code */
private int hash;
......
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h =(31 * h + val[i]; // 将每一位的特征在末位不断累加
}
hash = h;
}
return h;
}
}
我们发现 String
的 hash
值是计算出来以后缓存在内部的,假如 String
的 value
数组可变,那么可能每次改变 value
数组都要重新计算 hash
值,效率降低。
由于String
是不可变的,保证了hashcode
的唯一性,于是在创建对象时其hashcode
就可以放心的缓存了,不需要重新计算。这也就是Map
喜欢将String
作为Key
的原因,处理速度要快过其它的键对象。所以HashMap
中的键往往都使用String
。
- 线程安全需要:
例如以下代码:
public StringBuilder addSuffix(StringBuilder builder) {
return builder.append(“.jar");
}
// 这里的 StringBuilder 不具备线程可见性,且 StringBuilder 是可变类
String fileNameBuilder = new StringBuilder("demo");
String fullFileName = "";
fullFileName = addSuffix(fileNameBuilder).toString(); // 并发执行
fullFileName = addSuffix(fileNameBuilder).toString(); // 并发执行
在不加锁的前提下,假设有两个线程同时调用 addSuffix
方法并将结果赋值给 fullFileName
,那么 fullFileName
结果是 demo.jar
还是 demo.jar.jar
?显然这无法得到线程安全的保证。
如果换成是 String
,就能保证绝对线程安全,结果一定是 demo.jar
:
public String addSuffix(String str) {