前言
在平时开发中,我们很多时候都会用到String
,StringBuffer
,StringBuilde
这三者。那么这三者究竟是什么呢,下面一一讲述。
String
查看api
文档,可以知道,String
是继承object
类,并实现了序列化接口,字符序列接口,字符串排序接口。String
是Java中的字符串,Java程序中的所有字符串字面值(如"abc")都作为此类的实例实现,字符串是常量,它们的值在创建之后不能更改,字符串缓冲区支持可变的字符串。因为String
对象是不可变的,所有可以共享,例如:String str1 = "abc";
等效于char data[] = {'a','b','c'};String str2 = new String(data);
,因为String
是不可变的对象,每次对String
类型改变的时候其实都等同于生成一个新的String
对象,然后将指针指向新的String
对象,所以经常改变内容的字符串最好不要用String
,因为每次生成对象都会对系统性能产生影响,当内存多了无引用的对象,JVM
的GC就会工作,频繁GC就会造成界面的卡顿,下面直接上例子:
直接赋值和创建对象
情况一
String str1 = "abc" ;
char data[ ] = { 'a' , 'b' , 'c' } ;
String str2 = new String ( data) ;
System. out. println ( str1. equals ( str2) ) ;
System. out. println ( str1 == str2) ;
String str3 = "This is Java" ;
String str4 = new String ( "This is Java" ) ;
System. out. println ( str3. equals ( str4) ) ;
System. out. println ( str3 == str4) ;
上面代码str1
和str3
是直接赋值的,str2
是通过字符数组来构建一个字符串常量,str4
是直接通过new
关键字来创建对象。可以看到用equals
比较str1
和str2
,str3
和str4
是相等的,用==
比较str1
和str2
,str3
和str4
是不相等的。这里简单说一下equals
和==
的简单区别:
比较基本数据类型是否相同
判断引用是否指向堆内存的同一块地址,也就是是否指向同一个对象
判断两个变量是否对同一个对象的引用,也就是内存堆中的内容是否相同,其实就是对String
对象所封装的字符串内容作比较,具体的值是否相等,如果两个String
对象所封装的字符型内容相同,则equals()
方法将返回true
。
而上面的情况因为str1
和str2
是两个不同的对象,但是内容相等,所以==
返回了false,而equals
返回true,str3
和str4
也是一样道理,从上面可以简单得出一个结论,无论上直接赋值还是通过new
关键字来创建String
对象,都会生成两个不同的String
对象,即使字符型内容相同。
情况二
String str5 = "This is Java" ;
String str6 = "This is" ;
String str7 = "This is" + " Java" ;
System. out. println ( str5. equals ( str7) ) ;
System. out. println ( str5 == str7) ;
从上面代码可以看出,无论是用==
或者equals
来比较两个没有引用并且内容值是相等 返回都是true,也就是String str7 = "This is" + " Java";
这句话没有创建新的对象,而是引用指向str5
的地址。
情况三
String str8 = "This is Java" ;
String str9 = "This is" ;
String str10 = str9 + " Java" ;
System. out. println ( str8. equals ( str10) ) ;
System. out. println ( str8 == str10) ;
上面发现用==
比较时,str8
和str10
不是指向同一个地址,这是因为在编译时str8
和str9
就已经确定了,而str10
是引用变量,不会再编译时确定,所以会创建String
对象。
情况四
String str11 = "This is Java" ;
String str12 = new String ( "This is Java" ) ;
String str13 = str12;
System. out. println ( str11. equals ( str12) ) ;
System. out. println ( str11. equals ( str13) ) ;
System. out. println ( str12. equals ( str13) ) ;
System. out. println ( str11 == str13) ;
System. out. println ( str11 == str12) ;
System. out. println ( str12 == str13) ;
因为三个String
对象的值都是相等的,所以用equals
来比较是返回true,因为str13
是指向str12
,相当于传递引用,所以用==
来比较是相等的,都是同一个对象,用一张图来比较直观:
这里简单说一下,因为一开始String str11 = "This is Java";
在常量池已经创建This is Java
这个常量,所以String str12 = new String("This is Java");
这句代码不会再常量池创建对象,只会在堆上创建对象This is Java
,也就是说new String
创建字符串它其实分两步操作:
在堆上创建对象
检查常量池有没有字符串字面量,如果没有就在常量池创建常量,如果存在就不做任何操作
情况五
String str14 = "This is Java" ;
String str15 = "This is Java" ;
String str16 = "This is Java" ;
System. out. println ( str14. equals ( str15) ) ;
System. out. println ( str14. equals ( str16) ) ;
System. out. println ( str15. equals ( str16) ) ;
System. out. println ( str14 == str15) ;
System. out. println ( str14 == str16) ;
System. out. println ( str15 == str16) ;
直接上图:
情况六
String str17 = new String ( "This is Java" ) ;
String str9 = str17. intern ( ) ;
String str18 = "This is Java" ;
System. out. println ( str9 == str17) ;
System. out. println ( str9 == str18) ;
上面调用了intern
这个方法,这句话就是把字符串对象加入常量池中,实际操作如下,直接上图吧:
所以上面代码,用下面图显示:
总结
String a = "xxx"
可能创建一个或者不创建对象,当xxx
这个字符常量在String
常量池不存在,会在String
常量池创建一个String
对象,然后a
会指向这个内存地址,后面继续用这种方式继续创建xxx
字符串常量,始终只有一个内存地址被分配。
String a = new String("xxx")
至少创建一个对象,也有可能两个。只要用到new
就肯定在堆上创建一个String
对象,并且检查String
常量池是否存在,如果不存在就会在Stirng
常量池创建这个String
对象,存在就不创建。
StringBuffer字符串变量
在api
文档上,同样可以知道StringBuffer
继承Object
类,并实现了序列化接口,能够被添加char
序列和值的对象接口,可读序列接口。和String
相比,没有实现Comparable
,而是实现了Appendable
。它是线程安全的可变字符序列 ,一个类似于String
的字符串缓冲区,但是不能修改,虽然在任意时间点上它都包含某种特定的字符序列,但通过某些方法调用的可以改变该序列的长度和内容。StringBuffer
上的主要操作是append
和insert
方法,可以重载这些方法,以接受任意类型的数据,每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或者插入到字符串缓冲区中。append
方法始终将这些字符添加到缓冲区的末端,而insert
方法则在指定的点添加字符。StringBuffer
和String
不一样,它是**字符串变量,是可以改变的对象,当对字符串做操作时,就是在对象上做操作,不会像String
一样创建额外的对象进行操作。
StringBuffer str20 = new StringBuffer ( "This is" ) ;
str20. append ( " Java" ) ;
System. out. println ( str20) ;
str20. delete ( 5 , 8 ) ;
System. out. println ( str20) ;
str20. insert ( 9 , " is good" ) ;
System. out. println ( str20) ;
str20. replace ( 5 , 9 , "C++" ) ;
System. out. println ( str20) ;
str20. reverse ( ) ;
System. out. println ( str20) ;
System. out. println ( str20. toString ( ) . toUpperCase ( ) ) ;
String str21 = new String ( str20) ;
System. out. println ( str21) ;
String str22 = str20. toString ( ) ;
System. out. println ( str22) ;
StringBuilder字符串变量
和StringBuffer
一样,也是实现Serializable
,Appendable
,CharSequence
接口。一个可变的字符序列,这个类提供一个与StringBuffer
兼容的API,但是不保证同步,该类被设计用作StringBuffer
的一个简易替换,用在字符串缓冲区被单个线程使用的时候。如果可能,建议优先采用该类,因为在大多数实现中,比StringBuffer
要快。在StringBuilder
上的主要操作是append
和insert
方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串生成器中。append
方法始终将这些字符添加到生成器的末端;而 insert
方法则在指定的点添加字符。将StringBuilder
的实例用于多个线程是不安全的 。如果需要这样的同步,则建议使用StringBuffer。
主要一些操作:
StringBuilder str23 = new StringBuilder ( "This is" ) ;
str23. append ( " Java" ) ;
System. out. println ( str23) ;
str23. delete ( 5 , 8 ) ;
System. out. println ( str23) ;
str23. insert ( 9 , " is good" ) ;
System. out. println ( str23) ;
str23. replace ( 5 , 9 , "C++" ) ;
System. out. println ( str23) ;
str23. reverse ( ) ;
System. out. println ( str23) ;
System. out. println ( str23. toString ( ) . toUpperCase ( ) ) ;
String str24 = new String ( str23) ;
System. out. println ( str24) ;
String str25 = str23. toString ( ) ;
System. out. println ( str25) ;
三者速度比较
String str = new String ( "I Love Android" ) ;
long starttime1 = System. currentTimeMillis ( ) ;
for ( int i = 0 ; i < 100000 ; i++ ) {
str = str + "!" ;
}
long endtime1 = System. currentTimeMillis ( ) ;
System. out. println ( "String花费的时间 :" + ( endtime1 - starttime1) ) ;
StringBuilder str3 = new StringBuilder ( "I Love C++" ) ;
long starttime3 = System. currentTimeMillis ( ) ;
for ( int i = 0 ; i < 100000 ; i++ ) {
str3 = str3. append ( "!" ) ;
}
long endtime3 = System. currentTimeMillis ( ) ;
System. out. println ( "StringBuilder花费的时间 :" + ( endtime3 - starttime3) ) ;
StringBuffer str2 = new StringBuffer ( "I Love Java" ) ;
long starttime2 = System. currentTimeMillis ( ) ;
for ( int i = 0 ; i < 100000 ; i++ ) {
str2 = str2. append ( "!" ) ;
}
long endtime2 = System. currentTimeMillis ( ) ;
System. out. println ( "StringBuffer花费的时间 :" + ( endtime2 - starttime2) ) ;
运行截图:
总结
String :不可变类,任何对String的改变都会引发新的String对象的生成,适用于少量的字符串操作的情况
StringBuffer :线程安全,任何对它所指代的字符串的改变都不会产生新的对象,适用多线程下在字符缓冲区进行大量操作的情况
StringBuilder :线程不安全,因此不适合多线程中使用,适用于单线程下在字符缓冲区进行大量操作的情况
速度运行方面:StringBuilder > StringBuffer > String