|
| 1 | +String在Java中特别常用,而且我们经常要在代码中对字符串进行赋值和改变他的值,但是,为什么我们说字符串是不可变的呢? |
1 | 2 |
|
2 | | -* * * |
| 3 | +首先,我们需要知道什么是不可变对象? |
3 | 4 |
|
4 | | -## 定义一个字符串 |
| 5 | +不可变对象是在完全创建后其内部状态保持不变的对象。这意味着,一旦对象被赋值给变量,我们既不能更新引用,也不能通过任何方式改变内部状态。 |
5 | 6 |
|
6 | | - String s = "abcd"; |
7 | | - |
| 7 | +可是有人会有疑惑,String为什么不可变,我的代码中经常改变String的值啊,如下: |
8 | 8 |
|
9 | | -![String-Immutability-1][1] |
| 9 | +``` |
| 10 | +String s = "abcd"; |
| 11 | +s = s.concat("ef"); |
10 | 12 |
|
11 | | -`s`中保存了string对象的引用。下面的箭头可以理解为“存储他的引用”。 |
| 13 | +``` |
12 | 14 |
|
13 | | -## 使用变量来赋值变量 |
14 | 15 |
|
15 | | - String s2 = s; |
16 | | - |
| 16 | +这样,操作,不就将原本的"abcd"的字符串改变成"abcdef"了么? |
17 | 17 |
|
18 | | -![String-Immutability-2][2] |
| 18 | +但是,虽然字符串内容看上去从"abcd"变成了"abcdef",但是实际上,我们得到的已经是一个新的字符串了。 |
19 | 19 |
|
20 | | -s2保存了相同的引用值,因为他们代表同一个对象。 |
| 20 | +![][1] |
21 | 21 |
|
22 | | -## 字符串连接 |
| 22 | +如上图,在堆中重新创建了一个"abcdef"字符串,和"abcd"并不是同一个对象。 |
23 | 23 |
|
24 | | - s = s.concat("ef"); |
25 | | - |
| 24 | +所以,一旦一个string对象在内存(堆)中被创建出来,他就无法被修改。而且,String类的所有方法都没有改变字符串本身的值,都是返回了一个新的对象。 |
26 | 25 |
|
27 | | -![string-immutability][3] |
| 26 | +如果我们想要一个可秀改的字符串,可以选择StringBuffer 或者 StringBuilder这两个代替String。 |
28 | 27 |
|
29 | | -`s`中保存的是一个重新创建出来的string对象的引用。 |
| 28 | +### 为什么String要设计成不可变 |
30 | 29 |
|
31 | | -## 总结 |
| 30 | +在知道了"String是不可变"的之后,大家是不是一定都很疑惑:为什么要把String设计成不可变的呢?有什么好处呢? |
32 | 31 |
|
33 | | -一旦一个string对象在内存(堆)中被创建出来,他就无法被修改。特别要注意的是,String类的所有方法都没有改变字符串本身的值,都是返回了一个新的对象。 |
| 32 | +这个问题,困扰过很多人,甚至有人直接问过Java的创始人James Gosling。 |
34 | 33 |
|
35 | | -如果你需要一个可修改的字符串,应该使用StringBuffer 或者 StringBuilder。否则会有大量时间浪费在垃圾回收上,因为每次试图修改都有新的string对象被创建出来。 |
| 34 | +在一次采访中James Gosling被问到什么时候应该使用不可变变量,他给出的回答是: |
36 | 35 |
|
37 | | - [1]: http://www.programcreek.com/wp-content/uploads/2009/02/String-Immutability-1.jpeg |
38 | | - [2]: http://www.programcreek.com/wp-content/uploads/2009/02/String-Immutability-2.jpeg |
39 | | - [3]: http://www.programcreek.com/wp-content/uploads/2009/02/string-immutability-650x279.jpeg |
| 36 | +> I would use an immutable whenever I can. |
| 37 | +
|
| 38 | +那么,他给出这个答案背后的原因是什么呢?是基于哪些思考的呢? |
| 39 | + |
| 40 | +其实,主要是从缓存、安全性、线程安全和性能等角度触发的。 |
| 41 | + |
| 42 | +Q:缓存、安全性、线程安全和性能?这有都是啥 |
| 43 | +A:你别急,听我一个一个给你讲就好了。 |
| 44 | + |
| 45 | +#### 缓存 |
| 46 | + |
| 47 | +字符串是使用最广泛的数据结构。大量的字符串的创建是非常耗费资源的,所以,Java提供了对字符串的缓存功能,可以大大的节省堆空间。 |
| 48 | + |
| 49 | +JVM中专门开辟了一部分空间来存储Java字符串,那就是字符串池。 |
| 50 | + |
| 51 | +通过字符串池,两个内容相同的字符串变量,可以从池中指向同一个字符串对象,从而节省了关键的内存资源。 |
| 52 | + |
| 53 | +``` |
| 54 | +String s = "abcd"; |
| 55 | +String s2 = s; |
| 56 | +``` |
| 57 | + |
| 58 | + |
| 59 | +对于这个例子,s和s2都表示"abcd",所以他们会指向字符串池中的同一个字符串对象: |
| 60 | + |
| 61 | +![][2] |
| 62 | + |
| 63 | +但是,之所以可以这么做,主要是因为字符串的不变性。试想一下,如果字符串是可变的,我们一旦修改了s的内容,那必然导致s2的内容也被动的改变了,这显然不是我们想看到的。 |
| 64 | + |
| 65 | +#### 安全性 |
| 66 | + |
| 67 | +字符串在Java应用程序中广泛用于存储敏感信息,如用户名、密码、连接url、网络连接等。JVM类加载器在加载类的时也广泛地使用它。 |
| 68 | + |
| 69 | +因此,保护String类对于提升整个应用程序的安全性至关重要。 |
| 70 | + |
| 71 | +当我们在程序中传递一个字符串的时候,如果这个字符串的内容是不可变的,那么我们就可以相信这个字符串中的内容。 |
| 72 | + |
| 73 | +但是,如果是可变的,那么这个字符串内容就可能随时都被修改。那么这个字符串内容就完全可信了。这样整个系统就没有安全性可言了。 |
| 74 | + |
| 75 | +#### 线程安全 |
| 76 | + |
| 77 | +不可变会自动使字符串成为线程安全的,因为当从多个线程访问它们时,它们不会被更改。 |
| 78 | + |
| 79 | +因此,一般来说,不可变对象可以在同时运行的多个线程之间共享。它们也是线程安全的,因为如果线程更改了值,那么将在字符串池中创建一个新的字符串,而不是修改相同的值。因此,字符串对于多线程来说是安全的。 |
| 80 | + |
| 81 | +#### hashcode缓存 |
| 82 | + |
| 83 | +由于字符串对象被广泛地用作数据结构,它们也被广泛地用于哈希实现,如HashMap、HashTable、HashSet等。在对这些散列实现进行操作时,经常调用hashCode()方法。 |
| 84 | + |
| 85 | +不可变性保证了字符串的值不会改变。因此,hashCode()方法在String类中被重写,以方便缓存,这样在第一次hashCode()调用期间计算和缓存散列,并从那时起返回相同的值。 |
| 86 | + |
| 87 | +在String类中,有以下代码: |
| 88 | + |
| 89 | +``` |
| 90 | +private int hash;//this is used to cache hash code. |
| 91 | +``` |
| 92 | + |
| 93 | + |
| 94 | +#### 性能 |
| 95 | + |
| 96 | +前面提到了的字符串池、hashcode缓存等,都是提升性能的提现。 |
| 97 | + |
| 98 | +因为字符串不可变,所以可以用字符串池缓存,可以大大节省堆内存。而且还可以提前对hashcode进行缓存,更加高效 |
| 99 | + |
| 100 | +由于字符串是应用最广泛的数据结构,提高字符串的性能对提高整个应用程序的总体性能有相当大的影响。 |
| 101 | + |
| 102 | +### 总结 |
| 103 | + |
| 104 | +通过本文,我们可以得出这样的结论:字符串是不可变的,因此它们的引用可以被视为普通变量,可以在方法之间和线程之间传递它们,而不必担心它所指向的实际字符串对象是否会改变。 |
| 105 | + |
| 106 | +我们还了解了促使Java语言设计人员将该类设置为不可变类的其他原因。主要考虑的是缓存、安全性、线程安全和性能等方面 |
| 107 | + |
| 108 | + [1]: https://www.hollischuang.com/wp-content/uploads/2021/03/16163108328434.jpg |
| 109 | + [2]: https://www.hollischuang.com/wp-content/uploads/2021/03/16163114985563.jpg |
0 commit comments