JavaSE003_String类总结之内存分析

先上实例代码:

public class StringDemo {

	public static void main(String[] args) {  
        /** 
         * 情景一:字符串池 
         * JAVA虚拟机(JVM)中存在着一个字符串池,其中保存着很多String对象; 
         * 并且可以被共享使用,因此它提高了效率。 
         * 由于String类是final的,它的值一经创建就不可改变。 
         * 字符串池由String类维护,我们可以调用intern()方法来访问字符串池。  
         */  
        String s1 = "abc";     
        //↑ 在字符串池创建了一个对象  
        String s2 = "abc";     
        //↑ 字符串池已经存在对象“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"都是常量,因此变量str1的值在编译时就可以确定。 
         * 这行代码编译后的效果等同于: String str1 = "abcd"; 
         */  
        String str1 = "ab" + "cd";  //1个对象  
        String str11 = "abcd";   
        //↑ 字符串池已经存在对象“abcd”(共享),所以创建0个对象,累计创建一个对象  
        System.out.println("str1 = str11 : "+ (str1 == str11));  
        //↑ true 指向同一个对象,
        //↑------------------------------------------------------over  
        /** 
         * 情景四:  
         * 局部变量str2,str3存储的是存储两个拘留字符串对象(intern字符串对象)的地址。  
         * 第三行代码原理(str2+str3): 
         * 		运行期JVM首先会在堆中创建一个StringBuilder类, 
         * 		同时用str2指向的拘留字符串对象完成初始化, 
         * 		然后调用append方法完成对str3所指向的拘留字符串的合并, 
         * 		接着调用StringBuilder的toString()方法在堆中创建一个String对象, 
         * 		最后将刚生成的String对象的堆地址存放在局部变量str4中。
         * 而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));  
        //↑false	str6为变量,在运行期才会被解析。  
        final String str8 = "b";  
        String str9 = "a" + str8;  
        String str89 = "ab";  
        System.out.println("str9 = str89 : "+ (str9 == str89));  
        //↑true		str8为常量变量,编译期会被优化  
        //↑------------------------------------------------------over  
    }

}

分析:

1.String类初始化后是不可变的

   比如:String str=”kv”+”ill”+” ”+”ans”; 就是有4个字符串常量,首先”kv”和”ill”生成了”kvill”存在内存中,然后”kvill”又和” ” 生成 “kvill “存在内存中,最后又和”ans”生成了”kvill ans”;并把这个字符串的地址赋给了str,就是因为String的”不可变”产生了很多临时变量,这也就是为什么建议用StringBuffer的原因了,因为StringBuffer是可改变的。 

2、String中的final用法和理解

   final StringBuffer a = new StringBuffer("111"); 

  final StringBuffer b = new StringBuffer("222"); 

  a=b;//此句编译不通过  
  a.append("222");// 编译通过 
  可见,final只对引用的"值"(即内存地址)有效,它迫使引用只能指向初始指向的那个对象,改变它的指向会导致编译期错误。至于它所指向的对象的变化,final是不负责的。 

   final StringBuffer a = new StringBuffer("111"); 

   代码中的字符串常量在编译的过程中收集并放在class文件的常量区中,如"123"、"123"+"456"等,含有变量的表达式不会收录,如"123"+a。

   JVM在加载类的时候,根据常量区中的字符串生成常量池,每个字符序列如"123"会生成一个实例放在常量池里,这个实例是不在堆里的,也不会被GC,这个实例的value属性从源码的构造函数看应该是用new创建数组置入123的,此时value存放的字符数组地址是在堆里。

3、使用String不一定创建对象

   在执行到双引号包含字符串的语句时,如String a = "123",JVM会先到常量池里查找,如果有的话返回常量池里的这个实例的引用,否则的话创建一个新实例并置入常量池里。如果是 String a = "123" + b (假设b是"456"),前半部分"123"还是走常量池的路线,但是这个+操作符其实是转换成[SringBuffer].Appad()来实现的,所以最终a得到是一个新的实例引用,而且a的value存放的是一个新申请的字符数组内存空间的地址(存放着"123456"),而此时"123456"在常量池中是未必存在的。

   要注意: 我们在使用诸如String str = "abc";的格式定义类时,总是想当然地认为,创建了String类的对象str。担心陷阱!对象可能并没有被创建!而可能只是指向一个先前已经创建的对象。只有通过new()方法才能保证每次都创建一个新的对象

4、使用new String,一定创建对象

   在执行String a = new String("123")的时候,首先走常量池的路线取到一个实例的引用,然后在堆上创建一个新的String实例,走以下构造函数给value属性赋值,然后把实例引用赋值给a:

public String(String original) {
    int size = original.count;
    char[] originalValue = original.value;
    char[] v;
      if (originalValue.length > size) {
         // The array representing the String is bigger than the new
         // String itself.  Perhaps this constructor is being called
         // in order to trim the baggage, so make a copy of the array.
            int off = original.offset;
            v = Arrays.copyOfRange(originalValue, off, off+size);
     } else {
         // The array representing the String is the same
         // size as the String, so no point in making a copy.
        v = originalValue;
     }
    this.offset = 0;
    this.count = size;
    this.value = v;
    }

   从中我们可以看到,虽然是新创建了一个String的实例,但是value是等于常量池中的实例的value,即是说没有new一个新的字符数组来存放"123"。

如果是String a = new String("123"+b)的情况,首先看回第3点,"123"+b得到一个实例后,再按上面的构造函数执行。

5、String、StringBuffer、StringBuilder的区别

(1)三者在执行速度方面的比较

StringBuilder >  StringBuffer  >  String

String <(StringBuffer,StringBuilder)的原因:

    String:字符串常量

    StringBuffer:字符变量

    StringBuilder:字符变量

    从上面的名字可以看到,String是“字符常量”,也就是不可改变的对象。对于这句话的理解你可能会产生这样一个疑问  ,比如这段代码: 

String s = "abcd";
s = s+1;
System.out.print(s);// result : abcd1

    我们明明就是改变了String型的变量s的,为什么说是没有改变呢? 其实这是一种欺骗,JVM是这样解析这段代码的:首先创建对象s,赋予一个abcd,然后再创建一个新的对象s用来执行第二行代码,也就是说我们之前对象s并没有变化,所以我们说String类型是不可改变的对象了,详见第3点,由于这种机制,每当用String操作字符串时,实际上是在不断的创建新的对象,可想而知这样执行效率会有多低。

  而StringBuffer与StringBuilder就不一样了,他们是字符串变量,是可改变的对象,每当我们用它们对字符串做操作时,实际上是在一个对象上操作的,这样就不会像String一样创建一些而外的对象进行操作了,当然速度就快了。

下面举一个特殊的例子:

String str = “This is only a” + “ simple” + “ test”;
StringBuffer builder = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”);

你会很惊讶的发现,生成 str 对象的速度简直太快了,而这个时候 StringBuffer 居然速度上根本一点都不占优势。其实这是 JVM 的一个把戏,实际上:

String str = “This is only a” + “ simple” + “test”;

其实就是:

    String str = “This is only a simple test”;

    所以不需要太多的时间了。但大家这里要注意的是,如果你的字符串是来自另外的String对象的话,速度就没那么快了,譬如:

    String str2 = “This is only a”;

    String str3 = “ simple”;

    String str4 = “ test”;

    String str1 = str2 +str3 + str4;

    这时候JVM会规规矩矩的按照原来的方式去做。

(2)StringBuilder与 StringBuffer

    StringBuilder:线程非安全的

    StringBuffer:线程安全的

    当我们在字符串缓冲去被多个线程使用是,JVM不能保证StringBuilder的操作是安全的,虽然他的速度最快,但是可以保证StringBuffer是可以正确操作的。当然大多数情况下就是我们是在单线程下进行的操作,所以大多数情况下是建议用StringBuilder而不用StringBuffer的,就是速度的原因。 

(3)三者使用场景 

如果要操作少量的数据用 = String

单线程操作字符串缓冲区下操作大量数据 = StringBuilder

多线程操作字符串缓冲区 下操作大量数据 = StringBuffer


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值