美文网首页
Java系列3 常量池和字符串

Java系列3 常量池和字符串

作者: 莫小归 | 来源:发表于2018-08-07 15:37 被阅读0次

    参考:https://www.jianshu.com/p/c7f47de2ee80

    一.JVM相关概念

    1.class文件中的常量池
    • 存放 字面量(Literal) + 符号引用量(Symbolic References)
    • 字面量 = Java语言层面常量(如文本字符串,声明为final的常量值)
    • 符号引用量 = 类和接口的全限定名 + 字段名称和描述符 + 方法名称和描述符
    2.方法区中的运行时常量池
    • class文件中常量池的内容将在类加载后进入方法区的运行常量池
    • 运行常量池具备动态性,即运行期间也可将新常量放入常量池中
    3.常量池的好处
    • 节省内存空间:实现对象的共享
    • 节省运行时间:避免频繁创建和销毁对象

    二.8种基本类型的包装类和常量池

    • 包装类Byte,Short,Integer,Long,Character,Boolean实现了常量池技术
      这5种包装类在[-128,127]范围内在常量池存储值,超出此范围则仍然创建新对象
      Integer i1 = 40;
      Integer i2 = 40;
      System.out.println(i1==i2);  //输出TRUE
    
      Integer i1 = 400;
      Integer i2 = 400;
      System.out.println(i1==i2);//输出false
    
    • 浮点数包装类Float,Double没有实现常量池技术
    • 常量池应用场景
      1)Integer i1=40;
      Java在编译的时候会直接将代码封装成Integer i1=Integer.valueOf(40);从而使用常量池中的对象
      2)Integer i1 = new Integer(40);
      这种情况下会创建新的对象
      Integer i1 = 40;
      Integer i2 = new Integer(40);
      System.out.println(i1==i2);    //输出false
    
    • 示例
      判断以下语句的输出结果
    Integer i1 = 40;
    Integer i2 = 40;
    Integer i3 = 0;
    Integer i4 = new Integer(40);
    Integer i5 = new Integer(40);
    Integer i6 = new Integer(0);
    
    System.out.println("i1 = i2    " + (i1 == i2));
    System.out.println("i1 = i2 + i3  " + (i1 == i2 + i3 ));
    System.out.println("i1 = i4  " + ( i1 == i4 ));  
    System.out.println("i4 = i5  " + ( i4 == i5 ));
    System.out.println("i4 = i5 + i6  " + ( i4 == i5 + i6 ));
    System.out.println("40 = i5 + i6  " + ( 40 == i5 + i6 ));
    
    //输出结果为
    
    i1 = i2    true
    i1 = i2 + i3    true
    i1 = i4    false
    i4 = i5    false
    i4 = i5 + i6    true
    40 = i5 + i6    true
    

    注意:
    语句 i4 = i5 + i6
    + 操作符不适用Integer对象,故首先i5和i6自动拆箱完成数值相加,表达式变为 i4 == 40
    Integer对象无法与数值进行直接比较,所以i4自动拆箱转为int类型,表达式变为 40 == 40,返回真

    三.String和常量池

    1.String对象的两种创建方式
    • 不使用new,编译时创建,存于字符串常量池中:
    • 使用new运行时创建,存于
          String str1 = "abcd";          //在栈中创建引用str,然后搜索字符串常量池
                                                    //如果常量池有“abc",则令str指向常量池的"abc";否则则将"abc"放入常量池
          String str2 = new String("abcd"); 
          System.out.println(str1==str2);//false
    
    • 使用new时创建的是不同对象,不使用new则是多个引用指向常量池里的同一个值:
    String str1="abc";
    String str2="abc";
    System.out.println(str1==str2);  //true
    
    String str3=new String("abc");
    String str4=new String("abc"):
    System.out.println(Str3==str4);  //false
    
    2.字符串连接 “+” 和字符串不可变性
    • 只有使用引号包含文本的方式创建的String对象间使用“+”连接产生的新对象才被加入字符串常量池
      String str1 = "str";
      String str2 = "ing";
      
      String str3 = "str" + "ing";
      String str4 = str1 + str2;
      System.out.println(str3 == str4);//false  str4不是 引号包含文本 的方式创建的String对象
      
      String str5 = "string";
      System.out.println(str3 == str5);//true
    

    Java字符串拼接参考

    • String实例一旦生成将不会改变,直接使用String拼接将产生大量临时变量
    String str = "ki" + "ll" + "until" + "win";
    

    上述语句将生成3个临时变量
    1)临时变量kill
    2)临时变量killuntil
    3)临时变量killuntilwin

    • StringBuilder和StringBuffer
      1)StringBuilder线程不安全,StringBuffer线程安全
      2)性能上:StringBuilder > StringBuffer > String
    • 定义为final的字符串特例
    public static final String A = "ab"; // 常量A
    public static final String B = "cd"; // 常量B
    public static void main(String[] args) {
         String s = A + B;  // 将两个常量用+连接对s进行初始化 
         String t = "abcd";   
         System.out.println(s == t);  //true
    }
    

    A和B都是常量,值是固定的,因此s的值也是固定的,它在类被编译时就已经确定了

    public static final String A; // 常量A
    public static final String B;    // 常量B
    static {   
         A = "ab";   
         B = "cd";   
     }   
     public static void main(String[] args) {   
         String s = A + B;       // 将两个常量用+连接对s进行初始化   
         String t = "abcd";   
         System.out.println(s == t);   //false
     } 
    

    A和B虽然被定义为常量,但是它们都没有马上被赋值。在运算出s的值之前,他们何时被赋值,以及被赋予什么样的值,都是个变数。因此A和B在被赋值之前,性质类似于一个变量。那么s就不能在编译期被确定,而只能在运行时被创建了。

    3. String s1 = new String("xyz");创建了几个对象?
    • 2个
    • "xyz"在类加载时就已经创建并放在全局共享的字符串常量池中
    • 代码运行时会将常量池中的这个对象复制到heap,并把heap中的这个对象的引用交给s1
    4.java.lang.String.intern()体现运行时常量池的动态性
    • 运行时常量池相对于Class文件常量池的一个重要特征是具备动态性
    • 即Java语言并不要求常量一定只有编译期才能产生,运行期间也可能将新的常量放入池中
    • String的intern()方法会查找在常量池中是否存在一份equal相等的字符串,如果有则返回该字符串的引用,如果没有则添加自己的字符串进入常量池
    public static void main(String[] args) {    
          String s1 = new String("计算机");
          String s2 = s1.intern();
          String s3 = "计算机";
          System.out.println("s1 == s2? " + (s1 == s2));
          System.out.println("s3 == s2? " + (s3 == s2));
      }
    
    s1 == s2? false
    s3 == s2? true
    
    5.String常量池的跨包适用
    public class Test {
     public static void main(String[] args) {   
          String hello = "Hello", lo = "lo";
          System.out.println((hello == "Hello") + " ");
          System.out.println((Other.hello == hello) + " ");
          System.out.println((other.Other.hello == hello) + " ");
          System.out.println((hello == ("Hel"+"lo")) + " ");
          System.out.println((hello == ("Hel"+lo)) + " ");
          System.out.println(hello == ("Hel"+lo).intern());
     }   
    }
    
    class Other {
     static String hello = "Hello"; 
    }
    
    package other;
    public class Other {
     public static String hello = "Hello"; 
    }
    

    结果为:

    true true true true false true```
    在同包同类下,引用自同一String对象.
    在同包不同类下,引用自同一String对象.
    在不同包不同类下,依然引用自同一String对象.
    在编译成.class时能够识别为同一字符串的,自动优化成常量,引用自同一String对象.
    在运行时创建的字符串具有独立的内存地址,所以不引用自同一String对象.
    

    无情最是台城柳,依旧烟笼十里堤

    相关文章

      网友评论

          本文标题:Java系列3 常量池和字符串

          本文链接:https://www.haomeiwen.com/subject/kzcavftx.html