在Swift中逐行读取文件/URL



我试图读取NSURL中给定的文件并将其加载到数组中,项目由换行字符n分隔。

到目前为止,我是这样做的:

var possList: NSString? = NSString.stringWithContentsOfURL(filePath.URL) as? NSString
if var list = possList {
    list = list.componentsSeparatedByString("n") as NSString[]
    return list
}
else {
    //return empty list
}

出于几个原因,我对此不太满意。首先,我处理的文件大小从几千字节到几百MB不等。可以想象,处理这么大的字符串是缓慢而笨拙的。其次,这会在执行时冻结UI——同样,这不是很好。

我已经研究过在一个单独的线程中运行这段代码,但是我一直遇到麻烦,而且,它仍然不能解决处理大字符串的问题。

我想做的是下面的伪代码:

var aStreamReader = new StreamReader(from_file_or_url)
while aStreamReader.hasNextLine == true {
    currentline = aStreamReader.nextLine()
    list.addItem(currentline)
}

我如何在Swift中完成这个?

关于我正在阅读的文件的一些注意:所有文件由nrn分隔的短(<255字符)字符串组成。文件的长度范围从~100行到超过5000万行。它们可能包含欧洲字符和/或带有重音的字符。

(代码现在是Swift 2.2/Xcode 7.3。如果有人需要,可以在编辑历史中找到旧版本。最后提供了Swift 3的更新版本)

下面的Swift代码很大程度上受到了各种答案的启发如何逐行读取NSFileHandle中的数据?它从文件中分块读取,并将完整的行转换为字符串。

默认的行分隔符(n),字符串编码(UTF-8)和块大小(4096)可通过可选参数进行设置。

class StreamReader  {
    let encoding : UInt
    let chunkSize : Int
    var fileHandle : NSFileHandle!
    let buffer : NSMutableData!
    let delimData : NSData!
    var atEof : Bool = false
    init?(path: String, delimiter: String = "n", encoding : UInt = NSUTF8StringEncoding, chunkSize : Int = 4096) {
        self.chunkSize = chunkSize
        self.encoding = encoding
        if let fileHandle = NSFileHandle(forReadingAtPath: path),
            delimData = delimiter.dataUsingEncoding(encoding),
            buffer = NSMutableData(capacity: chunkSize)
        {
            self.fileHandle = fileHandle
            self.delimData = delimData
            self.buffer = buffer
        } else {
            self.fileHandle = nil
            self.delimData = nil
            self.buffer = nil
            return nil
        }
    }
    deinit {
        self.close()
    }
    /// Return next line, or nil on EOF.
    func nextLine() -> String? {
        precondition(fileHandle != nil, "Attempt to read from closed file")
        if atEof {
            return nil
        }
        // Read data chunks from file until a line delimiter is found:
        var range = buffer.rangeOfData(delimData, options: [], range: NSMakeRange(0, buffer.length))
        while range.location == NSNotFound {
            let tmpData = fileHandle.readDataOfLength(chunkSize)
            if tmpData.length == 0 {
                // EOF or read error.
                atEof = true
                if buffer.length > 0 {
                    // Buffer contains last line in file (not terminated by delimiter).
                    let line = NSString(data: buffer, encoding: encoding)
                    buffer.length = 0
                    return line as String?
                }
                // No more lines.
                return nil
            }
            buffer.appendData(tmpData)
            range = buffer.rangeOfData(delimData, options: [], range: NSMakeRange(0, buffer.length))
        }
        // Convert complete line (excluding the delimiter) to a string:
        let line = NSString(data: buffer.subdataWithRange(NSMakeRange(0, range.location)),
            encoding: encoding)
        // Remove line (and the delimiter) from the buffer:
        buffer.replaceBytesInRange(NSMakeRange(0, range.location + range.length), withBytes: nil, length: 0)
        return line as String?
    }
    /// Start reading from the beginning of file.
    func rewind() -> Void {
        fileHandle.seekToFileOffset(0)
        buffer.length = 0
        atEof = false
    }
    /// Close the underlying file. No reading must be done after calling this method.
    func close() -> Void {
        fileHandle?.closeFile()
        fileHandle = nil
    }
}

用法:

if let aStreamReader = StreamReader(path: "/path/to/file") {
    defer {
        aStreamReader.close()
    }
    while let line = aStreamReader.nextLine() {
        print(line)
    }
}

你甚至可以使用reader和for-in循环

for line in aStreamReader {
    print(line)
}

通过实现SequenceType协议(比较http://robots.thoughtbot.com/swift-sequences):

)
extension StreamReader : SequenceType {
    func generate() -> AnyGenerator<String> {
        return AnyGenerator {
            return self.nextLine()
        }
    }
}

Swift 3/Xcode 8 beta 6的更新:也"现代化"到使用guard和新的Data值类型:

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()
        }
    }
}

高效方便的逐行读取文本文件类(Swift 4, Swift 5)

注意:此代码与平台无关(macOS, iOS, ubuntu)

import Foundation
/// Read text file line by line in efficient way
public class LineReader {
   public let path: String
   fileprivate let file: UnsafeMutablePointer<FILE>!
   init?(path: String) {
      self.path = path
      file = fopen(path, "r")
      guard file != nil else { return nil }
   }
   public var nextLine: String? {
      var line: UnsafeMutablePointer<CChar>?
      var linecap: Int = 0
      defer { free(line) }
      return getline(&line, &linecap, file) > 0 ? String(cString: line!) : nil
   }
   deinit {
      fclose(file)
   }
}
extension LineReader: Sequence {
   public func makeIterator() -> AnyIterator<String> {
      return AnyIterator<String> {
         return self.nextLine
      }
   }
}

用法:

guard let reader = LineReader(path: "/Path/to/file.txt") else {
    return; // cannot open file
}
for line in reader {
    print(">" + line.trimmingCharacters(in: .whitespacesAndNewlines))      
}

github存储库

Swift 4.2安全语法

class LineReader {
    let path: String
    init?(path: String) {
        self.path = path
        guard let file = fopen(path, "r") else {
            return nil
        }
        self.file = file
    }
    deinit {
        fclose(file)
    }
    var nextLine: String? {
        var line: UnsafeMutablePointer<CChar>?
        var linecap = 0
        defer {
            free(line)
        }
        let status = getline(&line, &linecap, file)
        guard status > 0, let unwrappedLine = line else {
            return nil
        }
        return String(cString: unwrappedLine)
    }
    private let file: UnsafeMutablePointer<FILE>
}
extension LineReader: Sequence {
    func makeIterator() -> AnyIterator<String> {
        return AnyIterator<String> {
            return self.nextLine
        }
    }
}

用法:

guard let reader = LineReader(path: "/Path/to/file.txt") else {
    return
}
reader.forEach { line in
    print(line.trimmingCharacters(in: .whitespacesAndNewlines))      
}

我开始晚了,但这里是我为此目的编写的小类。经过一些不同的尝试(尝试子类NSInputStream),我发现这是一个合理和简单的方法。

记得在你的桥接头中添加#import <stdio.h>

// Use is like this:
let readLine = ReadLine(somePath)
while let line = readLine.readLine() {
    // do something...
}
class ReadLine {
    private var buf = UnsafeMutablePointer<Int8>.alloc(1024)
    private var n: Int = 1024
    let path: String
    let mode: String = "r"
    private lazy var filepointer: UnsafeMutablePointer<FILE> = {
        let csmode = self.mode.withCString { cs in return cs }
        let cspath = self.path.withCString { cs in return cs }
        return fopen(cspath, csmode)
    }()
    init(path: String) {
        self.path = path
    }
    func readline() -> String? {
        // unsafe for unknown input
        if getline(&buf, &n, filepointer) > 0 {
            return String.fromCString(UnsafePointer<CChar>(buf))
        }
        return nil
    }
    deinit {
        buf.dealloc(n)
        fclose(filepointer)
    }
}

这个函数接受一个文件URL并返回一个序列,该序列将返回文件的每一行,并惰性地读取它们。它适用于Swift 5。它依赖于底层的getline:

