|
| 1 | + |
| 2 | +import fs from 'fs/promises'; |
| 3 | +import path from 'path'; |
| 4 | +import { fileURLToPath } from 'url'; |
| 5 | +import FileHeaderManager from '../../utils/fileHeaderManager.js'; |
| 6 | + |
| 7 | +const __filename = fileURLToPath(import.meta.url); |
| 8 | +const __dirname = path.dirname(__filename); |
| 9 | + |
| 10 | +const sourceFile = path.resolve(__dirname, '../../spider/js/央视大全[官].js'); |
| 11 | +const testFile = path.resolve(__dirname, 'test_cctv_header_large_temp.js'); |
| 12 | + |
| 13 | +// 模拟 Buffer 模式的 Header 移除 |
| 14 | +async function removeHeaderBuffer(filePath) { |
| 15 | + const handle = await fs.open(filePath, 'r'); |
| 16 | + try { |
| 17 | + const stats = await handle.stat(); |
| 18 | + const buffer = Buffer.alloc(stats.size); |
| 19 | + await handle.read(buffer, 0, stats.size, 0); |
| 20 | + |
| 21 | + // 查找注释块 |
| 22 | + // /* = 0x2f 0x2a |
| 23 | + // */ = 0x2a 0x2f |
| 24 | + const start = buffer.indexOf('/*'); |
| 25 | + if (start === -1) return; |
| 26 | + |
| 27 | + const end = buffer.indexOf('*/', start); |
| 28 | + if (end === -1) return; |
| 29 | + |
| 30 | + // 提取注释内容进行检查 |
| 31 | + const commentBuf = buffer.subarray(start, end + 2); |
| 32 | + const commentStr = commentBuf.toString('utf8'); |
| 33 | + |
| 34 | + if (!commentStr.includes('@header(')) return; |
| 35 | + |
| 36 | + // 查找 @header |
| 37 | + const headerStart = commentStr.indexOf('@header('); |
| 38 | + // 这里简化处理:假设我们要移除整个 @header(...) |
| 39 | + // 实际需要括号平衡逻辑,这里简单起见,假设只有一层括号或用 regex |
| 40 | + // 为了公平对比,我们只测试 "读取 Buffer -> 查找位置 -> 拼接 Buffer -> 写入" 的过程 |
| 41 | + |
| 42 | + // 模拟:我们找到了 header 的 start 和 end |
| 43 | + // 实际逻辑应该复用 FileHeaderManager.findHeaderBlock |
| 44 | + // 但 findHeaderBlock 操作的是 string。 |
| 45 | + |
| 46 | + // 关键在于:是否可以只把 commentBlock 转 string,找到 offset, |
| 47 | + // 然后在原始 Buffer 上进行 slice 和 concat? |
| 48 | + |
| 49 | + // 假设 headerStartOffset 和 headerEndOffset 是相对于 buffer 的 |
| 50 | + const headerGlobalStart = start + headerStart; |
| 51 | + // 简单模拟 header 长度为 100 字节 |
| 52 | + const headerGlobalEnd = headerGlobalStart + 100; |
| 53 | + |
| 54 | + const newBuf = Buffer.concat([ |
| 55 | + buffer.subarray(0, headerGlobalStart), |
| 56 | + buffer.subarray(headerGlobalEnd) |
| 57 | + ]); |
| 58 | + |
| 59 | + await fs.writeFile(filePath, newBuf); |
| 60 | + |
| 61 | + } finally { |
| 62 | + await handle.close(); |
| 63 | + } |
| 64 | +} |
| 65 | + |
| 66 | +async function runTest() { |
| 67 | + console.log('=== 开始大文件性能对比测试 ==='); |
| 68 | + |
| 69 | + // 1. 创建大文件 (500KB) |
| 70 | + console.log('正在生成 500KB 测试文件...'); |
| 71 | + let content = await fs.readFile(sourceFile, 'utf8'); |
| 72 | + // 重复内容直到达到 500KB |
| 73 | + while (Buffer.byteLength(content) < 500 * 1024) { |
| 74 | + content += '\n// Padding content to increase file size...\n' + content; |
| 75 | + } |
| 76 | + // 确保头部有 @header |
| 77 | + if (!content.includes('@header(')) { |
| 78 | + console.warn('源文件没有 @header,测试可能无效'); |
| 79 | + } |
| 80 | + |
| 81 | + await fs.writeFile(testFile, content); |
| 82 | + const stats = await fs.stat(testFile); |
| 83 | + console.log(`测试文件大小: ${(stats.size / 1024).toFixed(2)} KB`); |
| 84 | + |
| 85 | + const iterations = 100; // 大文件操作慢,减少次数 |
| 86 | + |
| 87 | + // 2. 测试现有 String 模式 (FileHeaderManager.removeHeader) |
| 88 | + console.log(`\n[String Mode] removeHeader 测试 (${iterations} 次)...`); |
| 89 | + |
| 90 | + // 预热 |
| 91 | + const header = await FileHeaderManager.readHeader(testFile); |
| 92 | + |
| 93 | + const startString = performance.now(); |
| 94 | + for (let i = 0; i < iterations; i++) { |
| 95 | + // removeHeader 目前只返回字符串,不写入 |
| 96 | + // 为了模拟真实场景,我们需要包含 fs.readFile 和 fs.writeFile |
| 97 | + // FileHeaderManager.removeHeader 内部如果不传 path 传 content,则不包含读。 |
| 98 | + // 如果传 path,则包含读。 |
| 99 | + // 但它不包含写。 |
| 100 | + |
| 101 | + // 模拟完整流程:读 -> 处理 -> 写 |
| 102 | + const newContent = await FileHeaderManager.removeHeader(testFile); // 包含 readFile |
| 103 | + await fs.writeFile(testFile, newContent); |
| 104 | + |
| 105 | + // 恢复文件头以便下一次循环 (为了测试 remove,必须先有) |
| 106 | + // 这步不计入时间?不,这很麻烦。 |
| 107 | + // 我们可以测试 "读取 -> 替换(即使没找到也算处理) -> 写入" 的开销 |
| 108 | + // 或者只测试 removeHeader 的处理部分。 |
| 109 | + |
| 110 | + // 为了简单,我们测试 "Write Header" 吧,因为 writeHeader 包含 读 -> 改 -> 写 |
| 111 | + await FileHeaderManager.writeHeader(testFile, header, { createBackup: false }); |
| 112 | + } |
| 113 | + const endString = performance.now(); |
| 114 | + const timeString = endString - startString; |
| 115 | + console.log(`String Mode 总耗时: ${timeString.toFixed(2)} ms`); |
| 116 | + console.log(`String Mode 平均耗时: ${(timeString / iterations).toFixed(2)} ms`); |
| 117 | + |
| 118 | + |
| 119 | + // 3. 测试模拟 Buffer 模式 |
| 120 | + // 由于 FileHeaderManager 没有 Buffer 模式,我们只能模拟 "读 Buffer -> 改 Buffer -> 写 Buffer" |
| 121 | + // 对比 "读 String -> 改 String -> 写 String" |
| 122 | + |
| 123 | + console.log(`\n[Buffer Mode (Simulated)] writeHeader 测试 (${iterations} 次)...`); |
| 124 | + |
| 125 | + const startBuffer = performance.now(); |
| 126 | + for (let i = 0; i < iterations; i++) { |
| 127 | + const handle = await fs.open(testFile, 'r'); |
| 128 | + const bufStats = await handle.stat(); |
| 129 | + const buffer = Buffer.alloc(bufStats.size); |
| 130 | + await handle.read(buffer, 0, bufStats.size, 0); |
| 131 | + await handle.close(); |
| 132 | + |
| 133 | + // 模拟修改:在 Buffer 中找到 header 位置并替换 |
| 134 | + // 实际算法: |
| 135 | + // 1. 找到 comment block (buffer.indexOf) -> 极快 |
| 136 | + // 2. 将 comment block 转 string (很短) -> 极快 |
| 137 | + // 3. 在 comment string 中找 header -> 极快 |
| 138 | + // 4. 拼接 Buffer -> 极快 (不需要编解码大段代码) |
| 139 | + |
| 140 | + const startComment = buffer.indexOf('/*'); |
| 141 | + const endComment = buffer.indexOf('*/', startComment); |
| 142 | + |
| 143 | + // 假设我们找到了 offset,进行拼接 |
| 144 | + // 这里不做真实解析,只做 Buffer 操作模拟开销 |
| 145 | + const newBuf = Buffer.concat([ |
| 146 | + buffer.subarray(0, startComment), |
| 147 | + Buffer.from('/* Updated Header */'), // 模拟新头 |
| 148 | + buffer.subarray(endComment + 2) |
| 149 | + ]); |
| 150 | + |
| 151 | + await fs.writeFile(testFile, newBuf); |
| 152 | + |
| 153 | + // 恢复 (其实上面已经写入了,不需要额外恢复,因为每次都覆盖) |
| 154 | + } |
| 155 | + const endBuffer = performance.now(); |
| 156 | + const timeBuffer = endBuffer - startBuffer; |
| 157 | + console.log(`Buffer Mode 总耗时: ${timeBuffer.toFixed(2)} ms`); |
| 158 | + console.log(`Buffer Mode 平均耗时: ${(timeBuffer / iterations).toFixed(2)} ms`); |
| 159 | + |
| 160 | + const improvement = ((timeString - timeBuffer) / timeString * 100).toFixed(2); |
| 161 | + console.log(`\n🚀 预计 Buffer 模式提升: ${improvement}%`); |
| 162 | + |
| 163 | + // 清理 |
| 164 | + await fs.unlink(testFile); |
| 165 | +} |
| 166 | + |
| 167 | +runTest(); |
0 commit comments