【问题标题】:iOS Swift replace or update specific line in a file using file handle?iOS Swift 使用文件句柄替换或更新文件中的特定行?
【发布时间】:2021-02-15 15:02:47
【问题描述】:

我正在编写一个点云文件,需要用文件中的点总数更新文件头:vertexCount。我不知道积分什么时候会停止,所以我不能一直累积值并等待将其写入文件。

vertexCount 值保存在 ascii 文件的第 3 行,该文件以换行符结束。

我只看到使用write(to: URL, options: .atomic) 将数据附加到文件末尾的示例和函数

如何使用 FileHandle 替换文件中的特定行,或覆盖整个标题?

   ply
   format ascii 1.0
   element vertex \(vertexCount)

我看到this question about replacing file contents using an array。由于文件至少有 40 万行,我不想将它分成单独的行。我正在考虑在 end_header 关键字上将其分开,然后生成一个新的标头,但不确定这样做的效率如何。

【问题讨论】:

  • 那么你将面临的问题是,当位数增加时,它会覆盖它后面的字符。您将需要使用固定数量的数字才能准确地将它们写在上面(例如 0000001)。行数并不重要,因为它将替换最后一位数字之后的任何换行符
  • 另一种选择是在换行前留一些空格,以增加位数,您将逐渐减少计数和换行符之间的空格。
  • 是的,位数可能是个问题。我不确定不同的系统如何解析整数,但我认为 0000001 是一个有效的整数。最坏的情况是,如果某些系统不接受这个 ascii 文件,用户可以手动修剪多余的零。还是不知道 FileHandle 能不能做我想做的事情
  • 在下面查看我的帖子
  • @LeoDabus 将检查您的解决方案。如果我想将所有内容导出到一个文件中,则位数为 8

标签: ios swift nsdata point-clouds


【解决方案1】:

那么您将面临的问题是,当位数增加时,它将覆盖其后面的字符。您将需要使用固定数量的数字才能准确地将它们写在上面(例如 0000000001)。行数并不重要,因为它将替换最后一位数字之后的任何换行符。

extension FixedWidthInteger where Self: CVarArg {
    func strZero(maxLength: Int) -> String {
        String(format: "%0*d", maxLength, self)
    }

    func write(toPLYFile atURL: URL) throws {
        let fileHandle = try FileHandle(forUpdating: atURL)
        try fileHandle.seek(toOffset: 36)
        try fileHandle.write(contentsOf: Data(strZero(maxLength: 10).utf8))
        fileHandle.closeFile()
    }
}

var vertexCount = 1
let text = """
ply
format ascii 1.0
element vertex \(vertexCount.strZero(maxLength: 10))
abcdefghijklmnopqrstuvwxyz
1234567890
"""
print(text)
print("=========")
let fileURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
    .appendingPathComponent("file.txt")
try Data(text.utf8).write(to: fileURL, options: .atomic)
let fileHandle = try FileHandle(forUpdating: fileURL)
try fileHandle.seek(toOffset: 36)
vertexCount = 12345
try fileHandle.write(contentsOf: Data(vertexCount.strZero(maxLength: 10).utf8))
fileHandle.closeFile()
let stringLoaded = try String(contentsOf: fileURL)
print(stringLoaded)

这将打印出来


格式 ascii 1.0
元素顶点 0000000001
abcdefghijklmnopqrstuvwxyz
1234567890
=========

格式 ascii 1.0
元素顶点 0000012345
abcdefghijklmnopqrstuvwxyz
1234567890


更新使用:

do {
    // filepath to PLY file that is being updated
    let url = URL(fileURLWithPath: path) 
    
    let totalVertexCount = 12345
    try totalVertexCount.write(toPLYFile: url)
} catch {
    print("Error writing PLY! \(error)")
    return
}

【讨论】:

  • 谢谢,这看起来和我想知道的完全一样!
  • @AlexStone 不知道你为什么还在使用路径,但你绝对应该避免它。
  • 它是一个字符串,存储到核心数据中。我还没有尝试过归档/取消归档 URL。
  • 只要确保不要存储整个路径。您应该只存储文件名及其位置(目录)
  • 换句话说,应用程序是沙盒的,因此每次用户启动它时路径(位置)都会改变。所以你只需要使用文件名来重构url(并且需要子目录)
