字符串处理
字符串介绍
Java中的字符串是不可变的,即字符串一旦创建后不能被修改,但可以通过字符串拼接和字符串转换等操作来创建新的字符串。
直接定义字符串是指使用双引号表示字符串中的内容,例如“Hello Java”、“Java 编程”等。具体方法是用字符串常量直接初始化一个 String 对象。
示例:
// 一行编写
String str1 = "Hello Java";// 多行编写
String str;
str = "Hello Java";使用 String 类的构造方法来创建字符串。
示例:
String str1 = new String("Hello Java");以下是 String 类的构造方法:
| 构造方法 | 是否推荐使用 | 说明 |
|---|---|---|
public String() | 不推荐 | 创建一个空字符串 ""(等价于字面量 "") |
public String(String original) | 推荐 | 复制一个已有的 String 对象(最常用的构造函数) |
public String(char[] value) | 不推荐(JDK 9+ 废弃) | 根据字符数组创建字符串 |
public String(char[] value, int offset, int count) | 不推荐(JDK 9+ 废弃) | 从字符数组指定位置开始,取 count 个字符 |
public String(int[] codePoints, int offset, int count) | 可用 | 根据 Unicode 码点数组创建字符串(支持增补字符) |
public String(byte[] bytes, int offset, int length, Charset charset) | 推荐 | 使用指定字符集解码字节数组(推荐方式) |
public String(byte[] bytes, Charset charset) | 推荐 | 同上,整段字节数组 |
public String(byte[] bytes, int offset, int length) | 不推荐(JDK 9+ 废弃) | 使用平台默认字符集(容易乱码) |
public String(byte[] bytes) | 不推荐(JDK 9+ 废弃) | 使用平台默认字符集 |
public String(byte[] bytes, int offset, int length, String charsetName) throws UnsupportedEncodingException | 不推荐(JDK 9+ 废弃) | 通过字符集名称指定编码 |
public String(byte[] bytes, String charsetName) throws UnsupportedEncodingException | 不推荐(JDK 9+ 废弃) | 同上 |
public String(StringBuffer buffer) | 不推荐 | 从 StringBuffer 创建(线程安全但慢) |
public String(StringBuilder builder) | 不推荐 | 从 StringBuilder 创建(非线程安全但快) |
目前官方推荐的构造函数(不会乱码、高效)示例:
// 1. 最常用:复制已有字符串
String s1 = new String("Hello");
// 2. 从字节数组安全解码(推荐)
byte[] bytes = "中文".getBytes(StandardCharsets.UTF_8);
String s2 = new String(bytes, StandardCharsets.UTF_8);
String s3 = new String(bytes, "UTF-8"); // 也可以,但不推荐用字符串名
// 3. 支持增补字符(如表情)
int[] codePoints = {0x1F600}; // 笑脸表情
String s4 = new String(codePoints, 0, 1);字符串和整型相互转换
Java 中 String(字符串) ↔ int / Integer(整型) 的相互转换是最常见的面试题和日常开发操作。下面把所有正确、高效、推荐的方式全部列出来,并说明哪些方式已过时或有坑。
字符串转整数
| 方式 | 代码示例 | 是否推荐 | 说明 |
|---|---|---|---|
1. Integer.parseInt(str) | int num = Integer.parseInt("123"); | 强烈推荐(最常用) | 返回基本类型 int,性能最好 |
2. Integer.valueOf(str) | Integer num = Integer.valueOf("123"); | 推荐 | 返回包装类 Integer,会利用整数缓存(-128~127) |
3. new Integer(str) | Integer num = new Integer("123"); | 不推荐,已废弃(JDK 9+) | 性能差,多创建一个对象 |
4. Integer.decode(str) | Integer num = Integer.decode("123"); | 特殊场景 | 支持 "0xFF"、"#FF"、"077" 等十六进制、八进制写法 |
推荐写法对比:
String s = "123456";
// 推荐1:想要 int 基本类型
int a = Integer.parseInt(s); // 最快、最常用
// 推荐2:想要 Integer 包装类(比如要放进集合)
Integer b = Integer.valueOf(s); // 推荐,会缓存
// 绝对不要写(虽然还能用)
Integer c = new Integer(s); // JDK9 已废弃注意事项:
- 如果字符串不是纯数字,会抛
NumberFormatException - 不能有空格、前导零没问题,但不能有 "+" 号(除非用 decode)
- 空字符串或 null 会直接抛异常
Integer.parseInt(" 123 "); // 报错!有空格不行
Integer.parseInt("123"); // OK
Integer.parseInt("+123"); // 报错!parseInt 不支持 + 号
Integer.decode("+123"); // OK,支持 +0x -0 等整数转字符串
| 方式 | 代码示例 | 是否推荐 | 说明 |
|---|---|---|---|
1. String.valueOf(x) | String s = String.valueOf(123); | 强烈推荐 | 官方最推荐,性能好,支持 null |
2. Integer.toString(x) | String s = Integer.toString(123); | 推荐 | 也很常用 |
3. x + "" | String s = 123 + ""; | 强烈不推荐 | 看起来简单,实际性能最差(产生临时 StringBuilder) |
4. String.format("%d", x) | String s = String.format("%d", 123); | 格式化时使用 | 性能一般,用于需要格式化时 |
性能对比(从快到慢):
String.valueOf(int) → 最快
Integer.toString(int) → 差不多快
String.format("%d", int) → 慢很多
int + "" → 最慢(隐藏的 StringBuilder + 装箱)推荐写法:
int num = 999;
// 推荐1(最通用)
String s1 = String.valueOf(num);
// 推荐2
String s2 = Integer.toString(num);
// 不要写(虽然很多老代码这样写)
String s3 = num + "";
String s4 = "" + num;完整推荐示例
public class StringIntConvert {
public static void main(String[] args) {
// ===== String → int/Integer =====
String str = "2025";
int i1 = Integer.parseInt(str); // 推荐
Integer i2 = Integer.valueOf(str); // 推荐
// ===== int/Integer → String =====
int year = 2025;
Integer yearObj = 2025;
String s1 = String.valueOf(year); // 推荐
String s2 = Integer.toString(year); // 推荐
String s3 = yearObj.toString(); // 包装类专用
System.out.println(s1); // 2025
}
}总结口诀
| 方向 | 记住这一句就够了 |
|---|---|
| String → int | Integer.parseInt(str) |
| String → Integer | Integer.valueOf(str) |
| int → String | String.valueOf(i) 或 Integer.toString(i) |
| Integer → String | String.valueOf(obj) 或 obj.toString() |
永远不要用:
new Integer(str)(已废弃)int + ""(性能差)Integer.parseInt(null)(会 NPE)
字符串拼接
String 字符串虽然是不可变字符串,但也可以进行拼接只是会产生一个新的对象。
与绝大多数的程序设计语言一样,Java 语言允许使用 + 号连接(拼接)两个字符串。+ 运算符是最简单、最快捷,也是使用最多的字符串连接方式。在使用 + 运算符连接字符串和 int 型(或 double 型)数据时,+ 将 int(或 double)型数据自动转换成 String 类型。
示例:
String s1 = "Hello";
String s2 = "Java";
String s3 = s1 + s2; // 这里会将s1、s2字符串拼接成一个新的字符串
System.out.println("s3 = " + s3); // 结果:HelloJava
// 字符串和int类型拼接
int i = 100;
String s4 = s1 + i;
System.out.println("s4 = " + s4); // 结果:Hello100
// 字符串拼接浮点数
float f = 3.14F;
double d = 9.99;
String s5 = s1 + f;
System.out.println("s5 = " + s5); // 结果:Hello3.14
String s6 = s1 + d;
System.out.println("s6 = " + s6); // 结果:Hello9.99
// 字符串拼接布尔类型
boolean b1 = false;
boolean b2 = true;
String s7 = s1 + b1;
System.out.println("s7 = " + s7); // 结果:Hellofalse
String s8 = s1 + b2;
System.out.println("s8 = " + s8); // 结果:Hellotrue使用 String 类的 concat() 方法实现了将一个字符串连接到另一个字符串的后面。
concat() 方法语法格式如下:
字符串1.concat(字符串2);示例:
String s1 = "Java";
String s2 = "编程";
String s3 = s1.concat(s2); // 这里会将s1、s2字符串拼接成一个新的字符串
System.out.println("s3 = " + s3); // 结果:Java编程JDK 1.8 提供了一种新的字符串拼接姿势:String 类增加了一个静态方法 join()。
join() 方法语法格式如下:
String.join(分隔符, 字符串1, 字符串2);示例:
String s1 = "Java";
String s2 = "编程";
String s3 = String.join("-", s1, s2); // 这里会将s1、s2字符串拼接成一个新的字符串
System.out.println("s3 = " + s3); // 结果:Java-编程获取字符串的长度
要获取字符串的长度,可以使用 String 类的 length() 方法,其语法形式如下:
字符串.length();示例:
String s1 = "Hello World!";
int len = s1.length();
System.out.println("len = " + len); // 结果:12字符串大小写转换
String 类的 toUpperCase() 方法可以将字符串转换成大写,而非字母的字符不受影响。语法形式如下:
字符串.toUpperCase();String 类的 toLowerCase() 方法可以将字符串转换成小写,而非字母的字符不受影响。语法形式如下:
字符串.toLowerCase();示例:
// 定义小写字母字符
String s1 = "abc";
// 字符字母转成大写字符字母
String res1 = s1.toUpperCase();
System.out.println(res1); // 输出结果:ABC
// 定义大写写字母字符
String s2 = "DEF";
// 字符字母转成小写字符字母
String res2 = s2.toLowerCase();
System.out.println(res2); // 输出结果:def
// 非字母的字符
String s3 = "1234";
String res3 = s3.toUpperCase();
String res4 = s3.toLowerCase();
System.out.println(res3); // 输出结果:1234
System.out.println(res4); // 输出结果:1234
// 非字母的字符
String s4 = "你好,世界!";
String res5 = s4.toUpperCase();
String res6 = s4.toLowerCase();
System.out.println(res5); // 输出结果:你好,世界!
System.out.println(res6); // 输出结果:你好,世界!去除字符串中的空格
字符串中存在的首尾空格一般情况下都没有任何意义,如字符串“ Hello ”,但是这些空格会影响到字符串的操作,如连接字符串或比较字符串等,所以应该去掉字符串中的首尾空格,这需要使用 String 类提供的 trim() 方法。
trim() 方法语法格式如下:
字符串.trim();示例:
// trim方法去除字符串(去除字符串开头和末尾的空格)
String s1 = " Hello World! ";
System.out.println("字符串内容:" + s1); // 输出结果: Hello World!
System.out.println("字符串长度:" + s1.length()); // 输出结果:14
String res1 = s1.trim(); // 去除字符串前后的空格
System.out.println("字符串内容:" + res1); // 输出结果:Hello World!
System.out.println("字符串长度:" + res1.length()); // 输出结果:12提示
trim() 只能去掉字符串中前后的半角空格(英文空格),而无法去掉全角空格(中文空格)。
提取子字符串
在 String 类中提供了两个截取字符串的方法,一个是从指定位置截取到字符串结尾,另一个是截取指定范围的内容。下面对这两种方法分别进行介绍。
substring(int beginIndex)方法用于截取从指定位置开始到字符串结尾的子字符串。语法形式如下:
字符串.substring(起始位置);示例:
// 提取从索引位置开始至结尾处的字符串部分。
String s1 = "Hello World!";
String res1 = s1.substring(6);
System.out.println(res1); // 输出结果:World!substring(int beginIndex, int endIndex)方法用于截取指定范围的子字符串。语法形式如下:
字符串.substring(起始位置, 结束位置);示例:
// 从起始索引开始,截取到结束索引的字符串部分【这里不包含结束索引的字符】。
String s1 = "Hello World!";
String res1 = s1.substring(6, 11);
System.out.println(res1); // 输出结果:World字符串分割
String 类的 split() 方法可以按指定的分割符对目标字符串进行分割,分割后的内容存放在字符串数组中。该方法主要有如下两种重载形式:
// 按指定分隔符分割字符串
// regex表示分隔符
// limit表示分割后生成的字符串的限制个数,如果不指定,则表示不限制,直到将整个目标字符串完全分割为止。
String[] split(String regex);
String[] split(String regex, int limit);示例:
String s = "boo:and:foo";
// 1. 不带 limit(等价于 limit=0)
String[] a1 = s.split(":"); // ["boo", "and", "foo"] 长度 3
// 2. limit = 0(显式写 0,和上面完全一样)
String[] a2 = s.split(":", 0); // ["boo", "and", "foo"] 长度 3
// 3. limit = 2
String[] a3 = s.split(":", 2); // ["boo", "and:foo"] 长度 2
// 4. limit = 5(比实际分隔符多)
String[] a4 = s.split(":", 5); // ["boo", "and", "and", "foo"]? 不是!因为只有2个:,最多分成3段
// 实际结果还是 ["boo", "and", "foo"]
// 5. 末尾空字符串的经典案例
String s2 = "a,b,c,";
s2.split(",") → ["a","b","c"] // 末尾空串被丢弃
s2.split(",", 0) → ["a","b","c"] // 同上
s2.split(",", 4) → ["a","b","c",""] // 强制保留末尾空串
s2.split(",", -1) → ["a","b","c",""] // 负数也保留所有空串记忆口诀:
- 没写 limit 或 limit=0 → “去掉末尾所有空字符串”
- limit > 0 → “最多分成 limit 份,最后一份包含剩余全部内容(包括分隔符)
- limit < 0 → “全力分割,所有空字符串都保留”
字符串替换
String 类提供了 3 种字符串替换方法,分别是 replace()、replaceFirst() 和 replaceAll()。
replace() 方法用于将目标字符串中的指定字符(串)替换成新的字符(串)。
语法格式如下:
字符串.replace(旧字符, 新字符);示例:
String text = "我爱Java,Java真香!JavaScript 也挺好,但我不爱JavaScript。";
System.out.println("原字符串:" + text);
System.out.println();
// 1. replace(CharSequence target, CharSequence replacement)
// 替换【所有】完全匹配的子串(不是正则,是普通字符串匹配)
String r1 = text.replace("Java", "Kotlin");
System.out.println("1. replace(\"Java\", \"Kotlin\") → " + r1);replaceFirst() 方法用于将目标字符串中匹配某正则表达式的第一个子字符串替换成新的字符串。
语法格式如下:
字符串.replaceFirst(正则表达式, 新字符串);示例:
String text = "我爱Java,Java真香!JavaScript 也挺好,但我不爱JavaScript。";
System.out.println("原字符串:" + text);
System.out.println();
// 3. replaceFirst(String regex, String replacement)
// 只替换【第一个】匹配正则表达式的部分
String r3 = text.replaceFirst("Java", "Rust");
System.out.println("3. replaceFirst(\"Java\", \"Rust\") → " + r3);replaceAll() 方法用于将目标字符串中匹配某正则表达式的所有子字符串替换成新的字符串。
语法格式如下:
字符串.replaceAll(正则表达式, 新字符串);示例)
String text = "我爱Java,Java真香!JavaScript 也挺好,但我不爱JavaScript。";
System.out.println("原字符串:" + text);
System.out.println();
// 2. replaceAll(String regex, String replacement)
// 替换【所有】匹配正则表达式的部分
String r2 = text.replaceAll("Java", "Go");
System.out.println("2. replaceAll(\"Java\", \"Go\") → " + r2);
// 和 replace 效果看起来一样,但它是正则!下面演示正则威力:
String r2b = text.replaceAll("Java(Script)?", "Python");
System.out.println(" replaceAll(\"Java(Script)?\", \"Python\") → " + r2b);字符串比较
比较字符串的常用方法有 3 个:equals() 方法、equalsIgnoreCase() 方法、 compareTo() 方法。
equals() 方法将逐个地比较两个字符串的每个字符是否相同。如果两个字符串具有相同的字符和长度,它返回 true,否则返回 false。对于字符的大小写,也在检查的范围之内。
语法格式:
字符串.equals(字符串);示例:
String s1 = "Hello";
String s2 = "hello";
System.out.println(s1.equals(s2)); // 输出结果:falseequalsIgnoreCase() 方法的作用和语法与 equals() 方法完全相同,唯一不同的是 equalsIgnoreCase() 比较时不区分大小写。当比较两个字符串时,它会认为 A-Z 和 a-z 是一样的。
语法格式:
字符串.equalsIgnoreCase(字符串);示例:
String s1 = "Hello";
String s2 = "hello";
System.out.println(s1.equalsIgnoreCase(s2)); // 输出结果:trueequals() 与 == 的比较,如下表格所示:
| 比较方式 | 比较的是什么 | 用于基本类型 | 用于引用类型 | 是否能被重写 |
|---|---|---|---|---|
== | 变量在内存中的值 | 比较值是否相等 | 比较两个引用是否指向同一个对象(地址) | 不能重写 |
equals() | Object 类的方法,默认行为和 == 一样 | 不能直接用 | 默认比较地址,但很多类(如 String、Integer 等)已重写为比较内容 | 可以重写 |
示例:
public class EqualsVsDoubleEqual {
public static void main(String[] args) {
// 1. 基本类型:只有 ==,没有 equals
int a = 10;
int b = 10;
System.out.println(a == b); // true → 值相等
// 2. String 字符串(最常考!)
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
String s4 = new String("hello");
System.out.println(s1 == s2); // true → 都指向字符串常量池同一个对象
System.out.println(s1 == s3); // false → s3 是堆中新对象
System.out.println(s3 == s4); // false → 两个不同的 new 对象
System.out.println(s1.equals(s2)); // true
System.out.println(s1.equals(s3)); // true → String 重写了 equals,比较内容
System.out.println(s3.equals(s4)); // true → 内容一样
// 3. 包装类 Integer(注意缓存!)
Integer x = 127;
Integer y = 127;
Integer m = 128;
Integer n = 128;
System.out.println(x == y); // true → -128~127 缓存
System.out.println(m == n); // false → 超过127不在缓存,new了新对象
System.out.println(x.equals(y)); // true
System.out.println(m.equals(n)); // true
// 4. 自定义类(不重写 equals 的情况)
class Person {
String name;
Person(String name) { this.name = name; }
}
Person p1 = new Person("张三");
Person p2 = new Person("张三");
System.out.println(p1 == p2); // false → 不同对象
System.out.println(p1.equals(p2)); // false → 没重写 equals,默认比地址
}
}compareTo() 方法用于按字典顺序比较两个字符串的大小,该比较是基于字符串各个字符的 Unicode 值。
语法格式:
字符串.compareTo(字符串);示例:
String s1 = "H";
String s2 = "h";
System.out.println(s1.compareTo(s2)); // 输出结果:-32
System.out.println(s2.compareTo(s1)); // 输出结果:32字符串查找
String 类的 indexOf() 方法和 lastlndexOf() 方法用于在字符串中获取匹配字符(串)的索引值。
语法格式:
字符串.indexOf(字符串);
字符串.indexOf(字符串, 起始位置);示例:
String str = "Hello Java, Java is fun, Java!";
System.out.println("原字符串:" + str);
System.out.println();
// 1. indexOf(String s) —— 从头开始找,找到第一个匹配的位置
System.out.println("indexOf(\"Java\") = " + str.indexOf("Java"));
// 输出:6 → 第一次出现 "Java" 的起始位置是第6个字符(H占0)
// 2. indexOf(String s, int fromIndex) —— 从指定位置(包含)开始往后找
System.out.println("indexOf(\"Java\", 10) = " + str.indexOf("Java", 10));
// 输出:13 → 从第10个位置开始找,下一个 "Java" 在第13位
System.out.println("indexOf(\"Java\", 14) " + str.indexOf("Java", 14));
// 输出:23 → 从第14位开始找,最后一个 "Java" 在第23位
System.out.println("indexOf(\"Java\", 30) " + str.indexOf("Java", 30));
// 输出:-1 → 从第30位开始已经找不到,返回 -1 表示没找到
System.out.println("indexOf(\"xyz\") = " + str.indexOf("xyz"));
// 输出:-1 → 完全不存在也返回 -1lastIndexOf() 方法用于返回字符(串)在指定字符串中最后一次出现的索引位置,如果能找到则返回索引值,否则返回 -1。
语法格式:
字符串.lastIndexOf(字符串);
字符串.lastIndexOf(字符串, 起始位置);示例:
String str = "Hello Java, Java is fun, Java!";
System.out.println("原字符串:" + str);
System.out.println();
// 1. lastIndexOf(String s) —— 从字符串末尾开始找,找到最后一个匹配的位置
System.out.println("lastIndexOf(\"Java\") = " + str.lastIndexOf("Java"));
// 输出:23 → 最后一个 "Java" 的起始位置是第23个字符
// 2. lastIndexOf(String s, int fromIndex) —— 从指定位置(包含)开始往前找
System.out.println("lastIndexOf(\"Java\", 20) = " + str.lastIndexOf("Java", 20));
// 输出:13 → 从第20位开始往前找,找到的是第13位的 "Java"
System.out.println("lastIndexOf(\"Java\", 12) = " + str.lastIndexOf("Java", 12));
// 输出:6 → 从第12位开始往前找,找到的是第6位的 "Java"
System.out.println("lastIndexOf(\"Java\", 5) = " + str.lastIndexOf("Java", 5));
// 输出:-1 → 从第5位开始往前已经找不到,返回 -1
System.out.println("lastIndexOf(\"xyz\") = " + str.lastIndexOf("xyz"));
// 输出:-1 → 不存在也返回 -1String 类的 charAt() 方法可以在字符串内根据指定的索引查找字符。
语法格式:
字符串.charAt(索引);示例:
String str = "我爱Java";
// 位置: 01234
System.out.println(str.charAt(0)); // 输出: 我
System.out.println(str.charAt(1)); // 输出: 爱
System.out.println(str.charAt(2)); // 输出: J
System.out.println(str.charAt(3)); // 输出: a
System.out.println(str.charAt(4)); // 输出: v
// 常用:遍历每个字符
for (int i = 0; i < str.length(); i++) {
System.out.print(str.charAt(i) + " ");
}
// 输出: 我 爱 J a v aStringBuilder和StringBuffer
StringBuilder 和 StringBuffer 是 Java 中专门用来“高效拼接、修改字符串”的两个类。
| 特性 | String | StringBuffer | StringBuilder |
|---|---|---|---|
| 是否可变 | 不可变 | 可变 | 可变 |
| 线程安全 | 是 | 是 | 不是 |
| 性能 | 最慢(每次 + 都产生新对象) | 比 Builder 慢 10%~30% | 最快 |
示例:
public class StringDemo {
public static void main(String[] args) {
// 1. 用 + 号拼接(最慢!)
String s = "";
for (int i = 0; i < 10000; i++) {
s += "A"; // 每次都创建新 String 对象!
}
// 2. 用 StringBuilder(推荐!)
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
sb.append("A"); // 只在一个对象里改
}
String result1 = sb.toString();
// 3. 用 StringBuffer(线程安全,但慢一点)
StringBuffer sbf = new StringBuffer();
for (int i = 0; i < 10000; i++) {
sbf.append("A");
}
String result2 = sbf.toString();
}
}