स्विफ्ट में एक फ़ाइल / URL लाइन-बाय-लाइन पढ़ें


80

मैं एक फ़ाइल को पढ़ने की कोशिश कर रहा हूं 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
}

मैं कुछ कारणों से इससे बहुत खुश नहीं हूं। एक, मैं कुछ किलोबाइट से लेकर सैकड़ों एमबी आकार की फाइलों के साथ काम कर रहा हूं। जैसा कि आप कल्पना कर सकते हैं, स्ट्रिंग्स के साथ काम करना बड़ा और धीमा है। दूसरे, यह यूआई को जमा देता है जब यह निष्पादित कर रहा है - फिर से, अच्छा नहीं।

मैंने इस कोड को एक अलग थ्रेड में चलाने पर ध्यान दिया है, लेकिन मुझे इससे परेशानी हो रही है, और इसके अलावा, यह अभी भी विशाल तार से निपटने की समस्या को हल नहीं करता है।

मैं क्या करना चाहूंगा निम्नलिखित स्यूडोकोड की तर्ज पर कुछ है:

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

मैं स्विफ्ट में इसे कैसे पूरा करूंगा?

जिन फ़ाइलों के बारे में मैं पढ़ रहा हूँ, उनके बारे में कुछ नोट: सभी फ़ाइलों में \nया तो अलग किए गए शॉर्ट (<255 वर्ण) तार होते हैं \r\n। फ़ाइलों की लंबाई ~ 100 लाइनों से लेकर 50 मिलियन से अधिक तक होती है। वे यूरोपीय वर्ण, और / या उच्चारण के साथ वर्ण शामिल हो सकते हैं।


क्या आप सरणी को डिस्क पर लिखने के लिए चाहते हैं जैसे आप जाते हैं या बस ओएस को मेमोरी के साथ संभालना है? क्या मैक चलाने वाले के पास इतना रैम होगा कि आप फ़ाइल को मैप कर सकें और उसके साथ काम कर सकें? एकाधिक कार्य करना काफी आसान है, और मुझे लगता है कि आपके पास कई कार्य हो सकते हैं जो विभिन्न स्थानों पर फ़ाइल को पढ़ना शुरू करते हैं।
मक्षोम

जवाबों:


150

(कोड अब स्विफ्ट 2.2 / एक्सकोड 7.3 के लिए है। पुराने संस्करणों को संपादित इतिहास में पाया जा सकता है, अगर किसी को इसकी आवश्यकता है। स्विफ्ट 3 के लिए एक अद्यतन संस्करण अंत में प्रदान किया गया है।)

निम्नलिखित स्विफ्ट कोड विभिन्न उत्तरों से काफी हद तक प्रेरित है कि कैसे 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 line in aStreamReader {
    print(line)
}

SequenceTypeप्रोटोकॉल लागू करके (तुलना करें http://robots.thoughtbot.com/swift-fterences ):

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

स्विफ्ट 3 / एक्सकोड 8 बीटा 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()
        }
    }
}

1
@ मैट: इससे कोई फर्क नहीं पड़ता। आप एक्सटेंशन को "मुख्य वर्ग" के रूप में एक ही स्विफ्ट फ़ाइल में, या एक अलग फ़ाइल में रख सकते हैं। - वास्तव में आपको वास्तव में विस्तार की आवश्यकता नहीं है। आप generate()फंक्शन स्ट्रीमर क्लास में जोड़ सकते हैं और घोषित कर सकते हैं class StreamReader : Sequence { ... }। लेकिन यह कार्यक्षमता के अलग-अलग टुकड़ों के लिए एक्सटेंशन का उपयोग करने के लिए अच्छी स्विफ्ट शैली प्रतीत होती है।
मार्टिन आर

1
@zanzoken: आप किस तरह के URL का उपयोग कर रहे हैं? उपरोक्त कोड केवल फ़ाइल URL के लिए काम करता है । इसका उपयोग सामान्य सर्वर URL से पढ़ने के लिए नहीं किया जा सकता है। सवाल के तहत stackoverflow.com/questions/26674182/… और मेरी टिप्पणियों की तुलना करें ।
मार्टिन आर

2
@zanzoken: मेरा कोड पाठ फ़ाइलों के लिए है , और फ़ाइल को एक निर्दिष्ट एन्कोडिंग (डिफ़ॉल्ट रूप से UTF-8) का उपयोग करने की अपेक्षा करता है। यदि आपके पास मनमाना बाइनरी बाइट्स (जैसे कि एक छवि फ़ाइल) के साथ एक फ़ाइल है, तो डेटा-> स्ट्रिंग रूपांतरण विफल हो जाएगा।
मार्टिन आर

