Завдання-C: читання файлів рядок за рядком


140

Який підходящий спосіб роботи з великими текстовими файлами в Objective-C? Скажімо, мені потрібно прочитати кожен рядок окремо, і я хочу розглядати кожен рядок як NSString. Який найефективніший спосіб зробити це?

Одне рішення - це метод NSString:

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

а потім розділіть лінії за допомогою нового роздільника рядків, а потім повторіть елементи в масиві. Однак це здається досить неефективним. Чи не існує простого способу трактувати файл як потік, перераховуючи кожен рядок, замість того, щоб просто прочитати все це відразу? Якийсь такий, як Java's java.io.BufferedReader.


1
Трохи пізно, але перевірте [NSScanner scanUpToString: @ "\ n" intoString: & read], припускаючи, що ви хочете прочитати кожен рядок у рядку "read".
hauntsaninja

Погляньте, будь ласка, на це подібне запитання . Я створив проект, який займається читанням файлів по черзі .
JJD

Відповіді:


63

Це чудове запитання. Я думаю, що у @Diederik є хороша відповідь, хоча шкода, що у какао немає механізму саме того, що ти хочеш робити.

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

Apple має Посібник з програмування потоку, який може допомогти заповнити деталі, і це питання ТА може допомогти, якщо ви збираєтесь мати справу з uint8_t*буферами.

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

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


Редагувати: виявляється, що цей запит вже існує. Для цього існує радіолокаційна станція з 2006 року (rdar: // 4742914 для користувачів Apple).


10
Дивіться комплексний підхід Дейва Делонга до цієї проблеми тут: stackoverflow.com/questions/3707427#3711079
Квінн Тейлор,

Також можливо використовувати звичайне NSData та відображення пам'яті. Я створив відповідь з прикладами коду , який має той же інтерфейс , як реалізації Дейва Делонг в 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", буфер]; to [result appendFormat: @ "% s", буфер];
Codezy

1
як би ви змінили формат для прийняття порожніх рядків, а точніше рядків, що складаються з одного символу нового рядка?
jakev

Це зупиняється для мене рано після 812 рядків. 812-й рядок - "... ще 3", і це робить висновок читача порожніми рядками.
судо

1
Я додав чек, щоб пройти повз порожніх рядків: int fscanResult = fscanf (файл, "% 4095 [^ \ n]% n% * c", буфер і charsRead); if (fscanResult == 1) {[result appendFormat: @ "% s", буфер]; } else {if (feof (файл)) {break; } else if (ferror (файл)! = 0) {break; } fscanf (файл, "\ n", nil & charsRead); перерва; }
Ідіть Роуз-Хульман

1
Якщо я читаю документацію fscanf правильно, "%4095[^\n]%n%*c"мовчки споживає та викидає один символ із кожним прочитаним буфером. Схоже, цей формат передбачає, що рядки будуть коротшими за довжину буфера.
Благо

12

Mac OS X - це Unix, Objective-C - супер суперсет, тому ви можете просто використовувати стару школу fopenі fgetsз <stdio.h>. Це гарантовано працює.

[NSString stringWithUTF8String:buf]перетворить рядок C в NSString. Існують також методи створення рядків в інших кодуваннях та створення без копіювання.


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

9

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


6

Відповідний спосіб читання текстових файлів у програмі Cocoa / Objective-C задокументований у посібнику з програмування струнних програм Apple. У розділі для читання та запису файлів має бути саме те, що ви хочете. 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 довжині рядка, оскільки ми хочемо створити простір для завершення NULL.


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

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 є власний метод - я просто недостатньо досвідчений, щоб знати, що це :)


5
Якщо ви не знаєте, як це зробити в Objective-C, то навіщо говорити, що це не відповідь? Є безліч причин не опускатися до прямої C, якщо ви можете це зробити інакше. Наприклад, функції C обробляють char *, але для читання чогось іншого, наприклад, різних кодувань, потрібно набагато більше роботи. Також він хоче об'єкти NSString. Все, що говорити, прокручуючи це самостійно - це не лише більше коду, але й схильність до помилок.
Квінн Тейлор

3
Я погоджуюся з вами на 100%, але я виявив, що (іноді) краще отримати відповідь, яка працює швидко, реалізувати її, а потім, коли з’явиться більш правильна альтернатива, використовуйте це. Це особливо важливо під час складання прототипів, надання можливості отримати щось для роботи, а потім прогресувати звідти.
KevinDTimm

3
Я щойно зрозумів, що починається "Ця відповідь", а не "Відповідь". До! Я погоджуюся, це, безумовно, краще мати хак, який працює, ніж елегантний код, який не робить. Я вас не оскаржував, але викидати здогадки, не знаючи, що, можливо, може бути і Objective-C, теж не дуже корисно. Тим не менш, докладати зусиль завжди краще, ніж хтось, хто знає і не допомагає ... ;-)
Квінн Тейлор,

Це не дає відповіді на запитання. Щоб критикувати або вимагати роз'яснення у автора, залиште коментар під їх публікацією.
Роботизований кіт

1
@KevinDTimm: Я згоден; Мені просто шкода, що я не помітив, що це відповідь 5 років. Можливо, це metaпитання; повинні дуже старі запитання від постійних користувачів можуть бути позначені для перегляду?
Роботизований кіт

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

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, це те, що це не стосується бажання ОП скоротити використання пам'яті. ОП не запитував, як використовувати метод (який завантажує весь файл у пам'ять), він просив зручні для пам'яті альтернативи для великих текстових файлів. Цілком можливо мати багатогігабайтні текстові файли, що, очевидно, створює проблему з пам'яттю.
Джошуа Ноцці
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.