聊一聊Java字符串的不可变

前言

在 Java 开发中 String (字符串)对象是咱们使用最频繁的对象,也是很重要的对象。正是使用得如此频繁,String 在实现层面上不断进行优化,从 Java6 到 Java7,再到 Java9 的新实现 ,都是为了提高 String 对象的性能,而其中不变的是 String 所生俱来的特性:不可变。本文主要聊一聊 String 的不可变,以及为何存在的。html

什么是 String 的不可变

首先咱们先来看下什么是不可变对象:一旦对象被建立并初始化后,内部的状态数据就会保持不变。查看 JDK 源码中的 String 类,能够看到类自己被 final 修饰,而且内部的大部分属性都是 final 修饰的,除了字段 hash 是经过字符串内容计算并缓存起来的。这样的行为让 String 类没法被扩展,内部属性也没法被修改。java

接着咱们再来用画图的形式来讲明下 String 的不可变性。编程

一般咱们初始化字符串都是如下形式:缓存

String 类型的引用变量 a 保留了一个字符串对象 string 的引用,就如同下图所示,箭头则表示了变量 a 与真正 String 对象的引用关系。安全

再经过上述代码,咱们将变量 a 赋值给变量 b ,变量 b 也存储了字符串对象 string的引用,它们指向的是同一个对象。网络

当咱们尝试对变量 a 从新赋值,看下对变量 b 会不会有影响呢数据结构

想必小伙伴一看就知道,打印的结果确定是 string2,string,一样用画图的方式展现这两个变量与字符串对象的引用关系。多线程

将变量 a 从新赋值后,保存了新的引用,而不是直接在原有的字符串对象上进行数据改变,同时变量 b 仍然存的是对象 string 的引用,变量 ab 二者相互独立,不影响,这也正是说明了 String 对象的不可变。oracle

在这里初认 Java 的小伙伴还可能会有些困惑:对一个String对象 a 赋值 string,而后又让 a 值为 string2,这个时候a的值变成 了string2, a 的值改变了,为何还说 String 对象不可变呢。jvm

其实问题也很简单,这里的 a 只是存储 String 对象的引用,并非对象自己,a 存储的是指向对象所在内存的地址引用罢了,当第二次赋值时,a 引用指向了对象 string2的内存地址,而对象 string2 是从新建立的,以前的 string 对象仍在内存中,而且由变量 b 引用着。

除此以外,String 类的返回 String 对象的方法不会改变自身,都是返回一个新的 String 对象来实现,好比 concatreplacesubstring 等等。

为何 String 须要不可变

聊完什么是 String 的不可变后,接下来咱们再说说 String 为何须要不可变呢,又有什么好处呢?

字符串常量池的实现

在Java中,咱们一般有两种方式建立字符串对象,一种是经过字符串字面量方式建立,就如上文的代码,另一种就是经过 new 方式去建立,如 String c = new String("string 3"); 而二者区别就在于经过字符串字面量的方式建立时,JVM 会如今字符串池中检查字符串内容是否已经存在,若是存在就会直接返回对应的引用,而不是再次分配内存进行建立,若是不存在就会分配在内存中建立的同时将字符串数据缓存在字符串池中,便于重用。正是是因为字符串的不可变,一样的字符串内容可让 JVM 能够减小额外的内存分配操做,直接使用在字符串池中字符串对象便可,对性能提高和内存节省都大有好处。

关于字符串池,这里稍微简单介绍一下:**Java 的字符串池属于 JVM 专门给指定的特殊内存区域,用来存储字符串字面量。**在 Java 7 以前,分配于 JVM 的方法区内,属于常量池的一部分;而 Java7 以后字符串池被移至堆内存进行管理,这样的好处就是容许被 JVM 进行垃圾回收操做,将未被引用的字符串所占内存即便回收,以此节省内存。

Hashcode 缓存

字符串做为基础的数据结构,大量地应用在一些集合容器之中,尤为是一些散列集合,在散列集合中,存放元素都要根据对象的 hashCode() 方法来肯定元素的位置。因为字符串 hashcode 属性不会变动,保证了惟一性,使得相似 HashMap,HashSet 等容器才能实现相应的缓存功能。因为 String 的不可变,避免重复计算 hashcode,只有使用缓存的 hashcode 便可,这样一来大大提升了在散列集合中使用 String 对象的性能。

线程安全

在多线程中,只有不变的对象和值是线程安全的,能够在多个线程中共享数据。因为 String 自然的不可变,当一个线程”修改“了字符串的值,只会产生一个新的字符串对象,不会对其余线程的访问产生反作用,访问的都是一样的字符串数据,不须要任何同步操做。

安全性

因为字符串不管在任何 Java 系统中都普遍使用,会用来存储敏感信息,如帐号,密码,网络路径,文件处理等场景里,保证字符串 String 类的安全性就尤其重要了,若是字符串是可变的,容易被篡改,那咱们就没法保证使用字符串进行操做时,它是安全的,颇有可能出现 SQL 注入,访问危险文件等操做。

结语

经过本文,咱们介绍 String 是不可变的,能够将它们的引用能够被看成一个普通的变量来使用,不管是在方法间,仍是线程间传递它们,都不用担忧它指向的实际 String 对象发生改变,而且不可变的特性也在语言层面和程序层面上带了许多好处,在日常编程实践中咱们也应该多学习效仿,用 James Gosling,Java之父的话说就是”我会尽量地使用不可变对象“。

推荐阅读

参考资料

相关文章
相关标签/搜索