【解决方案2】:

这只是我写得很快的东西,肯定会考虑让它看起来和工作得更好一些 - 但应该足以展示这个概念。需要进行一些测试,看看这是否真的比逐行分离更有效。

它本质上所做的是将文件逐字节读取到数组中,直到找到第三行的开头。然后,它将我们的新字符串复制到缓冲区中。之后,它会寻找第四行的开头并将文件的其余部分复制到缓冲区中。

我还在计算总字节大小,以便在最后修剪缓冲区。

var finalFileByteLength = 0;

if let d = NSData(contentsOfFile: filePath) {
    
    let newLine = "element vertex \(vertexCount)\n".data(using: .ascii)! as NSData
    var buffer = [UInt8](repeating: 0, count: d.length + newLine.length)
    
    var bytePosition = 0
    var lineCount = 0
    while(true) {
        //Read one Byte
        d.getBytes(&buffer+bytePosition, range: NSMakeRange(bytePosition, 1))
        
        //If it's a new line character
        if(buffer[bytePosition] == 10) {
            lineCount += 1
            //If it found the end of the second line, copy our new line
            if lineCount == 2 {
                newLine.getBytes(&buffer+(bytePosition+1), length: newLine.length)
                bytePosition += 1
                break
            }
        }
        
        bytePosition += 1
        finalFileByteLength+=1
    }
    
    var oldLine3Length = 0
    finalFileByteLength+=newLine.length
    
    //Find the start of the fourth line in the initial file
    while(true) {
        //Read one Byte
        var char = UInt8()
        d.getBytes(&char, range: NSMakeRange(bytePosition, 1))
        
        //If it's a new line character
        if(char == 10) {
            //If it found the end of the third line, break so we have the start of the fourth line
            bytePosition += 1
            oldLine3Length += 1
            break
        }
        
        bytePosition += 1
        oldLine3Length += 1
    }
    
    //Header is now modified, copy the rest of the file
    d.getBytes(&buffer+(bytePosition+newLine.length-oldLine3Length), range: NSMakeRange(bytePosition, d.length - bytePosition))
    finalFileByteLength+=d.length - bytePosition + 1
    
    let finalFileData = NSData(bytes: &buffer, length: finalFileByteLength)
    
    //Print the result - this is probably where you'll write the entire String to a file
    print(String(data: finalFileData as Data, encoding: .ascii))
}

编辑:设法减少到这个:

if let d = NSData(contentsOfFile: filePath) {
    
    let newLine = "element vertex \(vertexCount)".data(using: .ascii)! as NSData
    var newLineBuffer = [UInt8](repeating: 0, count: newLine.length)
    newLine.getBytes(&newLineBuffer, length: newLine.length)
    
    var buffer = [UInt8](repeating: 0, count: d.length)
    d.getBytes(&buffer, length: d.length)
    
    var thirdIndex = buffer.firstIndex(of: 10)
    thirdIndex = buffer[buffer.index(after: thirdIndex!)...].firstIndex(of: 10)
    thirdIndex = buffer[buffer.index(after: thirdIndex!)...].firstIndex(of: 10)
    var fourthIndex = buffer[buffer.index(after: thirdIndex!)...].firstIndex(of: 10)
    
    buffer.removeSubrange(thirdIndex!+1..<fourthIndex!)
    buffer.insert(contentsOf: newLineBuffer, at: thirdIndex!+1)
    
    let finalFileData = NSData(bytes: buffer, length: buffer.count) as Data
    print(String(data:finalFileData, encoding: .ascii))
}

【讨论】:

  • 这会将整个文件加载到内存中(40 万行),它不会仅更新/写入 vertexCount
  • 是的,拆分文件的解决方案可行,但 40 万已经是妥协了。完整的文件可能有 2000 万行。虽然拆分解决方案可能是一个短期的权宜之计,但如果有更好的就地写入解决方案,我很感兴趣。
猜你喜欢
  • 1970-01-01
  • 2013-03-18
  • 2016-06-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-18
  • 1970-01-01
相关资源
最近更新 更多