java中String和StringBuffer、StringBuilder

1 篇文章 0 订阅
1 篇文章 0 订阅

好久没做java了,最近突然想复习复习java的东西,就想起来以前搞的最多的一个问题,也是面试中常被问到的问题,就是String,StringBuffer,

StringBuilder的区别。这个也是网上很多人都写过了,我就是总结了下,希望大家共勉。

首先,介绍这个东西之前,我还是希望大家掌握一些必须要知道的概念:堆,堆栈和常量池。

在JAVA中,有六个不同的地方可以存储数据:

1. 寄存器(register)。这是最快的存储区,因为它位于不同于其他存储区的地方——处理器内部。但是寄存器的数量极其有限,所以寄存器由编译器根据需求进行分配。你不能直接控制,也不能在程序中感觉到寄存器存在的任何迹象。

------最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制.

2. 堆栈(stack)。位于通用RAM中,但通过它的“堆栈指针”可以从处理器哪里获得支持。堆栈指针若向下移动,则分配新的内存;若向上移动,则释放那些 内存。这是一种快速有效的分配存储方法,仅次于寄存器。创建程序时候,JAVA编译器必须知道存储在堆栈内所有数据的确切大小和生命周期,因为它必须生成 相应的代码,以便上下移动堆栈指针。这一约束限制了程序的灵活性,所以虽然某些JAVA数据存储在堆栈中——特别是对象引用,但是JAVA对象不存储其 中。

------存放基本类型的变量数据和对象,数组的引用,但对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(字符串常量对象存放在常量池中)

3. 堆(heap)。一种通用性的内存池(也存在于RAM中),用于存放所以的JAVA对象。堆不同于堆栈的好处是:编译器不需要知道要从堆里分配多少存储区 域,也不必知道存储的数据在堆里存活多长时间。因此,在堆里分配存储有很大的灵活性。当你需要创建一个对象的时候,只需要new写一行简单的代码,当执行 这行代码时,会自动在堆里进行存储分配。当然,为这种灵活性必须要付出相应的代码。用堆进行存储分配比用堆栈进行存储存储需要更多的时间。

------存放所有new出来的对象。

4. 静态存储(static storage)。这里的“静态”是指“在固定的位置”。静态存储里存放程序运行时一直存在的数据。你可用关键字static来标识一个对象的特定元素是静态的,但JAVA对象本身从来不会存放在静态存储空间里。

------存放静态成员(static定义的)
5. 常量存储(constant storage)。
常量值通常直接存放在程序代码内部,这样做是安全的,因为它们永远不会被改变。有时,在嵌入式系统中,常量本身会和其他部分分割离开,所以在这种情况下,可以选择将其放在ROM中

------存放字符串常量和基本类型常量(public static final)
6. 非RAM存储。
如果数据完全存活于程序之外,那么它可以不受程序的任何控制,在程序没有运行时也可以存在。

------硬盘等永久存储空间
就速度来说,有如下关系:
寄存器 > 堆栈 > 堆 > 其他

这里我们主要关心栈,堆和常量池,对于栈和常量池中的对象可以共享,对于堆中的对象不可以共享。栈中的数据大小和生命周期是可以确定的,当没有引用指向数据时,这个数据就会消失。堆中的对象的由垃圾回收器负责回收,因此大小和生命周期不需要确定,具有很大的灵活性。
对于字符串:其对象的引用都是存储在栈中的,如果是编译期已经创建好(直接用双引号定义的)的就存储在常量池中,如果是运行期(new出来的)才能确定的就存储在堆中。对于equals相等的字符串,在常量池中永远只有一份,在堆中有多份。

如以下代码:

1.       String s1 = "china";

2.       String s2 = "china";

3.       String s3 = "china";

4.       String ss1 = new String("china");

5.       String ss2 = new String("china");

6.       String ss3 = new String("china");

