String
类不可变(final),内部维护的 char[] value 数组不可变(final)。
创建
方式一
String str = "abc";
字符串常量池如果有 “abc”,不创建对象;如果没有,在字符串常量池创建"abc"。
注意:字符串常量池逻辑上属于方法区,物理上存在于堆中。
方式二
String str = new String("abc");
字符串常量池如果有 “abc”,创建一个对象在堆;如果没有,在字符串常量池创建"abc",在堆中创建一个对象。
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
源码中,直接共用了一个数组,因为反正 String 都是不可变的。就算你,更改了一个 String,也只是将它指向了另一个对象。
方法三
String a = new String("abc").intern();
String b = new String("abc").intern();
通过前文可以知道,最终会有3个对象,字符串常量池1个,堆2个。
但调用 intern(),返回的是字符串常量池的引用,所以 a==b。
方式四
动态创建:拼接,通过动态创建 char[] 数组等
最大长度
方式一和方式二:String 长度最大 65534,受字符串常量池(65535)和编译器(65534)限制。
方式三:受 String 的 成员变量 value 的限制,它是一个数组,数组的最大长度受 Integer.MAX_VALUE (231-1)限制,所以 String 理论上的最大长度也是 231-1。但实际上,String 的最大长度会相对少一点,Integer.MAX_VALUE - 2 或 2 ^ 31 - 3,不同电脑上可能有差异。
拼接
方式一
String str = "a" + 123 + "b" ;
编译器优化,直接创建 “a123b”
方式二
String str = "a";
str = str + "b" ;
- 创建 “a”
- 创建 StringBuilder
- append(“a”)
- 创建 “b”
- append(“b”)
- toString()
类似于第二行的操作,一行新建一个 StringBuilder;如果实在循环里,一次循环新建一次 StringBuilder
StringBuilder
内部维护 char[] value, 默认初始化数组容量为16。
append
- 如果容量不够,创建新数组
- System.arraycopy,复制旧数据到新数组,令新数组为 value
- 给 value 赋新数据
toString
- 新建 String 对象
- 赋 value 为 StringBuilder 的 value 的拷贝
StringBuffer
内部维护 char[] value, 默认初始化数组容量为16。
内部维护 char[] toStringCache,用于 toString 方法,每次修改 StringBuffer 都会,清空 toStringCache。
与 StringBuilder 拥有共同的父类
与 StringBuilder 相比,大部分方法都采用了 Synchronized 关键字修饰,以此来实现在多线程下的操作字符串的安全性。
toString
@Override
public synchronized String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true);
}
toStringCache 只有在这里才被赋值,也就是说只有调用 toString(),toStringCache 才有值。
使用场景极为有限。
额外说明一点:无论是 String 的 + 拼接,还是 StringBuffer 和 StringBuilder 的 append,都能拼接 null。
public class Test {
public static void main(String[] args) {
String a = null + "a" + 1;
System.out.println(a);
String b = null;
StringBuilder sb = new StringBuilder();
sb.append(b);//sb.append(null);会编译不通过
System.out.println(sb.toString());
}
}
结果