Java中String的探究

1. String的基本介绍
        相关API
获取信息操作  
  字符串长度 length()
  比较字符串引用 “==”
  比较字符串内容 equals()  或 compareTo()
  已知位置,找字符 charAt()
  已知字符(串),找位置 indexOf() 或 lastIndexOf()
  判断开头和结尾 startWith()  或 endWith()
  其他类型转换为字符串 valueOf()
更改操作  
  连接字符串 “+” 或者 concat()
  替换子字符串 replace()
  获取子字符串 subString()
  分割字符串 split()
  更换大小写 toUpperCase()、toLowerCase()
  去除空白符 trim()
        String是常量,我们常称其为不可变性,意思是一旦创建就不能更改。
        所以对于上面的“更改操作”,返回值都是字符串,但并不是对源字符串进行更改,而是返回了新的字符串。下面有测试代码。
小实验:如果查看Java API文档中对String的方法的说明,会发现用了很多的“return a new String ”。
2. 验证String的不可变性
      所谓String的不可变性,是说一旦字符串被创建,对其所做的任何修改都会生成新的字符串对象。
代码如下:
public static void main(String[] args) {  
    String a = "abc";  
    String b = a.toUpperCase();  
    System.out.println("a: " + a);  
    System.out.println("b: " + b);  
    System.out.println("a==b: "+ (a==b));  
      
    //当a不发生变化时,不返回新字符串。   
    String c = a.toLowerCase();  
    System.out.println("c: " + c);  
    System.out.println("a==c: "+ (a==c));  
}  
运行结果:
[java] view plaincopyprint?
a: abc  
b: ABC  
a==b: false  
c: abc  
a==c: true  
运行结果分析:
           字符串a指向"abc",全为小写;字符串b由a得来,指向"ABC";这时a的内容并没有变化,也就证明了Java中String的不变性。
           后面利用"a==b"来判断a,b是否指向同一个对象,返回值为false,也能证明String的不变性。

           对于字符串c的例子说明如果a没有发生变化,那么不返回也不需要返回新字符串,所以"a==c"的返回值为tru           

String类不可变性的解释:

   对于String常量,它的值是在常量池中的。而JVM中的常量池在内存当中是以表的形式存在的, 对于String类型,有一张固定长度的CONSTANT_String_info表用来存储文字字符串值,注意:该表只存储文字字符串值,不存储符号引用。说到这里,对常量池中的字符串值的存储位置应该有一个比较明了的理解了。在程序执行的时候,常量池会储存在Method Area,而不是堆中。常量池中保存着很多String对象; 并且可以被共享使用,因此它提高了效率。

 当创建一个新的String时,首先会在JVM常量池中寻找。如果常量池中存在,则直接将字符串的引用指向该常量的地址;若常量池不存在,则创建新的字符串并存到常量池中,同时将字符串的引用指向新创建的常量的地址。在对已有引用重新赋值时,只是引用指向了新的常量,原来创建的常量并没有改变。



               String  s="abcd";                                                                                                                   String s2=s;


                 