1
@ ज़नज़ोकेन: एक छवि से स्कैन लाइनें पढ़ना एक पूरी तरह से अलग विषय है और इसका इस कोड से कोई लेना-देना नहीं है, क्षमा करें। मुझे यकीन है कि यह CoreGraphics विधियों के साथ उदाहरण के लिए किया जा सकता है, लेकिन मुझे आपके लिए तत्काल संदर्भ नहीं चाहिए।
मार्टिन आर

2
@ डीसीडीसीwhile !aStreamReader.atEof { try autoreleasepool { guard let line = aStreamReader.nextLine() else { return } ...code... } }
एपरोसेप्स

26

लाइन द्वारा पाठ फ़ाइल लाइन पढ़ने के लिए कुशल और सुविधाजनक वर्ग (स्विफ्ट 4, स्विफ्ट 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>? = nil
      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))      
}

गितुब पर रिपोजिटरी


6

स्विफ्ट 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))      
}

4

मैं खेल के लिए लेट हो गया हूं, लेकिन यहां उस उद्देश्य के लिए मैंने जो छोटी कक्षा लिखी है। कुछ अलग-अलग प्रयासों (उपवर्ग की कोशिश 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)
    }
}

मुझे यह पसंद है, लेकिन फिर भी इसमें सुधार किया जा सकता है। उपयोग करने वाले पॉइंटर्स बनाना withCStringआवश्यक नहीं है (और वास्तव में वास्तव में असुरक्षित), आप बस कॉल कर सकते हैं return fopen(self.path, self.mode)। यदि कोई फ़ाइल वास्तव में खोला जा सकता है, तो वर्तमान में readline()क्रैश हो जाएगा। UnsafePointer<CChar>कलाकारों की जरूरत नहीं है। अंत में, आपका उपयोग उदाहरण संकलित नहीं करता है।
मार्टिन आर

4

यह फ़ंक्शन एक फ़ाइल URL लेता है और एक अनुक्रम देता है जो फ़ाइल की हर पंक्ति को लौटाएगा, उन्हें आलसी रूप से पढ़ना। यह स्विफ्ट 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
    }
  })
}

उदाहरण के लिए, यहां आप इसका उपयोग अपने ऐप बंडल में "फू" नामक फ़ाइल की प्रत्येक पंक्ति को प्रिंट करने के लिए करेंगे:

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: "")
}

मैंने मार्टिन ब्राउन की टिप्पणी द्वारा उल्लिखित एक स्मृति रिसाव को हटाने के लिए एलेक्स ब्राउन के उत्तर को संशोधित करके, और इसे स्विफ्ट 5 के लिए अपडेट करके विकसित किया।


2

इस उत्तर का प्रयास करें , या मैक ओएस स्ट्रीम प्रोग्रामिंग गाइड पढ़ें

आप पा सकते हैं कि प्रदर्शन वास्तव में बेहतर होगा 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)
}

मैं सुझाव (ओं) की सराहना करता हूं, लेकिन मैं विशेष रूप से स्विफ्ट में कोड की तलाश कर रहा हूं। इसके अतिरिक्त, मैं एक बार में सभी लाइनों के बजाय एक पंक्ति में काम करना चाहता हूं।
मैट

तो क्या आप एक लाइन के साथ काम करना चाहते हैं, तो इसे जारी करें और अगले एक को पढ़ें? मुझे यह सोचने की आवश्यकता होगी कि स्मृति में इसके साथ काम करने के लिए यह तेज होने वाला है। क्या उन्हें क्रम में संसाधित करने की आवश्यकता है? यदि नहीं, तो आप एन्यूमरेशन ब्लॉक का उपयोग करके नाटकीय रूप से सरणी के प्रसंस्करण को गति दे सकते हैं।
मक्षोम

मैं एक बार में कई लाइनों को पकड़ना चाहता हूं, लेकिन मुझे सभी लाइनों को लोड करने की आवश्यकता नहीं होगी। क्रम में होने के नाते, यह महत्वपूर्ण नहीं है, लेकिन यह मददगार होगा।
मैट

यदि आप case 0...127गैर- ASCII वर्णों का विस्तार करते हैं तो क्या होगा ?
मैट

1
खैर जो वास्तव में आपकी फ़ाइलों में किस वर्ण एन्कोडिंग पर निर्भर करता है। यदि वे यूनिकोड के कई स्वरूपों में से एक हैं, तो आपको इसके लिए कोड की आवश्यकता होगी, यदि वे कई प्री-यूनिकोड पीसी "कोड-पेज" सिस्टम में से एक हैं, तो आपको उसे डिकोड करना होगा। फाउंडेशन लाइब्रेरी आपके लिए यह सब करती है, यह आपके लिए बहुत काम की चीज है।
ग्रिमैक्स

