Frage

Was ist die angemessene Art und Weise mit großen Textdateien in Objective-C des Umgangs? Nehmen wir an, ich brauche jede Zeile einzeln zu lesen und jede Zeile als NSString behandeln wollen. Was ist der effizienteste Weg, dies zu tun?

Eine Lösung ist die NSString-Methode:

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

und spaltete dann die Zeilen mit einem Neuen-Zeile-Separator und dann über die Elemente in dem Array iterieren. Dies scheint jedoch ziemlich ineffizient. Gibt es keine einfache Möglichkeit, die Datei als Stream zu behandeln, über jede Zeile aufzählt, statt zu lesen einfach alles auf einmal in? Kinda wie Java java.io.BufferedReader.

War es hilfreich?

Lösung

Das ist eine große Frage. Ich denke, @Diederik hat eine gute Antwort, obwohl es bedauerlich ist, dass Cocoa keinen Mechanismus hat für genau das, was Sie tun möchten.

NSInputStream können Sie Stücke von N Bytes lesen (sehr ähnlich java.io.BufferedReader), aber man muss es konvertieren zu einem NSString auf eigener Faust, dann für neue Zeilen scannen (oder was auch immer andere Trennzeichen) und speichern Sie alle verbleibenden Zeichen für die nächste Lese oder mehr Zeichen lesen, wenn ein newline nicht hat wurde noch lesen. ( NSFileHandle können Sie einen NSData lesen, die Sie dann konvertieren zu einem NSString, aber es ist im wesentlichen der gleiche Vorgang.)

Apple hat eine -Stream Programming Guide , die füllen kann helfen in den Details und diese Frage sO auch helfen, wenn Sie gehen mit uint8_t* Puffer zu tun haben.

Wenn Sie vorhaben, Strings wie dies häufig (vor allem in den verschiedenen Teilen des Programms) zu lesen, es wäre eine gute Idee sein, dieses Verhalten in einer Klasse zu kapseln, die die Details für Sie behandeln können, oder auch Subklassen NSInputStream ( es ist

Andere Tipps

Dies funktioniert für die allgemein einen String von Text lesen. Wenn Sie möchten mehr Text lesen (große Größe von Text) , dann die Methode verwenden, die anderen Leute hier so genannt wurden als gepufferte (die Größe des Textes im Speicherplatz reservieren) .

Sagen Sie bitte eine Textdatei lesen.

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

Sie wollen neue Linie loszuwerden.

// 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:@";"]];

Da haben Sie es.

Das sollte es tun:

#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;
}

Verwenden Sie wie folgt vor:

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

Dieser Code liest Nicht-Zeilenumbrüche aus der Datei, zu einer Zeit, bis 4095 auf. Wenn Sie eine Linie haben, die länger als 4095 Zeichen ist, hält sie zu lesen, bis es eine neue Zeile oder End-of-Datei trifft.

Hinweis : Ich habe diesen Code nicht getestet haben. Testen Sie es, bevor es verwendet wird.

Mac OS X Unix, Objective-C ist C Obermenge, so dass Sie nur Altschule fopen und fgets von <stdio.h> verwenden können. Es ist garantiert zu arbeiten.

[NSString stringWithUTF8String:buf] konvertiert C-String zu NSString. Es gibt auch Methoden für Strings in anderen Codierungen zu schaffen und ohne das Kopieren zu schaffen.

Sie können NSInputStream verwenden, die eine grundlegende Implementierung für Dateistreams hat. Sie können Bytes in einen Puffer (read:maxLength: Methode) gelesen. Sie haben die Puffer für neue Zeilen scannen selbst.

