如何用PHP提取Java RandomAccessFile创建的归档文件?
PHP归档提取逻辑实现与内存溢出解决方案
一、归档文件格式分析(从Java代码逆向)
从提供的Java归档生成代码,可以拆解出归档的结构规则:
- 文件头:前4字节为魔数
{1,3,3,7}(对应十进制值16974599) - 文件计数:紧随魔数的4字节,存储归档内的文件总数(初始创建时为0)
- 单个文件结构:
- 4字节:存储文件的字节大小(int类型)
- 文件名:ASCII编码的字符串,以
0字节作为结束标记 - 文件内容:对应大小的原始字节数据
二、PHP提取逻辑实现(流式处理防内存溢出)
放弃一次性读取整个文件的方式,改用流式分块读写,避免将大文件全部加载到内存中。以下是实现代码:
function extractArchive(string $archivePath, string $outputDir): void { // 创建输出目录(递归创建父目录) if (!is_dir($outputDir)) { mkdir($outputDir, 0755, true); } $archiveHandle = fopen($archivePath, 'rb'); if (!$archiveHandle) { throw new Exception('无法打开归档文件'); } // 验证归档魔数 $magicBytes = fread($archiveHandle, 4); $magicNumber = unpack('N', $magicBytes)[1]; if ($magicNumber !== 16974599) { fclose($archiveHandle); throw new Exception('无效的归档文件格式'); } // 读取归档内的文件总数 $fileCount = unpack('N', fread($archiveHandle, 4))[1]; for ($i = 0; $i < $fileCount; $i++) { // 读取当前文件的大小 $fileSize = unpack('N', fread($archiveHandle, 4))[1]; // 读取文件名(直到遇到终止符\x00) $fileName = ''; while (($byte = fread($archiveHandle, 1)) !== "\x00") { $fileName .= $byte; } // 准备输出文件路径 $outputFile = rtrim($outputDir, '/') . '/' . $fileName; $fileHandle = fopen($outputFile, 'wb'); if (!$fileHandle) { fclose($archiveHandle); throw new Exception('无法创建输出文件:' . $fileName); } // 分块读取并写入文件内容(每次处理4KB,可根据需求调整) $bytesProcessed = 0; $chunkSize = 4096; while ($bytesProcessed < $fileSize) { $readLength = min($chunkSize, $fileSize - $bytesProcessed); $contentChunk = fread($archiveHandle, $readLength); fwrite($fileHandle, $contentChunk); $bytesProcessed += $readLength; } fclose($fileHandle); } fclose($archiveHandle); }
三、内存溢出问题的核心解决思路
- 避免全量加载文件:之前用
fread()一次性读取整个文件内容,会将大文件全部存入内存,触发溢出。流式分块处理每次仅加载少量数据,内存占用始终保持在低水平。 - 拒绝依赖内存限制调整:即使临时调高内存到512MB,遇到更大的归档文件仍会溢出,流式处理才是从根源解决问题的方案。
内容的提问来源于stack exchange,提问作者TKH
相关产品推荐
相关产品推荐

