Java的String、StringBuffer、StringBuilder
2017-09-18 16:05
281 查看
一、String的解析
1.1 问题的引入相信许多人都遇到过这样的一道面试题
String s1 = "ab"; String s2 = "cd"; String s3 = "abcd"; String s4 = s1 + s2; String s5 = "ab" + "cd"; System.out.println(s4 == s3); System.out.println(s5 == s3);
由于==号比较的变量地址,因此这道题翻译一下就是s4所指向的地址与s3所指向的地址是否一致,s5所指向的地址与s3所指向的地址是否一致。
第一道题的答案是s4所指向的地址和s3是不一致的,具体为什么,我们可以借助smali看看在jvm中s4 = s1 + s2这段代码的真正执行逻辑
new-instance v5, Ljava/lang/StringBuilder; invoke-direct {v5}, Ljava/lang/StringBuilder;-><init>()V invoke-virtual {v5, v0}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; move-result-object v5 invoke-virtual {v5, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder; move-result-object v5 invoke-virtual {v5}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String; move-result-object v3 .line 12 .local v3, "s4":Ljava/lang/String;
在JVM中,s4 = s1 + s2这段代码并不只是简单的两个字符串相加,通过smali可以看出先是新建了一个StringBuilder对象,然后通过append方法将v0(“ab”),v1(“cd”)的值相加,然后再调用toString赋值给s4。因此s4等于是一个新建的对象,当然地址和s3也就不一致了。至于StringBuilder的概念我们下面再说。
第二道题的答案是s5所指向的地址和s3是一致的,这个结果首先要知道s5在jvm中是一个怎么样的解释方法,还是看smali代码:
const-string v4, "abcd" .line 14 .local v4, "s5":Ljava/lang/String;
可以看到s5=”ab”+”cd”被直接翻译成了s5=”abcd”,这样就和s3拥有了一样的常量字符,这两个对象就指向了常量池中一样的”abcd”地址。至于常量池的概念,我们下面再说。
1.2 常量池的概念
通过上面第二道题的结果,我们引出了常量池这个概念。在Java编译好的Class文件中,有一个称之为Constant Pool的区域,它是一个数组构成的表,用来存储程序中的各种常量,包括Class、String、Integer等等各种基本的Java类型。String Pool是Constant Pool中存储String常量的区域。在运行过程中,常量池中的String会在堆中创建一个新的对象,当一个变量被创建时,会优先查找常量池中是否有相同的String,如果有,怎直接返回常量池中String指向的地址。这样就能解释上面的s5 == s3了。
1.3 intern方法
既然引出了常量池的概念,我们也说一说intern这个方法,先看一个例子,我们在上面那题的基础上,给出一题
String s6 = s4.intern(); System.out.println(s6 == s3);
这个的输出是s6和s3是一样的对象,也就是等号成立。看intern这个方法的官方doc注释:
* When the intern method is invoked, if the pool already contains a * string equal to this {@code String} object as determined by * the {@link #equals(Object)} method, then the string from the pool is * returned. Otherwise, this {@code String} object is added to the * pool and a reference to this {@code String} object is returned.
当这个方法调用时,会先优先查找String pool中是否有相等的字符串,如果有,则直接返回String pool中的地址,否则,则新建一个字符串到String pool中。
1.4 String是一个不可变类型
通过查看源码可知String类中用来存储字符串的数组是一个final类型
/** The value is used for character storage. */ private final char value[];
因此只能被一次复制,之后不能再修改。字符串的扩展操作必定是新建了一个对象,所以String是一个不可变的类型。至于String不可变的原因,是由于String在Java中被当做了类似基本类型来使用,使用频率特别高如果可以任意改变它指向的内存的值,很容易导致其源数据改变造成一些意料之外的运行结果,比如在网络传输时,在发送某段字符串的过程中,String之前指向的内存值被修改了,导致数据出现了改变这一就会造成意想不到的结果。因此每次对String的修改都创建了一个新的对象,开辟了一块新的内存地址,以避免这种情况。
二、StringBuffer
2.1 StringBuffer是什么StringBuffer其实就是对String的可变化对象,上面已经说过String是一个不可变的对象,但是总有一些场景会出现不可变对象会造成效率的降低,因此就需要一个StringBuffer对象用来弥补String的不足,可以看到StringBuffer的成员变量中,用来存储字符串(声明在AbstractStringBuilder中)的变量已经不是final了,意味着它可以进行扩展,删除。
/** * The value is used for character storage. */ char[] value;
除此之外,StringBuffer是线程安全的,所有的公开方法都加上了
synchronized字段,这样就可以避免多线程操作造成的不可预知的结果。
2.2 为什么要StringBuffer
看下面一段代码
String s1 = ""; for (int i = 0; i < 1000; i++) { s1 += "ab"; }
这样一个循环增加字符串的长度由于String的不可变性,会在每次调用+=的时候创建一个新的对象,效率会非常的低。在第四节会测试看看这样的效率影响会有多大。如果改写成
StringBuffer stringBuffer = new StringBuffer(2000); for (int i = 0; i < 1000; i++) { stringBuffer.append("ab"); }
只需要创建一次对象,并且直接通过扩容StringBuilder就可以实现+=的效果。说到扩容,这里再介绍一下StringBuilder的append方法,最核心应该就是StringBuilder默认会创建一个长度为16的字符数组,如果在调用append的时候发现数组不过大,则会增加原长度的2倍+2的长度,如下:
/** * This implements the expansion semantics of ensureCapacity with no * size check or synchronization. */ void expandCapacity(int minimumCapacity) { int newCapacity = value.length * 2 + 2; if (newCapacity - minimumCapacity < 0) newCapacity = minimumCapacity; if (newCapacity < 0) { if (minimumCapacity < 0) // overflow throw new OutOfMemoryError(); newCapacity = Integer.MAX_VALUE; } value = Arrays.copyOf(value, newCapacity); }
因此,如果能提前估算出StringBuilder的大小,就能减少扩容带来的性能损耗。就像
StringBuffer stringBuffer = new StringBuffer(2000);这样声明即可。
三、StringBuilder
3.1 StringBuilder是什么StringBuilder可以说是StringBuffer的无同步锁版本,和StringBuffer唯一的区别就是所有的公开方法都没有加synchronized。
3.2 为什么要用StringBuilder
加锁会造成性能损耗,所以如果确定不会进行多线程操作时,使用StringBuilder会比使用StringBuffer带来更好的效率
四、String、StringBuffer、StringBuilder
4.1 效率的差别上面分别说明了String、StringBuffer和StringBuilder,现在我们就来看看这三个类的效率差别有多大吧。下面是对三者分别进行扩展1000个字符 10000个字符 100000个字符所需时间的测试结果
测试对象 | 1000 | 10000 | 100000 | 10000000 |
---|---|---|---|---|
String | 7ms | 173ms | 5033ms | |
StringBuffer | 0ms | 1ms | 1ms | 395ms |
StringBuilder | 0ms | 1ms | 1ms | 146ms |
所以对于字符串扩充这个操作效率是StringBuilder > StringBuffer > String
4.2 总结
在使用的过程中如果没有多线程的操作,尽量使用StringBuilder来进行字符串的操作,StringBuffer一般用于多线程操作同一字符的情况。而在进行字符串操作的时候尽量避免使用”+=”,”=”等操作,可以大大提升程序的运行效率。
相关文章推荐
- Java中的String、StringBuilder以及StringBuffer(转载)
- Java之字符串String,StringBuffer,StringBuilder
- Java String,StringBuilder和StringBuffer的区别 StringBuilder > StringBuffer> String
- Java:String、StringBuffer和StringBuilder的区别
- Java中String、StringBuilder和StringBuffer的简单区别
- Java中String、StringBuffer和StringBuilder的区别和堆栈内存分配
- Java 字符串(String, StringBuffer, StringBuilder,StringTonkenizer)
- 探秘Java中String、StringBuilder以及StringBuffer
- Java中String ,StringBuffer和StringBuilder的区别和用法
- Java中的String,StringBuilder,StringBuffer三者的区别
- java学习---String、StringBuffer与StringBuilder之间区别
- Java中String、StringBuffer、StringBuilder区别与理解
- [校招准备]之:java——String,StringBuffer,StringBuilder区别
- java中String、StringBuffer、StringBuilder的区别
- JAVA中String,StringBuffer与StringBuilder的区别
- Java—String、StringBuffer、StringBuilder的用法与区别
- Java中的String、StringBuilder以及StringBuffer
- JAVA之String,StringBuffer与StringBuilder三者之间的区别
- Java String,StringBuilder,StringBuffer
- Java String、StringBuffer和StringBuilder