写代码啦
字符串不可变的原理
回复数(0) 浏览数(16)
{{topic.upvote_count || 0}} 编辑 回复

在学习Java基础时,总会看见很多知识点提到字符串不可变;但自己实际又没办法理解,它所谓的不可变到底是指的哪里的不可变呢?是它内存中的值不可变吗?还是这个对象不可变呢?

也经常会看见如下的一些对 String 赋值的解说

每当出现这样的语句时, String s = "abc"; s = "bcd";实际上不是把 s 对象内部的值改为了"bcd",而是新创建了一个 s 对象,再对其进行赋值。

说到底,引起这样迷惑不解的原因就是对面向对象这个概念还不太熟悉,还没能够真正的完全理解什么叫面向对象,下面来依次解析字符串不可变的原理。

为什么字符串不可变?

首先打开你手边的开发工具,进入到String 类里,可以很容易的看见有final这个字段修饰String类名以及其中的char[] ;所以总结一下为什么字符串不可变:

  • String 被final修饰,不能被继承
  • String 内部的char[ ] 也被final修饰,不能修改

String String

字符串的不可变是指什么不可变?

是否有想过既然String不可变,为什么StringBuffer和StringBuilder能够轻松修改字符串,不用生成新的String对象呢?这个不可变需要从面向对象这个方面说起,更容易理解,如下图所示。

首先我们得清楚的明白,s 对象中的value[ ]存放的并不是实际的"abcd"字符串值,而是存放的一个内存地址,该地址指向内存中"abcd"的实际位置;所以从这个角度就更能把这一切理解透彻了。

这也就意味着 s 对象中不可变的是指向的这块内存地址,而这块内存地址的值仍然是可以修改可变的;这也回答了为什么StringBuffer和StringBuilder能够修改字符串的值。

这样也就能理解为什么更改 s 对象的值需要重新 new 一个 s 对象了,因为前一个 s 对象已经没法去改变自己指向的地址了。

字符串不变 字符串不变

String不可变的实际原因

看到这,你就会想为什么会有这样奇葩的规定呢?这不是浪费空间,闹着玩啊?

其实并不然,这一切的源头得从HashCode约定说起。大家都知道在Java世界中Object类是至高无上的,比较顶层的一个类了,而String类继承了Object类,并且也实现了HashCode的计算,就是为了能使String能够基于Hash存储;比如:常常使用的HashMap中,可以存储字符串类型。

而HashCode有三个重要的约定,也就是这样才让String陷入如此境地:

  • 同一个对象在整个生命周期中,无论调用多少次HashCode计算,都应该返回同一个Hash值

  • 如果2个对象调用equals方法比较,结果相等,那么它们的HashCode也一定相等

  • 假如2个不同的对象调用计算Hash值,可以允许返回相同的HashCode,因为Hash计算的值是有限的,所以可能并允许存在相等的情况,这被称之为Hash碰撞

如果还是迷糊不定,那我们就来反推这个原理;假若String内部的数据是可以允许改变的,那么 每一次改变都会导致Hash值重新计算,可想而知,这样是违背了HashCode的第一条约定;所以String内部的数据是不可以改变的。

String不可变的优缺点:

  • 优点: 线程安全、存储安全(Hash桶里存储安全)
  • 缺点: 每当修改的时候都需要重复创建新的对象

既然String都这么悲惨了,总不能每次都这么麻烦去创建新的对象冗余内存空间吧!所以StringBuffer和StringBuilder就把这一切扛在了肩上。

操作String的类

StringBuffer和StringBuilder是操作String的两大类,可以实现直接操作修改String对象中的内容;两者都是通过集成AbstractStringBuilder类,内部通过Byte[ ] 数组存放数据,默认使用的是UTF-16编码。

  • StringBuilder:线程不安全,不保证同步,速度快
  • StringBuffer:线程安全,因为类中的方法使用synchronized同步,速度相对较慢

上面说到一点点编码的问题,简单提一下:

常见的两种编码:

  • UTF-16:Java程序内部的存储编码方法,将常用的字符用2个字节存储,不常用的用4个字节存储

  • UTF-8:最适用的字符集编码,所有字符都使用4个字节存储,可以避免大多编码问题

  • Mac/Linux默认编码是UTF-8

  • Windows默认编码是GBk(国标库),独立于UTF-8和UTF-16的编码库

