在 Swift 中逐行读取文本文件?
Read a text file line by line in Swift?
我刚开始学习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
}
如果有其他方法,请告诉我。将不胜感激。
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 以前版本的编辑历史记录。
您可能确实想一次读入整个文件。我打赌它很小。
但随后您想将结果字符串拆分为一个数组,然后将数组的内容分布在各种 UI 元素中,例如 table 个单元格。
一个简单的例子:
var x: String = "abc\ndef"
var y = x.componentsSeparatedByString("\n")
// y is now a [String]: ["abc", "def"]
更新 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),而不是例如设备的文档文件夹
在 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
这不是很漂亮,但我相信它有效(在 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)
}
Swift 5.5
下面的解决方案显示了如何一次读取一行。这与将全部内容读入内存有很大不同。如果要读取的文件很大,则逐行读取的效果很好。将整个文件放入内存并不能很好地适应大文件。
下面的示例使用了一个 while 循环,它在没有更多行时退出,但您可以根据需要选择不同的行数来读取。
代码的工作原理如下:
- 创建一个 URL 告诉文件所在的位置
- 确保文件存在
- 打开文件进行阅读
- 为读取设置一些初始变量
- 使用
getLine()
阅读每一行
- 完成后关闭文件并释放缓冲区
如果您愿意,可以使代码不那么冗长;我已经包含了注释来解释变量的目的。
Swift 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)
}
在 >= Swift 5.0 你做的:
如果文件在主包中:
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)
}
组件 属性 将 return 一个字符串数组。
多一个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({([=10=].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
}
如果您有一个巨大的文件并且不想使用 String
、Data
等将所有数据加载到内存中,您可以使用函数 readLine()
从标准中读取内容逐行输入直到到达EOF。
let path = "path/file.txt"
guard let file = freopen(path, "r", stdin) else {
return
}
defer {
fclose(file)
}
while let line = readLine() {
print(line)
}
我刚开始学习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
}
如果有其他方法,请告诉我。将不胜感激。
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 以前版本的编辑历史记录。
您可能确实想一次读入整个文件。我打赌它很小。
但随后您想将结果字符串拆分为一个数组,然后将数组的内容分布在各种 UI 元素中,例如 table 个单元格。
一个简单的例子:
var x: String = "abc\ndef"
var y = x.componentsSeparatedByString("\n")
// y is now a [String]: ["abc", "def"]
更新 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),而不是例如设备的文档文件夹
在 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
这不是很漂亮,但我相信它有效(在 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)
}
Swift 5.5
下面的解决方案显示了如何一次读取一行。这与将全部内容读入内存有很大不同。如果要读取的文件很大,则逐行读取的效果很好。将整个文件放入内存并不能很好地适应大文件。
下面的示例使用了一个 while 循环,它在没有更多行时退出,但您可以根据需要选择不同的行数来读取。
代码的工作原理如下:
- 创建一个 URL 告诉文件所在的位置
- 确保文件存在
- 打开文件进行阅读
- 为读取设置一些初始变量
- 使用
getLine()
阅读每一行
- 完成后关闭文件并释放缓冲区
如果您愿意,可以使代码不那么冗长;我已经包含了注释来解释变量的目的。
Swift 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)
}
在 >= Swift 5.0 你做的:
如果文件在主包中:
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)
}
组件 属性 将 return 一个字符串数组。
多一个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({([=10=].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
}
如果您有一个巨大的文件并且不想使用 String
、Data
等将所有数据加载到内存中,您可以使用函数 readLine()
从标准中读取内容逐行输入直到到达EOF。
let path = "path/file.txt"
guard let file = freopen(path, "r", stdin) else {
return
}
defer {
fclose(file)
}
while let line = readLine() {
print(line)
}