PHP读取ZIP中央目录 获取未压缩文件总大小(大文件内存优化)
无需加载整个大ZIP文件,计算未压缩文件总大小
针对30GB以上的ZIP文件,我们可以通过读取文件末尾64KB数据定位中央目录,从中提取每个文件的未压缩大小并求和,无需加载整个文件到内存。你已经完成了中央目录的定位,接下来只需要解析目录中的每个文件条目即可。
ZIP中央目录条目(32位)的关键结构
每个中央目录文件条目的签名是\x50\x4b\x01\x02,从签名开始计算,未压缩大小存储在第28-31字节(小端序4字节无符号整数)。
完整实现代码
首先补充extractLast64Kb函数,用于读取文件末尾64KB数据:
function extractLast64Kb(string $path): string { $handle = fopen($path, 'rb'); if (!$handle) { throw new Exception("无法打开文件: $path"); } // 移动到文件末尾前64KB的位置 $fileSize = filesize($path); $startPos = max(0, $fileSize - 65536); fseek($handle, $startPos); // 读取最后64KB数据 $data = fread($handle, 65536); fclose($handle); return $data; }
然后完善中央目录定位与解析逻辑,实现大小求和:
function findCentralDirectory(string $data): int|false { $offset = 0; while ($offset < 65536) { $pos = strpos($data, "\x50\x4b\x01\x02", $offset); if ($pos !== false) { return $pos; } $offset++; } return false; } function findEndCentralDirectory(string $data): int|false { $offset = 0; while ($offset < 65536) { $pos = strpos($data, "\x50\x4b\x05\x06", $offset); if ($pos !== false) { return $pos; } $offset++; } return false; } function calculateUncompressedTotal(string $path): int { $data = extractLast64Kb($path); $centralDirStart = findCentralDirectory($data); if ($centralDirStart === false) { throw new Exception("未找到ZIP中央目录"); } $endCentralDirPos = findEndCentralDirectory($data); if ($endCentralDirPos === false) { throw new Exception("未找到ZIP中央目录结束标记"); } // 截取完整的中央目录内容(从第一个条目开始到结束标记前) $centralDirectory = substr($data, $centralDirStart, $endCentralDirPos - $centralDirStart); $totalSize = 0; $currentOffset = 0; $centralDirLength = strlen($centralDirectory); // 遍历每个中央目录条目(32位条目最小长度为46字节) while ($currentOffset <= $centralDirLength - 46) { // 检查当前位置是否为有效条目签名 $signature = substr($centralDirectory, $currentOffset, 4); if ($signature !== "\x50\x4b\x01\x02") { break; } // 解析未压缩大小:偏移28字节,小端序4位无符号整数 $uncompressedSizeBytes = substr($centralDirectory, $currentOffset + 28, 4); $uncompressedSize = unpack('V', $uncompressedSizeBytes)[1]; $totalSize += $uncompressedSize; // 获取当前条目长度,跳转到下一个条目 $entryLengthBytes = substr($centralDirectory, $currentOffset + 42, 2); $entryLength = unpack('v', $entryLengthBytes)[1]; $currentOffset += $entryLength; } return $totalSize; } // 使用示例 try { $total = calculateUncompressedTotal('path/to/large.zip'); echo "未压缩总大小:" . number_format($total) . " 字节"; } catch (Exception $e) { echo "错误:" . $e->getMessage(); }
代码说明
extractLast64Kb:安全读取文件末尾64KB数据,避免加载大文件到内存- 解析逻辑:
- 定位中央目录的起始和结束位置
- 循环遍历每个条目,验证签名确保有效性
- 通过
unpack函数解析小端序字节数据,得到单个文件的未压缩大小 - 累加所有文件的未压缩大小,返回总和
- 仅处理32位ZIP格式,无需考虑64位扩展字段
内容的提问来源于stack exchange,提问作者zanderwar
相关产品推荐
相关产品推荐

