FazBrowse GitHub Viewer
|
Trending
|
URL:
|
Home
Tools:
[Download Repo ZIP]
[View Raw Code]
[Original HTTPS Page]
toBeBetterJavaer/docs/src/string/string-source.md at master · itwanger/toBeBetterJavaer · GitHub
itwanger
/
toBeBetterJavaer
Public
Notifications
You must be signed in to change notification settings
Fork
2.5k
Star
17.5k
Code
Issues
58
Pull requests
8
Actions
Projects
Security and quality
0
Insights
Additional navigation options
Code
Issues
Pull requests
Actions
Projects
Security and quality
Insights
Expand file tree
Breadcrumbs
toBeBetterJavaer
/
docs
/
src
/
string
/
string-source.md
Copy path
More file actions
More file actions
Latest commit
History
History
History
522 lines (384 loc) · 24.9 KB
Breadcrumbs
toBeBetterJavaer
/
docs
/
src
/
string
/
string-source.md
Copy path
File metadata and controls
522 lines (384 loc) · 24.9 KB
Raw
Copy raw file
Download raw file
Open symbols panel
Edit and raw actions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
---
title
:
深入解读String类的源码
shortTitle
:
字符串源码解读
category
:
-
Java核心
tag
:
-
数组
&
字符串
description
:
本文将对Java
String类的源码进行深入分析
,
帮助你理解字符串在Java中的实现原理
、
操作方式和性能优化策略
。
通过详细解读String源码
,
我们将揭示字符串类的内部机制
,
掌握如何高效地进行字符串操作
,
以及在实际编程中如何应用这些知识来提高代码质量和性能
。
head
:
- -
meta
-
name
:
keywords
content
:
Java
,
String类
,
源码
,
源码解析
,
字符串操作
,
String
---
我正坐在沙发上津津有味地读刘欣大佬的
《
码农翻身
》——
Java
帝国这一章
,
门铃响了
。
起身打开门一看
,
是三妹
,
她从学校回来了
。
“
三妹
,
你回来的真及时
,
今天我们打算讲
Java
中的字符串呢
。”
等三妹换鞋的时候我说
。
“
哦
,
可以呀
,
哥
。
听说字符串的细节特别多
,
什么
[
字符串常量池
](
https
:
//javabetter.cn/string/constant-pool.html)了、[字符串不可变性](https://javabetter.cn/string/immutable.html)了、[字符串拼接](https://javabetter.cn/string/join.html)了、字符串长度限制了等等,你最好慢慢讲,否则我可能一时半会消化不了。”三妹的态度显得很诚恳。
“
嗯
,
我已经想好了
,
今天就只带你大概认识一下字符串
,
主要读一读它的源码
,
其他的细节咱们后面再慢慢讲
,
保证你能及时消化
。”
“
好
,
那就开始吧
。”
三妹已经准备好坐在了电脑桌的边上
。
我应了一声后走到电脑桌前坐下来
,
顺手打开
[
Intellij
IDEA
](
https
:
//javabetter.cn/overview/IDEA-install-config.html),并找到了 String 的源码(Java 8 版本)。
###
String
类的声明
```
java
public
final
class
String
implements
java
.
io
.
Serializable
,
Comparable
<
String
>,
CharSequence
{
}
```
“
第一
,
String
类是
[
final
](
https
:
//javabetter.cn/oo/final.html) 的,意味着它不能被子类[继承](https://javabetter.cn/oo/encapsulation-inheritance-polymorphism.html)。这些知识我们讲面向对象编程的时候都会讲到,三妹你不用担心。”
“
第二
,
String
类实现了
[
Serializable
接口
](
https
:
//javabetter.cn/io/Serializbale.html),意味着它可以[序列化](https://javabetter.cn/io/serialize.html)(后面同样会讲,戳链接可跳转)。”
“
第三
,
String
类实现了
[
Comparable
接口
](
https
:
//javabetter.cn/basic-extra-meal/comparable-omparator.html),意味着最好不要用‘==’来[比较两个字符串是否相等](https://javabetter.cn/string/equals.html),而应该用 `compareTo()` 方法去比较。”
因为
==
是用来比较两个对象的地址
,
这个在讲
[
字符串比较
](
https
:
//javabetter.cn/string/equals.html)的时候会详细讲。如果只是说比较字符串内容的话,可以使用 String 类的 equals 方法,源码和注释如下所示:
```
java
public
boolean
equals
(
Object
anObject
) {
// 检查是否是同一个对象的引用,如果是,直接返回 true
if
(
this
==
anObject
) {
return
true
;
}
// 检查 anObject 是否是 String 类的实例
if
(
anObject
instanceof
String
) {
String
anotherString
= (
String
)
anObject
;
// 将 anObject 强制转换为 String 类型
int
n
=
value
.
length
;
// 获取当前字符串的长度
// 检查两个字符串长度是否相等
if
(
n
==
anotherString
.
value
.
length
) {
char
v1
[] =
value
;
// 当前字符串的字符数组
char
v2
[] =
anotherString
.
value
;
// 另一个字符串的字符数组
int
i
=
0
;
// 用于遍历字符数组的索引
// 遍历比较两个字符串的每个字符
while
(
n
-- !=
0
) {
// 如果在任何位置字符不同,则返回 false
if
(
v1
[
i
] !=
v2
[
i
])
return
false
;
i
++;
}
// 所有字符都相同,返回 true
return
true
;
}
}
// 如果 anObject 不是 String 类型或长度不等,则返回 false
return
false
;
}
```
“
第四
,[
String
和
StringBuffer
、
StringBuilder
](
https
:
//javabetter.cn/string/builder-buffer.html) 一样,都实现了 CharSequence 接口,所以它们仨属于近亲。由于 String 是不可变的,所以遇到[字符串拼接](https://javabetter.cn/string/join.html)的时候就可以考虑一下 String 的另外两个好兄弟,StringBuffer 和 StringBuilder,它俩是可变的。”
###
String
底层为什么由
char
数组优化为
byte
数组
```
java
private
final
char
value
[];
```
“
第五
,
Java
9
以前
,
String
是用
char
型
[
数组
](
https
:
//javabetter.cn/array/array.html)实现的,之后改成了 byte 型数组实现,并增加了 coder 来表示编码。这样做的好处是在 Latin1 字符为主的程序里,可以把 String 占用的内存减少一半。当然,天下没有免费的午餐,这个改进在节省内存的同时引入了编码检测的开销。”
>
Latin1
(
Latin
-
1
)
是一种单字节字符集
(
即每个字符只使用一个字节的编码方式
),
也称为
ISO
-
8859
-
1
(
国际标准化组织
8859
-
1
),
它包含了西欧语言中使用的所有字符
,
包括英语
、
法语
、
德语
、
西班牙语
、
葡萄牙语
、
意大利语等等
。
在
Latin1
编码中
,
每个字符使用一个
8
位
(
即一个字节
)
的编码
,
可以表示
256
种不同的字符
,
其中包括
ASCII
字符集中的所有字符
,
即
0x00
到
0x7F
,
以及其他西欧语言中的特殊字符
,
例如
é
、
ü
、
ñ
等等
。
由于
Latin1
只使用一个字节表示一个字符
,
因此在存储和传输文本时具有较小的存储空间和较快的速度
下面是
JDK
11
版本中的
String
类源码
,
注意和
JDK
8
的不同
。
```
java
public
final
class
String
implements
java
.
io
.
Serializable
,
Comparable
<
String
>,
CharSequence
{
@
Stable
private
final
byte
[]
value
;
private
final
byte
coder
;
private
int
hash
;
}
```
从
`
char
[]`
到
`
byte
[]`,
最主要的目的是
**
节省字符串占用的内存空间
**。
内存占用减少带来的另外一个好处
,
就是
[
GC
](
https
:
//javabetter.cn/jvm/gc.html) 次数也会减少。
>
GC
,
也就是垃圾回收
,
JVM
的时候会讲到
。
可以戳链接了解
。
这里给你讲一个小知识
。
我们使用
`
jmap
-
histo
:
live
pid
|
head
-
n
10
`
命令就可以查看到堆内对象示例的统计信息
、
ClassLoader
的信息以及
finalizer
队列等
。
>
Java
的对象基本上都在
[
堆
](
https
:
//javabetter.cn/jvm/neicun-jiegou.html)上。后面也会讲。这里的 pid 就是进程号,可以通过 `ps -ef | grep java` 命令查看,下图中红色框出来的第二项就是 pid。

就以正在运行着的
[
编程喵
](
https
:
//github.com/itwanger/coding-more)项目来说吧(它是以 JDK 8 运行的),结果是这样的。

其中
String
对象有
17638
个
,
占用了
423312
个字节的内存
,
排在第三位
。
由于
Java
8
的
String
内部实现仍然是
`char[]`,
所以我们可以看到内存占用排在第
1
位的就是
char
数组
。
`char[]`
对象有
17673
个
,
占用了
1621352
个字节的内存
,
排在第一位
。
那也就是说优化
String
节省内存空间是非常有必要的
,
如果是去优化一个使用频率没有
String
这么高的类
,
就没什么必要
,
对吧
?
众所周知
,char
类型的数据在
JVM
中是占用两个字节的
,
并且使用的是
UTF
-
8
[
编码
](
https
:
//javabetter.cn/basic-extra-meal/java-unicode.html),其值范围在 '\u0000'(0)和 '\uffff'(65,535)(包含)之间。
也就是说
,
使用
`char[]`
来表示
String
就会导致
,
即使
String
中的字符只用一个字节就能表示
,
也得占用两个字节
。
>
PS
:
在计算机中
,
单字节字符通常指的是一个字节
(
8
位
)
可以表示的字符
,
而双字节字符则指需要两个字节
(
16
位
)
才能表示的字符
。
单字节字符和双字节字符的定义是相对的
,
不同的编码方式对应的单字节和双字节字符集也不同
。
常见的单字节字符集有
ASCII
(
美国信息交换标准代码
)、
ISO
-
8859
(
国际标准化组织标准编号
8859
)、
GBK
(
汉字内码扩展规范
)、
GB2312
(
中国国家标准
,
现在已经被
GBK
取代
),
像拉丁字母
、
数字
、
标点符号
、
控制字符都是单字节字符
。
双字节字符集包括
Unicode
、
UTF
-
8
、
GB18030
(
中国国家标准
),
中文
、
日文
、
韩文
、
拉丁文扩展字符属于双字节字符
。
当然了
,
仅仅将
`char[]`
优化为
`byte[]`
是不够的
,
还要配合
Latin
-
1
的编码方式
,
该编码方式是用单个字节来表示字符的
,
这样就比
UTF
-
8
编码节省了更多的空间
。
换句话说
,
对于
:
```
java
String
name
= "
jack
";
```
这样的
,
使用
Latin
-
1
编码
,
占用
4
个字节就够了
。
但对于
:
```
java
String
name
= "
小二
";
```
这种
,
木的办法
,
只能使用
UTF16
来编码
。
针对
JDK
9
的
String
源码里
,
为了区别编码方式
,
追加了一个
coder
字段来区分
。
```
java
/**
* The identifier of the encoding used to encode the bytes in
* {@code value}. The supported values in this implementation are
*
* LATIN1
* UTF16
*
* @implNote This field is trusted by the VM, and is a subject to
* constant folding if String instance is constant. Overwriting this
* field after construction will cause problems.
*/
private
final
byte
coder
;
```
Java
会根据字符串的内容自动设置为相应的编码
,
要么
Latin
-
1
要么
UTF16
。
也就是说
,
从
`char[]`
到
`byte[]`,**
中文是两个字节
,
纯英文是一个字节
,
在此之前呢
,
中文是两个字节
,
英文也是两个字节
**。
在
UTF
-
8
中
,
0
-
127
号的字符用
1
个字节来表示
,
使用和
ASCII
相同的编码
。
只有
128
号及以上的字符才用
2
个
、
3
个或者
4
个字节来表示
。
-
如果只有一个字节
,
那么最高的比特位为
0
;
-
如果有多个字节
,
那么第一个字节从最高位开始
,
连续有几个比特位的值为
1
,
就使用几个字节编码
,
剩下的字节均以
10
开头
。
具体的表现形式为
:
-
0
xxxxxxx
:
一个字节
;
-
110
xxxxx
10
xxxxxx
:
两个字节编码形式
(
开始两个
1
);
-
1110
xxxx
10
xxxxxx
10
xxxxxx
:
三字节编码形式
(
开始三个
1
);
-
11110
xxx
10
xxxxxx
10
xxxxxx
10
xxxxxx
:
四字节编码形式
(
开始四个
1
)。
也就是说
,
UTF
-
8
是变长的
,
那对于
String
这种有随机访问方法的类来说
,
就很不方便
。
所谓的随机访问
,
就是
charAt
、
subString
这种方法
,
随便指定一个数字
,
String
要能给出结果
。
如果字符串中的每个字符占用的内存是不定长的
,
那么进行随机访问的时候
,
就需要从头开始数每个字符的长度
,
才能找到你想要的字符
。
那你可能会问
,
UTF
-
16
也是变长的呢
?
一个字符还可能占用
4
个字节呢
?
的确
,
UTF
-
16
使用
2
个或者
4
个字节来存储字符
。
-
对于
Unicode
编号范围在
0
~
FFFF
之间的字符
,
UTF
-
16
使用两个字节存储
。
-
对于
Unicode
编号范围在
10000
~
10F
FFF
之间的字符
,
UTF
-
16
使用四个字节存储
,
具体来说就是
:
将字符编号的所有比特位分成两部分
,
较高的一些比特位用一个值介于
D800
~
DBFF
之间的双字节存储
,
较低的一些比特位
(
剩下的比特位
)
用一个值介于
DC00
~
DFFF
之间的双字节存储
。
但是在
Java
中
,
一个字符
(char)
就是
2
个字节
,
占
4
个字节的字符
,
在
Java
里也是用两个
char
来存储的
,
而
String
的各种操作
,
都是以
Java
的字符
(char)
为单位的
,
charAt
是取得第几个
char,
subString
取的也是第几个到第几个
char
组成的子串
,
甚至
length
返回的都是
char
的个数
。
所以
UTF
-
16
在
Java
的世界里
,
就可以视为一个定长的编码
。
>
参考链接
:[
https
:
//www.zhihu.com/question/447224628](https://www.zhihu.com/question/447224628)
###
String
类的
hashCode
方法
“
第六
,
每一个字符串都会有一个
hash
值
,
这个哈希值在很大概率是不会重复的
,
因此
String
很适合来作为
[
HashMap
](
https
:
//javabetter.cn/collection/hashmap.html)(后面会细讲)的键值。”
来看
String
类的
hashCode
方法
。
```
java
private
int
hash
;
// 缓存字符串的哈希码
public
int
hashCode
() {
int
h
=
hash
;
// 从缓存中获取哈希码
// 如果哈希码未被计算过(即为 0)且字符串不为空,则计算哈希码
if
(
h
==
0
&&
value
.
length
>
0
) {
char
val
[] =
value
;
// 获取字符串的字符数组
// 遍历字符串的每个字符来计算哈希码
for
(
int
i
=
0
;
i
<
value
.
length
;
i
++) {
h
=
31
*
h
+
val
[
i
];
// 使用 31 作为乘法因子
}
hash
=
h
;
// 缓存计算后的哈希码
}
return
h
;
// 返回哈希码
}
```
hashCode
方法首先检查是否已经计算过哈希码
,
如果已经计算过
,
则直接返回缓存的哈希码
。
否则
,
方法将使用一个循环遍历字符串的所有字符
,
并使用一个乘法和加法的组合计算哈希码
。
这种计算方法被称为
“
31
倍哈希法
”。
计算完成后
,
将得到的哈希值存储在
hash
成员变量中
,
以便下次调用
hashCode
方法时直接返回该值
,
而不需要重新计算
。
这是一种缓存优化
,
称为
“
惰性计算
”。
31
倍哈希法
(
31
-
Hash
)
是一种简单有效的字符串哈希算法
,
常用于对字符串进行哈希处理
。
该算法的基本思想是将字符串中的每个字符乘以一个固定的质数
31
的幂次方
,
并将它们相加得到哈希值
。
具体地
,
假设字符串为
s
,
长度为
n
,
则
31
倍哈希值计算公式如下
:
```
java
H
(
s
) = (
s
[
0
] *
31
^(
n
-
1
)) + (
s
[
1
] *
31
^(
n
-
2
)) + ... + (
s
[
n
-
1
] *
31
^
0
)
```
其中
,
s
[
i
]
表示字符串
s
中第
i
个字符的
ASCII
码值
,`^`
表示幂运算
。
31
倍哈希法的优点在于简单易实现
,
计算速度快
,
同时也比较均匀地分布在哈希表中
。
[
hashCode
方法
](
https
:
//javabetter.cn/basic-extra-meal/hashcode.html),我们会在另外一个章节里详细讲,戳前面的链接了解。
我们可以通过以下方法模拟
String
的
hashCode
方法
:
```
java
public
class
HashCodeExample
{
public
static
void
main
(
String
[]
args
) {
String
text
=
"沉默王二"
;
int
hashCode
=
computeHashCode
(
text
);
System
.
out
.
println
(
"字符串
\"
"
+
text
+
"
\"
的哈希码是: "
+
hashCode
);
System
.
out
.
println
(
"String 的 hashCode "
+
text
.
hashCode
());
}
public
static
int
computeHashCode
(
String
text
) {
int
h
=
0
;
for
(
int
i
=
0
;
i
<
text
.
length
();
i
++) {
h
=
31
*
h
+
text
.
charAt
(
i
);
}
return
h
;
}
}
```
看一下结果
:
```
字符串
"
沉默王二
"
的哈希码是
:
867758096
String
的
hashCode
867758096
```
结果是一样的
,
又学到了吧
?
###
String
类的
substring
方法
String
类中还有一个方法比较常用
substring
,
用来截取字符串的
,
来看源码
。
```
java
public
String
substring
(int
beginIndex
) {
// 检查起始索引是否小于 0,如果是,则抛出 StringIndexOutOfBoundsException 异常
if
(
beginIndex
<
0
) {
throw
new
StringIndexOutOfBoundsException
(
beginIndex
);
}
// 计算子字符串的长度
int
subLen
=
value
.
length
-
beginIndex
;
// 检查子字符串长度是否为负数,如果是,则抛出 StringIndexOutOfBoundsException 异常
if
(
subLen
<
0
) {
throw
new
StringIndexOutOfBoundsException
(
subLen
);
}
// 如果起始索引为 0,则返回原字符串;否则,创建并返回新的字符串
return
(
beginIndex
==
0
) ?
this
:
new
String
(
value
,
beginIndex
,
subLen
);
}
```
substring
方法首先检查参数的有效性
,
如果参数无效
,
则抛出
StringIndexOutOfBoundsException
[
异常
](
https
:
//javabetter.cn/exception/gailan.html)(后面会细讲)。接下来,方法根据参数计算子字符串的长度。如果子字符串长度小于零,也会抛出 StringIndexOutOfBoundsException 异常。
如果
beginIndex
为
0
,
说明子串与原字符串相同
,
直接返回原字符串
。
否则
,
使用
value
数组
(
原字符串的字符数组
)
的一部分
new
一个新的
String
对象并返回
。
下面是几个使用
substring
方法的示例
:
①、
提取字符串中的一段子串
:
```
java
String
str
=
"Hello, world!"
;
String
subStr
=
str
.
substring
(
7
,
12
);
// 从第7个字符(包括)提取到第12个字符(不包括)
System
.
out
.
println
(
subStr
);
// 输出 "world"
```
②、
提取字符串中的前缀或后缀
:
```
java
String
str
=
"Hello, world!"
;
String
prefix
=
str
.
substring
(
0
,
5
);
// 提取前5个字符,即 "Hello"
String
suffix
=
str
.
substring
(
7
);
// 提取从第7个字符开始的所有字符,即 "world!"
```
③、
处理字符串中的空格和分隔符
:
```
java
String
str
=
" Hello, world! "
;
String
trimmed
=
str
.
trim
();
// 去除字符串开头和结尾的空格
String
[]
words
=
trimmed
.
split
(
"
\\
s+"
);
// 将字符串按照空格分隔成单词数组
String
firstWord
=
words
[
0
].
substring
(
0
,
1
);
// 提取第一个单词的首字母
System
.
out
.
println
(
firstWord
);
// 输出 "H"
```
④、
处理字符串中的数字和符号
:
```
java
String
str
=
"1234-5678-9012-3456"
;
String
[]
parts
=
str
.
split
(
"-"
);
// 将字符串按照连字符分隔成四个部分
String
last4Digits
=
parts
[
3
].
substring
(
1
);
// 提取最后一个部分的后三位数字
System
.
out
.
println
(
last4Digits
);
// 输出 "456"
```
总之
,
substring
方法可以根据需求灵活地提取字符串中的子串
,
为字符串处理提供了便利
。
###
String
类的
indexOf
方法
indexOf
方法用于查找一个子字符串在原字符串中第一次出现的位置
,
并返回该位置的索引
。
来看该方法的源码
:
```
java
/*
* 查找字符数组 target 在字符数组 source 中第一次出现的位置。
* sourceOffset 和 sourceCount 参数指定 source 数组中要搜索的范围,
* targetOffset 和 targetCount 参数指定 target 数组中要搜索的范围,
* fromIndex 参数指定开始搜索的位置。
* 如果找到了 target 数组,则返回它在 source 数组中的位置索引(从0开始),
* 否则返回-1。
*/
static
int
indexOf
(
char
[]
source
,
int
sourceOffset
,
int
sourceCount
,
char
[]
target
,
int
targetOffset
,
int
targetCount
,
int
fromIndex
) {
// 如果开始搜索的位置已经超出 source 数组的范围,则直接返回-1(如果 target 数组为空,则返回 sourceCount)
if
(
fromIndex
>=
sourceCount
) {
return
(
targetCount
==
0
?
sourceCount
: -
1
);
}
// 如果开始搜索的位置小于0,则从0开始搜索
if
(
fromIndex
<
0
) {
fromIndex
=
0
;
}
// 如果 target 数组为空,则直接返回开始搜索的位置
if
(
targetCount
==
0
) {
return
fromIndex
;
}
// 查找 target 数组的第一个字符在 source 数组中的位置
char
first
=
target
[
targetOffset
];
int
max
=
sourceOffset
+ (
sourceCount
-
targetCount
);
// 循环查找 target 数组在 source 数组中的位置
for
(
int
i
=
sourceOffset
+
fromIndex
;
i
<=
max
;
i
++) {
/* Look for first character. */
// 如果 source 数组中当前位置的字符不是 target 数组的第一个字符,则在 source 数组中继续查找 target 数组的第一个字符
if
(
source
[
i
] !=
first
) {
while
(++
i
<=
max
&&
source
[
i
] !=
first
);
}
/* Found first character, now look at the rest of v2 */
// 如果在 source 数组中找到了 target 数组的第一个字符,则继续查找 target 数组的剩余部分是否匹配
if
(
i
<=
max
) {
int
j
=
i
+
1
;
int
end
=
j
+
targetCount
-
1
;
for
(
int
k
=
targetOffset
+
1
;
j
<
end
&&
source
[
j
]
==
target
[
k
];
j
++,
k
++);
// 如果 target 数组全部匹配,则返回在 source 数组中的位置索引
if
(
j
==
end
) {
/* Found whole string. */
return
i
-
sourceOffset
;
}
}
}
// 没有找到 target 数组,则返回-1
return
-
1
;
}
```
来看示例
。
①、
示例
1
:
查找子字符串的位置
```
java
String
str
=
"Hello, world!"
;
int
index
=
str
.
indexOf
(
"world"
);
// 查找 "world" 子字符串在 str 中第一次出现的位置
System
.
out
.
println
(
index
);
// 输出 7
```
②、
示例
2
:
查找字符串中某个字符的位置
```
java
String
str
=
"Hello, world!"
;
int
index
=
str
.
indexOf
(
","
);
// 查找逗号在 str 中第一次出现的位置
System
.
out
.
println
(
index
);
// 输出 5
```
③、
示例
3
:
查找子字符串的位置
(
从指定位置开始查找
)
```
java
String
str
=
"Hello, world!"
;
int
index
=
str
.
indexOf
(
"l"
,
3
);
// 从索引为3的位置开始查找 "l" 子字符串在 str 中第一次出现的位置
System
.
out
.
println
(
index
);
// 输出 3
```
④、
示例
4
:
查找多个子字符串
```
java
String
str
=
"Hello, world!"
;
int
index1
=
str
.
indexOf
(
"o"
);
// 查找 "o" 子字符串在 str 中第一次出现的位置
int
index2
=
str
.
indexOf
(
"o"
,
5
);
// 从索引为5的位置开始查找 "o" 子字符串在 str 中第一次出现的位置
System
.
out
.
println
(
index1
);
// 输出 4
System
.
out
.
println
(
index2
);
// 输出 8
```
###
String
类的其他方法
①、
比如说
`
length
()`
用于返回字符串长度
。
②、
比如说
`
isEmpty
()`
用于判断字符串是否为空
。
③、
比如说
`
charAt
()`
用于返回指定索引处的字符
。
④、
比如说
`
valueOf
()`
用于将其他类型的数据转换为字符串
。
```
java
String
str
=
String
.
valueOf
(
123
);
// 将整数 123 转换为字符串
```
valueOf
方法的背后其实调用的是包装器类的
toString
方法
,
比如说整数转为字符串调用的是
Integer
类的
toString
方法
。
```
java
public
static
String
valueOf
(
int
i
) {
return
Integer
.
toString
(
i
);
}
```
而
Integer
类的
toString
方法又调用了
Integer
类的静态方法
`
toString
(
int
i
)`:
```
java
public
static
String
toString
(
int
i
) {
// 最小值返回 "-2147483648"
if
(
i
==
Integer
.
MIN_VALUE
)
return
"-2147483648"
;
// 整数的长度,负数的长度减 1
int
size
= (
i
<
0
) ?
stringSize
(-
i
) +
1
:
stringSize
(
i
);
// 把整数复制到字符数组中
char
[]
buf
=
new
char
[
size
];
// 具体的复制过程
getChars
(
i
,
size
,
buf
);
// 通过 new 返回字符串
return
new
String
(
buf
,
true
);
}
```
至于
getChars
方法
,
就是把整数复制到字符数组中的具体过程了
,
这里就不展开了
。
⑥、
比如说
`
getBytes
()`
用于返回字符串的字节数组
,
可以指定编码方式
,
比如说
:
```
java
String
text
=
"沉默王二"
;
System
.
out
.
println
(
Arrays
.
toString
(
text
.
getBytes
(
StandardCharsets
.
UTF_8
)));
```
⑦、
比如说
`
trim
()`
用于去除字符串两侧的空白字符
,
来看源码
:
```
java
public
String
trim
() {
int
len
=
value
.
length
;
int
st
=
0
;
char
[]
val
=
value
;
/* avoid getfield opcode */
while
((
st
<
len
) && (
val
[
st
] <=
' '
)) {
st
++;
}
while
((
st
<
len
) && (
val
[
len
-
1
] <=
' '
)) {
len
--;
}
return
((
st
>
0
) || (
len
<
value
.
length
)) ?
substring
(
st
,
len
) :
this
;
}
```
举例
:`
" 沉默王二 "
.
trim
()`
会返回
"沉默王二"
⑧、
比如说
`
toCharArray
()`
用于将字符串转换为字符数组
。
```
java
String
text
=
"沉默王二"
;
char
[]
chars
=
text
.
toCharArray
();
System
.
out
.
println
(
Arrays
.
toString
(
chars
));
```
除此之外
,
还有
[
split
](
https
:
//javabetter.cn/string/split.html)、[equals](https://javabetter.cn/string/equals.html)、[join](https://javabetter.cn/string/join.html) 等这些方法,我们后面会一一来细讲。
###
小结
学完这一节
,
其实可以拿
LeetCode
的第三道题
「[
无重复字符的最长子串
](
https
:
//leetcode.cn/problems/longest-substring-without-repeating-characters/)」来练手,你可以通过暴力的方式,也就是两个 for 循环来解决。
当然了
,
如果实在解不出来
,
题解我放到了技术派的
「
二哥的
LeetCode
刷题笔记
」
当中
,
你可以去参考
。
是对字符串和
for
循环的一次很好的练习
。
- [
无重复字符的最长子串
](
https
:
//paicoding.com/column/7/3)
---
GitHub
上标星
17000
+
的开源知识库
《[
二哥的
Java
进阶之路
](
https
:
//github.com/itwanger/toBeBetterJavaer)》第一版 PDF 终于来了!包括 Java 基础语法、数组&字符串、OOP、集合框架、Java IO、异常处理、Java 新特性、网络编程、NIO、并发编程、JVM 等等,共计 32 万余字,500+张手绘图,可以说是通俗易懂、风趣幽默……详情戳:[太赞了,GitHub 上标星 17000+ 的 Java 教程](https://javabetter.cn/overview/)
微信搜
**
沉默王二
**
或扫描下方二维码关注二哥的原创公众号沉默王二
,
回复
**
222
**
即可免费领取
。

Back
|
FazBrowse Home
|
New Git URL