一.简介:
- 用来处理字符串常用的类有3种:String、StringBuffer和StringBuilder
- String是不可变字符串,字符串的值一旦确定,则不可修改(指内存种的值不可修改)
- 频繁对String进行修改,会在内存种产生很多对象,垃圾数据,特别是当内存种引用的对象多了以后,JVM的GC机制就会开始工作,性能就会降低
- String类种提供了很多方法,但没有直接增删改等方法
- 如果在程序种对字符串进行频繁修改,建议使用StringBuffer和StringBuilder
二.String、StringBuffer和StringBuilder的区别
1.String
- 不可变类,属性value为不可变数组,定义多少,String中字符数组的长度就是多少,不存在字符数组拓容一说。
- String类源码
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
- final修饰的String类,以及final修饰的char[] value表示String类不可被继承,不能有子类,且value只能被初始化一次。这里的value变量其实就是存储了String字符串中的所有字符。
- 那既然String不可变。我们再看一下String类的截取字符串的方法subString()
- 源码:
public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
- 可以看到,在subString方法中,如果传入放入参数为0,就返回自身原对象,否则就是重新创建一个新的对象。
- 类似的可以看到,String类中的concat方法,replace方法,都是内部重新生成一个String对象
- 这就是为什么我们采用String对象频繁的进行拼接,截取,替换操作效率很低下的原因。
2.StringBuffer
- 内部可变数组,存在初始化StringBuilder对象中字符数组容量为16,存在拓容。
- StringBuffer类源码:
public final class StringBuffer
extends AbstractStringBuilder
implements java.io.Serializable, CharSequence
{
/**
* A cache of the last value returned by toString. Cleared
* whenever the StringBuffer is modified.
*/
private transient char[] toStringCache;
/** use serialVersionUID from JDK 1.0.2 for interoperability */
static final long serialVersionUID = 3388685877147921107L;
/**
* Constructs a string buffer with no characters in it and an
* initial capacity of 16 characters.
*/
public StringBuffer() {
super(16);
}
/**
* Constructs a string buffer with no characters in it and
* the specified initial capacity.
*
* @param capacity the initial capacity.
* @exception NegativeArraySizeException if the {@code capacity}
* argument is less than {@code 0}.
*/
public StringBuffer(int capacity) {
super(capacity);
}
/**
* Constructs a string buffer initialized to the contents of the
* specified string. The initial capacity of the string buffer is
* {@code 16} plus the length of the string argument.
*
* @param str the initial contents of the buffer.
*/
public StringBuffer(String str) {
super(str.length() + 16);
append(str);
}
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
- 抽象类AbstractStringBuilder源码:
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
- StringBuffer类继承AbstractStringBuilder抽象类,其中StringBuffer的大部分方法都是直接调用的是父类的实现
- 第一个空参数的构造方法,调用父类(AbstractStringBuilder)的构造,默认字符数组的初始大小为16
- 第二个自定义初始char[] 容量大小
- 第三个以字符串String作为参数的构造。
- 第四个在参数str数组长度的基础上再增加16个字符长度,作为StringBuffer实例的初始化数组容量,并将str字符串append到StringBuffer的数组中
注意:
可以看到这里append方法用synchronized(同步) 修饰了,加了同步锁,来实现多线程下的线程安全
其他的和StringBuilder一致
- 具体看下父类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;
}
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));
}
}
- 示例:
public class StringBuffer的append测试 {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer();
String s = null; //没有分配空间
String s2 = "ccc";
sb.append("aaa");
sb.append(s);
sb.append(s2);
sb.append("bbb");
System.out.println(sb);//aaanullcccbbb
}
}
- append方法
- 该方法的作用是追加内容到当前的StringBuffer对象的末尾,类似于字符串的连接,调用该方法以后,StringBuffer对象的内容也发生改变
- 使用该方法继续宁字符串的连接,将比String更加节约内容,经常应用于数据库SQL语句的连接,比如项目中dao层写的SQL语句用append方法进行追加。
- 可以看到,首先判断append的参数str是否为null,如果为null的话,这里也是可以append进去的
- 其中ensureCapacityInternal方法是确保这次append的时候StringBuffer的内部数组容量是满足的,即这次要append的null字符长度为4,加上之前内部数组中已有的字符位数c之后作为参数执行。
- 如果不为null的话,就获取这次需要append的str字符长度。紧接着执行是否需要拓容的方法
- 即整个StringBuffer的append方法,直接将String类型的str字符串中的字符数组,拷贝到了StringBuffer的字符数组中
- 图示:
/**
* A cache of the last value returned by toString. Cleared
* whenever the StringBuffer is modified.
*/
private transient char[] toStringCache;
- 这里比StringBuilder多一个参数toStringCache,就是去缓存toString的,可以看下StringBuffer的toString方法
@Override
public synchronized String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true);
}
- 这里的作用就是如果StringBuffer对象此时存在toStringCache,在多次调用其toString方法时,其new出来的String对象是会共享同一个char[]内存的,达到共享的目的。但是StringBuffer只要做了修改,其toStringCache属性都会置null处理。这也是StringBuffer和StringBuilder的一个区别。
3.StringBuilder
- 其他的都是一样,不一样的地方有append方法前面没有synchronized(同步)修饰
- 还有一个不一样的是StringBuilder的toString方法
- 源码:
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
- 这里的toString方法直接new一个String对象,将StringBuilder对象的value进行一个拷贝,重新生成一个对象,不共享之前的StringBuilder的char[]
三.String、StringBuffer和StringBuilder性能对比
public class String和StringBuffer和StringBuilder性能对比 {
public static void main(String[] args) {
test_String();
test_StringBuffer();
test_StringBuilder();
}
public static void test_String(){
String str = "";
long start = System.currentTimeMillis();
for (int i = 0;i < 10000; i++){
str += "hello";
}
long end = System.currentTimeMillis();
System.out.println("String花费了"+(end-start)+"毫秒");
}
public static void test_StringBuffer(){
StringBuffer sb = new StringBuffer();
long start = System.currentTimeMillis();
for (int i = 0;i < 10000; i++){
sb.append("hello");
}
long end = System.currentTimeMillis();
System.out.println("StringBuffer花费了"+(end-start)+"毫秒");
}
public static void test_StringBuilder(){
StringBuilder sb = new StringBuilder();
long start = System.currentTimeMillis();
for (int i = 0;i < 10000; i++){
sb.append("hello");
}
long end = System.currentTimeMillis();
System.out.println("StringBuilder花费了"+(end-start)+"毫秒");
}
}
- 输出为:
String花费了442毫秒
StringBuffer花费了2毫秒
StringBuilder花费了0毫秒
- 从上面的结果可以看出:不考虑多线程,采用String对象,执行时间比其他两个都要高,而采用StringBuffer对象和采用StringBuilder对象的差别也比较明显。由此可见,如果我们的程序员是在单线程下运行,或者是不必考虑到线程同步问题,我们应该优先使用StringBuilder类。如果要保证线程安全,或者在现实的模块化编程中,负责某一模块的程序员不一定能清晰的判断该模块是否会放入多线程的环境中运行,自然是StringBuffer。
四.总结:
- StringBuffer
- 线程安全的(同步–如果多个线程同时访问 — 排队一个一个访问),多用于多线程
- 效率低
- StringBuilder
- 线程不安全(异步–如果多个线程同时访问 — 一起访问),多用於单线程
- 效率高
如果对你有帮助,点个赞吧0.0
若有不正之处,请多多谅解并欢迎批评指正,不甚感激