【发布时间】:2022-01-16 11:45:36
【问题描述】:
使用 Swift,我需要从二进制文件中读取整数,但由于它们的大小,无法将整个文件读入内存。我有 61G 字节(77 亿整数)的数据写入十几个不同大小的文件中。最大的是 18G 字节(22 亿整数)。有些文件可能会完全读入内存,但最大的文件大于可用 RAM。
在此处插入文件 IO Rant。
我已经编写了一次将文件写入 1000 万字节的代码,并且运行良好。我把它写成一个类,但其余代码都不是面向对象的。这不是一个应用程序,因此没有空闲时间来进行内存清理。代码如下:
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 { $0.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,我也只能读取 8G 字节的文件,因为它必须双倍消耗内存。
如您所见,我的代码没有使用此代码。对于读取,我只是将文件读入 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
}
}
这是我的问题:此代码用于将文件读入 Ints,但在 readInt 中,当执行下一个 fileHandle?.read 时,该代码不会从前一个 fileHandle?.read 释放内存。因此,当我浏览文件时,每次填充缓冲区时内存消耗都会增加 1000 万,直到程序崩溃。
请原谅我的代码,因为它正在进行中。我不断更改它以尝试不同的方法来解决此问题。我使用 data1 作为代码读取部分的可选变量,认为将其设置为 nil 会释放内存。当我只是重写它时,它会做同样的事情。
话虽如此,如果可行的话,这将是一种很好的编码方式。
所以问题是我是否有内存保留周期,或者是否有我需要在 data1 上使用的魔法 bean 让它停止这样做?
提前感谢您考虑这个问题。
【问题讨论】:
-
对于任何想要复制上述代码的人,您需要进行 Duncan C 建议的更改。此外,此代码在开始写入之前不会清空/删除文件。我一直在手动进行。我需要完善代码以供一般使用。
标签: swift file-io memory-leaks binaryfiles