String
public final class String
类定义使用了final说明无法被继承。
//1.8版本
/** The value is used for character storage. */
private final char value[];
//1.9版本,将char数组改为byte数组,并且添加coder作为编码标记
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final byte[] value;
/** The identifier of the encoding used to encode the bytes in {@code value}. */
private final byte coder;
}
以下以1.8为准进行讲述:
用private和final修饰char数组,用于存储String的内容。
value是数组引用类型的,所以指向的是堆中的内存。final说明这个数组的指向地址是不会改变。
private说明私有,只能自己使用。
我们都知道string是不可变,那上面的内容该如何解释不可变呢?:
- 保存字符串的数组被
final修饰且为私有的,并且String类没有提供/暴露修改这个字符串的方法。 String类被final修饰导致其不能被继承,进而避免了子类破坏String不可变。
不可变的好处
- 可以存储hash值,String的hash值经常被使用,比如hashmap中的key。String不可变使得对应的hash值也不变,这样只要计算一次就可以了。
- String pool的需要。只有string是不可变的才能是string pool的实现成为可能。
- 安全性。string经常被用来当做参数,string的不变性可以保证参数的内容不会发生改变,提升安全性
- 线程安全:String 不可变性天生具备线程安全,可以在多个线程中安全地使用。
String Pool
在工作中,String类是我们使用频率非常高的一种对象类型。JVM为了提升性能和减少内存开销,避免字符串的重复创建,其维护了一块特殊的内存空间,这个空间就是string pool。它由string私有地维护。
字符串的创建主要有两种方法,一种是直接字面量赋值,一种是new()方法创建string对象。
使用字面量赋值的情况下,jvm会查看string pool中是否有该字面量,如果没有,就在string pool中创建该字符串然后再返回地址,如果有则直接返回地址,所以使用直接赋值创建的string对象会指向同一地址。
使用new()的情况,jvm还是会查看stringpool中是否有该字面量,如果没有,就在stringpool中创建该字面量,然后在堆中再创建一个该字面量的string对象,将堆中的对象地址返回。如果有,就直接在堆中创建返回堆中的地址。
字符串池的优缺点:字符串池的优点就是避免了相同内容的字符串的创建,节省了内存,省去了创建相同字符串的时间,同时提升了性能;另一方面,字符串池的缺点就是牺牲了JVM在常量池中遍历对象所需要的时间,不过其时间成本相比而言比较低。
intern方法使用: 一个初始为空的字符串池,它由类String独自维护。当调用 intern方法时,如果池已经包含一个等于此String对象的字符串(用equals(oject)方法确定),则返回池中的字符串。否则,将此String对象添加到池中,并返回此String对象的引用。 对于任意两个字符串s和t,当且仅当s.equals(t)为true时,s.instan() == t.instan才为true。所有字面值字符串和字符串赋值常量表达式都使用 intern方法进行操作。
GC回收: 字符串池中维护了共享的字符串对象,这些字符串不会被垃圾收集器回收。
stringbuilder 和 stringbuffer
类定义
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
从上可以看到两者都继承了相同的类AbstractStringBuilder和相同的接口,所以两者的主要区别就在于实现上。
AbstractStringBuilder类中有数组的定义。
char[] value;
用stringbulider来说明AbstractStringBuilder
stringbulider的构造函数大同小异,传入的参数有空值,string、char的相关类和int数据,传入int数据是为了初始化父类value数组的大小。别的都会在传入参数的长度基础上加16位预置空间(无参就是0)。之后如果有参就调用append方法将参数写入。如:
public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}
public StringBuilder append(StringBuffer sb) {
super.append(sb);
return this;
}
append方法只有参数不同,其实现格式都一样。调用父类对应方法,返回。 那接下来看看父类的一个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;
}
其中ensureCapacityInternal实现了value数组的扩容。
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
str.getChars(0, len, value, count);这一步实现了内容的复制。
public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {
if (srcBegin < 0) {
throw new StringIndexOutOfBoundsException(srcBegin);
}
if (srcEnd > value.length) {
throw new StringIndexOutOfBoundsException(srcEnd);
}
if (srcBegin > srcEnd) {
throw new StringIndexOutOfBoundsException(srcEnd - srcBegin);
}
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
}
所以AbstractStringBuilder类提供了可以改变的字符数组和一些可以编辑数组的方法,从而实现了这两个类的内容可变。
stringbuffer的实现也是一样。那这两者有什么不同? 从stringbuilder中可以找到下面的这一句话:
This class provides an API compatible
* with {@code StringBuffer}, but with no guarantee of synchronization.
也就是stringbuilder没有使用synchronization。而stringbuffer中使用了synchronize对方法加锁。 同样的append:
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
三者性能差别
每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
对于三者使用的总结:
- 操作少量的数据: 适用
String - 单线程操作字符串缓冲区下操作大量数据: 适用
StringBuilder - 多线程操作字符串缓冲区下操作大量数据: 适用
StringBuffer