统计含指定重复字符的字符串行数及Node.js代码优化问询
优化海量字符串统计代码的方案
需求说明
现有一个每行由随机字母组成的文件,需要完成以下操作:
- 统计恰好包含任意字母出现2次的行数
- 统计恰好包含任意字母出现3次的行数
- 计算这两个行数的乘积
文件可能包含海量字符串,需要优化现有代码提升性能。
现有代码的性能问题
原代码存在几个明显的性能瓶颈:
- 保留了
console.log(chars)调试输出,海量数据下会产生巨大IO开销,严重拖慢处理速度 - 统计完字符计数后,两次通过
Object.entries().filter().map()判断条件,做了大量不必要的数组转换和全量遍历操作 - 使用对象存储字符计数,比数组索引访问的哈希开销更高
- 用
readFileSync一次性读取整个文件,海量场景下会直接占满内存导致崩溃
具体优化措施
- 移除调试日志:直接删掉
console.log(chars),生产环境绝对不能保留这类调试输出 - 提前标记状态,避免二次遍历:在统计字符出现次数的过程中,直接标记是否出现过2次或3次的字符,不用等全部统计完再遍历计数结果
- 用数组替代对象计数:因为字母是固定的26个小写字母(假设输入均为小写),用数组索引对应字母ASCII码,存取速度远快于对象
- 改用流式读取文件:对于海量文件,用
readline模块逐行读取处理,避免一次性加载整个文件到内存 - 用
some()替代filter+map:如果需要遍历计数结果,用some()方法,找到第一个符合条件的项就停止遍历,比生成完整数组高效得多
优化后的代码
情况1:文件体积适中(可一次性加载)
function solution(arrOfStrings) { let sumOfTwo = 0; let sumOfThree = 0; for (const str of arrOfStrings) { // 数组索引0对应a,1对应b...25对应z,初始化为0 const countArr = new Array(26).fill(0); let hasTwo = false; let hasThree = false; for (const char of str) { const idx = char.charCodeAt(0) - 97; // 'a'的ASCII码为97 countArr[idx]++; // 只要出现过一次2次/3次就标记为true if (countArr[idx] === 2) { hasTwo = true; } else if (countArr[idx] === 3) { hasThree = true; // 两个状态都满足时直接跳出循环,节省后续遍历时间 if (hasTwo) break; } } if (hasTwo) sumOfTwo++; if (hasThree) sumOfThree++; } return sumOfTwo * sumOfThree; } const fs = require("fs"); const text = fs.readFileSync("./someTextFile.txt", "utf-8"); const arr = text.split("\n").filter(line => line.trim()); // 过滤空行 console.log(solution(arr));
情况2:海量文件(流式处理)
const fs = require("fs"); const readline = require("readline"); let sumOfTwo = 0; let sumOfThree = 0; function processLine(line) { if (!line.trim()) return; const countArr = new Array(26).fill(0); let hasTwo = false; let hasThree = false; for (const char of line) { const idx = char.charCodeAt(0) - 97; countArr[idx]++; if (countArr[idx] === 2) { hasTwo = true; } else if (countArr[idx] === 3) { hasThree = true; if (hasTwo) break; } } if (hasTwo) sumOfTwo++; if (hasThree) sumOfThree++; } async function processFile() { const rl = readline.createInterface({ input: fs.createReadStream("./someTextFile.txt"), crlfDelay: Infinity // 兼容不同换行符 }); for await (const line of rl) { processLine(line); } console.log(sumOfTwo * sumOfThree); } processFile().catch(err => console.error(err));
额外说明
- 如果输入字符串包含大写字母或其他字符,只需调整数组计数逻辑(比如扩展数组长度,或者增加字符范围判断)
- 提前跳出字符遍历的逻辑:当
hasTwo和hasThree都为true时,后续字符的计数变化不会影响结果(我们只关心是否出现过2次或3次,不关心出现次数更多的情况),所以可以直接终止当前字符串的遍历,节省时间
内容的提问来源于stack exchange,提问作者Akira Frank
相关产品推荐
相关产品推荐

