彻底搞懂Java中的字符串与字符串常量池(JDK8)

Java中的字符串

Java中没有原生的字符串类型,但是提供了StringStringBufferStringBuilder来表示字符串,在它们的代码实现中都是通过char[]来存储字符串中的字符的。下图是它们的继承关系:

CharSequence
AbstractStringBuilder
String
Appendable
StringBuilder
StringBuffer

它们的区别如下:

  • 可变性:String是不可变的,StringBuilderStringBuffer是可变的。
  • 安全性:StringStringBuffer是线程安全的,StringBuilder是线程不安全的。

可变性

字符串的可变性体现在是否可以修改char[]数组。首先,char[]数组的访问权限要么是私有的,要么是包访问权限,这意味着不可能直接修改char[]数组;其次,StringStringBufferStringBuilder都被final修饰,这意味着不可能通过继承的方式修改char[]数组;最后,String类没有提供任何修改char[]数组的API,而StringBufferStringBuilder提供了大量的修改API。因此,String是不可变的,而StringBuilderStringBuffer是可变的。

字符串的特殊性

  • 字符串字面量:在Java中实例化对象一般有三种方式(new关键字、调用clone()方法和反序列化),但是,字符串对象可以直接使用String str="字符序列"的形式实例化。
  • 操作符的重载:当一个字符串对象通过++=与其它对象相加时,这两种操作符会被重载为对字符串拼接的操作,并且这个操作会由StringBuilder实现:
int a=1;
String str="a="+a;
//字节码为:
iconst_1
istore_1
new #2 <java/lang/StringBuilder>
dup
invokespecial #3 <java/lang/StringBuilder.<init> : ()V>
ldc #4 <a=>
invokevirtual #5 <java/lang/StringBuilder.append : (Ljava/lang/String;)Ljava/lang/StringBuilder;>
iload_1
invokevirtual #6 <java/lang/StringBuilder.append : (I)Ljava/lang/StringBuilder;>
invokevirtual #7 <java/lang/StringBuilder.toString : ()Ljava/lang/String;>
astore_2

字符串常量池

由于字符串的不可变性以及字符串使用的频繁性,JVM在堆中通过哈希表实现了一个字符串常量池,用于避免字符串的重复创建。在使用字符串字面量实例化字符串对象时,如果字符串常量池中没有该字符串,就会将该字符串实例化并将该字符串的引用加入字符串常量池;如果字符串常量池中有该字符串引用,那么就会直接返回该引用。

public static void main(String[] args) {
    String str1 = "helloWorld";
    String str2 = "helloWorld";
    System.out.println(str1 == str2);//true
}

在这里插入图片描述

new关键字实例化字符串时创建了几个字符串对象

如果已经了解了字符串常量池,那么再看这个问题就是一个笑话。

String str1=new String("helloWorld");
String str2new String(new char[]{'h','e','l','l','o','W','o','r','l','d'});

先看str1,很明显是两个:一个字面量对象和一个new出来的对象:

在这里插入图片描述

再看str2,就只有一个new出来的对象:

在这里插入图片描述

字符串的intern方法

intern()方法是一个本地方法,该方法会判断字符串常量池是否存在指向当前字符串对象的引用,如果存在,则返回该引用;如果不存在,则先在常量池中添加指向该字符串对象的引用,然后再返回该引用。请看下面两段程序的分析图加深理解:

String str=new String("helloWorld");
System.out.println(str.intern()==str);//false

在这里插入图片描述

String str=new String("hello")+new String("World");
System.out.println(str.intern()==str);//true

在这里插入图片描述

字符串拼接中的优化

当字符串字面量进行拼接时,拼接后的字符串也将直接放在字符串常量池中,原因是编译器在编译前可以直接确认它的值,因此在编译时进行了常量折叠优化:

String str1 = "hello";
String str2 = "h"+"e"+"l"+"l"+"o";
System.out.println(s1==s2);//true

如果拼接字符串中有一个是变量或是通过new关键字实例化的对象,那么拼接后的字符串将放在堆中,原因是编译器在编译前不能确认它们的值,只能当作操作符重载处理:

String s1 = "helloWorld";
String s2 = "hello";
String s3 = "World";
String s4 = new String("hello") + new String("World");
String s5 = new String("hello") + "World";
String s6 = s2 + "World";
String s7 = "hello" + "World";
System.out.println(s1==s2+s3);//false
System.out.println(s1==s4);//false
System.out.println(s1==s5);//false
System.out.println(s1==s6);//false
System.out.println(s1==s7);//true

final修饰的字面量实例化的字符串编译器在编译前也可以直接确认它的值:

public static void main(String[] args) {
    String s1 = "helloWorld";
    final String s2 = "World";
    String s3 = new String("hello") + s2;
    String s4 = "hello" + s2;
    System.out.println(s1==s3);//false
    System.out.println(s1==s4);//true
}
  • 5
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

亻乍屯页女子白勺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值