☰
torch7 DiskFile 完全指南:磁盘文件读写、字节序控制与序列化实战
2026/9/25 5:49:36 网站建设 项目流程
  • 深度学习

【免费下载链接】torch7

http://torch.ch

项目地址:https://gitcode.com/gh_mirrors/to/torch7
点击查看免费下载

导读:DiskFile是 torch7 中负责把数据读写到磁盘文件的File实现,它继承了 File 的全部能力(ASCII/二进制模式、类型化读写、对象序列化),并额外提供字节序(大小端)控制、长整型宽度定制与缓冲控制等专属接口。本文以 doc/diskfile.md 为核心,结合 lib/TH/THDiskFile.c、DiskFile.c 与 test/longSize.lua 等源码与测试,讲清构造参数、模式选择、字节序 API、longSize 与 noBuffer 的底层原理,并给出可复制的实战示例,让读者能写出跨平台、可回读、高性能的磁盘数据存取代码。

DiskFile 是什么

DiskFile是 File 这个抽象类的一个具体子类,专门用于对磁盘上的文件执行基本的读写操作。它实现了 File 中定义的所有方法,并额外增加了一批与字节序(endian)编码相关的接口。

在 torch7 的 C 层,这一关系通过“虚表(vtable)”机制体现:基础读写能力定义在struct THFileVTable(见 lib/TH/THFilePrivate.h)中,而 lib/TH/THDiskFile.c 中THDiskFile_new用一套完整的THDiskFile_read* / THDiskFile_write*函数填充这张表,构成磁盘文件的具体实现。

一个THDiskFile对象在内存中的结构非常简单(见 lib/TH/THDiskFile.c):

