压缩与解压:HarmonyOS开发中Zip文件处理

举报
Jack20 发表于 2026/06/29 11:03:06 2026/06/29
【摘要】 压缩与解压:HarmonyOS开发中Zip文件处理 一、小知识你肯定用过zip文件——下载个开源项目是zip,备份个相册是zip,就连浏览器导出书签都是zip。这东西太常见了,以至于我们很少思考它背后的原理。但做应用开发时,你迟早会遇到这些场景:用户要导出一堆数据,总不能一个一个文件发吧?日志文件积攒了几百个,占了好几G空间,得压缩归档批量下载资源包,zip格式比一个个文件下载快多了应用内...

压缩与解压:HarmonyOS开发中Zip文件处理

一、小知识

你肯定用过zip文件——下载个开源项目是zip,备份个相册是zip,就连浏览器导出书签都是zip。这东西太常见了,以至于我们很少思考它背后的原理。

但做应用开发时,你迟早会遇到这些场景:

  • 用户要导出一堆数据,总不能一个一个文件发吧?
  • 日志文件积攒了几百个,占了好几G空间,得压缩归档
  • 批量下载资源包,zip格式比一个个文件下载快多了
  • 应用内资源太大,打包成zip按需解压加载

这些场景都离不开压缩与解压。HarmonyOS内置了zlib模块,支持标准的zip格式处理,既能压缩单个文件,也能打包整个目录,还能设置压缩级别平衡速度和压缩率。

压缩这事儿看着简单,实际用起来全是细节——路径处理、编码问题、大文件内存溢出、压缩进度显示……咱们这篇就把这些坑都填上。

二、核心原理

2.1 Zip文件格式

Zip文件本质上是个容器,里面包含:

  • 本地文件头:每个文件的元信息(文件名、压缩方法、CRC校验等)
  • 压缩数据:实际压缩后的文件内容
  • 中央目录:文件索引,记录所有文件的偏移量
  • 中央目录结束记录:整个zip的元信息
graph TD
    subgraph Zip文件结构
        A[本地文件头1]:::primary
        B[压缩数据1]:::info
        C[本地文件头2]:::primary
        D[压缩数据2]:::info
        E[...]:::warning
        F[中央目录]:::success
        G[中央目录结束记录]:::danger
    end
    
    A --> B --> C --> D --> E --> F --> G
    
    classDef primary fill:#2196F3,stroke:#1976D2,color:#fff
    classDef info fill:#00BCD4,stroke:#0097A7,color:#fff
    classDef warning fill:#FF9800,stroke:#F57C00,color:#fff
    classDef success fill:#4CAF50,stroke:#388E3C,color:#fff
    classDef danger fill:#F44336,stroke:#D32F2F,color:#fff

2.2 压缩算法

Zip格式支持多种压缩算法,最常用的是DEFLATE

  • STORE:不压缩,直接存储(适合已压缩的图片、视频)
  • DEFLATE:LZ77+Huffman编码,通用性强,压缩率好
  • BZIP2:压缩率更高,但速度慢(HarmonyOS暂不支持)

压缩级别从0到9:

  • 0:不压缩(STORE)
  • 1:最快压缩,压缩率最低
  • 6:默认级别,平衡速度和压缩率
  • 9:最高压缩率,速度最慢

2.3 压缩解压流程

graph TD
    A[压缩请求]:::primary --> B{压缩类型?}:::warning
    B -->|单文件| C[读取文件内容]:::info
    B -->|目录| D[遍历目录文件]:::info
    C --> E[调用zlib压缩]:::success
    D --> F[逐个文件压缩]:::success
    F --> G[写入Zip中央目录]:::info
    E --> H[生成Zip文件]:::danger
    G --> H
    
    I[解压请求]:::primary --> J[解析Zip中央目录]:::info
    J --> K[读取文件头]:::info
    K --> L[调用zlib解压]:::success
    L --> M[写入目标路径]:::danger
    
    classDef primary fill:#2196F3,stroke:#1976D2,color:#fff
    classDef info fill:#00BCD4,stroke:#0097A7,color:#fff
    classDef warning fill:#FF9800,stroke:#F57C00,color:#fff
    classDef success fill:#4CAF50,stroke:#388E3C,color:#fff
    classDef danger fill:#F44336,stroke:#D32F2F,color:#fff