{{topic.upvote_count || 0}}

在学习Java基础时,总会看见很多知识点提到字符串不可变;但自己实际又没办法理解,它所谓的不可变到底是指的哪里的不可变呢?是它内存中的值不可变吗?还是这个对象不可变呢?

也经常会看见如下的一些对 String 赋值的解说

每当出现这样的语句时, String s = "abc"; s = "bcd";实际上不是把 s 对象内部的值改为了"bcd",而是新创建了一个 s 对象,再对其进行赋值。

说到底,引起这样迷惑不解的原因就是对面向对象这个概念还不太熟悉,还没能够真正的完全理解什么叫面向对象,下面来依次解析字符串不可变的原理。

为什么字符串不可变?

首先打开你手边的开发工具,进入到String 类里,可以很容易的看见有final这个字段修饰String类名以及其中的char[] ;所以总结一下为什么字符串不可变:

  • String 被final修饰,不能被继承
  • String 内部的char[ ] 也被final修饰,不能修改

String String

字符串的不可变是指什么不可变?

是否有想过既然String不可变,为什么StringBuffer和StringBuilder能够轻松修改字符串,不用生成新的String对象呢?这个不可变需要从面向对象这个方面说起,更容易理解,如下图所示。

首先我们得清楚的明白,s 对象中的value[ ]存放的并不是实际的"abcd"字符串值,而是存放的一个内存地址,该地址指向内存中"abcd"的实际位置;所以从这个角度就更能把这一切理解透彻了。

这也就意味着 s 对象中不可变的是指向的这块内存地址,而这块内存地址的值仍然是可以修改可变的;这也回答了为什么StringBuffer和StringBuilder能够修改字符串的值。

这样也就能理解为什么更改 s 对象的值需要重新 new 一个 s 对象了,因为前一个 s 对象已经没法去改变自己指向的地址了。

字符串不变 字符串不变

String不可变的实际原因

看到这,你就会想为什么会有这样奇葩的规定呢?这不是浪费空间,闹着玩啊?

其实并不然,这一切的源头得从HashCode约定说起。大家都知道在Java世界中Object类是至高无上的,比较顶层的一个类了,而String类继承了Object类,并且也实现了HashCode的计算,就是为了能使String能够基于Hash存储;比如:常常使用的HashMap中,可以存储字符串类型。

而HashCode有三个重要的约定,也就是这样才让String陷入如此境地:

  • 同一个对象在整个生命周期中,无论调用多少次HashCode计算,都应该返回同一个Hash值

  • 如果2个对象调用equals方法比较,结果相等,那么它们的HashCode也一定相等

  • 假如2个不同的对象调用计算Hash值,可以允许返回相同的HashCode,因为Hash计算的值是有限的,所以可能并允许存在相等的情况,这被称之为Hash碰撞

如果还是迷糊不定,那我们就来反推这个原理;假若String内部的数据是可以允许改变的,那么 每一次改变都会导致Hash值重新计算,可想而知,这样是违背了HashCode的第一条约定;所以String内部的数据是不可以改变的。

String不可变的优缺点:

  • 优点: 线程安全、存储安全(Hash桶里存储安全)
  • 缺点: 每当修改的时候都需要重复创建新的对象

既然String都这么悲惨了,总不能每次都这么麻烦去创建新的对象冗余内存空间吧!所以StringBuffer和StringBuilder就把这一切扛在了肩上。

操作String的类

StringBuffer和StringBuilder是操作String的两大类,可以实现直接操作修改String对象中的内容;两者都是通过集成AbstractStringBuilder类,内部通过Byte[ ] 数组存放数据,默认使用的是UTF-16编码。

  • StringBuilder:线程不安全,不保证同步,速度快
  • StringBuffer:线程安全,因为类中的方法使用synchronized同步,速度相对较慢

上面说到一点点编码的问题,简单提一下:

常见的两种编码:

  • UTF-16:Java程序内部的存储编码方法,将常用的字符用2个字节存储,不常用的用4个字节存储

  • UTF-8:最适用的字符集编码,所有字符都使用4个字节存储,可以避免大多编码问题

  • Mac/Linux默认编码是UTF-8

  • Windows默认编码是GBk(国标库),独立于UTF-8和UTF-16的编码库

16
回复 编辑