typedef struct THDiskFile__ { THFile file; // 基类字段:读写标志、quiet、binary、autoSpacing 等 FILE *handle; // C 标准库文件句柄 char *name; // 磁盘文件名 int isNativeEncoding; // 当前是否使用本机字节序编码 int longSize; // long 类型的字节宽度(0/4/8) } THDiskFile;

在 Lua 侧,torch7 通过 DiskFile.c 中torch_DiskFile_init注册torch.DiskFile元表,并声明其父类为torch.File(见 DiskFile.c)。因此用print(file)打印一个 DiskFile 时,会得到形如:

torch.DiskFile on <foo.dat> [status: open -- mode r ]

这样的描述字符串,其中status为open/closed,mode显示当前是只读(r)、只写(w)还是读写(rw)——具体格式化逻辑见 DiskFile.c。

默认行为

  • 默认情况下,DiskFile处于 ASCII 模式。
  • 若切换到 binary 模式,默认的字节序编码是本机(native)字节序。

这两个默认值在THDiskFile_new中直接初始化:self->file.isBinary = 0; self->isNativeEncoding = 1;(见 lib/TH/THDiskFile.c)。

构造函数与打开模式

torch.DiskFile(fileName, [mode], [quiet])

打开磁盘上的fileName,使用给定的mode打开文件。参数说明:

参数类型默认值说明
fileNamestring必填磁盘文件的路径名
modestring"r""r"(只读)、"w"(只写)、"rw"(读写)
quietbooleanfalse仅当为true时,打开失败不抛 Lua 错误,而是返回nil

Lua 绑定侧的实现(DiskFile.c)验证了参数默认值:

static int torch_DiskFile_new(lua_State *L) { const char *name = luaL_checkstring(L, 1); // 必填 const char *mode = luaL_optstring(L, 2, "r"); // 默认 "r" int isQuiet = luaT_optboolean(L, 3, 0); // 默认 false THFile *self = THDiskFile_new(name, mode, isQuiet); luaT_pushudata(L, self, "torch.DiskFile"); return 1; }

三种模式的行为细节

  • "r"(只读):打开已存在的文件用于读取。底层调用fopen(name, "rb")。
  • "w"(只写):创建/覆盖文件用于写入。底层调用fopen(name, "wb")。
  • "rw"(读写):若文件不存在,会自动创建;若文件已存在,打开后指针定位在文件开头。底层逻辑(lib/TH/THDiskFile.c)先尝试fopen(name, "r+b"),失败则先fopen(name, "wb")创建、关闭,再以"r+b"重新打开。

模式字符串的合法性由THDiskFile_mode校验:只接受长度 1 的"r"/"w",或长度 2 的"rw",其他取值会在THDiskFile_new中抛出参数错误"file mode should be 'r','w' or 'rw'"(见 lib/TH/THDiskFile.c、lib/TH/THDiskFile.c)。

quiet 参数:优雅处理打开失败

若quiet为true,文件打开失败(例如只读模式下文件不存在)时不会抛出 Lua 错误,而是返回nil,方便调用方自行判断:

f = torch.DiskFile('maybe_not_exist.dat', 'r', true) if not f then print('无法打开文件,继续降级处理') end

对应 C 实现:if(!handle) { if(isQuiet) return 0; else THError(...); }(见 lib/TH/THDiskFile.c)。注意,quiet只影响“打开”这一步的错误处理;打开之后的读写错误行为由 File 基类的 quiet()/pedantic() 方法控制。

读写模式:ASCII 与二进制

DiskFile继承自 File 的类型化读写方法,包括readByte/readChar/readShort/readInt/readLong/readFloat/readDouble/readBool与对应的write*系列。这些方法受两种编码模式影响:

  • ascii()(默认):数字以人类可读的字符形式存储,布尔值写作0/1。结合默认的 autoSpacing(),每个数字/布尔值后自动追加空格,每次 write 调用后追加换行;可通过 noAutoSpacing() 关闭。
  • binary():数字直接以内存中的寄存器表示写入,不可读但明显更快;字节序由 DiskFile 的专属接口控制(见下节)。

在 C 层,这两种模式的分支集中在THDiskFile_read*/THDiskFile_write*的isBinary判断中。以读取为例(lib/TH/THDiskFile.c):

if(dfself->file.isBinary) { nread = fread__(data, sizeof(TYPE), n, dfself->handle); if(!dfself->isNativeEncoding && (sizeof(TYPE) > 1) && (nread > 0)) THDiskFile_reverseMemory(data, data, sizeof(TYPE), nread); } else { // ASCII 模式:逐元素 fscanf,如 %hd/%d/%g/%lg ... }

各类型在 ASCII 模式下使用的格式化串(lib/TH/THDiskFile.c):

类型读取写入
Short%hd%hd
Int%d%d
Float%g%.9g
Half%g(经TH_float2half转换)%.9g(经TH_half2float转换)
Double%lg%.17g
Long%ld%ld

可见 ASCII 模式特意保留了浮点的高精度表示(%.9g/%.17g),保证读写往返不损失有效数字;而Byte/Char在两种模式下都直接按单字节fread/fwrite处理(见 lib/TH/THDiskFile.c)。

完整示例:ASCII 模式读写

-- 写 f = torch.DiskFile('data.asc', 'w') -- 默认 ASCII 模式 f:writeInt(42) f:writeDouble(3.14159265358979) f:writeString('hello torch') f:close() -- 确保数据落盘 -- 读(假设文件从开头读起) f = torch.DiskFile('data.asc', 'r') local i = f:readInt() --> 42 local d = f:readDouble() --> 3.14159265358979 local s = f:readString('*l') --> "hello torch"(不含行尾符) f:close()

readString/writeString的格式语义同样继承自 File:"*l"读取下一行(跳过行尾符),"*a"读取文件剩余全部内容;由于 torch 对数字类型有更精确的区分,Lua 的"*n"格式不被支持,请改用数字读取方法。

字节序控制:跨平台数据交换的关键

二进制模式下,多字节数值在磁盘上的排列顺序由字节序决定。torch7 为DiskFile提供了 4 个专属接口:

nativeEndianEncoding()

在 binary 模式下,强制使用本机字节序编码(即不进行任何字节交换)。这也是打开文件后的默认状态。

C 实现只是把标志位置 1(lib/TH/THDiskFile.c):

void THDiskFile_nativeEndianEncoding(THFile *self) { ... dfself->isNativeEncoding = 1; }

littleEndianEncoding()

在二进制模式下,强制使用小端序(little end)编码:数值从最低有效字节到最高有效字节,随内存地址递增存储。

void THDiskFile_littleEndianEncoding(THFile *self) { ... dfself->isNativeEncoding = THDiskFile_isLittleEndianCPU(); }

注意其巧妙之处:isNativeEncoding的语义是“写入时是否做字节反转”。若本机恰为小端机,小端编码即为本机编码,isNativeEncoding = 1;若本机为大端机,则置为 0,读写时对每个多字节元素执行反转。

bigEndianEncoding()

在二进制模式下,强制使用大端序(big end)编码:数值从最高有效字节到最低有效字节,随内存地址递增存储(数值重要性随地址增大而递减)。实现与上面对称:

dfself->isNativeEncoding = !THDiskFile_isLittleEndianCPU();

isBigEndianCPU() / isLittleEndianCPU()

返回true当且仅当当前 CPU 采用对应字节序。底层检测非常直观:在栈上放一个int x = 7,检查其首字节是否为 0(见 lib/TH/THDiskFile.c):

int THDiskFile_isLittleEndianCPU(void) { int x = 7; char *ptr = (char *)&x; if(ptr[0] == 0) // 首字节是高字节 → 大端 return 0; else // 首字节是低字节 → 小端 return 1; }

字节反转的底层实现

当isNativeEncoding == 0且元素宽度大于 1 字节时,读写都会调用THDiskFile_reverseMemory,对每个元素按字节块做头尾交换(lib/TH/THDiskFile.c):

static void THDiskFile_reverseMemory(void *dst, const void *src, size_t blockSize, size_t numBlocks) { if(blockSize > 1) { size_t halfBlockSize = blockSize/2; ... for(b = 0; b < numBlocks; b++) for(i = 0; i < halfBlockSize; i++) { char z = charSrc[i]; charDst[i] = charSrc[blockSize-1-i]; charDst[blockSize-1-i] = z; } } }

实战场景

-- 在大端机上写一个“标准小端”的二进制文件,供小端机读取 f = torch.DiskFile('tensor_le.bin', 'w') f:binary() f:littleEndianEncoding() f:writeObject(torch.randn(3, 3)) f:close() -- 读取前先确认机器字节序 print(torch.DiskFile.isLittleEndianCPU()) -- true/false print(torch.DiskFile.isBigEndianCPU()) -- true/false

跨平台共享数据时,约定好一种固定字节序(如小端),并在读写两端显式调用littleEndianEncoding()即可。注意这三个接口只对 binary() 模式生效,ASCII 模式本身是平台无关的。

longSize:定制 long 的磁盘宽度

longSize([size])

long类型在写入/读取文件时按size字节处理,size可以是0、4或8;0表示使用系统默认宽度(sizeof(long))。

C 层对取值有严格校验(lib/TH/THDiskFile.c):

void THDiskFile_longSize(THFile *self, int size) { THArgCheck(dfself->handle != NULL, 1, "attempt to use a closed file"); THArgCheck(size == 0 || size == 4 || size == 8, 1, "Invalid long size specified"); dfself->longSize = size; }

底层原理:三种宽度分支

long之所以要单独重写读写逻辑(见 lib/TH/THDiskFile.c),是因为它需要处理“内存宽度”与“磁盘宽度”不一致的转换:

  • longSize == 0或longSize == sizeof(long):按本机sizeof(long)直接fread/fwrite,非本机字节序时做整块反转。
  • longSize == 4:按 4 字节读写,元素经int32_t缓冲转换后落地;写入时buffer[i] = data[i]完成截断,读取时data[i-1] = ((int *)data)[i-1]完成扩展。
  • longSize == 8:分配8*n字节缓冲,把 4 字节的long值放入 8 字节槽位的“大端半区”或“小端半区”(由big_endian变量决定),实现真正的 64 位对齐布局。

这正好解释了官方文档中 “0, 4 or 8” 三个取值:0是系统默认,4用于在 32 位语义下精简存储,8用于强制 64 位宽布局。

实测用例

仓库自带的回归测试 test/longSize.lua 完整展示了这一接口的读写闭环:

f = torch.DiskFile('tensor8.bin','w') f:binary() f:longSize(8) -- 以 8 字节宽度写 long f:writeObject(tensor) f:close() f = torch.DiskFile('tensor8.bin','r') f:binary() f:longSize(8) -- 以 8 字节宽度读 tensor2 = f:readObject() f:close() tester:assert(tensor:norm()==tensor2:norm())

同文件中的longSize(4)用例结构完全相同,验证了两种宽度的往返一致性。另外 doc/serialization.md 提到torch.load支持b32/b64格式加载 32/64 位 OS 上保存的文件,正是与longSize相关的跨平台场景。

noBuffer:关闭读写缓冲

noBuffer()

禁用DiskFile的读写缓冲。C 实现调用setvbuf将文件流设为无缓冲模式(lib/TH/THDiskFile.c):

void THDiskFile_noBuffer(THFile *self) { ... if (setvbuf(dfself->handle, NULL, _IONBF, 0)) { THError("error: cannot disable buffer"); } }

适用场景:需要确保每次写入立即落盘(例如边写边由另一进程/线程读取、或需要实时持久化的日志场景)。正常情况下无需调用;若调用后想恢复默认缓冲行为,可通过 synchronize()(对应fflush,见 lib/TH/THDiskFile.c)或 close() 强制刷盘。

序列化:writeObject / readObject 与 torch.save / torch.load

DiskFile最常用的场景之一是与 File 的序列化方法配合,把任意可序列化对象(Torch 对象、table、number、string、纯 Lua 函数等)写入磁盘并完整恢复。

官方文档 doc/file.md 中给出了引用保持的经典示例,这里用DiskFile完整跑一遍:

-- 构造一个包含“两次同一张张量”的数组 array = {} x = torch.Tensor(1) table.insert(array, x) table.insert(array, x) -- array[1] 与 array[2] 指向同一地址 array[1][1] = 3.14 -- 写入磁盘并关闭,确保数据落盘 file = torch.DiskFile('foo.asc', 'w') file:writeObject(array) file:close() -- 重新加载 file = torch.DiskFile('foo.asc', 'r') arrayNew = file:readObject() file:close() -- arrayNew[1] 与 arrayNew[2] 依然指向同一地址! -- arrayNew[1][1] == arrayNew[2][1] == 3.14 arrayNew[1][1] = 2.72 -- 此时 arrayNew[1][1] == arrayNew[2][1] == 2.72

这正是 File:writeObject 的“引用去重”机制:同一对象只保存一次,之后只写引用,既省空间又保留对象之间的依赖关系(相关实现见 File.lua 的writeObjects/writeObjectsRef记录表)。注意:若在同一个文件中重复写入同一对象,修改后的内容不会再次记录,因为只写入指向原对象的引用。

对于更上层的需求,可直接使用 doc/serialization.md 描述的便捷接口:

-- torch.save / torch.load 内部就是基于 File 的封装 obj = { mat = torch.randn(10,10), name = '10', test = { entry = 1 } } torch.save('test.dat', obj) -- 默认二进制格式 obj2 = torch.load('test.dat') -- 完整还原 -- 需要跨平台分享时改用 ASCII torch.save('test.asc', obj, 'ascii') obj3 = torch.load('test.asc', 'ascii')

若需频繁改变同一对象内容并反复写入,可通过 File 的 referenced(false) 关闭引用追踪,避免对象被 File 长期持有(见 File.lua 的env.force机制)。

与 MemoryFile / PipeFile 的对比

同为 File 的子类,三者定位不同:

  • DiskFile:读写磁盘文件,支持r/w/rw三种模式、字节序控制与 seek 定位。
  • MemoryFile:在内存缓冲区上读写,适合快速构建/克隆对象(见 doc/file.md 的writeObject → seek(1) → readObject克隆技巧)。
  • PipeFile:通过管道(popen)读写外部进程输出。

值得注意的实现细节:在 lib/TH/THDiskFile.c 中,THPipeFile_new的虚表与 DiskFile 几乎完全共用(仅free换成pclose),可见三者共享同一套类型化读写内核,差异只在数据源。

错误处理与最佳实践

  1. 打开文件:torch.DiskFile(name, mode)默认在出错时抛 Lua 错误(pedantic 行为);需要容错时传quiet=true并检查返回值是否为nil。
  2. 读写过程中的错误:由 File 的quiet()/pedantic()控制——默认 pedantic 抛错;quiet 模式下用 hasError() 检查、clearError() 清除错误标志。
  3. 定位与遍历:position() 返回当前字节位置(首位置为1,遵循 Lua 索引约定),seek(position) 与 seekEnd() 实现随机跳转;底层在 64 位平台使用fseeko/ftello,并校验位置不超过LLONG_MAX(见 lib/TH/THDiskFile.c)。
  4. 落盘:写完数据后务必调用close(),或至少调用synchronize()触发fflush,防止缓冲数据滞留。
  5. 关闭后的访问:对已关闭的 DiskFile 做任何操作都会抛错——C 层每个入口都有THArgCheck(dfself->handle != NULL, 1, "attempt to use a closed file")守卫(如 lib/TH/THDiskFile.c)。
  6. 跨平台共享:二进制文件建议显式指定字节序(如littleEndianEncoding()),并视需要以longSize(4)/longSize(8)固定 long 宽度;ASCII 文件天然跨平台。

小结

DiskFile把 C 标准库的文件 I/O 包装为 torch7 统一的File接口:默认 ASCII 可读格式、可切换的高性能二进制模式、可显式控制的大小端字节序、可定制的 long 宽度,以及基于引用追踪的完整对象序列化。结合 lib/TH/THDiskFile.c 的源码与 test/longSize.lua 的测试用例,读者可以放心地在自己的项目中使用torch.DiskFile实现可靠的模型/张量持久化与跨平台数据交换。

  • 深度学习

【免费下载链接】torch7

http://torch.ch

项目地址:https://gitcode.com/gh_mirrors/to/torch7
点击查看免费下载
上一篇:serve-handler 安装和配置指南
下一篇:解决Blender导入三维模型难题:Photogrammetry-Importer常见问题与解决方案

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询