목표 -C : 파일을 한 줄씩 읽기


140

Objective-C에서 큰 텍스트 파일을 처리하는 적절한 방법은 무엇입니까? 각 줄을 개별적으로 읽고 각 줄을 NSString으로 취급하려고한다고 가정 해 봅시다. 가장 효율적인 방법은 무엇입니까?

한 가지 해결책은 NSString 방법을 사용하는 것입니다.

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

그런 다음 줄 바꿈 구분 기호로 줄을 분할 한 다음 배열의 요소를 반복합니다. 그러나 이것은 상당히 비효율적입니다. 파일을 한 번에 모두 읽는 대신 파일을 스트림으로 취급하여 각 줄을 열거하는 쉬운 방법이 있습니까? Java의 java.io.BufferedReader를 좋아하십시오.


1
조금 늦었지만 각 행을 'read'문자열로 읽으려는 경우 [NSScanner scanUpToString : @ "\ n"intoString : & read]를 확인하십시오.
hauntsaninja

비슷한 질문을 살펴보십시오 . 파일을 한 줄씩 읽는 프로젝트를 설정했습니다 .
JJD

답변:


63

좋은 질문입니다. @Diederik 은 좋은 대답을 가지고 있다고 생각 하지만, Cocoa가 정확히 당신이하고 싶은 일에 대한 메커니즘을 가지고 있지 않다는 것은 불행합니다.

NSInputStreamN 바이트 청크 (매우 유사 java.io.BufferedReader) 를 읽을 수는 있지만 직접로 변환 NSString한 다음 줄 바꿈 (또는 다른 구분 기호)을 스캔하고 다음 읽기를 위해 나머지 문자를 저장하거나 더 많은 문자를 읽으십시오 개행을 아직 읽지 않은 경우. ( NSFileHandle당신은 읽을 수 NSData있는 당신이 다음에 변환 할 수 있습니다NSString 있지만 본질적으로 동일한 프로세스입니다.)

Apple은 세부 사항을 작성하는 데 도움 이되는 스트림 프로그래밍 안내서 를 가지고 있으며이 SO 질문uint8_t*버퍼를 다루는 데 도움이 될 수 있습니다 .

당신이 (특히 프로그램의 다른 부분에) 자주 같은 문자열을 읽기 위하여려고하는 경우 당신에 대한 세부 정보를 처리, 또는 서브 클래스 수있는 클래스에서이 동작을 캡슐화하는 좋은 아이디어가 될 것입니다 NSInputStream(이 있어요 수 있도록 설계 서브 클래스 )와 정확히 당신이 원하는 것을 읽을 수 있도록 방법을 추가.

레코드의 경우이 기능을 추가하는 것이 좋을 것으로 생각되며이를 가능하게하는 개선 요청을 제출할 것입니다. :-)


