วัตถุประสงค์ -C: อ่านไฟล์ทีละบรรทัด


140

วิธีที่เหมาะสมในการจัดการกับไฟล์ข้อความขนาดใหญ่ใน Objective-C คืออะไร? สมมติว่าฉันต้องอ่านแต่ละบรรทัดแยกกันและต้องการที่จะถือว่าแต่ละบรรทัดเป็น NSString วิธีที่มีประสิทธิภาพที่สุดในการทำเช่นนี้คืออะไร?

ทางออกหนึ่งคือการใช้วิธี NSString:

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

แล้วแยกบรรทัดด้วยตัวแบ่งบรรทัดใหม่แล้ววนซ้ำองค์ประกอบในอาร์เรย์ อย่างไรก็ตามดูเหมือนว่าจะไม่มีประสิทธิภาพพอสมควร ไม่มีวิธีง่ายๆในการจัดการไฟล์เป็นสตรีมระบุแต่ละบรรทัดแทนที่จะอ่านมันทั้งหมดในครั้งเดียวหรือไม่? ค่อนข้างชอบจาวาของ java.io.BufferedReader


1
ช้าไปหน่อยลองดู [NSScanner scanUpToString: @ "\ n" เข้าสู่String: & read] โดยสมมติว่าคุณต้องการอ่านแต่ละบรรทัดในสตริง 'read'
hauntsaninja

โปรดดูคำถามที่คล้ายกันนี้ ฉันจะตั้งค่าโครงการที่เกี่ยวข้องกับการอ่านไฟล์ทีละบรรทัด
JJD

คำตอบ:


63

นั่นเป็นคำถามที่ดี ฉันคิดว่า@Diederikมีคำตอบที่ดีแม้ว่าจะโชคร้ายที่ Cocoa ไม่มีกลไกสำหรับสิ่งที่คุณต้องการจะทำ

NSInputStreamช่วยให้คุณอ่านชิ้นของ N ไบต์ (คล้ายกันมากjava.io.BufferedReader) แต่คุณต้องแปลงมันเป็นNSStringของคุณเองแล้วสแกนหาบรรทัดใหม่ (หรือตัวคั่นอื่น ๆ ) และบันทึกอักขระที่เหลือสำหรับการอ่านครั้งถัดไปหรืออ่านตัวอักษรเพิ่มเติม ถ้าบรรทัดใหม่ยังไม่ได้อ่าน ( NSFileHandleให้คุณอ่านสิ่งNSDataที่คุณสามารถแปลงเป็นNSStringได้ แต่โดยพื้นฐานแล้วเป็นกระบวนการเดียวกัน)

Apple มีคู่มือการเขียนโปรแกรมสตรีมที่สามารถช่วยกรอกรายละเอียดและคำถาม SO นี้อาจช่วยได้เช่นกันหากคุณกำลังจะจัดการกับuint8_t*บัฟเฟอร์

หากคุณกำลังจะอ่านสตริงเช่นนี้บ่อย ๆ (โดยเฉพาะในส่วนต่าง ๆ ของโปรแกรม) มันเป็นความคิดที่ดีที่จะสรุปพฤติกรรมนี้ในคลาสที่สามารถจัดการรายละเอียดของคุณหรือแม้แต่คลาสย่อยNSInputStream(มันถูกออกแบบมาเพื่อ คลาสย่อย ) และการเพิ่มเมธอดที่อนุญาตให้คุณอ่านสิ่งที่คุณต้องการ

สำหรับบันทึกฉันคิดว่านี่จะเป็นคุณสมบัติที่ดีในการเพิ่มและฉันจะยื่นคำขอการปรับปรุงสำหรับสิ่งที่ทำให้เป็นไปได้ :-)


