Obiettivo-C: lettura di un file riga per riga


140

Qual è il modo appropriato di trattare file di testo di grandi dimensioni in Objective-C? Diciamo che ho bisogno di leggere ogni riga separatamente e voglio trattare ogni riga come una NSString. Qual è il modo più efficiente per farlo?

Una soluzione sta usando il metodo NSString:

+ (id)stringWithContentsOfFile:(NSString *)path 
      encoding:(NSStringEncoding)enc 
      error:(NSError **)error 

e quindi dividere le linee con un separatore newline e quindi scorrere gli elementi nell'array. Tuttavia, questo sembra abbastanza inefficiente. Non esiste un modo semplice per trattare il file come un flusso, enumerandolo su ogni riga, invece di leggerlo tutto in una volta? Un po 'come Java java.io.BufferedReader di Java.


1
Un po 'in ritardo, ma dai un'occhiata a [NSScanner scanUpToString: @ "\ n" intoString: & read], supponendo che tu voglia leggere ogni riga nella stringa' read '.
Hauntsaninja,

Si prega di dare un'occhiata a questa domanda simile . Ho creato un progetto che si occupa di leggere i file riga per riga .
JJD

Risposte:


63

Questa è un'ottima domanda. Penso che @Diederik abbia una buona risposta, anche se è un peccato che Cocoa non abbia un meccanismo esattamente per quello che vuoi fare.

NSInputStreamti permette di leggere blocchi di N byte (molto simili a java.io.BufferedReader), ma devi convertirlo in uno NSStringda solo, quindi cercare nuove righe (o qualunque altro delimitatore) e salvare eventuali caratteri rimanenti per la lettura successiva o leggere più caratteri se una nuova riga non è stata ancora letta. ( NSFileHandleti permette di leggere un NSDatache puoi quindi convertire in un NSString, ma è essenzialmente lo stesso processo.)

Apple ha una Guida alla programmazione di stream che può aiutarti a compilare i dettagli, e questa domanda SO può aiutare anche se hai a che fare con uint8_t*buffer.

Se stai leggendo stringhe come questa frequentemente (specialmente in diverse parti del tuo programma), sarebbe una buona idea incapsulare questo comportamento in una classe in grado di gestire i dettagli per te, o anche sottoclassare NSInputStreamprogettato per essere sottoclasse ) e l'aggiunta di metodi che consentono di leggere esattamente ciò che si desidera.

Per la cronaca, penso che questa sarebbe una bella caratteristica da aggiungere, e presenterò una richiesta di miglioramento per qualcosa che lo rende possibile. :-)


Modifica: risulta che questa richiesta esiste già. C'è un radar risalente al 2006 per questo (rdar: // 4742914 per le persone interne ad Apple).


10
Vedi l'approccio globale di Dave DeLong a questo problema qui: stackoverflow.com/questions/3707427#3711079
Quinn Taylor

È anche possibile utilizzare NSData semplici e la mappatura della memoria. Ho creato una risposta con esempio di codice che ha la stessa API come implementazione di Dave DeLong NSFileHandle: stackoverflow.com/a/21267461/267043
Bjørn Olav Ruud

95

Questo funzionerà per la lettura generale a Stringda Text. Se desideri leggere un testo più lungo (grandi dimensioni del testo) , usa il metodo che è stato citato in altre persone come buffer (riserva la dimensione del testo nello spazio di memoria) .

Di 'che leggi un file di testo.

NSString* filePath = @""//file path...
NSString* fileRoot = [[NSBundle mainBundle] 
               pathForResource:filePath ofType:@"txt"];

Vuoi sbarazzarti della nuova linea.

// read everything from text
NSString* fileContents = 
      [NSString stringWithContentsOfFile:fileRoot 
       encoding:NSUTF8StringEncoding error:nil];

// first, separate by new line
NSArray* allLinedStrings = 
      [fileContents componentsSeparatedByCharactersInSet:
      [NSCharacterSet newlineCharacterSet]];

// then break down even further 
NSString* strsInOneLine = 
      [allLinedStrings objectAtIndex:0];

// choose whatever input identity you have decided. in this case ;
NSArray* singleStrs = 
      [currentPointString componentsSeparatedByCharactersInSet:
      [NSCharacterSet characterSetWithCharactersInString:@";"]];

Ecco qua.


17
ho un file da 70 mb, usare questo codice per leggere il file non mi fa aumentare la memoria in modo lineare. Qualcuno può aiutarmi?
GameLoading

