实习的收获(2)--关于函数&正则 性能优化

80 阅读3分钟

🔍 文件类型检测的性能优化之旅:正则表达式 vs 函数方法

在前一段时间的前端实习中,我遇到了一个有趣的性能优化问题:如何高效检测压缩包中的特定文件类型和路径?相信看完整篇文章你也会对代码性能优化有更加深刻的理解!

💼 需求背景

​场景​​:需要分析一个压缩文件,判断其中是否包含特定类型的文件(如 .xnb格式)以及是否存在于指定的内容文件夹中。

🚦 初始方案:正则表达式法

不知道大家听说没听说过"文献雕龙",感觉写代码也是如此,实习期间,小明最初写了一段自认为很"优雅"的代码高高兴兴的拿给同事 MaXiao 看:

let i18n = false;
let content = false;

mod.modFiles.forEach(item => {
  if(FileHandler.compareFileName(basename(item), 'SMAPI.Installer.dll')) smapi = true;
  if(basename(item) == 'manifest.json') plugins = true;
  if(basename(item) == 'config.json') plugins2 = true;
  if(dirname(item).replace(/^[\/]/g, '') === 'Portraits') portraits = true;
  if(extname(item) == '.json' && item.toLowerCase().includes('i18n')) i18n = true;
  
  // 复杂的正则表达式匹配
  const contentFolders = /^(?=.*.xnb$)(?=.*(data|dialogue|animals|buildings|characters|effects|fonts|loosesprites|maps|minigames|portraits|strings|terrainfeatures|tilesheets|volcanolayouts|xact))/i;
  if(contentFolders.test(item.toLowerCase())) content = true;
});

if(smapi) return 1;
if(plugins) return 2;
if(portraits) return 3;
if(plugins2) return 21;

👨💻 同事的建议

同事 MaXiao 看到代码后,表示不大行,并提出了改进建议(此时小明内心os🤡🤡🤡),并分享了他的写法给小明参考,如下:

const toModsFlags = ['METADATA', 'GLOBALS', 'MODELS', 'MATERIALS', 'TEXTURES'].map(it => path.sep + it);

mod.modFiles.forEach(item => {
    if (extname(item) === '.pak' || extname(item) == '.lua') pak = true;
    toModsFlags.find(flag => item.toUpperCase().includes(flag)) && (toMods = true);
});

if (pak) return 1;

🤔 小明的疑问与探索

小明产生了疑问:是正则表达式的可读性不好?,同事 MaXiao 表示是性能原因,"听人劝吃饱饭",小明改写了部分代码

重构为函数方法

function isContentFolder(item) {
    // 检查是否以.xnb结尾(不区分大小写)
    const isXnb = item.toLowerCase().endsWith('.xnb');
    if (!isXnb) return false;

    // 检查是否包含任一指定文件夹(不区分大小写)
    const folders = ['data', 'dialogue', 'animals', 'buildings', 'characters', 
                    'effects', 'fonts', 'loosesprites', 'maps', 'minigames', 
                    'portraits', 'strings', 'terrainfeatures', 'tilesheets', 
                    'volcanolayouts', 'xact'];
    
    const lowerItem = item.toLowerCase();
    return folders.some(folder => lowerItem.includes(folder));
}

设计性能测试

小明半信半疑,决定用科学的方法来验证。 为了客观比较两种方法的性能,小明借助ai快速的编写了完整的测试文件:


function generateRandomPath() {
  const prefixes = ['content/', 'data/', 'assets/', ''];
  const categories = [
    'data', 'dialogue', 'animals', 'buildings', 'characters',
    'effects', 'fonts', 'loosesprites', 'maps', 'minigames',
    'portraits', 'strings', 'terrainfeatures', 'tilesheets',
    'volcanolayouts', 'xact'
  ];
  const suffixes = ['.xnb', '.png', '.json', '.txt'];

  const prefix = prefixes[Math.floor(Math.random() * prefixes.length)];
  const category = categories[Math.floor(Math.random() * categories.length)];
  const suffix = suffixes[Math.floor(Math.random() * suffixes.length)];

  return `${prefix}${category}/file${suffix}`;
}

