黑马程序员---深入解析String、StringBuilder

来源:互联网 发布:魔兽争霸3mac原生版 编辑:程序博客网 时间:2024/06/05 15:28


------- android培训、java培训、期待与您交流! ----------

摘录一些很经典的代码段。个人觉得特别有代表性,能更深入的理解String以及StringBuilder。值得看看。参考了不少别人的资料,但都是自己仔细总结的。期待大家为我纠正错误之处。

====================================================================================
                         String
一.String的内存解析。
这是刚开始接触java的同学相对较多遇到问题的地方吧。先看两个语句:
String str0 = "java";
String str1 = new String("hello java!");
两种声明字符串的方式。前一种,并没有在堆中分配内存。只是将"java"保存在常量池中。第二种方式,是将"hello java!"保存在常量池中,new操作所开辟的堆空间里会复制一份"hello java!"。然后讲str1指向new后的堆空间。这
就是为什么说第二个语句实际上是两个对象。如图:

---------------------------------------------------------------------------------------------------------------------------------------------------
二.String是不可变的。
刚看到String是不可变的时候,有点不明白。后来查看一些资料,再看看String类的源代码,就明白了。这也解决了另外一个常被问到的面试题:问,String类可以被继承吗?先来看看String的源码,String类的声明是这样的:public final class String......这样是不是就有答案了。至于String是不可变的原因是,从源码可以看出,String的成员变量差不多都是被final修饰的。再看两个语句:
String str = "java";
str = "hello";
String的不可变是指,虚拟机并没有改变原来的对象"java",而是新创建了一个对象"hello",再让str指向这个新的对象"hello"。
----------------------------------------------------------------------------------------------------------------------------------------------------
三.String相关的几段经典代码。
*****代码段一 
        String str0 = "ab";  
        String str1 = "a" + "b";  
        System.out.println(str0 == str1); 
*****代码段二 
        String str0 = "ab";  
        String str1 = "b";  
        String str2 = "a" + str1;  
        System.out.println(str0 == str2);   
*****代码段三
      String str = "java";
      String str1 = new String("java");
      System.out.println(str==str1.intern());
代码段一,输出的结果是true,代码段二的结果是false。
对于以上两段代码的说明:
1.当使用String str = "java";方式来创建对象时,java运行时会拿这个"java"去String的常量池里去找是否有这个字符串,如果有,直接将常量池中地址给str。如果没有,讲"java"加入常量池。
2.当使用String str = new String("java");来创建对象时,同样会拿"java"去常量池中找,如果没有,则添加。并复制一份到new的堆空间,将堆空间地址给str。如果有,直接复制一份到new的堆空间,将堆空间地址给str。
3.使用常量来串联表达式时,不会在堆空间新建对象。只是在常量池中检查,有则直接指向,没有则在池中创建再被指向。如代码段一String str1 = "a" + "b";因为只在常量池中操作,所以返回true。
4.使用带变量表达式创建String,则不仅检查常量池,还会在堆中新建对象。如代码段二。返回false。
5.关于字符串常量池,还有一个intern()方法。以前没接触毕老师视频的时候,因为不知道字符串有个常量池,所以和一同学不理解编程中遇到的现象。为此查了不少资料才搞明白。当时在一资料上看到这个方法。印象比较深,这个方法的意思就是将常量池的字符串进行比较(我是这么理解的)。如代码段三。
-------------------------------------------------------------------------------------------------------------------------------------------------------
四.关于一道面试题的多种说法解析。
public class Example{    String str = new String("good");    char[] ch = {'a','b','c'};    public static void main(String[] args)    {        Example ex = new Example();        ex.change(ex.str, ex.ch);        System.out.print(ex.str+"......and.....");        System.out.print(ex.ch);    }    public void change(String str,char ch[])    {        str = "test ok";        ch[0]='g';    }}

