String,StringBuilder,StringBuffer学习
String,StringBuilder,StringBuffer的话题也是老生长谈了。权当记录一下。
1. String相关
- 常量池:在编译期被确定,保存在.class文件中的一些常量,其中就有字符串。
- String不属于Java的基本数据类型。是对象。
- String被创建的时候会先去检查字符常量池里面有没有这个字符串,有的话直接使用,没有的话在字符常量池里面创建一个,再返回地址。
String s1 = "what";
String s2 = "what";
System.out.println(s1 == s2);
上面的代码输出:
true
s1会被创建,s2直接使用s1中的地址。
- 使用new String()的时候会先在对内存中申请一块区域存储对象,再去检查字符常量池是否有这个字符串。
String s1 = "what";
String s2 = new String("what");;
System.out.println(s1 == s2);
输出:
false
- 编译期不能确定的情况
String s1 = "what0";
String s2 = new String("what0");;
int q = 0;
String s3 = "what" + q;
System.out.println(s1 == s2);
System.out.println(s1 == s3);
输出:
false
false
因为q在编译期是无法被确定的所以返回false;
- String的intern方法
public native String intern();
一个本地方法,返回这个String对象在常量池中的引用。
String s1 = "what0";
String s2 = new String("what0");;
int q = 0;
String s3 = "what" + q;
System.out.println(s1 == s2.intern());
System.out.println(s1 == s3.intern());
输出:
true
true
比较的是常量池中的同一个引用,所以都是返回的true
因位字符串一旦改变就会重新创建,所以效率很低。
2. StringBuilder相关
- 类定义
public final class StringBuilder
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
和StringBuffer都继承自AbstractStringBuilder类(String的变换相关的操作都被封装在里面,append,insert等)。同时实现序列化接口和字符序列接口。
- 重要的属性
/**
* The value is used for character storage.
*/
char[] value;
/**
* The count is the number of characters used.
*/
int count;
实际上StringBuilder和StringBuffer都是用char[] 字符数组实现的。
- 关键的构造器
public StringBuilder() {
super(16);
}
public StringBuilder(int capacity) {
super(capacity);
}
public StringBuilder(String str) {
super(str.length() + 16);
append(str);
}
父类对应的构造器:
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
默认的容量是16,也就是能容纳16个字符。同时也可以指定容量。
- 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;
}
调用的是父类的方法,同时其中很重要的就是当容量不够的时候的处理:
private void ensureCapacityInternal(int minimumCapacity) {
// overflow-conscious code
if (minimumCapacity - value.length > 0) {
value = Arrays.copyOf(value,
newCapacity(minimumCapacity));
}
}
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;
}
这里着重关注的当然是newCapacity()方法
int newCapacity = (value.length << 1) + 2;
默认的增长的容量为原容量的2倍加上2
StringBuilder builder = new StringBuilder();
System.out.println(builder.capacity());
builder.append("01234567890123456");
System.out.println(builder.capacity());
输出:
16
34
再看一下下面这一段代码:
StringBuilder builder = new StringBuilder();
System.out.println(builder.capacity());
builder.append("012345678901234");
System.out.println(builder.capacity());
builder.append("01234567890123456789");
System.out.println(builder.capacity());
输出:
16
16
35
这里第二次append的时候并不是直接扩容2倍再+2,而是刚好35。
if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}
原来:newCapacity - minCapacity也就是34 - 35是小于零的所以直接把35作为了新的容量。
所以并不是一定单纯的原来的容量*2 + 2,如果原来的容量*2 + 2还是不够的话就直接使用目标容量。
3. StringBuffer相关
- StringBuffer的实现原理和StringBuilder是基本一样的。只是里面使用了多个synchronized来实现同步而已。但是相对效率上就会低一些。所以当涉及到并发的时候可以使用StringBuffer,单线程情况下StringBuilder效果更佳