37
Questa non è una risposta alla domanda. La domanda era leggere un file riga per riga per ridurre l'utilizzo della memoria
dooz,

34

Questo dovrebbe fare il trucco:

#include <stdio.h>

NSString *readLineAsNSString(FILE *file)
{
    char buffer[4096];

    // tune this capacity to your liking -- larger buffer sizes will be faster, but
    // use more memory
    NSMutableString *result = [NSMutableString stringWithCapacity:256];

    // Read up to 4095 non-newline characters, then read and discard the newline
    int charsRead;
    do
    {
        if(fscanf(file, "%4095[^\n]%n%*c", buffer, &charsRead) == 1)
            [result appendFormat:@"%s", buffer];
        else
            break;
    } while(charsRead == 4095);

    return result;
}

Utilizzare come segue:

FILE *file = fopen("myfile", "r");
// check for NULL
while(!feof(file))
{
    NSString *line = readLineAsNSString(file);
    // do stuff with line; line is autoreleased, so you should NOT release it (unless you also retain it beforehand)
}
fclose(file);

Questo codice legge i caratteri non newline dal file, fino a 4095 alla volta. Se hai una linea più lunga di 4095 caratteri, continua a leggere fino a quando non raggiunge una nuova riga o fine del file.

Nota : non ho testato questo codice. Si prega di testarlo prima di usarlo.


1
basta cambiare [risultato appendFormat: "% s", buffer]; a [risultato appendFormat: @ "% s", buffer];
Codezy,

1
come modificheresti il ​​formato per accettare linee vuote, o piuttosto linee costituite da un singolo carattere di nuova riga?
Jakev,

Questo si interrompe presto per me dopo 812 linee. La riga 812 è "... 3 in più", e questo sta facendo in modo che il lettore emetta stringhe vuote.
sudo,

1
Ho aggiunto un segno di spunta per superare le righe vuote: int fscanResult = fscanf (file, "% 4095 [^ \ n]% n% * c", buffer e & charsRead); if (fscanResult == 1) {[risultato appendFormat: @ "% s", buffer]; } else {if (feof (file)) {break; } else if (ferror (file)! = 0) {break; } fscanf (file, "\ n", nil, & charsRead); rompere; }
Vai a Rose-Hulman il

1
Se sto leggendo bene la documentazione di fscanf, "%4095[^\n]%n%*c"consumerò silenziosamente e getterò via un carattere con ogni buffer letto. Sembra che questo formato presupponga che le linee saranno più corte della lunghezza del buffer.
Blago,

12

Mac OS X è Unix, Objective-C è superset C, quindi puoi semplicemente usare la vecchia scuola fopene fgetsda <stdio.h>. È garantito per funzionare.

[NSString stringWithUTF8String:buf]convertirà la stringa C in NSString. Esistono anche metodi per creare stringhe in altre codifiche e creare senza copiare.


[la copia del commento anonimo] fgetsincluderà il '\n'carattere, quindi potresti volerlo rimuovere prima di convertire la stringa.
Kornel,

9

È possibile utilizzare NSInputStreamquale ha un'implementazione di base per i flussi di file. È possibile leggere i byte in un buffer ( read:maxLength:metodo). Devi scansionare tu stesso il buffer per le nuove righe.


6

Il modo appropriato di leggere i file di testo in Cocoa / Objective-C è documentato nella guida alla programmazione delle stringhe di Apple. La sezione per leggere e scrivere file dovrebbe essere proprio quello che stai cercando. PS: Cos'è una "linea"? Due sezioni di una stringa separate da "\ n"? O "\ r"? O "\ r \ n"? O forse stai effettivamente seguendo i paragrafi? La guida precedentemente menzionata include anche una sezione sulla divisione di una stringa in righe o paragrafi. (Questa sezione è chiamata "Paragrafi e interruzioni di riga", ed è collegata nel menu di sinistra della pagina che ho indicato sopra. Sfortunatamente questo sito non mi consente di pubblicare più di un URL come sono non ancora un utente affidabile.)

