Java 基础 之 String
在 Java 中字符串的使用非常广泛, 我们常用的如: String ,StringBuffer, StringBuilder. 但是一直都没有系统的整理过他们的区别, 今天就系统的整理一下, 记录自己的学习历程.
一: String
首先我们看下 String 的源码:
public final class String implementsjava.io.Serializable, Comparable, CharSequence
从 String 的类声明上就可以发现, String 是一个类而不是基本数据类型.
既然是一个类, 那 String 的使用也很简单, 直接 new 就可以了.
String 可以通过 "+" 来完成字符串的拼接, 那我们来做个测试:
使用是很简单, 但是通过我们上面的测试发现一个问题就是, 当我们 给 str + "test" 后, 他的 hashCode 发生了变化, 说明了第 10 行的 str 已经是一个新的对象了.
在 String 的源码中维护了一个 finalchar value[], 如下:
private final charvalue[];
所以从源码中我们可以发现, value 是 final 类型的, 是不能被改变的, 所以只要改变就只能生一个新的 String 对象. 也可以说 String 类型的对象是长度不可变的, String 拼接字符串每次都要生成一个新的对象, 所以拼接字符串的效率也比较低.
二: StringBuffer
首先我们看下 StringBuffer 的源码:publicfinalclassStringBufferextendsAbstractStringBuilder
implementsjava.io.Serializable,CharSequence
从源码中我们可以看出, StringBuffer 也是一个 final class, 也不能被继承. 既然是类, 那使用也一样直接 new 一个对象出来.
StringBuffer 是通过. append 来完成字符串的拼接, 那让我们来做个测试
从我们的测试来看, StringBuffer 和 String 类是不同的, 因为 StringBuffer 被修改后并没有产生新的对象, 是在之前的对象上修改的. 下面我们来看下部分源码:publicStringBuffer(){
super(16);
}
publicStringBufferappend(CharSequences){
if(s==null)s="null";
if(s instanceofString)returnthis.append((String)s);
if(s instanceofStringBuffer)returnthis.append((StringBuffer)s);
returnthis.append(s,0,s.length());
}
publicsynchronizedStringBufferappend(CharSequences,intstart,intend)
{
super.append(s,start,end);
returnthis;
}
从上面的代码中可以看出来, StringBuffer 的初始容量可以容纳 16 个字符, 当该对象的实体存放的字符的长度大于 16 时, 实体容量就自动增加.
StringBuffer 的函数都是加了 Synchronized 关键字的, 所以 StringBuffer 的方法是线程安全的, 可以在多线程中使用.
总结:
如果对字符串的改变少, 使用 String;
如果对字符串修改的较多或需要线程安全就用 StringBuffer,
三: StringBuilder
首先我们看下 StringBuilder 的源码:publicfinalclassStringBuilderextendsAbstractStringBuilder
implementsjava.io.Serializable,CharSequence
从源码中我们可以看出, StringBuilder 和 StringBuffer 一样, 也是一个 final class, 也不能被继承. 既然是类, 那使用也一样直接 new 一个对象出来.
StringBuilder 是通过. append 来完成字符串的拼接, 那让我们来做个测试
从上面的测试我门可以发现 StringBuilder 类的对象能够被多次的修改, 并且不产生新对象.
下面我们来看下部分源码:publicStringBuilder(Stringstr){
super(str.length()+16);
append(str);
}
privateStringBuilderappend(StringBuildersb){
if(sb==null)returnappend("null");
intlen=sb.length();
intnewcount=count+len;
if(newcount>value.length)
expandCapacity(newcount);
sb.getChars(0,len,value,count);
count=newcount;
returnthis;
}
从源码中我们可以发现它和 StringBuffer 之间的最大不同在于 StringBuilder 的方法不是线程安全的(不能同步访问). 所以在单线程的环境下 StringBuilder 相较于 StringBuffer 有速度优势, 因为它不需要做同步处理.
StringBuffer 的默认长度是 16, StringBuilder 的默认长度是 16 + 初始化传入字符串的长度.
四: 扩容
下面是扩容的源码:voidexpandCapacity(intminimumCapacity){
intnewCapacity=value.length*2+2;
if(newCapacity-minimumCapacity<0)
newCapacity=minimumCapacity;
if(newCapacity<0){
if(minimumCapacity<0)// overflow
thrownewOutOfMemoryError();
newCapacity=Integer.MAX_VALUE;
}
value=Arrays.copyOf(value,newCapacity);
}
当我们对 StringBuffer 和 StringBuilder 进行 append 的时候, 会先判断当前的容量是否可以放下, 如果长度不够, 就会调用 expandCapacity 来进行扩容
扩容的计算公式: 现在的长度 * 2 + 2;
如果我们扩容后的长度 (newCapacity) 还是放不下 (minimumCapacity) 的长度那就直接把所需要的长度赋值给扩容后的长度(newCapacity = minimumCapacity);
因为当 int 的最大值 + 1 就会变成负数, 所以我们需要在扩容完后验证, newCapacity 的正确性防止内存溢出, 如果超过了 int 的最大长度, 那么就把 NewCapacity 设置成 Integer.MAX_VALUE,
newCapacity = Integer.MAX_VALUE;
最后把原数组 copy 到扩容后的数组中:
value = Arrays.copyOf(value,newCapacity);
总结:
当需要频繁的字符串拼接和删除时, 建议使用 StringBuffer 或 StringBuilder
在单线程的程序中, 使用 String 或 StringBuilder
在多线程的程序中, 使用 StringBuffer.
来源: http://www.jianshu.com/p/b8cfa52be50c