1、String
1.1、简介
String:字符串
Java 最常用的引用类型之一。
- 底层实现:
private final char[]
。即不可变的字符数组,且没有任何相关修改方法。 - 不可变性:
- 字符串对象创建后无法改变。
- 任何对字符串的修改,底层都会创建新的字符串对象并返回引用。
1.1.1、创建方式
字面量 | new | |
---|---|---|
含义 | 双引号 "" 包围 |
new String(参数) |
存储位置 | 常量池 | 堆 |
创建过程 | 先检查常量池中是否存在,是则返回引用,否则创建并放在常量池中 | 每次创建一个全新的字符串对象 |
说明 | 允许 JVM 优化内存分配,节省空间 | 参数可以是字符串、字符数组、字节数组 |
1.1.2、特殊值(空)
特殊的字符串值
-
空值(
null
):任何引用对象都可指向 null,表示不存在。 -
空串(
""
):值为空的有效字符串对象。 -
空格串(
" "
):值为若干个空格。String str1 = "ABC"; String str2 = null; String str3 = "";
1.1.3、转义字符
转义字符:
\
将字符串中的特殊符号进行转义,避免程序识别错误。
-
常用转义字符:
特殊符号 对应转义字符 "
\"
'
\'
\
\\
换行 \n
回车 \r
tab \t
Unicode 字符 \u####
-
示例:
// 三个字符:A 换行符 好 String str = "A\n\u597D";
1.1.4、多行字符串
两种书写形式
-
拼接:使用
+
拼接多个字符串。 -
三引号:(Java 13)使用一对
"""
包围字符串内容。String str1 = "SELECT * " + "FROM tb_user" + "WHERE id = 1;"; String str2 = """ SELECT * FROM tb_user WHERE id = 1; """;
1.2、基础操作
1.2.1、比较
字符串比较
- 比较内容:使用
equals()
(而不是==
)。 - 忽略大小写:
euqalsIgnoreCase()
示例:对于内容相同的字符串,分析
==
的结果。
-
字面量:s2 和 s1 是对常量池中同一个字符串对象的引用。
String s1 = "hello"; String s2 = "hello"; System.out.println(s1.equals(s2)); // true System.out.println(s1 == s2); // true
-
字面量 & new:s1 和 s2 分别在常量池和堆中,是不同的对象。
String s1 = "hello"; String s2 = new String("hello"); System.out.println(s1.equals(s2)); // true System.out.println(s1 == s2); // false
-
new:s1 和 s2 均创建了新的字符串对象,是不同的对象。
String s1 = new String("hello"); String s2 = new String("hello"); System.out.println(s1.equals(s2)); // true System.out.println(s1 == s2); // false
1.2.2、判空
-
isEmpty():(Java 6)判断字符串长度是否为 0,即空串(
""
)。"".isEmpty(); // true " ".isEmpty(); // false "\n".isEmpty(); // false
-
isBlank():(Java 11)判断字符串是否空白。
-
空串(
""
)、空格串(" "
)、缩进符\t
,回车符\r
,换行符\n
。 -
可理解为是否包含有意义字符。
"".isBlank(); // true " ".isEmpty(); // true "\n".isBlank(); // true
-
1.2.3、分割
分割字符串
-
方法签名:传入正则表达式,返回分割后的字符串数组。
public String[] split(String regex) {}
-
示例:
String s = "A,B,C"; String[] result = s.split(","); // ["A", "B", "C"]
1.3、类型转换
1.3.1、valueOf → 字符串
valueOf():可以将引用类型、基本类型、字符数组转换为 String。
引用类型
本质:
对象.toString()
。
public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
基本类型
-
数值:本质是
包装类型.toString(xxx)
。public static String valueOf(int i) {} public static String valueOf(long l) {} public static String valueOf(float f) {} public static String valueOf(double d) {}
-
布尔:
public static String valueOf(boolean b) { return b ? "true" : "false"; }
-
字符:本质是
new String()
的重载方法。public static String valueOf(char c) { char data[] = {c}; return new String(data, true); }
字符数组
本质:
new String()
的重载方法。
// 整个数组
public static String valueOf(char data[]) {
return new String(data);
}
// 指定起始索引和字符个数
public static String valueOf(char data[], int offset, int count) {
return new String(data, offset, count);
}
1.3.2、parseXxx() → 其它
除了
Character
类,各包装类中定义了parseXxx()
方法。
-
整数型:Byte、Short、Integer、Long 类中定义,可指定进制。
// 普通转换:四种类型 public static 基本类型 parseXxx(String s) throws NumberFormatException {} public static 基本类型 parseXxx(String s, int radix) throws NumberFormatException {} // 无符号转换:Integer、Long public static 基本类型 parseUnsignedXxx(String s) throws NumberFormatException {} public static 基本类型 parseUnsignedXxx(String s, int radix) throws NumberFormatException {}
-
浮点型:Float 和 Double 类中定义。
public static 基本类型 parseXxx(String s) throws NumberFormatException {}
-
布尔型:Boolean 类中定义。
public static boolean parseBoolean(String s) {}
1.3.3、字符数组
char[] → String
-
方法签名:String 的构造方法。
- 对 char[] 的修改不会影响 String。
- 原因:new String() 创建字符串实例时,不会直接引用传入的 char[],而是复制一份。
- 传入的 char[] 和 String 使用的 char[] 是两个不同的数组。
public String(char value[]) {} public String(char value[], int offset, int count) {}
-
示例:
char[] chs = {'a', 'b', 'c', 'd'}; String s1 = new String(chs); // abcd String s2 = new String(chs, 0, 2); // ab
String → char[]
-
方法签名:String 定义的实例方法。
public char[] toCharArray() {}
-
示例:
String str = "Hello"; str.toCharArray(); // ['H', 'e', 'l', 'l', 'o']
1.3、子串操作
Hint:索引从 0 开始
1.3.1、搜索子串
判断包含
-
方法签名:参数为
CharSequence
接口,String 是其实现类。public boolean contains(CharSequence s) {}
-
示例:
"Hello".contains("lo"); // true "Hello".contains("ol"); // false
索引
-
方法签名:
// 首次出现 public int indexOf(String str) {} // 最后一次出现 public int lastIndexOf(String str) {}
-
示例:
"Hello".indexOf("l"); // 2 "Hello".lastIndexOf("l"); // 3
判断前后缀
-
方法签名:
// 是否以prefix开头 public boolean startsWith(String prefix) {} // 是否以suffix结尾 public boolean endsWith(String suffix) {}
-
示例:
"Hello".startsWith("He"); // true "Hello".endsWith("lo"); // true
1.3.2、提取子串
提取子串
-
方法签名:
// 指定索引之后 public String substring(int beginIndex) {} // 指定索引范围(前闭后开) public String substring(int beginIndex, int endIndex){}
-
示例:
"Hello".substring(2); // "llo" "Hello".substring(2, 4); // "ll"
移除首尾空白字符
-
trim()
:包含英文空格\t
,回车符\r
,换行符\n
。" \tHello\r\n ".trim(); // "Hello"
-
strip()
:(Java 11+)在trim()
的基础上,移除中文空格字符\u3000
。"\u3000Hello\u3000".strip(); // "Hello" " Hello ".stripLeading(); // "Hello " " Hello ".stripTrailing(); // " Hello"
1.3.3、替换子串
根据字符或字符串
-
方法签名:
// 使用 newChar 替换掉所有的 oldChar public String replace(char oldChar, char newChar) {} // 使用 replacement 替换掉所有的 target public String replace(CharSequence target, CharSequence replacement) {}
-
示例:
String s = "hello"; s.replace('l', 'x'); // hexxo s.replace("l", "xx"); // hexxxxo
根据正则表达式
-
方法签名:
// 使用 replacement 替换掉首次匹配的 regex public String replaceFirst(String regex, String replacement) {} // 使用 replacement 替换掉所有匹配的 regex public String replaceAll(String regex, String replacement) {}
-
示例:
String s2 = "A,,B;C ,D"; s2.replaceFirst("[,;\\s]+", ","); // "A,B;C ,D" s2.replaceAll("[,;\\s]+", ","); // "A,B,C,D"
编码
-
ASCII 编码:
- 美国国家标准学会(American National Standard Institute:ANSI)制定。
- 英文字母、数字和常用符号的编码。
- 占用一个字节,编码范围从
0
到127
,最高位始终为0
- 示例:字符
'A'
的编码是0x41
,字符'1'
的编码是0x31
。 - 说明:
- 一个字节不足以纳入汉字等其它语言的编码。
-
GB2312 标准:
- 使用 2 个字节表示一个汉字。
- 第一个字节的最高位始终为
1
,以便和ASCII
编码区分开。 - 示例:汉字
'中'
的GB2312
编码是0xd6d0
。 - 说明:不同国家编码的标准不统一,同时使用则会产生冲突。
-
Unicode 编码:
-
为了统一全球所有语言的编码,全球统一码联盟发布
-
把世界上主要语言纳入同一个编码。
-
需要 2 个或者更多字节表示.
-
示例:对比中英文字符在
ASCII
、GB2312
和Unicode
的编码-
英文字符:英文字符的
Unicode
编码就是简单地在前面添加一个00
字节。┌────┐ ASCII: │ 41 │ └────┘ ┌────┬────┐ Unicode: │ 00 │ 41 │ └────┴────┘
-
中文字符:
'中'
的GB2312
编码和Unicode
编码:┌────┬────┐ GB2312: │ d6 │ d0 │ └────┴────┘ ┌────┬────┐ Unicode: │ 4e │ 2d │ └────┴────┘
-
-
-
UTF-8 编码:
- 英文字符的
Unicode
编码高字节总是00
,包含大量英文的文本会浪费空间。 - 节省空间:
UTF-8
编码是一种变长编码,把固定长度的Unicode
编码变成1~4字节的变长编码。- 英文字符
'A'
的UTF-8
编码变为0x41
,正好和ASCII
码一致 - 中文
'中'
的UTF-8
编码为3字节0xe4b8ad
。
- 英文字符
- 容错能力强:若传输过程中某些字符出错,不会影响后续字符。因为
UTF-8
编码依靠高字节位来确定一个字符究竟是几个字节,它经常用来作为传输编码。
- 英文字符的
Java 的 char 在内存中以 Unicode 编码表示,一个 char 占用两个字节的 Unicode 编码。
2、拼接字符串
+
+
:使用 +
连接多个任意类型的变量。
- 说明:连接之前,其它数据类型会先自动转型为
String
。 - 底层:StringBuilder.append()
String.join()
join()
:使用指定分隔符,连接多个字符串数组元素。
-
方法签名:
public static String join(CharSequence delimiter, CharSequence... elements) {}
-
示例:
String[] arr = {"A", "B", "C"}; String s = String.join("***", arr); // "A***B***C"
StringBuilder
Java编译器对
String
做了特殊处理,可以直接用+
拼接字符串。
考察下面的循环代码:
String s = "";
for (int i = 0; i < 1000; i++) {
s = s + "," + i;
}
虽然可以直接拼接字符串,但是,在循环中,每次循环都会创建新的字符串对象,然后扔掉旧的字符串。这样,绝大部分字符串都是临时对象,不但浪费内存,还会影响GC效率。
为了能高效拼接字符串,Java标准库提供了StringBuilder
,它是一个可变对象,可以预分配缓冲区,这样,往StringBuilder
中新增字符时,不会创建新的临时对象:
StringBuilder sb = new StringBuilder(1024);
for (int i = 0; i < 1000; i++) {
sb.append(',');
sb.append(i);
}
String s = sb.toString();
StringBuilder
还可以进行链式操作:
// 链式操作
Run
如果我们查看StringBuilder
的源码,可以发现,进行链式操作的关键是,定义的append()
方法会返回this
,这样,就可以不断调用自身的其他方法。
仿照StringBuilder
,我们也可以设计支持链式操作的类。例如,一个可以不断增加的计数器:
// 链式操作
注意:对于普通的字符串+
操作,并不需要我们将其改写为StringBuilder
,因为Java编译器在编译时就自动把多个连续的+
操作编码为StringConcatFactory
的操作。在运行期,StringConcatFactory
会自动把字符串连接操作优化为数组复制或者StringBuilder
操作。
你可能还听说过StringBuffer
,这是Java早期的一个StringBuilder
的线程安全版本,它通过同步来保证多个线程操作StringBuffer
也是安全的,但是同步会带来执行速度的下降。
StringBuilder
和StringBuffer
接口完全相同,现在完全没有必要使用StringBuffer
。
StringJoiner
要高效拼接字符串,应该使用StringBuilder
。
很多时候,我们拼接的字符串像这样:
// Hello Bob, Alice, Grace!
Run
类似用分隔符拼接数组的需求很常见,所以Java标准库还提供了一个StringJoiner
来干这个事:
import java.util.StringJoiner;
Run
慢着!用StringJoiner
的结果少了前面的"Hello "
和结尾的"!"
!遇到这种情况,需要给StringJoiner
指定“开头”和“结尾”:
import java.util.StringJoiner;
Run
String.join()
String
还提供了一个静态方法join()
,这个方法在内部使用了StringJoiner
来拼接字符串,在不需要指定“开头”和“结尾”的时候,用String.join()
更方便:
String[] names = {"Bob", "Alice", "Grace"};
var s = String.join(", ", names);
对比
String | StringBuffer | StringBuilder | |
---|---|---|---|
可变 | ❌ | ✔ | ✔ |
线程安全 | ✔ | ✔(synchronized) | ❌ |
性能 | 低(不可变,拼接/更改时频繁创建新对象) | 中(同步锁) | 高 |
存储 | 字符串常量池 | 堆 | 堆 |