【问题标题】:Turning a Folder Into a Single File Without Zipping it?将文件夹变成单个文件而不压缩它?
【发布时间】:2015-02-01 18:05:15
【问题描述】:

因此,我的目标是创建一个自包含文件,其中包含要加载到 3D 场景中的所有资产。例如,会有一个文本文件详细说明模型的所有位置、旋转、比例等,然后是其中包含模型和纹理的文件夹。这在文件夹中并不难,但为了简单起见,我想将它们全部放入一个文件中,避免压缩和解压缩。我不想压缩和解压它的原因是为了加快加载时间,因为这是为了游戏。

有没有办法做到这一点?还是我必须硬着头皮把它做成拉链。

谢谢。

【问题讨论】:

  • a) Zip 不需要压缩。 b) Zip、7z、Tar、Bzip2 等。有很多这样的格式。 c) 您可以随时制作自己的二进制文件格式。 d) 除了 b 中提到的一般存档格式之外,还有一些已建立的专用 3D 模型文件格式(以及保存/加载它们的库)...
  • 无论您使用什么,如果您将按需加载资源,那么您需要一种具有索引的存档格式。邮编可以;焦油没有。这意味着使用 tar 您必须四处寻找才能发现您拥有哪些资源以及它们在哪里。
  • @cdhowie 根据大家所说的,我想我会使用低压缩的 zip。
  • 最好使用最大压缩或不压缩。您甚至可以为不同的文件单独选择,具体取决于您使用它们的方式。

标签: c++ file directory archive


【解决方案1】:

您可以创建不压缩的 ZIP 文件。 (“存储”模式。)也有类似的打包归档器可用于从多个文件创建单个归档。 *nix 系统上的 TAR 文件为此很受欢迎,还有许多其他文件。

此外,平衡解压缩所花费的时间与从磁盘加载(未压缩)文件所花费的时间。很多时候,未压缩文件的磁盘读取时间比加载压缩文件并在内存中解压缩所花费的时间要长。

【讨论】:

  • 您的回答是正确的,但据我了解,BlueSpud 似乎需要更像文件格式的东西。
  • 同意。我的观点是,继续使用 ZIP 文件(带有压缩)可能最终会因为磁盘 I/O 减少而节省时间。
  • @AdrianMaire 如果解压缩它的速度和文件夹一样快,它会很好地满足我的需求。
【解决方案2】:

您可以使用没有压缩或 TAR 的 ZIP。但这里有一个更好的主意:压缩它。 CPU时间很便宜。磁盘传输需要很长时间。大多数情况下,加载压缩数据并解压它比加载未压缩数据要快。

【讨论】:

  • 所以你是说加载压缩文件然后解压缩它会比不压缩它更快?
  • 是的,一些快速算法是正确的,特别是对于压缩比非常高的 3D 模型。
  • @BlueSpud 取决于您的 HDD 和 CPU,可能(正如 AdrianMaire 所说,压缩算法)。较慢的 HDD 和较快的 CPU 将增加收益。另一方面,SSD 和 Raspi...
  • @AdrianMaire 对具体算法有什么建议吗?我对文件压缩知之甚少。
  • @BlueSpud:不,我不知道,但大多数库允许不同级别的压缩,在无压缩后取较低的。但也要考虑 .3ds、.obj 等文件格式。它们允许您直接从 3D 建模程序导入 3D 模型(这是一个很大的好处)。他们中的大多数都提供库来打开它们。
【解决方案3】:

如果这是针对 Windows 应用程序,您可以在 .RC 文件中为您的 EXE 或包含的 DLL 使用用户定义的资源语句,将外部文件中的数据作为二进制资源插入。如果您只想分发依赖于其他文件中包含的数据的单个 EXE 文件,或者不想让您的应用程序归档和分发的文件可通过以下方式访问/修改(无需一些工作),这将特别方便最终用户。

非常简单——语法很简单

nameID typeID filename

用于资源文件。您可以使用::FindResource()::LoadResource(). 访问数据,用户定义资源的MS 文档位于http://msdn.microsoft.com/en-us/library/windows/desktop/aa381054(v=vs.85).aspx

【讨论】:

  • 不幸的是,这是在 Linux Mac 和 windows 中,所以我有一个项目,不想制作 3 个项目来更改每个项目。
【解决方案4】:

原始问题的时间安排可能是偶然的。事实证明,将来我建议的查找/加载资源方法也不会成为我们的选择(我之前使用的代码是跨平台的),所以我想出了一个解决方案为你工作。完整的代码作为 SO 答案发布有点多(但请参阅我的后续评论),尽管您可能很快就可以制作出自己的快速版本。

