
一、前置思考1.1 大文件处理是内存杀手与卡顿之源视频播放、大文件上传下载、数据库备份恢复、离线地图包……这些场景都要面对几十 MB 到几个 GB 的文件。直接readAsArrayBuffer(整个文件)会把应用内存峰值拉爆、触发 GC 卡顿甚至 OOM。❌ 错误示范1: 读 500MB 文件到内存再解析 → 内存峰值 500MB, 进程可能被杀 ❌ 错误示范2: 下载中断后从头再来 → 弱网下永远下载不完 ❌ 错误示范3: 上传时分片太小/太大 → 太小: 请求数爆炸; 太大: 失败重传成本高 ❌ 错误示范4: 全部文件一次性 fs.write → 写缓存堆积, IO 抖动1.2 本文路线深入分片读写、mmap 大文件映射、断点续传架构、内存峰值管控四个方向给出企业级大文件处理方案。二、核心原理2.1 大文件读写的三条路径路径A: 一次性读入 (小文件适用) readAsArrayBuffer → 内存峰值 文件大小 路径B: 分片流式读写 (大文件主力) open(file, READ) → 循环 seek read(64KB) → 内存峰值 ≈ 分片大小, 恒定 路径C: mmap 内存映射 (随机访问场景) mmap(文件) → 按页访问文件内容 → 按需缺页加载, 随机访问高效路径内存峰值顺序读随机访问适用一次性读入文件大小快快 5MB分片流式分片大小快慢任意大小mmap页缓存共享快快大文件随机访问2.2 分片读写机制分片读: offset 0 while offset fileSize: chunk read(offset, 64KB) // 一次最多读 64KB process(chunk) offset 64KB 分片写: offset 0 while offset total: write(offset, chunkData) offset chunkData.length分片大小选择网络传输 1-4MB 片、本地读写 64KB-1MB 片。片太小 IO 调用次数多片太大单次失败重传成本高。2.3 断点续传原理上传断点续传: ① 分片: 文件切成 N 片 (每片带 index checksum) ② 服务端记录已收片 index ③ 断网/失败 → 查询已收片列表 ④ 只重传缺失片 → 续传完成 下载断点续传: ① 记录已下载字节数 (metadata 文件 / KV Store) ② 断网 → 从 offset 继续 HTTP Range 请求 ③ 校验续传后文件完整性 (整体 checksum)2.4 mmap 大文件映射mmap 将文件映射到进程地址空间访问哪个页才加载哪个页缺页中断mmap 好处: - 随机访问快 (省去 seek read 两次系统调用) - 多进程可共享同一映射 (共享读) - 内存按需加载, 峰值受控 mmap 注意: - 映射大文件占用地址空间 (64位下无碍) - 写映射需注意脏页回写时机 - 文件被外部截断/修改 → SIGBUS 风险三、源码/API 深度解析3.1 分片读取fs fileioimport{fileIoasfs}fromkit.CoreFileKit;constCHUNK_SIZE64*1024;// 64KBasyncfunctionreadLargeFile(path:string,onChunk:(data:ArrayBuffer,offset:number,isLast:boolean)void):Promisevoid{constfilefs.openSync(path,fs.OpenMode.READ_ONLY);try{conststatfs.statSync(path);letoffset0;while(offsetstat.size){constsizeMath.min(CHUNK_SIZE,stat.size-offset);constbufnewArrayBuffer(size);constreadLenfs.readSync(file.fd,buf,{offset:offset});onChunk(buf,offset,offsetreadLenstat.size);offsetreadLen;}}finally{fs.closeSync(file.fd);}}3.2 断点续传元数据import{preferences}fromkit.ArkData;interfaceTransferMeta{fileId:string;totalBytes:number;doneBytes:number;chunkIndex:number;// 下一个待传片 indexchunkSize:number;checksum:string;// 整体校验值}classTransferResume{privatestaticKEYtransfer_meta;staticasyncsave(pref:preferences.Preferences,meta:TransferMeta):Promisevoid{awaitpref.put(TransferResume.KEY,JSON.stringify(meta));awaitpref.flush();}staticasyncload(pref:preferences.Preferences):PromiseTransferMeta|null{constrawawaitpref.get(TransferResume.KEY,);returnraw?null:JSON.parse(raw)asTransferMeta;}}3.3 内存峰值管控对象池 复用缓冲classChunkPool{privatepool:ArrayBuffer[][];privatereadonlymax4;// 最多复用 4 个缓冲acquire(size:number):ArrayBuffer{for(leti0;ithis.pool.length;i){if(this.pool[i].byteLengthsize){returnthis.pool.splice(i,1)[0];}}returnnewArrayBuffer(size);}release(buf:ArrayBuffer):void{if(this.pool.lengththis.max){this.pool.push(buf);}}}四、企业级实战落地4.1 分片上传架构┌─────────┐ 分片(4MB) ┌──────────┐ 合并/校验 ┌────────┐ │ 客户端 │ ───────────→ │ 服务端 │ ───────────→ │ 对象存储 │ │ 断点记录 │ ←─────────── │ 已收片列表 │ └────────┘ └─────────┘ 缺失片重传 └──────────┘asyncfunctionuploadByChunks(filePath:string,fileId:string,api:(chunk:ArrayBuffer,index:number)Promiseboolean):Promisevoid{constfilefs.openSync(filePath,fs.OpenMode.READ_ONLY);conststatfs.statSync(filePath);constchunkSize4*1024*1024;// 4MB 片consttotalChunksMath.ceil(stat.size/chunkSize);// 从服务端查询已上传片 (模拟)constuploaded:SetnumberawaitqueryUploaded(fileId);for(leti0;itotalChunks;i){if(uploaded.has(i)){continue;}// 跳过已传片constsizeMath.min(chunkSize,stat.size-i*chunkSize);constbufnewArrayBuffer(size);fs.readSync(file.fd,buf,{offset:i*chunkSize});// 上传失败自动重试 (此处简化)awaitapi(buf,i);}fs.closeSync(file.fd);}4.2 分片下载 合并// 下载: 服务端返回文件总大小 → 分片 Range 拉取 → 逐片写入asyncfunctiondownloadWithRange(url:string,filePath:string):Promisevoid{consttotalawaitqueryFileSize(url);constfilefs.openSync(filePath,fs.OpenMode.READ_WRITE|fs.OpenMode.CREATE);letoffset0;while(offsettotal){constendMath.min(offset2*1024*1024-1,total-1);constdataawaithttpRangeGet(url,offset,end);// HTTP Rangefs.writeSync(file.fd,data,{offset:offset});offsetend1;}fs.closeSync(file.fd);}4.3 mmap 大文件随机读取日志/DB 分析// 场景: 分析 1GB 日志文件中指定偏移的内容// 方案: mmap 映射后按需读取, 内存峰值与访问区域成正比import{fileIoasfs}fromkit.CoreFileKit;asyncfunctionmmapRead(path:string,offset:number,length:number):PromiseArrayBuffer{constfilefs.openSync(path,fs.OpenMode.READ_ONLY);constmapfs.mmapSync(file.fd,{offset:0,length:fs.statSync(path).size,protection:1// PROT_READ});constoutnewArrayBuffer(length);newUint8Array(out).set(newUint8Array(map,offset,length));fs.munmapSync(map);fs.closeSync(file.fd);returnout;}4.4 内存峰值对比实测模拟方案文件大小内存峰值耗时一次性读入500MB500MB2.1s分片读取500MB64KB2.3smmap 随机读 10MB 区间500MB~10MB0.3s五、问题排查与性能优化坑现象原因解决OOM大文件读入内存一次性 readAsArrayBuffer分片流式下载永远失败弱网中断从头来无断点续传Range 元数据记录内存抖动处理时 GC 频繁每片新建缓冲对象池复用上传很慢分片过小请求爆炸片太小4MB 分片重传成本高大失败整片重传片太大适中分片 重试文件损坏续传后打不开无校验checksum 校验SIGBUSmmap 访问异常文件被截断捕获信号/长度校验5.1 分片大小调优场景推荐分片原因本地文件拷贝1MB平衡系统调用次数弱网上传512KB-1MB失败重传成本低高速上传4MB-8MB减少请求数内存受限设备64KB-256KB峰值控制5.2 传输调度优化并发度控制: 同时 2-3 个分片并发 (太多拥塞, 太少吞吐低) 失败重试: 指数退避 (1s → 2s → 4s → 上限30s) 进度持久化: 每完成一片写元数据 (Preferences/KV) 续传校验: 完成后整体 checksum, 不一致回退重下六、高阶总结与最佳实践永远不要整文件读入内存分片流式是处理任意大小文件的唯一正确姿势。断点续传三件套分片 index 进度元数据 checksum 校验。mmap 是大文件随机访问的利器按需加载峰值受控。对象池复用缓冲消除高频分片处理的 GC 抖动。分片大小看场景网络 1-4MB本地 64KB-1MB内存受限设备更小。一句话记住大文件三原则——读分片、写流式、传断点内存峰值永远等于分片大小而非文件大小。