Objetivo-C: leer un archivo línea por línea


140

¿Cuál es la forma adecuada de tratar con archivos de texto grandes en Objective-C? Digamos que necesito leer cada línea por separado y quiero tratar cada línea como un NSString. ¿Cuál es la forma más eficiente de hacer esto?

Una solución es usar el método NSString:

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

y luego dividir las líneas con un separador de nueva línea, y luego iterar sobre los elementos en la matriz. Sin embargo, esto parece bastante ineficiente. ¿No hay una manera fácil de tratar el archivo como una secuencia, enumerando cada línea, en lugar de solo leerlo todo de una vez? Algo así como java.io.BufferedReader de Java.


1
Un poco tarde, pero echa un vistazo a [NSScanner scanUpToString: @ "\ n" intoString: & read], suponiendo que deseas leer cada línea en la cadena 'read'.
hauntsaninja

Por favor, eche un vistazo a esta pregunta similar . Configuré un proyecto que se ocupa de leer archivos línea por línea .
JJD

Respuestas:


63

Esa es una gran pregunta. Creo que @Diederik tiene una buena respuesta, aunque es lamentable que Cocoa no tenga un mecanismo para exactamente lo que quieres hacer.

NSInputStreamle permite leer fragmentos de N bytes (muy similares a java.io.BufferedReader), pero debe convertirlos en uno NSStringpor su cuenta, luego buscar nuevas líneas (o cualquier otro delimitador) y guardar los caracteres restantes para la próxima lectura, o leer más caracteres si aún no se ha leído una nueva línea. (le NSFileHandlepermite leer uno NSDataque luego puede convertir en unNSString , pero es esencialmente el mismo proceso).

Apple tiene una Guía de programación de Stream que puede ayudar a completar los detalles, y esta pregunta SO también puede ayudar si se trata deuint8_t* buffers.

Si va a leer cadenas como esta con frecuencia (especialmente en diferentes partes de su programa), sería una buena idea encapsular este comportamiento en una clase que pueda manejar los detalles por usted, o incluso subclasificar NSInputStream(está diseñado para ser subclases ) y agregar métodos que le permiten leer exactamente lo que desea.

Para el registro, creo que esta sería una buena característica para agregar, y presentaré una solicitud de mejora para algo que lo haga posible. :-)


