Mục tiêu-C: Đọc một dòng tệp theo dòng


140

Cách thích hợp để xử lý các tệp văn bản lớn trong Objective-C là gì? Giả sử tôi cần đọc riêng từng dòng và muốn coi mỗi dòng là NSString. Cách hiệu quả nhất để làm điều này là gì?

Một giải pháp là sử dụng phương pháp NSString:

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

và sau đó phân tách các dòng bằng dấu phân cách dòng mới, và sau đó lặp lại các phần tử trong mảng. Tuy nhiên, điều này có vẻ khá không hiệu quả. Có cách nào dễ dàng để coi tập tin là một luồng, liệt kê qua từng dòng, thay vì chỉ đọc tất cả cùng một lúc không? Kiểu như java.io.BufferedReader của Java.


1
Hơi muộn một chút, nhưng hãy kiểm tra [NSScanner scanUpToString: @ "\ n" vàoString: & read], giả sử bạn muốn đọc từng dòng vào chuỗi 'đọc'.
hauntsaninja

Xin hãy xem câu hỏi tương tự này . Tôi thiết lập một dự án liên quan đến việc đọc các tệp theo từng dòng .
JJD

Câu trả lời:


63

Đó là một câu hỏi tuyệt vời. Tôi nghĩ rằng @Diederik có một câu trả lời hay, mặc dù thật không may là ca cao không có cơ chế cho chính xác những gì bạn muốn làm.

NSInputStreamcho phép bạn đọc các đoạn N byte (rất giống với java.io.BufferedReader), nhưng bạn phải tự chuyển đổi nó thành một byte NSString, sau đó quét các dòng mới (hoặc bất kỳ dấu phân cách nào khác) và lưu bất kỳ ký tự còn lại nào cho lần đọc tiếp theo hoặc đọc thêm ký tự nếu một dòng mới chưa được đọc. ( NSFileHandlecho phép bạn đọc một NSDatacái mà sau đó bạn có thể chuyển đổi thành một NSString, nhưng về cơ bản đó là cùng một quy trình.)

Apple có một Hướng dẫn lập trình trực tuyến có thể giúp điền thông tin chi tiết và câu hỏi SO này cũng có thể hữu ích nếu bạn sẽ xử lý uint8_t*bộ đệm.

Nếu bạn thường xuyên đọc các chuỗi như thế này (đặc biệt là ở các phần khác nhau trong chương trình của bạn), thì nên gói gọn hành vi này trong một lớp có thể xử lý các chi tiết cho bạn hoặc thậm chí là phân lớp NSInputStream(nó được thiết kế để phân lớp ) và thêm các phương thức cho phép bạn đọc chính xác những gì bạn muốn.

Đối với hồ sơ, tôi nghĩ rằng đây sẽ là một tính năng hay để thêm và tôi sẽ gửi yêu cầu nâng cao cho thứ gì đó có thể thực hiện được. :-)


Chỉnh sửa: Hóa ra yêu cầu này đã tồn tại. Có một Radar có niên đại từ năm 2006 cho điều này (ndar: // 4742914 cho người nội bộ Apple).


10
Xem cách tiếp cận toàn diện của Dave DeLong cho vấn đề này tại đây: stackoverflow.com/questions/3707427#3711079
Quinn Taylor

Cũng có thể sử dụng ánh xạ bộ nhớ và NSData đơn giản. Tôi đã tạo một câu trả lời với mã ví dụ có cùng API với triển khai NSFileHandle của Dave DeLong: stackoverflow.com/a/21267461/267043
Bjørn Olav Ruud

95

Điều này sẽ làm việc cho việc đọc chung Stringtừ Text. Nếu bạn muốn đọc văn bản dài hơn (kích thước lớn của văn bản) , thì hãy sử dụng phương pháp mà người khác ở đây đã đề cập, chẳng hạn như được đệm (dành kích thước của văn bản trong không gian bộ nhớ) .

Giả sử bạn đọc một tệp văn bản.

NSString* filePath = @""//file path...
NSString* fileRoot = [[NSBundle mainBundle] 
               pathForResource:filePath ofType:@"txt"];

Bạn muốn thoát khỏi dòng mới.

// 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:@";"]];

Có bạn có nó.


17
Tôi có một tệp 70 mb, sử dụng mã này để đọc tệp không cho tôi biết nó tăng bộ nhớ một cách tuyến tính. Ai giúp tôi với?
Tải game vào

37
Đây không phải là câu trả lời cho câu hỏi. Câu hỏi là đọc từng dòng tệp để giảm mức sử dụng bộ nhớ
doozMen

34

Cái này cần phải dùng mẹo:

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

Sử dụng như sau:

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

Mã này đọc các ký tự không phải dòng mới từ tệp, tối đa 4095 tại một thời điểm. Nếu bạn có một dòng dài hơn 4095 ký tự, nó sẽ tiếp tục đọc cho đến khi nó chạm vào một dòng mới hoặc cuối tập tin.

Lưu ý : Tôi chưa kiểm tra mã này. Vui lòng kiểm tra nó trước khi sử dụng nó.


1
chỉ cần thay đổi [result appendFormat: "% s", bộ đệm]; đến [kết quả appendFormat: @ "% s", bộ đệm];
Codezy

1
Làm thế nào bạn sửa đổi định dạng để chấp nhận các dòng trống, hay đúng hơn là các dòng bao gồm một ký tự dòng mới?
jakev

Điều này dừng lại sớm cho tôi sau 812 dòng. Dòng thứ 812 là "... thêm 3" và điều đó làm cho đầu đọc xuất ra các chuỗi trống.
sudo

1
Tôi đã thêm một kiểm tra để vượt qua các dòng trống: int fscanResult = fscanf (tệp, "% 4095 [^ \ n]% n% * c", bộ đệm, & charsRead); if (fscanResult == 1) {[result appendFormat: @ "% s", bộ đệm]; } other {if (feof (file)) {break; } if if (ferror (file)! = 0) {break; } fscanf (tệp, "\ n", nil, & charsRead); phá vỡ; }
Đi Rose-Hulman

1
Nếu tôi đang đọc đúng tài liệu fscanf, "%4095[^\n]%n%*c"sẽ âm thầm tiêu thụ và vứt bỏ một ký tự với mỗi bộ đệm được đọc. Dường như định dạng này giả định rằng các dòng sẽ ngắn hơn chiều dài bộ đệm.
Blago

12

Mac OS X là Unix, Objective-C là superset C, vì vậy bạn chỉ có thể sử dụng trường học cũ fopenfgetstừ <stdio.h>. Nó được đảm bảo để làm việc.

[NSString stringWithUTF8String:buf]sẽ chuyển đổi chuỗi C thành NSString. Ngoài ra còn có các phương pháp để tạo chuỗi trong các bảng mã khác và tạo mà không cần sao chép.


[sao chép nhận xét ẩn danh] fgetssẽ bao gồm '\n'ký tự, vì vậy bạn có thể muốn loại bỏ ký tự đó trước khi chuyển đổi chuỗi.
Kornel

9

Bạn có thể sử dụng NSInputStreamcó triển khai cơ bản cho các luồng tệp. Bạn có thể đọc byte vào một bộ đệm ( read:maxLength:phương thức). Bạn phải quét bộ đệm cho dòng mới.


6

Cách thích hợp để đọc các tệp văn bản trong Ca cao / Mục tiêu-C được ghi lại trong hướng dẫn lập trình Chuỗi của Apple. Phần để đọc và viết các tập tin nên là những gì bạn đang theo đuổi. PS: "Dòng" là gì? Hai phần của một chuỗi được phân tách bằng "\ n"? Hay "\ r"? Hoặc "\ r \ n"? Hoặc có thể bạn thực sự sau đoạn văn? Hướng dẫn được đề cập trước đây cũng bao gồm một phần về việc tách một chuỗi thành các dòng hoặc đoạn văn. (Phần này được gọi là "Đoạn văn và ngắt dòng" và được liên kết đến trong menu bên trái của trang tôi đã chỉ ở trên. Thật không may, trang web này không cho phép tôi đăng nhiều hơn một URL như tôi chưa phải là người dùng đáng tin cậy.)

Để diễn giải Knuth: tối ưu hóa sớm là gốc rễ của mọi tội lỗi. Đừng chỉ đơn giản cho rằng "đọc toàn bộ tập tin vào bộ nhớ" là chậm. Bạn đã điểm chuẩn nó? Bạn có biết rằng nó thực sự đọc toàn bộ tập tin vào bộ nhớ? Có lẽ nó chỉ đơn giản trả về một đối tượng proxy và tiếp tục đọc phía sau hậu trường khi bạn sử dụng chuỗi? ( Tuyên bố miễn trừ trách nhiệm: Tôi không biết nếu NSString thực sự làm điều này. Nó có thể hình dung được. ) Vấn đề là: trước tiên hãy đi theo cách làm tài liệu. Sau đó, nếu điểm chuẩn cho thấy điều này không có hiệu suất bạn mong muốn, hãy tối ưu hóa.


Vì bạn đề cập đến kết thúc dòng CRLF (Windows): Đó thực sự là một trường hợp phá vỡ cách làm việc của Objective-C. Nếu bạn sử dụng một trong các -stringWithContentsOf*phương thức theo sau -componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet], nó sẽ thấy \r\nriêng biệt và thêm một dòng trống sau mỗi dòng.
Siobhán

