String类型深度解析

上一篇在介绍java内存结构时提到了方法区这块,本地方法区存在一块特殊的内存区域,叫常量池(Constant Pool),这块内存将与String类型的分析密切相关。

1.String的本质

首先看下面一段代码:

               String str1 = "hadoop";
		String str2 = "android";
		str1 = str1+str2;
		System.out.println(str1)
输出结果可想而知:hadoopandroid.

我们都知道String是值不可变(immutable)的常量,是线程安全的(can be shared)。为什么此时会输出hadoopandroid了?首先在栈中有个str1变量指向堆中的"hadoop"对象,栈中"str2"变量指向堆中的"android"对象,当执行到str1 = str1 + str2时,系统重新在堆中new一个更大的数组出来,然后将"hadoop"和"android"都复制进去,然后栈中的str1指向这个新new出来的数组。所谓的不可变是指:它没有在组"hadoop"上进行修改,而是新建了个更大数组进行扩展,也就是说,这时候堆里还是有"hadoop"这个对象数组存在的,只不过这个时候str1不再指向这个"hadoop"数组了,而是指向了另外一个新建的数组。

  打开String的源码,类注释中有这么一段话“Strings are constant; their values cannot be changed after they are created. String buffers support mutable strings.Because String objects are immutable they can be shared.”。这句话总结归纳了String的一个最重要的特点:String是值不可变(immutable)的常量,是线程安全的(can be shared)
    接下来,String类使用了final修饰符,表明了String类的第二个特点:String类是不可继承的。
    下面是String类的成员变量定义,从类的实现上阐明了String值是不可变的(immutable)
            private final char value[];
            private final int count; 
     因此,我们看String类的concat方法。实现该方法第一步要做的肯定是扩大成员变量value的容量,扩容的方法重新定义一个大容量的字符数组buf。第二步就是把原来value中的字符copybuf中来,再把需要concat的字符串值也copybuf中来,这样子,buf中就包含了concat之后的字符串值。下面就是问题的关键了,如果value不是final的,直接让value指向buf,然后返回this,则大功告成,没有必要返回一个新的String对象。但是。。。可惜。。。由于valuefinal型的,所以无法指向新定义的大容量数组buf,那怎么办呢?“return new String(0, count + otherLen, buf);”,这是Stringconcat实现方法的最后一条语句,重新new一个String对象返回。这下真相大白了吧!

     总结:String实质是字符数组,两个特点:1、该类不可被继承;2、不可变性(immutable)

2.String常见的定义方法及误区

1)

         String a = "abc"; //.1
         String b = "abc"; //.2
         System.out.println(a==b);
输出结果为true。

分析:
  1代码执行后在常量池(constant pool)中创建了一个值为abc的String对象,2执行时,因为常量池中存在"abc"所以就不再创建新的String对象了栈中两个引用地址同时指向"abc"。

2)

       String   c   =   new   String("xyz");①
       String   d   =   new   String("xyz");②<span style="font-family: Arial, Helvetica, sans-serif;">	</span>
<pre name="code" class="java" style="color: rgb(51, 51, 51); letter-spacing: 0.5px; line-height: 22.5px;">       System.out.println(c==d);

 
分析:①Class被加载时,"xyz"被作为常量读入,在常量池(constant pool)里创建了一个共享的值为"xyz"的String对象;然后当调用到new String("xyz")的时候,会在堆(heap)里创建这个new   String("xyz")对象;②由于常量池(constant pool)中存在"xyz"所以不再创建"xyz",然后创建新的new String("xyz")。在堆中创建了两个对象,栈中引用对象地址不一样,故输出false. 

3)

    String   s1   =   new   String("xyz");     //创建二个对象(常量池和堆中),一个引用 
  String   s2   =   new   String("xyz");     //创建一个对象(堆中),并且以后每执行一次创建一个对象,一个引用 

  String   s3   =   "xyz";     //创建一个对象(常量池中),一个引用   
  String   s4   =   "xyz";     //不创建对象(共享上次常量池中的数据),只是创建一个新的引用
System.out.println(s1==s3) //false

这里在进行详细分析。第一种方式通过关键字new定义过程:在程序编译期,编译程序先去字符串常量池检查,是否存在“xyz,如果不存在,则在常量池中开辟一个内存空间存放“xyz”;如果存在的话,则不用重新开辟空间,保证常量池中只有一个“xyz”常量,节省内存空间。然后在内存堆中开辟一块空间存放new出来的String实例,在栈中开辟一块空间,命名为“s1”,存放的值为堆中String实例的内存地址,这个过程就是将引用s1指向new出来的String实例。各位,最模糊的地方到了!堆中new出来的实例和常量池中的“xyz”是什么关系呢?看下图分析:

常量池中的xyz实际上是通过其地址把String.value的值赋给堆中的实例。从String类的public String(String original) {
int size = original.count;
char[] originalValue = original.value;这两行代码也可以看出来,其实就相当于一份拷贝而已,然后栈中引用s1指向堆中的实例,当接着执行String s3 = "xyz";时,因为池中已经存在“xyz”的实例对象,则s3直接指向池中的实例对象;否则,在池中先创建一个实例对象,s3再指向它。

 3StringStringBufferStringBuilder的联系与区别 
        上面已经分析了String的本质了,下面简单说说StringBuffer和StringBuilder。

     StringBuffer和StringBuilder都继承了抽象类AbstractStringBuilder,这个抽象类和String一样也定义了char[] value和int count,但是与String类不同的是,它们没有final修饰符。因此得出结论:String、StringBuffer和StringBuilder在本质上都是字符数组,不同的是,在进行连接操作时,String每次返回一个新的String实例,而StringBuffer和StringBuilder的append方法直接返回this,所以这就是为什么在进行大量字符串连接运算时,不推荐使用String,而推荐StringBuffer和StringBuilder。那么,哪种情况使用StringBuffe?哪种情况使用StringBuilder呢?         

     关于StringBuffer和StringBuilder的区别,翻开它们的源码,下面贴出append()方法的实现。

public synchronized StringBuffer append(String str) {
	super.append(str);
        return this;
    }
public StringBuilder append(String str) {
	super.append(str);
        return this;
    }
上面第一张图是StringBuffer中append()方法的实现,第二张图为StringBuilder对append()的实现。区别应该一目了然,StringBuffer在方法前加了一个synchronized修饰,起到同步的作用,可以在多线程环境使用。为此付出的代价就是降低了执行效率。 因此,如果在多线程环境可以使用StringBuffer进行字符串连接操作,单线程环境使用StringBuilder,它的效率更高。
以上只是个人理解,如有错误忘指出。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值