此篇博客的所有源码都基于JDK1.8
String类代表字符串。其类名被final修饰,所以不能被继承,方法不能被重写。使该类具有不变性。
String类的部分源码:
public final class String implements Serializable, Comparable<String>, CharSequence {
private final char[] value;
public String() {this.value = "".value; }
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
}
可以看到,字符串的实质是一个字符数组。他被private final修饰,之所以称字符串具有不变性,就是因为他的访问权限是private的,外部不能访问,又被final修饰,所以可看做具有不变性。但真正具有不变性的不是字符数组,而是指向该数组的引用value。
证明:被fianl修饰的变量真正具不可变性的是变量的引用。而不是变量本身。
public class RealSituation {
final int[] array1 = {2,5,3};
int[] array2 = {23,21,20};
public void destroy() {
// 编译警告,被final修饰的引用具有不变性。
//array1 = array2;
// 引用指向的对象具有可变性
array1[0] = 22;
}
public static void main(String[] args) {
RealSituation real = new RealSituation();
System.out.println("数组未被改变之前:");
for (int i = 0; i < real.array1.length; i++) {
System.out.print(real.array1[i] + " ");
}
real.destroy();
System.out.println("\n数组被破坏:");
for (int i = 0; i < real.array1.length; i++) {
System.out.print(real.array1[i] + " ");
}
}
}
数组未被改变之前:
2 5 3
数组被破坏:
22 5 3
可以看到,数组被改变了,但其引用被final修饰不能被重新赋值,而String类并没有提供改变数组元素的API,所以String类型的字符串具有不可变性。
String类常用的API:
String replace(CharSequence target, CharSequence replacement) ;字符串部分替换
String substring(int beginIndex, int endIndex) ; 字符串截取
String trim() ; 去掉字符串中的前后空格
注意:这些方法返回的都是新的String对象,并不是对原String对象修改。使用时要小心。
常出的面试题:
public class StringTest {
public static void main(String[] args) {
String str1 = "hello";
String str2 = new String("hello");
String str3 = "hello";
System.out.println(str1 == str2); // == 比较内存地址
System.out.println(str1 == str3);
System.out.println(str1.equals(str2));
}
}
运行结果:
false
true
true
String str1 = “hello”; 是由编译器创建的String对象,该对象存储在常量池中,当执行String str3 = “hello”;时,JVM会在常量池中寻找是否有 "hello"字符串,如果常量池中存在此字符串,则直接将字符串 "hello"的地址赋给str3。所以str1和str3的内存地址相等。由于str2是通过new创建的字符串,通过关键字new创建的对象存储在堆中,所以str2和str1、str2的地址不同。
String类重写了Object类的equals方法。比较的是String类中封装的字符数组的内容。所以str1.equals(str2)的结果为true。
这2个类都继承自AbstractStringBuilder。实现基本相同。区别是StringBuffer是线程安全的,部分方法被关键字synchronized修饰,执行速度慢与StringBuilder,但线程安全。
StringBuffer和StringBuilder的扩容机制。(这里以StringBuffer为例)
StringBuffer的字符数组初始容量为16,在以String类和CharSequence为参数实例化的情况下,计算String和CharSequence的长度,在此长度上加上16,即为StringBuffer的初始容量。
public StringBuffer() {
super(16);
}
public StringBuffer(int var1) {
super(var1);
}
public StringBuffer(String var1) {
super(var1.length() + 16);
this.append(var1);
}
public StringBuffer(CharSequence var1) {
this(var1.length() + 16);
this.append(var1);
}
append方法的实现稍微有些复杂。
char[] value; // 存储元素的字符数组
int count; // 字符数组中元素的个数 - 1
public synchronized StringBuffer append(String var1) {
this.toStringCache = null;
super.append(var1);
return this;
}
// 调用父类AbstractStringBuilder的append方法。
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
int len = str.length();
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
// 参数字符串为null的情况。可以看到,即使参数为空,value数组还是会存储一个“null"字符串,判断是否需要扩容,这应该注意。
private AbstractStringBuilder appendNull() {
int c = count;
ensureCapacityInternal(c + 4);
final char[] value = this.value;
value[c++] = 'n';
value[c++] = 'u';
value[c++] = 'l';
value[c++] = 'l';
count = c;
return this;
}
// 判断数组空间是否溢出,如果溢出,进行扩容。
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
// 扩容算法 原数组长度左偏移1位 + 2, 。满足条件则扩容到此大小,如不满足则直接将数组扩容到minCapacity或最大MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8,否则抛出异常
private int newCapacity(int minCapacity) {
// overflow-conscious code
int newCapacity = (value.length << 1) + 2;
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
return (newCapacity <= 0 || MAX_ARRAY_SIZE - newCapacity < 0)
? hugeCapacity(minCapacity)
: newCapacity;
}
private int hugeCapacity(int minCapacity) {
if (Integer.MAX_VALUE - minCapacity < 0) { // overflow
throw new OutOfMemoryError();
}
return (minCapacity > MAX_ARRAY_SIZE)
? minCapacity : MAX_ARRAY_SIZE;
}
String、StringBuffer、StringBuilder的区别: