Electron 智能文件分析器开发实战适配鸿蒙

目录


功能概述

智能文件分析器是一个强大的文件信息提取工具,能够自动识别文件类型并提取详细的统计信息。该功能在文件选择的基础上,进一步提供了深度的文件分析能力。

核心功能

  1. 文件类型识别

    • 根据扩展名显示对应的文件图标
    • 支持 30+ 种常见文件类型
  2. 文本文件分析

    • 编码格式检测(UTF-8、ASCII、UTF-16 等)
    • 行数统计
    • 单词数统计
    • 字符数统计(含/不含空格)
    • 段落数统计
    • 内容预览(前 20 行)
  3. 图片文件分析

    • 图片格式识别(PNG、JPEG、GIF)
    • 图片尺寸提取(宽度、高度)
    • 颜色深度检测(PNG)
    • 图片预览
  4. 文件统计信息

    • 创建时间
    • 访问时间
    • 文件扩展名

应用场景

  • 📝 代码审查:快速了解代码文件的行数和结构
  • 📊 文档分析:统计文档的字数和段落数
  • 🖼️ 图片管理:查看图片尺寸和格式信息
  • 🔍 文件检查:检测文件编码和基本信息

技术架构

分析流程

用户选择文件
    ↓
点击"智能分析文件"按钮
    ↓
读取文件 Buffer
    ↓
检测文件编码
    ↓
根据文件类型分类处理
    ├── 文本文件 → 文本分析
    ├── 图片文件 → 图片分析
    └── 其他文件 → 基础信息
    ↓
生成统计结果
    ↓
显示分析结果和预览

关键技术

  1. Buffer 操作:使用 Node.js Buffer 读取文件二进制数据
  2. 文件头解析:通过文件头(Magic Number)识别文件格式
  3. 编码检测:通过 BOM 和字符分析检测文件编码
  4. 正则表达式:用于文本统计(单词、段落等)
  5. 位运算:用于解析图片文件的尺寸信息

文件编码检测

BOM(Byte Order Mark)检测

BOM 是文件开头的特殊字节序列,用于标识文件编码:

function detectEncoding(buffer) {
    // UTF-8 BOM: EF BB BF
    if (buffer[0] === 0xEF && buffer[1] === 0xBB && buffer[2] === 0xBF) {
        return 'UTF-8 (BOM)';
    }
    
    // UTF-16 LE BOM: FF FE
    if (buffer[0] === 0xFF && buffer[1] === 0xFE) {
        return 'UTF-16 LE';
    }
    
    // UTF-16 BE BOM: FE FF
    if (buffer[0] === 0xFE && buffer[1] === 0xFF) {
        return 'UTF-16 BE';
    }
    
    // 无 BOM,通过字符分析
    return detectEncodingByContent(buffer);
}

基于内容的编码检测

当文件没有 BOM 时,通过检查字符范围来判断编码:

function detectEncodingByContent(buffer) {
    let hasNonAscii = false;
    
    // 只检查前 1000 字节,提高性能
    for (let i = 0; i < Math.min(buffer.length, 1000); i++) {
        if (buffer[i] > 127) {
            hasNonAscii = true;
            break;
        }
    }
    
    return hasNonAscii ? 'UTF-8 (可能)' : 'ASCII';
}

编码检测的局限性

  • 简单检测:只能识别常见的编码格式
  • 准确度:对于没有 BOM 的文件,准确度有限
  • 改进方案:可以使用 chardetjschardet 库提高准确度
// 使用 chardet 库(需要安装:npm install chardet)
const chardet = require('chardet');

function detectEncodingAdvanced(buffer) {
    const detected = chardet.detect(buffer);
    return detected || '未知编码';
}

文本文件分析

行数统计

function countLines(content) {
    // 使用换行符分割
    const lines = content.split('\n');
    return lines.length;
}

注意

  • Windows 使用 \r\n,Unix/Linux 使用 \n
  • split('\n') 可以处理两种情况

单词数统计

function countWords(content) {
    // 使用正则表达式分割单词
    // \s+ 匹配一个或多个空白字符
    const words = content.split(/\s+/).filter(w => w.length > 0);
    return words.length;
}

