零碎读取二进制文件并转换为具有内存效率的整数
Reading Binary File Piecemeal and Converting to Integers With Memory Efficiency
使用 Swift,我需要从二进制文件中读取整数,但由于它们的大小而无法将整个文件读入内存。我将 61G 字节(77 亿整数)的数据写入十几个不同大小的文件中。最大的是18G字节(22亿个整数)。一些文件可能会完全读入内存,但最大的文件大于可用 RAM。
在此处插入文件 IO Rant。
我已经编写了一次写入 1000 万字节文件的代码,并且运行良好。我把它写成 class 但其余代码的 none 是面向对象的。这不是应用程序,因此没有空闲时间来清理内存。这是代码:
class BufferedBinaryIO {
var data = Data(capacity: 10000000)
var data1:Data?
let fileName:String!
let fileurl:URL!
var fileHandle:FileHandle? = nil
var (forWriting,forReading) = (false,false)
var tPointer:UnsafeMutablePointer<UInt8>?
var pointer = 0
init?(forWriting name:String) {
forWriting = true
fileName = name
fileurl = URL(fileURLWithPath:fileName)
if FileManager.default.fileExists(atPath: fileurl.path) {
try! fileHandle = FileHandle(forWritingTo: fileurl)
if fileHandle == nil {
print("Can't open file to write.")
return nil
}
}
else {
// if file does not exist write data for the first time
do{
try data.write(to: fileurl, options: .atomic)
try fileHandle = FileHandle(forWritingTo: fileurl)
} catch {
print("Unable to write in new file.")
return nil
}
}
}
init?(forReading name:String) {
forReading = true
fileName = name
fileurl = URL(fileURLWithPath:fileName)
if FileManager.default.fileExists(atPath: fileurl.path) {
try! fileHandle = FileHandle(forReadingFrom: fileurl)
if fileHandle == nil {
print("Can't open file to write.")
return nil
}
}
else {
// if file does not exist write data for the first time
do{
try fileHandle = FileHandle(forWritingTo: fileurl)
} catch {
print("Unable to write in new file.")
return nil
}
}
}
deinit {
if forWriting {
fileHandle?.seekToEndOfFile()
fileHandle?.write(data)
}
try? fileHandle?.close()
}
func write(_ datum: Data) {
guard forWriting else { return }
self.data.append(datum)
if data.count == 10000000 {
fileHandle?.write(data)
data.removeAll()
}
}
func readInt() -> Int? {
if data1 == nil || pointer == data1!.count {
if #available(macOS 10.15.4, *) {
//data1?.removeAll()
//data1 = nil
data1 = try! fileHandle?.read(upToCount: 10000000)
pointer = 0
} else {
// Fallback on earlier versions
}
}
if data1 != nil && pointer+8 <= data1!.count {
let retValue = data1!.withUnsafeBytes { [=10=].load(fromByteOffset: pointer,as: Int.self) }
pointer += 8
// data.removeFirst(8)
return retValue
} else {
print("here")
}
return nil
}
}
正如我所说,写入文件工作正常,我可以从文件中读取,但我遇到了问题。
一些读取二进制并将其转换为各种类型的解决方案使用如下代码:
let rData = try! Data(contentsOf: url)
let tPointer = UnsafeMutablePointer<UInt8>.allocate(capacity: rData.count)
rData.copyBytes(to: tPointer, count: rData.count)
第一行读入整个文件消耗相同数量的内存,接下来的两行内存消耗加倍。因此,即使我有 16G 字节的 Ram,我也只能读取一个 8GB 的文件,因为它必须双倍消耗内存。
如您所见,我的代码没有使用此代码。对于读取,我只是将文件读入 data1,一次 1000 万字节,然后像使用常规数据类型一样使用 data1 并访问它并且可以很好地读取数据,而不会增加内存使用量。
使用此代码的程序主体中的代码如下所示:
file loop .... {
let string = String(format:"~path/filename.data")
let dataPath = String(NSString(string: string).expandingTildeInPath)
let fileBuffer = BufferedBinaryIO(forReading: dataPath)
while let value = fileBuffer!.readInt() {
loop code
}
}
这是我的问题:此代码可以将文件读入 Int,但在 readInt 内部,代码在执行下一个 fileHandle?.read 时不会释放前一个 fileHandle?.read 的内存。因此,当我浏览文件时,每次填充缓冲区时内存消耗都会增加 1000 万,直到程序崩溃。
请原谅我的代码,因为它还在进行中。我不断更改它以尝试不同的方法来解决此问题。我将 data1 用作代码读取部分的可选变量,认为将其设置为 nil 会释放内存。当我刚刚覆盖它时,它会做同样的事情。
话虽如此,如果可行的话,这将是一种很好的编码方式。
所以问题是我是否有内存保留周期,或者是否有我需要在 data1 上使用的魔豆来停止这样做?
提前感谢您对这个问题的考虑。
您没有显示实际从您的文件中读取的代码,因此很难确定发生了什么。
从您展示的代码我们可以看出您正在使用 FileHandle,它允许随机访问文件并读取任意大小的数据块。
假设您正确地完成了该部分并且一次读取了 1000 万字节,那么您的问题可能是 iOS 和 Mac OS 处理内存的方式。对于某些事情,OS 将不再使用的内存块放入“自动释放池”,当您的代码 returns 和事件循环得到服务时,它会被释放。如果您同步处理数 GB 的文件数据,它可能没有机会在下一次传递之前释放内存。
(详细解释 Mac OS/iOS 内存管理以涵盖自动释放将非常复杂。如果您有兴趣,我建议您查找 Apple 手动引用计数和自动引用计数, a.k.a ARC,并寻找解释“幕后”发生的事情的结果。)
尝试将读取 1000 万字节数据的代码放入 autoreleasePool()
语句的闭包中。这将导致任何自动释放的内存实际被释放。类似于下面的伪代码:
while (more data) {
autoreleasepool {
// read a 10 million byte block of data
// process that block
}
}
使用 Swift,我需要从二进制文件中读取整数,但由于它们的大小而无法将整个文件读入内存。我将 61G 字节(77 亿整数)的数据写入十几个不同大小的文件中。最大的是18G字节(22亿个整数)。一些文件可能会完全读入内存,但最大的文件大于可用 RAM。
在此处插入文件 IO Rant。
我已经编写了一次写入 1000 万字节文件的代码,并且运行良好。我把它写成 class 但其余代码的 none 是面向对象的。这不是应用程序,因此没有空闲时间来清理内存。这是代码:
class BufferedBinaryIO {
var data = Data(capacity: 10000000)
var data1:Data?
let fileName:String!
let fileurl:URL!
var fileHandle:FileHandle? = nil
var (forWriting,forReading) = (false,false)
var tPointer:UnsafeMutablePointer<UInt8>?
var pointer = 0
init?(forWriting name:String) {
forWriting = true
fileName = name
fileurl = URL(fileURLWithPath:fileName)
if FileManager.default.fileExists(atPath: fileurl.path) {
try! fileHandle = FileHandle(forWritingTo: fileurl)
if fileHandle == nil {
print("Can't open file to write.")
return nil
}
}
else {
// if file does not exist write data for the first time
do{
try data.write(to: fileurl, options: .atomic)
try fileHandle = FileHandle(forWritingTo: fileurl)
} catch {
print("Unable to write in new file.")
return nil
}
}
}
init?(forReading name:String) {
forReading = true
fileName = name
fileurl = URL(fileURLWithPath:fileName)
if FileManager.default.fileExists(atPath: fileurl.path) {
try! fileHandle = FileHandle(forReadingFrom: fileurl)
if fileHandle == nil {
print("Can't open file to write.")
return nil
}
}
else {
// if file does not exist write data for the first time
do{
try fileHandle = FileHandle(forWritingTo: fileurl)
} catch {
print("Unable to write in new file.")
return nil
}
}
}
deinit {
if forWriting {
fileHandle?.seekToEndOfFile()
fileHandle?.write(data)
}
try? fileHandle?.close()
}
func write(_ datum: Data) {
guard forWriting else { return }
self.data.append(datum)
if data.count == 10000000 {
fileHandle?.write(data)
data.removeAll()
}
}
func readInt() -> Int? {
if data1 == nil || pointer == data1!.count {
if #available(macOS 10.15.4, *) {
//data1?.removeAll()
//data1 = nil
data1 = try! fileHandle?.read(upToCount: 10000000)
pointer = 0
} else {
// Fallback on earlier versions
}
}
if data1 != nil && pointer+8 <= data1!.count {
let retValue = data1!.withUnsafeBytes { [=10=].load(fromByteOffset: pointer,as: Int.self) }
pointer += 8
// data.removeFirst(8)
return retValue
} else {
print("here")
}
return nil
}
}
正如我所说,写入文件工作正常,我可以从文件中读取,但我遇到了问题。
一些读取二进制并将其转换为各种类型的解决方案使用如下代码:
let rData = try! Data(contentsOf: url)
let tPointer = UnsafeMutablePointer<UInt8>.allocate(capacity: rData.count)
rData.copyBytes(to: tPointer, count: rData.count)
第一行读入整个文件消耗相同数量的内存,接下来的两行内存消耗加倍。因此,即使我有 16G 字节的 Ram,我也只能读取一个 8GB 的文件,因为它必须双倍消耗内存。
如您所见,我的代码没有使用此代码。对于读取,我只是将文件读入 data1,一次 1000 万字节,然后像使用常规数据类型一样使用 data1 并访问它并且可以很好地读取数据,而不会增加内存使用量。
使用此代码的程序主体中的代码如下所示:
file loop .... {
let string = String(format:"~path/filename.data")
let dataPath = String(NSString(string: string).expandingTildeInPath)
let fileBuffer = BufferedBinaryIO(forReading: dataPath)
while let value = fileBuffer!.readInt() {
loop code
}
}
这是我的问题:此代码可以将文件读入 Int,但在 readInt 内部,代码在执行下一个 fileHandle?.read 时不会释放前一个 fileHandle?.read 的内存。因此,当我浏览文件时,每次填充缓冲区时内存消耗都会增加 1000 万,直到程序崩溃。
请原谅我的代码,因为它还在进行中。我不断更改它以尝试不同的方法来解决此问题。我将 data1 用作代码读取部分的可选变量,认为将其设置为 nil 会释放内存。当我刚刚覆盖它时,它会做同样的事情。
话虽如此,如果可行的话,这将是一种很好的编码方式。
所以问题是我是否有内存保留周期,或者是否有我需要在 data1 上使用的魔豆来停止这样做?
提前感谢您对这个问题的考虑。
您没有显示实际从您的文件中读取的代码,因此很难确定发生了什么。
从您展示的代码我们可以看出您正在使用 FileHandle,它允许随机访问文件并读取任意大小的数据块。
假设您正确地完成了该部分并且一次读取了 1000 万字节,那么您的问题可能是 iOS 和 Mac OS 处理内存的方式。对于某些事情,OS 将不再使用的内存块放入“自动释放池”,当您的代码 returns 和事件循环得到服务时,它会被释放。如果您同步处理数 GB 的文件数据,它可能没有机会在下一次传递之前释放内存。
(详细解释 Mac OS/iOS 内存管理以涵盖自动释放将非常复杂。如果您有兴趣,我建议您查找 Apple 手动引用计数和自动引用计数, a.k.a ARC,并寻找解释“幕后”发生的事情的结果。)
尝试将读取 1000 万字节数据的代码放入 autoreleasePool()
语句的闭包中。这将导致任何自动释放的内存实际被释放。类似于下面的伪代码:
while (more data) {
autoreleasepool {
// read a 10 million byte block of data
// process that block
}
}