Skip to content

Commit 5eff31f

Browse files
committed
聊聊String、StringBuilder、StringBuffer 三兄弟
1 parent 37538c0 commit 5eff31f

File tree

7 files changed

+513
-1
lines changed

7 files changed

+513
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@
119119
- [聊聊Java字符串,以及为什么String是不可变的?](docs/string/immutable.md)
120120
- [深入理解Java字符串常量池](docs/string/constant-pool.md)
121121
- [深入解析String.intern()方法](docs/string/intern.md)
122+
- [聊聊String、StringBuilder、StringBuffer 三兄弟](docs/string/builder-buffer.md)
122123
- [Java如何判断两个字符串是否相等?](docs/string/equals.md)
123124
- [最优雅的Java字符串拼接是哪种方式?](docs/string/join.md)
124125
- [如何在Java中优雅地分割String字符串?](docs/string/split.md)

docs/.vuepress/sidebar.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ export const sidebarConfig = sidebar({
126126
"string/immutable",
127127
"string/constant-pool",
128128
"string/intern",
129+
"string/builder-buffer",
129130
"string/equals",
130131
"string/join",
131132
"string/split",

docs/home.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ head:
128128
- [聊聊Java字符串,以及为什么String是不可变的?](string/immutable.md)
129129
- [深入理解Java字符串常量池](string/constant-pool.md)
130130
- [深入解析String.intern()方法](string/intern.md)
131+
- [聊聊String、StringBuilder、StringBuffer 三兄弟](docs/string/builder-buffer.md)
131132
- [Java如何判断两个字符串是否相等?](string/equals.md)
132133
- [最优雅的Java字符串拼接是哪种方式?](string/join.md)
133134
- [如何在Java中优雅地分割String字符串?](string/split.md)

docs/overview/what-is-java.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ head:
2828
2929
“三妹啊,听我慢慢来给你解释。”
3030

31+
### Java 的由来
32+
3133
Java 是一门计算机编程语言,高级、健壮、面向对象,并且非常安全。它由 Sun 公司在 1995 年开发,主力开发叫 James Gosling,被称为 Java 之父,就是下图这位,头秃的厉害。
3234

3335
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/overview/one-01.png)
@@ -70,6 +72,33 @@ public class HelloWorld {
7072

7173
“好吧。”
7274

75+
### Java 是编译型语言还是解释型语言
76+
77+
“二哥,你之前给我看了 .class 文件和 .java 源代码,它们之间的关系是什么样的呢?”三妹还是挺喜欢学习的嘛,发现的问题都很关键。
78+
79+
“不错不错,都能挖掘到这个点了。”
80+
81+
.java 是源代码,也就是我们开发人员可以看懂的,可以编写的;.class 是字节码文件,是经过 javac 编译后的文件,是交给 [JVM](https://tobebetterjavaer.com/jvm/what-is-jvm.html) 执行的文件。
82+
83+
[JVM到底是如何运行Java代码的?](https://tobebetterjavaer.com/jvm/how-run-java-code.html)
84+
85+
上面👆🏻这篇文章会详细讲解运行的过程。
86+
87+
“三妹,这里再顺带给你讲一下,Java 是编译型语言还是解释型语言。”
88+
89+
“好啊,我正要问这个‘编译’到底是怎么回事呢?”
90+
91+
Java的第一道工序是通过javac命令把Java源码编译成字节码,之后,我们可以通过java命令运行字节码,此时就有2种处理方式了。
92+
93+
- 1、字节码由JVM逐条解释执行。
94+
- 2、部分字节码可能由 [JIT,即时编译,戳链接了解](https://tobebetterjavaer.com/jvm/jit.html) 编译为机器指令直接执行。
95+
96+
也就是说,为了跨平台,Java源代码首先会编译成字节码,字节码不是机器语言,需要JVM来解释。有了JVM这个中间层,Java的运行效率就没有直接把源代码编译为机器码来得效率更高,这个应该能理解吗,多了中间商嘛。所以为了提高效率,JVM引入了 JIT 编译器,把一些经常执行的字节码直接搞成机器码。
97+
98+
所以,Java是解释和编译并存。但通常来说,我们会说“Java 是编译型语言”,尽管这样并不准确,主要是 JIT 是后面才出现的,“先入为主嘛”。
99+
100+
### 学 Java 有钱途吗?
101+
73102
“二哥,学 Java 到底有没有前途啊?我毕业以后能不能找到工作啊?”
74103

75104
“三妹啊,就目前来说,Java 不仅仅是一门编程语言,它还是一个由一系列计算机软件和规范组成的技术体系,这个技术体系提供了完整的用于软件开发和跨平台部署的支持环境,并广泛应用于以下这些场合。”

docs/string/builder-buffer.md

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
---
2+
title: 聊聊String、StringBuilder、StringBuffer 三兄弟
3+
shortTitle: String与StringBuilder和StringBuffer
4+
category:
5+
- Java核心
6+
tag:
7+
- 数组&字符串
8+
description: Java程序员进阶之路,小白的零基础Java教程,从入门到进阶,聊聊String与StringBuilder和StringBuffer 三兄弟
9+
head:
10+
- - meta
11+
- name: keywords
12+
content: Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java入门,教程,java字符串,String,StringBuilder,StringBuffer,string stringbuilder,string stringbuffer,string stringbuilder stringbuffer
13+
---
14+
15+
## 聊聊String、StringBuilder、StringBuffer 三兄弟
16+
17+
“哥,[上一篇深入理解 String.intern()](https://tobebetterjavaer.com/string/intern.html) 讲到了 StringBuilder,这一节我们就来聊聊吧!”三妹很期待。
18+
19+
“好啊,它们之间的关系还真的是挺和谐的。”看着三妹好奇的样子,我感到学技术就应该是这个样子才对。
20+
21+
由于字符串是不可变的,所以当遇到[字符串拼接](https://tobebetterjavaer.com/string/join.html)(尤其是使用`+`号操作符)的时候,就需要考量性能的问题,你不能毫无顾虑地生产太多 String 对象,对珍贵的内存造成不必要的压力。
22+
23+
于是 Java 就设计了一个专门用来解决此问题的 StringBuffer 类。
24+
25+
```java
26+
public final class StringBuffer extends AbstractStringBuilder implements Serializable, CharSequence {
27+
28+
public StringBuffer() {
29+
super(16);
30+
}
31+
32+
public synchronized StringBuffer append(String str) {
33+
super.append(str);
34+
return this;
35+
}
36+
37+
public synchronized String toString() {
38+
return new String(value, 0, count);
39+
}
40+
41+
// 其他方法
42+
}
43+
```
44+
45+
不过,由于 StringBuffer 操作字符串的方法加了 [`synchronized` 关键字](https://tobebetterjavaer.com/thread/synchronized-1.html)进行了同步,主要是考虑到多线程环境下的安全问题,所以执行效率会比较低。
46+
47+
于是 Java 就给 StringBuffer “生了个兄弟”,名叫 StringBuilder,说,“孩子,你别管线程安全了,你就在单线程环境下使用,这样效率会高得多,如果要在多线程环境下修改字符串,你到时候可以使用 `ThreadLocal` 来避免多线程冲突。”
48+
49+
```java
50+
public final class StringBuilder extends AbstractStringBuilder
51+
implements java.io.Serializable, CharSequence
52+
{
53+
// ...
54+
55+
public StringBuilder append(String str) {
56+
super.append(str);
57+
return this;
58+
}
59+
60+
public String toString() {
61+
// Create a copy, don't share the array
62+
return new String(value, 0, count);
63+
}
64+
65+
// ...
66+
}
67+
```
68+
69+
除了类名不同,方法没有加 synchronized,基本上完全一样。
70+
71+
实际开发中,StringBuilder 的使用频率也是远高于 StringBuffer,甚至可以这么说,StringBuilder 完全取代了 StringBuffer。
72+
73+
[之前我们也曾聊过](https://tobebetterjavaer.com/overview/what-is-java.html),Java 是一门解释型的编程语言,所以当编译器遇到 `+` 号这个操作符的时候,会将 `new String("二哥") + new String("三妹")` 这行代码编译为以下代码:
74+
75+
```java
76+
new StringBuilder().append("二哥").append("三妹").toString();
77+
```
78+
79+
这个过程是我们看不见的,但这正是 Java 的“智能”之处,它可以在编译的时候偷偷地帮我们做很多优化,这样既可以提高我们的开发效率(`+` 号写起来比创建 StringBuilder 对象便捷得多),也不会影响 JVM 的执行效率。
80+
81+
当然了,如果我们使用 [javap](https://tobebetterjavaer.com/jvm/bytecode.html) 反编译 `new String("二哥") + new String("三妹")` 的字节码的时候,也是能看出 StringBuilder 的影子的。
82+
83+
```
84+
0: new #2 // class java/lang/StringBuilder
85+
3: dup
86+
4: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V
87+
7: new #4 // class java/lang/String
88+
10: dup
89+
11: ldc #5 // String 二哥
90+
13: invokespecial #6 // Method java/lang/String."<init>":(Ljava/lang/String;)V
91+
16: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
92+
19: new #4 // class java/lang/String
93+
22: dup
94+
23: ldc #8 // String 三妹
95+
25: invokespecial #6 // Method java/lang/String."<init>":(Ljava/lang/String;)V
96+
28: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
97+
31: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
98+
34: areturn
99+
```
100+
101+
可以看到 Java 编译器将字符串拼接操作(`+`)转换为了 StringBuilder 对象的 append 方法,然后再调用 StringBuilder 对象的 toString 方法返回拼接后的字符串。
102+
103+
来看一下 StringBuilder 的 toString 方法:
104+
105+
```java
106+
public String toString() {
107+
return new String(value, 0, count);
108+
}
109+
```
110+
111+
value 是一个 char 类型的数组:
112+
113+
```java
114+
/**
115+
* The value is used for character storage.
116+
*/
117+
char[] value;
118+
```
119+
120+
在 StringBuilder 对象创建时,会为 value 分配一定的内存空间(初始容量 16),用于存储字符串。
121+
122+
```java
123+
/**
124+
* Constructs a string builder with no characters in it and an
125+
* initial capacity of 16 characters.
126+
*/
127+
public StringBuilder() {
128+
super(16);
129+
}
130+
```
131+
132+
随着字符串的拼接,value 数组的长度会不断增加,因此在 StringBuilder 对象的实现中,value 数组的长度是可以[动态扩展的,就像ArrayList那样](https://tobebetterjavaer.com/collection/arraylist.html)
133+
134+
继续来看 StringBuilder 的 toString 方法:
135+
136+
```java
137+
public String toString() {
138+
return new String(value, 0, count);
139+
}
140+
```
141+
142+
value 用于存储 StringBuilder 对象中包含的字符序列。count 是一个 int 类型的变量,表示字符序列的长度。toString() 方法会调用 `new String(value, 0, count)`,使用 value 数组中从 0 开始的前 count 个元素创建一个新的字符串对象,并将其返回。
143+
144+
再来看一下 append 方法:
145+
146+
```java
147+
public StringBuilder append(String str) {
148+
super.append(str);
149+
return this;
150+
}
151+
```
152+
153+
实际上是调用了 AbstractStringBuilder 中的 append(String str) 方法。在 AbstractStringBuilder 中,append(String str) 方法会检查当前字符序列中的字符是否够用,如果不够用则会进行扩容,并将指定字符串追加到字符序列的末尾。
154+
155+
```java
156+
/**
157+
* Appends the specified string to this character sequence.
158+
* <p>
159+
* The characters of the {@code String} argument are appended, in order,
160+
* increasing the length of this sequence by the length of the argument.
161+
* If {@code str} is {@code null}, then the four characters {@code "null"}
162+
* are appended.
163+
* <p>
164+
* Let <i>n</i> be the length of this character sequence just prior to
165+
* execution of the {@code append} method. Then the character at index
166+
* <i>k</i> in this character sequence is equal to the character at index
167+
* <i>k</i> in the argument {@code str}, if <i>k</i> is less than
168+
* <i>n</i>; otherwise, it is equal to the character at index
169+
* <i>k-n</i> in the argument {@code str}.
170+
*
171+
* @param str a string.
172+
* @return a reference to this object.
173+
*/
174+
public AbstractStringBuilder append(String str) {
175+
if (str == null)
176+
return appendNull();
177+
int len = str.length();
178+
ensureCapacityInternal(count + len);
179+
str.getChars(0, len, value, count);
180+
count += len;
181+
return this;
182+
}
183+
```
184+
185+
append(String str) 方法将指定字符串追加到当前字符序列中。如果指定字符串为 null,则追加字符串 "null";否则会检查指定字符串的长度,然后根据当前字符序列中的字符数和指定字符串的长度来判断是否需要扩容。
186+
187+
如果需要扩容,则会调用 ensureCapacityInternal(int minimumCapacity) 方法进行扩容。扩容之后,将指定字符串的字符拷贝到字符序列中。
188+
189+
来看一下 ensureCapacityInternal 方法:
190+
191+
```java
192+
private void ensureCapacityInternal(int minimumCapacity) {
193+
// overflow-conscious code
194+
if (minimumCapacity - value.length > 0)
195+
expandCapacity(minimumCapacity);
196+
}
197+
198+
void expandCapacity(int minimumCapacity) {
199+
int newCapacity = value.length * 2 + 2;
200+
if (newCapacity - minimumCapacity < 0)
201+
newCapacity = minimumCapacity;
202+
if (newCapacity < 0) {
203+
if (minimumCapacity < 0) // overflow
204+
throw new OutOfMemoryError();
205+
newCapacity = Integer.MAX_VALUE;
206+
}
207+
value = Arrays.copyOf(value, newCapacity);
208+
}
209+
```
210+
211+
`ensureCapacityInternal(int minimumCapacity)` 方法用于确保当前字符序列的容量至少等于指定的最小容量 minimumCapacity。如果当前容量小于指定的容量,就会为字符序列分配一个新的内部数组。新容量的计算方式如下:
212+
213+
- 如果指定的最小容量大于当前容量,则新容量为两倍的旧容量加上 2;
214+
- 如果指定的最小容量小于等于当前容量,则不会进行扩容,直接返回当前对象。
215+
216+
在进行扩容之前,`ensureCapacityInternal(int minimumCapacity)` 方法会先检查当前字符序列的容量是否足够,如果不足就会调用 `expandCapacity(int minimumCapacity)` 方法进行扩容。`expandCapacity(int minimumCapacity)` 方法首先计算出新容量,然后使用 `Arrays.copyOf(char[] original, int newLength)` 方法将原字符数组扩容到新容量的大小。
217+
218+
关于扩容,后面在讲[ArrayList](https://tobebetterjavaer.com/collection/arraylist.html)的时候会再次说明,今天就先聊到这吧。
219+
220+
“我想,关于 String、StringBuilder、StringBuilder 之间的差别,你都搞清楚了吧?”我问。
221+
222+
“哥,你真棒!区别我是搞清楚了,你后面讲的源码扩容还没消化,我一会去加个餐,再细看一下。”三妹说。
223+
224+
“可以的,实际上,你现在只需要知道 StringBuilder 的用法就可以了。”喝了一口右手边的可口可乐(无糖)后,我感觉好爽快啊。
225+
226+
---
227+
228+
最近整理了一份牛逼的学习资料,包括但不限于Java基础部分(JVM、Java集合框架、多线程),还囊括了 **数据库、计算机网络、算法与数据结构、设计模式、框架类Spring、Netty、微服务(Dubbo,消息队列) 网关** 等等等等……详情戳:[可以说是2022年全网最全的学习和找工作的PDF资源了](https://tobebetterjavaer.com/pdf/programmer-111.html)
229+
230+
微信搜 **沉默王二** 或扫描下方二维码关注二哥的原创公众号沉默王二,回复 **111** 即可免费领取。
231+
232+
![](https://cdn.tobebetterjavaer.com/tobebetterjavaer/images/gongzhonghao.png)

docs/string/intern.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ new StringBuilder().append("二哥").append("三妹").toString();
131131
- 在 StringBuilder 对象上调用 append("三妹"),将 "三妹" 追加到 StringBuilder 中。
132132
- 在 StringBuilder 对象上调用 toString() 方法,将 StringBuilder 转换为一个新的字符串对象,内容为 "二哥三妹"。
133133

134-
关于 StringBuilder,我们随后会详细地讲到。今天先了解到这。
134+
关于 [StringBuilder](https://tobebetterjavaer.com/string/builder-buffer.html),我们随后会详细地讲到。今天先了解到这。
135135

136136
不过需要注意的是,尽管 intern 可以确保所有具有相同内容的字符串共享相同的内存空间,但也不要烂用 intern,因为任何的缓存池都是有大小限制的,不能无缘无故就占用了相对稀缺的缓存空间,导致其他字符串没有坑位可占。
137137

0 commit comments

Comments
 (0)