正则表达式说明

  • /\s+/:匹配一个或多个空白字符(空格、制表符、换行等)
  • filter(w => w.length > 0):过滤空字符串

字符数统计

function countCharacters(content) {
    return {
        withSpaces: content.length,                    // 含空格
        withoutSpaces: content.replace(/\s/g, '').length  // 不含空格
    };
}

段落数统计

function countParagraphs(content) {
    // 段落由空行分隔(\n\n 或 \n\s*\n)
    const paragraphs = content
        .split(/\n\s*\n/)
        .filter(p => p.trim().length > 0);
    return paragraphs.length;
}

完整的文本分析函数

function analyzeTextFile(content, encoding) {
    const lines = content.split('\n');
    const words = content.split(/\s+/).filter(w => w.length > 0);
    const chars = content.length;
    const charsNoSpaces = content.replace(/\s/g, '').length;
    const paragraphs = content.split(/\n\s*\n/).filter(p => p.trim().length > 0).length;
    
    return {
        type: 'text',
        encoding: encoding,
        lines: lines.length,
        words: words.length,
        characters: chars,
        charactersNoSpaces: charsNoSpaces,
        paragraphs: paragraphs
    };
}

文本预览

function getTextPreview(content, maxLines = 20) {
    const lines = content.split('\n');
    const previewLines = lines.slice(0, maxLines);
    const preview = previewLines.join('\n');
    
    if (lines.length > maxLines) {
        return preview + '\n\n... (还有 ' + (lines.length - maxLines) + ' 行)';
    }
    
    return preview;
}

图片文件分析

文件头(Magic Number)识别

不同格式的图片文件有独特的文件头:

格式 文件头(十六进制) 文件头(ASCII)
PNG 89 50 4E 47 .PNG
JPEG FF D8 FF ÿØÿ
GIF 47 49 46 38 GIF8
BMP 42 4D BM

PNG 文件解析

PNG 文件的结构:

  • 文件头:8 字节(89 50 4E 47 0D 0A 1A 0A)
  • IHDR 块:包含图片尺寸信息(从第 16 字节开始)
function parsePNG(buffer) {
    // 检查 PNG 文件头
    if (buffer[0] !== 0x89 || buffer[1] !== 0x50 || 
        buffer[2] !== 0x4E || buffer[3] !== 0x47) {
        return null;
    }
    
    // PNG 尺寸信息在 IHDR 块中(偏移 16-23)
    // 宽度:4 字节,大端序
    const width = (buffer[16] << 24) | 
                  (buffer[17] << 16) | 
                  (buffer[18] << 8) | 
                  buffer[19];
    
    // 高度:4 字节,大端序
    const height = (buffer[20] << 24) | 
                   (buffer[21] << 16) | 
                   (buffer[22] << 8) | 
                   buffer[23];
    
    // 颜色深度:1 字节(第 24 字节)
    const colorDepth = buffer[24] * 8;
    
    return {
        format: 'PNG',
        width: width,
        height: height,
        colorDepth: colorDepth
    };
}

位运算说明

  • <<:左移运算符
  • |:按位或运算符
  • 大端序(Big-Endian):高位字节在前

GIF 文件解析

GIF 文件的结构:

  • 文件头:6 字节(GIF87a 或 GIF89a)
  • 逻辑屏幕描述符:7 字节(包含尺寸信息)
function parseGIF(buffer) {
    // 检查 GIF 文件头
    if (buffer[0] !== 0x47 || buffer[1] !== 0x49 || 
        buffer[2] !== 0x46 || buffer[3] !== 0x38) {
        return null;
    }
    
    // GIF 尺寸信息在小端序(Little-Endian)
    // 宽度:2 字节(偏移 6-7)
    const width = (buffer[7] << 8) | buffer[6];
    
    // 高度:2 字节(偏移 8-9)
    const height = (buffer[9] << 8) | buffer[8];
    
    return {
        format: 'GIF',
        width: width,
        height: height
    };
}

JPEG 文件解析

JPEG 文件解析较复杂,因为尺寸信息在 SOF(Start of Frame)段中,位置不固定:

