Java基础小技巧回顾--浅析String ==操作

Posted 软件猫

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java基础小技巧回顾--浅析String ==操作相关的知识,希望对你有一定的参考价值。

原文地址:http://blog.csdn.net/xieyuooo/article/details/6859160


本文非常简单,不过有很多朋友经常问,网上很多例子也写个大概,很多人也只是知道和大概,就本文而来读起来非常的轻松,不过算是一些小技巧;但是我们的程序中相信用得最多的就是char数组和byte[]数组,而String就是由char[]数组组成的,一般情况下我们就可以认为String用得是最多的对象之一。


有关Sring的空间利用率方面,这里不想多说,只能说很低很低,尤其是你定义的String长度很短的时候,简直利用率不好说;在前序的一篇文章中说明了关于java的对象空间申请方法以及对象在JVM内部如何做对其的过程,就能较为明确的知道一个String是多么的浪费空间;本文就不在多提及这方面的问题了。


再谈及到String与StringBuffer和StringBuilder的区别时,前面一篇文章中将他们循环做了一系列的性能对比,发现StringBuilder性能最高,大家都知道用StringBuilder来用了,但是要明白细节才是最好的;简单来讲String是不可变的字符串,而StringBuffer和StringBuilder是可变的字符串对象,而StringBuffer是在进行内容修改时(即char数组修改)会进行线程同步操作,在同步过程中存在征用加锁和访问对象的过程,开销较大,在方法内定义的局部变量中没有必要同步,因为就是当前线程使用,所以StringBuilder为一个非同步的可变字符串对象。


OK,我们介绍了基本的概念,可以回到正题了;那么String到底是一个神马东西,通过前面的对象结构来看,首先根据String内部的定义,应该有以下内容:一个char数组指针指向一个数组对象(数组对象也是一个对象,和普通对象最大的区别需要一个位置来记录数组的长度)、offset、count、hash、serialVersionUID(这个不用计算在对象的大小中,因为在JVM启动时就会被装入到方法区中)。其次,还有对象对其的过程,而String的内容为char数组引用,指向的数组对象的内部的内容,也就是一个String相当于就包含了两个对象,两个对象都有头部,以及对其方式,数组头部会多一个保存数组长度的区域,头部还会存储对象加锁状态、唯一标识、方法区指针、GC中的Mark标志等等相应的内容,如果头部存储空间不够就会在外部开辟一个空间来存储,内部用一个指针指向那块空间;另外对象会按照8byte对其方法进行对其,即对象大小不是8byte的倍数,将会填充,方便寻址。


String经常说是不可变的字符串,但是我个人并不习惯将他说成是常量,而很多人也对String字符串不可变以及StringBuilder可变有着很多疑惑之处,String可以做+,为什么说它不可变呢?String的+到底做了什么?有人说String还有一些内容可能会放在常量池,这是什么东西?常量池和常量池的字符串拼接结果是什么(我曾在网上看到有人写常量池中字符串和常量池中字符串拼接结果还在常量池,其实未必,后面我们用事实来说话)?


当你对上述问题了如指掌,String你基本了解得有点通透了;OK,在解释这个问题之前,我们先说明一个在Hotspot自从分代JVM产生后到目前为止(G1还没有正式出来之前)不变的道理就是,当你在程序中只要使用了new关键字或者通过任何反射机制实例化的任何对象都将首先放在堆当中,当然一般情况下首先是放在Eden空间中(在一些细节的版本中会有一些区别,如启动了TABL、或对象超过指定大小直接进入Old或对象连Eden也放不下也会直接进入Old);这是不用说的事实,总之目前我们只要知道它肯定是在堆当中的就可以了。


我们先来看一段非常非常简单的代码如下所示:

[java]  view plain  copy  
  1. public class StringTest   
  2.   
  3.     public static void main(String[] args)   
  4.         String a = "abc";  
  5.         String b = "def";  
  6.           
  7.         String c = a + b;  
  8.         String d = "abc" + "def";  
  9.           
  10.         String e = new String("abc");  
  11.           
  12.         System.out.println(a == e);  
  13.         System.out.println(a.equals(e));  
  14.         System.out.println(a == "abc");  
  15.         System.out.println(a == e.intern());  
  16.         System.out.println(c == "abcdef");  
  17.         System.out.println(d == "abcdef");  
  18.       
  19.   

请在没有在java上运行前猜猜结果是多少,然后再看结果。



结果如下:

false
true
true
true
false
true


如果你的结果不是猜得,而是直接自己通过理解得到的,后面的文章你就不用看了,对你来说应该没有多大意义,如果你某一个结果说得不对,或者是自己瞎猜出来的,OK,后文可能会对你的理解造成一些影响。