一些理解:
1.字符串是不可变的;
2.值传递与引用传递的不同(事实上,java中只有值传递);
3.成员变量与局部变量区别;
经过验证,确实在程序change函数里的str前面加上this。就会出现不一样的结果。查阅许多资料后,我的理解是这样的:
1.字符串是不可变的。这话没有错。如第二部分的解说。也许也有些类似以上的题目,被解说为字符串是不可变的。我觉得这种说法,不太合适。这里的字符串不可变体现在,当change函数里str前加上this时,change被调用后,str指向了"test ok",原来的"good"暂时还在,只不过str不再指向它了。这就是说"good"是不可变的。我将程序稍微调整如代码段四。运行结果说明了所有问题。
******代码段四
//相比前面代码增加了成员变量num,以及change函数增加同名形参。public class Example{          String str = new String("good");          char[] ch = {'a','b','c'};          int num = 9;          public static void main(String[] args)          {                  Example ex = new Example();                  ex.change(ex.str, ex.ch,ex.num);                  System.out.print(ex.str+"......and.....");                  System.out.println(ex.ch);                  System.out.println(ex.num);          }          public void change(String str,char ch[],int num)          {                     str = "test ok";                     ch[0]='g';                     num = 11;          }}
2.关于值传递和引用传递的说法。查过资料说,String是对象类型,但有值传递的特征。我的理解是,同样如代码段四。这里是不是可以这么理解,具有值传递特征的变量才会出现局部变量和成员变量的问题。
3.如代码段五。这一段不需要任何解释了。结合上面一条说法,大家可以自己思考下。
******代码段五
class Person{         String name;         int age;         Person(String name,int age)         {                    this.name = name;                    this.age = age;         }} 

最后,我觉得可以这样分析这道面试题(有错之处欢迎跟帖纠正):
因为String类以及Stirng类几乎所有成员变量都被final修饰,所以String是不可变的,因此String虽然是对象,但有值传递的特征。在java中,当函数的形参与成员变量同名时,可以使用this来区分。这里change函数里没有添加this,所以结果为good gbc。

===========================================================================================
        String、StringBuilder、StringBuffer

StringBuilder是1.5才出现的。与StringBuffer有至少两个不一样的地方。第一,名字不一样(废话)。第二,StringBuilder是线程非安全的。也就是说,频繁的操作中不需要判断同步锁,在单线程情况下,可以大大提高效率。帮助文档里是这样介绍的:一个可变的字符序列。此类提供一个与StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。如果可能,建议优先采用该类,因为在大多数实现中,它比 StringBuffer 要快。我打开两份帮助文档,分别找到StringBuilder和StringBuffer,一一比对,从上到下,每一个方法都是一模一样的。所以,再没什么好说的了。
既然官方文档说字符串缓冲区被单个线程使用的情况很普遍,不如介绍下StringBuilder里的一部分方法实在:
在 StringBuilder 上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符添加或插入到字符串生成器中。append 方法始终将这些字符添加到生成器的末端;而 insert 方法则在指定的点添加字符。其它的方法,都参照帮助文档吧。之所以前面加个String,是这里有必要说明一下String与StringBuilder的区
别(StringBuffer是一样的了)。如前文叙述,String是不可变的。通过以下两个语句来看看String是怎样完成字符串的串联操作的。
String str = new String("hello");
str += " java";
1.第一句,首先,检查常量池是否有"hello",没有则创建"hello",再将"hello"复制到new的堆空间,让str指向堆中的"hello"。
2.第二句,在常量池中创建" java"。但没有指向。然后创建一个StringBuilder对象(1.5前是StringBuffer),调用append(),将字符串串联
后,通过StringBuilder.toString()转化为String。也即使说,String的串联操作是通过StringBuilder完成的。而且整个过程中创建了四个对象。创建对象以及频繁操作后多余的对象要被gc回收,都是非常耗费时间的。所以说StringBuilder效率高。可以自己试着写小程序来比较运行时间。(差距是非常大的)。