1.基本性质
String 类被声明为 final,不可继承,实现了序列化,比较和字符数组的接口,JAVA8 使用 char 数组存储数据(JAVA9 改为了 byte 数组),且被 final 修饰符修饰,String 对象一旦创建,不可修改。
JAVA9 从 char[] 修改为 byte[] 的考虑:堆中字符串是主要的组成部分,char 数组每个字符使用 2 个字节(16位,使用 UTF-16 编码),byte 数组每个字符使用 1 个字节(8位,使用 Latin-1 编码),而 String 对象大多是拉丁字符,一个字节足够表达,同时增加了一个编码标志位 coder 用来表明是哪种编码,java 会根据字符串内容自动设置编码。
但对于中文字符来说,还是要用 utf16 编码,没有节省空间。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
private final char value[];
}
这些设计都为了保障 String 的不变性,不可变性的优势:
- hash 在很多数据结构中经常被使用,例如 HashMap 的 key,不可变使得 hash 值可以被缓存,只需要计算一次;
- 高频使用的字符串可以缓存到 String Pool 中,提高效率;
- 作为参数,不可变能够预防异常,例如网络连接场景,String 被改变后 host 可能转换,很难排查;
- 天然支持线程安全的需求,支持并发场景;
构建方法
public String(){} // 缺省用空字符串占位初始化
public String(String original) {} // 用传入 original 的值初始化 value 和 hash
public String(char value[]){} // 拷贝 value 数组的值初始化
public String(char value[], int offset, int count){} // 用传入 value 数组的一部分初始化
public String(int[] codePoints, int offset, int count) {} // 用 unicode code points 数组的一部分初始化
常用方法,原生 String 对象提供了 isEmpty, length, charAt, equals, startsWith, concat, toLowerCase, toUpperCase
等方法,也经常使用封装库 org.apache.commons.lang3.StringUtils 提供的 isAnyBlank, isAllBlank
等方法,和 org.springframework.util.StringUtils 提供的其他方法。
2.字符串常量池
为了减少创建的字符串数量,JVM 维护了一个字符串常量池(称为 StringTable 或 String Intern Pool),String.intern() 可以保障相同内容的字符串变量引用同一个内存对象。
// 使用 String new 对象的方式需要手动触发 intern 才会放到字符串常量池
String s1 = new String("a");
String s2 = new String("a");
System.out.println(s1 == s2); // false,没有使用字符串常量池
String s3 = s1.intern();
String s4 = s1.intern();
System.out.println(s1 == s3); // false,仅 s3 使用字符串常量池
System.out.println(s3 == s4); // true,s3 和 s4 都使用了字符串常量池
// 双引号创建会自动使用字符串常量池
String s11 = "b";
String s12 = "b";
System.out.println(s11 == s12); // true
字符串常量池是一个固定大小的 HashTable,放入的 String 过多时会出现大量 hash 冲突,造成链表增长效率降低。可以通过 -XX:StringTableSize 设置长度,JAVA8 的缺省值是 60013。
3.构建 String
String 的不可变性带来了上述的诸多好处,但实际运行中还是有大量字符串修改的需求,于是有了 StringBuffer 和 StringBuilder 来构建字符串。
- StringBuffer 线程安全,使用了
synchronized同步,效率较低。String 在修改时调用了 StringBuffer 的 append 方法,完成后调用 toString() 返回。
String str = "hi";
str += "haha";
// 等价于
StringBuffer sb = new StringBuffer(str);
sb.append("haha");
str = sb.toString();
- StringBuilder 线程不安全,效率较高,无并发情况下适合使用。
4.总结
String 设计成不可继承不可修改的特性,有字符串常量池设计,提高运行效率。构建字符串时,如果非并发情况使用 StringBuilder,较小字符串构建使用 String,并发修改使用 StringBuffer。