Per parafrasare Knuth: l'ottimizzazione prematura è la radice di tutti i mali. Non dare per scontato che "leggere l'intero file in memoria" sia lento. L'hai confrontato? Sai che in realtà legge l'intero file in memoria? Forse restituisce semplicemente un oggetto proxy e continua a leggere dietro le quinte mentre consumi la stringa? ( Dichiarazione di non responsabilità: non ho idea se NSString lo faccia davvero. Probabilmente potrebbe. ) Il punto è: prima di tutto segui il modo documentato di fare le cose. Quindi, se i benchmark mostrano che questo non ha le prestazioni che desideri, ottimizza.


Dal momento che menzioni i finali di riga CRLF (Windows): questo è in realtà un caso che interrompe il modo Objective-C di fare le cose. Se si utilizza uno dei -stringWithContentsOf*metodi seguiti da -componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet], vede \re \nseparatamente e aggiunge una riga vuota dopo ogni riga.
Siobhán,

Detto questo, la soluzione fgets fallisce nei file solo CR. Ma quelli sono (teoricamente) rari al giorno d'oggi, e i budget funzionano sia per LF che per CRLF.
Siobhán,

6

Molte di queste risposte sono frammenti di codice lunghi o letti nell'intero file. Mi piace usare i metodi c proprio per questo compito.

FILE* file = fopen("path to my file", "r");

size_t length;
char *cLine = fgetln(file,&length);

while (length>0) {
    char str[length+1];
    strncpy(str, cLine, length);
    str[length] = '\0';

    NSString *line = [NSString stringWithFormat:@"%s",str];        
    % Do what you want here.

    cLine = fgetln(file,&length);
}

Nota che fgetln non manterrà il tuo personaggio newline. Inoltre, facciamo +1 sulla lunghezza della str perché vogliamo fare spazio per la terminazione NULL.


4

Per leggere un file riga per riga (anche per file di dimensioni estreme) è possibile eseguire le seguenti funzioni:

DDFileReader * reader = [[DDFileReader alloc] initWithFilePath:pathToMyFile];
NSString * line = nil;
while ((line = [reader readLine])) {
  NSLog(@"read line: %@", line);
}
[reader release];

O:

DDFileReader * reader = [[DDFileReader alloc] initWithFilePath:pathToMyFile];
[reader enumerateLinesUsingBlock:^(NSString * line, BOOL * stop) {
  NSLog(@"read line: %@", line);
}];
[reader release];

La classe DDFileReader che abilita ciò è la seguente:

File di interfaccia (.h):

@interface DDFileReader : NSObject {
    NSString * filePath;

    NSFileHandle * fileHandle;
    unsigned long long currentOffset;
    unsigned long long totalFileLength;

    NSString * lineDelimiter;
    NSUInteger chunkSize;
}

@property (nonatomic, copy) NSString * lineDelimiter;
@property (nonatomic) NSUInteger chunkSize;

- (id) initWithFilePath:(NSString *)aPath;

- (NSString *) readLine;
- (NSString *) readTrimmedLine;

#if NS_BLOCKS_AVAILABLE
- (void) enumerateLinesUsingBlock:(void(^)(NSString*, BOOL *))block;
#endif

@end

Implementazione (.m)

#import "DDFileReader.h"

@interface NSData (DDAdditions)

- (NSRange) rangeOfData_dd:(NSData *)dataToFind;

@end

@implementation NSData (DDAdditions)

- (NSRange) rangeOfData_dd:(NSData *)dataToFind {

    const void * bytes = [self bytes];
    NSUInteger length = [self length];

    const void * searchBytes = [dataToFind bytes];
    NSUInteger searchLength = [dataToFind length];
    NSUInteger searchIndex = 0;

    NSRange foundRange = {NSNotFound, searchLength};
    for (NSUInteger index = 0; index < length; index++) {
        if (((char *)bytes)[index] == ((char *)searchBytes)[searchIndex]) {
            //the current character matches
            if (foundRange.location == NSNotFound) {
                foundRange.location = index;
            }
            searchIndex++;
            if (searchIndex >= searchLength) { return foundRange; }
        } else {
            searchIndex = 0;
            foundRange.location = NSNotFound;
        }
    }
    return foundRange;
}

@end

@implementation DDFileReader
@synthesize lineDelimiter, chunkSize;

- (id) initWithFilePath:(NSString *)aPath {
    if (self = [super init]) {
        fileHandle = [NSFileHandle fileHandleForReadingAtPath:aPath];
        if (fileHandle == nil) {
            [self release]; return nil;
        }

        lineDelimiter = [[NSString alloc] initWithString:@"\n"];
        [fileHandle retain];
        filePath = [aPath retain];
        currentOffset = 0ULL;
        chunkSize = 10;
        [fileHandle seekToEndOfFile];
        totalFileLength = [fileHandle offsetInFile];
        //we don't need to seek back, since readLine will do that.
    }
    return self;
}

