Objective-C:行ごとにファイルを読み取る


140

Objective-Cで大きなテキストファイルを処理する適切な方法は何ですか?各行を個別に読み取る必要があり、各行をNSStringとして扱いたいとしましょう。これを行う最も効率的な方法は何ですか?

1つの解決策は、NSStringメソッドを使用することです。

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

次に、改行セパレータで行を分割し、配列の要素を反復処理します。ただし、これはかなり非効率的です。ファイルを一度に読み込むのではなく、ファイルをストリームとして扱い、各行を列挙する簡単な方法はありませんか?Javaのjava.io.BufferedReaderのようなものです。


1
少し遅れますが、[NSScanner scanUpToString:@ "\ n" intoString:&read]をチェックしてください。各行を文字列 'read'に読み込む場合を想定しています。
hauntsaninja

この同様の質問をご覧ください。私は、ファイルを1行ずつ読み取るプロジェクトを設定しました。
JJD 2012年

回答:


63

それは素晴らしい質問です。@Diederikは良い答えだと思いますが、Cocoaにあなたがやりたいことを正確に行うためのメカニズムがないのは残念です。

NSInputStreamNバイトのチャンクを読み取ることができますが(と非常に似てjava.io.BufferedReaderいます)、それを自分でに変換し、NSString改行(またはその他の区切り文字)をスキャンして、残りの文字を保存して次の読み取りに使用するか、さらに文字を読み取る必要があります。改行がまだ読まれていない場合。(次に、に変換できるNSFileHandleを読み取ることができますが、基本的に同じプロセスです。)NSDataNSString

Appleには、詳細を記入するのに役立つStream Programming Guideがあり、このSOの質問は、uint8_t*バッファを扱う場合にも役立つかもしれません。

あなたはあなたのための詳細を処理することができ、クラスでこの振る舞いをカプセル化するために、良いアイデア、あるいはサブクラス化でしょう(特にあなたのプログラムのさまざまな部分で)頻繁にこのような文字列を読んでするつもりならNSInputStream(だように設計されましたサブクラス化)し、必要なものを正確に読み取ることができるメソッドを追加します。

記録としては、これは追加すると便利な機能だと思います。これを可能にするための機能強化リクエストを提出します。:-)