我们首先解释前面4个结果,再解释最后2个结果;前4个其实在前面的文章中已经说过他们的区别,不过为了方便文本继续向下说明,这里再说明一次,首先String a = "abc"这样的申请,会将对象放入常量池中,也就是放在Perm Geration中的,而String e = new String("abc")这个对象是放在Eden空间的,所以当使用a == e发生地址对比,两者肯定结果是不一样的;而当发生a == "abc"两个地址是一样的,都是指向常量池的对应对象的首地址;而equals是对比值不用多说,肯定是一样的;a == e.intern()为什么也是true呢,就是当intern()这个方法发生时,它会在常量池中寻找和e这个字符串等值的字符串(匹配的方法为equals),如果没有发现则在常量池申请一个一样的字符串对象,并将对象首地址范围,如果发现了则直接范围首地址;而a是常量池中的对象,所以e在常量池中就能找到的地址就是a的首地址;关于这个问题就不多阐述了,也有相关的很多说明,下面说下后面两个结果;算是较为神奇的结果,也是另很多人纳闷的结果,不过不用着急,说完后就很简单了。


后面两个结果一个是a指向常量池的“abc”,b指向常量池中的“def”,c是通过a和b相加,两个都是常量池对象;而d是直接等价于“abc”+“def”按照道理说,两个也是常量池对象,为什么两个对象和常量池的“abcdef”比较的结果不一样呢?(关于他们为什么是在常量池就不多说了,上面那一段已经有结果了);我们不管怎么样,首先秒杀掉一句话就是:常量池的String+常量池String结果还在常量池,这句话是不正确的,或者你的测试用例正好是后者,那么你中招了,很多事情只是通过测试也未必能得出非常有效的结果,但是较为全面的测试会让我们得出更多的结论,看看我们两种几乎一摸一样的测试,但是结果竟然是不一样的;简单说结果是前者的对象结果不是在常量池中(记住,常量池中同一个字符串肯定是唯一的),后者的结果肯定在常量池;为什么,不是我说的,是Hotspot VM告诉我的,我们做一个简单的小实验,就知道是为什么了,首先将代码修改成这样:

[java]  view plain  copy  
  1. public class StringTest   
  2.   
  3.     public static void main(String[] args)   
  4.         String a = "abc";  
  5.         String b = "def";  
  6.           
  7.         String c = a + b;  
  8.       
  9.   

我们看看编译完成后它是个什么样子:

C:\\>javac StringTest.java

C:\\>javap -verbose StringTest

[java]  view plain  copy  
  1. Compiled from "StringTest.java"  
  2. public class StringTest extends java.lang.Object  
  3.   SourceFile: "StringTest.java"  
  4.   minor version: 0  
  5.   major version: 50  
  6.   Constant pool:  
  7. const #1 = Method       #9.#18//  java/lang/Object."<init>":()V  
  8. const #2 = String       #19;    //  abc  
  9. const #3 = String       #20;    //  def  
  10. const #4 = class        #21;    //  java/lang/StringBuilder  
  11. const #5 = Method       #4.#18//  java/lang/StringBuilder."<init>":()V  
  12. const #6 = Method       #4.#22//  java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;  
  13. const #7 = Method       #4.#23//  java/lang/StringBuilder.toString:()Ljava/lang/String;  
  14. const #8 = class        #24;    //  StringTest  
  15. const #9 = class        #25;    //  java/lang/Object  
  16. const #10 = Asciz       <init>;  
  17. const #11 = Asciz       ()V;  
  18. const #12 = Asciz       Code;  
  19. const #13 = Asciz       LineNumberTable;  
  20. const #14 = Asciz       main;  
  21. const #15 = Asciz       ([Ljava/lang/String;)V;  
  22. const #16 = Asciz       SourceFile;  
  23. const #17 = Asciz       StringTest.java;  
  24. const #18 = NameAndType #10:#11;//  "<init>":()V  
  25. const #19 = Asciz       abc;  
  26. const #20 = Asciz       def;  
  27. const #21 = Asciz       java/lang/StringBuilder;  
  28. const #22 = NameAndType #26:#27;//  append:(Ljava/lang/String;)Ljava/lang/StringBuilder;  
  29. const #23 = NameAndType #28:#29;快速回顾C#基础(编程的小技巧)待完善

    浅析关于java的一些基础问题(上篇)

    Java小技巧:万字长文总结Java多进程

    后端开发Java基础学习(适合有其它语言基础)

    [ Java学习基础 ] 浅析Java方法调用

    Java基础知识回顾之二 ----- 修饰符和String