StringBuilder和StringBuffer
阅读了上篇博客后,了解了String的一些特性,知道了String的不可变性,那么java中有没有提供可变的字符串呢?强大的java,当然有!他们就是StringBuilder和StringBuffer!下面我们就开始介绍他俩的一些特性和使用场景。
我们先来看一下,他们三哥们有什么区别?为啥可变,又为啥不可变?
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;
// 此处省略一万行
}
细心的你们应该发现了,这边有一行注释叫
/** The value is used for character storage. */ 知道你们英文不好= =,帮你们谷歌翻译一下,大概就是(英文好的请忽略)
/** 该值用于字符存储。 */
嗯,这里就是说这个 value[]说存储字符串的值的,并且!它被final修饰了,final修饰变量还记得嘛,是说变量不可被改变,因此我们就知道了,奥,String的值不可变!
可变性源码解析
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.
*/
// 此处省略一万行
}
这里看到,好像没找着value?我翻到了最底下也没看到,但是这边注意到StringBuffer继承了一个类叫做AbstractStringBuilder,那是不是藏在父类里面呢?带着疑问我们去看一下
果然,在这边找到了,可以看到这里的value没有被final修饰,因此它是可变的!
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
char[] value;
/**
* The count is the number of characters used.
*/
int count;
// 此处省略一万行
}
StringBuilder
同样悄咪咪的看一下源码
还是一样的,在StringBuilder类中,并没有找到value,有了之前的经验,那么就去父类中瞅一哈
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;
// 此处省略一万行
}
果然都是一个套路,成功的找到了没有被final修饰的value
abstract class AbstractStringBuilder implements Appendable, CharSequence {
/**
* The value is used for character storage.
*/
char[] value;
/**
* The count is the number of characters used.
*/
int count;
// 此处省略一万行
}
线程安全
还是先解释一下什么是线程安全,大概意思就是说,同一时间只能有一个线程去访问我的内存资源(比如说一个方法,一个变量,或者一个代码块),这种就是叫做线程安全的,如果不能限制多个线程同时去访问内存资源,就会造成混乱,产生不必要的麻烦,这种我们就称为线程不安全。
String
一般来说,String是没有线程安不安全的说法的,它都是不变的,那么不用想了,肯定安全啊!
对于可变的字符串类 StringBuilder 和 StringBuffer来说,才会有这方面的考虑,果然有取就有舍啊,增加了可变性的同时也带来了隐藏的危害。
StringBuffer
我们先来看源码,我这边随便copy了几个源码中的方法,来看看
@Override
public synchronized int codePointBefore(int index) {
return super.codePointBefore(index);
}
/**
* @since 1.5
*/
@Override
public synchronized int codePointCount(int beginIndex, int endIndex) {
return super.codePointCount(beginIndex, endIndex);
}
/**
* @since 1.5
*/
@Override
public synchronized int offsetByCodePoints(int index, int codePointOffset) {
return super.offsetByCodePoints(index, codePointOffset);
}
/**
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
@Override
public synchronized void getChars(int srcBegin, int srcEnd, char[] dst,
int dstBegin)
{
super.getChars(srcBegin, srcEnd, dst, dstBegin);
}
/**
* @throws IndexOutOfBoundsException {@inheritDoc}
* @see #length()
*/
@Override
public synchronized void setCharAt(int index, char ch) {
if ((index < 0) || (index >= count))
throw new StringIndexOutOfBoundsException(index);
toStringCache = null;
value[index] = ch;
有没有注意到什么?很好,这边的方法全都被synchronized关键字修饰了,之前我们讲修饰符的时候提到过这个,是被synchronized修饰的方法叫做同步方法,也就是线程安全的。那么之前也说到了有取就有舍,提高了安全性的同时,又降低了执行的效率,果然想两全其美真的难啊!所以再多线程的场景下,我们还是尽量去使用StringBuffer,不能为了一点点效率而提升了安全隐患,得不偿失。
StringBuilder
一言不合看源码,再随便copy几个方法看看
@Override
public StringBuilder append(double d) {
super.append(d);
return this;
}
/**
* @since 1.5
*/
@Override
public StringBuilder appendCodePoint(int codePoint) {
super.appendCodePoint(codePoint);
return this;
}
/**
* @throws StringIndexOutOfBoundsException {@inheritDoc}
*/
@Override
public StringBuilder delete(int start, int end) {
super.delete(start, end);
return this;
}
/**
* @throws StringIndexOutOfBoundsException {@inheritDoc}
*/
@Override
public StringBuilder deleteCharAt(int index) {
super.deleteCharAt(index);
return this;
}
/**
* @throws StringIndexOutOfBoundsException {@inheritDoc}
*/
@Override
public StringBuilder replace(int start, int end, String str) {
super.replace(start, end, str);
return this;
}
/**
* @throws StringIndexOutOfBoundsException {@inheritDoc}
*/
@Override
public StringBuilder insert(int index, char[] str, int offset,
int len)
{
super.insert(index, str, offset, len);
return this;
}
这边可以看到,StringBuilder类中的方法,是没有synchronized修饰的,也就是所谓的线程不安全,但是好处是,它的效率高于StringBuffer,所以单线程的时候,推荐使用StringBuilder,速度快。
效率性
String
因为String是不可变对象,因此每次进行字符串拼接的时候,都是重新进行了对象的创建,十分影响效率。
public static void main(String[] args) {
String str = "";
long beginTime = System.currentTimeMillis();
for (int i = 0; i < 10000; i++){
str = str + i;
}
long endTime = System.currentTimeMillis();
System.out.println("执行时间:" + (endTime - beginTime)); // 执行时间:403
}
这里的执行之间为403毫秒
StringBuilder
相对比String,肯定是要快上不少的,因为不用每次都是创建新对象,只是想“容器” value 中动态的添加数据。
public static void main(String[] args) {
StringBuilder str = new StringBuilder();
long beginTime = System.currentTimeMillis();
for (int i = 0; i < 10000; i++){
str.append(i);
}
long endTime = System.currentTimeMillis();
System.out.println("执行时间:" + (endTime - beginTime)); // 执行时间:3
}
哪尼?只要3毫秒,好像是快很多奥
StringBuffer
之前提到了,StringBuffer是线程安全的,那么它的速度理论上来说应该会比StringBuilder要慢一点,看看结果
public static void main(String[] args) {
StringBuffer str = new StringBuffer();
long beginTime = System.currentTimeMillis();
for (int i = 0; i < 10000; i++){
str.append(i);
}
long endTime = System.currentTimeMillis();
System.out.println("执行时间:" + (endTime - beginTime)); // 执行时间:3
}
好像区别不大?几乎速度是一模一样的!
那我们把数字调大一点可能会有所差别,来看看
public static void main(String[] args) {
StringBuilder str = new StringBuilder();
long beginTime = System.currentTimeMillis();
for (int i = 0; i < 9999999; i++){
str.append(i);
}
long endTime = System.currentTimeMillis();
System.out.println("执行时间:" + (endTime - beginTime)); // 执行时间:409
}
public static void main(String[] args) {
StringBuffer str = new StringBuffer();
long beginTime = System.currentTimeMillis();
for (int i = 0; i < 9999999; i++){
str.append(i);
}
long endTime = System.currentTimeMillis();
System.out.println("执行时间:" + (endTime - beginTime)); // 执行时间:503
}
观察结果可以看到,好像还是StringBuilder略胜一筹,说明他俩在数量级较小的情况下,效率几乎一致!
内存占比
/**
* 定义三个方法做测试
*/
private static void StringTest() {
String s = "";
for (int i = 0; i < 10000; i++) {
s += "qwertyuiopasdfghjklzxcvbnmqazwsxedcrfvtgbyhnujmiklopplokmnjiuhbvgytfcxdrzsewaq";
}
}
private static void StringBufferTest() {
StringBuffer stringBuffer = new StringBuffer("");
for (int i = 0; i < 10000; i++) {
stringBuffer = stringBuffer
.append("qwertyuiopasdfghjklzxcvbnmqazwsxedcrfvtgbyhnujmiklopplokmnjiuhbvgytfcxdrzsewaq");
}
}
private static void StringBuilderTest() {
StringBuilder stringBuilder = new StringBuilder("");
for (int i = 0; i < 10000; i++) {
stringBuilder = stringBuilder
.append("qwertyuiopasdfghjklzxcvbnmqazwsxedcrfvtgbyhnujmiklopplokmnjiuhbvgytfcxdrzsewaq");
}
}
来测试一下
StringTest
public static void main(String[] args) {
StringTest();
long memory = Runtime.getRuntime().totalMemory()
- Runtime.getRuntime().freeMemory();
System.out.println("memory: " + memory); // memory: 63437192
}
StringBuilderTest
public static void main(String[] args) {
StringBuilderTest();
long memory = Runtime.getRuntime().totalMemory()
- Runtime.getRuntime().freeMemory();
System.out.println("memory: " + memory); // memory: 9187648
}
StringBufferTest
public static void main(String[] args) {
StringBufferTest();
long memory = Runtime.getRuntime().totalMemory()
- Runtime.getRuntime().freeMemory();
System.out.println("memory: " + memory); // memory: 9187648
}
可以看到,String最小,而StringBuilder居然和StringBuffer是一样的,想想也是,它们的数据都存放在value里,当然一样了!
好了,看到这里,我们也该总结一下了,它们的区别在哪里
- 可变性:String不可变,StringBuilder和StringBuffer可变
- 线程安全性:String和StringBuffer线程安全,StringBuilder线程不安全
- 效率性:StringBuilder>StringBuffer>String
- 内存占比:String>StringBuilder=StringBuffer
- 使用场景:不经常做拼接等操作的,使用量较小的的用String;经常改变字符串本身,并且又是多线程操作的,在乎执行效率的,用StringBuffer;经常改变字符串本身,单线程操作,在乎执行效率的,用StringBuilder。
具体的一些它们里面的一些方法,留给大家自己去查阅资料,做练习