三、代码实战

3.1 基础用法:单文件压缩解压

先从最简单的场景开始——压缩和解压单个文件:

import zlib from '@ohos.zlib';
import fs from '@ohos.file.fs';
import { Callback } from '@ohos.base';

/**
 * 单文件压缩工具
 */
export class FileCompressor {
  /**
   * 压缩单个文件
   * @param srcPath 源文件路径
   * @param destZipPath 目标zip文件路径
   * @param level 压缩级别 0-9
   */
  async compressFile(
    srcPath: string,
    destZipPath: string,
    level: number = 6
  ): Promise<void> {
    // 检查源文件是否存在
    if (!fs.accessSync(srcPath)) {
      throw new Error(`Source file not found: ${srcPath}`);
    }

    // 确保目标目录存在
    const destDir = destZipPath.substring(0, destZipPath.lastIndexOf('/'));
    if (!fs.accessSync(destDir)) {
      fs.mkdirSync(destDir, true);
    }

    // 创建压缩选项
    const options: zlib.ZipOptions = {
      level: level,                    // 压缩级别
      memLevel: 8,                     // 内存级别(影响内存占用)
      strategy: zlib.CompressStrategy.DEFAULT_STRATEGY
    };

    // 执行压缩
    return new Promise((resolve, reject) => {
      zlib.compressFile(srcPath, destZipPath, options, (errCode) => {
        if (errCode === 0) {
          console.info(`[Compressor] Compressed: ${srcPath} -> ${destZipPath}`);
          resolve();
        } else {
          reject(new Error(`Compress failed with code: ${errCode}`));
        }
      });
    });
  }

  /**
   * 解压单个zip文件
   * @param zipPath zip文件路径
   * @param destDir 目标目录
   */
  async decompressFile(zipPath: string, destDir: string): Promise<void> {
    // 检查zip文件是否存在
    if (!fs.accessSync(zipPath)) {
      throw new Error(`Zip file not found: ${zipPath}`);
    }

    // 确保目标目录存在
    if (!fs.accessSync(destDir)) {
      fs.mkdirSync(destDir, true);
    }

    // 创建解压选项
    const options: zlib.UnzipOptions = {
      cover: true  // 覆盖已存在的文件
    };

    // 执行解压
    return new Promise((resolve, reject) => {
      zlib.decompressFile(zipPath, destDir, options, (errCode) => {
        if (errCode === 0) {
          console.info(`[Compressor] Decompressed: ${zipPath} -> ${destDir}`);
          resolve();
        } else {
          reject(new Error(`Decompress failed with code: ${errCode}`));
        }
      });
    });
  }

  /**
   * 获取压缩统计信息
   */
  async getCompressionStats(srcPath: string, zipPath: string): Promise<CompressionStats> {
    const srcStat = fs.statSync(srcPath);
    const zipStat = fs.statSync(zipPath);

    return {
      originalSize: srcStat.size,
      compressedSize: zipStat.size,
      compressionRatio: ((1 - zipStat.size / srcStat.size) * 100).toFixed(2) + '%'
    };
  }
}

/**
 * 压缩统计信息
 */
interface CompressionStats {
  originalSize: number;
  compressedSize: number;
  compressionRatio: string;
}

3.2 进阶用法:目录压缩

实际场景中,更常见的是压缩整个目录:

import zlib from '@ohos.zlib';
import fs from '@ohos.file.fs';

/**
 * 目录压缩器
 * 支持递归压缩、过滤、进度回调
 */
export class DirectoryCompressor {
  private totalFiles: number = 0;
  private processedFiles: number = 0;

