Java 字符串查找
一、方法 1:indexOf ()/lastIndexOf ()(基础位置查找)
indexOf() 是 Java 字符串最基础的查找方法,用于定位字符 / 子串首次出现的索引;lastIndexOf() 则定位最后一次出现的索引,两者是处理简单查找需求的首选。核心语法
// 查找字符首次出现的索引
int indexOf(int ch);
// 从指定位置开始查找字符
int indexOf(int ch, int fromIndex);
// 查找子串首次出现的索引
int indexOf(String str);
// 从指定位置开始查找子串
int indexOf(String str, int fromIndex);
// lastIndexOf() 语法与indexOf()一致,仅查找方向相反(从末尾往前)
完整代码示例
public class StringSearchDemo {
public static void main(String[] args) {
String original = "Java is a programming language, Java is easy to learn";
// 空值校验(实际开发必备)
if (original == null) {
System.out.println("字符串不能为空");
return;
}
// 1. 查找字符首次出现的索引(字符'a'的ASCII码是97,也可直接传'a')
int charFirstIndex = original.indexOf('a');
System.out.println("字符'a'首次出现的索引:" + charFirstIndex); // 输出:1
// 2. 查找子串首次出现的索引
int strFirstIndex = original.indexOf("Java");
System.out.println("子串'Java'首次出现的索引:" + strFirstIndex); // 输出:0
// 3. 从指定位置(索引10)开始查找子串
int strFromIndex = original.indexOf("Java", 10);
System.out.println("从索引10开始查找'Java'的索引:" + strFromIndex); // 输出:32
// 4. 查找子串最后一次出现的索引
int strLastIndex = original.lastIndexOf("Java");
System.out.println("子串'Java'最后一次出现的索引:" + strLastIndex); // 输出:32
// 5. 查找不存在的子串(返回-1,核心判断依据)
int notExistIndex = original.indexOf("Python");
System.out.println("不存在的子串返回值:" + notExistIndex); // 输出:-1
}
}
关键说明
- 返回值规则:找到则返回索引(从 0 开始),未找到返回
-1(这是判断是否存在的核心依据); - 性能特点:时间复杂度
O(n)(n 为字符串长度),底层基于字符数组遍历,效率极高; - 适用场景:简单的字符 / 子串位置查找(如判断关键词是否存在、提取指定位置前的文本)。
二、方法 2:contains ()(存在性判断)
若仅需判断子串是否存在,无需获取具体索引,优先使用
contains(),代码更简洁、语义更清晰。核心语法
boolean contains(CharSequence s);
完整代码示例
public class StringSearchDemo {
public static void main(String[] args) {
String original = "Hello Java, Hello World";
// 判断子串是否存在
boolean hasJava = original.contains("Java");
boolean hasPython = original.contains("Python");
System.out.println("是否包含'Java':" + hasJava); // 输出:true
System.out.println("是否包含'Python':" + hasPython); // 输出:false
}
}
关键说明
- 底层逻辑:
contains()本质是调用indexOf(s) != -1,性能与indexOf()一致; - 适用场景:仅需判断 “有 / 无” 的场景(如表单关键词过滤、文本内容校验);
- 注意点:参数是
CharSequence类型,可直接传入 String、StringBuilder 等,无需类型转换。
三、方法 3:matches ()(正则表达式匹配)
当查找需求涉及复杂规则(如手机号、邮箱、模糊匹配),需使用
matches()结合正则表达式,实现灵活的模式查找。核心语法
boolean matches(String regex);
完整代码示例
public class StringSearchDemo {
public static void main(String[] args) {
// 1. 匹配邮箱格式
String email = "test@example.com";
String emailRegex = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$";
boolean isEmail = email.matches(emailRegex);
System.out.println("是否为合法邮箱:" + isEmail); // 输出:true
// 2. 匹配手机号(11位,以1开头)
String phone = "13812345678";
String phoneRegex = "^1[3-9]\\d{9}$";
boolean isPhone = phone.matches(phoneRegex);
System.out.println("是否为合法手机号:" + isPhone); // 输出:true
// 3. 模糊匹配(包含任意数字)
String text = "Java8 is better than Java7";
String numRegex = ".*\\d+.*"; // .*表示任意字符,\\d+表示至少一个数字
boolean hasNum = text.matches(numRegex);
System.out.println("是否包含数字:" + hasNum); // 输出:true
}
}
关键说明
- 底层逻辑:基于正则表达式引擎匹配,功能强大但性能低于
indexOf()/contains(); - 适用场景:复杂规则匹配(如格式校验、模糊查找、特殊字符过滤);
- 注意点:
matches()会匹配整个字符串(而非子串),若需匹配子串,需在正则前后加.*。
四、方法 4:Pattern + Matcher(高效正则查找)
matches() 每次调用都会重新编译正则表达式,若需高频次匹配同一规则(如批量校验 10 万条手机号),需使用Pattern预编译正则,结合Matcher实现高效查找。完整代码示例
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class StringSearchDemo {
public static void main(String[] args) {
// 预编译正则表达式(仅编译一次,复用多次)
Pattern phonePattern = Pattern.compile("^1[3-9]\\d{9}$");
// 批量校验手机号
String[] phones = {"13812345678", "12345678901", "19987654321"};
for (String phone : phones) {
Matcher matcher = phonePattern.matcher(phone);
boolean isPhone = matcher.matches();
System.out.println(phone + " 是否为合法手机号:" + isPhone);
}
// 查找字符串中所有数字子串
String text = "Java8 发布于2014年,Java17 发布于2021年";
Pattern numPattern = Pattern.compile("\\d+"); // 匹配数字子串
Matcher numMatcher = numPattern.matcher(text);
System.out.println("\n提取的数字子串:");
while (numMatcher.find()) { // 遍历所有匹配的子串
String num = numMatcher.group(); // 获取匹配的内容
int start = numMatcher.start(); // 获取匹配的起始索引
int end = numMatcher.end(); // 获取匹配的结束索引
System.out.println("内容:" + num + ",起始索引:" + start + ",结束索引:" + end);
}
}
}
输出结果
13812345678 是否为合法手机号:true
12345678901 是否为合法手机号:false
19987654321 是否为合法手机号:true
提取的数字子串:
内容:8,起始索引:4,结束索引:5
内容:2014,起始索引:9,结束索引:13
内容:17,起始索引:18,结束索引:20
内容:2021,起始索引:24,结束索引:28
关键说明
- 核心优势:
Pattern预编译后可复用,避免重复编译正则的性能损耗,高频场景下效率提升 10 倍以上; - 核心方法:
matcher.find():查找下一个匹配的子串;matcher.group():获取当前匹配的内容;matcher.start()/end():获取匹配内容的起止索引;
- 适用场景:批量数据校验、提取字符串中所有符合规则的子串。
五、方法 5:String.indexOf () 循环查找(批量子串查找)
若需查找字符串中所有目标子串的位置(如统计关键词出现次数),可通过
indexOf()循环实现,相比正则更高效。完整代码示例
public class StringSearchDemo {
public static void main(String[] args) {
String original = "Java is Java, Java is everywhere";
String target = "Java";
int count = 0; // 统计出现次数
int index = 0; // 起始查找位置
// 循环查找所有目标子串
while ((index = original.indexOf(target, index)) != -1) {
count++;
System.out.println("找到'" + target + "',索引:" + index);
index += target.length(); // 移动查找位置,避免重复匹配
}
System.out.println("'" + target + "' 总共出现:" + count + " 次");
}
}
输出结果
找到'Java',索引:0
找到'Java',索引:8
找到'Java',索引:14
'Java' 总共出现:3 次
关键说明
- 核心逻辑:每次找到子串后,将查找起始位置移至当前索引 + 子串长度,避免重复匹配(如 “JavaJava” 中只匹配两次);
- 性能特点:纯字符数组遍历,效率远高于正则,适合长文本、高频次的批量查找;
- 适用场景:统计关键词出现次数、提取所有关键词的位置。
六、实战对比与避坑指南
1. 方法选型对比
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| indexOf()/lastIndexOf() | 高效、简单 | 仅支持简单查找 | 单个字符 / 子串位置查找 |
| contains() | 语义清晰、代码简洁 | 仅判断存在性 | 子串存在性校验 |
| matches() | 支持复杂正则 | 性能低、每次编译正则 | 单次简单正则匹配 |
| Pattern + Matcher | 高效、支持复杂规则 | 代码稍复杂 | 批量正则匹配、提取子串 |
| indexOf () 循环 | 高效、可控性强 | 需手动处理循环 | 批量子串查找、次数统计 |
2. 常见避坑点
- 空指针异常:查找前必须校验字符串是否为
null,否则调用indexOf()/contains()会抛出NullPointerException; - 正则转义:正则中的特殊字符(如
.、*、+)需转义(加\\),否则匹配结果错误; - 重复匹配:循环查找时需移动起始位置,避免同一子串被多次匹配;
- 全字符串匹配:
matches()匹配整个字符串,若需匹配子串,正则前后需加.*(如".*Java.*")
浙公网安备 33010602011771号