2

यह अच्छा है कि पुराने सी-फॉल्ट सी एपीआई स्विफ्ट में बहुत आरामदायक है, जब आप अनसेफ पॉइंटर को ग्रो कर लेते हैं। यहाँ एक साधारण बिल्ली है जो स्टडिन और प्रिंट से स्टडआउट लाइन-बाय-लाइन पढ़ती है। आपको फाउंडेशन की भी जरूरत नहीं है। डार्विन पर्याप्त:

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

1
"लाइन द्वारा" को संभालने में विफल रहता है। यह आउटपुट के लिए इनपुट डेटा को ब्लिट करता है, और सामान्य वर्णों और लाइन अंत वर्णों के बीच भिन्न को नहीं पहचानता है। जाहिर है, आउटपुट में इनपुट के समान लाइनें होती हैं, लेकिन ऐसा इसलिए है क्योंकि न्यूलाइन को भी धुंधला कर दिया जाता है।
एलेक्स ब्राउन

3
@AlexBrown: यह सच नहीं है। fgets()एक नई लाइन वर्ण (या EOF) तक (और सहित) वर्ण पढ़ता है। या मैं आपकी टिप्पणी को गलत समझ रहा हूं?
मार्टिन आर

@ मर्टिन आर, कृपया यह स्विफ्ट 4/5 में कैसे दिखेगा? मुझे लाइन-फाईल पर फ़ाइल लाइन पढ़ने के लिए इस सरल चीज़ की आवश्यकता है -
gebroscience

1

या आप बस एक का उपयोग कर सकते हैं 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)")
}

यह आसान है, आलसी है, और अन्य स्विफ्ट चीजों जैसे कि एन्युमेरेटर्स और फंक्शंस जैसे कि मैप, कम, फिल्टर के साथ चेन करना आसान है; 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) { ... }

अब जवाब देने के लिए बहुत धन्यवाद जिसने मुझे गेटलाइन कोड दिया!
एलेक्स ब्राउन

1
जाहिर है मैं एन्कोडिंग को पूरी तरह से नजरअंदाज कर रहा हूं। पाठक के लिए एक अभ्यास के रूप में छोड़ दिया।
एलेक्स ब्राउन

ध्यान दें कि getline()डेटा के लिए एक बफर आवंटित करने के रूप में आपका कोड मेमोरी लीक करता है।
मार्टिन आर

1

(नोट: मैं MacOS Sierra 10.12.3 के साथ Xcode 8.2.1 पर स्विफ्ट 3.0.1 का उपयोग कर रहा हूं)

मैंने यहाँ देखे गए सभी उत्तरों को याद किया कि वह LF या CRLF की तलाश में था। यदि सबकुछ ठीक हो जाता है, तो वह सिर्फ एलएफ पर मैच कर सकता है और अंत में अतिरिक्त सीआर के लिए लौटे स्ट्रिंग की जांच कर सकता है। लेकिन सामान्य क्वेरी में कई खोज स्ट्रिंग शामिल हैं। दूसरे शब्दों में, परिसीमनकर्ता को एक होना चाहिए Set<String>, जहां सेट न तो खाली है और न ही एक स्ट्रिंग के बजाय खाली स्ट्रिंग है।

पिछले साल इस पर मेरी पहली कोशिश में, मैंने "सही काम" करने और तारों के एक सामान्य सेट की खोज करने की कोशिश की। यह बहुत कठिन था; आपको एक पूर्ण विकसित पार्सर और राज्य मशीनों और इस तरह की आवश्यकता है। मैंने इसे छोड़ दिया और इस परियोजना का हिस्सा था।

अब मैं फिर से प्रोजेक्ट कर रहा हूं, और फिर से उसी चुनौती का सामना कर रहा हूं। अब मैं CR और LF पर हार्ड-कोड सर्च करने जा रहा हूं। मुझे नहीं लगता कि किसी को सीआर / एलएफ पार्सिंग के बाहर इस तरह के दो स्वतंत्र और अर्ध-निर्भर पात्रों पर खोज करने की आवश्यकता होगी।