- (void) dealloc {
    [fileHandle closeFile];
    [fileHandle release], fileHandle = nil;
    [filePath release], filePath = nil;
    [lineDelimiter release], lineDelimiter = nil;
    currentOffset = 0ULL;
    [super dealloc];
}

- (NSString *) readLine {
    if (currentOffset >= totalFileLength) { return nil; }

    NSData * newLineData = [lineDelimiter dataUsingEncoding:NSUTF8StringEncoding];
    [fileHandle seekToFileOffset:currentOffset];
    NSMutableData * currentData = [[NSMutableData alloc] init];
    BOOL shouldReadMore = YES;

    NSAutoreleasePool * readPool = [[NSAutoreleasePool alloc] init];
    while (shouldReadMore) {
        if (currentOffset >= totalFileLength) { break; }
        NSData * chunk = [fileHandle readDataOfLength:chunkSize];
        NSRange newLineRange = [chunk rangeOfData_dd:newLineData];
        if (newLineRange.location != NSNotFound) {

            //include the length so we can include the delimiter in the string
            chunk = [chunk subdataWithRange:NSMakeRange(0, newLineRange.location+[newLineData length])];
            shouldReadMore = NO;
        }
        [currentData appendData:chunk];
        currentOffset += [chunk length];
    }
    [readPool release];

    NSString * line = [[NSString alloc] initWithData:currentData encoding:NSUTF8StringEncoding];
    [currentData release];
    return [line autorelease];
}

- (NSString *) readTrimmedLine {
    return [[self readLine] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}

#if NS_BLOCKS_AVAILABLE
- (void) enumerateLinesUsingBlock:(void(^)(NSString*, BOOL*))block {
  NSString * line = nil;
  BOOL stop = NO;
  while (stop == NO && (line = [self readLine])) {
    block(line, &stop);
  }
}
#endif

@end

La lezione è stata tenuta da Dave DeLong


4

Proprio come ha detto @porneL, l'API C è molto utile.

NSString* fileRoot = [[NSBundle mainBundle] pathForResource:@"record" ofType:@"txt"];
FILE *file = fopen([fileRoot UTF8String], "r");
char buffer[256];
while (fgets(buffer, 256, file) != NULL){
    NSString* result = [NSString stringWithUTF8String:buffer];
    NSLog(@"%@",result);
}

4

Come altri hanno risposto, sia NSInputStream che NSFileHandle sono ottime opzioni, ma può anche essere fatto in modo abbastanza compatto con NSData e mappatura della memoria:

BRLineReader.h

#import <Foundation/Foundation.h>

@interface BRLineReader : NSObject

@property (readonly, nonatomic) NSData *data;
@property (readonly, nonatomic) NSUInteger linesRead;
@property (strong, nonatomic) NSCharacterSet *lineTrimCharacters;
@property (readonly, nonatomic) NSStringEncoding stringEncoding;

- (instancetype)initWithFile:(NSString *)filePath encoding:(NSStringEncoding)encoding;
- (instancetype)initWithData:(NSData *)data encoding:(NSStringEncoding)encoding;
- (NSString *)readLine;
- (NSString *)readTrimmedLine;
- (void)setLineSearchPosition:(NSUInteger)position;

@end

BRLineReader.m

#import "BRLineReader.h"

static unsigned char const BRLineReaderDelimiter = '\n';

@implementation BRLineReader
{
    NSRange _lastRange;
}

- (instancetype)initWithFile:(NSString *)filePath encoding:(NSStringEncoding)encoding
{
    self = [super init];
    if (self) {
        NSError *error = nil;
        _data = [NSData dataWithContentsOfFile:filePath options:NSDataReadingMappedAlways error:&error];
        if (!_data) {
            NSLog(@"%@", [error localizedDescription]);
        }
        _stringEncoding = encoding;
        _lineTrimCharacters = [NSCharacterSet whitespaceAndNewlineCharacterSet];
    }

    return self;
}

- (instancetype)initWithData:(NSData *)data encoding:(NSStringEncoding)encoding
{
    self = [super init];
    if (self) {
        _data = data;
        _stringEncoding = encoding;
        _lineTrimCharacters = [NSCharacterSet whitespaceAndNewlineCharacterSet];
    }

    return self;
}

- (NSString *)readLine
{
    NSUInteger dataLength = [_data length];
    NSUInteger beginPos = _lastRange.location + _lastRange.length;
    NSUInteger endPos = 0;
    if (beginPos == dataLength) {
        // End of file
        return nil;
    }

    unsigned char *buffer = (unsigned char *)[_data bytes];
    for (NSUInteger i = beginPos; i < dataLength; i++) {
        endPos = i;
        if (buffer[i] == BRLineReaderDelimiter) break;
    }

    // End of line found
    _lastRange = NSMakeRange(beginPos, endPos - beginPos + 1);
    NSData *lineData = [_data subdataWithRange:_lastRange];
    NSString *line = [[NSString alloc] initWithData:lineData encoding:_stringEncoding];
    _linesRead++;

    return line;
}

- (NSString *)readTrimmedLine
{
    return [[self readLine] stringByTrimmingCharactersInSet:_lineTrimCharacters];
}

- (void)setLineSearchPosition:(NSUInteger)position
{
    _lastRange = NSMakeRange(position, 0);
    _linesRead = 0;
}

@end

1

Questa risposta NON è ObjC ma C.

Poiché ObjC è basato su "C", perché non utilizzare i budget?

E sì, sono sicuro che ObjC ha il suo metodo - non sono ancora abbastanza esperto per sapere di cosa si tratta :)


5
Se non sai come farlo in Objective-C, allora perché dire che non è la risposta? Ci sono molte ragioni per non passare alla C diritta se puoi farlo diversamente. Ad esempio, le funzioni C gestiscono char * ma ci vuole molto più lavoro per leggere qualcos'altro, come codifiche diverse. Inoltre, vuole oggetti NSString. Tutto sommato, lanciare questo da soli non è solo più codice, ma anche soggetto a errori.
Quinn Taylor,

3
Sono d'accordo con te al 100%, ma ho scoperto che (a volte) è meglio ottenere una risposta che funzioni rapidamente, implementarla e quindi quando appare un'alternativa più corretta, utilizzarla. Ciò è particolarmente importante durante la prototipazione, dando l'opportunità di far funzionare qualcosa e poi progredire da lì.
KevinDTimm,

3
Ho appena realizzato che è iniziata "Questa risposta" non "La risposta". Doh! Sono d'accordo, è sicuramente meglio avere un trucco che funzioni rispetto a un codice elegante che non lo fa. Non ti ho votato a fondo, ma non è nemmeno molto utile fare un'ipotesi senza sapere cosa potrebbe avere Objective-C. Anche così, fare uno sforzo è sempre meglio di qualcuno che conosce e non aiuta ... ;-)
Quinn Taylor,

Questo non fornisce una risposta alla domanda. Per criticare o richiedere chiarimenti a un autore, lascia un commento sotto il suo post.
Robotic Cat,

1
@KevinDTimm: sono d'accordo; Mi dispiace solo di non aver notato che era una risposta di 5 anni. Forse questa è una metadomanda; le domande molto vecchie degli utenti normali dovrebbero essere contrassegnate per la revisione?
Robotic Cat

0

dalla risposta di @Adam Rosenfield, la stringa di formattazione di fscanfsarebbe cambiata come di seguito:

"%4095[^\r\n]%n%*[\n\r]"

funzionerà in osx, linux, terminazioni di linea di Windows.


0

Usare la categoria o l'estensione per semplificarci un po 'la vita.

extension String {

    func lines() -> [String] {
        var lines = [String]()
        self.enumerateLines { (line, stop) -> () in
            lines.append(line)
        }
        return lines
    }

}

// then
for line in string.lines() {
    // do the right thing
}

0

Ho trovato molto utile la risposta di @lukaswelte e il codice di Dave DeLong . Ero alla ricerca di una soluzione a questo problema, ma bisogno di file di grandi dimensioni da parse \r\nnon solo \n.

Il codice come scritto contiene un bug se analizza da più di un carattere. Ho modificato il codice come di seguito.

File .h:

#import <Foundation/Foundation.h>

@interface FileChunkReader : NSObject {
    NSString * filePath;

    NSFileHandle * fileHandle;
    unsigned long long currentOffset;
    unsigned long long totalFileLength;

    NSString * lineDelimiter;
    NSUInteger chunkSize;
}

