最高效方式是遍历 char 数组手动计数:O(n) 时间、O(1) 空间;推荐 toCharArray() 遍历,避免装箱与中间对象;稀疏场景可用 indexOf 跳跃查找;Unicode 补充字符需用 codePoints()。

Java 中统计 String 中特定字符出现次数,最高效的方式是遍历字符串并手动计数,时间复杂度 O(n),空间复杂度 O(1)。避免创建中间对象(如正则匹配、split、stream 中的包装类),能显著提升性能,尤其在高频或大数据量场景下。
用 for 循环遍历 char 数组(推荐)
将字符串转为 toCharArray() 后遍历,比 charAt(i) 略快(避免边界检查开销),且语义清晰、无额外对象生成。
- 直接访问底层字符数组,JVM 优化充分
- 适合单字符统计,逻辑简单,不易出错
- 示例:
public static int countChar(String str, char target) {
if (str == null) return 0;
int count = 0;
for (char c : str.toCharArray()) {
if (c == target) count++;
}
return count;
}
用 indexOf 循环查找(适合少量匹配)
利用 String.indexOf(char, fromIndex) 跳跃式定位,避免逐个比较,在目标字符稀疏时有一定优势,且不产生新数组。
- 无需复制字符,内存更友好
- 对超长字符串且目标字符极少的情况,可能略快于全遍历
- 注意:从 0 开始查找,每次更新起始位置为
index + 1
public static int countCharByIndex(String str, char target) {
if (str == null) return 0;
int count = 0, index = 0;
while ((index = str.indexOf(target, index)) != -1) {
count++;
index++; // 移动一位,避免重复匹配同一位置
}
return count;
}
慎用 Stream 和正则(非高效场景)
虽然代码简洁,但 chars().filter(...).count() 或 replaceAll().length() 会触发装箱、流管道、正则编译等开销,性能明显下降。
立即学习“Java免费学习笔记(深入)”;
-
str.chars().filter(c -> c == target).count():每个 char 装箱为Integer,创建 Stream 对象 -
str.replaceAll("[^x]", "").length()(x 为目标字符):构造正则引擎、生成新字符串,时间和空间成本高 - 仅建议用于脚本、测试或可读性优先的低频调用
注意 Unicode 补充字符(如 emoji)
若字符串含代理对(surrogate pair,如 ?、??),普通 char 遍历会把一个字符拆成两个 char,导致统计错误。
- 需改用
codePoints()流或String.codePointCount()+String.offsetByCodePoints() - 示例(支持全 Unicode):
public static long countCodePoint(String str, int targetCP) {
if (str == null) return 0L;
return str.codePoints().filter(cp -> cp == targetCP).count();
}
调用时用 countCodePoint(str, "?".codePointAt(0)) 即可正确统计 emoji。


















