特性
不可变类:所谓的不可变类是指这个类的实例一旦创建完成后,就不能改变其成员变量值。以下几点保证了String的不可变性:
- String类被final修饰,不可继承
- 内部所有成员都设置为私有变量且不存在value的set方法
- 获取value时不是直接返回对象引用,而是返回对象的copy
成员变量
private final char value[];
private final int offset;
private final int count;
其中,value存放具体字符串,offset表示偏移量,count表示char的数量。同时注意到,三者均为final修饰,根据java中final的要求所修饰变量要么在声明时候初始化,要么在构造函数中初始化。
常量池
jvm虚拟机为每个被装载的类型维护一个常量池。常量池就是该类型所用到常量的一个有序集合,包括直接常量(string,integer和 floating point常量)和对其他类型,字段和方法的符号引用。
对于String常量,它的值是在常量池中的。而JVM中的常量池在内存当中是以表的形式存在的, 对于String类型,有一张固定长度CONSTANT_String_info表用来存储文字字符串值,注意:该表只存储文字字符串值,不存储符号引用。常量池中保存着很多String对象; 并且可以被共享使用,因此它提高了效率。
字符串常量池优势:
- 节省内存空间:常量池中所有相同的字符串常量被合并,只占用一个空间。
- 节省运行时间:比较字符串时,==比equals()快。对于两个引用变量,只用==判断引用是否相等,也就可以判断实际值是否相等。
分析如下代码:
// str1指向在常量池中生成一个字符串123
String str1 = "123";
// str2通过new的形式指向了堆中生成的字符串,常量池中已经有123
String str2 = new String("123");
// str3在常量池和堆中各生成一个字符串,自身指向堆中数据
String str3 = new String("456");
常见问题
String s1 = new String(“xyz”); 创建了几个对象?
- 类加载对一个类只会进行一次。”xyz”在类加载时就已经创建并驻留了(如果该类被加载之前已经有”xyz”字符串被驻留过则不需要重复创建用于驻留的”xyz”实例)。驻留的字符串是放在全局共享的字符串常量池中的。
- 在这段代码后续被运行的时候,”xyz”字面量对应的String实例已经固定了,不会再被重复创建。所以这段代码将常量池中的对象复制一份放到heap中,并且把heap中的这个对象的引用交给s1 持有。
- 所以创建2个对象
String真的不能修改吗?
可以用反射完成String修改,参考如下demo:
Field valueField = String.class.getDeclaredField("value");
valueField.setAccessible(true);
char[] value = (char[]) valueField.get(s);
value[5] = '_';
java.lang.String.intern()本质
- String的intern()方法会查找在常量池中是否存在一份equal相等的字符串,如果有则返回该字符串的引用,如果没有则添加自己的字符串进入常量池。