String StringBuffer StringBuild问题是面试题中经常考的问题.
在网络收集了一些讲解 但讲解的比较复杂
所以在此做出总结 并且复习一下
原文地址:http://www.iteye.com/topic/522167
String
1.
String s1="a";
String s2="a";
String s3=new String("a");
String s4=new String("a");
结果
s1==s2//true
s2==s3//false
s3==s4//false
首先介绍下String:
String
类代表字符串。Java 程序中的所有字符串字面值(如"abc"
)都作为此类的实例实现。
字符串是常量;它们的值在创建之后不能更改。字符串缓冲区支持可变的字符串。因为 String 对象是不可变的,所以可以共享.
按理说s1是String的一个引用对象 应采取new String()的方式来创建示例
为什么它可以像基础数据类型一样直接赋值呢?
(java规定 没有原因)可以理解为因为String类太常用了,这样直接赋值,避免多次创建内容相同的String对象,节省空间,提高效率。
现在介绍常量池的概念:
简单理解就是 jvm为避免多次创建内容相同的String对象 把一些string对象放入一个区域
当需要调用时 直接让引用指向常量池中的对象
拘留字符串:
堆中的String对象(不是常量池)
再来解决问题
可以看到s1 s3是两个不同的引用对象
在编译期间 jvm会把=" "方式的字符串放入常量池 并在堆中创建拘留字符串并把该字符串在常量池中的入口地址赋给拘留字符串
s1:当通过 =" " 的方式来创建String对象时s中存放的是早已创建的拘留字符串的地址
s2:同s1
s3: 通过=new String(" ")方式创建对象时 ss中存放的是 根据该字符串在堆中产生的新的拘留字符串的地址
ss4:同ss
//如果堆中已经存在相同字符串 则用已存在的拘留字符串来初始化新的拘留字符串
因为s1 s3指向两个地址 所以s1==s3返回false
s2 s1指向的都是常量池中 字符串"a" 在堆内存中的同一个拘留字符串 所以s2==s1返回true
而new String(" ")每次创建新的对象 所以s3==s4返回false
2:
String s1="a";
String s2=s1+"b";
string s3="a"+"b";
结果
s2==s3//false
s3中 "a"+"b"会直接在编译期就合并成常量"ab" 并放入常量池中, 因此相同字面值常量"ab"所对应的是同一个拘留字符串对象,自然地址也就相同。
s2中 jvm首先会根据s1在堆中创建一个StringBuffer对象再通过append()方法,然后调用append方法完成对sb所指向的拘留字符串的合并操作,接着调用StringBuilder的toString()方法在堆中创建一个String对象,最后将刚生成的String对象的堆地址存放在局部变量s2中
String StringBuffer StringBuild的比较
- //String
- public final class String
- {
- private final char value[];
- public String(String original) {
- // 把原字符串original切分成字符数组并赋给value[];
- }
- }
- //StringBuffer
- public final class StringBuffer extends AbstractStringBuilder
- {
- char value[]; //继承了父类AbstractStringBuilder中的value[]
- public StringBuffer(String str) {
- super(str.length() + 16); //继承父类的构造器,并创建一个大小为str.length()+16的value[]数组
- append(str); //将str切分成字符序列并加入到value[]中
- }
- }
而StringBuffer是根据字符串分配预留16个单位缓冲区的可变数组
所以说String是不可变常量StringBuffer是变量
注意:这个对初学者来说有个误区,有人说String str1=new String("abc"); str1=new String("cba");不是改变了字符串str1吗?那么你有必要先搞懂对象引用和对象本身的区别。这里我简单的说明一下,对象本身指的是存放在堆空间中的该对象的实例数据(非静态非常量字段)。而对象引用指的是堆中对象本身所存放的地址,一般方法区和Java栈中存储的都是对象引用,而非对象本身的数据。
StringBuffer与StringBuilder的线程安全性问题
StringBuffer和StringBuilder可以算是双胞胎了,这两者的方法没有很大区别。但在线程安全性方面,StringBuffer允许多线程进行字符操作。这是因为在源代码中StringBuffer的很多方法都被关键字synchronized 修饰了,而StringBuffer没有。
总结:StringBuffer线程不安全 StringBuilder线程安全 因为StringBuild线程不安全 所以效率也高一些
注意:是不是String也不安全呢?事实上不存在这个问题,String是不可变的。线程对于堆中指定的一个String对象只能读取,无法修改。试问:还有什么不安全的呢?
String常量与String变量的"+"操作比较
▲测试①代码: (测试代码位置1) String str="";
(测试代码位置2) str="Heart"+"Raid";
[耗时: 0ms]
▲测试②代码 (测试代码位置1) String s1="Heart";
String s2="Raid";
String str="";
(测试代码位置2) str=s1+s2;
[耗时: 15—16ms]
由上面分析已知 测试①的"Heart"+"Raid"在编译阶段就已经连接起来,形成了一个字符串常量"HeartRaid",并指向堆中的拘留字符串对象。运行时只需要将"HeartRaid"指向的拘留字符串对象地址取出1W次,存放在局部变量str中。这确实不需要什么时间。
测试②中局部变量s1和s2存放的是两个不同的拘留字符串对象的地址。然后会通过下面三个步骤完成“+连接”:
1、StringBuilder temp=new StringBuilder(s1),
2、temp.append(s2);
3、str=temp.toString();
我们发现,虽然在中间的时候也用到了append()方法,但是在开始和结束的时候分别创建了StringBuilder和String对象。可想而知:调用1W次,是不是就创建了1W次这两种对象呢?不划算。
String对象的"累+"连接操作与StringBuffer对象的append()累和连接操作比较。
▲测试①代码: (代码位置1) String s1="Heart";
String s="";
(代码位置2) s=s+s1;
[耗时: 4200—4500ms]
▲测试②代码 (代码位置1) String s1="Heart";
StringBuffer sb=new StringBuffer();
(代码位置2) sb.append(s1);
[耗时: 0ms(当循环100000次的时候,耗时大概16—31ms)]
结论:大量字符串累加时,StringBuffer的append()效率远好于String对象的"累+"连接
原因:测试① 中的s=s+s1,JVM会利用首先创建一个StringBuilder,并利用append方法完成s和s1所指向的字符串对象值的合并操作,接着调用StringBuilder的 toString()方法在堆中创建一个新的String对象,其值为刚才字符串的合并结果。而局部变量s指向了新创建的String对象。
因为String对象中的value[]是不能改变的,每一次合并后字符串值都需要创建一个新的String对象来存放。循环1W次自然需要创建1W个String对象和1W个StringBuilder对象,效率低就可想而知了。
测试②中sb.append(s1);只需要将自己的value[]数组不停的扩大来存放s1即可。循环过程中无需在堆中创建任何新的对象。效率高就不足为奇了。
总结:
(1) 在编译阶段就能够确定的字符串常量,完全没有必要创建String或StringBuffer对象。直接使用字符串常量的"+"连接操作效率最高。
(2) StringBuffer对象的append效率要高于String对象的"+"连接操作。
(3) 不停的创建对象是程序低效的一个重要原因。那么相同的字符串值能否在堆中只创建一个String对象那。显然拘留字符串能够做到这一点,除了程序中的字符串常量会被JVM自动创建拘留字符串之外,调用String的intern()方法也能做到这一点。当调用intern()时,如果常量池中已经有了当前String的值,那么返回这个常量指向拘留对象的地址。如果没有,则将String值加入常量池中,并创建一个新的拘留字符串对象。
-version1.0 2015.1.26