function parseJPEG(buffer) {
    // 检查 JPEG 文件头
    if (buffer[0] !== 0xFF || buffer[1] !== 0xD8) {
        return null;
    }
    
    // JPEG 尺寸信息需要查找 SOF 段
    // 这里简化处理,实际需要遍历文件查找 SOF 标记
    let i = 2;
    while (i < buffer.length - 1) {
        if (buffer[i] === 0xFF && 
            (buffer[i + 1] >= 0xC0 && buffer[i + 1] <= 0xC3)) {
            // 找到 SOF 段
            const height = (buffer[i + 5] << 8) | buffer[i + 6];
            const width = (buffer[i + 7] << 8) | buffer[i + 8];
            return {
                format: 'JPEG',
                width: width,
                height: height
            };
        }
        i++;
    }
    
    return {
        format: 'JPEG',
        width: '需完整解析',
        height: '需完整解析'
    };
}

统一的图片分析函数

function analyzeImageFile(buffer) {
    // PNG
    if (buffer[0] === 0x89 && buffer[1] === 0x50 && 
        buffer[2] === 0x4E && buffer[3] === 0x47) {
        return parsePNG(buffer);
    }
    
    // JPEG
    if (buffer[0] === 0xFF && buffer[1] === 0xD8) {
        return parseJPEG(buffer);
    }
    
    // GIF
    if (buffer[0] === 0x47 && buffer[1] === 0x49 && 
        buffer[2] === 0x46 && buffer[3] === 0x38) {
        return parseGIF(buffer);
    }
    
    return null;
}

UI 设计与交互

HTML 结构

<div class="file-analyzer" id="file-analyzer">
    <button id="analyze-file-btn">
        <span id="analyze-btn-text">🔍 智能分析文件</span>
        <span id="analyze-loading" class="loading"></span>
    </button>
    <div class="analyzer-result" id="analyzer-result">
        <div class="file-icon" id="file-icon"></div>
        <h4>📊 文件分析结果</h4>
        <div id="analyzer-stats"></div>
        <div id="file-preview-container">
            <h4>👀 内容预览</h4>
            <div class="file-preview" id="file-preview"></div>
        </div>
    </div>
</div>

CSS 样式

.file-analyzer {
    margin-top: 20px;
    padding: 20px;
    background: rgba(255, 255, 255, 0.15);
    border-radius: 10px;
}

.file-analyzer button {
    background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
    color: white;
    border: none;
    padding: 12px 30px;
    font-size: 1em;
    border-radius: 25px;
    cursor: pointer;
    transition: all 0.3s ease;
    box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}

.file-analyzer button:hover {
    transform: translateY(-2px);
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
}