对于通过new产生一个字符串(假设为”china”)时,会先去常量池中查找是否已经有了”china”对象,如果没有则在常量池中创建一个此字符串对象,然后堆中再创建一个常量池中此”china”对象的拷贝对象。这也就是有道面试题:String s = new String(“xyz”);产生几个对象?一个或两个,如果常量池中原来没有”xyz”,就是两个。

对于基础类型的变量和常量:变量和引用存储在栈中,常量存储在常量池中。
如以下代码:

1.       int i1 = 9;

2.       int i2 = 9;

3.       int i3 = 9;

4.       public staticfinalint INT1 =9;

5.       public staticfinalint INT2 =9;

6.       public staticfinalint INT3 =9;

对于成员变量和局部变量:成员变量就是方法外部,类的内部定义的变量;局部变量就是方法或语句块内部定义的变量。局部变量必须初始化。
形式参数是局部变量,局部变量的数据存在于栈内存中。栈内存中的局部变量随着方法的消失而消失。
成员变量存储在堆中的对象里面,由垃圾回收器负责回收。
如以下代码:

1.       class BirthDate {

2.       private int day;

3.       private int month;

4.       private int year;

5.       public BirthDate(int d,int m,int y) {

6.       day = d;

7.       month = m;

8.       year = y;

9.       }

10.     省略get,set方法………

11.     }

12.      