Điều đó nói rằng, giải pháp fgets thất bại trên các tệp chỉ CR. Nhưng những thứ đó (về mặt lý thuyết) ngày nay rất hiếm, và fget không hoạt động cho cả LF và CRLF.
Siobhán

6

Rất nhiều câu trả lời là những đoạn mã dài hoặc chúng đọc trong toàn bộ tệp. Tôi thích sử dụng các phương thức c cho chính nhiệm vụ này.

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

Lưu ý rằng fgetln sẽ không giữ ký tự dòng mới của bạn. Ngoài ra, Chúng tôi +1 độ dài của str vì chúng tôi muốn tạo khoảng trống cho kết thúc NULL.


4

Để đọc một dòng tệp theo từng dòng (cũng cho các tệp cực lớn) có thể được thực hiện bằng các chức năng sau:

DDFileReader * reader = [[DDFileReader alloc] initWithFilePath:pathToMyFile];
NSString * line = nil;
while ((line = [reader readLine])) {
  NSLog(@"read line: %@", line);
}
[reader release];

Hoặc là:

DDFileReader * reader = [[DDFileReader alloc] initWithFilePath:pathToMyFile];
[reader enumerateLinesUsingBlock:^(NSString * line, BOOL * stop) {
  NSLog(@"read line: %@", line);
}];
[reader release];

Lớp DDFileReader cho phép điều này là như sau:

Tệp giao diện (.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

Thực hiện (.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

Lớp học được thực hiện bởi Dave DeLong


4

Đúng như @yheL đã nói, api C rất tiện dụng.

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

Như những người khác đã trả lời cả NSInputStream và NSFileHandle đều là các tùy chọn tốt, nhưng nó cũng có thể được thực hiện theo cách khá nhỏ gọn với NSData và ánh xạ bộ nhớ:

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

Câu trả lời này KHÔNG phải ObjC mà là C.

Vì ObjC dựa trên 'C', tại sao không sử dụng fgets?

Và vâng, tôi chắc chắn ObjC có phương pháp riêng của mình - Tôi vẫn chưa đủ thành thạo để biết nó là gì :)


5
Nếu bạn không biết cách thực hiện trong Objective-C, thì tại sao lại nói đó không phải là câu trả lời? Có rất nhiều lý do để không rơi xuống thẳng C nếu bạn có thể làm điều đó khác. Ví dụ, các hàm C xử lý char * nhưng phải mất nhiều công sức hơn để đọc một cái gì đó khác, chẳng hạn như các bảng mã khác nhau. Ngoài ra, anh ta muốn các đối tượng NSString. Tất cả đã nói, tự lăn cái này không chỉ nhiều mã hơn mà còn dễ bị lỗi.
Quinn Taylor

3
Tôi đồng ý với bạn 100%, nhưng tôi thấy rằng (đôi khi) tốt hơn là nhận được câu trả lời nhanh chóng, thực hiện nó và sau đó khi một phương án đúng hơn xuất hiện, hãy sử dụng nó. Điều này đặc biệt quan trọng khi tạo mẫu, tạo cơ hội để có được một cái gì đó để làm việc và sau đó tiến triển từ đó.
KevinDTimm

3
Tôi chỉ nhận ra rằng nó bắt đầu "Câu trả lời này" chứ không phải "Câu trả lời". Đừng! Tôi đồng ý, chắc chắn sẽ có một hack hoạt động tốt hơn so với mã thanh lịch không có. Tôi đã không đánh giá thấp bạn, nhưng đưa ra một phỏng đoán không biết Objective-C có thể cũng không hữu ích lắm. Mặc dù vậy, nỗ lực luôn tốt hơn người biết và không giúp đỡ ... ;-)
Quinn Taylor