.analyzer-result .stat-item {
    margin: 10px 0;
    padding: 8px;
    background: rgba(0, 0, 0, 0.2);
    border-radius: 5px;
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.file-preview {
    max-height: 200px;
    overflow-y: auto;
    font-family: 'Monaco', 'Courier New', monospace;
    font-size: 0.85em;
    line-height: 1.6;
}

加载动画

.loading {
    display: inline-block;
    width: 20px;
    height: 20px;
    border: 3px solid rgba(255, 255, 255, 0.3);
    border-radius: 50%;
    border-top-color: #fff;
    animation: spin 1s ease-in-out infinite;
}

@keyframes spin {
    to { transform: rotate(360deg); }
}

交互逻辑

analyzeBtn.addEventListener('click', async () => {
    if (!currentFilePath) {
        alert('请先选择一个文件');
        return;
    }
    
    // 禁用按钮,显示加载状态
    analyzeBtn.disabled = true;
    analyzeBtnText.textContent = '分析中';
    analyzeLoading.style.display = 'inline-block';
    
    try {
        await analyzeFile(currentFilePath);
    } catch (error) {
        console.error('分析出错:', error);
        alert('分析文件时出错: ' + error.message);
    } finally {
        // 恢复按钮状态
        analyzeBtn.disabled = false;
        analyzeBtnText.textContent = '🔍 智能分析文件';
        analyzeLoading.style.display = 'none';
    }
});

完整代码实现

文件类型图标映射

function getFileIcon(ext) {
    const iconMap = {
        '.txt': '📄', '.md': '📝', '.json': '📋', 
        '.js': '📜', '.html': '🌐', '.css': '🎨',
        '.jpg': '🖼️', '.jpeg': '🖼️', '.png': '🖼️', 
        '.gif': '🖼️', '.mp4': '🎬', '.avi': '🎬',
        '.pdf': '📕', '.doc': '📘', '.docx': '📘',
        '.xls': '📊', '.xlsx': '📊', '.zip': '📦',
        '.py': '🐍', '.java': '☕', '.cpp': '⚡',
        '.go': '🐹', '.rs': '🦀', '.php': '🐘'
    };
    return iconMap[ext.toLowerCase()] || '📁';
}

主分析函数

async function analyzeFile(filePath) {
    try {
        const stats = fs.statSync(filePath);
        const ext = path.extname(filePath).toLowerCase();
        const buffer = fs.readFileSync(filePath);
        const encoding = detectEncoding(buffer);
        
        // 显示文件图标
        fileIcon.textContent = getFileIcon(ext);
        
        const statsHtml = [];
        
        // 基础信息
        statsHtml.push(`
            <div class="stat-item">
                <span class="stat-label">文件扩展名</span>
                <span class="stat-value">${ext || '无'}</span>
            </div>
            <div class="stat-item">
                <span class="stat-label">创建时间</span>
                <span class="stat-value">${formatDate(stats.birthtime)}</span>
            </div>
            <div class="stat-item">
                <span class="stat-label">访问时间</span>
                <span class="stat-value">${formatDate(stats.atime)}</span>
            </div>
        `);
        
        // 文本文件分析
        const textExtensions = ['.txt', '.md', '.json', '.js', '.html', 
                                 '.css', '.py', '.java', '.cpp', '.c', 
                                 '.go', '.rs', '.php', '.xml', '.yaml', '.yml'];
        if (textExtensions.includes(ext)) {
            try {
                const content = buffer.toString('utf-8');
                const analysis = analyzeTextFile(content, encoding);
                
                statsHtml.push(`
                    <div class="stat-item" style="background: rgba(255, 215, 0, 0.2); margin-top: 15px;">
                        <span class="stat-label">📝 文本分析</span>
                        <span class="stat-value"></span>
                    </div>
                    <div class="stat-item">
                        <span class="stat-label">编码格式</span>
                        <span class="stat-value">${analysis.encoding}</span>
                    </div>
                    <div class="stat-item">
                        <span class="stat-label">总行数</span>
                        <span class="stat-value">${analysis.lines.toLocaleString()}</span>
                    </div>
                    <div class="stat-item">
                        <span class="stat-label">单词数</span>
                        <span class="stat-value">${analysis.words.toLocaleString()}</span>
                    </div>
                    <div class="stat-item">
                        <span class="stat-label">字符数(含空格)</span>
                        <span class="stat-value">${analysis.characters.toLocaleString()}</span>
                    </div>
                    <div class="stat-item">
                        <span class="stat-label">字符数(不含空格)</span>
                        <span class="stat-value">${analysis.charactersNoSpaces.toLocaleString()}</span>
                    </div>
                    <div class="stat-item">
                        <span class="stat-label">段落数</span>
                        <span class="stat-value">${analysis.paragraphs}</span>
                    </div>
                `);
                
                // 显示预览
                const previewLines = content.split('\n').slice(0, 20);
                filePreview.textContent = previewLines.join('\n');
                if (previewLines.length < analysis.lines) {
                    filePreview.textContent += '\n\n... (还有 ' + 
                        (analysis.lines - previewLines.length) + ' 行)';
                }
                filePreviewContainer.style.display = 'block';
            } catch (e) {
                statsHtml.push(`
                    <div class="stat-item">
                        <span class="stat-label">⚠️ 无法读取文本内容</span>
                        <span class="stat-value">${e.message}</span>
                    </div>
                `);
            }
        }
        
        // 图片文件分析
        if (['.jpg', '.jpeg', '.png', '.gif', '.bmp'].includes(ext)) {
            const imageAnalysis = analyzeImageFile(buffer);
            if (imageAnalysis) {
                statsHtml.push(`
                    <div class="stat-item" style="background: rgba(255, 215, 0, 0.2); margin-top: 15px;">
                        <span class="stat-label">🖼️ 图片分析</span>
                        <span class="stat-value"></span>
                    </div>
                    <div class="stat-item">
                        <span class="stat-label">图片格式</span>
                        <span class="stat-value">${imageAnalysis.format}</span>
                    </div>
                    <div class="stat-item">
                        <span class="stat-label">宽度</span>
                        <span class="stat-value">${imageAnalysis.width} px</span>
                    </div>
                    <div class="stat-item">
                        <span class="stat-label">高度</span>
                        <span class="stat-value">${imageAnalysis.height} px</span>
                    </div>
                `);
                
                // 显示图片预览
                const img = document.createElement('img');
                img.src = `file://${filePath}`;
                img.style.maxWidth = '100%';
                img.style.maxHeight = '200px';
                img.style.borderRadius = '8px';
                filePreview.innerHTML = '';
                filePreview.appendChild(img);
                filePreviewContainer.style.display = 'block';
            }
        }
        
        analyzerStats.innerHTML = statsHtml.join('');
        analyzerResult.classList.add('show');
        
    } catch (error) {
        console.error('分析文件失败:', error);
        analyzerStats.innerHTML = `
            <div class="stat-item" style="background: rgba(255, 0, 0, 0.2);">
                <span class="stat-label">❌ 分析失败</span>
                <span class="stat-value">${error.message}</span>
            </div>
        `;
        analyzerResult.classList.add('show');
    }
}

功能扩展

1. 代码文件分析

function analyzeCodeFile(content, ext) {
    const analysis = analyzeTextFile(content);
    
    // 代码特定统计
    const codeStats = {
        ...analysis,
        functions: countFunctions(content, ext),
        classes: countClasses(content, ext),
        comments: countComments(content, ext),
        imports: countImports(content, ext)
    };
    
    return codeStats;
}

function countFunctions(content, ext) {
    // JavaScript/TypeScript
    if (['.js', '.ts', '.jsx', '.tsx'].includes(ext)) {
        const matches = content.match(/function\s+\w+|const\s+\w+\s*=\s*\(/g);
        return matches ? matches.length : 0;
    }
    // Python
    if (ext === '.py') {
        const matches = content.match(/def\s+\w+/g);
        return matches ? matches.length : 0;
    }
    return 0;
}

function countComments(content, ext) {
    let pattern;
    if (['.js', '.ts', '.jsx', '.tsx', '.java', '.cpp', '.c'].includes(ext)) {
        pattern = /\/\/.*|\/\*[\s\S]*?\*\//g;
    } else if (ext === '.py') {
        pattern = /#.*/g;
    } else {
        return 0;
    }
    
    const matches = content.match(pattern);
    return matches ? matches.length : 0;
}

2. JSON 文件验证

function analyzeJSONFile(content) {
    try {
        const json = JSON.parse(content);
        return {
            valid: true,
            type: Array.isArray(json) ? '数组' : '对象',
            keys: Object.keys(json).length,
            size: JSON.stringify(json).length
        };
    } catch (error) {
        return {
            valid: false,
            error: error.message
        };
    }
}

3. Markdown 文件分析

function analyzeMarkdownFile(content) {
    const analysis = analyzeTextFile(content);
    
    return {
        ...analysis,
        headings: (content.match(/^#+\s/gm) || []).length,
        links: (content.match(/\[.*?\]\(.*?\)/g) || []).length,
        images: (content.match(/!\[.*?\]\(.*?\)/g) || []).length,
        codeBlocks: (content.match(/```[\s\S]*?```/g) || []).length
    };
}

4. 文件哈希计算

const crypto = require('crypto');

function calculateFileHash(filePath, algorithm = 'md5') {
    const buffer = fs.readFileSync(filePath);
    const hash = crypto.createHash(algorithm);
    hash.update(buffer);
    return hash.digest('hex');
}

// 使用
const md5Hash = calculateFileHash(filePath, 'md5');
const sha256Hash = calculateFileHash(filePath, 'sha256');

5. 文件依赖分析

function analyzeDependencies(content, ext) {
    const dependencies = [];
    
    // JavaScript/TypeScript
    if (['.js', '.ts', '.jsx', '.tsx'].includes(ext)) {
        const importMatches = content.match(/import\s+.*?\s+from\s+['"](.*?)['"]/g);
        if (importMatches) {
            importMatches.forEach(match => {
                const dep = match.match(/['"](.*?)['"]/)[1];
                dependencies.push(dep);
            });
        }
    }
    
    // Python
    if (ext === '.py') {
        const importMatches = content.match(/^import\s+(\w+)|^from\s+(\w+)/gm);
        if (importMatches) {
            importMatches.forEach(match => {
                const dep = match.match(/(?:import|from)\s+(\w+)/)[1];
                dependencies.push(dep);
            });
        }
    }
    
    return dependencies;
}

性能优化

1. 大文件处理

对于大文件,不应该一次性读取到内存:

async function analyzeLargeFile(filePath) {
    const stats = fs.statSync(filePath);
    const fileSize = stats.size;
    const maxSize = 10 * 1024 * 1024; // 10MB
    
    if (fileSize > maxSize) {
        // 只读取文件头进行分析
        const buffer = Buffer.alloc(1024);
        const fd = fs.openSync(filePath, 'r');
        fs.readSync(fd, buffer, 0, 1024, 0);
        fs.closeSync(fd);
        
        return {
            tooLarge: true,
            size: fileSize,
            preview: analyzeFileHeader(buffer)
        };
    }
    
    // 小文件正常处理
    return analyzeFile(filePath);
}

2. 流式读取

const readline = require('readline');
const fs = require('fs');

async function analyzeLargeTextFile(filePath) {
    const fileStream = fs.createReadStream(filePath);
    const rl = readline.createInterface({
        input: fileStream,
        crlfDelay: Infinity
    });
    
    let lineCount = 0;
    let wordCount = 0;
    
    for await (const line of rl) {
        lineCount++;
        wordCount += line.split(/\s+/).filter(w => w.length > 0).length;
    }
    
    return {
        lines: lineCount,
        words: wordCount
    };
}

3. 异步处理

async function analyzeFileAsync(filePath) {
    // 使用 Promise.all 并行处理
    const [stats, buffer] = await Promise.all([
        fs.promises.stat(filePath),
        fs.promises.readFile(filePath)
    ]);
    
    // 异步分析
    const analysis = await Promise.resolve(analyzeFileContent(buffer, stats));
    
    return analysis;
}

4. 缓存机制

const analysisCache = new Map();

function analyzeFileWithCache(filePath) {
    const stats = fs.statSync(filePath);
    const cacheKey = `${filePath}-${stats.mtime.getTime()}`;
    
    if (analysisCache.has(cacheKey)) {
        return analysisCache.get(cacheKey);
    }
    
    const analysis = analyzeFile(filePath);
    analysisCache.set(cacheKey, analysis);
    
    // 限制缓存大小
    if (analysisCache.size > 100) {
        const firstKey = analysisCache.keys().next().value;
        analysisCache.delete(firstKey);
    }
    
    return analysis;
}

最佳实践

1. 错误处理

async function analyzeFile(filePath) {
    try {
        // 检查文件是否存在
        if (!fs.existsSync(filePath)) {
            throw new Error('文件不存在');
        }
        
        // 检查文件大小
        const stats = fs.statSync(filePath);
        if (stats.size === 0) {
            throw new Error('文件为空');
        }
        
        // 执行分析
        const buffer = fs.readFileSync(filePath);
        // ...
        
    } catch (error) {
        // 详细的错误信息
        console.error('分析文件失败:', {
            filePath,
            error: error.message,
            stack: error.stack
        });
        
        // 用户友好的错误提示
        return {
            success: false,
            error: error.message
        };
    }
}

2. 文件类型验证

function isValidTextFile(ext) {
    const validExtensions = [
        '.txt', '.md', '.json', '.js', '.html', '.css',
        '.py', '.java', '.cpp', '.c', '.go', '.rs', '.php'
    ];
    return validExtensions.includes(ext.toLowerCase());
}

function isValidImageFile(ext) {
    const validExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'];
    return validExtensions.includes(ext.toLowerCase());
}

3. 内存管理

function analyzeFileSafely(filePath) {
    const maxMemory = 50 * 1024 * 1024; // 50MB
    const stats = fs.statSync(filePath);
    
    if (stats.size > maxMemory) {
        // 使用流式处理
        return analyzeLargeFile(filePath);
    }
    
    // 正常处理
    return analyzeFile(filePath);
}

4. 用户反馈

async function analyzeFileWithProgress(filePath, onProgress) {
    const stats = fs.statSync(filePath);
    const totalSize = stats.size;
    let processedSize = 0;
    
    const stream = fs.createReadStream(filePath);
    
    stream.on('data', (chunk) => {
        processedSize += chunk.length;
        const progress = (processedSize / totalSize) * 100;
        onProgress(progress);
    });
    
    stream.on('end', () => {
        onProgress(100);
    });
    
    // 分析逻辑...
}

常见问题

1. 大文件导致内存溢出

问题:读取大文件时内存溢出。

解决方案

  • 限制文件大小
  • 使用流式读取
  • 只读取文件头进行分析
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB

if (stats.size > MAX_FILE_SIZE) {
    alert('文件过大,无法完整分析');
    return;
}

2. 编码检测不准确

问题:无法准确检测文件编码。

解决方案

  • 使用专业的编码检测库
  • 提供手动选择编码的选项
  • 尝试多种编码方式
// 使用 chardet 库
const chardet = require('chardet');
const detected = chardet.detect(buffer);

3. 图片尺寸解析失败

问题:某些图片格式无法解析尺寸。

解决方案

  • 使用图片处理库(如 sharpjimp
  • 在浏览器中加载图片获取尺寸
// 使用 sharp 库
const sharp = require('sharp');

async function getImageSize(filePath) {
    const metadata = await sharp(filePath).metadata();
    return {
        width: metadata.width,
        height: metadata.height
    };
}

4. 性能问题

问题:分析大文件时界面卡顿。

解决方案

  • 使用 Web Worker 进行后台处理
  • 显示加载进度
  • 异步处理
// 使用 Web Worker
const worker = new Worker('file-analyzer-worker.js');
worker.postMessage({ filePath, buffer });
worker.onmessage = (e) => {
    displayResults(e.data);
};

5. 二进制文件误判

问题:二进制文件被当作文本文件处理。

解决方案

  • 检查文件头
  • 检测二进制字符
  • 限制文本文件大小
function isBinaryFile(buffer) {
    // 检查是否包含 NULL 字节
    if (buffer.indexOf(0) !== -1) {
        return true;
    }
    
    // 检查控制字符比例
    let controlChars = 0;
    for (let i = 0; i < Math.min(buffer.length, 512); i++) {
        if (buffer[i] < 32 && buffer[i] !== 9 && buffer[i] !== 10 && buffer[i] !== 13) {
            controlChars++;
        }
    }
    
    return controlChars / buffer.length > 0.3;
}

总结

通过本文,我们学习了:

  1. 文件编码检测:通过 BOM 和字符分析检测文件编码
  2. 文本文件分析:统计行数、字数、字符数、段落数
  3. 图片文件分析:通过文件头解析图片尺寸和格式
  4. UI 设计:创建美观的分析结果展示界面
  5. 性能优化:处理大文件和提升分析速度
  6. 功能扩展:代码分析、JSON 验证、Markdown 分析等

关键要点

  • 文件头解析是识别文件格式的关键技术
  • 位运算用于解析二进制文件格式
  • 正则表达式用于文本统计和分析
  • 错误处理对于提升用户体验至关重要
  • 性能优化需要考虑大文件和内存管理

下一步学习


祝您开发愉快! 🚀


最后更新:2025年11月10日
Electron 版本:39.1.1
Node.js 版本:20.17.0

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