基本上,我编写了一个实用程序,它只将输出文件的基本名称作为argv[1] ("output-base-name"),而argv[2...(argc-1)] 是输入文件。我打开output-base-name.cppoutput-base-name.h 进行写入(如果存在,将覆盖)。

对于每个输入文件,生成器实用程序都会这样做:

1:根据文件名(“generated_name”)生成一个变量名友好的标识符
2:读取输入文件
3:将以下内容转储到output-base-name.cpp

static uint8_t generated_name[] = {
    // Initialize with contents of input file as a bunch of 0xNN, 0xNN... values.
};

并入output-base-name.h,对应

static const unsigned IDR_generated_name = N; // start at 0, see below re:range check

4:在最后一个输入文件之后,结束 output-base-name.cpp 类似

static struct {
    unsigned       ID;
    unsigned       size;  // I assume you aren't trying to embed files >4GB!
    const uint8_t* data;
} 
fileData[] = {
   // one array element for each input file
   { IDR_generated_name,  sizeof(generated_name), generated_name },
   ... 
};

const uint8_t* getResourceData(unsigned ID){
    // a real version would do a safer lookup/range check of some sort based on ID
    return fileData[ID].data;
}

unsigned getResourceSize(unsigned ID){
    // a real version would do a safer lookup/range check of some sort based on ID
    return fileData[ID].size;
}

5:用原型完成 output-base-name.h,无论你的“get”函数是什么样的,例如

extern const uint8_t* getResourceData(unsigned ID);
extern unsigned getResourceSize(unsigned ID);

只需在需要访问数据的任何地方使用output-base-name.h 并将output-base-name.cpp 添加到您的makefile 或项目(使其依赖于二进制输入文件,使用构建规则中的生成器实用程序,make 将自动重新生成必要时为您服务)。如果您希望它们在某种动态库中,只需将适当的导出属性添加到您的“get”函数。

让生成器将你的输出包装在一个命名空间中,如果你愿意,可以将一个基于类的接口组合在一起(或者生成一个而不是示例“get”函数),你应该很好。

与使用未压缩的 zip 文件(甚至我建议用于 Windows 目标的嵌入式资源方法)相比,这种方法有几个优点:

  1. 在应用程序代码中处理起来要简单得多 - 无需读取 zip 目录、提取文件数据等。
  2. 不增加对其他第三方库的依赖来查找/提取文件数据(假设您不滚动自己的 zip 库,这将是更多工作)
  3. 所有解析和查找工作都已为您完成 - 如果将数据粘贴到静态库或可执行文件中,链接器已经解析了每个字节数组的地址,如果是共享库或 DLL,则由 OS 加载器解析
  4. 您知道数据将存在或您的二进制文件无法运行(使用 zip lib 方法,甚至 Windows 上的 Find/LoadResource 都不是这种情况)
  5. 与 zip 文件相比,Joe 用户更难以访问数据(因此更安全)
  6. 应该比 zip 或 Windows 资源方法更快,因为在应用程序的加载过程中预先解析了数据地址
  7. 它还可以用于任何形式的相对静态数据,您希望随应用程序一起提供(或提供给)应用程序 - 只需使用生成器将文件打包到共享/动态库中并使用生成的导出访问器函数即可。您甚至可以稍后(在编译/发布时间之后)创建更新的或全新的数据包,并使用配置选项在运行时加载。
  8. 如有必要,它将在较旧的 C++ 编译器中工作(或者,不生成任何命名空间或类包装器,甚至是普通的旧 C)

这只是我使用的方法的一个示例,但它涵盖了所有基础知识,因此,如果早点而不是晚点(再次请参阅评论)解决方案对您来说更好,这可能就足够了(您想要范围检查“get”函数中的值 - 至少在调试版本中,可能使用某种容器而不是使用 c 样式的结构数组等)。

【讨论】:

  • 回答中引用的后续评论(第 1 部分):尽管未在回答中发布完整的源代码(由于长度和元帖子here 中讨论的其他原因),但我几乎完成了将作为开源发布的完整实现。我将出城几天,但我会清理、完成并为我的快速和肮脏/概念验证版本添加更多功能,并在我回来时将其发布在我的博客上。
  • 后续第 2 部分(对于一个来说太长了):当我发布它时,我将删除这些评论并在答案中添加指向源位置的链接,所以如果有人感兴趣完整来源,只需“收藏”原始问题,然后当我发布链接时(可能一周左右),您个人资料中的收藏选项卡就会突出显示。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-10-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多