  /**
   * 压缩目录
   * @param srcDir 源目录
   * @param destZipPath 目标zip路径
   * @param options 压缩选项
   */
  async compressDirectory(
    srcDir: string,
    destZipPath: string,
    options: DirectoryCompressOptions = {}
  ): Promise<void> {
    const {
      level = 6,
      excludePatterns = [],
      includeHidden = false,
      onProgress
    } = options;

    // 收集要压缩的文件列表
    const fileList = await this.collectFiles(srcDir, excludePatterns, includeHidden);
    this.totalFiles = fileList.length;
    this.processedFiles = 0;

    if (fileList.length === 0) {
      throw new Error('No files to compress');
    }

    console.info(`[DirCompressor] Total files to compress: ${this.totalFiles}`);

    // 创建临时目录存放压缩文件
    const tempDir = `${destZipPath}_temp`;
    if (!fs.accessSync(tempDir)) {
      fs.mkdirSync(tempDir, true);
    }

    try {
      // 逐个文件压缩
      for (const fileInfo of fileList) {
        const relativePath = fileInfo.path.substring(srcDir.length + 1);
        const tempFilePath = `${tempDir}/${relativePath.replace(/\//g, '_')}.zip`;

        // 压缩单个文件
        await this.compressSingleFile(fileInfo.path, tempFilePath, level);

        this.processedFiles++;
        if (onProgress) {
          onProgress({
            current: this.processedFiles,
            total: this.totalFiles,
            currentFile: relativePath
          });
        }
      }

      // 合并所有zip为一个(这里简化处理,实际应使用ZipOutputStream)
      // HarmonyOS的zlib模块暂不支持直接创建多文件zip
      // 实际项目中建议使用native实现或第三方库

      console.info(`[DirCompressor] Compression completed`);
    } finally {
      // 清理临时文件
      this.cleanupTempDir(tempDir);
    }
  }

  /**
   * 收集目录下的所有文件
   */
  private async collectFiles(
    dir: string,
    excludePatterns: string[],
    includeHidden: boolean
  ): Promise<FileInfo[]> {
    const files: FileInfo[] = [];
    await this.walkDirectory(dir, files, excludePatterns, includeHidden);
    return files;
  }

  /**
   * 递归遍历目录
   */
  private async walkDirectory(
    currentDir: string,
    fileList: FileInfo[],
    excludePatterns: string[],
    includeHidden: boolean
  ): Promise<void> {
    const entries = fs.listFileSync(currentDir);

    for (const entry of entries) {
      const fullPath = `${currentDir}/${entry}`;
      const stat = fs.statSync(fullPath);

      // 跳过隐藏文件
      if (!includeHidden && entry.startsWith('.')) {
        continue;
      }

      // 检查排除模式
      if (this.shouldExclude(entry, excludePatterns)) {
        continue;
      }

      if (stat.isDirectory()) {
        // 递归处理子目录
        await this.walkDirectory(fullPath, fileList, excludePatterns, includeHidden);
      } else {
        // 添加文件到列表
        fileList.push({
          path: fullPath,
          size: stat.size,
          name: entry
        });
      }
    }
  }

  /**
   * 检查是否应该排除
   */
  private shouldExclude(fileName: string, patterns: string[]): boolean {
    for (const pattern of patterns) {
      if (fileName.includes(pattern) || this.matchWildcard(fileName, pattern)) {
        return true;
      }
    }
    return false;
  }

  /**
   * 通配符匹配
   */
  private matchWildcard(text: string, pattern: string): boolean {
    const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
    return regex.test(text);
  }

  /**
   * 压缩单个文件
   */
  private async compressSingleFile(
    srcPath: string,
    destPath: string,
    level: number
  ): Promise<void> {
    return new Promise((resolve, reject) => {
      zlib.compressFile(srcPath, destPath, { level }, (errCode) => {
        if (errCode === 0) {
          resolve();
        } else {
          reject(new Error(`Compress failed: ${errCode}`));
        }
      });
    });
  }

  /**
   * 清理临时目录
   */
  private cleanupTempDir(tempDir: string): void {
    if (fs.accessSync(tempDir)) {
      const files = fs.listFileSync(tempDir);
      for (const file of files) {
        fs.unlinkSync(`${tempDir}/${file}`);
      }
      fs.rmdirSync(tempDir);
    }
  }
}

/**
 * 目录压缩选项
 */
interface DirectoryCompressOptions {
  level?: number;              // 压缩级别
  excludePatterns?: string[];  // 排除模式(如 ['*.tmp', 'node_modules'])
  includeHidden?: boolean;     // 是否包含隐藏文件
  onProgress?: (progress: CompressProgress) => void;  // 进度回调
}

/**
 * 文件信息
 */
interface FileInfo {
  path: string;
  size: number;
  name: string;
}

/**
 * 压缩进度
 */
