【问题标题】:Read a text file line by line in Swift?在 Swift 中逐行读取文本文件?
【发布时间】:2015-10-25 01:16:18
【问题描述】:

我刚开始学习 Swift。我已经从文本文件中读取了我的代码,并且应用程序显示了整个文本文件的内容。如何逐行显示并多次调用该行?

TextFile.txt 包含以下内容:

1. Banana 
2. Apple
3. pear
4. strawberry
5. blueberry
6. blackcurrant

以下是目前拥有的..

  if let path = NSBundle.mainBundle().pathForResource("TextFile", ofType: "txt"){
        var data = String(contentsOfFile:path, encoding: NSUTF8StringEncoding, error: nil)
            if let content = (data){
                TextView.text = content
    }

如果有其他方法,请告诉我。将不胜感激。

【问题讨论】:

  • 好的,这个文件里好像只有一行。您是指单独的单词而不是行吗?
  • 对不起,它应该是一个使用 /n 分隔的列表

标签: ios arrays swift text nsstring


【解决方案1】:

Swift 3.0

if let path = Bundle.main.path(forResource: "TextFile", ofType: "txt") {
    do {
        let data = try String(contentsOfFile: path, encoding: .utf8)
        let myStrings = data.components(separatedBy: .newlines)
        TextView.text = myStrings.joined(separator: ", ")
    } catch {
        print(error)
    }
}

变量myStrings应该是数据的每一行。

使用的代码来自: Reading file line by line in iOS SDK 用 Obj-C 编写并使用 NSString

检查以前版本的 Swift 的编辑历史记录。

【讨论】:

  • 嘿,如果我要一次显示两行,那会是= myStrings[5,1] ...吗?
  • 我不明白if let content = (data){ 行的用途。括号在这里有什么作用?
  • @Flimm 括号在这里没有任何作用。我留下了问题的原始代码,只修改了if let中的内容。
  • 这不会逐行读取文本文件。它读取整个文件,将其全部保存到数据中,然后将其全部作为行数组返回。
  • 在@algal 评论之后,这个答案不适用于非常大的文件。对于需要一点一点读取大文件块的人,请查看以下答案:stackoverflow.com/questions/24581517/…
【解决方案2】:

Swift 5.5

下面的解决方案展示了如何一次读取一行。这与将全部内容读入内存完全不同。如果您要读取大文件,则逐行读取可以很好地扩展。将整个文件放入内存并不能很好地适应大文件。

下面的示例使用了一个 while 循环,当没有更多行时退出,但您可以根据需要选择不同的行数来读取。

代码工作如下:

  1. 创建一个告诉文件所在位置的 URL
  2. 确保文件存在
  3. 打开文件进行阅读
  4. 设置一些初始变量以供读取
  5. 使用getLine()读取每一行
  6. 完成后关闭文件并释放缓冲区

如果你愿意,你可以让代码不那么冗长;我已经包含了 cmets 来解释变量的用途。

斯威夫特 5.5

import Cocoa

// get URL to the the documents directory in the sandbox
let home = FileManager.default.homeDirectoryForCurrentUser

// add a filename
let fileUrl = home
    .appendingPathComponent("Documents")
    .appendingPathComponent("my_file")
    .appendingPathExtension("txt")


// make sure the file exists
guard FileManager.default.fileExists(atPath: fileUrl.path) else {
    preconditionFailure("file expected at \(fileUrl.absoluteString) is missing")
}

// open the file for reading
// note: user should be prompted the first time to allow reading from this location
guard let filePointer:UnsafeMutablePointer<FILE> = fopen(fileUrl.path,"r") else {
    preconditionFailure("Could not open file at \(fileUrl.absoluteString)")
}

// a pointer to a null-terminated, UTF-8 encoded sequence of bytes
var lineByteArrayPointer: UnsafeMutablePointer<CChar>? = nil

// see the official Swift documentation for more information on the `defer` statement
// https://docs.swift.org/swift-book/ReferenceManual/Statements.html#grammar_defer-statement
defer {
    // remember to close the file when done
    fclose(filePointer)

    // The buffer should be freed by even if getline() failed.
    lineByteArrayPointer?.deallocate()
}

// the smallest multiple of 16 that will fit the byte array for this line
var lineCap: Int = 0

// initial iteration
var bytesRead = getline(&lineByteArrayPointer, &lineCap, filePointer)


while (bytesRead > 0) {
    
    // note: this translates the sequence of bytes to a string using UTF-8 interpretation
    let lineAsString = String.init(cString:lineByteArrayPointer!)
    
    // do whatever you need to do with this single line of text
    // for debugging, can print it
    print(lineAsString)
    
    // updates number of bytes read, for the next iteration
    bytesRead = getline(&lineByteArrayPointer, &lineCap, filePointer)
}

【讨论】:

    【解决方案3】:

    这并不漂亮,但我相信它有效(在 Swift 5 上)。这使用底层 POSIX 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(fileURL.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
        }
      })
    }
    

    以下是您可以如何使用它:

    for line in lines(ofFile:myFileURL) {
      print(line)
    }
    

    【讨论】:

    • 必须通过所有行的要求有点……难?对于数十或数百 MB 的文本文档,可能并不总是想要做的。这可以通过某种方式改进吗?
    • 你说得对,这是这个解决方案的一个缺陷。我认为最明显的改进不是返回一个UnfoldSequence,而是一个实现Sequence的自定义类型,它是足够聪明,如果您没有完成对序列的迭代而是销毁了实例,那么它仍然会在其 deinit 方法中进行清理。
    【解决方案4】:

    Swift 2.0 / Xcode 7.2 更新

        do {
            if let path = NSBundle.mainBundle().pathForResource("TextFile", ofType: "txt"){
                let data = try String(contentsOfFile:path, encoding: NSUTF8StringEncoding)
    
                let myStrings = data.componentsSeparatedByCharactersInSet(NSCharacterSet.newlineCharacterSet())
                print(myStrings)
            }
        } catch let err as NSError {
            //do sth with Error
            print(err)
        }
    

    另外值得一提的是,此代码读取项目文件夹中的文件(因为使用了 pathForResource),而不是例如设备的文件夹

    【讨论】:

      【解决方案5】:

      在 Swift 5.0 中执行此操作的最简单、最简单的方法可能如下:

      import Foundation
      
      // Determine the file name
      let filename = "main.swift"
      
      // Read the contents of the specified file
      let contents = try! String(contentsOfFile: filename)
      
      // Split the file into separate lines
      let lines = contents.split(separator:"\n")
      
      // Iterate over each line and print the line
      for line in lines {
          print("\(line)")
      }
      

      注意:这会将整个文件读入内存,然后只遍历内存中的文件以生成行......

      归功于:https://wiki.codermerlin.com/mediawiki/index.php/Code_Snippet:_Print_a_File_Line-by-Line

      【讨论】:

      • 这不会逐行读取文本文件。它读取整个文件,将其全部保存到一个字符串中,将其拆分为一个字符串数组,然后逐个打印它们。如果您的字符串太大而无法放入内存,这将失败。
      【解决方案6】:

      您可能确实想一次读取整个文件。我敢打赌它很小。

      但是您想将生成的字符串拆分为一个数组,然后将数组的内容分配给各种 UI 元素,例如表格单元格。

      一个简单的例子:

          var x: String = "abc\ndef"
          var y = x.componentsSeparatedByString("\n")
          // y is now a [String]: ["abc", "def"]
      

      【讨论】:

      • 是的! :D 这正是我要问的。那么 x 会被分配给 = data 吗?
      • Swift 4: var y = x.components(separatedBy: "\n")
      【解决方案7】:

      在 >= Swift 5.0 中,您可以:

      如果文件在主 Bundle 中:

      if let bundleURL = Bundle.main.url(forResource: "YOUR_FILE_NAME", withExtension: "txt"),
         let contentsOfFile = try? String(contentsOfFile: bundleURL.path, encoding: .utf8) {
          let components = contentsOfFile.components(separatedBy: .newlines)
          print(components)
      }
      

      components 属性将返回一个字符串数组。

      【讨论】:

        【解决方案8】:

        另一种 getline 解决方案:

        • 易于使用。只是复制过去。
        • 在实际项目中测试。
        extension URL
        {
            func foreachRow(_ mode:String, _ rowParcer:((String, Int)->Bool) )
            {
                //Here we should use path not the absoluteString (wich contains file://)
                let path = self.path
                
                guard let cfilePath = (path as NSString).utf8String,
                      let m = (mode as NSString).utf8String
                else {return}
                
                //Open file with specific mode (just use "r")
                guard let file = fopen(cfilePath, m)
                else {
                    print("fopen can't open file: \"\(path)\", mode: \"\(mode)\"")
                    return
                }
                
                //Row capacity for getline() 
                var cap = 0
                
                var row_index = 0
                
                //Row container for getline()
                var cline:UnsafeMutablePointer<CChar>? = nil
                
                //Free memory and close file at the end
                defer{free(cline); fclose(file)}
                            
                while getline(&cline, &cap, file) > 0
                {
                    if let crow = cline,
                       // the output line may contain '\n' that's why we filtered it
                       let s = String(utf8String: crow)?.filter({($0.asciiValue ?? 0) >= 32}) 
                    {
                        if rowParcer(s, row_index)
                        {
                            break
                        }
                    }
                    
                    row_index += 1
                }
            }
        }
        

        用法:

            let token = "mtllib "
        
            var mtlRow = ""
        
            largeObjFileURL.foreachRow("r"){ (row, i) -> Bool in
        
                if row.hasPrefix(token)
                {
                    mtlRow = row
        
                    return true // end of file reading
                }
        
                return false // continue file reading
            }
        

        【讨论】:

          猜你喜欢
          • 2014-08-26
          • 1970-01-01
          • 2014-12-01
          • 2011-07-23
          • 2012-10-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多