1.String
1.1 String不变性原理
我们知道被 final 关键字修饰的类不能被继承,修饰的方法不能被重写,修饰的变量是基本数据类型则值不能改变,修饰的变量是引用类型则不能再指向其他对象。因此,final 关键字修饰的数组保存字符串并不是 String 不可变的根本原因,因为这个数组保存的字符串是可变的(final 修饰引用类型变量的情况)。
String 真正不可变有下面几点原因:
- 保存字符串的数组被 final 修饰且为私有的,并且String 类没有提供/暴露修改这个字符串的方法。
- String 类被 final 修饰导致其不能被继承,进而避免了子类破坏 String 不可变。
String类源码:
1.2 String对象和常量池
String s1 = "Aismy"; // String 直接创建(常量池)
String s2 = "Aismy"; // String 直接创建(常量池)
String s3 = s1; // 相同引用
String s4 = new String("Aismy"); // String 对象创建
String s5 = new String("Aismy"); // String 对象创建
System.out.println(s4==s5); //比较的是引用的地址
System.out.println(s1==s2); //s1和s2指向同一地址,因此相等
System.out.println(s4.equals(s5)); //因为String重写equals方法,因此比较的是两个对象的值是否相等
回顾==与equls
- 对于基本变量比较的是值是否相等
- 对于引用对象比较的是地址是否相等
- 在String中重写了equls,若不重写则和==效果一样;
2.StringBuffer 和 StringBuilder 类
当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。
和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。
在使用 StringBuffer 类时,每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象,所以如果需要对字符串进行修改推荐使用 StringBuffer。
StringBuilder 类在 Java 5 中被提出,它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问)。
由于 StringBuilder 相较于 StringBuffer 有速度优势,所以多数情况下建议使用 StringBuilder 类。
2.1StringBuffer构造器:
构造一个字符串缓冲区,其中没有字符,初始容量为 16 个字符
public StringBuffer() {
super(16);
}
构造一个没有字符且具有指定初始容量的字符串缓冲区。
参数:
容量——初始容量
public StringBuffer(int capacity) {
super(capacity);
}
构造一个字符串缓冲区,初始化为指定字符串的内容。字符串缓冲区的初始容量是16加上字符串参数的长度。
参数:
str - 缓冲区的初始内容
public StringBuffer(String str) {
super(str.length() + 16);
append(str);
}
构造一个字符串缓冲区,其中包含与指定CharSequence相同的字符。字符串缓冲区的初始容量是16加上CharSequence参数的长度。
如果指定的CharSequence的长度小于或等于 0,则返回容量为16的空缓冲区。
参数:
seq - 要复制的序列。
public StringBuffer(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}
2.2 StringBuffer 线程安全原因
StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。
@Override
public synchronized StringBuffer append(Object obj) {
toStringCache = null;
super.append(String.valueOf(obj));
return this;
}
3.总结
每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
- 操作少量的数据: 适用 String
- 单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder
- 多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer
4.相关面试题
4.1字符串拼接用“+” 还是 StringBuilder?
Java 语言本身并不支持运算符重载,“+”和“+=”是专门为 String 类重载过的运算符,也是 Java 中仅有的两个重载过的元素符。
String str1 = "he";
String str2 = "llo";
String str3 = "world";
String str4 = str1 + str2 + str3;
对象引用和“+”的字符串拼接方式,实际上是通过 StringBuilder 调用 append() 方法实现的,拼接完成之后调用 toString() 得到一个 String 对象 。
不过,在循环内使用“+”进行字符串的拼接的话,存在比较明显的缺陷:编译器不会创建单个 StringBuilder 以复用,会导致创建过多的 StringBuilder 对象。
String[] arr = {"he", "llo", "world"};
String s = "";
for (int i = 0; i < arr.length; i++) {
s += arr[i];
}
System.out.println(s);
StringBuilder 对象是在循环内部被创建的,这意味着每循环一次就会创建一个 StringBuilder 对象。
因此在循环中拼接字符串使用StringBuilder 会避免这个问题
4.2String#equals() 和 Object#equals() 有何区别?
String 中的 equals 方法是被重写过的,比较的是 String 字符串的值是否相等。 Object 的 equals 方法是比较的对象的内存地址。
4.3字符串常量池的作用了解吗?
字符串常量池 是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。
JDK1.7 之前运行时常量池逻辑包含字符串常量池存放在方法区。JDK1.7 的时候,字符串常量池被从方法区拿到了堆中。