目录
1.常量池
常量池是一种存储常量的表格,它包含了编译期生成的各种字面量和符号引用。
CLass文件常量池存下内容:
字面量:
- 文本字符串(代码中用双引号包裹的字符串部分的值)
- 被声明为finnal的常量
- 基本数据类型的值
- 其他
符号引用:
- 类符号引用:类的完全限定名
- 字段的名称和描述符
- 方法的名称和描述符
1.1 运行时常量池
当类被加载的时候常量池这部分信息将会被存放到运行时常量池中,并把里面的符号地址转化为内存真实地址。运行时常量池就是将字节码文件中的信息放入方法区中,属于方法区的一部分。运行时常量池是类在加载阶段完后,将class常量池中的符号引用转存到运行时常量中;每个类都有一个运行时常量池,可以用来动态获取类信息。
1.2 String常量池(串池)
String常量池的底层实现: 在HotSpot JVM中,String常量池的底层使用了哈希表(HashMap或类似的数据结构)来存储字符串字面量和对应的字符串对象引用。这个哈希表的键是字符串字面量,而值是字符串对象的引用。这样可以快速查找字符串字面量并重用已经存在的字符串对象。
String直接赋值和使用new实例对象: 当使用直接赋值方式创建字符串时,例如 String s1 = "Hello";
,如果常量池中已经存在 “Hello” 这个字符串字面量,那么s1
将指向常量池中的这个字符串。如果常量池中没有,它会在常量池中创建一个 “Hello” 字符串字面量,然后s1
将指向这个新创建的字符串。
当使用 new
操作符创建字符串对象时,例如 String s2 = new String("Hello");
,无论常量池中是否已经存在 “Hello” 字符串字面量,都会在堆内存中创建一个新的字符串对象,s2
指向堆内存中的这个对象。如果常量池中已存在 “Hello” 字符串字面量,它不会再次在常量池中创建,但仍然会在堆内存中创建一个新的对象。然后堆里的对象的value指向常量池内的这个Hello字符串。
jdk 1.8中String常量池存在堆中
2. 面试案例
2.1 字符串是否相等问题
String s1="a";
String s2=new String("a");
System.out.println(s1==s2);//false
由上面的讲解可以得知为false。
以下看综合案例
String s1="a";
String s2=new String("b");
String s3="a"+"b";
String s4=s1+s2;
String s5="ab";
String s6=s4.intern();
System.out.println(s3==s4);//false
System.out.println(s3==s5);//true
System.out.println(s3==s6);//true
String x2=new String("c")+new String("d");
String x1="cd";
x2.intern();
System.out.println(x1==x2);//false
String s3=”a”+”b”:
编译器会自动优化,组合这两个字面量变成“ab”,之后这个字面量“ab”和在堆中创建的String对象的引用会被放到String常量池内。所以s3==s5为true。
String s4=s1+s2:
编译器内部对于String字符串变量拼接,会创建一个StringBuilder,对于每一个要拼接的内容,调用append进行添加,最后在使用toString()方法返回成字符串。StringBuilder对象存放在堆中,StringBuilder的toString方法得到的对象也是在堆中,并没有被添加到常量池;s4是在堆中,而s3是在字符串常量池中,所以s3==s4为false
String.intern()
JDK 1.6 中的
String.intern()
在JDK 1.6中,
String.intern()
方法的行为有以下特点:
当调用
String.intern()
方法时,如果字符串常量池中已经包含了一个等于此String
对象的字符串(内容相同),则返回字符串常量池中的这个字符串的引用。如果字符串常量池中没有包含等于此
String
对象的字符串,那么会将该String
对象添加到字符串常量池中,并返回字符串常量池中的这个字符串的引用。在JDK 1.6中,字符串常量池是固定大小的,不会自动调整大小。这可能导致字符串常量池中的字符串数量有限,并且在大量字符串被调用
intern()
的情况下,可能会导致性能问题或OutOfMemoryError
异常。JDK 1.7 中的
String.intern()
在JDK 1.7中,
String.intern()
方法的行为发生了变化:
与JDK 1.6不同,JDK 1.7中的字符串常量池不再是一个固定大小的区域。它被移到了堆内存,这意味着字符串常量池的大小可以动态地调整以适应应用程序的需求。
当调用
String.intern()
方法时,如果字符串常量池中已经包含了一个等于此String
对象的字符串(内容相同),则返回字符串常量池中的这个字符串的引用,与JDK 1.6相同。如果字符串常量池中没有包含等于此
String
对象的字符串,那么intern()
方法会将当前字符串对象的引用添加到字符串常量池中,并返回该引用,而不是创建一个新的字符串对象。这是JDK 1.7中的区别之一。
s3==s6:
当s6=s4.intern()的时候常量池中有ab字符串,故不放入常量池,s4依旧是堆对象,而s6指向的是常量池中的对象,故为true。
2.2 String创建几个对象
String str="a"+new String("b");
创建五个对象:
对象1: new StringBuilder()
对象2: 常量池中的”a”
对象3: new String(“b”)
对象4: 常量池中的”b”
对象5 :StringBuilder执行toString方法生成的new String(“ab”),ab不会放在字符串常量池中。
String str=new String("a")+new String("b");
创建六个对象:
对象1: new StringBuilder()
对象2: new String(“a”)
对象3: 常量池中的”a”
对象4: new String(“b”)
对象5: 常量池中的”b”
对象6 :new String(“ab”)
注:
在JDK1.7以前,字符串常量池 和 运行时常量池都存放在方法区中,对方法区的实现成为永久代
在JDK1.7,字符串常量池 从方法区转移到了堆中,运行时 常量池还是在方法区中
在jdk1.8以后,取消了永久代,取而代之的是元空间。运行时常量池和静态常量池都存放在元空间中,而字符串常量池依然在堆空间中(只是和堆共享空间,但和堆互相隔离)
StringBuilder最后也用到了new String,但为什么没有入池 ?
通过new String(“ab”) 创建对象时,是通过双引号修饰的字面量来创建的,而 StringBuilder() 拼接的字符串,不会进入字符串常量池。可以理解为,在代码中写的字符串字面量才会进入字符串常量池
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/197017.html