String 被声明为 final,因此它不可被继承,它们的值在创建之后不能修改。
1、String 源码片段
java.lang.String
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** String的属性值 */
private final char value[];
/**数组被使用的开始位置**/
private final int offset;
/**String中元素的个数**/
private final int count;
/**String类型的hash值**/
private int hash; // Default to 0
......
从源码看出:
- String 底层使用一个字符数组来维护(Java8)。Java9 后使用 byte 数据,可参考 Java9后String的空间优化 。
- value 数组被 final 修饰,意味着 value 数组初始化之后就不能再引用其它数组。并且 String 内部没有改变 value 数组的方法,从而保证 String 不可变。
2、创建字符串对象两种方式
- 直接赋值创建的对象是存放在方法区的常量池中。
String str="hello";
- 通过构造方法创建的对象存放在堆内存中。
String str=new String("hello");
创建字符串的练习:
1、 String str = "123";
如上:jvm在编译阶段会判断常量池中是否有 “123” 字面量。若有,str 直接指向这个字面量的引用,如果没有会在常量池里创建这个字面量。
2、 String str = new String("123");
如上:jvm 编译时期判断常量池中 “123” 是否存在,从而判断是否创建字面量,然后运行时期通过 new 关键字在 java heap 中创建 String 对象。
String a = "a";
String b = "b";
String str = "ab";
3、 System.out.println(str == "a" + "b"); // true
4、 System.out.println(ab == a + b); // false
如3:字面量"+"拼接是在编译期间进行的。jvm编译阶段过编译器会把字符串常量直接合并成 “ab”,所以创建对象时可能会在常量池中创建1个对象。
字面量"+“拼接是在编译期间进行的。jvm编译阶段过编译器会把字符串常量直接合并成"ab”,所以创建对象时可能会在常量池中创建1个对象。
字符串引用的"+"拼接运算实在运行时进行的,新创建的字符串存放在堆中。
3、字符串常量池
字符串常量池(String Pool)保存所有字符串字面量(literal strings),这些字面量在编译时期就确定。
可以使用 String 的 intern() 方法在运行过程中将字符串添加到 String Pool 中。
String s1 = new String("intern test");
String s2 = new String("intern test");
System.out.println(s1 == s2); // false
String s3 = s1.intern();
String s4 = s1.intern();
System.out.println(s3 == s4); // true
如上:当一个字符串调用 intern() 时,如果 String Pool 中已经存在一个字符串和该字符串值相等(使用 equals() 方法确定),那么就会返回 String Pool 中字符串的引用;否则,会在 String Pool 中添加一个新的字符串,并返回这个新字符串的引用。
4、String, StringBuffer & StringBuilder
基本区别:
- String 不可变,因此是线程安全的
- StringBuilder 可变,不是线程安全的
- StringBuffer 可变,是线程安全的,内部使用 synchronized 进行同步
使用场景:
-
在字符串不经常发生变化的业务场景优先使用 String。(如常量的声明,少量的字符串操作)
-
在单线程情况下,若有大量的字符串操作情况,应使用 StringBuilder 来操作字符串。使用 String"+" 来拼接会产生大量无用的中间对象,耗费空间且执行效率低下。(如封装 JSON 数据)
-
在多线程情况下,若有大量的字符串操作情况,应使用 StringBuffer。(如 HTTP 参数解析和封装)