Die entsprechende Art und Weise Textdateien in Cocoa / Objective-C ist dokumentiert in Apples String Programmieranleitung zu lesen. Der Abschnitt für Lesen und Schreiben von Dateien nur sein sollte, was Sie sind nach. PS: Was ist eine „Linie“? Zwei Abschnitte einer Zeichenfolge durch „\ n“ voneinander getrennt? Oder "\ r"? Oder "\ r \ n"? Oder vielleicht sind Sie eigentlich nach den Absätzen? Die zuvor erwähnte Führung enthält auch einen Abschnitt eine Zeichenkette in Zeilen oder Absätze auf splitten. (Dieser Abschnitt ist „Die Absätze und Zeilenumbrüche“ genannt und wird in dem linken Seite im Menü der Seite verknüpft ich oben erwähnt. Leider ist diese Seite nicht zulassen, dass mir mehr als eine URL zu schreiben, wie ich bin nicht ein vertrauenswürdiger Benutzer noch.)

Um Knuth paraphrasiert: vorzeitige Optimierung ist die Wurzel aller Übel. Sie nicht einfach davon ausgehen, dass „die gesamte Datei in den Speicher einzulesen“ langsam ist. Haben Sie es gebenchmarkt? Wissen Sie, dass es wirklich die gesamte Datei in den Speicher liest? Vielleicht gibt es einfach ein Proxy-Objekt und hält hinter den Kulissen zu lesen, wie Sie die Zeichenfolge verbrauchen? ( Disclaimer:.. Ich habe keine Ahnung, ob NSString dies tatsächlich tut es könnte möglicherweise ) Der Punkt ist: zuerst mit dem dokumentierten Weg gehen, Dinge zu tun. Wenn dann Benchmarks zeigen, dass dies nicht die Leistung hat Sie wünschen, zu optimieren.

Viele dieser Antworten sind lange Stücke von Code oder sie lesen in der gesamten Datei. Ich mag die c Methoden für diese Aufgabe sehr verwenden.

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

, dass fgetln Hinweis nicht Ihre Newline-Zeichen halten. Auch wir +1 die Länge des str weil wir Platz für die NULL-Terminierung machen wollen.

Um eine Datei Zeile für Zeile (auch für extreme große Dateien) lesen kann durch die folgenden Funktionen ausgeführt werden:

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

Oder:

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

Die Klasse DDFileReader, die dies ermöglicht, ist die folgende:

Schnittstellendatei (.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

Implementation (.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

Die Klasse wurde von Dave DeLong

Wie @porneL sagte, die C api ist sehr praktisch.

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

Wie andere haben beantwortet beide NSInputStream und NSFileHandle sind feine Optionen, aber es kann auch in einem relativ kompakten Art und Weise mit NSData und Speicher-Mapping durchgeführt werden:

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

Diese Antwort ist nicht ObjC aber C.

Da ObjC ist 'C' basiert, warum nicht fgets verwenden?

Und ja, ich bin sicher, dass ObjC hat seine eigene Methode ist - ich bin einfach nicht kompetent genug noch zu wissen, was es ist:)

von @ Adam Rosenfield Antwort, die Formatierungszeichenfolge von fscanf würde wie unten geändert werden:

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

es wird in osx, Linux arbeiten, windows Zeilenenden.

Mit Kategorie oder Erweiterung unseres Leben ein bisschen leichter zu machen.

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
}

Ich fand Antwort von @lukaswelte und Code von Dave DeLong sehr hilfreich. Ich war auf der Suche nach einer Lösung für dieses Problem, aber notwendig, um durch \r\n große Dateien zu analysieren nicht nur \n.

Der Code geschrieben enthält einen Fehler, wenn sie von mehr als ein Zeichen Parsen. Ich habe den Code wie unten verändert.

H-Datei:

#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

.m-Datei:

#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

Ich füge dies, weil alle anderen Antworten, die ich versuchte, kurz eine oder andere Weise fiel. Das folgende Verfahren kann große Dateien, beliebig lange Leitungen handhaben, sowie Leerzeilen. Es wird mit dem tatsächlichen Inhalt getestet worden und wird von dem Ausgang Newline-Zeichen Streifen aus.

- (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;
}

Kredit geht an @ Adam Rosenfield und @sooop

Hier ist eine schöne einfache Lösung, die ich für kleinere Dateien verwenden:

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

dieses Skript verwenden, es funktioniert super:

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);
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top