Objective-C: построчное чтение файла

140

Как лучше всего работать с большими текстовыми файлами в Objective-C? Скажем, мне нужно читать каждую строку отдельно и рассматривать каждую строку как NSString. Как это сделать наиболее эффективно?

Одно из решений - использовать метод NSString:

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

а затем разделите строки разделителем новой строки, а затем выполните итерацию по элементам в массиве. Однако это кажется довольно неэффективным. Нет ли простого способа рассматривать файл как поток, перечисляя по каждой строке, вместо того, чтобы просто читать все сразу? Вроде как Java java.io.BufferedReader.

Куинн Тейлор
источник
1
Немного поздно, но посмотрите [NSScanner scanUpToString: @ "\ n" intoString: & read], предполагая, что вы хотите прочитать каждую строку в строке 'read'.
hauntsaninja
Пожалуйста, взгляните на этот аналогичный вопрос . Я создал проект, который занимается построчным чтением файлов .
JJD 02

Ответы:

63

Отличный вопрос. Я думаю, что у @Diederik есть хороший ответ, хотя, к сожалению, у Какао нет механизма для того, что вы хотите делать.

NSInputStreamпозволяет вам читать фрагменты из N байтов (очень похоже на java.io.BufferedReader), но вы должны преобразовать его в NSStringсамостоятельно, затем сканировать новые строки (или любой другой разделитель) и сохранять любые оставшиеся символы для следующего чтения или читать больше символов если новая строка еще не была прочитана. ( NSFileHandleпозволяет читать, NSDataкоторый затем можно преобразовать в NSString, но по сути это тот же процесс.)

У Apple есть Руководство по потоковому программированию, которое может помочь в заполнении деталей, и этот вопрос SO также может помочь, если вы собираетесь иметь дело с uint8_t*буферами.

Если вы собираетесь часто читать такие строки (особенно в разных частях вашей программы), было бы неплохо инкапсулировать это поведение в классе, который может обрабатывать детали за вас, или даже создать подкласс NSInputStream(он предназначен для подклассы ) и добавление методов, позволяющих читать именно то, что вы хотите.

Для протокола, я думаю, что было бы неплохо добавить эту функцию, и я буду подавать запрос на улучшение чего-то, что делает это возможным. :-)