interface CompressProgress {
  current: number;
  total: number;
  currentFile: string;
}

3.3 实战案例:日志归档

结合压缩实现日志文件的自动归档和清理:

import { FileCompressor } from './FileCompressor';
import fs from '@ohos.file.fs';

/**
 * 日志归档器
 * 自动压缩旧日志,释放存储空间
 */
export class LogArchiver {
  private logDir: string;
  private archiveDir: string;
  private maxLogAge: number;      // 日志保留天数
  private compressor: FileCompressor;

  constructor(logDir: string, archiveDir: string, maxLogAge: number = 7) {
    this.logDir = logDir;
    this.archiveDir = archiveDir;
    this.maxLogAge = maxLogAge;
    this.compressor = new FileCompressor();
  }

  /**
   * 执行归档
   * 将超过保留期的日志压缩归档
   */
  async archive(): Promise<ArchiveResult> {
    const result: ArchiveResult = {
      archivedFiles: 0,
      savedSpace: 0,
      errors: []
    };

    // 确保归档目录存在
    if (!fs.accessSync(this.archiveDir)) {
      fs.mkdirSync(this.archiveDir, true);
    }

    // 获取所有日志文件
    const logFiles = fs.listFileSync(this.logDir);
    const now = Date.now();
    const maxAgeMs = this.maxLogAge * 24 * 60 * 60 * 1000;

    for (const logFile of logFiles) {
      const logPath = `${this.logDir}/${logFile}`;
      
      // 跳过目录
      if (fs.statSync(logPath).isDirectory()) {
        continue;
      }

      // 检查文件年龄
      const stat = fs.statSync(logPath);
      const fileAge = now - stat.mtime;

      if (fileAge > maxAgeMs) {
        try {
          // 压缩归档
          const archiveName = `${logFile}_${this.formatDate(stat.mtime)}.zip`;
          const archivePath = `${this.archiveDir}/${archiveName}`;

          await this.compressor.compressFile(logPath, archivePath, 9);  // 最高压缩

          // 删除原文件
          fs.unlinkSync(logPath);

          result.archivedFiles++;
          result.savedSpace += stat.size - fs.statSync(archivePath).size;

          console.info(`[LogArchiver] Archived: ${logFile}`);
        } catch (error) {
          result.errors.push(`Failed to archive ${logFile}: ${error.message}`);
        }
      }
    }

    return result;
  }

  /**
   * 清理过期的归档文件
   * @param maxArchiveAge 归档保留天数
   */
  async cleanupOldArchives(maxArchiveAge: number = 30): Promise<number> {
    if (!fs.accessSync(this.archiveDir)) {
      return 0;
    }

    const archives = fs.listFileSync(this.archiveDir);
    const now = Date.now();
    const maxAgeMs = maxArchiveAge * 24 * 60 * 60 * 1000;
    let deletedCount = 0;

    for (const archive of archives) {
      const archivePath = `${this.archiveDir}/${archive}`;
      const stat = fs.statSync(archivePath);

      if (now - stat.mtime > maxAgeMs) {
        fs.unlinkSync(archivePath);
        deletedCount++;
        console.info(`[LogArchiver] Deleted old archive: ${archive}`);
      }
    }

    return deletedCount;
  }

  /**
   * 获取存储统计
   */
  getStorageStats(): StorageStats {
    const stats: StorageStats = {
      logDirSize: 0,
      archiveDirSize: 0,
      logFileCount: 0,
      archiveFileCount: 0
    };

    if (fs.accessSync(this.logDir)) {
      const logFiles = fs.listFileSync(this.logDir);
      stats.logFileCount = logFiles.length;
      for (const file of logFiles) {
        stats.logDirSize += fs.statSync(`${this.logDir}/${file}`).size;
      }
    }

    if (fs.accessSync(this.archiveDir)) {
      const archives = fs.listFileSync(this.archiveDir);
      stats.archiveFileCount = archives.length;
      for (const archive of archives) {
        stats.archiveDirSize += fs.statSync(`${this.archiveDir}/${archive}`).size;
      }
    }

    return stats;
  }

  /**
   * 格式化日期
   */
  private formatDate(timestamp: number): string {
    const date = new Date(timestamp);
    return `${date.getFullYear()}${(date.getMonth() + 1).toString().padStart(2, '0')}${date.getDate().toString().padStart(2, '0')}`;
  }
}