13.     public class Test{

14.     public staticvoid main(String args[]){

15.     int date = 9;

16.     Test test = new Test();

17.     test.change(date);

18.     BirthDate d1= new BirthDate(7,7,1970);

19.     }

20.      

21.     public void change1(int i){

22.     i = 1234;

23.     }

1.       class BirthDate {

2.       private int day;

3.       private int month;

4.       private int year;

5.       public BirthDate(int d,int m,int y) {

6.       day = d;

7.       month = m;

8.       year = y;

9.       }

10.     省略get,set方法………

11.     }

12.      

13.     public class Test{

14.     public staticvoid main(String args[]){

15.     int date = 9;

16.     Test test = new Test();

17.     test.change(date);

18.     BirthDate d1= new BirthDate(7,7,1970);

19.     }

20.      

21.     public void change1(int i){

22.     i = 1234;

23.     }

对于以上这段代码,date为局部变量,i,d,m,y都是形参为局部变量,day,month,year为成员变量。下面分析一下代码执行时候的变化:
1. main方法开始执行:int date = 9;
date局部变量,基础类型,引用和值都存在栈中。
2. Test test = new Test();
test为对象引用,存在栈中,对象(newTest())存在堆中。
3. test.change(date);
i为局部变量,引用和值存在栈中。当方法change执行完成后,i就会从栈中消失。
4. BirthDate d1= new BirthDate(7,7,1970);
d1为对象引用,存在栈中,对象(newBirthDate())存在堆中,其中d,m,y为局部变量存储在栈中,且它们的类型为基础类型,因此它们的数据也存储在栈中。day,month,year为成员变量,它们存储在堆中(new BirthDate()里面)。当BirthDate构造方法执行完之后,d,m,y将从栈中消失。
5.main方法执行完之后,date变量,test,d1引用将从栈中消失,new Test(),new BirthDate()将等待垃圾回收。

好了,有了上面的基础,下面我们就来解释String,StringBuffer,StringBuilder的区别了。

一、概貌

String:
是对象不是原始类型.
为不可变对象,一旦被创建,就不能修改它的值.
对于已经存在的String对象的修改都是重新创建一个新的对象,然后把新的值保存进去.
String 是final类,即不能被继承.

StringBuffer:
是一个可变对象,当对他进行修改的时候不会像String那样重新建立对象
它只能通过构造函数来建立,
StringBuffer sb = new StringBuffer();
note:不能通过付值符号对他进行付值.
sb = "welcome to here!";//error
对象被建立以后,在内存中就会分配内存空间,并初始保存一个null.向StringBuffer
中付值的时候可以通过它的append方法.

StringBuilder:

此类从jdk1.5开始,它是一个可变字符序列,这点跟StringBuffer是一样的。它比StringBuffer出现的晚,一般我们认为它是StringBuffer的替代品。StringBuffer同步,它不同步,因此也就意味着在单处理机中它的速度要比StringBuffer快。因为同步是耗资源的。

二、详细阐述

怎么理解概述中的String类的对象不可改变呢?

1.String a = "123";

a = "456";

下面详细解释下这个创建过程:第一句,会在常量池中创建一个常量串”123”,并且在栈中开辟一块引用a,使这个引用指向常量池中的“123“这个常量串的地址。第二句话产生的效果是把a这个引用重新指向了一个新的常量串”456“这个地址。而原来的常量串”123“仍然存在常量池中,等待系统的回收。

2.String a=new String(“123”);

这种创建个效果跟上面有些不同。相同点都是会在常量池中开辟一块常量空间存放常量串“123“。不同的是上面是在栈中创建一个引用a,指向常量”123“。而这个是创建两个对象。一个常量对象,也就是常量池中的常量串,另一个New String()也会在堆中创建一个对象,此对象中内容是常量池中”123“常量串的地址。(注意:这点很重要!)。new String()创建的堆对象里面并不是直接就存的是常量串”123“的拷贝值。

3.StringBuffer sb = newStringBuffer("asd");

sb.append("fgh");

在这个过程中,只存在sb这么一个对象,sb一直都指向一个 StringBuffer instance*.append也只是改变此 instance的内容而已。

String是一个final Class StringBuffer不是。所以对于 String a = "yacht";String b = "yacht1"; String c = a + b ; 存在一个对象拷贝构造和解析的消耗问题;

解释下这段代码:在java中,String c=a + b;实际上是如下执行的:

StringBuffer c=new StringBuffer();

c.append(a);

c.append(b);

c.toString();

看到了吧,对String对象进行连接字符串的操作,在jvm内部,还是通过StringBuffer的机制实现的。

对于一个StringBuffer来说,StringBuffer sb = newStringBuffer()sb.append("yacht"); sb.append("yacht1"); 因为StringBuffer是一个可以实例化的类,而且它的内建机制是维护了一个capacity大小的字符数组,所以它的append操作不存在对象的消耗问题,所以我觉得如果存在String连接这种事情,StringBuffer来做会好很多。

但事情并不是这么简单,看下面代码:

String a = "yacht1" +"yacht2" + "yacht3" + "yacht4";

StringBuffer sb = new StringBuffer();

sb.append("yacht1") ;

sb.append("yacht2");

sb.append("yacht3") ;

sb.append("yacht4");

String a = sb.toString();

如果按照我上面说的,String的效率肯定比StringBuffer的低,但经过测试不是这样,为什么?这里,我们需要理解程序过程的两个时期,一个是编译时,一个是运行时,在编译时,编译器会对你的程序做出优化,所以String  a会被优化成yacht1yacht2yacht3yacht4,而StringBuffer只会在运行时才处理。所以效率是不一样的。

如果代码是这样的:

String a ;

for(int i = 0; i< 100000;i++){

a += String.valueOf(i) ;

}

StringBuffer sb = new StringBuffer();

for(int i = 0; i< 100000;i++){

sb.append(i) ;

}

String a = sb.toString();

如果是这种情况的话,String的效率就大大不如StringBuffer,区别在哪里,就在于运行时和编译时的优化问题上!

public class xxx {

public static void main(String[] args) {

String s1 = "You are hired!";

String s2 = "You are hired!";

if (s1==s2) {

System.out.println("一个内存空间");

}

else {

System.out.println("不是一个内存空间");

}

}

}

打印的结果是:一个内存空间。这里==的意义是两个操作数是否指向同一个内存空间。可见s2在不用new创建的情况下会自动检索到具有相同内容的常量池中空间并把s2也指向这个内存空间,所以当然就是true了。

在看下面的代码:

public class xx {

public static void main(String[] args) {

String s1 = "You are hired!";

String s2 = "You are hired!";

s1 = s1.replace('h','f');

System.out.println(s1);

if (s1==s2) {

System.out.println("一个内存空间");

} else {

System.out.println("不是一个内存空间");

}

}

}

代码结果是You are fired!不是一个内存空间。可见,Strings1的内容虽然被改写,但是已经不在是原来第一次分配到的那个内存空间了。也就是String类的内容能被改变,但一旦改变系统将为其分配新的内存空间。说到与stringBuffer的区别,从根本上来说应该是stringBuffer在做字符长度变动的时候将继续使用原来的内存空间,不新分配.String的长度一旦变动,就如上面的例子一样,其内部将分配新的内存空间。

StringBuffer必须new出来,StringBufferappend的效率比string+=的效率高,仔细研究发现还有很大的区别,看看以前scjp的考题

public class Test {

public static void stringReplace (Stringtext) {

text = text.replace('j' , 'i');

}

public static void bufferReplace(StringBuffer text) {

text = text.append("C");

}

public static void main (String args[]) {

String textString = new String ("java");

StringBuffer textBuffer = new StringBuffer("java");

stringReplace (textString);

bufferReplace (textBuffer);

System.out.println (textString +textBuffer);

}

}

这段代码打印什么呢? javajavaC还是iavajavaC呢?哈哈,我这么问了,大家当然知道是打印是 javajavaC了。这是为什么呢?原文章中说的中心就是一句话:String的值是不会变的!首先肯定这个回答是正确的,但是不是很明了。为什么呢?看我逐个分析:

stringReplace (textString);

bufferReplace (textBuffer);

这是上面的代码。是两个函数调用。而且函数调用的参数传递的是对象。好吧,大家都明白,对象传递,传递的是对象的地址,那么这两句话是把前面声明的String类和StringBuffer类的在堆内存中开辟的地址分别传递给了这两个函数。此时,当StringBuffer的对象到函数中去了,就更改了这个地址里面原有的内容了,我们看到,这调用append方法在原有内容后面加上了个‘C’。这个都没问题。关键就是String,它命名传递过去的就是String对象在堆内存中的地址,按道理说在方法中改变了地址中的值后,返回的就是改变后的值了嘛!这里怎么值还是没变呢?这里大家注意:String在堆内存中存的不是直接的“java“这个值,而是常量池中的”java“这个串的引用(地址)。当我们要去改变String这个对象的值时,jvm一看,是String中存的是地址,它就去常量池中找这个地址,找到了这个地址,要改变它,jvm就重新开辟一块空间,存储改变后的地址。这个改变后的地址是由调用函数中的形参引用的,在本例中,是

public static void stringReplace (Stringtext) {

text = text.replace('j' , 'i');

}

函数中的text引用的。但是原来的textString还是指向的常量池中的“java“串的地址。这就是: String的值是不会变的!这句话在内存中是怎么实现的解释。相信现在大家就该记住了,我们说的对象传递,传递的是地址,这句话没错,可是到了String面前,它就不灵了(绝对不是错了!),String的对象仍然是传地址,但是它的效果是等同于传值的。希望大家记住。

以下做为扩展:

StringBuffer s1 = newStringBuffer("a");

StringBuffer s2 = newStringBuffer("a");

s1.equals(s2)//为什么是false

String s1 = new String("a");

String s2 = new String("a");

s1.equals(s2)//为什么是true

StringBuffer类中没有重新定义equals这个方法,因此这个方法就来自Object类,而Object类中的equals方法是用来比较地址的,所以等于false.String类中重新定义了equals这个方法,而且比较的是值,而不是地址。所以会是true

三、StringBuilder

其用法类似于StringBuffer,这里大家只需要知道它是线程不安全的(即不同步)。它在单处理机中可以安全的替代StringBuffer就可以了。其用法和方法详细请参见jdk1.6帮助文档。

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值