цель-c: чтение файла построчно

<p>как правильно обращаться с большими текстовыми файлами в objective-c? допустим, мне нужно читать каждую строку отдельно и хотеть рассматривать каждую строку как строку nsstring. каков наиболее эффективный способ сделать это?</p><p>одним из решений является использование метода nsstring:</p><pre><code>+ (id)stringwithcontentsoffile:(nsstring *)path encoding:(nsstringencoding)enc error:(nserror **)error </code></pre><p>а затем разбить строки с помощью разделителя новой строки, а затем перебрать элементы массива. однако это кажется довольно неэффективным. нет ли простого способа обработать файл как поток, перечисляя по каждой строке, вместо того, чтобы просто прочитать все сразу? вроде как java.io.bufferedreader java.</p>

<p>это отличный вопрос. я думаю, что у {<em>@diederik</em> есть хороший ответ, хотя, к сожалению, у какао нет механизма для именно того, что вы хотите сделать.</p><p> <a href="http://developer.apple.com/documentation/cocoa/reference/foundation/classes/nsinputstream\_class/"><code>nsinputstream</code></a> позволяет читать фрагменты по n байтов (очень похоже на <code>java.io.bufferedreader</code>), но вам нужно преобразовать его в <code>nsstring</code> самостоятельно, затем сканировать строки (или любой другой разделитель) и сохранять оставшиеся символы для затем прочитайте или прочитайте больше символов, если символ новой строки еще не прочитан. ( <a href="http://developer.apple.com/documentation/cocoa/reference/foundation/classes/nsfilehandle\_class/"><code>nsfilehandle</code></a> позволяет читать <code>nsdata</code>, который затем можно преобразовать в <code>nsstring</code>, но по сути это тот же процесс.)</p><p>у apple есть <a href="http://developer.apple.com/documentation/cocoa/conceptual/streams/streams.html">руководство по программированию потока</a> , которое может помочь заполнить детали, и <a href="https://stackoverflow.com/questions/1002652/">этот so вопрос</a> также может помочь, если вы собираетесь работать с буферами <code>uint8\_t*</code>.</p><p>если вы собираетесь читать такие строки часто (особенно в разных частях вашей программы), было бы неплохо инкапсулировать это поведение в классе, который может обработать детали для вас, или даже создать подкласс <code>nsinputstream</code> (это <a href="http://developer.apple.com/documentation/cocoa/reference/foundation/classes/nsinputstream\_class/reference/reference.html#//apple\_ref/doc/uid/20001982-64356">предназначен для создания подклассов</a> ) и добавления методов, которые позволяют вам читать именно то, что вы хотите.</p><p>для справки, я думаю, что это было бы хорошей функцией для добавления, и я буду подавать запрос на улучшение для чего-то, что делает это возможным. :-)</p><hr><p><strong>edit:</strong> оказывается, этот запрос уже существует. для этого есть радар, датированный 2006 годом (rdar: // 4742914 для сотрудников apple).</p>

<p>это будет работать для общего чтения <code>string</code> из <code>text</code>. если вы хотите прочитать более длинный текст<em> (большой размер текста) </em>, тогда используйте метод, который упоминали другие люди, такие как буферизованный<em> (резервный размер текста в памяти) </em>.</p><h3>скажем, вы читаете текстовый файл.</h3><pre><code>nsstring* filepath = @""//file path... nsstring* fileroot = [[nsbundle mainbundle] pathforresource:filepath oftype:@"txt"]; </code></pre><h3>вы хотите избавиться от новой линии.</h3><pre><code>// 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:@";"]]; </code></pre><p>там у вас есть это.</p>

<p>это должно сделать трюк:</p><pre><code>#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; } </code></pre><p>используйте следующим образом:</p><pre><code>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); </code></pre><p>этот код читает не-новые символы из файла, до 4095 за раз. если у вас есть строка длиной более 4095 символов, она продолжает читать, пока не достигнет новой строки или конца файла.</p><p><em> примечание </em>: я не проверял этот код. пожалуйста, проверьте его перед использованием.</p>

<p>mac os x - unix, objective-c - надмножество c, так что вы можете просто использовать old-school <code>fopen</code> и <code>fgets</code> из <code><stdio.h></code>. это гарантированно работает.</p><p><code>[nsstring stringwithutf8string:buf]</code> преобразует строку c в <code>nsstring</code>. существуют также методы для создания строк в других кодировках и создания без копирования.</p>

<p>вы можете использовать <code>nsinputstream</code>, который имеет базовую реализацию для файловых потоков. вы можете прочитать байты в буфер (метод <code>read:maxlength:</code>). вы должны сканировать буфер для новых строк самостоятельно.</p>

<p>соответствующий способ чтения текстовых файлов в cocoa/objective-c описан в руководстве по программированию apple string. раздел для <a href="http://developer.apple.com/documentation/cocoa/conceptual/strings/articles/readingfiles.html">чтения и записи файлов</a> должен быть именно тем, что вам нужно. ps: что такое "линия"? два раздела строки, разделенные "\ n"? или "г"? или "\ r\n"? или, может быть, вы на самом деле после абзацев? ранее упомянутое руководство также включает раздел по разбиению строки на строки или абзацы. (этот раздел называется «абзацы и разрывы строк» и связан с левым меню на странице, на которую я указал выше. к сожалению, этот сайт не позволяет мне публиковать более одного url, поскольку я еще не заслуживающий доверия пользователь.)</p><p>перефразируя кнута: преждевременная оптимизация - корень всего зла. не просто предполагайте, что «чтение всего файла в память» происходит медленно. вы оценили это? знаете ли вы, что он <em>фактически</em> читает весь файл в память? может быть, он просто возвращает прокси-объект и продолжает читать за кулисами, пока вы используете строку? (<em>disclaimer: я понятия не имею, действительно ли nsstring делает это. это возможно.</em>) суть в том, чтобы сначала использовать документированный способ действий. затем, если тесты показывают, что это не соответствует желаемой производительности, оптимизируйте.</p>

<p>многие из этих ответов представляют собой длинные фрагменты кода или они читаются во всем файле. мне нравится использовать методы c для этой самой задачи.</p><pre><code>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); } </code></pre><p>обратите внимание, что fgetln не сохранит ваш символ новой строки. кроме того, мы +1 длина строки, потому что мы хотим освободить место для завершения null.</p>