s=s.concat("ef“)

关于String str1="hello"; 和 String str2= new String("hello");在JVM中内存分配的区别:

首先简要了解一下JVM的基本知识:


编译器(或者JVM)为了更高效地处理数据,会用不同的算法把内存分为各种区域,不同的区域拥有各自的特性,Java中,内存可以分为栈,堆,静态域和常量池等。(可能有不同的叫法,但逻辑是一致的)

不同内存区域的功能和特点:

栈区:存放局部变量(变量名,对象的引用等)特点:内存随着函数的调用而开辟,随着函数调用结束而释放。

堆区:存放对象(也就是new出来的东西)特点:可以跨函数使用,每个对象有自己对应的存储空间。

静态域:存放在对象中用static定义的静态成员。

常量池:存放常量。(常量池指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。)




String str1 = "hello";:引用str1被存放在栈区,字符串常量"hello"被存放在常量池,引用str1指向了常量池中的"hello"(str1中的存放了常量池中"hello"的地址)。

Srtring  str2 = new String ("hello");:引用str2被存放在栈区,同时在堆区开辟一块内存用于存放一个新的String类型对象。(同上,str2指向了堆区新开辟的String类型的对象)

如下图:


      


这两种方法的区别是什么?

第一种:常量池的字符串常量,不能重复出现,也就是说,在定义多个常量时,编译器先去常量池查找该常量是否已经存在,如果不存在,则在常量池创建一个新的字符串常量;如果该常量已经存在,那么新创建的String类型引用指向常量池中已经存在的值相同的字符串常量,也就是说这是不在常量池开辟新的内存。

String str1 = "hello";

String str2 = "hello";

示意图如图1



第二种:在堆中创建新的内存空间,不考虑该String类型对象的值是否已经存在。换句话说:不管它的 只是多少,第二种方法的这个操作已经会产生的结果是:在堆区开辟一块新的内存,用来存放新定义的String类型的对象。

String str1 = new String("hello");

String str2 = new String("hello");

示意图如果2


                                                                                        


案例分析:

public static void main(String[] args) {  
        /** 
         * 情景一:字符串池 
         * JAVA虚拟机(JVM)中存在着一个字符串池,其中保存着很多String对象; 
         * 并且可以被共享使用,因此它提高了效率。 
         * 由于String类是final的,它的值一经创建就不可改变。 
         * 字符串池由String类维护,我们可以调用intern()方法来访问字符串池。  
         */  
        String s1 = "abc";     
        //↑ 在字符串池创建了一个对象  
        String s2 = "abc";     
        //↑ 字符串pool已经存在对象“abc”(共享),所以创建0个对象,累计创建一个对象  
        System.out.println("s1 == s2 : "+(s1==s2));    
        //↑ true 指向同一个对象,  
        System.out.println("s1.equals(s2) : " + (s1.equals(s2)));    
        //↑ true  值相等  
        //↑------------------------------------------------------over  
        /** 
         * 情景二:关于new String("") 
         *  
         */  
        String s3 = new String("abc");  
        //↑ 创建了两个对象,一个存放在字符串池中,一个存在与堆区中;  
        //↑ 还有一个对象引用s3存放在栈中  
        String s4 = new String("abc");  
        //↑ 字符串池中已经存在“abc”对象,所以只在堆中创建了一个对象  
        System.out.println("s3 == s4 : "+(s3==s4));  
        //↑false   s3和s4栈区的地址不同,指向堆区的不同地址;  
        System.out.println("s3.equals(s4) : "+(s3.equals(s4)));  
        //↑true  s3和s4的值相同  
        System.out.println("s1 == s3 : "+(s1==s3));  
        //↑false 存放的地区多不同,一个栈区,一个堆区  
        System.out.println("s1.equals(s3) : "+(s1.equals(s3)));  
        //↑true  值相同  
        //↑------------------------------------------------------over  
        /** 
         * 情景三:  
         * 由于常量的值在编译的时候就被确定(优化)了。 
         * 在这里,"ab"和"cd"都是常量,因此变量str3的值在编译时就可以确定。 
         * 这行代码编译后的效果等同于: String str3 = "abcd"; 
         */  
        String str1 = "ab" + "cd";  //1个对象  
        String str11 = "abcd";   
        System.out.println("str1 = str11 : "+ (str1 == str11));  
        //↑------------------------------------------------------over  
        /** 
         * 情景四:  
         * 局部变量str2,str3存储的是存储两个拘留字符串对象(intern字符串对象)的地址。 
         *  
         * 第三行代码原理(str2+str3): 
         * 运行期JVM首先会在堆中创建一个StringBuilder类, 
         * 同时用str2指向的拘留字符串对象完成初始化, 
         * 然后调用append方法完成对str3所指向的拘留字符串的合并, 
         * 接着调用StringBuilder的toString()方法在堆中创建一个String对象, 
         * 最后将刚生成的String对象的堆地址存放在局部变量str3中。 
         *  
         * 而str5存储的是字符串池中"abcd"所对应的拘留字符串对象的地址。 
         * str4与str5地址当然不一样了。 
         *  
         * 内存中实际上有五个字符串对象: 
         *       三个拘留字符串对象、一个String对象和一个StringBuilder对象。 
         */  
        String str2 = "ab";  //1个对象  
        String str3 = "cd";  //1个对象                                         
        String str4 = str2+str3;                                        
        String str5 = "abcd";    
        System.out.println("str4 = str5 : " + (str4==str5)); // false  
        //↑------------------------------------------------------over  
        /** 
         * 情景五: 
         *  JAVA编译器对string + 基本类型/常量 是当成常量表达式直接求值来优化的。 
         *  运行期的两个string相加,会产生新的对象的,存储在堆(heap)中 
         */  
        String str6 = "b";  
        String str7 = "a" + str6;  
        String str67 = "ab";  
        System.out.println("str7 = str67 : "+ (str7 == str67));  
        //↑str6为变量,在运行期才会被解析。  
        final String str8 = "b";  
        String str9 = "a" + str8;  
        String str89 = "ab";  
        System.out.println("str9 = str89 : "+ (str9 == str89));  
        //↑str8为常量变量,编译期会被优化  
        //↑------------------------------------------------------over  
    }

注意:1、使用String不一定创建对象。String s ="abc";若此时常量池中有abc,则引用直接指向该常量,此时不必创建新的常量。

            2、使用String s = new String("abc");一定创建新的对象。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章