typealias LineState = (
  // pointer to a C string representing a line
  linePtr:UnsafeMutablePointer<CChar>?,
  linecap:Int,
  filePtr:UnsafeMutablePointer<FILE>?
)
/// Returns a sequence which iterates through all lines of the the file at the URL.
///
/// - Parameter url: file URL of a file to read
/// - Returns: a Sequence which lazily iterates through lines of the file
///
/// - warning: the caller of this function **must** iterate through all lines of the file, since aborting iteration midway will leak memory and a file pointer
/// - precondition: the file must be UTF8-encoded (which includes, ASCII-encoded)
func lines(ofFile url:URL) -> UnfoldSequence<String,LineState>
{
  let initialState:LineState = (linePtr:nil, linecap:0, filePtr:fopen(url.path,"r"))
  return sequence(state: initialState, next: { (state) -> String? in
    if getline(&state.linePtr, &state.linecap, state.filePtr) > 0,
      let theLine = state.linePtr  {
      return String.init(cString:theLine)
    }
    else {
      if let actualLine = state.linePtr  { free(actualLine) }
      fclose(state.filePtr)
      return nil
    }
  })
}

例如,下面是你如何使用它来打印app bundle中名为"foo"文件的每一行:

let url = NSBundle.mainBundle().urlForResource("foo", ofType: nil)!
for line in lines(ofFile:url) {
  // suppress print's automatically inserted line ending, since
  // lineGenerator captures each line's own new line character.
  print(line, separator: "", terminator: "")
}

我通过修改Alex Brown的答案来消除Martin R的评论中提到的内存泄漏,并将其更新为Swift 5,从而开发了这个答案。

试试这个答案,或者阅读Mac OS流编程指南。

您可能会发现使用stringWithContentsOfURL的性能实际上会更好,因为使用基于内存(或内存映射)的数据比使用基于磁盘的数据更快。

在另一个线程上执行它也有很好的文档记录,例如这里。

更新

如果你不想一次读取所有的数据,并且你不想使用NSStreams,那么你可能不得不使用c级文件I/O。有很多不这样做的理由——阻塞、字符编码、处理I/O错误、速度等等——这就是基础库的作用。我在下面概述了一个简单的答案,它只处理ACSII数据:

class StreamReader {
    var eofReached = false
    let fileHandle: UnsafePointer<FILE>
    init (path: String) {
        self.fileHandle = fopen(path.bridgeToObjectiveC().UTF8String, "rb".bridgeToObjectiveC().UTF8String)
    }
    deinit {
        fclose(self.fileHandle)
    }
    func nextLine() -> String {
        var nextChar: UInt8 = 0
        var stringSoFar = ""
        var eolReached = false
        while (self.eofReached == false) && (eolReached == false) {
            if fread(&nextChar, 1, 1, self.fileHandle) == 1 {
                switch nextChar & 0xFF {
                case 13, 10 : // CR, LF
                    eolReached = true
                case 0...127 : // Keep it in ASCII
                    stringSoFar += NSString(bytes:&nextChar, length:1, encoding: NSASCIIStringEncoding)
                default :
                    stringSoFar += "<(nextChar)>"
                }
            } else { // EOF or error
                self.eofReached = true
            }
        }
        return stringSoFar
    }
}
// OP's original request follows:
var aStreamReader = StreamReader(path: "~/Desktop/Test.text".stringByStandardizingPath)
while aStreamReader.eofReached == false { // Changed property name for more accurate meaning
    let currentline = aStreamReader.nextLine()
    //list.addItem(currentline)
    println(currentline)
}

或者您可以直接使用Generator:

let stdinByLine = GeneratorOf({ () -> String? in
    var input = UnsafeMutablePointer<Int8>(), lim = 0
    return getline(&input, &lim, stdin) > 0 ? String.fromCString(input) : nil
})

让我们试一试

for line in stdinByLine {
    println(">>> (line)")
}

它简单,懒惰,很容易与其他快速的东西链接,如枚举器和函数,如map, reduce, filter;使用lazy()包装器


适用于所有FILE:

let byLine = { (file:UnsafeMutablePointer<FILE>) in
    GeneratorOf({ () -> String? in
        var input = UnsafeMutablePointer<Int8>(), lim = 0
        return getline(&input, &lim, file) > 0 ? String.fromCString(input) : nil
    })
}

称为

for line in byLine(stdin) { ... }