मैं खोज तरीकों का उपयोग कर रहा हूँ Data , इसलिए मैं यहां स्ट्रिंग एनकोडिंग और सामान नहीं कर रहा हूं। बस कच्चे बाइनरी प्रसंस्करण। बस मान लीजिए कि मुझे एएससीआईआई सुपरसेट मिला, जैसे आईएसओ लैटिन -1 या यूटीएफ -8, यहां। आप अगली-उच्च परत पर स्ट्रिंग एन्कोडिंग को संभाल सकते हैं, और आप इस बात पर ध्यान केंद्रित करते हैं कि क्या माध्यमिक कोड-पॉइंट्स के साथ एक सीआर / एलएफ अभी भी सीआर या एलएफ के रूप में गिना जाता है।

एल्गोरिथ्म: बस अपने वर्तमान बाइट ऑफसेट से अगले सीआर और अगले एलएफ के लिए खोज जारी रखें ।

  • यदि न तो पाया जाता है, तो अगले डेटा स्ट्रिंग को वर्तमान ऑफ़सेट से लेकर एंड-ऑफ़-डेटा तक माना जाता है। ध्यान दें कि टर्मिनेटर की लंबाई 0. है। इसे अपने रीडिंग लूप के अंत के रूप में चिह्नित करें।
  • यदि कोई LF पहले पाया जाता है, या केवल एक LF पाया जाता है, तो वर्तमान ऑफ़सेट से LF तक के अगले डेटा स्ट्रिंग पर विचार करें। ध्यान दें कि टर्मिनेटर की लंबाई 1. एलएफ के बाद ऑफसेट को स्थानांतरित करें।
  • यदि केवल एक सीआर पाया जाता है, तो एलएफ मामले की तरह करें (बस एक अलग बाइट मान के साथ)।
  • अन्यथा, हमें एक सीआर मिला, जिसके बाद एक एलएफ था।
    • यदि दो समीप हैं, तो एलएफ मामले की तरह संभाल लें, इसके अलावा टर्मिनेटर की लंबाई 2 होगी।
    • यदि उनके बीच एक बाइट है, और कहा कि बाइट भी सीआर है, तो हमें "विंडोज डेवलपर ने एक पाठ बाइनरी \ n लिखा है जबकि पाठ मोड में, एक \ r \ r \ n" समस्या दे रहा है। इसके अलावा इसे 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लंबाई का एक ब्लॉक है जो कम से कम एक गीगाबाइट का एक महत्वपूर्ण अंश है, तो आप तब भी एक हिट लेंगे जब वर्तमान बाइट ऑफसेट से अधिक सीआर या एलएफ मौजूद नहीं होगा; हमेशा हर पुनरावृत्ति के दौरान अंत तक फलहीन खोज। विखंडू में डेटा पढ़ने से मदद मिलेगी:

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

}

आपको इन विचारों को स्वयं एक साथ मिलाना होगा, क्योंकि मैंने अभी तक ऐसा नहीं किया है। विचार करें:

  • बेशक, आपको लाइनों को पूरी तरह से एक चंक में निहित पर विचार करना होगा।
  • लेकिन आपको संभालना होगा जब एक पंक्ति के छोर आसन्न चोंच में होते हैं।
  • या जब समापन बिंदु के बीच कम से कम एक हिस्सा है
  • बड़ी जटिलता तब होती है जब लाइन एक मल्टी-बाइट अनुक्रम के साथ समाप्त होती है, लेकिन कहा जाता है कि अनुक्रम दो विखंडू को काटता है! (बस सीआर में समाप्त होने वाली एक पंक्ति जो कि चंक में अंतिम बाइट भी है, एक समतुल्य मामला है, क्योंकि आपको यह देखने के लिए अगली बार पढ़ने की आवश्यकता है कि क्या आपका जस्ट-सीआर वास्तव में एक CRLF या CR-CRLF है। जब समान शेंग्नि होते हैं। सीआर-सीआर के साथ चंक समाप्त होता है।)
  • और आपको यह संभालने की जरूरत है कि आपके वर्तमान ऑफसेट से अधिक टर्मिनेटर नहीं हैं, लेकिन एंड-ऑफ-डेटा बाद के चंक में है।

सौभाग्य!


1

@ Dankogai के उत्तर के बाद , मैंने स्विफ्ट 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()

इसने मेरे लिए काम किया।

धन्यवाद


0

मुझे एक ऐसा संस्करण चाहिए था जो लगातार बफर या डुप्लिकेट कोड को संशोधित नहीं करता था, क्योंकि दोनों अक्षम हैं, और किसी भी आकार के बफर (1 बाइट सहित) और किसी भी सीमांकक के लिए अनुमति देगा। यह एक सार्वजनिक विधि है: readline()। इस विधि को कॉल करने से अगली पंक्ति का स्ट्रिंग मान वापस आ जाएगा या EOF पर शून्य हो जाएगा।

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)
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.