<p>как и другие, ответили, что nsinputstream и nsfilehandle - хорошие варианты, но это также может быть сделано довольно компактным способом с nsdata и отображением памяти:</p><p>brlinereader.h</p><pre><code>#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 </code></pre><p>brlinereader.m</p><pre><code>#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 </code></pre>

<p>как сказал @pornel, c api очень удобен.</p><pre><code>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); } </code></pre>

<p>прочитать файл построчно (также для очень больших файлов) можно с помощью следующих функций:</p><pre><code>ddfilereader * reader = [[ddfilereader alloc] initwithfilepath:pathtomyfile]; nsstring * line = nil; while ((line = [reader readline])) { nslog(@"read line: %@", line); } [reader release]; </code></pre><p>или же:</p><pre><code>ddfilereader * reader = [[ddfilereader alloc] initwithfilepath:pathtomyfile]; [reader enumeratelinesusingblock:^(nsstring * line, bool * stop) { nslog(@"read line: %@", line); }]; [reader release]; </code></pre><p>класс ddfilereader, который включает это, является следующим:</p><p><strong>интерфейсный файл (.h):</strong></p><pre><code>@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 </code></pre><p><strong>реализация (.m)</strong></p><pre><code>#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 </code></pre><p>класс был сделан <a href="https://stackoverflow.com/users/115730/dave-delong">дэйв делонг</a> </p>

<p>этот ответ не objc, а c.</p><p>поскольку objc основан на c, почему бы не использовать fgets?</p><p>и да, я уверен, что у objc есть свой метод - я просто недостаточно опытен, чтобы знать, что это такое :)</p>

<p>я добавляю это, потому что все другие ответы, которые я пробовал, терпели неудачу так или иначе. следующий метод может обрабатывать большие файлы, произвольные длинные строки, а также пустые строки. он был протестирован с реальным содержимым и удалит символ новой строки из вывода.</p><pre><code>- (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; } </code></pre><p>кредит идет на @ адам розенфилд и @sooop</p>

<p>я нашел ответ @lukaswelte и код от <a href="https://stackoverflow.com/users/115730/dave-delong">дэйв делонг</a> очень полезным. я искал решение этой проблемы, но мне нужно было проанализировать большие файлы по <code>\r\n</code>, а не <code>\n</code>. </p><p>написанный код содержит ошибку при разборе более чем одного символа. я изменил код, как показано ниже.</p><p>.h файл:</p><pre><code>#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 </code></pre><p>.m файл:</p><pre><code>#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 </code></pre>

<p>из ответа @adam rosenfield строка форматирования <code>fscanf</code> будет изменена, как показано ниже:</p><pre><code>"%4095[^\r\n]%n%*[\n\r]" </code></pre><p>он будет работать в окончаниях строк osx, linux, windows. </p>

<p>использование категории или расширения, чтобы сделать нашу жизнь немного проще.</p><pre><code>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 } </code></pre>