Skip to content

Commit 6cd3d62

Browse files
author
Taois
committed
feat: 文件头工具调优
1 parent 785065f commit 6cd3d62

File tree

2 files changed

+209
-43
lines changed

2 files changed

+209
-43
lines changed
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
2+
import FileHeaderManager from '../../utils/fileHeaderManager.js';
3+
4+
// 旧的算法实现 (为了对比)
5+
function findHeaderBlockOld(text, ext) {
6+
const startMarker = '@header(';
7+
const startIndex = text.indexOf(startMarker);
8+
if (startIndex === -1) return null;
9+
10+
let index = startIndex + startMarker.length;
11+
let balance = 1;
12+
let inString = false;
13+
let stringChar = '';
14+
let escape = false;
15+
let inLineComment = false;
16+
let inBlockComment = false;
17+
18+
for (; index < text.length; index++) {
19+
const char = text[index];
20+
21+
if (inLineComment) {
22+
if (char === '\n') inLineComment = false;
23+
continue;
24+
}
25+
26+
if (inBlockComment) {
27+
if (char === '*' && text[index + 1] === '/') {
28+
inBlockComment = false;
29+
index++;
30+
}
31+
continue;
32+
}
33+
34+
if (inString) {
35+
if (escape) {
36+
escape = false;
37+
} else if (char === '\\') {
38+
escape = true;
39+
} else if (char === stringChar) {
40+
inString = false;
41+
}
42+
continue;
43+
}
44+
45+
// Start of comment
46+
if (char === '/' && text[index + 1] === '/') {
47+
inLineComment = true;
48+
index++;
49+
} else if (char === '/' && text[index + 1] === '*') {
50+
inBlockComment = true;
51+
index++;
52+
} else if (ext === '.py' && char === '#') {
53+
inLineComment = true;
54+
} else if (char === '"' || char === "'") {
55+
inString = true;
56+
stringChar = char;
57+
} else if (char === '(') {
58+
balance++;
59+
} else if (char === ')') {
60+
balance--;
61+
if (balance === 0) {
62+
return {
63+
start: startIndex,
64+
end: index + 1,
65+
content: text.substring(startIndex + startMarker.length, index)
66+
};
67+
}
68+
}
69+
}
70+
return null;
71+
}
72+
73+
// 构造测试数据
74+
const simpleHeader = `
75+
/*
76+
@header({
77+
title: "Simple",
78+
version: 1
79+
})
80+
*/
81+
`;
82+
83+
const complexHeader = `
84+
/*
85+
@header({
86+
title: "Complex Header",
87+
description: "Contains strings with parens ) and comments // inside strings",
88+
nested: {
89+
obj: { a: 1, b: 2 },
90+
arr: [1, 2, 3, "string with )"]
91+
},
92+
regex: "cctv://(.+)",
93+
// This is a comment inside the block
94+
/* Another block comment */
95+
code: "function() { return 'ok'; }"
96+
})
97+
*/
98+
`;
99+
100+
const largeHeader = `
101+
/*
102+
@header({
103+
title: "Large Header",
104+
data: "${'x'.repeat(5000)}",
105+
items: [
106+
${Array(100).fill('{ name: "item", value: "test string with ) inside" }').join(',\n')}
107+
]
108+
})
109+
*/
110+
`;
111+
112+
async function runBenchmark() {
113+
console.log('=== findHeaderBlock 算法性能基准测试 ===');
114+
const iterations = 100000; // 增加循环次数以放大差异
115+
116+
const testCases = [
117+
{ name: 'Simple Header', data: simpleHeader },
118+
{ name: 'Complex Header', data: complexHeader },
119+
{ name: 'Large Header (~10KB)', data: largeHeader }
120+
];
121+
122+
for (const testCase of testCases) {
123+
console.log(`\n测试场景: ${testCase.name} (循环 ${iterations} 次)`);
124+
125+
// 1. 测试旧算法
126+
const startOld = performance.now();
127+
for (let i = 0; i < iterations; i++) {
128+
findHeaderBlockOld(testCase.data, '.js');
129+
}
130+
const endOld = performance.now();
131+
const timeOld = endOld - startOld;
132+
console.log(`[旧算法] 总耗时: ${timeOld.toFixed(4)} ms | 平均: ${(timeOld/iterations).toFixed(5)} ms`);
133+
134+
// 2. 测试新算法
135+
const startNew = performance.now();
136+
for (let i = 0; i < iterations; i++) {
137+
FileHeaderManager.findHeaderBlock(testCase.data, '.js');
138+
}
139+
const endNew = performance.now();
140+
const timeNew = endNew - startNew;
141+
console.log(`[新算法] 总耗时: ${timeNew.toFixed(4)} ms | 平均: ${(timeNew/iterations).toFixed(5)} ms`);
142+
143+
// 3. 计算提升
144+
const improvement = ((timeOld - timeNew) / timeOld * 100).toFixed(2);
145+
console.log(`🚀 性能提升: ${improvement}%`);
146+
147+
// 验证正确性
148+
const resOld = findHeaderBlockOld(testCase.data, '.js');
149+
const resNew = FileHeaderManager.findHeaderBlock(testCase.data, '.js');
150+
if (resOld?.content !== resNew?.content) {
151+
console.error('❌ 结果不一致!');
152+
console.log('Old Content Length:', resOld?.content?.length);
153+
console.log('New Content Length:', resNew?.content?.length);
154+
// console.log('Old:', resOld?.content);
155+
// console.log('New:', resNew?.content);
156+
} else {
157+
console.log('✅ 结果一致');
158+
}
159+
}
160+
}
161+
162+
runBenchmark();