@property (nonatomic, copy) NSString * lineDelimiter;
@property (nonatomic) NSUInteger chunkSize;

- (id) initWithFilePath:(NSString *)aPath;

- (NSString *) readLine;
- (NSString *) readTrimmedLine;

#if NS_BLOCKS_AVAILABLE
- (void) enumerateLinesUsingBlock:(void(^)(NSString*, BOOL *))block;
#endif

@end

File .m:

#import "FileChunkReader.h"

@interface NSData (DDAdditions)

- (NSRange) rangeOfData_dd:(NSData *)dataToFind;

@end

@implementation NSData (DDAdditions)

- (NSRange) rangeOfData_dd:(NSData *)dataToFind {

    const void * bytes = [self bytes];
    NSUInteger length = [self length];

    const void * searchBytes = [dataToFind bytes];
    NSUInteger searchLength = [dataToFind length];
    NSUInteger searchIndex = 0;

    NSRange foundRange = {NSNotFound, searchLength};
    for (NSUInteger index = 0; index < length; index++) {
        if (((char *)bytes)[index] == ((char *)searchBytes)[searchIndex]) {
            //the current character matches
            if (foundRange.location == NSNotFound) {
                foundRange.location = index;
            }
            searchIndex++;
            if (searchIndex >= searchLength)
            {
                return foundRange;
            }
        } else {
            searchIndex = 0;
            foundRange.location = NSNotFound;
        }
    }

    if (foundRange.location != NSNotFound
        && length < foundRange.location + foundRange.length )
    {
        // if the dataToFind is partially found at the end of [self bytes],
        // then the loop above would end, and indicate the dataToFind is found
        // when it only partially was.
        foundRange.location = NSNotFound;
    }

    return foundRange;
}

@end

@implementation FileChunkReader

@synthesize lineDelimiter, chunkSize;

- (id) initWithFilePath:(NSString *)aPath {
    if (self = [super init]) {
        fileHandle = [NSFileHandle fileHandleForReadingAtPath:aPath];
        if (fileHandle == nil) {
            return nil;
        }

        lineDelimiter = @"\n";
        currentOffset = 0ULL; // ???
        chunkSize = 128;
        [fileHandle seekToEndOfFile];
        totalFileLength = [fileHandle offsetInFile];
        //we don't need to seek back, since readLine will do that.
    }
    return self;
}

- (void) dealloc {
    [fileHandle closeFile];
    currentOffset = 0ULL;

}

- (NSString *) readLine {
    if (currentOffset >= totalFileLength)
    {
        return nil;
    }

    @autoreleasepool {

        NSData * newLineData = [lineDelimiter dataUsingEncoding:NSUTF8StringEncoding];
        [fileHandle seekToFileOffset:currentOffset];
        unsigned long long originalOffset = currentOffset;
        NSMutableData *currentData = [[NSMutableData alloc] init];
        NSData *currentLine = [[NSData alloc] init];
        BOOL shouldReadMore = YES;


        while (shouldReadMore) {
            if (currentOffset >= totalFileLength)
            {
                break;
            }

            NSData * chunk = [fileHandle readDataOfLength:chunkSize];
            [currentData appendData:chunk];

            NSRange newLineRange = [currentData rangeOfData_dd:newLineData];

            if (newLineRange.location != NSNotFound) {

                currentOffset = originalOffset + newLineRange.location + newLineData.length;
                currentLine = [currentData subdataWithRange:NSMakeRange(0, newLineRange.location)];

                shouldReadMore = NO;
            }else{
                currentOffset += [chunk length];
            }
        }

        if (currentLine.length == 0 && currentData.length > 0)
        {
            currentLine = currentData;
        }

        return [[NSString alloc] initWithData:currentLine encoding:NSUTF8StringEncoding];
    }
}