// 正则表达式方法
const combinedPattern = /^(?=.*\.xnb$)(?=.*content)(?=.*(data|dialogue|animals|buildings|characters|effects|fonts|loosesprites|maps|minigames|portraits|strings|terrainfeatures|tilesheets|volcanolayouts|xact))/i;

function testWithRegex(item) {
  return combinedPattern.test(item.toLowerCase());
}

// 函数方法
function testWithFunction(item) {
  const lowerItem = item.toLowerCase();
  return (
    lowerItem.endsWith('.xnb') &&
    lowerItem.includes('content') &&
    (
      lowerItem.includes('data') ||
      lowerItem.includes('dialogue') ||
      lowerItem.includes('animals') ||
      lowerItem.includes('buildings') ||
      lowerItem.includes('characters') ||
      lowerItem.includes('effects') ||
      lowerItem.includes('fonts') ||
      lowerItem.includes('loosesprites') ||
      lowerItem.includes('maps') ||
      lowerItem.includes('minigames') ||
      lowerItem.includes('portraits') ||
      lowerItem.includes('strings') ||
      lowerItem.includes('terrainfeatures') ||
      lowerItem.includes('tilesheets') ||
      lowerItem.includes('volcanolayouts') ||
      lowerItem.includes('xact')
    )
  );
}

// 生成测试数据(1000条随机路径)
const testData = Array.from({ length: 1000 }, generateRandomPath);

// 性能测试函数
function runPerformanceTest(method, name) {
  console.time(name);
  for (const item of testData) {
    method(item);
  }
  console.timeEnd(name);
}

// 运行测试
console.log('=== 性能测试===');
runPerformanceTest(testWithRegex, '正则表达式');
runPerformanceTest(testWithFunction, '函数方法');

// 验证结果一致性
console.log('\n=== 结果一致性验证 ===');
let regexPass = 0, funcPass = 0;
for (const item of testData) {
  const regexResult = testWithRegex(item);
  const funcResult = testWithFunction(item);
  if (regexResult === funcResult) {
    if (regexResult) regexPass++;
    if (funcResult) funcPass++;
  } else {
    console.warn(`结果不一致: ${item} -> 正则=${regexResult}, 函数=${funcResult}`);
  }
}
console.log(`正则匹配成功数: ${regexPass}, 函数匹配成功数: ${funcPass}`);

📊 测试结果与分析

运行测试后的结果令人惊讶:

=== 性能测试===
正则表达式: 0.52ms
函数方法: 0.204ms

=== 结果一致性验证 ===
正则匹配成功数: 77, 函数匹配成功数: 77

🧠 深入理解性能差异

通过这次实践以及小明后续也查看相关的博客和文档,小明学到了很多,并作出以下总结:

正则表达式的性能特点

✅ ​​优点​​:

  • ​一次性匹配​​:单行代码完成复杂匹配
  • ​引擎优化​​:现代正则引擎对常见模式有深度优化
  • ​代码简洁​​:适合规则固定的场景

❌ ​​缺点​​:

  • ​回溯问题​​:复杂正则可能导致性能急剧下降
  • ​多断言开销​​:多个正向先行断言需要重复扫描字符串
  • ​调试困难​​:性能问题不易定位

函数方法的性能特点

✅ ​​优点​​:

  • ​灵活性高​​:可分步检查条件,避免重复扫描
  • ​可优化性强​​:支持提前终止、缓存等优化策略
  • ​易于维护​​:逻辑清晰,便于调试和扩展

❌ ​​缺点​​:

  • ​代码量多​​:需要手动实现每个判断条件
  • ​实现要求高​​:需要合理设计才能发挥性能优势

🎯 实习经验总结

1. ​​不要盲目追求代码简洁​

最初的单行正则虽然简洁,但牺牲了可读性和性能。

2. ​​性能优化需要数据支撑​

通过科学的测试方法,用数据说话,避免主观臆断。

3. ​​考虑可维护性​

函数方法虽然代码量稍多,但更易于后续维护和团队协作。

4. ​​场景决定方案选择​

  • 简单匹配:推荐使用正则表达式
  • 复杂条件:建议使用函数方法
  • 高性能要求:必须进行实际测试

💡 最终收获

这次经历让小明认识到:​​在软件开发中,没有绝对的最优解,只有最适合当前场景的解决方案。​

最后,感谢同事 MaXiao 的指点💐💐💐