Java字符串的操作大家应该再熟悉不过了,然而,我们往往只使用String来操作字符串,却忘记了字符串操作还有StringBuffer和StringBuilder类。
下面我们分别来介绍这三个类之间的关系。
以下以创建三个字符串对象为例:
String str1 = "Hello World";
String str2 = "Hello World";
String str3 = new String("Hello World");
当我们使用equals或“==”判断两者是否相等的时候:str1与str2的内容相同,“==”比较返回true。为什么会出现这样的结果呢???
str1与str3的内容相同,但“==”比较返回false。
这是因为字符串特殊内存机制造成的。
JVM运行的时候,将内存分为两部分,一部分是堆,一部分是栈,堆中存放的是创建的对象,栈中存放的是方法调用过程中的局部变量或引用,而设计Java字符串对象内存实现的时候,在堆中又开辟了很小的一块内存,被称为字符串常量池,专门用来存放特定的字符串对象。因此:
String str1 = "Hello World";
String str2 = "Hello World";
创建的过程是:首先查看常量池中有没有内容与"Hello World"相同的字符串对象,若有则直接将引用变量指向该对象,若无则创建一个包含此内容的字符串对象再将引用变量指向该对象。String str1 = "Hello World";
String str3 = new String("Hello World");
str3的创建过程:首先在堆中创建一个字符串对象将引用指向此对象,然后在字符串常量池中查找是否有内容相同的字符串对象存在,若有则将new的字符串对象与字符串常量池中的字符串对象联系起来,若无则在字符串常量池中创建一个内容相同的字符串对象并建立联系。以上代码实现后引用与对象的关系如下图:
if(str1.intern()==str3.intern()){
...
}
所以当我们作字符串连接时:
String str1 = "Hello World";
str1= str1 + " str";
当执行str1=str1+" str"时,实际上会在字符串常量池中创建一个新的字符串("Hello World str")对象(先查找,有则指向,无则创建并指向),并将引用变量str1的指向修改为这个新的字符串对象,作其他修改也是如此。因此当要对一个字符串做大量连接时,会产生很多冗余的中间对象,这样会使性能大幅下降。而StringBuffer却没有这方面的缺点,该类的对象允许对其内容的修改且不产生中间对象。
StringBuffer对象的创建语法只有new这一种:
StringBuffer sb = new StringBuffer("Hello World");
//使用append方法作连接操作
sb.append("str");
更多API请查看Java API文档,这里需要注意的是StringBuffer类也有equals方法,但此方法比较的是对象,而非字符串内容,要做内容比较需要先使用toString方法再作比较。1.StringBuilder类的执行效率比StringBuffer稍微高一些,但是StringBuilder类的字符串编辑方法并没有进行同步,在使用多线程同时操作时可能会产生问题,应该在单线程的情况下使用。2.StringBuffer类对其内容的编辑、修改的方法进行了同步,多线程使用时不会产生问题。
由于使用基本相同,在这里不在赘述,更多API请查看在线API文档
参考:《Java开发手册》
补充点看到的面试题目:
1 . String 和 StringBuffer 的区别:
JAVA 平台提供了两个类:String 和 StringBuffer,它们可以储存和操作字符串,即包含多个字符的字符数据。这个 String 类提供了数值不可改变的字符串。而这个 StringBuffer 类提供的字符串进行修改。当你知道字符数据要改变 的时候你就可以使用 StringBuffer。典型地,你可以使用 StringBuffers 来动态构造字符数据。
2 . String, StringBuffer StringBuilder的区别:
String 的长度是不可变的;
StringBuffer的长度是可变的,如果你对字符串中的内容经常进行操作,特别是内容要修改时,那么使用 StringBuffer,如果最后需要 String,那么使用 StringBuffer 的 toString() 方法;线程安全;
StringBuilder 是从 JDK 5 开始,为StringBuffer该类补充了一个单个线程使用的等价类;通常应该优先使用 StringBuilder 类,因为它支持所有相同的操作,但由于它不执行同步,所以速度更快。
3 . String s=new String(“xyz”);创建了几个字符串对象:
两个对象,一个是静态存储区的"xyz",一个是用new创建在堆上的对象。
4 . 下面这条语句一共创建了多少个对象:String s=“a”+”b”+”c”+”d”;
对于如下代码:
第一条打印结果为 false ,第二条打印结果为 true,这说明 Javac 编译可以对字符串常量直接相加的表达式进行优化,不必要等到运行期去进行加法运算处理,而是在编译时去掉其中的加号,直接将其编译成一个这些常量相连的结果。题目中的代码被编译器在编译时优化后,相当于直接定义了一个”abcd”的字符串,所以只创建了一个String对象,因此打印结果为true。public class Test{ public static void main(String[] args){ String s1 = "a"; String s2 = s1 + "b"; String s3 = "a" + "b"; System.out.println(s2 == "ab"); System.out.println(s3 == "ab"); String s ="a" + "b" + "c" + "d"; System.out.println(s== "abcd"); } } /** C:\Users\Administrator\Desktop>javac Test.java C:\Users\Administrator\Desktop>java Test false true true */
5 . String s = "Hello";s = s + " world!”; 这两行代码执行后,原始的 String 对象中的内容到底变了没有?
没有。只是s不指向原来的String对象了,而指向了另一个String对象,其内容为"Hello world!",原来那个String对象还存在于内存之中。
6 . 如何把一段逗号分割的字符串转换成一个数组?
使用split()方法或StingTokenizer类
7 . 有没有哪种情况用+做字符串连接比调用StringBuffer/StringBuilder对象的append方法性能更好?
如果连接后得到的字符串在静态存储区中是早已存在的,那么用+做字符串连接是优于StringBuffer/StringBuilder的append方法的。
附:
在使用动态数组时Vector和ArrayList也是有同步区别的:
Vector方法同步,可以在多线程中使用,但在单线程中因为代码要在同步操作上耗费大量时间,效率会很低,此时应当使用ArrayList;
ArrayList方法不同步,用于单线程。