Java 中的字符串之字符串常量池
1.字符串的创建
在 Java 当中,使用 String 类来表示,"abc"是一个字符串常量,是 String 类对象,String 类是一种引用类型,其引用变量可以指向 String 类的对象,如果需要创建一个字符串对象,有以下 3 种方法:
方法 1:直接定义 String 类引用变量指向一个字符串常量对象。方法 2:使用 new 创建一个新的字符串对象,传入构造函数的参数类型为 String。方法 3:使用 new 创建一个新的字符串对象,传入构造函数的参数类型为字符数组。
字符串类是引用变量,在一个方法中修改引用的值是无效的,因为方法里面的引用变量是局部变量,作用域是在方法内。区别与数组,利用[]
形式访问的数组对象的内容,并不是引用变量的值,所以数组利用[]
修改是有效的。
jdk1.8 版本中 String 类的本质是char[]
,这一点可以通过访问 String 源码能够观察到。
我们发现这个字符数组是被private final
修饰的,因此字符串是不可变对象,因为你无法获取和修改这个对象。
2.字符串相等问题
2.1 字符串常量池
在 Java 中为了提高效率,引入了常量池,那什么是常量池呢?常见的常量池有 class 文件常量池,运行时常量池,字符串常量池等。
这个“池”怎么理解呢?打个比方,你的女朋友想要吃瓜子,然后你剥瓜子,你事先把瓜子剥到一个盘中,然后你的女朋友要吃瓜子的时候可以从这个盘子中取,不需要等待你把剥瓜子剥完,这个盘就是这里所谓的“池”,使用“池”能够提高程序运行的效率。
class 文件常量池储存在磁盘上,编译后形成的字节码文件,原本定义的很多基本类型常量,比如int a = 2
,这个 2 就被放在了这个常量池当中。运行时常量池,当程序把编译好的字节码文件加载到 JVM 后,会形成一个运行时常量池,也称为方法区,实际上是 class 文件常量池。字符串常量池,主要存放字符串常量,本质上是一个哈希表,直接使用双引号""
声明定义的字符串常量都会被存入字符串常量池当中去,jdk1.8 后使用 new 新建的 String 对象也会存入常量池中。
刚刚说到了哈希表,那哈希表是什么呢?哈希表是一种数据结构,是描述和组织数据的一种方式,本质上哈希表就是一个数组,在存储数据的时候会根据一个映射关系将数据存储在数组当中,这个映射关系就叫做哈希函数,当然也有可能存在多个数据通过哈希函数映射后,得到在数组中的位置是一样的,这时就引发了哈希冲突,解决哈希冲突常用的方法就是使用链表,也就是说哈希表里面数组类型为链表,数组中存放的是链表的首元素的地址。这里简单认识一下,后面介绍数据结构时专门讲解哈希表。
2.2 字符串引用相等的判断
了解了字符串常量池就能来分析 String 引用是否相等了。
(1):定义str1
时,检查字符串csdn
是否入池,发现没有入池,先将字符串入池,然后将该字符串对象地址赋值给str1
,定义str2
时,检查字符串csdn
是否入池,发现已经入池,直接将已经入池的字符串对象地址赋值给str2
,所以str1
与str2
引用的值是相等的,所以输出true
。
(2):定义str3
时,会先新建一个 String 对象,然后检查字符串csdn
是否入池,如果没入池则入池,再将入池后的对象的内容拷贝给新建的 String 对象,当然在这个栗子csdn
是已经入池了的,则直接将该对象的内容拷贝给新建的 String 对象。很明显,str1
与str3
的引用值是不同的,所以输出false
。
(3):定义str4
时,编译时就会把cs
与dn
拼接为csdn
,所以编译后str4
得到的就是csdn
对象的地址,这就与栗子(1)一样了,会先去常量池里找,常量池中有该字符串,所以str4
的引用值与str1
相等,结果输出true
。
(4):定义str5
时,会将字符串cs
入池,因为str5
是一个变量,编译时期不会将两字符串拼起来,所以定义str6
时会将dn
入池,运行时会使用StringBuilder
类将两个字符串进行拼接(该类在后面详细介绍),会得到一个新对象,该对象中存有拼接后的字符串csdn
,注意使用拼接得到的字符串不会自动入池。所以str1
与str6
的值不相等,输出false
。
当然除了自动入池,还可以手动入池,可以使用方法手动入池。
2.3 字符串内容相等的判断
判断两个字符串内容是否相等,使用string
类中的equals
方法,下图为该方法的源码。(Object 类是所有类的父类,所以 Object 变量可以接受所以类的引用变量)
对于源码实现的思路不细说,重点是知道如何使用它。
2.4 理解字符串内容不可变
我们根据源码知道字符串本质上就是一个字符数组。但是这个数组被private final
修饰,我们不能访问和修改,所以字符串内容是不可变的,如果硬要改变,只能采取反射(现在了解即可,后续博文详细介绍)。反射是面向对象编程的一种重要特性, 有些编程语言也称为 "自省".指的是程序运行过程中, 获取/修改某个对象的详细信息(类型信息, 属性信息等), 相当于让一个对象更好的 "认清自己"
为什么 String 要不可变?
方便实现字符串对象池. 如果 String 可变, 那么对象池就需要考虑何时深拷贝字符串的问题了。
不可变对象是线程安全的。
不可变对象更方便缓存 hash code, 作为 key 时可以更高效的保存到 HashMap 中。
版权声明: 本文为 InfoQ 作者【未见花闻】的原创文章。
原文链接:【http://xie.infoq.cn/article/8a9aaec49bdb1d480e947ef95】。文章转载请联系作者。
评论