문제

무엇을 적절한 방법을 다루는 큰 텍스트 파일로 제공하는 것을 목적으 C?자의 말을 읽을 필요가 각 라인에는 별도로 및 치료를 원하는 각 라인으로@입니다.무엇을 가장 효율적인 방법이었는지 설명하려고 노력합니다.

하나의 솔루션을 사용하여@입법:

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

다음 분 라인으로 줄 바꿈 구분한 다음,반복한다.그러나 이것은 매우 비효율적이다.가 쉽지 않을 치료하는 방법 파일의 스트림으로,열거를 통해서 각 라인,대신 그것을 읽는 모든가?처럼 Java java.io.BufferedReader.

도움이 되었습니까?

해결책

그것은 좋은 질문입니다. 제 생각에는 @Diederik 코코아가 당신이하고 싶은 일에 대한 메커니즘이 없다는 것은 불행한 일이지만 좋은 대답을 가지고 있습니다.

NSInputStream N Bytes의 덩어리를 읽을 수 있습니다 (매우 유사합니다. java.io.BufferedReader),하지만 당신은 그것을 NSString 혼자서, Newlines (또는 다른 구분 기호)를 스캔하고 다음 읽기를 위해 나머지 캐릭터를 저장하거나 Newline을 아직 읽지 않은 경우 더 많은 캐릭터를 읽으십시오. (NSFileHandle 당신이 읽을 수 있습니다 NSData 그런 다음 An으로 변환 할 수 있습니다 NSString, 그러나 그것은 본질적으로 같은 과정입니다.)

애플에는 a 스트림 프로그래밍 안내서 그것은 세부 사항을 채우는 데 도움이 될 수 있습니다 이렇게 질문 당신이 다루려고한다면 도움이 될 수 있습니다. uint8_t* 버퍼.

이과 같은 현이 자주 (특히 프로그램의 다른 부분에서) 문자열을 읽으려면 세부 사항을 처리하거나 서브 클래싱을 처리 할 수있는 수업 에서이 동작을 캡슐화하는 것이 좋습니다. NSInputStream (그것의 서브 클래스로 설계되었습니다) 및 원하는 것을 정확하게 읽을 수있는 방법을 추가합니다.

레코드의 경우, 이것이 추가하기에 좋은 기능이 될 것이라고 생각하며,이를 가능하게하는 무언가에 대한 강화 요청을 제출할 것입니다. :-)


편집하다: 이 요청이 이미 존재합니다. 2006 년부터 이에 대한 레이더가 있습니다 (rdar : // 4742914는 사과 내부 사람들을위한 4742914).

다른 팁

이것은 일반 독서 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:@";"]];

당신은 그것을 가지고 있습니다.

이것은 트릭을 수행해야합니다.

#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 자 미만의 라인이있는 경우 Newline 또는 FILE OF FILE에 부딪 칠 때까지 계속 읽습니다.

메모:이 코드를 테스트하지 않았습니다. 사용하기 전에 테스트하십시오.

Mac OS X는 Unix, Objective-C는 C Superset이므로 구식 만 사용할 수 있습니다. fopen 그리고 fgets ~에서 <stdio.h>. 작동하도록 보장됩니다.

[NSString stringWithUTF8String:buf] c 문자열을 변환합니다 NSString. 다른 인코딩에서 문자열을 생성하고 복사하지 않고 생성하는 방법도 있습니다.

당신이 사용할 수있는 NSInputStream 파일 스트림에 대한 기본 구현이 있습니다. 버퍼에 바이트를 읽을 수 있습니다 (read:maxLength: 방법). 최신의 버퍼를 직접 스캔해야합니다.

Cocoa/Objective-C에서 텍스트 파일을 읽는 적절한 방법은 Apple의 문자열 프로그래밍 안내서에 문서화되어 있습니다. 섹션 파일 읽기 및 쓰기 당신이 뒤 따르는 것이되어야합니다. 추신 : "라인"이란 무엇입니까? " n"으로 구분 된 문자열의 두 섹션? 또는 " r"? 또는 " r n"? 아니면 실제로 단락이 끝났습니까? 앞에서 언급 한 안내서에는 문자열을 줄 또는 단락으로 분할하는 섹션도 포함되어 있습니다. (이 섹션은 "단락 및 라인 브레이크"라고하며 위에서 지적한 페이지의 왼쪽 메뉴에서 연결되어 있습니다. 불행히도이 사이트는 내가 두 개 이상의 URL을 게시 할 수 없습니다. 아직 신뢰할 수있는 사용자가 아닙니다.)

Knuth를 역설적으로 말하면 : 조기 최적화는 모든 악의 근본입니다. 단순히 "전체 파일을 메모리에 읽는 것이 느리다고 가정하지 마십시오. 당신은 그것을 벤치마킹 했습니까? 당신은 그것을 알고 있습니까? 실제로 전체 파일을 메모리에 읽으십니까? 어쩌면 그것은 단순히 프록시 객체를 반환하고 문자열을 소비 할 때 무대 뒤에서 계속 읽을 수 있습니까? (면책 조항 : NSString이 실제로 이것을하는지 모르겠습니다. 아마도 아마도 가능할 수 있습니다.) 요점은 다음과 같습니다. 먼저 문서화 된 일을하는 방법으로 가십시오. 그런 다음 벤치 마크가 원하는 성능이 없음을 보여 주면 최적화하십시오.

이 답변 중 많은 사람들이 코드의 긴 덩어리이거나 전체 파일에서 읽습니다. 이 작업에 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은 Newline 캐릭터를 유지하지 않습니다. 또한, 우리는 Null 종료를위한 공간을 만들고 싶기 때문에 STR의 길이를 +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

수업은 데이브 델롱

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

다른 사람들이 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

이 답변은 OBJC가 아니라 C입니다.

OBJC가 'C'기반이므로 FGES를 사용하지 않는 이유는 무엇입니까?

그리고 네, 나는 OBJC가 자체 방법을 가지고 있다고 확신합니다 - 나는 그것이 무엇인지 알지 못할만큼 충분히 능숙하지 않습니다. :)

@adam Rosenfield의 답변에서, 서식 문자열 fscanf 아래처럼 변경됩니다.

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

OSX, Linux, Windows Line Endings에서 작동합니다.

카테고리 나 확장을 사용하여 우리의 삶을 조금 더 쉽게 만들 수 있습니다.

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
}

@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

나는이 때문에 다른 모든 답변을 나은 방법 중 하나 또는 다른합니다.다음 방법은 큰 파일을 처리할 수 있습니다.임의 긴 줄 뿐만 아니라 빈 라인입니다.테스트되었으로 실제 내용과 지구 밖으로 줄 바꿈 문자에서 출력됩니다.

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

신@아담은 그것과@sooop

작은 파일에 사용하는 멋진 간단한 솔루션은 다음과 같습니다.

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

이 스크립트를 사용하면 훌륭하게 작동합니다.

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);
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top