在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)

}

关于我正在读取的文件的一些注意事项:

所有文件都由短字符串(<255个字符)组成,用\n或分隔\r\n。文件的长度从大约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)

}

}

您甚至可以将阅读器与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()

}

}

}


还要“现代化”使用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中逐行读取文件/ URL 的全部内容, 来源链接: utcz.com/qa/417024.html

回到顶部