String类
常用构造方法
1.无参构造
public String() {
this.value = "".value;
}
2.常用的有参构造
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
public String(byte bytes[]) {
this(bytes, 0, bytes.length);
}
public String(StringBuilder builder) {
this.value = Arrays.copyOf(builder.getValue(), builder.length());
}
public String(StringBuffer buffer) {
synchronized(buffer) {
this.value = Arrays.copyOf(buffer.getValue(), buffer.length());
}
}
String可以通过传入byte数组和char数组来创建String类型对象,也可以在其中指定要传入的byte数组和char数组的起始位置和长度
String还可以通过传入StringBuffer和StringBuilder对象去创建String类型的对象
3.通过字面量定义的方式
String str = "123";
String也可以通过字面量的定义方式去创建,这种情况创建的String会在字符串常量池中存储(区别于new给一个字符串赋值,此时的字符串值声明在字符串常量池中)
String类的特点
1.底层为char数组
jdk1.8之后为byte数组
2.内容不可变
首先,从上图可以看到String内部用 final 修饰 char 数组,这个char数组就是String在底层的存储方式。final修饰的引用数据类型的地址值是不可变的,即使该char数组的内容可以被修改,但由于String类也是final修饰的,所以该类不可被继承的,并且,Java 作者在 String 的所有方法里都避免去修改 char 数组中的数据,涉及到对 char 数组中数据进行修改的操作全部都会重新创建一个 String 对象
3.内容可以共享
String内容可以共享的原因是String有常量池,通过字面量定义的方式生成的String会在字符串常量池中存储,而字符串常量池中是不会存储相同内容(使用String类的equals()比较,返回true)的字符串的。
字符串常量池:
如果是new出来的对象,则在堆中开辟空间(与常量池无关)
如果是直接用字面量定义的方式生成的,则存在常量池中,可以共享(使用==比较时地址值相同)
StringBuilder
常用构造方法
//构造一个不带任何字符的字符串生成器,其初始容量为 16 个字符
public StringBuilder() {
super(16);
}
//构造一个不带任何字符的字符串生成器,其初始容量由 capacity 参数指定
public StringBuilder(int capacity) {
super(capacity);
}
//构造一个字符串生成器,并初始化为指定的字符串内容
public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}
//构造一个字符串生成器,它包含与指定的 CharSequence 相同的字符
public StringBuilder(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}
底层结构
Stringbuilder和Stringbuffer继承自同一个顶层类AbstractStringBuilder,所以成员变量不是finall修饰的
相比于String类,StringBuilder底层实现也是char数组,但是它并没有用final修饰,这意味着它的长度是可变的
因此,StringBuilder在追加字符串时,将不再去创建新的对象
扩容机制
如果我们用无参的构造方法去创建一个StringBuilder对象
//StringBuilder的无参构造
public StringBuilder() {
super(16); //调用了父类的无参构造方法
}
//父类的方法
AbstractStringBuilder(int capacity) {
value = new char[capacity]; //将数组长度设置为16
}
我们可以看到StringBuilder将 16 传入到了父类方法中并创建了一个长度为16的char数组,名字叫value
当我们通过append方法去添加元素时
@Override
public StringBuilder append(Object obj) {
return append(String.valueOf(obj));
}
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
public AbstractStringBuilder append(String str) {
if (str == null) //当添加的字符串为空时
return appendNull(); //进入该方法
int len = str.length(); //不为空时,用len变量来承接字符串的长度
ensureCapacityInternal(count + len);
str.getChars(0, len, value, count);
count += len;
return this;
}
由上面的源码我们可以知道,分两种情况
1.当添加的字符串为 null 时候则会执行以下内容,即将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;
}
2.当添加的字符串不为null时则执行:
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
将添加的字符串的长度+ 原数组元素的个数(此时为第一次添加,原数组元素个数为0,以下将字符串长度即说明为两者之和)和value[ ]的长度作比较
①如果添加的字符串长度小于初始化数组的长度。(即小于16)。直接要添加的字符串添加入数组中。
②如果添加的字符串长度大于当前数组的长度(16)则会执行以下方法
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;
}
进行数组长度扩容
由代码value.length << 1 + 2可以看到,先将 数组长度左移1位,再+2。即将初始的数组长度✖2 +2(即位34)。扩容后再与要添加的字符串长度相比较
如果新扩容的数组长度小于要添加的字符串的长度。则将要添加的字符串长度直接当作数组新长度。newCapacity = minCapacity;
最后对这个新长度做个判断,如果该长度小于0或者该长度大于最大限制长度,否则返回该新长度,将添加的字符串逐个添加进数组元素中,并返回copyOf[ ]
其他
jdk1.8以后,String的“+”拼接底层优化了,也用的StringBuilder,但String“+”拼接的每次“=”赋值,都会创建一个StringBuilder对象,因此多次拼接建议用StringBuilder
String“+”操作拼接常量时,在javac时就已经完成了拼接,不会进行StringBuilder
StringBuffer
常用构造方法
//构造一个其中不带字符的字符串缓冲区,其初始容量为 16 个字符。
public StringBuffer() {
super(16);
}
//构造一个不带字符,但具有指定初始容量的字符串缓冲区。即对 char[ ] 大小进行指定
public StringBuffer(int capacity) {
super(capacity);
}
//构造一个字符串缓冲区,并将其内容初始化为指定的字符串内容。
public StringBuffer(String str) {
super(str.length() + 16);
append(str);
}
//构造一个字符串生成器,它包含与指定的 CharSequence 相同的字符
public StringBuffer(CharSequence seq) {
this(seq.length() + 16);
append(seq);
}
将StringBuffer与StringBuilder构造方法对比,我们不难发现他们是相似的
底层结构
与StringBuilder类似,继承自同一个父类 AbstractStringBuilder
与StringBuilder不同的是,StringBuffer的方法中添加了 synchronized 锁,可以保证其线程安全,其余的实现逻辑与StringBuilder相同
StringBuilder 与 String、StringBuffer的比较
同属于java.lang包下
String :jdk1.0,不可变,效率低,但是复用率高。(即一个常量池中不能创建两个一样的字符串常量)
StringBuilder与StringBuffer 非常类似,均代表可变的字符序列,其方法也一样
StringBuffer :jdk1.0,可变字符序列,效率较高(增删)、线程安全
StringBuilder :jdk1.5,可变字符序列,效率最高、线程不安全