แก้ไข:ปรากฎคำขอนี้มีอยู่แล้ว มีการนัดหมายเรดาร์ตั้งแต่ปี 2549 สำหรับสิ่งนี้ (rdar: // 4742914 สำหรับคนที่อยู่ภายใน Apple)


10
ดูวิธีการที่ครอบคลุมของ Dave DeLong สำหรับปัญหานี้ได้ที่นี่: stackoverflow.com/questions/3707427#3711079
Quinn Taylor

นอกจากนี้ยังเป็นไปได้ที่จะใช้ NSData ธรรมดาและการจับคู่หน่วยความจำ ฉันได้สร้างคำตอบพร้อมโค้ดตัวอย่างซึ่งมี API เดียวกับการใช้ NSFileHandle ของ Dave DeLong: stackoverflow.com/a/21267461/267043
Bjørn Olav Ruud

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 การใช้รหัสนี้เพื่ออ่านไฟล์ไม่ได้ทำให้ฉันเพิ่มหน่วยความจำแบบเส้นตรง มีใครช่วยฉันได้บ้าง
โหลดเกม

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
เพียงแค่เปลี่ยน [ผลการผนวกรูปแบบ: "% s", บัฟเฟอร์]; ถึง [ผลลัพธ์ appendFormat: @ "% s", บัฟเฟอร์];
Codezy

1
คุณจะปรับเปลี่ยนรูปแบบอย่างไรเพื่อยอมรับบรรทัดว่างเปล่าหรือมากกว่าบรรทัดที่ประกอบด้วยอักขระขึ้นบรรทัดใหม่เดียว
jakev

นี่เป็นการหยุดก่อนสำหรับฉันหลังจาก 812 บรรทัด บรรทัดที่ 812 คือ "... 3 more" และนั่นทำให้การอ่านเอาต์พุตสตริงว่างเปล่า
sudo

1
ฉันเพิ่มเช็คเพื่อผ่านบรรทัดว่าง: int fscanResult = fscanf (ไฟล์, "% 4095 [^ \ n]% n% * c", บัฟเฟอร์, & charsRead); if (fscanResult == 1) {[ผลลัพธ์ appendFormat: @ "% s", บัฟเฟอร์]; } else {if (feof (file)) {break; } else if (ferror (file)! = 0) {break; } fscanf (ไฟล์, "\ n", ไม่มี, & charsRead); หยุดพัก; }
Go Rose-Hulman

1
ถ้าฉันอ่านเอกสาร fscanf อย่างถูกต้อง"%4095[^\n]%n%*c"จะใช้และทิ้งอักขระหนึ่งตัวในการอ่านบัฟเฟอร์แต่ละครั้ง ดูเหมือนว่ารูปแบบนี้จะถือว่าบรรทัดนั้นสั้นกว่าความยาวบัฟเฟอร์
Blago

12

ระบบปฏิบัติการ Mac OS X เป็นระบบปฏิบัติการยูนิกซ์ Objective-C คือ C superset ดังนั้นคุณก็สามารถใช้เก่าโรงเรียนfopenและจากfgets <stdio.h>มันรับประกันว่าจะทำงาน

[NSString stringWithUTF8String:buf]จะแปลงสตริง C NSStringถึง นอกจากนี้ยังมีวิธีการสร้างสตริงในการเข้ารหัสและการสร้างโดยไม่ต้องคัดลอก


[การคัดลอกความคิดเห็นที่ไม่ระบุชื่อ] fgetsจะรวม'\n'อักขระดังนั้นคุณอาจต้องการตัดออกก่อนที่จะแปลงสตริง
Kornel

9

คุณสามารถใช้NSInputStreamซึ่งมีการใช้งานขั้นพื้นฐานสำหรับการสตรีมไฟล์ คุณสามารถอ่านไบต์เป็นบัฟเฟอร์ ( read:maxLength:เมธอด) คุณต้องสแกนบัฟเฟอร์เพื่อขึ้นบรรทัดใหม่ด้วยตัวเอง


6

วิธีที่เหมาะสมในการอ่านไฟล์ข้อความใน Cocoa / Objective-C มีการจัดทำเป็นเอกสารไว้ในคู่มือการเขียนโปรแกรม String ของ Apple ส่วนสำหรับการอ่านและเขียนไฟล์ควรเป็นสิ่งที่คุณต้องการ PS: "เส้น" คืออะไร? สองส่วนของสตริงคั่นด้วย "\ n" หรือ "\ r" หรือ "\ r \ n" หรือบางทีคุณอาจตามหลังย่อหน้า? คู่มือที่กล่าวถึงก่อนหน้านี้ยังรวมถึงส่วนในการแยกสตริงออกเป็นบรรทัดหรือย่อหน้า (ส่วนนี้เรียกว่า "ย่อหน้าและตัวแบ่งบรรทัด" และเชื่อมโยงกับในเมนูด้านซ้ายของหน้าเว็บที่ฉันชี้ไปด้านบนโชคไม่ดีที่ไซต์นี้ไม่อนุญาตให้ฉันโพสต์ URL มากกว่าหนึ่งรายการเนื่องจากฉัน ยังไม่ได้เป็นผู้ใช้ที่น่าเชื่อถือ)

การถอดความ Knuth: การปรับให้เหมาะสมก่อนกำหนดเป็นรากของความชั่วร้ายทั้งหมด อย่าคิดเพียงว่า "การอ่านไฟล์ทั้งหมดลงในหน่วยความจำ" นั้นช้า คุณเคยเปรียบเทียบหรือยัง คุณรู้หรือไม่ว่ามันจริงอ่านแฟ้มทั้งหมดลงในหน่วยความจำ? บางทีมันอาจส่งคืนวัตถุพร็อกซีและอ่านต่อไปเรื่อย ๆ ในขณะที่คุณใช้งานสตริง? ( ข้อจำกัดความรับผิดชอบ: ฉันไม่รู้ว่าถ้า NSString ทำสิ่งนี้จริง ๆ มันน่าจะเป็นไปได้ ) ประเด็นคือ: ก่อนอื่นให้ทำเอกสารวิธีการทำสิ่งต่าง ๆ จากนั้นหากการวัดประสิทธิภาพแสดงว่าไม่มีประสิทธิภาพที่คุณต้องการให้ปรับให้เหมาะสม


เนื่องจากคุณพูดถึงจุดสิ้นสุดของบรรทัด CRLF (Windows): ที่จริงแล้วเป็นกรณีที่แบ่งวิธีการทำวัตถุประสงค์ -C หากคุณใช้-stringWithContentsOf*วิธีใดวิธีหนึ่งตามด้วย-componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]จะเห็น\rและ\nแยกต่างหากและเพิ่มบรรทัดว่างหลังจากแต่ละบรรทัด
Siobhán

