我已经阅读了从文本文件读取和写入数据
我需要将数据(字符串)附加到文本文件的末尾。
一种明显的方法是从磁盘读取文件并将字符串附加到文件末尾并将其写回,但效率不高,特别是当您处理大文件并经常执行时。
所以问题是“如何将字符串附加到文本文件的末尾,而不读取文件并将整个内容写回”?
到目前为止我已经:
let dir:NSURL = NSFileManager.defaultManager().URLsForDirectory(NSSearchPathDirectory.CachesDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask).last as NSURL
let fileurl = dir.URLByAppendingPathComponent("log.txt")
var err:NSError?
// until we find a way to append stuff to files
if let current_content_of_file = NSString(contentsOfURL: fileurl, encoding: NSUTF8StringEncoding, error: &err) {
"\(current_content_of_file)\n\(NSDate()) -> \(object)".writeToURL(fileurl, atomically: true, encoding: NSUTF8StringEncoding, error: &err)
}else {
"\(NSDate()) -> \(object)".writeToURL(fileurl, atomically: true, encoding: NSUTF8StringEncoding, error: &err)
}
if err != nil{
println("CANNOT LOG: \(err)")
}
这是用 Swift 3.0 编写的 PointZeroTwo 的答案 的更新,有一个简短的说明 - 在游乐场测试中使用简单的文件路径是可行的,但在我的实际应用程序中,我需要使用 .documentDirectory (或您选择的目录)构建 URL用于阅读和写作 - 确保它在整个应用程序中保持一致):
extension String {
func appendLineToURL(fileURL: URL) throws {
try (self + "\n").appendToURL(fileURL: fileURL)
}
func appendToURL(fileURL: URL) throws {
let data = self.data(using: String.Encoding.utf8)!
try data.append(fileURL: fileURL)
}
}
extension Data {
func append(fileURL: URL) throws {
if let fileHandle = FileHandle(forWritingAtPath: fileURL.path) {
defer {
fileHandle.closeFile()
}
fileHandle.seekToEndOfFile()
fileHandle.write(self)
}
else {
try write(to: fileURL, options: .atomic)
}
}
}
//test
do {
let dir: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last! as URL
let url = dir.appendingPathComponent("logFile.txt")
try "Test \(Date())".appendLineToURL(fileURL: url as URL)
let result = try String(contentsOf: url as URL, encoding: String.Encoding.utf8)
}
catch {
print("Could not write to file")
}
你应该使用 NSFileHandle,它可以寻找文件末尾
let dir:NSURL = NSFileManager.defaultManager().URLsForDirectory(NSSearchPathDirectory.CachesDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask).last as NSURL
let fileurl = dir.URLByAppendingPathComponent("log.txt")
let string = "\(NSDate())\n"
let data = string.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
if NSFileManager.defaultManager().fileExistsAtPath(fileurl.path!) {
var err:NSError?
if let fileHandle = NSFileHandle(forWritingToURL: fileurl, error: &err) {
fileHandle.seekToEndOfFile()
fileHandle.writeData(data)
fileHandle.closeFile()
}
else {
println("Can't open fileHandle \(err)")
}
}
else {
var err:NSError?
if !data.writeToURL(fileurl, options: .DataWritingAtomic, error: &err) {
println("Can't write \(err)")
}
}
一些已发布答案的变体,具有以下特征:
会默默失败
class Logger {
static var logFile: URL? {
guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return nil }
let formatter = DateFormatter()
formatter.dateFormat = "dd-MM-yyyy"
let dateString = formatter.string(from: Date())
let fileName = "\(dateString).log"
return documentsDirectory.appendingPathComponent(fileName)
}
static func log(_ message: String) {
guard let logFile = logFile else {
return
}
let formatter = DateFormatter()
formatter.dateFormat = "HH:mm:ss"
let timestamp = formatter.string(from: Date())
guard let data = (timestamp + ": " + message + "\n").data(using: String.Encoding.utf8) else { return }
if FileManager.default.fileExists(atPath: logFile.path) {
if let fileHandle = try? FileHandle(forWritingTo: logFile) {
fileHandle.seekToEndOfFile()
fileHandle.write(data)
fileHandle.closeFile()
}
} else {
try? data.write(to: logFile, options: .atomicWrite)
}
}
}
这是一种更有效地更新文件的方法。
let monkeyLine = "\nAdding a 🐵 to the end of the file via FileHandle"
if let fileUpdater = try? FileHandle(forUpdating: newFileUrl) {
// Function which when called will cause all updates to start from end of the file
fileUpdater.seekToEndOfFile()
// Which lets the caller move editing to any position within the file by supplying an offset
fileUpdater.write(monkeyLine.data(using: .utf8)!)
// Once we convert our new content to data and write it, we close the file and that’s it!
fileUpdater.closeFile()
}
这是 Swift 2 的版本,在 String 和 NSData 上使用扩展方法。
//: Playground - noun: a place where people can play
import UIKit
extension String {
func appendLineToURL(fileURL: NSURL) throws {
try self.stringByAppendingString("\n").appendToURL(fileURL)
}
func appendToURL(fileURL: NSURL) throws {
let data = self.dataUsingEncoding(NSUTF8StringEncoding)!
try data.appendToURL(fileURL)
}
}
extension NSData {
func appendToURL(fileURL: NSURL) throws {
if let fileHandle = try? NSFileHandle(forWritingToURL: fileURL) {
defer {
fileHandle.closeFile()
}
fileHandle.seekToEndOfFile()
fileHandle.writeData(self)
}
else {
try writeToURL(fileURL, options: .DataWritingAtomic)
}
}
}
// Test
do {
let url = NSURL(fileURLWithPath: "test.log")
try "Test \(NSDate())".appendLineToURL(url)
let result = try String(contentsOfURL: url)
}
catch {
print("Could not write to file")
}
这里是 PointZeroTwo 的答案的更新,为 Swift 4.1 编写:
extension String {
func appendLine(to url: URL) throws {
try self.appending("\n").append(to: url)
}
func append(to url: URL) throws {
let data = self.data(using: String.Encoding.utf8)
try data?.append(to: url)
}
}
extension Data {
func append(to url: URL) throws {
if let fileHandle = try? FileHandle(forWritingTo: url) {
defer {
fileHandle.closeFile()
}
fileHandle.seekToEndOfFile()
fileHandle.write(self)
} else {
try write(to: url)
}
}
}
更新:我就此写了一篇博客文章,您可以在这里找到它!
保持事物Swifty,这是一个使用默认实现的
FileWriter
协议的示例(在撰写本文时为 Swift 4.1):
注意:这仅适用于文本。您可以执行类似于写入/附加的操作
Data
。
import Foundation
enum FileWriteError: Error {
case directoryDoesntExist
case convertToDataIssue
}
protocol FileWriter {
var fileName: String { get }
func write(_ text: String) throws
}
extension FileWriter {
var fileName: String { return "File.txt" }
func write(_ text: String) throws {
guard let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
throw FileWriteError.directoryDoesntExist
}
let encoding = String.Encoding.utf8
guard let data = text.data(using: encoding) else {
throw FileWriteError.convertToDataIssue
}
let fileUrl = dir.appendingPathComponent(fileName)
if let fileHandle = FileHandle(forWritingAtPath: fileUrl.path) {
fileHandle.seekToEndOfFile()
fileHandle.write(data)
} else {
try text.write(to: fileUrl, atomically: false, encoding: encoding)
}
}
}
所有答案(截至目前)都会为每个写入操作重新创建 FileHandle。这对于大多数应用程序来说可能没问题,但这也相当低效:进行系统调用,并且每次创建 FileHandle 时都会访问文件系统。
为了避免多次创建文件句柄,请使用以下内容:
final class FileHandleBuffer {
let fileHandle: FileHandle
let size: Int
private var buffer: Data
init(fileHandle: FileHandle, size: Int = 1024 * 1024) {
self.fileHandle = fileHandle
self.size = size
self.buffer = Data(capacity: size)
}
deinit { try! flush() }
func flush() throws {
try fileHandle.write(contentsOf: buffer)
buffer = Data(capacity: size)
}
func write(_ data: Data) throws {
buffer.append(data)
if buffer.count > size {
try flush()
}
}
}
// USAGE
// Create the file if it does not yet exist
FileManager.default.createFile(atPath: fileURL.path, contents: nil)
let fileHandle = try FileHandle(forWritingTo: fileURL)
// Seek will make sure to not overwrite the existing content
// Skip the seek to overwrite the file
try fileHandle.seekToEnd()
let buffer = FileHandleBuffer(fileHandle: fileHandle)
for i in 0..<count {
let data = getData() // Your implementation
try buffer.write(data)
print(i)
}
我使用一个类,其中日期格式化程序和文件网址仅创建一次,您可以通过一次调用从任何地方使用它。
这是 Logger.swift 文件的代码:
class Logger {
private static let shared = Logger()
private var fileURL:URL?
private var dateFormatter:DateFormatter?
private init() {
print("Logger -> Init")
//DateFormatter for timestamp
self.dateFormatter = DateFormatter()
self.dateFormatter?.dateFormat = "HH:mm:ss"
//FileURL for log
let fileName = "APPNAME Log - \(Date()).log"
guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
print("Logger -> Documents directory could not be accessed")
return
}
self.fileURL = documentsDirectory.appendingPathComponent(fileName)
print("Logger -> Final file url path: \(String(describing: self.fileURL?.path))")
}
static func log(_ message: String) {
let logger = Logger.shared
guard let fileURL = logger.fileURL,
let dateFormatter = logger.dateFormatter else {
print("Logger -> FileURL/Dateformatter not accessible")
return
}
let timestamp = dateFormatter.string(from: Date())
let stringToWrite = timestamp + ": " + message + "\n"
guard let data = stringToWrite.data(using: String.Encoding.utf8) else {
print("Logger -> Failed to create data from string")
return
}
//Check for existing file
if FileManager.default.fileExists(atPath: fileURL.path) {
do {
let fileHandle = try FileHandle(forWritingTo: fileURL)
fileHandle.seekToEndOfFile()
fileHandle.write(data)
fileHandle.closeFile()
}
catch {
print("Logger -> Error creating filehandle: \(error)")
}
return
}
//Create a new file
do {
try data.write(to: fileURL, options: .atomicWrite)
}
catch {
print("Logger -> Error writing to file: \(error)")
}
}
}
无论何时你想使用它,你都可以简单地执行以下操作:
Logger.log("Some string I want to save in the file!")
作为一个奖励,这是一个快速函数,我只是将其放入一个 swift 文件中(不是一个类,只是一个在底层放置此代码的 swift 文件),它会自动将文件名和函数放入字符串(也打印到控制台)
func DLog(_ message: String, filename: String = #file, function: String = #function, line: Int = #line) {
let string = "[\((filename as NSString).lastPathComponent):\(line)] \(function) - \(message)"
print("\(string)") // Print to DebugConsole
//Write same string to file
Logger.log(string)
}
您可以从任何地方使用此 DLog 功能,如下所示:
DLog("String to print AND write to a file")
这是以前答案的现代总结,作为
URL
的扩展,并带有一个参数来附加一个(行)分隔符,默认值是一个linefeed
字符。
如果文件不存在,则会创建该文件。
trailing
参数定义分隔符的位置。如果是 false
,则将分隔符插入到字符串的前面(默认为 true
,即 appends 分隔符)
extension URL {
func appendText(_ text: String,
addingLineSeparator separator: String = "\n",
trailing: Bool = true) throws {
let newText = trailing ? text + separator : separator + text
let newData = Data(newText.utf8)
do {
let fileHandle = try FileHandle(forWritingTo: self)
defer{ fileHandle.closeFile() }
fileHandle.seekToEndOfFile()
fileHandle.write(newData)
} catch let error as NSError where error.domain == NSCocoaErrorDomain && error.code == NSFileNoSuchFileError {
try newData.write(to: self, options: .atomic)
}
}
}
这个简短而简单:
let data = Data(...)
let path = URL.documentsDirectory.appending(path: "my_file.txt")
if FileManager().fileExists(atPath: path.path) {
let fileHandle = try FileHandle(forWritingTo: path)
fileHandle.seekToEndOfFile()
try fileHandle.write(contentsOf: data)
} else {
// create if not exists
try data.write(to: path)
}