如何在swift4中读取特定文件的行?

时间:2017-10-28 17:44:38

标签: swift swift4

在Playground中测试我在一个String数组中读取整个文件,每行一个字符串。 但我需要的只是一条特定的行:

let dir = try? FileManager.default.url(for: .documentDirectory,
                                   in: .userDomainMask, appropriateFor: nil, create: true)
let fileURL = dir!.appendingPathComponent("test").appendingPathExtension("txt")
let text: [String] = try String(contentsOf: fileURL).components(separatedBy: NSCharacterSet.newlines)
let i = 2   // computed before, here to simplify
print(text[i])

有一种方法可以避免阅读完整的大文件吗?

2 个答案:

答案 0 :(得分:0)

我猜你的意思是你想要检索索引而不用手动搜索数组,例如for-in循环。

在Swift 4中,您可以将Array.index(where:)StringProtocol的通用contains(_:)功能结合使用来查找您要查找的内容。

让我们假设您正在寻找包含text: [String]数组中“重要内容”字样的第一行。

您可以使用:

text.index(where: { $0.contains("important stuff") })

在幕后,Swift循环查找文本,但内置增强功能,这应该比手动循环text数组更好。

注意如果没有匹配的行,则此搜索的结果可能是nil。因此,在使用结果之前,您需要确保它不是nil

强行打开结果(冒着可怕的 fatal error: unexpectedly found nil while unwrapping an Optional value 冒险):

print(text[lineIndex!)

或者,使用if let声明:

if let lineIndex = stringArray.index(where: { $0.contains("important stuff") }) {
    print(text[lineIndex])
}
else {
    print("Sorry; didn't find any 'important stuff' in the array.")
}

或者,使用guard声明:

guard let lineIndex = text.index(where: {$0.contains("important stuff")}) else {
    print("Sorry; didn't find any 'important stuff' in the array.")
    return
}
print(text[lineIndex])

答案 1 :(得分:0)

要在不读取整个文件的情况下查找特定行,您可以使用此StreamReader答案。它包含在Swift 3中工作的代码。我在Swift 4中测试过它:看看我的GitHub repo,TEST-StreamReader,我的测试代码。

你仍然需要循环才能到达正确的行,但是一旦你检索到该行,你就必须循环break

这是SO答案中的StreamReader类:

class StreamReader  {

    let encoding : String.Encoding
    let chunkSize : Int
    var fileHandle : FileHandle!
    let delimData : Data
    var buffer : Data
    var atEof : Bool

    init?(path: String, delimiter: String = "\n", encoding: String.Encoding = .utf8,
          chunkSize: Int = 4096) {

        guard let fileHandle = FileHandle(forReadingAtPath: path),
            let delimData = delimiter.data(using: encoding) else {
                return nil
        }
        self.encoding = encoding
        self.chunkSize = chunkSize
        self.fileHandle = fileHandle
        self.delimData = delimData
        self.buffer = Data(capacity: chunkSize)
        self.atEof = false
    }

    deinit {
        self.close()
    }

    /// Return next line, or nil on EOF.
    func nextLine() -> String? {
        precondition(fileHandle != nil, "Attempt to read from closed file")

        // Read data chunks from file until a line delimiter is found:
        while !atEof {
            if let range = buffer.range(of: delimData) {
                // Convert complete line (excluding the delimiter) to a string:
                let line = String(data: buffer.subdata(in: 0..<range.lowerBound), encoding: encoding)
                // Remove line (and the delimiter) from the buffer:
                buffer.removeSubrange(0..<range.upperBound)
                return line
            }
            let tmpData = fileHandle.readData(ofLength: chunkSize)
            if tmpData.count > 0 {
                buffer.append(tmpData)
            } else {
                // EOF or read error.
                atEof = true
                if buffer.count > 0 {
                    // Buffer contains last line in file (not terminated by delimiter).
                    let line = String(data: buffer as Data, encoding: encoding)
                    buffer.count = 0
                    return line
                }
            }
        }
        return nil
    }

    /// Start reading from the beginning of file.
    func rewind() -> Void {
        fileHandle.seek(toFileOffset: 0)
        buffer.count = 0
        atEof = false
    }

    /// Close the underlying file. No reading must be done after calling this method.
    func close() -> Void {
        fileHandle?.closeFile()
        fileHandle = nil
    }
}

extension StreamReader : Sequence {
    func makeIterator() -> AnyIterator<String> {
        return AnyIterator {
            return self.nextLine()
        }
    }
}