편집 : 이 요청이 이미 존재합니다. 이를 위해 2006 년부터 데이트 한 레이더가 있습니다 (Apple-internal people의 경우 rdar : // 4742914).


10
여기에이 문제에 대한 데이브 드롱의 포괄적 인 접근 방식을 참조하십시오 stackoverflow.com/questions/3707427#3711079
퀸 테일러

일반 NSData 및 메모리 매핑을 사용할 수도 있습니다. Dave DeLong의 NSFileHandle 구현과 동일한 API를 사용하는 예제 코드로 답변을 만들었습니다. stackoverflow.com/a/21267461/267043
Bjørn Olav Ruud

95

이것은 일반적으로 읽기 A의 작동 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
나는 70MB 파일을 가지고 있는데,이 코드를 사용하여 파일을 읽으면 메모리가 선형으로 증가한다는 것을 알지 못한다. 아무도 나를 도울 수 있습니까?
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
[result appendFormat : "% s", buffer] 만 변경하십시오. [response appendFormat : @ "% s", 버퍼];
Codezy

1
빈 줄이나 단일 줄 바꿈 문자로 구성된 줄을 허용하도록 형식을 어떻게 수정 하시겠습니까?
jakev

이것은 812 줄 후에 나에게 일찍 멈추고 있습니다. 812 번째 줄은 "... 3 more"로, 리더 출력을 빈 문자열로 만듭니다.
sudo

1
빈 줄을 지나는 검사를 추가했습니다. int fscanResult = fscanf (file, "% 4095 [^ \ n] % n % * c", buffer, & charsRead); if (fscanResult == 1) {[결과 appendFormat : @ "% s", 버퍼]; } else {if (feof (file)) {중단; } 그렇지 않으면 if (ferror (file)! = 0) {break; } fscanf (파일, "\ n", nil, & charsRead); 단절; }
Rose-Hulman으로 이동

1
fscanf 설명서를 올바르게 "%4095[^\n]%n%*c"읽으면 버퍼를 읽을 때마다 한 문자 씩 자동으로 소비하고 버립니다. 이 형식은 행이 버퍼 길이보다 짧을 것으로 가정합니다.
Blago

12

Mac OS X은 Unix이고 Objective-C는 C 수퍼 셋이므로 구식 fopenfgets에서 사용할 수 있습니다 <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 전용 파일에서 실패합니다. 그러나 요즘에는 (이론적으로) 드물고, LF와 CRLF 모두에서 fgets가 작동합니다.
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은 개행 문자를 유지하지 않습니다. 또한 NULL 종료를위한 공간을 만들고 싶기 때문에 str의 길이를 +1합니다.


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

수업은 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'기반이므로 fget을 사용하지 않는 이유는 무엇입니까?

그리고 네, ObjC는 자신의 방법을 가지고 있다고 확신합니다-나는 그것이 무엇인지 아직 알기에 충분히 능숙하지 않습니다 :)


5
Objective-C에서 수행하는 방법을 모른다면 왜 대답이 아니라고 말합니까? 달리 할 수 ​​있다면 스트레이트 C로 드롭하지 않는 많은 이유가 있습니다. 예를 들어, C 함수는 char *를 처리하지만 다른 인코딩과 같은 다른 것을 읽으려면 훨씬 더 많은 작업이 필요합니다. 또한 NSString 객체를 원합니다. 모두들 말하지만, 이것을 스스로 굴리는 것은 더 많은 코드 일뿐 만 아니라 오류가 발생하기 쉽습니다.
Quinn Taylor

3
나는 당신에게 100 % 동의하지만, 때로는 빠른 답변을 얻고 그것을 구현 한 다음보다 정확한 대안이 나타나면 그것을 활용하는 것이 좋습니다. 프로토 타이핑 할 때 특히 중요하며, 무언가를 얻을 수있는 기회를 제공하고 거기서부터 진행할 수 있습니다.
KevinDTimm

3
방금 "답변"이 아니라 "이 대답"이 시작되었다는 것을 깨달았습니다. 도! 나는 그렇지 않은 우아한 코드보다 작동하는 핵을 갖는 것이 더 낫다는 것에 동의합니다. 난 당신을 downvote하지 않았지만 Objective-C가 무엇을 가지고 있는지 알지 않고 추측을 던지는 것도별로 도움이되지 않습니다. 그럼에도 불구하고 노력하는 것은 항상 알고 있고 도움이되지 않는 사람보다 낫습니다 ... ;-)
Quinn Taylor

이것은 질문에 대한 답변을 제공하지 않습니다. 저자에게 비평을하거나 설명을 요청하려면 게시물 아래에 의견을 남겨주십시오.
로봇 식 고양이

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의 답변과 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

나는이 답변이 전체 텍스트 파일을 한 번에 한 청크 대신 메모리로 읽는 것에 의존한다는 것을 알았습니다. 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끝까지 한 줄씩 읽는 방법을 보여주는 샘플 코드입니다 .

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

그는 한 번에 한 줄씩 읽는 방법에 대해 물었으므로 전체 내용을 메모리로 읽지 않습니다. 솔루션은 전체 내용으로 문자열을 만든 다음 줄로 나눕니다.
David

-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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.