Editar: Resulta que esta solicitud ya existe. Hay un Radar que data de 2006 para esto (rdar: // 4742914 para personas internas de Apple).


10
Vea el enfoque integral de Dave DeLong para este problema aquí: stackoverflow.com/questions/3707427#3711079
Quinn Taylor el

También es posible usar NSData simple y mapeo de memoria. He creado una respuesta con un código de ejemplo que tiene la misma API que la implementación NSFileHandle de Dave DeLong: stackoverflow.com/a/21267461/267043
Bjørn Olav Ruud

95

Esto funcionará para leer un general Stringde Text. Si desea leer un texto más largo (gran tamaño del texto) , utilice el método que otras personas mencionaron aquí, como el búfer (reserve el tamaño del texto en el espacio de la memoria) .

Digamos que lees un archivo de texto.

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

Desea deshacerse de la nueva línea.

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

Ahí tienes.


17
Tengo un archivo de 70 mb, usar este código para leer el archivo no me ayuda, aumenta la memoria linealmente. ¿Alguien puede ayudarme?
GameLoading

37
Esta no es una respuesta a la pregunta. La pregunta era leer un archivo línea por línea para reducir el uso de memoria
doozMen

34

Esto debería funcionar:

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

Use de la siguiente manera:

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

Este código lee caracteres que no son de nueva línea del archivo, hasta 4095 a la vez. Si tiene una línea que tiene más de 4095 caracteres, sigue leyendo hasta que llega a una nueva línea o al final del archivo.

Nota : no he probado este código. Por favor, pruébelo antes de usarlo.


1
simplemente cambie [result appendFormat: "% s", buffer]; a [result appendFormat: @ "% s", buffer];
Codezy

1
¿Cómo modificaría el formato para aceptar líneas vacías, o más bien líneas que consisten en un solo carácter de nueva línea?
jakev

Esto se detiene temprano para mí después de 812 líneas. La línea 812 es "... 3 más", y eso hace que el lector muestre cadenas vacías.
sudo

1
Agregué un cheque para pasar las líneas vacías: int fscanResult = fscanf (archivo, "% 4095 [^ \ n]% n% * c", buffer, & charsRead); if (fscanResult == 1) {[result appendFormat: @ "% s", buffer]; } else {if (feof (file)) {break; } else if (ferror (file)! = 0) {break; } fscanf (archivo, "\ n", nil, & charsRead); descanso; }
Ir Rose-Hulman

1
Si estoy leyendo la documentación de fscanf correctamente, "%4095[^\n]%n%*c"consumiré y desecharé en silencio un carácter con cada lectura de búfer. Parece que este formato supone que las líneas serán más cortas que la longitud del búfer.
Blago

12

Mac OS X es Unix, Objective-C es un superconjunto C, por lo que puede usar la vieja escuela fopeny fgetsde <stdio.h>. Está garantizado para trabajar.

[NSString stringWithUTF8String:buf]convertirá la cadena C a NSString. También hay métodos para crear cadenas en otras codificaciones y crear sin copiar.


[copiar comentario anónimo] fgetsincluirá el '\n'carácter, por lo que es posible que desee quitarlo antes de convertir la cadena.
Kornel

9

Puede usar el NSInputStreamque tiene una implementación básica para secuencias de archivos. Puede leer bytes en un búfer ( read:maxLength:método). Tienes que escanear el búfer en busca de nuevas líneas.


6

La forma adecuada de leer archivos de texto en Cocoa / Objective-C está documentada en la guía de programación String de Apple. La sección para leer y escribir archivos. debe ser justo lo que buscas. PD: ¿Qué es una "línea"? ¿Dos secciones de una cadena separadas por "\ n"? O "\ r"? O "\ r \ n"? ¿O tal vez estás realmente después de los párrafos? La guía mencionada anteriormente también incluye una sección sobre la división de una cadena en líneas o párrafos. (Esta sección se llama "Párrafos y saltos de línea", y está vinculada en el menú del lado izquierdo de la página que señalé anteriormente. Desafortunadamente este sitio no me permite publicar más de una URL ya que estoy no es un usuario confiable todavía).

Parafraseando a Knuth: la optimización prematura es la raíz de todo mal. No asuma simplemente que "leer todo el archivo en la memoria" es lento. ¿Lo has comparado? ¿Sabes que en realidad lee todo el archivo en la memoria? ¿Tal vez simplemente devuelve un objeto proxy y sigue leyendo detrás de escena mientras consume la cadena? ( Descargo de responsabilidad: no tengo idea si NSString realmente hace esto. Posiblemente podría ) . El punto es: primero ve con la forma documentada de hacer las cosas. Luego, si los puntos de referencia muestran que esto no tiene el rendimiento que desea, optimice.


Como menciona las terminaciones de línea de CRLF (Windows): en realidad es un caso que rompe la forma de hacer las cosas de Objective-C. Si utiliza uno de los -stringWithContentsOf*métodos seguidos -componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet], ve el \ry por \nseparado y agrega una línea en blanco después de cada línea.
Siobhán

Dicho esto, la solución fgets falla en los archivos CR-only. Pero esos son (en teoría) raros hoy en día, y fgets funciona tanto para LF como para CRLF.
Siobhán

6

Muchas de estas respuestas son largos fragmentos de código o se leen en todo el archivo. Me gusta usar los métodos c para esta misma tarea.

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

Tenga en cuenta que fgetln no mantendrá su carácter de nueva línea. Además, hacemos +1 la longitud de la cadena porque queremos hacer espacio para la terminación NULL.


4

Para leer un archivo línea por línea (también para archivos grandes extremos) se puede hacer mediante las siguientes funciones:

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 clase DDFileReader que permite esto es la siguiente:

Archivo de interfaz (.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

Implementación (.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 clase fue hecha por Dave DeLong


4

Al igual que @porneL dijo, la API de C es muy útil.

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

Como otros han respondido, tanto NSInputStream como NSFileHandle son buenas opciones, pero también se puede hacer de manera bastante compacta con NSData y la asignación de 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

Esta respuesta NO es ObjC sino C.

Dado que ObjC está basado en 'C', ¿por qué no usar fgets?

Y sí, estoy seguro de que ObjC tiene su propio método, pero todavía no soy lo suficientemente competente como para saber qué es :)


55
Si no sabes cómo hacerlo en Objective-C, ¿por qué decir que no es la respuesta? Hay muchas razones para no descender a la recta C si puede hacerlo de otra manera. Por ejemplo, las funciones de C manejan char * pero se necesita mucho más trabajo para leer otra cosa, como diferentes codificaciones. Además, quiere objetos NSString. En total, rodar esto usted mismo no solo es más código, sino que también es propenso a errores.
Quinn Taylor

3
Estoy de acuerdo con usted al 100%, pero he descubierto que (a veces) es mejor obtener una respuesta que funcione rápidamente, implementarla y luego, cuando aparezca una alternativa más correcta, utilícela. Esto es especialmente importante cuando se realizan prototipos, ya que brinda la oportunidad de hacer que algo funcione y luego progresar desde allí.
KevinDTimm

3
Me acabo de dar cuenta de que comenzó "Esta respuesta" no "La respuesta". Doh! Estoy de acuerdo, definitivamente es mejor tener un truco que funcione que un código elegante que no funciona. No te rechacé, pero arrojar una conjetura sin saber lo que puede tener Objective-C probablemente tampoco sea muy útil. Aun así, hacer un esfuerzo siempre es mejor que alguien que sabe y no ayuda ... ;-)
Quinn Taylor

Esto no proporciona una respuesta a la pregunta. Para criticar o solicitar una aclaración de un autor, deje un comentario debajo de su publicación.
Gato robótico

1
@KevinDTimm: estoy de acuerdo; Solo lamento no haberlo visto, era una respuesta de 5 años. Quizás esta sea una metapregunta; ¿Deberían marcarse para revisión las preguntas muy antiguas de usuarios habituales?
Gato robótico

0

de la respuesta de @Adam Rosenfield, la cadena de formato de fscanfse cambiaría de la siguiente manera:

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

funcionará en osx, linux, terminaciones de línea de windows.


0

Usando categoría o extensión para hacer nuestra vida un poco más fácil.

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

Encontré la respuesta de @lukaswelte y el código de Dave DeLong muy útil. Estaba buscando una solución a este problema, pero necesitaba analizar archivos grandes \r\nno solo \n.

El código tal como está escrito contiene un error si se analiza por más de un carácter. He cambiado el código de la siguiente manera.

archivo .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

archivo .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

Estoy agregando esto porque todas las otras respuestas que probé se quedaron cortas de una manera u otra. El siguiente método puede manejar archivos grandes, líneas largas arbitrarias, así como líneas vacías. Se ha probado con contenido real y eliminará el carácter de nueva línea de la salida.

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

El crédito va para @Adam Rosenfield y @sooop


0

Veo que muchas de estas respuestas se basan en leer todo el archivo de texto en la memoria en lugar de tomarlo un fragmento a la vez. Aquí está mi solución en Swift agradable y moderno, usando FileHandle para mantener bajo el impacto de la 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
    }

}

Tenga en cuenta que esto conserva el retorno de carro al final de la línea, por lo que, según sus necesidades, es posible que desee ajustar el código para eliminarlo.

Uso: simplemente abra un identificador de archivo en su archivo de texto de destino y llame readLinecon una longitud máxima adecuada: 1024 es estándar para texto sin formato, pero lo dejé abierto en caso de que sepa que será más corto. Tenga en cuenta que el comando no desbordará el final del archivo, por lo que es posible que tenga que verificar manualmente que no lo haya alcanzado si tiene la intención de analizar todo. Aquí hay un código de muestra que muestra cómo abrir un archivo myFileURLy leerlo línea por línea hasta el final.

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

Aquí hay una buena solución simple que uso para archivos más pequeños:

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

Estaba preguntando cómo leer una línea a la vez para que no lea todo el contenido en la memoria. Su solución crea una cadena con todo el contenido y luego la divide en líneas.
David

-7

Use este script, funciona muy bien:

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
Lo que @fisninear dice es que esto no responde al deseo del OP de reducir el uso de memoria. El OP no estaba preguntando cómo usar el método (que carga todo el archivo en la memoria), estaba pidiendo alternativas amigables con la memoria para archivos de texto grandes. Es muy posible tener archivos de texto de varios gigabytes, lo que obviamente crea un problema de memoria.
Joshua Nozzi
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.