count_chars() 能直接用但仅适用于ascii字符频次统计,对中文、emoji等utf-8多字节字符会错误拆分为字节计数;安全做法是配合mbstring扩展,用mb_strlen()和mb_substr($str, $i, 1, 'utf-8')逐字符遍历统计。

count_chars() 能不能直接用?
能,但只适合统计 ASCII 字符频次,且返回格式反直觉。它默认返回一个 256 元素的数组,索引是 ASCII 码(0–255),值是该字符出现次数。中文、emoji、UTF-8 多字节字符会直接被拆成多个字节计数,结果完全不可信。
- 对
"你好"调用count_chars($str),实际统计的是 UTF-8 编码的 6 个字节(每个汉字 3 字节),不是 2 个字符 - 想查某个具体字符(比如
"a")出现几次?得写$arr[ord('a')],绕且易错 - 如果只需要一个字符的频次,用它反而比手动遍历还重
mb_substr() + 循环才是 UTF-8 安全的底线做法
PHP 原生没有「按字符而非字节」统计的单函数,必须依赖多字节扩展。核心逻辑:用 mb_strlen() 获取真实字符数,再用 mb_substr() 逐个切出字符比对。
- 确保已启用
mbstring扩展(php -m | grep mbstring可确认) - 必须显式指定编码,如
mb_substr($str, $i, 1, 'UTF-8'),漏掉第三个参数会退化为substr() - 别用
for ($i = 0; $i 然后 <code>mb_substr($str, $i, 1)—— 每次调用都重新算长度,性能差;先存$len = mb_strlen($str, 'UTF-8')
示例(统计 "a" 在字符串中出现次数):
$str = "a你好a世界";
$count = 0;
$len = mb_strlen($str, 'UTF-8');
for ($i = 0; $i < $len; $i++) {
if (mb_substr($str, $i, 1, 'UTF-8') === 'a') {
$count++;
}
}
正则 preg_match_all() 适合模式化匹配场景
当你要统计的不是固定字符,而是符合某规则的片段(比如所有数字、所有中文、所有邮箱里的 @ 符号),preg_match_all() 更直接,且天然支持 UTF-8(加 u 修饰符)。
立即学习“PHP免费学习笔记(深入)”;
- 统计中文字符数:
preg_match_all('/\p{Han}/u', $str, $matches),结果是count($matches[0]) - 统计所有标点:
preg_match_all('/\p{P}/u', $str, $matches) - 注意:空字符串或无匹配时,
$matches是空数组,count($matches[0])不会报错但返回 0 - 性能比循环略低,但代码更清晰,尤其规则复杂时
自定义函数封装要注意编码参数透传
很多人封装成 mb_str_count($str, $char) 后发现中文不生效,问题几乎都出在没把编码参数往下传。默认用 mb_internal_encoding() 不可靠,它可能被其他代码改过,或根本没设。
- 必须让调用者能指定编码,且默认值设为
'UTF-8',而不是依赖全局设置 - 对单字符统计,别忘了用
mb_strlen($char, $encoding) === 1校验输入是否真为单个 Unicode 字符,避免传入多字节字符串导致逻辑错乱 - 如果统计目标是子串(不止一个字符),要用
mb_strpos()循环查找,不能用strpos()
真正容易被忽略的点:PHP 的字符串函数对编码极其敏感,mb_* 系列函数不传编码参数 ≠ 默认 UTF-8,而是默认用 mb_internal_encoding() 当前值 —— 这个值在 CLI 和 Web SAPI 下可能不同,线上出问题往往就卡在这儿。