根据@dankogai的回答,我对Swift 4+做了一些修改,

    let bufsize = 4096
    let fp = fopen(jsonURL.path, "r");
    var buf = UnsafeMutablePointer<Int8>.allocate(capacity: bufsize)
    while (fgets(buf, Int32(bufsize-1), fp) != nil) {
        print( String(cString: buf) )
     }
    buf.deallocate()

这对我很有用。

谢谢

迅速5.5:使用url.lines h1> DC文档在这里

使用例子:

guard let url = URL(string: "https://www.example.com") else {
    return
}
// Manipulating an `Array` in memory seems to be a requirement.
// This will balloon in size as lines of data get added.
var myHugeArray = [String]()
do {
    // This should keep the inbound data memory usage low
    for try await line in url.lines {
        myHugeArray.append(line)
    }
} catch {
     debugPrint(error)
}

你可以在SwiftUI .task { }修饰符中使用它,或者将它包装在Task返回类型中以使其工作脱离主线程。

一旦你掌握了UnsafePointer,原来好的老式C API在Swift中是很舒服的。下面是一个简单的cat程序,它从标准输入读取并逐行打印到标准输出。你甚至不需要粉底。达尔文就足够了:

import Darwin
let bufsize = 4096
// let stdin = fdopen(STDIN_FILENO, "r") it is now predefined in Darwin
var buf = UnsafePointer<Int8>.alloc(bufsize)
while fgets(buf, Int32(bufsize-1), stdin) {
    print(String.fromCString(CString(buf)))
}
buf.destroy()

(注意:我在Xcode 8.2.1和macOS Sierra 10.12.3上使用Swift 3.0.1)

我在这里看到的所有答案都遗漏了他可能在寻找LF或CRLF。如果一切顺利,他/她可以在LF上匹配,并检查返回的字符串是否在末尾有额外的CR。但是一般的查询涉及多个搜索字符串。换句话说,分隔符需要是Set<String>,其中集合既不为空也不包含空字符串,而不是单个字符串。

在我去年的第一次尝试中,我试图做"正确的事情"并搜索一个通用的字符串集。这太难了;您需要一个完整的解析器和状态机等等。我放弃了它,也放弃了它所属的项目。

现在我又在做这个项目,又面临同样的挑战。现在我要在CR和LF上硬编码搜索。我认为没有人需要在CR/LF解析之外搜索像这样的两个半独立和半依赖的字符。

我使用Data提供的搜索方法,所以我不在这里做字符串编码和东西。只是原始的二进制处理。假设我得到了一个ASCII超集,比如ISO Latin-1或UTF-8。您可以在下一层处理字符串编码,并且您可以判断带有次要代码点的CR/LF是否仍然算作CR或LF。

算法:从当前字节偏移量中继续搜索下一个CR 下一个LF。

  • 如果两者都没有找到,则认为下一个数据字符串是从当前偏移量到数据结束。注意,终止符长度为0。将此标记为阅读循环的结束。
  • 如果先找到一个LF,或者只找到一个LF,则认为下一个数据字符串是从当前到LF的偏移量。注意,终止长度为1。将偏移量移动到LF之后。
  • 如果只找到一个CR,就像LF的情况(只是用不同的字节值)。
  • 否则,我们得到一个CR接着一个LF。
    • 如果两者相邻,则按LF情况处理,只是终止符长度为2。
    • 如果它们之间有一个字节,并且该字节也是CR,那么我们就得到了"Windows开发人员在文本模式下编写二进制rn,给出rrn"问题。也像LF的情况一样处理它,除了终止符长度将是3。
    • 否则,CR和LF不连接,并按仅CR的情况处理。

这里有一些代码:

