Java String及其相关的类

1. String 类
String 在 Java 中是 Object 类的子类,本身被声明为 final 类,所以 不能被继承,用于处理字符串。
1.1 定义字符串
1.1.1 创建字符串常量:
|
|
s1 是 String 的对象,“MicroLOONG” 这个字符串被存放在 常量池 中。而 s2 与 s1 具有 相同的引用,所以他们代表同一个对象。
1.1.2 创建字符串对象:
|
|
s 是通过 String 类的构造方法创建的对象,“MicroLOONG” 这个字符串被存放在 堆 中。
1.2 字符串比较
表达式 == 和 equals() 方法是常用的字符串内容比较方式。
对于赋值了相同内容的字符串常量,如上文的 s1 与 s2 ,使用两种方式效果一致,结果都是 true,因为它们具有相同的引用。
但是有时只能使用 equals():
|
|
结果为:
|
|
可从上面的结果归纳出:
-
==比较的是引用地址;String类覆写了equals()方法,比较的是字符串内容。 -
比较两个字符串内容优先使用
equals()。 -
要忽略大小写比较,可使用
equalsIgnoreCase()方法。
1.3 连接字符串
前文提到 String 被声明为 final 类,那如何完成字符串拼接呢?
1.3.1 使用 + 拼接:
|
|
实际上,Java中的 + 对字符串拼接的原理是将 String 转成了 StringBuilder,然后使用 StringBuilder.append() 方法 进行处理的。
相当于:
|
|
1.3.2 使用 StringBuffer 或 StringBuilder 拼接:
StringBuffer 和 StringBuilder 类与 String 类不同,它们并不是 final 的,所以可以直接修改。
StringBuffer 和 StringBuilder 类的 append() 方法可以将其他类型的数据转换为字符串后,追加到对象的末尾。
|
|
1.3.3 使用 concat 拼接:
String 类提供了 concat() 方法,效果跟 StringBuffer 和 StringBuilder 类的 append() 方法相同。
|
|
因为String 类是 final 的,所以 concat() 方法实际是将两个字符串的值 复制到新的字符数组 中,然后创建一个 新的 String 对象。
1.3.4 使用 join 拼接:
在 JDK8 中 String 类的静态方法 join(),它用指定的字符串来连接字符串数组/集合。
对于以下集合,输出字符串 A - B - C。
|
|
而使用 join() 方法省去了循环添加字符串的代码,实际上也是使用 StringBuilder 实现的。
|
|
总结:
效率比较:StringBuilder > StringBuffer > concat > + > String.join
-
+拼接字符串的原理也是StringBuilder,但是在 循环 中使用+会导致频繁新建StringBuilder对象,耗费时间,浪费资源。非循环体内可以直接使用+。 -
StringBuilder效率最高,但是StringBuilder不是线程安全的,StringBuffer中的append()方法声明了synchronized,是 线程安全 的。所以在并发场景中尽量使用StringBuffer。 -
String.join更适合处理 字符串数组或者集合 的拼接。
1.4 类型转换
1.4.1 任意类型转换为字符串
使用静态方法 valueOf(),根据不同类型的参数进行重载:
|
|
1.4.2 字符串转换为其他类型
字符串转换为基本数据类型,如果类型转换结果不匹配,则抛出 NumberFormatException 异常。
|
|
字符串转换为字符数组:
|
|
1.5 格式化字符串
使用 String 类的静态方法 format() 格式化字符串。
|
|
也可使用类似 C 语言的 printf() 方法。
|
|
结果都是:
|
|
1.6 正则表达式
正则表达式定义了字符串的模式,可以用来搜索、编辑或处理文本,它不局限于 Java 语言。常见的元字符如下所示:
| 字符 | 说明 |
|---|---|
| \ | 将下一字符标记为 特殊字符、文本、反向引用或八进制转义符。例如,n 匹配字符 n。\n 匹配换行符。序列 \\\\ 匹配 \\,\\( 匹配 (。 |
| ^ | 匹配输入字符串 开始 的位置。如果设置了 RegExp 对象的 Multiline 属性,^ 还会与 \n 或 \r 之后的位置匹配。 |
| $ | 匹配输入字符串 结尾 的位置。如果设置了 RegExp 对象的 Multiline 属性,$ 还会与 \n 或 \r 之前的位置匹配。 |
| * | 零次或多次 匹配前面的字符或子表达式。例如,zo* 匹配 z 和zoo。* 等效于 {0,}。 |
| + | 一次或多次 匹配前面的字符或子表达式。例如,zo+ 与 zo 和 zoo 匹配,但与 z 不匹配。+ 等效于 {1,}。 |
| ? | 零次或一次 匹配前面的字符或子表达式。例如,do(es)? 匹配 do 或 does 中的 do。? 等效于 {0,1}。 |
| {n} | n 是非负整数,正好匹配 n 次。例如,o{2} 与 Bob 中的 o 不匹配,但与 food 中的两个 o 匹配。 |
| {n,} | n 是非负整数,至少匹配 n 次。例如,o{2,} 不匹配 Bob 中的 o,而匹配 foooood 中的所有 o。o{1,} 等效于 o+。o{0,} 等效于 o*。 |
| {n,m} | m 和 n 是非负整数,其中 n <= m。匹配 至少 n 次,至多 m 次。例如,o{1,3} 匹配 fooooood 中的头三个 o。‘o{0,1}’ 等效于 o?。注意:您不能将空格插入逗号和数字之间。 |
| ? | 当此字符紧随任何其他限定符(*、+、?、{n}、{n,}、{n,m})之后时,匹配模式是 非贪心的。非贪心的 模式匹配搜索到的、尽可能短的字符串,而默认的 贪心的 模式匹配搜索到的、尽可能长的字符串。例如,在字符串 oooo 中,o+? 只匹配单个 o,而 o+ 匹配所有 o。 |
| . | 匹配除 \r\n 之外的 任何单个字符。若要匹配包括 \r\n 在内的任意字符,请使用诸如 [\s\S] 之类的模式。 |
| (pattern) | 匹配 pattern 并捕获该匹配的子表达式。可以使用 $0…$9 属性从结果匹配集合中检索捕获的匹配。若要匹配括号字符 ( ),请使用 \( 或者 \)。 |
| (?:pattern) | 匹配 pattern 但不捕获该匹配的子表达式,即它是一个非捕获匹配,不存储供以后使用的匹配。这对于用 or 字符 (\|) 组合模式部件的情况很有用。例如,industr(?:y|ies) 是比 industry|industries 更经济的表达式。 |
| (?=pattern) | 执行正向预测先行搜索的子表达式,该表达式匹配处于匹配 pattern 的字符串的起始点的字符串。它是一个非捕获匹配,即不能捕获供以后使用的匹配。例如,Windows (?=95\|98\|NT\|2000) 匹配 Windows 2000 中的 Windows,但不匹配 Windows 3.1 中的 Windows。预测先行不占用字符,即发生匹配后,下一匹配的搜索紧随上一匹配之后,而不是在组成预测先行的字符后。 |
| (?!pattern) | 执行反向预测先行搜索的子表达式,该表达式匹配不处于匹配 pattern 的字符串的起始点的搜索字符串。它是一个非捕获匹配,即不能捕获供以后使用的匹配。例如,Windows (?!95|98|NT|2000) 匹配 Windows 3.1 中的 Windows,但不匹配 Windows 2000 中的 Windows。预测先行不占用字符,即发生匹配后,下一匹配的搜索紧随上一匹配之后,而不是在组成预测先行的字符后。 |
| x|y | 匹配 x或y。例如,z\|food 能匹配 z 或 food。(z\|f)ood 则匹配 zood 或 food。 |
| [xyz] | 字符集。匹配 包含的任一字符。例如,[abc]匹配plain中的a。 |
| [^xyz] | 反向字符集。匹配 未包含的任何字符。例如,[^abc]匹配plain中p,l,i,n。 |
| [a-z] | 字符范围。匹配 指定范围内的任何字符。例如,[a-z]匹配a到z范围内的任何小写字母。 |
| [^a-z] | 反向范围字符。匹配 不在指定的范围内的任何字符。例如,[^a-z]匹配任何不在 a 到 z 范围内的任何字符。 |
| \b | 匹配 一个字边界,即字与空格间的位置。例如,er\b 匹配 never 中的 er,但不匹配 verb 中的 er。 |
| \B | 匹配 非字边界。er\B 匹配 verb 中的 er,但不匹配 never中的er。 |
| \cx | 匹配 x 指示的 控制字符。例如,\cM 匹配 Control-M 或回车符。x 的值必须在 A-Z 或 a-z 之间。如果不是这样,则假定 c 就是 c 字符本身。 |
| \d | 匹配 数字字符。等效于 [0-9]。 |
| \D | 匹配 非数字字符。等效于 [^0-9]。 |
| \f | 匹配 换页符。等效于 \x0c 和 \cL。 |
| \n | 匹配 换行符。等效于 \x0a 和 \cJ。 |
| \r | 匹配 一个回车符。等效于 \x0d 和 \cM。 |
| \s | 匹配 任何空白字符,包括空格、制表符、换页符等。与 [ \f\n\r\t\v] 等效。 |
| \S | 匹配 任何非空白字符。与 [^ \f\n\r\t\v] 等效。 |
| \t | 匹配 制表符。与 \x09 和 \cI 等效。 |
| \v | 匹配 垂直制表符。与 \x0b 和 \cK 等效。 |
| \w | 匹配 任何字类字符,包括下划线。与 [A-Za-z0-9_] 等效。 |
| \W | 匹配 任何非单词字符。与 [^A-Za-z0-9_] 等效。 |
| \xn | 匹配 n,此处的 n 是一个十六进制转义码。十六进制转义码必须正好是两位数长。例如,\x41匹配A。\x041与\x04&1等效。允许在正则表达式中使用 ASCII 代码。 |
| \num | 匹配 num,此处的 num 是一个正整数。到捕获匹配的反向引用。例如,(.)\1匹配两个连续的相同字符。 |
| \n | 标识一个八进制转义码或反向引用。如果 \n 前面至少有 n 个捕获子表达式,那么 n 是反向引用。否则,如果 n 是八进制数 (0-7),那么 n 是八进制转义码。 |
| \nm | 标识一个八进制转义码或反向引用。如果 \nm 前面至少有 nm 个捕获子表达式,那么 nm 是反向引用。如果 \nm 前面至少有 n 个捕获,则 n 是反向引用,后面跟有字符 m。如果两种前面的情况都不存在,则 \nm 匹配八进制值 nm,其中 n 和 m 是八进制数字 0-7。 |
| \nml | 当n 是八进制数 0-3,m 和 l 是八进制数 0-7 时,匹配八进制转义码 nml。 |
| \un | 匹配n,其中 n 是以四位十六进制数表示的 Unicode 字符。例如,\u00A9 匹配版权符号 ©。 |
\\ 来表示 \。如:表示一位数字的正则表达式为 \\d。例1,剔除字符串 .A1/b2?C3,d4|E5 中的非数字字符:
|
|
字符串 regex 包含正则表达式,表示非数字字符。 使用 replaceAll() 方法将非数字字符的字符替换为 ""。
也可通过 java.util.regex 包下的 Pattern 和 Matcher 类可以利用正则表达式处理字符串。
|
|
Pattern 类通过静态方法 complie() 创建一个正则表达式,并使用 matcher() 方法生成一个 Matcher 对象。
例2,分割字符串,提取单词:
|
|
字符串 regex 包含正则表达式,表示匹配一次或多次非单词字符(如空格)。使用 split() 方法利用正则表达式作为标记进行分割。
例3,实现一个类似 Thymeleaf 的模板引擎:
现有以下一段字符串,其中 ${key} 表示变量,也是将要被替换的部分。
Hello, ${name}! You are learning ${lang}!
|
|
使用 Matcher 类的 find() 方法在字符串中查找匹配到正则表达式的子串。appendReplacement() 方法是将匹配的内容替换为第二个参数的内容,然后通过 StringBuilder 或 StringBuffer 连接字符串。map.get(matcher.group(1)) 表示获取 map 中键 matcher.group(1) 对应的值,即正则表达式中的 \\w+。由于匹配对象后面可能还有字符,比如字符串 s 中的 !,所以最后通过 appendTail() 方法追加。
替换后结果为 Hello, MicroLOONG! You are learning Java!。
更多内容在 String 类的 Java API 文档
2. StringBuffer 和 StringBuilder 类
StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,与 被 final 修饰的 String 不同,创建的对象可以修改。
在上文拼接字符串时已提到了这三种类,使用场景:
-
操作少量的数据: 适用
String -
单线程操作字符串缓冲区下操作大量数据: 适用
StringBuilder -
多线程操作字符串缓冲区下操作大量数据: 适用
StringBuffer
3. StringJoiner 类
在上文拼接字符串数组使用的是 String.join() 方法,另外还可以使用 StringJoiner 类的构造方法实现。
|
|
代码中,StringJoiner 类的 add() 方法依赖 StringBuilder 进行字符串拼接。
还可以通过 StringJoiner(delimiter, prefix, suffix) 加入前缀 prefix 和后缀 suffix。
4. StringTokenizer 类
在上文分割字符串使用的是 正则表达式,另外还可以使用 StringTokenizer 类的构造方法实现。
|
|
StringTokenizer(String str, String delim, boolean returnDelims) 构造方法的第二个参数可以指定分隔符,如果不指定则默认以空格("")、制表符(\t)、换行符(\n)、回车符(\r)作为分隔符。如果定义了第三个参数,可以指定是否返回分隔符。
StringTokenizer 类的 hasMoreTokens() 方法返回是否还有分隔符。如果还有分隔符,可以利用 nextToken() 方法返回从当前位置到下一个分隔符的字符串。