utils/fileHeaderManager.js

Lines changed: 47 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class FileHeaderManager {
3737

3838
/**
3939
* Find the @header(...) block in the comment text
40+
* Optimized state machine for parsing nested structures
4041
* @param {string} text Comment text
4142
* @param {string} ext File extension (.js or .py)
4243
* @returns {Object|null} { start, end, content }
@@ -48,52 +49,31 @@ class FileHeaderManager {
4849

4950
let index = startIndex + startMarker.length;
5051
let balance = 1;
51-
let inString = false;
52-
let stringChar = '';
53-
let escape = false;
54-
let inLineComment = false;
55-
let inBlockComment = false;
56-
57-
for (; index < text.length; index++) {
52+
const len = text.length;
53+
54+
// Fast scan loop
55+
while (index < len) {
5856
const char = text[index];
5957

60-
if (inLineComment) {
61-
if (char === '\n') inLineComment = false;
62-
continue;
63-
}
64-
65-
if (inBlockComment) {
66-
if (char === '*' && text[index + 1] === '/') {
67-
inBlockComment = false;
68-
index++;
69-
}
70-
continue;
71-
}
72-
73-
if (inString) {
74-
if (escape) {
75-
escape = false;
76-
} else if (char === '\\') {
77-
escape = true;
78-
} else if (char === stringChar) {
79-
inString = false;
80-
}
81-
continue;
82-
}
83-
84-
// Start of comment
85-
if (char === '/' && text[index + 1] === '/') {
86-
inLineComment = true;
87-
index++;
88-
} else if (char === '/' && text[index + 1] === '*') {
89-
inBlockComment = true;
58+
// 1. String literal detection (Most common content inside JSON)
59+
if (char === '"' || char === "'") {
60+
const quote = char;
9061
index++;
91-
} else if (ext === '.py' && char === '#') {
92-
inLineComment = true;
93-
} else if (char === '"' || char === "'") {
94-
inString = true;
95-
stringChar = char;
96-
} else if (char === '(') {
62+
while (index < len) {
63+
const c = text[index];
64+
if (c === '\\') {
65+
index += 2; // Skip escaped char
66+
} else if (c === quote) {
67+
index++; // Include closing quote
68+
break; // End of string
69+
} else {
70+
index++;
71+
}
72+
}
73+
continue; // Continue outer loop
74+
}
75+
// 2. Parentheses balance
76+
else if (char === '(') {
9777
balance++;
9878
} else if (char === ')') {
9979
balance--;
@@ -105,6 +85,30 @@ class FileHeaderManager {
10585
};
10686
}
10787
}
88+
// 3. Comments skipping (Only if strictly needed inside header object, usually standard JSON doesn't have comments but JS objects might)
89+
// Optimization: Assume standard JSON5/JS object format inside @header, check for comments only if / or # encountered
90+
else if (char === '/') {
91+
const next = text[index + 1];
92+
if (next === '/') { // Line comment
93+
index += 2;
94+
const newline = text.indexOf('\n', index);
95+
index = newline === -1 ? len : newline;
96+
continue;
97+
} else if (next === '*') { // Block comment
98+
index += 2;
99+
const endComment = text.indexOf('*/', index);
100+
if (endComment === -1) { index = len; }
101+
else { index = endComment + 2; }
102+
continue;
103+
}
104+
}
105+
else if (ext === '.py' && char === '#') { // Python comment
106+
const newline = text.indexOf('\n', index + 1);
107+
index = newline === -1 ? len : newline;
108+
continue;
109+
}
110+
111+
index++;
108112
}
109113
return null;
110114
}

0 commit comments

Comments
 (0)