Objective-C: Lecture d’un fichier ligne par ligne

Quelle est la manière appropriée de traiter des fichiers texte volumineux dans Objective-C? Disons que je dois lire chaque ligne séparément et que vous voulez traiter chaque ligne comme une NSSsortingng. Quelle est la manière la plus efficace de le faire?

Une solution utilise la méthode NSSsortingng:

+ (id)ssortingngWithContentsOfFile:(NSSsortingng *)path encoding:(NSSsortingngEncoding)enc error:(NSError **)error 

divisez ensuite les lignes avec un séparateur de nouvelle ligne, puis parcourez les éléments du tableau. Cependant, cela semble assez inefficace. N’y a-t-il pas de moyen facile de traiter le fichier en tant que stream, en l’énumérant sur chaque ligne, au lieu de simplement le lire en une seule fois? Un peu comme java.io.BufferedReader de Java.

C’est une excellente question. Je pense que @Diederik a une bonne réponse, même s’il est regrettable que Cocoa ne dispose pas d’un mécanisme pour ce que vous voulez faire.

NSInputStream vous permet de lire des morceaux de N octets (très similaire à java.io.BufferedReader ), mais vous devez le convertir en NSSsortingng vous-même, puis rechercher les nouvelles lignes (ou tout autre délimiteur) et enregistrer les caractères restants pour le lire ensuite, ou lire plus de caractères si une nouvelle ligne n’a pas encore été lue. ( NSFileHandle vous permet de lire un NSData que vous pouvez ensuite convertir en NSSsortingng , mais c’est essentiellement le même processus.)

Apple a un Guide de programmation de stream qui peut vous aider à compléter les détails, et cette question peut également vous aider si vous avez affaire à des tampons uint8_t* .

Si vous allez lire des chaînes comme celle-ci fréquemment (en particulier dans différentes parties de votre programme), il serait judicieux d’encapsuler ce comportement dans une classe capable de gérer les détails pour vous, ou même de sous- NSInputStream (il est conçu pour être sous-classé ) et en ajoutant des méthodes qui vous permettent de lire exactement ce que vous voulez.

Pour la petite histoire, je pense que ce serait une fonctionnalité intéressante à append, et je déposerai une demande d’amélioration pour quelque chose qui rend cela possible. 🙂


