zoukankan      html  css  js  c++  java
  • 浅析Java中字符串初始化new String()和直接赋值的区别、数组初始化时用new与不用new的区别

      首先明白一个事,Java存在一个常量池,可以用来存储字符串常量。

    一、创建的字符串变量在内存中的区别

      对于字符串:其对象的引用都是存储在栈中的,如果是编译期已经创建好(直接用双引号定义的)的就存储在常量池中,如果是运行期(new出来的)才能确定的就存储在堆中。对于equals相等的字符串,在常量池中永远只有一份,在堆中有多份。

      例如:String str1="ABC"; 和String str2 = new String("ABC"); 两者看似都是创建了一个字符串对象,但在内存中确是各有各的想法。

    1、String str1="ABC" :可能创建一个对象或者不创建对象。

      如果"ABC"这个字符串在 Java String 池里不存在,会在 Java String 池创建一个String对象("ABC")。如果已经存在,str1直接reference to 这个String池里的对象。

      在编译期,JVM会去常量池来查找是否存在“ABC”,如果不存在,就在常量池中开辟一个空间来存储“ABC”;如果存在,就不用新开辟空间。然后在栈内存中开辟一个名字为str1的空间,来存储“ABC”在常量池中的地址值。

    2、String str2 = new String("ABC") :至少创建一个对象,也可能两个。

      因为用到 new 关键字,会在heap堆中创建一个 str2 的String 对象,它的value 是 "ABC"。同时,如果"ABC"这个字符串在 Java String 池里不存在,也会在 Java String 池创建一个String对象("ABC")。

      在编译阶段JVM先去常量池中查找是否存在“ABC”,如果不存在,则在常量池中开辟一个空间存储“ABC”。在运行时期,通过String类的构造器在堆内存中new了一个空间,然后将String池中的“ABC”复制一份存放到该堆空间中,在栈中开辟名字为str2的空间,存放堆中new出来的这个String对象的地址值。

      也就是说,前者在初始化的时候可能创建了一个对象,也可能一个对象也没有创建;后者因为new关键字,至少在内存中创建了一个对象,也有可能是两个对象。

    二、String类的特性

      String类 是final修饰的,不可以被继承。

      String类的底层是基于char数组的。

    三、intern() 方法

      String 有一个intern() 方法,用来检测在String pool是否已经有这个String存在。

    public String intern()
    // 返回字符串对象的规范化表示形式

      一个初始时为空的字符串池,它由类 String 私有地维护。

      当调用 intern 方法时,如果常量池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并且返回此 String 对象的引用。

      它遵循对于任何两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true。

      所有字面值字符串和字符串赋值常量表达式都是内部的。

      返回:一个字符串,内容与此字符串相同,但它保证来自字符串池中。

    // 考虑下面的问题:
    String str1 = new String("ABC");
    String str2 = new String("ABC");
    
    // str1 == str2 的值是True 还是False呢? False.
    
    String str3 = "ABC";
    String str4 = "ABC";
    
    String str5 = "A" + "BC";
    
    // str3 == str4 的值是True 还是False呢? True.
    // str3 == str5 的值是True 还是False呢? True.
    
    // 在写代码的时候,一般不要 String str2 = new String("ABC");
    
    String a = "ABC";
    String b = "AB";
    String c = b+"C";
    System.out.println(a==c); // false

      a和b都是字符串常量所以在编译期就被确定了!

      而c中有个b是引用不是字符串常量,所以不会在编译期确定

      而String是final的!所以在b+"c"的时候实际上是新创建了一个对象,然后在把新创建对象的引用传给c。

    public static void main(String[] args) throws Exception {  
            String a =  "b" ;   
            String b =  "b" ;   
              
            System.out.println( a == b);   
            String d = new String( "d" ).intern() ; 
            String c = "d" ;  
            //String d = new String( "d" ).intern() ;   
            System.out.println( c == d);  
            System.out.println("------------------"); 
            String d1 = new String( "d" ) ; 
            String e1=d1.intern();
            String c1 = "d" ;  
            //String d = new String( "d" ).intern() ;   
            System.out.println( c1 == d1);  
            System.out.println( c1 == e1);  
            System.out.println( e1 == d1); 
            System.out.println("------------------"); 
            String s1=new String("kvill"); 
            String s2=s1.intern(); 
            System.out.println( s1==s2 ); //s1=s1.intern()
            System.out.println( s1+" "+s2 ); 
            System.out.println( s2==s1.intern() ); 
        }

      运行结果:

    true
    true
    ------------------
    false
    true
    false
    ------------------
    false
    kvill kvill
    true

      s1==s1.intern()为false说明原来的“kvill”仍然存在;

    String s1 = "china"; 
    String s2 = "china";
    String s3 = "china"; 
    String ss1 = new String("china"); 
    String ss2 = new String("china"); 
    String ss3 = new String("china"); 

      这里解释一下,对于通过 new 产生一个字符串(假设为 ”china” )时,会先去常量池中查找是否已经有了 ”china” 对象,如果没有则在常量池中创建一个此字符串对象,然后堆中再创建一个常量池中此 ”china” 对象的拷贝对象。

      也就是有道面试题: String s = new String(“xyz”); 产生几个对象?

      一个或两个。如果常量池中原来没有 ”xyz”, 就是两个。如果原来的常量池中存在“xyz”时,就是一个。

      对于基础类型的变量和常量:变量和引用存储在栈中,常量存储在常量池中。

    四、性能与安全

    1、性能效率

      String类被设计成不可变(immutable)类,所以它的所有对象都是不可变对象。例如:

    String str = “hello";
    str = str + "world“;

      所以当上文str指向了一个String对象(内容为“hello”),然后对str进行“+”操作,str原来指向的对象并没有变,而是str又指向了另外一个对象(“hello world”),原来的对象还在内存中。

      由此也可以看出,频繁的对String对象进行修改,会造成很大的内存开销。此时应该用StringBuffer或StringBuilder来代替String

      而 new String() 更加不适合,因为每一次创建对象都会调用构造器在堆中产生新的对象,性能低下且内存更加浪费。

    2、安全性

      对象都是只读的,所以多线程并发访问也不会有任何问题。

      由于不可变,用来存储数据也是极为安全的。

    五、博客推荐

      这一篇写的很详细,推荐阅读。

      Java的string类常量池及不可变性:https://blog.csdn.net/u010887744/article/details/50844525

    六、数组初始化时用new与不用new的区别

      不同于String类,String由于实现了常量池,所以new 和不new 有区别:new的话,引用变量指向堆区。不new的话,引用变量指向常量池。

      而对于数组的定义,初始化时用new与不用new 没区别,只是两种方式罢了,因为数组是引用数据类型,建立对象时,无论用不用new,数组实体都是放在堆内存中,引用变量放在栈内存。

    参考文章:

    https://blog.csdn.net/u014082714/article/details/50087563

    https://blog.csdn.net/qq_33417486/article/details/82787598

  • 相关阅读:
    Lambda表达式、依赖倒置
    ASP.NET vNext 概述
    Uname
    RHEL4 i386下安装rdesktop【原创】
    Taxonomy of class loader problems encountered when using Jakarta Commons Logging(转)
    How to decompile class file in Java and Eclipse
    先有的资源,能看的速度看,不能看的,抽时间看。说不定那天就真的打不开了(转)
    Google App Engine 学习和实践
    【VBA研究】VBA通过HTTP协议实现邮件轨迹跟踪查询
    js正則表達式语法
  • 原文地址:https://www.cnblogs.com/goloving/p/14875086.html
Copyright © 2011-2022 走看看