/**
 * 归档结果
 */
interface ArchiveResult {
  archivedFiles: number;
  savedSpace: number;
  errors: string[];
}

/**
 * 存储统计
 */
interface StorageStats {
  logDirSize: number;
  archiveDirSize: number;
  logFileCount: number;
  archiveFileCount: number;
}

3.4 完整UI示例

import { FileCompressor, CompressionStats } from './FileCompressor';
import { LogArchiver, ArchiveResult, StorageStats } from './LogArchiver';
import picker from '@ohos.file.picker';

@Entry
@Component
struct ZipDemoPage {
  @State message: string = '压缩解压演示';
  @State progress: string = '';
  @State stats: StorageStats | null = null;
  
  private compressor: FileCompressor = new FileCompressor();
  private archiver: LogArchiver = new LogArchiver(
    '/data/service/el2/base/hms/logs',
    '/data/service/el2/base/hms/logs/archive'
  );

  async aboutToAppear(): Promise<void> {
    this.updateStats();
  }

  /**
   * 更新统计信息
   */
  updateStats(): void {
    this.stats = this.archiver.getStorageStats();
  }

  /**
   * 选择文件并压缩
   */
  async selectAndCompress(): Promise<void> {
    // 创建文件选择器
    const documentPicker = new picker.DocumentViewPicker();
    
    try {
      const selectResult = await documentPicker.select({
        maxSelectNumber: 1
      });
      
      if (selectResult && selectResult.length > 0) {
        const srcPath = selectResult[0];
        const zipPath = `${srcPath}.zip`;
        
        this.progress = '压缩中...';
        await this.compressor.compressFile(srcPath, zipPath, 6);
        
        const stats = await this.compressor.getCompressionStats(srcPath, zipPath);
        this.message = `压缩完成!\n原始大小: ${this.formatSize(stats.originalSize)}\n压缩后: ${this.formatSize(stats.compressedSize)}\n压缩率: ${stats.compressionRatio}`;
        this.progress = '';
      }
    } catch (error) {
      this.message = `压缩失败: ${error.message}`;
      this.progress = '';
    }
  }

  /**
   * 执行日志归档
   */
  async doArchive(): Promise<void> {
    this.progress = '归档中...';
    
    try {
      const result: ArchiveResult = await this.archiver.archive();
      
      this.message = `归档完成!\n已归档: ${result.archivedFiles} 个文件\n释放空间: ${this.formatSize(result.savedSpace)}`;
      
      if (result.errors.length > 0) {
        this.message += `\n错误: ${result.errors.length}`;
      }
      
      this.updateStats();
    } catch (error) {
      this.message = `归档失败: ${error.message}`;
    } finally {
      this.progress = '';
    }
  }

  /**
   * 格式化文件大小
   */
  formatSize(bytes: number): string {
    if (bytes < 1024) {
      return `${bytes} B`;
    } else if (bytes < 1024 * 1024) {
      return `${(bytes / 1024).toFixed(2)} KB`;
    } else if (bytes < 1024 * 1024 * 1024) {
      return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
    } else {
      return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
    }
  }

  build() {
    Column() {
      // 标题
      Text(this.message)
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 20 })
        .textAlign(TextAlign.Center)

      // 进度提示
      if (this.progress) {
        Row() {
          LoadingProgress()
            .width(20)
            .height(20)
          Text(this.progress)
            .fontSize(14)
            .margin({ left: 10 })
        }
        .margin({ bottom: 20 })
      }

      // 统计信息
      if (this.stats) {
        Column() {
          Text('存储统计')
            .fontSize(16)
            .fontWeight(FontWeight.Medium)
            .margin({ bottom: 10 })

          Row() {
            Text(`日志文件: ${this.stats.logFileCount}`)
            Blank()
            Text(this.formatSize(this.stats.logDirSize))
          }
          .width('100%')
          .justifyContent(FlexAlign.SpaceBetween)

          Row() {
            Text(`归档文件: ${this.stats.archiveFileCount}`)
            Blank()
            Text(this.formatSize(this.stats.archiveDirSize))
          }
          .width('100%')
          .justifyContent(FlexAlign.SpaceBetween)
          .margin({ top: 5 })
        }
        .width('90%')
        .padding(15)
        .backgroundColor('#F0F0F0')
        .borderRadius(10)
        .margin({ bottom: 20 })
      }