ที่กล่าวว่าโซลูชัน fgets ล้มเหลวในไฟล์ CR-only แต่ในปัจจุบันมีน้อยมาก (ในทางทฤษฎี) และเครื่องมือทำงานได้ทั้ง LF และ CRLF
Siobhán

6

คำตอบจำนวนมากเหล่านี้เป็นโค้ดยาว ๆ หรืออ่านในไฟล์ทั้งหมด ฉันชอบที่จะใช้วิธีการคสำหรับงานนี้มาก

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 ความยาวของ str เพราะเราต้องการสร้างที่ว่างสำหรับการยกเลิก 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

ชั้นเรียนดำเนินการโดย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 ทั้งหมดบอกว่าการม้วนตัวเองนี้ไม่ได้เป็นเพียงแค่รหัสมากขึ้นเท่านั้น แต่ยังมีข้อผิดพลาดอีกด้วย
Quinn Taylor

3
ฉันเห็นด้วยกับคุณ 100% แต่ฉันพบว่า (บางครั้ง) ดีกว่าที่จะได้รับคำตอบที่ใช้งานได้อย่างรวดเร็วนำไปใช้และจากนั้นเมื่อมีทางเลือกที่ถูกต้องมากกว่าปรากฏขึ้นให้ใช้มัน นี่เป็นสิ่งสำคัญอย่างยิ่งเมื่อสร้างต้นแบบให้โอกาสที่จะได้รับบางสิ่งบางอย่างจากนั้นดำเนินการต่อ
KevinDTimm

3
ฉันเพิ่งรู้ว่ามันเริ่ม "คำตอบนี้" ไม่ใช่ "คำตอบ" Doh! ฉันเห็นด้วยว่าการแฮ็คนั้นใช้งานได้ดีกว่ารหัสที่สง่างาม ฉันไม่ได้ลงคะแนนคุณ แต่ให้เดาโดยไม่รู้ว่า Objective-C อาจมีประโยชน์อะไรบ้างเช่นกัน ดังนั้นแม้ทำให้ความพยายามอยู่เสมอดีกว่าคนที่รู้และไม่ได้ช่วย ... ;-)
ควินน์เทย์เลอร์

สิ่งนี้ไม่ได้ให้คำตอบสำหรับคำถาม หากต้องการวิจารณ์หรือขอคำชี้แจงจากผู้แต่งโปรดแสดงความคิดเห็นใต้โพสต์ของพวกเขา
Robotic Cat

1
@KevinDTimm: ฉันเห็นด้วย ฉันเสียใจด้วยที่ฉันไม่เห็นว่ามันเป็นคำตอบอายุ 5 ปี บางทีนี่อาจเป็นmetaคำถาม ควรคำถามเก่า ๆ จากผู้ใช้ทั่วไปสามารถตั้งค่าสถานะเพื่อตรวจสอบได้หรือไม่
หุ่นยนต์ Cat

0

จากคำตอบของ @Adam Rosenfield สตริงการจัดรูปแบบของfscanfจะมีการเปลี่ยนแปลงดังนี้:

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

มันจะทำงานใน osx, linux, windows line endings


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

ฉันเห็นคำตอบมากมายเหล่านี้ขึ้นอยู่กับการอ่านไฟล์ข้อความทั้งหมดในหน่วยความจำแทนที่จะใช้ทีละอัน นี่คือวิธีแก้ปัญหาของฉันใน 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 กำลังพูดคือว่าสิ่งนี้ไม่ได้ตอบสนองความต้องการของ OP ในการลดการใช้หน่วยความจำ OP ไม่ได้ถามว่าจะใช้วิธีการอย่างไร (ซึ่งโหลดไฟล์ทั้งหมดลงในหน่วยความจำ) เขากำลังขอทางเลือกที่เป็นมิตรกับหน่วยความจำสำหรับไฟล์ข้อความขนาดใหญ่ ค่อนข้างเป็นไปได้ที่จะมีไฟล์ข้อความหลายกิกะไบต์ซึ่งสร้างปัญหาหน่วยความจำอย่างเห็นได้ชัด
Joshua Nozzi
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.