Objective-C: lendo um arquivo linha por linha


140

Qual é a maneira apropriada de lidar com arquivos de texto grandes no Objective-C? Digamos que eu precise ler cada linha separadamente e queira tratar cada linha como um NSString. Qual é a maneira mais eficiente de fazer isso?

Uma solução está usando o método NSString:

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

e, em seguida, divida as linhas com um separador de nova linha e itere sobre os elementos da matriz. No entanto, isso parece bastante ineficiente. Não existe uma maneira fácil de tratar o arquivo como um fluxo, enumerando cada linha, em vez de apenas ler tudo de uma vez? Como o java.io.BufferedReader do Java.


1
Um pouco tarde, mas verifique [NSScanner scanUpToString: @ "\ n" intoString: & read], supondo que você queira ler cada linha na string 'read'.
211111 hauntsaninja

Por favor, dê uma olhada nesta pergunta semelhante . Montei um projeto que lida com a leitura de arquivos linha por linha .
JJD

Respostas:


63

Essa é uma ótima pergunta. Acho que o @Diederik tem uma boa resposta, embora seja lamentável que o cacau não tenha um mecanismo para exatamente o que você deseja fazer.

NSInputStreampermite ler trechos de N bytes (muito parecidos com java.io.BufferedReader), mas você deve convertê-lo em um NSStringpor conta própria, procurar novas linhas (ou qualquer outro delimitador) e salvar os caracteres restantes para a próxima leitura ou ler mais caracteres se uma nova linha ainda não foi lida. ( NSFileHandlepermite ler um NSDataque você pode converter em um NSString, mas é essencialmente o mesmo processo.)

A Apple possui um Guia de programação de fluxo que pode ajudar a preencher os detalhes, e essa pergunta SO também pode ajudar se você estiver lidando com uint8_t*buffers.

Se você estiver lendo seqüências de caracteres como essa com frequência (especialmente em diferentes partes do seu programa), seria uma boa idéia encapsular esse comportamento em uma classe que possa lidar com os detalhes para você, ou mesmo subclassificar NSInputStream(ela foi projetada para ser subclasse ) e adicionando métodos que permitem ler exatamente o que você deseja.

Para o registro, acho que esse seria um bom recurso a ser adicionado e apresentarei uma solicitação de aprimoramento para algo que torne isso possível. :-)