Modifier: cette requête existe déjà. Il existe un radar datant de 2006 pour cela (rdar: // 4742914 pour les personnes internes à Apple).

Cela fonctionnera pour la lecture générale d’une Ssortingng partir de Text . Si vous souhaitez lire un texte plus long (grande taille de texte) , utilisez la méthode mentionnée ici, telle que la mise en mémoire tampon (réservez la taille du texte dans l’espace mémoire) .

Disons que vous lisez un fichier texte.

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

Vous voulez vous débarrasser de la nouvelle ligne.

 // read everything from text NSSsortingng* fileContents = [NSSsortingng ssortingngWithContentsOfFile:fileRoot encoding:NSUTF8SsortingngEncoding error:nil]; // first, separate by new line NSArray* allLinedSsortingngs = [fileContents componentsSeparatedByCharactersInSet: [NSCharacterSet newlineCharacterSet]]; // then break down even further NSSsortingng* strsInOneLine = [allLinedSsortingngs objectAtIndex:0]; // choose whatever input identity you have decided. in this case ; NSArray* singleStrs = [currentPointSsortingng componentsSeparatedByCharactersInSet: [NSCharacterSet characterSetWithCharactersInSsortingng:@";"]]; 

Voilà.

Cela devrait faire l’affaire:

 #include  NSSsortingng *readLineAsNSSsortingng(FILE *file) { char buffer[4096]; // tune this capacity to your liking -- larger buffer sizes will be faster, but // use more memory NSMutableSsortingng *result = [NSMutableSsortingng ssortingngWithCapacity: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; } 

Utilisez comme suit:

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

Ce code lit les caractères non nouveaux dans le fichier, jusqu’à 4095 à la fois. Si vous avez une ligne de plus de 4095 caractères, elle continue à lire jusqu’à ce qu’elle atteigne une nouvelle ligne ou une fin de fichier.

Note : Je n’ai pas testé ce code. Veuillez le tester avant de l’utiliser.

Mac OS X est Unix, Objective-C est un sur-ensemble C, vous pouvez donc utiliser les fonctions fopen et old-school de . C’est garanti pour fonctionner.

[NSSsortingng ssortingngWithUTF8Ssortingng:buf] convertira la chaîne C en NSSsortingng . Il existe également des méthodes pour créer des chaînes dans d’autres encodages et créer sans copier.

Vous pouvez utiliser NSInputStream qui a une implémentation de base pour les stream de fichiers. Vous pouvez lire les octets dans un tampon ( read:maxLength: method). Vous devez scanner le tampon pour les nouvelles lignes vous-même.

La manière appropriée de lire les fichiers texte dans Cocoa / Objective-C est documentée dans le guide de programmation Ssortingng d’Apple. La section sur la lecture et l’écriture des fichiers doit correspondre à ce que vous recherchez. PS: Qu’est-ce qu’une “ligne”? Deux sections d’une chaîne séparées par “\ n”? Ou “\ r”? Ou “\ r \ n”? Ou peut-être vous êtes réellement après les paragraphes? Le guide mentionné précédemment comprend également une section sur la division d’une chaîne en lignes ou en paragraphes. (Cette section s’appelle “Paragraphes et sauts de ligne” et est liée au menu de gauche de la page indiquée ci-dessus. Malheureusement, ce site ne me permet pas de publier plus d’une URL pas encore un utilisateur fiable.)

Pour paraphraser Knuth: l’optimisation prématurée est la racine de tout mal. Ne supposez pas simplement que “lire tout le fichier en mémoire” est lent. L’avez-vous évalué? Savez-vous qu’il lit réellement tout le fichier en mémoire? Peut-être retourne-t-il simplement un object proxy et continue à lire en coulisse lorsque vous consumz la chaîne? ( Déni de responsabilité: je ne sais pas si NSSsortingng fait cela. On pourrait le penser. ) Le point est le suivant: commencez par suivre la manière documentée de faire les choses. Ensuite, si les tests montrent que cela n’a pas la performance que vous désirez, optimisez.

Un grand nombre de ces réponses sont de longues portions de code ou sont lues dans le fichier entier. J’aime utiliser les méthodes c pour cette tâche.

 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'; NSSsortingng *line = [NSSsortingng ssortingngWithFormat:@"%s",str]; % Do what you want here. cLine = fgetln(file,&length); } 

Notez que fgetln ne conservera pas votre caractère de nouvelle ligne. De même, nous avons +1 la longueur de la str car nous voulons faire de la place pour la terminaison NULL.

Lire un fichier ligne par ligne (même pour les fichiers de très grande taille) peut être effectué par les fonctions suivantes:

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

Ou:

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

La classe DDFileReader qui permet cela est la suivante:

Fichier d’interface (.h):

 @interface DDFileReader : NSObject { NSSsortingng * filePath; NSFileHandle * fileHandle; unsigned long long currentOffset; unsigned long long totalFileLength; NSSsortingng * lineDelimiter; NSUInteger chunkSize; } @property (nonatomic, copy) NSSsortingng * lineDelimiter; @property (nonatomic) NSUInteger chunkSize; - (id) initWithFilePath:(NSSsortingng *)aPath; - (NSSsortingng *) readLine; - (NSSsortingng *) readTrimmedLine; #if NS_BLOCKS_AVAILABLE - (void) enumerateLinesUsingBlock:(void(^)(NSSsortingng*, BOOL *))block; #endif @end 

Mise en œuvre (.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:(NSSsortingng *)aPath { if (self = [super init]) { fileHandle = [NSFileHandle fileHandleForReadingAtPath:aPath]; if (fileHandle == nil) { [self release]; return nil; } lineDelimiter = [[NSSsortingng alloc] initWithSsortingng:@"\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]; } - (NSSsortingng *) readLine { if (currentOffset >= totalFileLength) { return nil; } NSData * newLineData = [lineDelimiter dataUsingEncoding:NSUTF8SsortingngEncoding]; [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 ssortingng chunk = [chunk subdataWithRange:NSMakeRange(0, newLineRange.location+[newLineData length])]; shouldReadMore = NO; } [currentData appendData:chunk]; currentOffset += [chunk length]; } [readPool release]; NSSsortingng * line = [[NSSsortingng alloc] initWithData:currentData encoding:NSUTF8SsortingngEncoding]; [currentData release]; return [line autorelease]; } - (NSSsortingng *) readTrimmedLine { return [[self readLine] ssortingngByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; } #if NS_BLOCKS_AVAILABLE - (void) enumerateLinesUsingBlock:(void(^)(NSSsortingng*, BOOL*))block { NSSsortingng * line = nil; BOOL stop = NO; while (stop == NO && (line = [self readLine])) { block(line, &stop); } } #endif @end 

La classe a été faite par Dave DeLong

Comme l’a dit @porneL, le C api est très pratique.

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

Comme d’autres ont répondu, NSInputStream et NSFileHandle sont des options bien, mais cela peut aussi être fait de manière assez compacte avec NSData et le mappage de mémoire:

BRLineReader.h

 #import  @interface BRLineReader : NSObject @property (readonly, nonatomic) NSData *data; @property (readonly, nonatomic) NSUInteger linesRead; @property (strong, nonatomic) NSCharacterSet *lineTrimCharacters; @property (readonly, nonatomic) NSSsortingngEncoding ssortingngEncoding; - (instancetype)initWithFile:(NSSsortingng *)filePath encoding:(NSSsortingngEncoding)encoding; - (instancetype)initWithData:(NSData *)data encoding:(NSSsortingngEncoding)encoding; - (NSSsortingng *)readLine; - (NSSsortingng *)readTrimmedLine; - (void)setLineSearchPosition:(NSUInteger)position; @end 

BRLineReader.m

 #import "BRLineReader.h" static unsigned char const BRLineReaderDelimiter = '\n'; @implementation BRLineReader { NSRange _lastRange; } - (instancetype)initWithFile:(NSSsortingng *)filePath encoding:(NSSsortingngEncoding)encoding { self = [super init]; if (self) { NSError *error = nil; _data = [NSData dataWithContentsOfFile:filePath options:NSDataReadingMappedAlways error:&error]; if (!_data) { NSLog(@"%@", [error localizedDescription]); } _ssortingngEncoding = encoding; _lineTrimCharacters = [NSCharacterSet whitespaceAndNewlineCharacterSet]; } return self; } - (instancetype)initWithData:(NSData *)data encoding:(NSSsortingngEncoding)encoding { self = [super init]; if (self) { _data = data; _ssortingngEncoding = encoding; _lineTrimCharacters = [NSCharacterSet whitespaceAndNewlineCharacterSet]; } return self; } - (NSSsortingng *)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 

Cette réponse n’est pas ObjC mais C.

Puisque ObjC est basé sur C, pourquoi ne pas utiliser des gadgets?

Et oui, je suis sûr que ObjC a sa propre méthode – je ne suis pas encore assez compétent pour savoir ce que c’est 🙂

de la réponse de @Adam Rosenfield, la chaîne de formatage de fscanf serait modifiée comme ci-dessous:

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

il fonctionnera dans osx, linux, les fins de ligne Windows.

Utiliser une catégorie ou une extension pour faciliter notre vie.

 extension Ssortingng { func lines() -> [Ssortingng] { var lines = [Ssortingng]() self.enumerateLines { (line, stop) -> () in lines.append(line) } return lines } } // then for line in ssortingng.lines() { // do the right thing } 

J’ai trouvé la réponse de @lukaswelte et le code de Dave DeLong très utile. Je cherchais une solution à ce problème mais je devais parsingr les gros fichiers en \r\n pas seulement \n .

Le code tel qu’il est écrit contient un bogue si l’parsing est effectuée par plusieurs caractères. J’ai changé le code comme ci-dessous.

Fichier .h:

 #import  @interface FileChunkReader : NSObject { NSSsortingng * filePath; NSFileHandle * fileHandle; unsigned long long currentOffset; unsigned long long totalFileLength; NSSsortingng * lineDelimiter; NSUInteger chunkSize; } @property (nonatomic, copy) NSSsortingng * lineDelimiter; @property (nonatomic) NSUInteger chunkSize; - (id) initWithFilePath:(NSSsortingng *)aPath; - (NSSsortingng *) readLine; - (NSSsortingng *) readTrimmedLine; #if NS_BLOCKS_AVAILABLE - (void) enumerateLinesUsingBlock:(void(^)(NSSsortingng*, BOOL *))block; #endif @end 

fichier .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:NSUTF8SsortingngEncoding]; [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 [[NSSsortingng alloc] initWithData:currentLine encoding:NSUTF8SsortingngEncoding]; } } - (NSSsortingng *) readTrimmedLine { return [[self readLine] ssortingngByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; } #if NS_BLOCKS_AVAILABLE - (void) enumerateLinesUsingBlock:(void(^)(NSSsortingng*, BOOL*))block { NSSsortingng * line = nil; BOOL stop = NO; while (stop == NO && (line = [self readLine])) { block(line, &stop); } } #endif @end 

J’ajoute ceci parce que toutes les autres réponses que j’ai essayées sont tombées dans un sens ou dans l’autre. La méthode suivante peut gérer des fichiers volumineux, des lignes longues arbitraires et des lignes vides. Il a été testé avec du contenu réel et supprimera le caractère de nouvelle ligne de la sortie.

 - (NSSsortingng*)readLineFromFile:(FILE *)file { char buffer[4096]; NSMutableSsortingng *result = [NSMutableSsortingng ssortingngWithCapacity: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; } 

Le crédit revient à @Adam Rosenfield et @sooop

Voici une solution simple que j’utilise pour les fichiers plus petits:

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

Utilisez ce script, ça marche super bien:

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