struct DataInternetLineIterator: IteratorProtocol {
    /// Descriptor of the location of a line
    typealias LineLocation = (offset: Int, length: Int, terminatorLength: Int)
    /// Carriage return.
    static let cr: UInt8 = 13
    /// Carriage return as data.
    static let crData = Data(repeating: cr, count: 1)
    /// Line feed.
    static let lf: UInt8 = 10
    /// Line feed as data.
    static let lfData = Data(repeating: lf, count: 1)
    /// The data to traverse.
    let data: Data
    /// The byte offset to search from for the next line.
    private var lineStartOffset: Int = 0
    /// Initialize with the data to read over.
    init(data: Data) {
        self.data = data
    }
    mutating func next() -> LineLocation? {
        guard self.data.count - self.lineStartOffset > 0 else { return nil }
        let nextCR = self.data.range(of: DataInternetLineIterator.crData, options: [], in: lineStartOffset..<self.data.count)?.lowerBound
        let nextLF = self.data.range(of: DataInternetLineIterator.lfData, options: [], in: lineStartOffset..<self.data.count)?.lowerBound
        var location: LineLocation = (self.lineStartOffset, -self.lineStartOffset, 0)
        let lineEndOffset: Int
        switch (nextCR, nextLF) {
        case (nil, nil):
            lineEndOffset = self.data.count
        case (nil, let offsetLf):
            lineEndOffset = offsetLf!
            location.terminatorLength = 1
        case (let offsetCr, nil):
            lineEndOffset = offsetCr!
            location.terminatorLength = 1
        default:
            lineEndOffset = min(nextLF!, nextCR!)
            if nextLF! < nextCR! {
                location.terminatorLength = 1
            } else {
                switch nextLF! - nextCR! {
                case 2 where self.data[nextCR! + 1] == DataInternetLineIterator.cr:
                    location.terminatorLength += 1  // CR-CRLF
                    fallthrough
                case 1:
                    location.terminatorLength += 1  // CRLF
                    fallthrough
                default:
                    location.terminatorLength += 1  // CR-only
                }
            }
        }
        self.lineStartOffset = lineEndOffset + location.terminatorLength
        location.length += self.lineStartOffset
        return location
    }
}

当然,如果你有一个Data块的长度至少是一个gb的显著部分,当当前字节偏移量没有更多的CR或LF时,你会受到打击;在每次迭代中,总是无果而终地搜索直到最后。读取数据块会有帮助:

struct DataBlockIterator: IteratorProtocol {
    /// The data to traverse.
    let data: Data
    /// The offset into the data to read the next block from.
    private(set) var blockOffset = 0
    /// The number of bytes remaining.  Kept so the last block is the right size if it's short.
    private(set) var bytesRemaining: Int
    /// The size of each block (except possibly the last).
    let blockSize: Int
    /// Initialize with the data to read over and the chunk size.
    init(data: Data, blockSize: Int) {
        precondition(blockSize > 0)
        self.data = data
        self.bytesRemaining = data.count
        self.blockSize = blockSize
    }
    mutating func next() -> Data? {
        guard bytesRemaining > 0 else { return nil }
        defer { blockOffset += blockSize ; bytesRemaining -= blockSize }
        return data.subdata(in: blockOffset..<(blockOffset + min(bytesRemaining, blockSize)))
    }
}
你必须自己把这些想法混合在一起,因为我还没有做过。考虑:
  • 当然,你必须考虑完全包含在块中的行。
  • 但是你必须处理行尾在相邻块中的情况。
  • 或者当端点之间至少有一个块时
  • 最大的复杂性是当行以多字节序列结束时,但是该序列跨越两个块!(以just CR结尾的行也是块中的最后一个字节也是相同的情况,因为您需要读取下一个块来查看您的just-CR实际上是CRLF还是CR-CRLF。当数据块以CR-CR结尾时,也有类似的恶作剧。)
  • 你需要处理当前偏移量中没有终止符的情况,但是数据结束在后面的块中。

祝你好运!

我想要一个不不断修改缓冲区或重复代码的版本,因为这两者都是低效的,并且允许任何大小的缓冲区(包括1字节)和任何分隔符。它有一个公共方法:readline()。调用此方法将返回下一行的String值,或者在EOF时返回nil。

