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
java基础之String详解
最新推荐文章于 2023-09-22 14:20:38 发布