- (NSString *) readTrimmedLine {
    return [[self readLine] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}

#if NS_BLOCKS_AVAILABLE
- (void) enumerateLinesUsingBlock:(void(^)(NSString*, BOOL*))block {
    NSString * line = nil;
    BOOL stop = NO;
    while (stop == NO && (line = [self readLine])) {
        block(line, &stop);
    }
}
#endif

@end

0

Sto aggiungendo questo perché tutte le altre risposte che ho provato sono state insufficienti in un modo o nell'altro. Il seguente metodo può gestire file di grandi dimensioni, righe lunghe arbitrarie e righe vuote. È stato testato con il contenuto effettivo e eliminerà il carattere di nuova riga dall'output.

- (NSString*)readLineFromFile:(FILE *)file
{
    char buffer[4096];
    NSMutableString *result = [NSMutableString stringWithCapacity:1000];

    int charsRead;
    do {
        if(fscanf(file, "%4095[^\r\n]%n%*[\n\r]", buffer, &charsRead) == 1) {
            [result appendFormat:@"%s", buffer];
        }
        else {
            break;
        }
    } while(charsRead == 4095);

    return result.length ? result : nil;
}

Il merito va a @Adam Rosenfield e @sooop


0

Vedo che molte di queste risposte si basano sulla lettura dell'intero file di testo in memoria invece di prenderlo un pezzo alla volta. Ecco la mia soluzione nella bella Swift moderna, usando FileHandle per ridurre l'impatto della memoria:

enum MyError {
    case invalidTextFormat
}

extension FileHandle {

    func readLine(maxLength: Int) throws -> String {

        // Read in a string of up to the maximum length
        let offset = offsetInFile
        let data = readData(ofLength: maxLength)
        guard let string = String(data: data, encoding: .utf8) else {
            throw MyError.invalidTextFormat
        }

        // Check for carriage returns; if none, this is the whole string
        let substring: String
        if let subindex = string.firstIndex(of: "\n") {
            substring = String(string[string.startIndex ... subindex])
        } else {
            substring = string
        }

        // Wind back to the correct offset so that we don't miss any lines
        guard let dataCount = substring.data(using: .utf8, allowLossyConversion: false)?.count else {
            throw MyError.invalidTextFormat
        }
        try seek(toOffset: offset + UInt64(dataCount))
        return substring
    }

}

Tieni presente che ciò preserva il ritorno a capo alla fine della riga, quindi a seconda delle tue esigenze potresti voler regolare il codice per rimuoverlo.

Utilizzo: è sufficiente aprire un handle di file sul file di testo di destinazione e chiamare readLinecon una lunghezza massima adeguata: 1024 è standard per il testo normale, ma l'ho lasciato aperto nel caso in cui si sappia che sarà più breve. Nota che il comando non supererà la fine del file, quindi potresti dover controllare manualmente che non lo hai raggiunto se intendi analizzare l'intera cosa. Ecco un codice di esempio che mostra come aprire un file myFileURLe leggerlo riga per riga fino alla fine.

do {
    let handle = try FileHandle(forReadingFrom: myFileURL)
    try handle.seekToEndOfFile()
    let eof = handle.offsetInFile
    try handle.seek(toFileOffset: 0)

    while handle.offsetInFile < eof {
        let line = try handle.readLine(maxLength: 1024)
        // Do something with the string here
    }
    try handle.close()
catch let error {
    print("Error reading file: \(error.localizedDescription)"
}

-2

Ecco una bella soluzione semplice che uso per file più piccoli:

NSString *path = [[NSBundle mainBundle] pathForResource:@"Terrain1" ofType:@"txt"];
NSString *contents = [NSString stringWithContentsOfFile:path encoding:NSASCIIStringEncoding error:nil];
NSArray *lines = [contents componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"\r\n"]];
for (NSString* line in lines) {
    if (line.length) {
        NSLog(@"line: %@", line);
    }
}

Stava chiedendo come leggere una riga alla volta in modo che non leggesse l'intero contenuto in memoria. La tua soluzione crea una stringa con l'intero contenuto, quindi la divide in righe.
David,

-7

Usa questo script, funziona benissimo:

NSString *path = @"/Users/xxx/Desktop/names.txt";
NSError *error;
NSString *stringFromFileAtPath = [NSString stringWithContentsOfFile: path
                                                           encoding: NSUTF8StringEncoding
                                                              error: &error];
if (stringFromFileAtPath == nil) {
    NSLog(@"Error reading file at %@\n%@", path, [error localizedFailureReason]);
}
NSLog(@"Contents:%@", stringFromFileAtPath);

1
Ciò che @fisninear sta dicendo è che questo non risponde al desiderio dell'OP di ridurre l'utilizzo della memoria. L'OP non stava chiedendo come usare il metodo (che carica l'intero file in memoria), stava chiedendo alternative compatibili con la memoria per file di testo di grandi dimensioni. È possibile avere file di testo multi-gigabyte, il che ovviamente crea un problema di memoria.
Joshua Nozzi,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.