java基础之String详解

String简介
它是一个final类,不能被继承使用,所有成员方法都被隐式指定为final类型,实现了序列化Serializable,实例化对象大小比较Comparable<String>,只读字符序列CharSequence接口。

讲解String之前,先回顾一下字符串常量池、class文件常量池和运行时常量池的区别?
①字符串常量池
<=jdk6.0时,String Pool存放在Perm Gem方法区的运行时常量池中,>=jdk7.0被移到堆中。String Pool由StringTable实现,其实是一个Hash表,在VM中仅一份,被所有类共享。

jdk6.0中,StringTable的长度固定为1009,因此如果放入String Pool中的String非常多,就会造成hash冲突,导致链表过长,当调用String#intern()时会需要到链表上一个一个找,从而导致性能大幅度下降。
jdk7.0中,StringTable的长度可以通过参数指定-XX:StringTableSize=2048。

<=jdk6.0,String Pool里放的都是字符串常量。
>=jdk7.0中,由于String#intern()发生了改变,因此String Pool中也可以存放于堆内字符串对象的引用。

使用字符串常量池,每当我们使用字面量(String s="1")创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就将此字符串对象的地址赋值给引用s(引用s在Java栈中)。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中,并将此字符串对象的地址赋值给引用s(引用s在Java栈中)。

 使用字符串常量池,每当我们使用关键字new(String s=new String("1"))创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么不再在字符串常量池创建该字符串对象,而直接堆中复制该对象的副本,然后将堆中对象的地址赋值给引用s,如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中,然后在堆中复制该对象的副本,然后将堆中对象的地址赋值给引用s。

②class文件常量池
Java类被编译后形成一份class文件,class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References),每个class文件都有一个class常量池。其中字面量:1.文本字符串 2.八种基本类型的值 3.被声明为final的常量等;符号引用:1.类和接口的全限定名 2.字段的名称和描述符 3.方法的名称和描述符。

③运行时常量池
运行时常量池是方法区的一部分,存放一些运行时常量数据,它是class常量池被加载到内存之后的版本。不同之处是:它的字面量可以动态的添加(intern),符号引用可以被解析为直接引用。JVM在执行某个类的时候,必须经过加载、连接、初始化、使用、卸载,而连接又包括验证、准备、解析三个阶段。而当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中,由此可知,运行时常量池也是每个类都有一个。在解析阶段,会把符号引用替换为直接引用,解析的过程会去查询字符串常量池,也就是我们上面所说的StringTable,以保证运行时常量池所引用的字符串与字符串常量池中是一致的。

创建字符串多种形式
String s1=”1”;
String s2=new String(“1”);  常量池不存在,则是两个对象,存在则是一个

“+”连接
①String s1=”1”+”2”+”3”
此种方式编译期确定值,即编译期s1="123",直接入字符串常量池,前提判断是否已存在。

②String s2 = ”1”+”3”+new String(“1”)+”4”
使用“+”连接字符串含有变量时运行期确定的。第一步连接操作最开始时如果都是字符串常量,编译后将尽可能多的字符串常量连接在一起,形成新的字符串常量参与后续的连接。第二步字符串连接是从左向右依次进行,对于不同的字符串,首先以最左边的字符串为参数创建StringBuilder对象(可变字符串对象),然后依次对右边进行append操作,最后将StringBuilder对象通过toString()方法转换成String对象(注意:中间的多个字符串常量不会自动拼接)。
实际上的实现过程为:String s2=new StringBuilder(“13”).append(new String(“1”)).append(“4”).toString()。
当使用+进行多个字符串连接时,实际上是产生了一个StringBuilder对象和一个String对象。

③String s3 = new String(“1”)+new String(“1”),原理类似②


String.intern()解析
String.intern()是一个Native方法,底层调用C++的 StringTable::intern 方法,当调用 intern 方法时,如果常量池中已经该字符串,则返回池中的字符串;否则将此字符串添加到常量池中,并返回字符串的引用。
public static void main(String[] args) { 
       String s3 = new String("1") + new String("1");
       System.out.println(s3 == s3.intern());
}
JDK6:false
JDK7、8:true
JDK6的内存模型

JDK6中的常量池是放在永久代的,永久代和Java堆是两个完全分开的区域。而存在变量使用“+”连接而来的的对象存在Java堆中,且并未将对象存于常量池中,当调用 intern 方法时,如果常量池中已经该字符串,则返回池中的字符串;否则将此字符串添加到常量池中,并返回字符串的引用。所以结果为false。

JDK7、JDK8的内存模型

JDK7中,字符串常量池已经被转移至Java堆中,开发人员也对intern 方法做了一些修改。因为字符串常量池和new的对象都存于Java堆中,为了优化性能和减少内存开销,当调用 intern 方法时,如果常量池中已经存在该字符串,则返回池中字符串;否则直接存储堆中的引用,也就是字符串常量池中存储的是指向堆里的对象。所以结果为true。

// 字符串创建实例比较
public static void main(String[] args) {
        /**
         * 情景一:字符串池
         * JAVA虚拟机中存在着一个字符串池,其中保存着很多String对象,并且可以被共享使用,提高性能效率。
         * 由于String类是final的,值一旦创建就不可改变,字符串池由String类维护,可以调用intern()方法来访问字符串池。  
         */
        String s1 ="abc";
        // 在字符串池创建了一个对象

        String s2 ="abc";
        // 字符串池中已存在对象abc,使用池中对象 

        System.out.println("s1 == s2 : " + (s1 == s2));
        // true 指向同一个对象,  

        System.out.println("s1.equals(s2) : " + (s1.equals(s2)));
        // ↑rue 表示值相等  

        /**
         * 情景二:关于new字符串
         */
        String s3 = new String("abc");
        // 创建了两个对象,一个存放在字符串池中,一个存放在堆中,对象引用s3存放在栈中
 
        String s4 = new String("abc");
        // 字符串池中已存在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 值相同  

        /**
         * 情景三:  
         * 常量的值在编译时被确定(优化)。
         * 如下"ab"和"cd"都是常量,因此变量str3的值在编译时就被确定。
         * 这行代码编译后的效果等同于:String str3 = "abcd";
         */
        String str1 ="ab" +"cd";  //1个对象  
        String str11 ="abcd";
        System.out.println("str1 = str11 : " + (str1 == str11));

        /**
         * 情景四:  
         * 局部变量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  

        /**
         * 情景五:
         * JAVA编译器对string + 基本类型/常量是当成常量表达式直接求值来优化的
         * 运行期的两个string相加会产生新的对象,存储在堆(heap)中
         */
        String str6 ="b";  
        String str7 ="a" + str6;  
        String str67 ="ab";  
        System.out.println("str7 = str67 : " + (str7 == str67));

        //str6为变量,在运行期才会被解析,str8为常量变量,编译期会被优化  
        final String str8 ="b";
        String str9 ="a" +str8;
        String str89 ="ab";
        System.out.println("str9 = str89 : " + (str9 == str89));
    }

 部分转载:https://blog.csdn.net/qq_34490018/article/details/82110578

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值