Изменить: оказывается, этот запрос уже существует. Для этого есть радар 2006 года (rdar: // 4742914 для сотрудников Apple).

Куинн Тейлор
источник
10
См. Исчерпывающий подход Дейва Делонга к этой проблеме здесь: stackoverflow.com/questions/3707427#3711079
Куинн Тейлор
Также можно использовать простой NSData и отображение памяти. Я создал ответ с примером кода, который имеет тот же API, что и реализация NSFileHandle Дэйва Делонга: stackoverflow.com/a/21267461/267043
Бьорн Олав Рууд
95

Это будет работать для общего чтения Stringиз Text. Если вы хотите прочитать более длинный текст (большой размер текста) , используйте метод, упомянутый здесь другими людьми, например, буферизованный (зарезервируйте размер текста в памяти) .

Скажем, вы читаете текстовый файл.

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

Вы хотите избавиться от новой строки.

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

Вот и все.

Юн Ли
источник
17
У меня есть файл размером 70 МБ, использование этого кода для чтения файла не помогает мне, он увеличивает память линейно. Может кто-нибудь мне помочь?
GameLoading
37
Это не ответ на вопрос. Вопрос заключался в том, чтобы читать файл построчно, чтобы уменьшить использование памяти
doozMen
34

Это должно помочь:

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

Используйте следующим образом:

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

Этот код считывает из файла до 4095 символов, не являющихся символами новой строки. Если у вас есть строка длиной более 4095 символов, она будет продолжать чтение до тех пор, пока не достигнет новой строки или конца файла.

Примечание : я не тестировал этот код. Пожалуйста, проверьте его перед использованием.

Адам Розенфилд
источник
1
просто измените [результат appendFormat: "% s", buffer]; to [результат appendFormat: @ "% s", буфер];
Codezy 08
1
как бы вы изменили формат, чтобы он принимал пустые строки или, скорее, строки, состоящие из одного символа новой строки?
jakev
Для меня это рано прекращается после 812 строк. 812-я строка - это «... еще 3», и это заставляет читателя выводить пустые строки.
sudo
1
Я добавил проверку, чтобы пропустить пустые строки: int fscanResult = fscanf (file, "% 4095 [^ \ n]% n% * c", buffer, & charsRead); if (fscanResult == 1) {[результат appendFormat: @ "% s", буфер]; } else {если (feof (файл)) {перерыв; } еще если (ferror (файл)! = 0) {перерыв; } fscanf (файл, "\ n", nil, & charsRead); перемена; }
Go Rose-Hulman
1
Если я правильно читаю документацию fscanf, он "%4095[^\n]%n%*c"будет молча потреблять и отбрасывать один символ при каждом чтении буфера. Похоже, этот формат предполагает, что строки будут короче, чем длина буфера.
Благо
12

Mac OS X - это Unix, Objective-C - это надмножество C, поэтому вы можете просто использовать старую школу fopenи fgetsоттуда <stdio.h>. Гарантированно сработает.

[NSString stringWithUTF8String:buf]преобразует строку C в NSString. Также существуют методы создания строк в других кодировках и создания без копирования.

Корнель
источник
[копирование анонимного комментария] fgetsбудет включать '\n'символ, поэтому вы можете удалить его перед преобразованием строки.
Kornel
9

Вы можете использовать то, NSInputStreamчто имеет базовую реализацию для файловых потоков. Вы можете читать байты в буфер ( read:maxLength:метод). Вы должны сами сканировать буфер на предмет новых строк.

диедерих
источник
6

Соответствующий способ чтения текстовых файлов в Cocoa / Objective-C задокументирован в руководстве Apple по программированию String. Раздел для чтения и записи файлов должен быть именно тем, что вам нужно. PS: Что такое «линия»? Две части строки, разделенные "\ n"? Или "\ r"? Или "\ r \ n"? Или, может быть, вы действительно после абзацев? Ранее упомянутое руководство также включает раздел о разделении строки на строки или абзацы. (Этот раздел называется «Абзацы и разрывы строк», и ссылка на него находится в левом меню страницы, на которую я указал выше. К сожалению, этот сайт не позволяет мне размещать более одного URL-адреса, поскольку я еще не заслуживающий доверия пользователь.)

Перефразируя Кнута: преждевременная оптимизация - это корень всех зол. Не думайте, что «чтение всего файла в память» происходит медленно. Вы проверили это? Вы знаете, что он на самом деле считывает весь файл в память? Может быть, он просто возвращает прокси-объект и продолжает читать за кулисами, пока вы потребляете строку? ( Отказ от ответственности: я понятия не имею, действительно ли это делает NSString. Возможно, это возможно. ) Суть в следующем: сначала используйте задокументированный способ выполнения действий. Затем, если тесты покажут, что это не дает желаемой производительности, оптимизируйте.

Стиг Браутасет
источник
Поскольку вы упоминаете окончание строки CRLF (Windows): на самом деле это случай, который нарушает способ ведения дел в Objective-C. Если вы используете один из следующих -stringWithContentsOf*методов -componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet], он видит \rи \nотдельно и добавляет пустую строку после каждой строки.
Siobhán
Тем не менее, решение fgets не работает с файлами только для CR. Но в настоящее время это (теоретически) редкость, и fgets действительно работает как для LF, так и для CRLF.
Siobhán
6

Многие из этих ответов представляют собой длинные фрагменты кода или читаются во всем файле. Мне нравится использовать методы c именно для этой задачи.

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

Обратите внимание, что fgetln не сохранит ваш символ новой строки. Кроме того, мы +1 к длине str, потому что хотим освободить место для завершения NULL.

DCurro
источник
4

Прочитать файл построчно (также для очень больших файлов) можно с помощью следующих функций:

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

Или:

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

Класс DDFileReader, обеспечивающий это, следующий:

Файл интерфейса (.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

Реализация (.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

Занятие провел Дэйв Делонг.

Лукасвелте
источник
4

Как сказал @porneL, C api очень удобен.

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);
}
Wdanxna
источник
4

Как ответили другие, NSInputStream и NSFileHandle - прекрасные варианты, но это также можно сделать довольно компактным способом с помощью NSData и сопоставления памяти:

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

Это НЕ ObjC, а C.

Поскольку ObjC основан на языке C, почему бы не использовать fgets?