      // 操作按钮
      Button('选择文件压缩')
        .width('80%')
        .onClick(() => this.selectAndCompress())

      Button('执行日志归档')
        .width('80%')
        .margin({ top: 15 })
        .onClick(() => this.doArchive())

      Button('清理过期归档')
        .width('80%')
        .margin({ top: 15 })
        .onClick(async () => {
          const count = await this.archiver.cleanupOldArchives(30);
          this.message = `已清理 ${count} 个过期归档`;
          this.updateStats();
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Start)
    .padding({ top: 50, left: 20, right: 20 })
  }
}

四、踩坑与注意事项

4.1 内存溢出问题

压缩大文件时,如果一次性读入内存可能导致OOM:

// 问题:大文件压缩可能内存溢出
// zlib.compressFile内部会分块处理,但仍有上限

// 解决方案:超大文件分块压缩
async function compressLargeFile(srcPath: string, destPath: string): Promise<void> {
  const CHUNK_SIZE = 10 * 1024 * 1024;  // 10MB分块
  const stat = fs.statSync(srcPath);
  
  if (stat.size > 100 * 1024 * 1024) {  // 超过100MB
    // 使用流式压缩(需要native实现)
    throw new Error('File too large, use streaming compression');
  }
  
  // 正常压缩
  await compressFile(srcPath, destPath);
}

4.2 文件名编码问题

Zip文件名默认使用系统编码,中文文件名可能乱码:

// 问题:中文文件名乱码
// Zip格式支持UTF-8文件名,但需要正确设置标志位

// HarmonyOS的zlib模块默认使用UTF-8,一般不会有问题
// 但解压第三方创建的zip时可能遇到GBK编码

// 解决方案:尝试多种编码
function detectFileNameEncoding(rawName: string): string {
  // 尝试UTF-8解码
  try {
    return decodeURIComponent(escape(rawName));
  } catch {
    // 可能是GBK编码,需要额外处理
    return rawName;  // 降级处理
  }
}

4.3 路径遍历攻击

解压恶意zip时,文件路径可能包含../,导致文件被写到预期目录外:

// 危险:直接使用zip中的路径
const destPath = path.join(destDir, entry.fileName);  // 可能是 /data/../sensitive

// 安全做法:校验路径
function sanitizePath(baseDir: string, relativePath: string): string {
  // 规范化路径
  const normalized = path.normalize(path.join(baseDir, relativePath));
  
  // 检查是否在目标目录内
  if (!normalized.startsWith(path.normalize(baseDir))) {
    throw new Error('Path traversal detected');
  }
  
  return normalized;
}

4.4 压缩率与速度权衡

不同类型文件的压缩效果差异巨大:

// 已压缩文件再压缩效果差,反而浪费时间
const alreadyCompressed = ['.jpg', '.png', '.mp4', '.zip', '.gz'];

function shouldCompress(fileName: string): boolean {
  const ext = fileName.split('.').pop()?.toLowerCase() ?? '';
  return !alreadyCompressed.includes(ext);
}

// 根据文件类型选择压缩策略
function getCompressionLevel(fileName: string): number {
  if (!shouldCompress(fileName)) {
    return 0;  // 不压缩,直接存储
  }
  
  // 文本文件压缩效果好,用高压缩率
  const textFiles = ['.txt', '.json', '.xml', '.log'];
  const ext = fileName.split('.').pop()?.toLowerCase() ?? '';
  
  return textFiles.includes(ext) ? 9 : 6;
}

4.5 并发压缩问题

多个压缩任务同时执行可能导致资源竞争:

// 问题:并发压缩占用大量CPU和内存
const files = [...];  // 100个文件
await Promise.all(files.map(f => compress(f)));  // 可能卡死

// 解决方案:限制并发数
async function compressWithLimit(files: string[], limit: number = 4): Promise<void> {
  const queue: string[] = [...files];
  const workers: Promise<void>[] = [];
  
  for (let i = 0; i < limit; i++) {
    workers.push((async () => {
      while (queue.length > 0) {
        const file = queue.shift();
        if (file) {
          await compress(file);
        }
      }
    })());
  }
  
  await Promise.all(workers);
}