import Foundation
// LineStream(): path: String, [buffSize: Int], [delim: String] -> nil | String
// ============= --------------------------------------------------------------
// path:     the path to a text file to be parsed
// buffSize: an optional buffer size, (1...); default is 4096
// delim:    an optional delimiter String; default is "n"
// ***************************************************************************
class LineStream {
    let path: String
    let handle: NSFileHandle!
    let delim: NSData!
    let encoding: NSStringEncoding
    var buffer = NSData()
    var buffSize: Int
    var buffIndex = 0
    var buffEndIndex = 0
    init?(path: String,
      buffSize: Int = 4096,
      delim: String = "n",
      encoding: NSStringEncoding = NSUTF8StringEncoding)
    {
      self.handle = NSFileHandle(forReadingAtPath: path)
      self.path = path
      self.buffSize = buffSize < 1 ? 1 : buffSize
      self.encoding = encoding
      self.delim = delim.dataUsingEncoding(encoding)
      if handle == nil || self.delim == nil {
        print("ERROR initializing LineStream") /* TODO use STDERR */
        return nil
      }
    }
  // PRIVATE
  // fillBuffer(): _ -> Int [0...buffSize]
  // ============= -------- ..............
  // Fill the buffer with new data; return with the buffer size, or zero
  // upon reaching end-of-file
  // *********************************************************************
  private func fillBuffer() -> Int {
    buffer = handle.readDataOfLength(buffSize)
    buffIndex = 0
    buffEndIndex = buffer.length
    return buffEndIndex
  }
  // PRIVATE
  // delimLocation(): _ -> Int? nil | [1...buffSize]
  // ================ --------- ....................
  // Search the remaining buffer for a delimiter; return with the location
  // of a delimiter in the buffer, or nil if one is not found.
  // ***********************************************************************
  private func delimLocation() -> Int? {
    let searchRange = NSMakeRange(buffIndex, buffEndIndex - buffIndex)
    let rangeToDelim = buffer.rangeOfData(delim,
                                          options: [], range: searchRange)
    return rangeToDelim.location == NSNotFound
        ? nil
        : rangeToDelim.location
  }
  // PRIVATE
  // dataStrValue(): NSData -> String ("" | String)
  // =============== ---------------- .............
  // Attempt to convert data into a String value using the supplied encoding; 
  // return the String value or empty string if the conversion fails.
  // ***********************************************************************
    private func dataStrValue(data: NSData) -> String? {
      if let strVal = NSString(data: data, encoding: encoding) as? String {
          return strVal
      } else { return "" }
}
  // PUBLIC
  // readLine(): _ -> String? nil | String
  // =========== ____________ ............
  // Read the next line of the file, i.e., up to the next delimiter or end-of-
  // file, whichever occurs first; return the String value of the data found, 
  // or nil upon reaching end-of-file.
  // *************************************************************************
  func readLine() -> String? {
    guard let line = NSMutableData(capacity: buffSize) else {
        print("ERROR setting line")
        exit(EXIT_FAILURE)
    }
    // Loop until a delimiter is found, or end-of-file is reached
    var delimFound = false
    while !delimFound {
        // buffIndex will equal buffEndIndex in three situations, resulting
        // in a (re)filling of the buffer:
        //   1. Upon the initial call;
        //   2. If a search for a delimiter has failed
        //   3. If a delimiter is found at the end of the buffer
        if buffIndex == buffEndIndex {
            if fillBuffer() == 0 {
                return nil
            }
        }
        var lengthToDelim: Int
        let startIndex = buffIndex
        // Find a length of data to place into the line buffer to be
        // returned; reset buffIndex
        if let delim = delimLocation() {
            // SOME VALUE when a delimiter is found; append that amount of
            // data onto the line buffer,and then return the line buffer
            delimFound = true
            lengthToDelim = delim - buffIndex
            buffIndex = delim + 1   // will trigger a refill if at the end
                                    // of the buffer on the next call, but
                                    // first the line will be returned
        } else {
            // NIL if no delimiter left in the buffer; append the rest of
            // the buffer onto the line buffer, refill the buffer, and
            // continue looking
            lengthToDelim = buffEndIndex - buffIndex
            buffIndex = buffEndIndex    // will trigger a refill of buffer
                                        // on the next loop
        }
        line.appendData(buffer.subdataWithRange(
            NSMakeRange(startIndex, lengthToDelim)))
    }
    return dataStrValue(line)
  }
}

按如下方式调用:

guard let myStream = LineStream(path: "/path/to/file.txt")
else { exit(EXIT_FAILURE) }
while let s = myStream.readLine() {
  print(s)
}

相关内容

  • 没有找到相关文章

最新更新