4

我正在尝试在 iPhone/iPad 应用程序中使用 LZMA SDK,我的出发点是 Mo Dejong 提供的 iPhone 的 LZMA 示例项目,可在此处获得: https ://github.com/jk/lzmaSDK 原文在这里:http: //www.modejong.com/iOS/lzmaSDK.zip (我都试过了,我都得到了相同的结果)。

问题是提取使用的内存与 .7z 包含的未压缩内存一样多。换句话说,假设我有一个 40MB 的压缩文件,未压缩的文件是一个大约 250MB 的二进制 sqlite DB,它会慢慢地使用越来越多的内存,因为它将文件一直解压缩到 250MB。这将使 iPad1 或 iPhone4 之前的任何设备(256MB RAM)崩溃。我感觉很多人最终都会遇到同样的问题,所以现在的解决方案可以帮助很多开发人员。

我最初使用基于 Windows 的 7-zip(最新版本)和 16MB 字典大小在 PC 上创建了 .7z 文件。它应该只需要 18MB 的 RAM 来解压缩(在查看任务管理器的 PC 上进行测试时就是这种情况)。我还尝试使用 keka(开源 mac 存档器)创建存档,但它没有解决任何问题,尽管我可以确认 keka 本身在 mac 上提取文件期间仅使用 19MB 的内存,这是我所期望的。我猜下一步是比较 Keka 的源代码和 LZMA SDK 的源代码。

创建 .7z 文件时,我尝试了不同的字典大小和其他设置,但没有任何帮助。我还尝试在压缩之前将我的单个二进制文件分成 24 个较小的部分,但这也没有帮助(仍然使用超过 250MB 的 RAM 来提取 24 个部分)。

请注意,我对原始代码所做的唯一更改是使用更大的 .7z 文件。另请注意,提取完成后它会立即释放 RAM,但这无济于事。我觉得它并没有像提取应有的那样释放 RAM,或者它正在将整个内容放入 RAM 直到完成,然后才将其移出 RAM。此外,如果我尝试使用 mac 应用程序提取相同的确切文件,同时运行仪器,我看不到相同的行为(例如,在提取文件时,StuffIt Expander 的 RAM 最大约为 60MB,Keka,开源 mac存档器最大内存为 19MB)。

我不是一个 mac/xcode/objective-c 开发人员(还)所以任何帮助都将不胜感激。我可以改用 zip 或 rar,但我使用 LZMA 获得了更出色的压缩,所以如果可能的话,我想坚持使用这个解决方案,但显然我需要让它在不崩溃的情况下工作。

谢谢!

Instruments.app 分析示例应用程序的屏幕截图

4

3 回答 3

1

7zip 的作者 Igor Pavlov 给我发了电子邮件,他基本上说我在原始问题中所做的观察是 SDK 的 c 版本的已知限制。C++ 版本没有这个限制。实际报价:

“7-Zip 使用另一个用 C++ 编写的多线程解码器。该 C++ .7z 解码器不需要为整个实体块分配 RAM 块。另请阅读此线程:

http://sourceforge.net/projects/sevenzip/forums/forum/45797/topic/5655623

因此,在有人修复适用于 iOS 的 SDK 之前,解决方法是:

1) 确定文件解压缩操作所需的 RAM 限制。

2) 存档中超出上述 1 限制的任何 SINGLE 文件必须拆分,您可以使用任何二进制拆分器应用程序执行此操作,例如拆分: http ://www.fourmilab.ch/splits/

3)文件准备好后,使用MoDJ在他的回答中描述的字典/块大小选项创建7z文件,例如24兆限制:7za a -mx=9 -md=24m -ms=24m CompressedFile。 7z 源文件*

4) 在您的 iOS 应用程序中,解压缩文件后,确定哪些文件已被拆分,然后将它们重新连接在一起。代码并不复杂(我假设 splits.exe 使用的命名约定是 file.001、file.002 等)

    if(iParts>1)
    {
        //If this is a multipart binary split file, we must combine all of the parts before we can use it
        NSString *finalfilePath = whateveryourfinaldestinationfilenameis
        NSString *splitfilePath = [finalfilePath stringByAppendingString:@".001"];

        NSFileHandle *myHandle;
        NSFileManager *fileManager = [NSFileManager defaultManager];
        NSError *error;

        //If the target combined file exists already, remove it
        if ([fileManager fileExistsAtPath:finalfilePath]) 
        {
            BOOL success = [fileManager removeItemAtPath:finalfilePath error:&error];
            if (!success) NSLog(@"Error: %@", [error localizedDescription]);
        }

        myHandle  = [NSFileHandle fileHandleForUpdatingAtPath:splitfilePath];
        NSString *nextPart;
        //Concatenate each piece in order
        for (int i=2; i<=iParts; i++) {
            //Assumes fewer than 100 pieces
            if (i<10) nextPart = [splitfilePath stringByReplacingOccurrencesOfString:@".001" withString:[NSString stringWithFormat:@".00%d", i]];
            else nextPart = [splitfilePath stringByReplacingOccurrencesOfString:@".001" withString:[NSString stringWithFormat:@".0%d", i]];
            NSData *datapart = [[NSData alloc] initWithContentsOfFile:(NSString *)nextPart];
            [myHandle seekToEndOfFile];
            [myHandle writeData:datapart];
        }    
        [myHandle closeFile];
        //Rename concatenated file
        [fileManager moveItemAtPath:splitfilePath toPath:finalfilePath error:&error];
    }