И да, я уверен, что у ObjC есть свой метод - я просто еще недостаточно разбираюсь в том, что это такое :)

KevinDTimm
источник
5
Если вы не знаете, как это сделать в Objective-C, почему тогда говорить, что это не ответ? Есть множество причин не опускаться до прямой C, если вы можете сделать это иначе. Например, функции C обрабатывают char *, но требуется гораздо больше работы, чтобы прочитать что-то еще, например, различные кодировки. Также ему нужны объекты NSString. В общем, прокатывание этого самостоятельно не только увеличивает количество кода, но и чревато ошибками.
Куинн Тейлор
3
Я согласен с вами на 100%, но я обнаружил, что (иногда) лучше получить быстрый ответ, внедрить его, а затем, когда появится более правильная альтернатива, использовать его. Это особенно важно при прототипировании, давая возможность заставить что-то работать, а затем продвигаться дальше.
KevinDTimm,
3
Я просто понял, что он начинался «Этот ответ», а не «Ответ». Дох! Я согласен, определенно лучше иметь работающий хак, чем элегантный код, который не работает. Я не голосовал против вас, но предположение, не зная, что такое Objective-C, вероятно, тоже не очень полезно. Тем не менее, прилагать усилия всегда лучше, чем кто-то, кто знает и не помогает ... ;-)
Куинн Тейлор
Это не дает ответа на вопрос. Чтобы критиковать или запросить разъяснения у автора, оставьте комментарий под его сообщением.
Robotic Cat
1
@KevinDTimm: Согласен; Мне только жаль, что я не заметил, что это был ответ 5-летней давности. Может это metaвопрос; следует ли помечать для проверки очень старые вопросы обычных пользователей?
Robotic Cat
0

из ответа @Adam Rosenfield строка форматирования fscanfбудет изменена, как показано ниже:

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

он будет работать в osx, linux, окончании строк Windows.

оооп
источник
0

Использование категории или расширения, чтобы сделать нашу жизнь немного проще.

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

Я нашел ответ @lukaswelte и код Дэйва Делонга очень полезными. Я искал решение этой проблемы, но мне нужно было разбирать \r\nне только большие файлы \n.

Написанный код содержит ошибку при синтаксическом анализе более чем одним символом. Я изменил код, как показано ниже.

.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

.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
Hovey
источник
0

Я добавляю это, потому что все остальные ответы, которые я пробовал, так или иначе не оправдали себя. Следующий метод может обрабатывать большие файлы, произвольные длинные строки, а также пустые строки. Он был протестирован с реальным контентом и будет вырезать символ новой строки из вывода.

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

Кредит принадлежит @Adam Rosenfield и @sooop

Благо
источник
0

Я вижу, что многие из этих ответов полагаются на чтение всего текстового файла в память вместо того, чтобы брать его по частям. Вот мое решение в красивом современном Swift, использующее FileHandle для снижения воздействия на память:

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
    }

}

Обратите внимание, что это сохраняет возврат каретки в конце строки, поэтому в зависимости от ваших потребностей вы можете изменить код, чтобы удалить его.

Использование: просто откройте дескриптор целевого текстового файла и вызовите readLineподходящую максимальную длину - 1024 является стандартным для обычного текста, но я оставил его открытым на случай, если вы знаете, что он будет короче. Обратите внимание, что команда не переполняет конец файла, поэтому вам, возможно, придется вручную проверить, что вы не достигли его, если вы собираетесь анализировать все это. Вот пример кода, который показывает, как открыть файл myFileURLи прочитать его построчно до конца.

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

Вот хорошее простое решение, которое я использую для небольших файлов:

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);
    }
}
Крис
источник
Он спрашивал, как читать строку за раз, чтобы она не считывала все содержимое в память. Ваше решение создает строку со всем содержимым, а затем разбивает ее на строки.
Дэвид
-7

Используйте этот скрипт, он отлично работает:

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
Что говорит @fisninear, так это то, что это не решает желания OP уменьшить использование памяти. OP не спрашивал, как использовать метод (который загружает весь файл в память), он просил альтернативы, удобные для памяти для больших текстовых файлов. Вполне возможно иметь текстовые файлы размером в несколько гигабайт, что, очевидно, создает проблемы с памятью.
Джошуа Ноцци 01