五、HarmonyOS 6适配说明

5.1 API接口变化

HarmonyOS 6对zlib API进行了Promise化改造:

// HarmonyOS 5写法:回调模式
zlib.compressFile(srcPath, destPath, options, (errCode) => {
  if (errCode === 0) {
    console.info('Compress success');
  }
});

// HarmonyOS 6适配:Promise模式
try {
  await zlib.compressFile(srcPath, destPath, options);
  console.info('Compress success');
} catch (error) {
  console.error(`Compress failed: ${error.message}`);
}

5.2 新增流式压缩

HarmonyOS 6支持流式压缩,解决大文件内存问题:

// HarmonyOS 6新增:流式压缩
import zlib from '@ohos.zlib';

async function streamingCompress(srcPath: string, destPath: string): Promise<void> {
  // 创建压缩流
  const zipStream = zlib.createZipStream({
    level: 6,
    method: zlib.CompressMethod.DEFLATE
  });

  // 打开目标文件写入流
  const destFile = fs.openSync(destPath, fs.OpenMode.CREATE | fs.OpenMode.WRITE_ONLY);

  // 分块读取、压缩、写入
  const srcFile = fs.openSync(srcPath, fs.OpenMode.READ_ONLY);
  const CHUNK_SIZE = 1024 * 1024;  // 1MB
  const stat = fs.statSync(srcPath);
  
  for (let offset = 0; offset < stat.size; offset += CHUNK_SIZE) {
    const buffer = new ArrayBuffer(CHUNK_SIZE);
    const readLen = fs.readSync(srcFile.fd, buffer, { offset: 0, length: CHUNK_SIZE });
    
    // 压缩数据块
    const compressed = await zipStream.write(buffer.slice(0, readLen));
    
    // 写入目标文件
    fs.writeSync(destFile.fd, compressed);
  }

  // 完成压缩
  const finalData = await zipStream.end();
  fs.writeSync(destFile.fd, finalData);

  // 关闭文件
  fs.closeSync(srcFile);
  fs.closeSync(destFile);
}

5.3 压缩选项扩展

HarmonyOS 6新增更多压缩控制选项:

// HarmonyOS 6新增选项
const options: zlib.ZipOptions = {
  level: 6,
  memLevel: 8,
  strategy: zlib.CompressStrategy.DEFAULT_STRATEGY,
  
  // 新增选项
  windowBits: 15,           // 滑动窗口大小
  dictionary: undefined,    // 预定义字典(提升特定数据压缩率)
  checksum: true,           // 是否计算校验和
  progress: (current: number, total: number) => {
    console.info(`Progress: ${(current / total * 100).toFixed(1)}%`);
  }
};

5.4 多线程压缩

HarmonyOS 6支持多线程压缩,提升大文件压缩速度:

// HarmonyOS 6新增:多线程压缩
import zlib from '@ohos.zlib';

const options: zlib.ZipOptions = {
  level: 6,
  threads: 4,  // 使用4个线程并行压缩
  chunkSize: 256 * 1024  // 每个线程处理256KB
};

await zlib.compressFile(largeFilePath, destPath, options);

六、总结

维度 评价
学习难度 ⭐⭐⭐
使用频率 ⭐⭐⭐⭐
重要程度 ⭐⭐⭐⭐
调试难度 ⭐⭐

压缩解压是数据管理的必备技能。通过本文的学习,你应该掌握了:

核心收获

  1. Zip格式原理:文件结构、压缩算法、压缩级别选择
  2. 基础操作:单文件压缩解压、目录压缩、进度监控
  3. 实战应用:日志归档、批量导出、存储优化
  4. 安全防护:路径遍历检测、编码处理、并发控制

最佳实践建议

  • 根据文件类型选择压缩级别,已压缩文件用STORE模式
  • 大文件使用流式压缩,避免内存溢出
  • 解压前校验文件路径,防止路径遍历攻击
  • 限制并发压缩任务数,避免资源竞争
  • 定期清理过期归档,释放存储空间

压缩不是万能的——对于已经高度压缩的图片、视频文件,再压缩反而可能增大体积。记住:只压缩那些"压缩收益高"的文件,比如文本、日志、JSON数据等。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。