我在 Flutter linux 上对其进行了测试,其中Uri.base 指向项目的根目录(带有pubspec.yaml、README.md 等的文件夹)-如果您在android / iOS 上运行它,请检查Uri.base 指向的位置和位置不好就换baseUri:
final debug = true;
final Set<Uri> foldersCreated = {};
final baseUri = Uri.base;
final baseUriLength = baseUri.toString().length;
final zipFileUri = baseUri.resolve('inputFolder/backup.zip');
final outputFolderUri = baseUri.resolve('outputFolder/');
print('0. files will be stored in [$outputFolderUri]');
final list = FileList(zipFileUri, debug: debug);
print('1. reading ZipDirectory...');
final directory = ZipDirectory.read(InputStream(list));
print('2. iterating over ZipDirectory file headers...');
for (final zfh in directory.fileHeaders) {
final zf = zfh.file;
final content = zf.content;
// writing file
final uri = outputFolderUri.resolve(zf.filename);
final folderUri = uri.resolve('.');
if (foldersCreated.add(folderUri)) {
if (debug) print(' #### creating folder [${folderUri.toString().substring(baseUriLength)}] #### ');
Directory.fromUri(folderUri).createSync(recursive: true);
}
File.fromUri(uri).writeAsBytesSync(content);
print("file: [${zf.filename}], compressed: ${zf.compressedSize}, uncompressed: ${zf.uncompressedSize}, length: ${content.length}");
}
list.close();
print('3. all done!');
这里有一个List,由一个LruMap 支持,它从你的巨大压缩文件中读取数据块:
class FileList with ListMixin<int> {
RandomAccessFile _file;
LruMap<int, List<int>> _cache;
final int maximumPages;
final int pageSize;
final bool debug;
FileList(Uri uri, {
this.pageSize = 1024, // 1024 is just for tests: make it bigger (1024 * 1024 for example) for normal use
this.maximumPages = 4, // maybe even 2 is good enough?
this.debug = false,
}) {
_file = File.fromUri(uri).openSync();
length = _file.lengthSync();
_cache = LruMap(maximumSize: maximumPages);
}
void close() => _file.closeSync();
@override
int length;
int minIndex = -1;
int maxIndex = -1;
List<int> page;
@override
int operator [](int index) {
// print(index);
// 1st cache level
if (index >= minIndex && index < maxIndex) {
return page[index - minIndex];
}
// 2nd cache level
int key = index ~/ pageSize;
final pagePosition = key * pageSize;
page = _cache.putIfAbsent(key, () {
if (debug) print(' #### reading page #$key (position $pagePosition) #### ');
_file.setPositionSync(pagePosition);
return _file.readSync(pageSize);
});
minIndex = pagePosition;
maxIndex = pagePosition + pageSize;
return page[index - pagePosition];
}
@override
void operator []=(int index, int value) => null;
}
您可以使用pageSize 和maximumPages 来找到最佳解决方案 - 我认为您可以从pageSize: 1024 * 1024 和maximumPages: 4 开始,但您必须自己检查它
当然,所有这些代码都应该在 Isolate 中运行,因为解压缩几个 GB 需要很长时间,然后您的 UI 会冻结,但首先按原样运行它并查看日志
编辑
似乎ZipFile.content 有一些内存泄漏,因此替代方案可能是基于“tar 文件”的解决方案,它使用tar 包,并且由于它读取Stream 作为输入,您可以使用压缩的*.tar.gz文件(您的 Documents.tar 有 17408 字节,而 Documents.tar.gz 有 993 字节),请注意,您甚至可以直接从套接字的 stream 读取数据,因此不需要任何中间 .tar.gz 文件:
final baseUri = Uri.base;
final tarFileUri = baseUri.resolve('inputFolder/Documents.tar.gz');
final outputFolderUri = baseUri.resolve('outputFolder/');
print('0. files will be stored in [$outputFolderUri]');
final stream = File.fromUri(tarFileUri)
.openRead()
.transform(gzip.decoder);
final reader = TarReader(stream);
print('1. iterating over tar stream...');
while (await reader.moveNext()) {
final entry = reader.current;
if (entry.type == TypeFlag.dir) {
print("dir: [${entry.name}]");
final folderUri = outputFolderUri.resolve(entry.name);
await Directory.fromUri(folderUri).create(recursive: true);
}
if (entry.type == TypeFlag.reg) {
print("file: [${entry.name}], size: ${entry.size}");
final uri = outputFolderUri.resolve(entry.name);
await entry.contents.pipe(File.fromUri(uri).openWrite());
}
}
print('2. all done!');