Điều này không cung cấp một câu trả lời cho câu hỏi. Để phê bình hoặc yêu cầu làm rõ từ một tác giả, hãy để lại nhận xét bên dưới bài đăng của họ.
Mèo robot

1
@KevinDTimm: Tôi đồng ý; Tôi chỉ xin lỗi vì tôi đã không nhận ra đó là một câu trả lời 5 tuổi. Có lẽ đây là một metacâu hỏi; câu hỏi rất cũ từ người dùng thường xuyên có thể được gắn cờ để xem xét?
Mèo robot

0

từ câu trả lời của @Adam Rosenfield, chuỗi định dạng fscanfsẽ được thay đổi như dưới đây:

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

Nó sẽ hoạt động trong osx, linux, kết thúc dòng windows.


0

Sử dụng danh mục hoặc phần mở rộng để làm cho cuộc sống của chúng tôi dễ dàng hơn một chút.

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

Tôi thấy phản hồi của @lukaswelte và mã từ Dave DeLong rất hữu ích. Tôi đang tìm kiếm một giải pháp cho vấn đề này nhưng cần thiết để các file lớn phân tích cú pháp bằng cách \r\nkhông chỉ \n.

Mã như được viết chứa một lỗi nếu phân tích cú pháp nhiều hơn một ký tự. Tôi đã thay đổi mã như dưới đây.

tập tin .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

tập tin .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

Tôi đang thêm điều này bởi vì tất cả các câu trả lời khác tôi đã cố gắng giảm bằng cách này hay cách khác. Phương pháp sau đây có thể xử lý các tệp lớn, các dòng dài tùy ý, cũng như các dòng trống. Nó đã được thử nghiệm với nội dung thực tế và sẽ loại bỏ ký tự dòng mới khỏi đầu ra.

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

Tín dụng vào @Adam Rosenfield và @sooop


0

Tôi thấy rất nhiều câu trả lời này dựa vào việc đọc toàn bộ tệp văn bản vào bộ nhớ thay vì lấy từng đoạn một. Đây là giải pháp của tôi trong Swift hiện đại, sử dụng FileHandle để giảm tác động bộ nhớ:

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
    }

}

Lưu ý rằng điều này bảo vệ lợi nhuận vận chuyển ở cuối dòng, vì vậy tùy thuộc vào nhu cầu của bạn, bạn có thể muốn điều chỉnh mã để loại bỏ nó.

Cách sử dụng: chỉ cần mở một tay cầm tệp cho tệp văn bản đích của bạn và gọi readLinevới độ dài tối đa phù hợp - 1024 là tiêu chuẩn cho văn bản thuần túy, nhưng tôi để nó mở trong trường hợp bạn biết nó sẽ ngắn hơn. Lưu ý rằng lệnh sẽ không tràn vào cuối tệp, vì vậy bạn có thể phải kiểm tra thủ công rằng bạn chưa đạt tới nó nếu bạn có ý định phân tích toàn bộ nội dung. Dưới đây là một số mã mẫu cho biết cách mở tệp tại myFileURLvà đọc từng dòng cho đến khi kết thúc.

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

Đây là một giải pháp đơn giản mà tôi sử dụng cho các tệp nhỏ hơn:

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

Anh ấy đã hỏi về cách đọc một dòng tại một thời điểm để nó không đọc toàn bộ nội dung vào bộ nhớ. Giải pháp của bạn tạo ra một chuỗi với toàn bộ nội dung sau đó chia nó thành các dòng.
David

-7

Sử dụng tập lệnh này, nó hoạt động rất tốt:

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
Điều @fisninear đang nói là điều này không giải quyết được mong muốn của OP là giảm mức sử dụng bộ nhớ. OP không hỏi cách sử dụng phương thức (tải toàn bộ tệp vào bộ nhớ), anh ta đang yêu cầu các giải pháp thay thế thân thiện với bộ nhớ cho các tệp văn bản lớn. Hoàn toàn có thể có các tệp văn bản nhiều gigabyte, điều này rõ ràng tạo ra vấn đề về bộ nhớ.
Joshua Nozzi
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.