編集:このリクエストはすでに存在することが判明しました。これについては2006年からのレーダーがあります(Apple内部の人々の場合はrdar:// 4742914)。


10
この問題に対するDave DeLongの包括的なアプローチについては、こちらをご覧ください: stackoverflow.com/questions/3707427#3711079
Quinn Taylor

プレーンなNSDataとメモリマッピングを使用することもできます。:私はデイブデロングのNSFileHandleの実装と同じAPIを持つサンプルコードと答え作成した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 mbのファイルを持っています。このコードを使用してファイルを読み取っても、メモリが直線的に増加することはありません。誰も私を助けることができますか?
GameLoading、2011年

37
これは質問に対する回答ではありません。問題は、メモリ使用量を減らすためにファイルを1行
ずつ読み取ることでした

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
[result appendFormat: "%s"、buffer]を変更するだけです。to [結果appendFormat:@ "%s"、buffer];
Codezy

1
空の行、または単一の改行文字で構成される行を受け入れるようにフォーマットをどのように変更しますか?
jakev 2013年

これは、812行後、私には早く止まります。812行目は "... 3 more"であり、リーダーは空の文字列を出力しています。
sudo

1
空の行を通過するチェックを追加しました。int fscanResult = fscanf(file、 "%4095 [^ \ n]%n%* c"、buffer、&charsRead); if(fscanResult == 1){[result appendFormat:@ "%s"、buffer]; } else {if(feof(file)){break; } else if(ferror(file)!= 0){break; } fscanf(file、 "\ n"、nil、&charsRead); ブレーク; }
ローズハルマン行き

1
私がfscanfのドキュメントを正しく読んでいる場合、"%4095[^\n]%n%*c"バッファが読み込まれるたびに1文字が暗黙的に消費されて破棄されます。このフォーマットは、行がバッファー長よりも短いと想定しているようです。
Blago

12

Mac OS XはUnixであり、Objective-CはCのスーパーセットであるため、旧式のfopenfgetsfromをそのまま使用できます<stdio.h>。動作することが保証されています。

[NSString stringWithUTF8String:buf]C文字列をに変換しNSStringます。他のエンコーディングで文字列を作成し、コピーせずに作成する方法もあります。


[匿名コメントのコピー] fgetsには'\n'文字が含まれるため、文字列を変換する前にそれを取り除くことができます。
Kornel 2013年

9

NSInputStreamファイルストリームの基本的な実装を持つwhichを使用できます。バイトをバッファに読み込むことができます(read:maxLength:メソッド)。改行がないかバッファを自分でスキャンする必要があります。


6

Cocoa / Objective-Cでテキストファイルを読み取る適切な方法は、Appleの文字列プログラミングガイドに記載されています。ファイルの読み取りと書き込みのセクションは、まさにあなたが求めているものでなければなりません。PS:「ライン」とは何ですか?「\ n」で区切られた文字列の2つのセクション?または「\ r」?または「\ r \ n」?それともあなたは実際に段落の後ですか?前述のガイドには、文字列を行または段落に分割するセクションも含まれています。(このセクションは「段落と改行」と呼ばれ、上記でポイントしたページの左側のメニューにリンクされています。残念ながら、このサイトでは、複数のURLを投稿できません。まだ信頼できるユーザーではありません。)

Knuthを言い換えると、時期尚早の最適化がすべての悪の根源です。「ファイル全体をメモリに読み込む」のが遅いと単純に想定しないでください。それをベンチマークしましたか?実際にファイル全体をメモリ読み込むことを知っていますか?多分それは単にプロキシオブジェクトを返し、あなたが文字列を消費するときに裏で読み続けますか?(免責事項:NSStringが実際にこれを行うかどうかはわかりません。おそらく可能です。)重要なのは、最初に文書化された方法で実行することです。次に、ベンチマークが、これが希望するパフォーマンスを持たないことを示している場合は、最適化します。


CRLF(Windows)の行末について言及したので、これは実際にはObjective-Cの方法を壊すケースです。の-stringWithContentsOf*後にいずれかの方法を使用-componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]すると、\rおよびが\n個別に表示され、各行の後に空白行が追加されます。
シボーン

つまり、CRのみのファイルではfgetsソリューションが失敗します。しかし、これらは(理論的には)珍しく、fgetsはLFとCRLFの両方で機能します。
シボーン

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は改行文字を保持しないことに注意してください。また、NULL終端用のスペースを作りたいので、strの長さを+1しました。


4

ファイルを1行ずつ(極端に大きなファイルの場合も)読み取るには、次の関数を使用します。

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

クラスはDave DeLongによって行われました


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 2009年

3
「答え」ではなく「この答え」が始まったことに気づきました。どー!確かに、機能するエレガントなコードよりも機能するハックを使用する方が間違いなく優れています。私はあなたに反対票を投じませんでしたが、Objective-Cが何を持っているかを知っている推測を捨てることは、おそらくあまり役​​に立ちません。それでも、努力することは、知っていて助けにならない人よりも常に優れています... ;-)
Quinn Taylor

これは質問に対する答えを提供しません。批評したり、著者に説明を求めたりするには、投稿の下にコメントを残してください。
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による応答とDave DeLongからのコードが非常に参考になりました。私はこの問題の解決策を探していましたが、\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

これらの回答の多くは、テキストファイルを一度に1つにまとめるのではなく、テキストファイル全体をメモリに読み込むことに依存しているようです。FileHandleを使用してメモリへの影響を低く保つ、最新のSwiftでの私の解決策を次に示します。

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、最後まで1行ずつ読み取る方法を示すサンプルコードをいくつか示します。

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

内容をすべてメモリに読み込まないように、一度に1行を読み取る方法を尋ねていました。ソリューションは、内容全体を含む文字列を作成し、それを行に分割します。
デビッド

-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は、メソッド(ファイル全体をメモリにロードする)の使用方法を尋ねるのではなく、大きなテキストファイルのメモリフレンドリーな代替方法を求めていました。マルチギガバイトのテキストファイルが存在する可能性は十分にあり、これは明らかにメモリの問題を引き起こします。
Joshua Nozzi 2014
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.