zoukankan      html  css  js  c++  java
  • Java.lang.String类

    1.String类定义

         String 字符串对象本质上是一个 final 修饰的字符串数组对象, java字符串就是Unicode字符序列. 因为被final修饰, 所以字符串是常量,它们的值一旦被创建后不能改变。 Java没有内置的字符串类型, Java内部通过在标准Java类库中提供了一个预定义的String类,实现对字符串类型的支持

      通过JDK源码前三行, 可以看出

      1. String类被final修饰, 这说明String不可继承

      2. String类的主力成员字段value是个final修饰的char[]数组。final修饰的字段创建以后就不可改变。注意, 虽然value是不可变,也只是value这个引用地址不可变。挡不住Array数组是可变的事实。

    public static void main(String[] args) {
            final char[] value = {1, 2, 3};
            int[] another = {4, 5, 6};
            //编译器报错,value用final修饰,编译器不允许我把value指向堆区另一个地址
            //value = another;   
            value[0] = 100;  //数组变成{100,2,3}
        }

      

       Array的数据结构如图所示, 变量value存储的只是对象的引用(指向堆中的地址 0x1000),数组对象的本体结构在heap堆。String类里的value用final修饰,只是说stack里的这个叫value的引用地址0x1000不可变。可没说heap里数组对象本身数据不可变。value[0] = 100时, 变量value引用的值0x1000并没有改变, 改变的是heap堆中对象本身的数据值.

      

    2.字符串的创建

    1.String a = "abc"; 
    String b = "abc";
    创建了一个对象,
    第一行在字符串常量池创建"abc",
    第二行检查字符串常量池中有"abc"对象,直接返回
    2.String c = new String("abc"); 
      String d = new String("abc"); 
      System.out.println(c==d);   // false
    System.out.println(c.intern()==d.intern());   //true 总共创建了3个对象
    第一行在堆和字符串常量池中各创建一个"abc"
    第二行在堆中创建一个"abc", 常量池中已有, 不创建
    3.String s = "a" + "b" + "c";
       只创建了1个对象
       赋值符号右边的"a"、"b"、"c"都是常量,对于常量,编译时就直接存储它们的字面值而不是它们的引用,在编译时就直接将它们连接的结果提取出来变成了"abc",
    该语句在class文件中就相当于String s = "abc" ,然后当JVM执行到这一句的时候, 就在String pool里找 ,如果没有这个字符串,就会产生一个
    4.String s1 = "abc"; 
      String s2 = "a"; 
      String s3 = "bc"; 
      String s4 = s2 + s3; 
      System.out.println(s1 == s4);  // false   
    因为s2+s3实际上是使用StringBuilder.append来完成,会生成不同的对象。
    s1指向常量池"abc",
    s4指向堆中"abc"(append连接而来)
    5.String S1 = "abc"; 
      final String S2 = "a"; 
      final String S3 = "bc"; 
      String S4 = S2 + S3; 
      System.out.println(S1 == S4); // true
    因为final变量在编译后会直接替换成对应的值,所以实际上等于s4="a"+"bc",而这种情况下,编译器会直接合并为s4="abc",所以最终s1==s4。

    3. String类常量池内存分析,基于JDK1.7 (分析: https://blog.csdn.net/qq_34115899/article/details/86583262#commentsedit)

    问题一:

    String h = new String("cc");      // 第一行创建2个对象, 堆中对象"cc", 常量池对象"cc"
    String intern = h.intern();       // intern 指向常量池中对象0x333
    System.out.println(intern == h);  // h指向堆中对象0x001, 地址引用不同,所以false

     问题二:

    String str2 = new String("str") + new String("01");  // 堆中有"str","01","str01" 三个对象; 常量池中有 "str","01" 两个对象
    String str1 = "str01";                               // 常量池中创建"str01"对象, 变量str1 指向常量池中对象
    str2.intern();                                       // 检查常量池中已经有了"str1"对象, 返回对象引用时,没有变量接收, 相当于啥都没干
    System.out.println(str2 == str1); // false

     问题三:

    String str2 = new String("str") + new String("01");
    String str1 = "str01";
    String str3 = str2.intern();
    System.out.println(str3 == str1); // true

     问题四:

    String str2 = new String("str") + new String("01");
    str2.intern();
    String str1 = "str01";
    System.out.println(str2 == str1);  // true
     
    String str3 = new String("str01");
    str3.intern();
    String str4 = "str01";
    System.out.println(str3 == str4);  // false

    问题五:

    4.为什么String类被设计为不可变的,是否真的不可变?

      1.如何保证String不可变 

    1. 对于属性不提供设值方法
    2. 所有的属性定义为private final
    3. 类声明为final不允许继承
    4.Return deep cloned objects with copied content for all mutable fields in class(为类中的所有可变字段返回具有复制内容的深度克隆对象)
        注意:不用final关键字也可以实现对象不可变,使用final只是显示的声明,提示开发者和编译器为不可变。

       2. 为什么String类被设计为不可变(加final)

    1.为了实现字符串池, 只有当字符串是不可变的,字符串常量池才有可能实现。
         字符串常量池(String pool, String intern pool, String保留池) 是Java堆内存中一个特殊的存储区域, 当创建一个String对象时,假如此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象。假若字符串对象允许改变,那么String interning将不能实现, 比如改变一个对象会影响到另一个独立对象. 严格来说,这种常量池的思想,是一种优化手段.
         jdk1.6 以及之前,字符串常量池是存在于永久代中的(方法区),频繁使用,会造成该区域内存占有过大,造成垃圾收集器的gc 从而影响程序的运行
         jdk 1.7 常量池移动到了堆空间中,并且常量池中只存储字符串的引用,不再存储字符串值
         jdk1.8 又进行改变,字符串常量池已经不存在,又出现了一个metaSpace(元空间)   
     
        
    2.为了线程安全        
         如果字符串是可变的,那么会引起很严重的安全问题。譬如,数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接,或者在socket编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞。    
         因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。
    
    
    3.为了实现String可以创建HashCode不可变性
         因为字符串是不可变的,所以在它创建的时候HashCode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。

    参考:

      Java String 类为什么是final的?

      为什么String类被设计为不可变,是否真的不可变?

      String类和常量池内存分析

      String.intern()的深入研究

      String s = a+b+c+d+e;创建了几个对象的详细分析?

      String,StringBuilder,StringBuffer 实现原理解析

      

  • 相关阅读:
    HBase Flush 机制
    HBase 预分区 & Phoenix 加盐
    Phoenix 索引
    python 列表、元组、字典、集合的比较操作
    Redis HA
    基于 Redis 和 ZooKeeper 的分布式锁
    Ubuntu 系统设置 Swappiness 值
    Ubuntu_16.04 安装 Pyhon3.6
    Win10创意者更新秋季版升级常见问题解决方案
    如何使用注册表在Win10中调整屏幕亮度
  • 原文地址:https://www.cnblogs.com/hcl1991/p/10900924.html
Copyright © 2011-2022 走看看