字符串是Java开发中使用最多的数据类型之一,但不属于8种基本数据类型,属于对象。了解字符串的使用原理有助于写出高质量的代码。
源码String类在JDK的发展过程中也不是一成不变的,其中JDK1.8升级至JDK1.9时变化稍微大一些。
JDK1.8
public final class String
implements java.io.Serializable, Comparable, CharSequence
{
private final char value[];
...
}
JDK1.9+
public final class String
implements java.io.Serializable, Comparable, CharSequence,
Constable, ConstantDesc {
@Stable
private final byte[] value;
private final byte coder;
...
static final boolean COMPACT_STRINGS;
static {
COMPACT_STRINGS = true;
}
}
- 比较发现String存储由原来的char value[ ]变成了byte[ ] value,这是因为字符串中拉丁字母居多,使用char存储时占用两个字节,大多数情况下会造成一个字节的浪费,空间浪费增加GC被触发的概率,而byte只占用一个字节,会减少空间浪费。若是存储汉字会用到两个字节,此时字节码标识符coder和字符串压缩COMPACT_STRINGS就发挥重要作用。String类是final类,不能被继承。存储使用char value[ ]或byte[ ] value也是final,也就是String一旦被创建就无法修改,所有改变属性的操作均会产生新的对象。Java中String使用最频繁,String创建后一般会保存在堆中,而堆是可以被多线程共享的,这样做不安全,final则解决了安全问题。基本数据类型的包装对象和String类一样,都是不可修改的。
常量池主要分为:class常量池、运行时常量池。使用字符串时编译时,首先会将字符串放在字符串常量池中,但是字符串常量池只是民间的一种说法,官方文档中没有,具体存储的位置有可能在常量池中,也有可能在堆中。String在使用时,首先会把字符串放入字符串常量池中,若是字面量,则直接使用,若是创建对象,则返回堆中对象引用。
// 字面量
String str = "abc";
代码编译时会直接在常量池中创建"abc",运行时会直接从字符串常量池中返回"abc"的引用。
字面量使用简单,不用在堆中创建对象,直接返回引用,推荐使用该方法使用字符串。
// 创建对象
String str = new String("abc");
使用new创建字符串对象时,一般分两步:
1,代码编译时,在字符串常量池中创建"abc"’;
2,使用new时,在堆中创建str对象,并引用字符串常量池的"abc",并返回str的引用。
虽然所使用字面量和使用new创建对象效果相同:
但是使用new需要在堆中创建str对象并返回对象引用,有点浪费空间,一般没有特别要求,不建议使用该方法创建字符串。
优化 为什么要使用字符串常量池使用new创建字符串对象。
若是使用new创建String对象,虽str1 != str2,但是两者都指向字符串常量池中的"abc"。如何验证,调用intern()可直接获取字符串常量池内容。
// 创建对象
String str1 = new String("abc");
String str2 = new String("abc");
System.out.println(str1 == str2);
System.out.println(str1.intern() == str2.intern());
结果:
说明str1和str2是两个对象,但两者字符串引用相同。
若是知道要使用的字符串在字符串常量池中已经存在,可以调用intern()直接从字符串常量池中取值再赋值。
// 创建对象
String str2 = new String("abc");
String str5 = str2.intern();
System.out.println(str5);
结果:
abc
使用字面量同样引用同一块"abc"。
// 字面量
String str = "abc";
String str1 = "abc";
System.out.println(str == str1);
结果:
说明:
说明若是内容相同,则后创建的对象会直接使用常量池中的内容。
总而言之,字符串常量池不管有没有这个说法,但是若内容字符串已存在,后续使用则不会创建新对象,会直接取已存在的字符串,这样做效率更高,节省资源,毕竟String使用频率如此之高。
// 纯字符穿拼接
String strPlus = "a" + "b" + "c";
像代码中纯字符串拼接的情况不会出现在理智的代码中,但是出现了也不会有影响,编译器会自动优化成:
// 纯字符穿拼接
String strPlus = "abc";
验证:
// 字面量
String str = "abc";
// 字符穿拼接
String strPlus = "a" + "b" + "c";
System.out.println(str == strPlus);
结果:
// 多字符串拼接
String strAdd = new String();
for (int i = 0; i < 1000; i++) {
strAdd = strAdd + i;
}
上述代码会在编译时自动优化成:
// 多字符串拼接自动优化
for (int i = 0; i < 1000; i++) {
strAdd = new StringBuilder(strAdd).append(i).toString();
}
这样会减少对象的创建,提高拼接速度。若是需要拼接的字符串比较少,还是建议使用"+"进行拼接,此时拼接效率要比StringBuilder拼接效率高。
若是字符串拼接有关于其他对象的引用,则编译器不会做优化。