于 2012-10-01T06:38:13.750 回答
0

好的,所以这是一个棘手的问题。您遇到问题的原因是因为 iOS 没有虚拟内存,而您的桌面系统有。lzmaSDK 库的编写方式假定您的系统有足够的虚拟内存用于解压缩。您不会看到在桌面上运行的问题。只有在 iOS 上分配大量内存进行解压时才会遇到问题。最好通过重写 lzma SDK 来解决这个问题,以便直接更好地利用映射内存,但这不是一项简单的任务。以下是解决此问题的方法。

使用7za

实际上,您需要将 2 个命令行选项传递给 7zip 存档程序,以便将文件分割成更小的块。我将建议您只使用我最终使用的 24 兆大小,因为这是一个不错的空间/内存权衡。这是应该可以解决问题的命令行,请注意,在此示例中,我有名为 XYZ.flat 的大型电影文件,然后我想将它们一起压缩到一个 archive.7z 文件中:

7za a -mx=9 -md=24m -ms=24m Animations_9_24m_NOTSOLID.7z *.flat

如果将此分段文件与不将文件分成分段的版本进行比较,您会看到分段后文件变大了一点:

$ ls -la Animations_9_24m.7z Animations_9_24m_NOTSOLID.7z
-rw-r--r--  1 mo  staff  8743171 Sep 30 03:01 Animations_9_24m.7z
-rw-r--r--  1 mo  staff  9515686 Sep 30 03:21 Animations_9_24m_NOTSOLID.7z

因此,分段减少了大约 800K 的压缩,但损失并不大,因为现在解压缩例程不会尝试分配一堆内存。解压内存使用现在限制为 24 兆块,iOS 可以处理。

通过打印出压缩文件的标题信息来仔细检查您的结果:

$ 7za l -slt Animations_9_24m_NOTSOLID.7z

Path = Animations_9_24m_NOTSOLID.7z
Type = 7z
Method = LZMA
Solid = +
Blocks = 7
Physical Size = 9515686
Headers Size = 1714

请注意上述输出中的“Blocks”元素,它表示数据已被分割成不同的 24 meg 块。

如果您将上面的分段文件信息与不带 -ms=24m 参数的输出进行比较,您会看到:

$ 7za l -slt Animations_9_24m.7z

Path = Animations_9_24m.7z
Type = 7z
Method = LZMA
Solid = +
Blocks = 1
Physical Size = 8743171
Headers Size = 1683

请注意“块”值,您不希望只有 1 个大块,因为在 iOS 上解压缩时会尝试分配大量内存。

于 2012-09-30T10:51:04.593 回答
0

我遇到了同样的问题,但找到了一个更实用的解决方法:

  • 使用 LZMA SDK 的 CPP 接口。它只使用很少的内存,并且不会像 C 接口那样遭受内存消耗问题(正如 tradergordo 已经正确说过的那样)。

  • 看看 LZMAAlone.cpp,去掉任何不必要的东西(比如编码、7-zip 文件格式的东西,顺便说一句。编码仍然需要大内存)并为你的 CPP LZMA 解压缩器创建一个小头文件,例如:

extern "C" int extractLZMAFile(const char *filePath, const char *outPath);

  • 对于非常大的文件(如 100MB+ db 文件),然后我使用 LZMA 解压缩来压缩此文件。当然,由于单独的LZMA没有任何文件容器,所以需要给出解压文件的名称

  • 因为我没有完整的 7Z 支持,所以我使用 tar 作为容器以及 lzma 压缩文件。在https://github.com/mhausherr/Light-Untar-for-iOS有一个很小的 ​​iOS 解压器

不幸的是,即使我愿意,我也无法提供任何来源。

于 2013-03-02T15:00:17.880 回答