Edit: Acontece que esta solicitação já existe. Existe um radar datado de 2006 para isso (rdar: // 4742914 para pessoas internas da Apple).


10
Veja abordagem abrangente de Dave DeLong para este problema aqui: stackoverflow.com/questions/3707427#3711079
Quinn Taylor

Também é possível usar o NSData simples e o mapeamento de memória. Eu criei uma resposta com exemplo de código que tem o mesmo API como NSFileHandle implementação de Dave DeLong: stackoverflow.com/a/21267461/267043
Bjørn Olav Ruud

95

Isso funcionará para a leitura geral a Stringpartir de Text. Se você quiser ler um texto mais longo (tamanho grande) , use o método mencionado por outras pessoas como buffer (reserve o tamanho do texto no espaço de memória) .

Digamos que você leia um arquivo de texto.

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

Você quer se livrar da nova linha.

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

Aí está.


17
Eu tenho um arquivo de 70 mb, usando este código para ler o arquivo não me hep ele aumenta a memória linearmente. Alguém pode me ajudar?
GameLoading 21/03

37
Isso não é resposta à pergunta. A pergunta era para ler um arquivo linha por linha para reduzir o uso de memória
doozMen

34

Isso deve fazer o truque:

#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 da seguinte maneira:

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

Esse código lê caracteres que não são de nova linha do arquivo, até 4095 por vez. Se você tiver uma linha com mais de 4095 caracteres, ela continuará lendo até atingir uma nova linha ou final de arquivo.

Nota : Eu não testei este código. Por favor, teste-o antes de usá-lo.


1
basta alterar [resultado appendFormat: "% s", buffer]; para [resultado appendFormat: @ "% s", buffer];
Codezy 8/07/10

1
como você modificaria o formato para aceitar linhas vazias, ou melhor, linhas consistindo em um único caractere de nova linha?
jakev

Isso está parando cedo para mim depois de 812 linhas. A 812ª linha é "... mais 3", e isso faz com que o leitor produza strings vazias.
Sudo

1
Eu adicionei uma verificação para passar pelas linhas vazias: int fscanResult = fscanf (arquivo, "% 4095 [^ \ n]% n% * c", buffer, & charsRead); if (fscanResult == 1) {[resultado appendFormat: @ "% s", buffer]; } else {if (feof (arquivo)) {break; } else if (ferror (arquivo)! = 0) {break; } fscanf (arquivo, "\ n", nulo e & charsRead); pausa; }
Vá Rose-Hulman

1
Se eu estiver lendo a documentação do fscanf corretamente, "%4095[^\n]%n%*c"consumirei silenciosamente e jogarei fora um caractere com cada leitura de buffer. Parece que este formato assume que as linhas serão mais curtas que o tamanho do buffer.
Blago 02/02

12

O Mac OS X é Unix, o Objective-C é um superconjunto de C, então você pode usar a velha escola fopene a fgetspartir de <stdio.h>. É garantido que funcione.

[NSString stringWithUTF8String:buf]irá converter a string C para NSString. Também existem métodos para criar seqüências de caracteres em outras codificações e criar sem copiar.


[copiar comentário anônimo] fgetsincluirá o '\n'caractere; portanto, você pode retirar isso antes de converter a string.
Kornel

9

Você pode usar NSInputStreamuma implementação básica para fluxos de arquivos. Você pode ler bytes em um buffer ( read:maxLength:método). Você precisa verificar se há novas linhas no buffer.


6

A maneira apropriada de ler arquivos de texto no Cocoa / Objective-C está documentada no guia de programação String da Apple. A seção para ler e gravar arquivos deve ser exatamente o que você . PS: O que é uma "linha"? Duas seções de uma sequência separadas por "\ n"? Ou "\ r"? Ou "\ r \ n"? Ou talvez você esteja realmente procurando parágrafos? O guia mencionado anteriormente também inclui uma seção sobre como dividir uma string em linhas ou parágrafos. (Esta seção é chamada de "parágrafos e quebras de linha" e está vinculada ao menu à esquerda da página que apontei acima. Infelizmente, este site não permite que eu publique mais de um URL, pois ainda não é um usuário confiável.)

Parafraseando Knuth: a otimização prematura é a raiz de todo mal. Não assuma simplesmente que "a leitura do arquivo inteiro na memória" é lenta. Você comparou? Você sabia que ele realmente lê o arquivo inteiro na memória? Talvez ele simplesmente retorne um objeto proxy e continue lendo nos bastidores enquanto você consome a string? ( Isenção de responsabilidade: eu não tenho idéia se o NSString realmente faz isso. É possível que sim. ) O ponto é: primeiro, vá com a maneira documentada de fazer as coisas. Então, se os benchmarks mostrarem que isso não tem o desempenho que você deseja, otimize.


Como você menciona as terminações de linha CRLF (Windows): esse é realmente um caso que quebra a maneira de fazer o Objective-C. Se você usar um dos -stringWithContentsOf*métodos seguidos por -componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet], ele verá o \re \nseparadamente e adicionará uma linha em branco após cada linha.
Siobhán

Dito isto, a solução fgets falha em arquivos somente CR. Mas esses são (teoricamente) raros hoje em dia, e os recursos funcionam para LF e CRLF.
Siobhán

6

Muitas dessas respostas são grandes pedaços de código ou são lidas no arquivo inteiro. Eu gosto de usar os métodos c para essa mesma tarefa.

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

Observe que fgetln não manterá seu caractere de nova linha. Além disso, +1 do comprimento do str porque queremos criar espaço para a terminação NULL.


4

Para ler um arquivo linha por linha (também para arquivos grandes extremos), você pode fazer as seguintes funções:

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

Ou:

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

A classe DDFileReader que permite isso é o seguinte:

Arquivo de interface (.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

Implementação (.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

A aula foi feita por Dave DeLong


4

Assim como o @porneL disse, a API C é muito ú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 outras pessoas responderam, o NSInputStream e o NSFileHandle são boas opções, mas isso também pode ser feito de maneira bastante compacta com o NSData e o mapeamento de memória:

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 resposta NÃO é ObjC, mas C.

Como o ObjC é baseado em 'C', por que não usar fgets?

E sim, tenho certeza de que a ObjC tem seu próprio método - ainda não sou proficiente o suficiente para saber o que é :)


5
Se você não sabe como fazer isso no Objective-C, por que dizer que não é a resposta? Existem várias razões para não cair diretamente para C, se você puder fazê-lo de outra forma. Por exemplo, as funções C lidam com char *, mas é preciso muito mais trabalho para ler outra coisa, como codificações diferentes. Além disso, ele quer objetos NSString. Ao todo, rolar isso sozinho não é apenas mais código, mas também propenso a erros.
Quinn Taylor

3
Concordo 100% com você, mas descobri que (às vezes) é melhor obter uma resposta que funcione rapidamente, implementá-la e, quando uma alternativa mais correta aparecer, utilize-a. Isso é especialmente importante na criação de protótipos, oferecendo a oportunidade de fazer com que algo funcione e depois progredir a partir daí.
22410 KevinDTimm

3
Acabei de perceber que começou "Esta resposta" e não "A resposta". Doh! Concordo, é definitivamente melhor ter um hack que funcione do que um código elegante que não. Eu não te diminuí o voto, mas jogar um palpite sem saber o que o Objective-C pode ter provavelmente também não ajuda muito. Mesmo assim, fazendo um esforço é sempre melhor do que alguém que conhece e não ajuda ... ;-)
Quinn Taylor

Isso não fornece uma resposta para a pergunta. Para criticar ou solicitar esclarecimentos a um autor, deixe um comentário abaixo da postagem.
Robotic Cat

1
@KevinDTimm: Eu concordo; Lamento não ter encontrado uma resposta de 5 anos. Talvez isso seja uma metapergunta; perguntas muito antigas de usuários regulares podem ser sinalizadas para revisão?
Robotic Cat

0

da resposta de @Adam Rosenfield, a sequência de formatação de fscanfseria alterada como abaixo:

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

funcionará em osx, linux, finais de linha do windows.


0

Usando categoria ou extensão para facilitar nossa vida.

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

Achei a resposta de @lukaswelte e o código de Dave DeLong muito úteis. Eu estava procurando uma solução para esse problema, mas precisava analisar arquivos grandes, \r\nnão apenas\n .

O código escrito contém um erro se estiver analisando por mais de um caractere. Eu mudei o código como abaixo.

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

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

Estou adicionando isso porque todas as outras respostas que tentei ficaram aquém de uma maneira ou de outra. O método a seguir pode manipular arquivos grandes, linhas longas arbitrárias e linhas vazias. Foi testado com conteúdo real e retirará o caractere de nova linha da saída.

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

O crédito vai para @Adam Rosenfield e @sooop


0

Vejo muitas dessas respostas baseadas na leitura de todo o arquivo de texto na memória, em vez de levá-lo um pedaço de cada vez. Aqui está minha solução no Swift moderno e agradável, usando o FileHandle para manter baixo o impacto na memória:

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
    }

}

Observe que isso preserva o retorno de carro no final da linha; portanto, dependendo das suas necessidades, você pode ajustar o código para removê-lo.

Uso: basta abrir um identificador de arquivo para o arquivo de texto de destino e chamar readLinecom um comprimento máximo adequado - 1024 é padrão para texto sem formatação, mas deixei em aberto caso você saiba que será mais curto. Observe que o comando não excederá o final do arquivo; portanto, talvez seja necessário verificar manualmente se você não o alcançou se pretender analisar a coisa toda. Aqui está um código de exemplo que mostra como abrir um arquivo myFileURLe lê-lo linha por linha até o 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

Aqui está uma solução simples e agradável que eu uso para arquivos menores:

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

Ele estava perguntando sobre como ler uma linha de cada vez, para que ela não lesse todo o conteúdo na memória. Sua solução cria uma string com todo o conteúdo e depois a divide em linhas.
David

-7

Use este script, ele funciona muito bem:

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
O que @fisninear está dizendo é que isso não atende ao desejo do OP de reduzir o uso de memória. O OP não estava perguntando como usar o método (que carrega o arquivo inteiro na memória), ele estava pedindo alternativas compatíveis com a memória para arquivos de texto grandes. É bem possível ter arquivos de texto com vários gigabytes, o que obviamente cria um problema de memória.
Joshua Nozzi
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.