سؤال

ما هي الطريقة المناسبة للتعامل مع الملفات النصية الكبيرة في Objective-C؟لنفترض أنني بحاجة إلى قراءة كل سطر على حدة وأريد التعامل مع كل سطر باعتباره سلسلة NSString.ما هي الطريقة الأكثر فعالية للقيام بذلك؟

أحد الحلول هو استخدام طريقة NSString:

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

ثم قم بتقسيم الأسطر بفاصل سطر جديد، ثم قم بالتكرار فوق العناصر الموجودة في المصفوفة.ومع ذلك، يبدو هذا غير فعال إلى حد ما.ألا توجد طريقة سهلة للتعامل مع الملف كتدفق، والتعداد فوق كل سطر، بدلاً من مجرد قراءته بالكامل مرة واحدة؟يشبه إلى حد ما Java.io.BufferedReader في Java.

هل كانت مفيدة؟

المحلول

هذا سؤال عظيم.أظن @ديديريك لديه إجابة جيدة، على الرغم من أنه من المؤسف أن الكاكاو ليس لديه آلية لما تريد القيام به بالضبط.

NSInputStream يسمح لك بقراءة أجزاء من N بايت (تشبه إلى حد كبير java.io.BufferedReader)، ولكن عليك تحويله إلى NSString بنفسك، ثم ابحث عن الأسطر الجديدة (أو أي محدد آخر) واحفظ أي أحرف متبقية للقراءة التالية، أو اقرأ المزيد من الأحرف إذا لم تتم قراءة السطر الجديد بعد.(NSFileHandle يتيح لك قراءة NSData والتي يمكنك بعد ذلك تحويلها إلى NSString, ، ولكنها في الأساس نفس العملية.)

أبل لديها دليل برمجة الدفق التي يمكن أن تساعد في ملء التفاصيل، و هذا السؤال SO قد يساعدك أيضًا إذا كنت ستتعامل معه uint8_t* مخازن.

إذا كنت ستقرأ سلاسل مثل هذه بشكل متكرر (خاصة في أجزاء مختلفة من برنامجك)، فسيكون من الجيد تغليف هذا السلوك في فصل دراسي يمكنه التعامل مع التفاصيل نيابةً عنك، أو حتى تصنيف فرعي NSInputStream (إنه مصممة لتكون فئة فرعية) وإضافة طرق تتيح لك قراءة ما تريده بالضبط.

للعلم، أعتقد أن هذه ستكون ميزة جيدة لإضافتها، وسأقوم بتقديم طلب تحسين لشيء يجعل ذلك ممكنًا.:-)


يحرر: تبين أن هذا الطلب موجود بالفعل.يوجد رادار يعود تاريخه إلى عام 2006 لهذا الغرض (rdar://4742914 للأشخاص العاملين في Apple).

نصائح أخرى

وهذا العمل لقراءة عامة ل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 حرف، فإنه يحتفظ القراءة حتى يضرب سطر جديد أو نهاية الملف.

ملاحظة : أنا لم تختبر هذا الرمز. يرجى اختباره قبل استخدامه.

وماك OS X هو يونكس، الهدف C هو C شاملة، لذلك يمكنك فقط استخدام fopen المدرسة القديمة وfgets من <stdio.h>. انها مضمونة للعمل.

و[NSString stringWithUTF8String:buf] سيتم تحويل سلسلة C إلى NSString. هناك أيضا وسائل لخلق سلاسل في ترميزات الأخرى وخلق دون النسخ.

ويمكنك استخدام NSInputStream التي لديها تنفيذ الأساسي للتدفقات الملف. يمكنك أن تقرأ بايت في المخزن مؤقت (طريقة read:maxLength:). لديك لمسح المخزن المؤقت للأسطر جديدة نفسك.

ويتم توثيق الطريقة المناسبة لقراءة الملفات النصية في الكاكاو / الهدف-C في دليل البرمجة أبل سلسلة. قسم ل القراءة والكتابة ملفات ينبغي أن يكون مجرد ما كنت بعد. PS: ما هو "الخط"؟ قسمين من سلسلة مفصولة "\ ن"؟ أو "\ ص"؟ أو "\ ص \ ن"؟ أو ربما كنت فعلا بعد الفقرات؟ يتضمن الدليل المذكورة سابقا أيضا قسما عن تقسيم السلسلة إلى خطوط أو الفقرات. (ويسمى هذا القسم "الفقرات وفواصل لاين"، ويرتبط في القائمة جنبا إلى الجانب الأيسر من الصفحة أشرت إليها أعلاه، وللأسف هذا الموقع لا يسمح لي للنشر URL أكثر من واحد وأنا لا أحد المستخدمين جدير بالثقة حتى الآن).

لإعادة صياغة كانوث: من السابق لأوانه الأمثل هو أصل كل الشرور. لا تفترض ببساطة أن "قراءة الملف بأكمله في الذاكرة" بطيئة. هل مميزا؟ هل تعرف أنه في الواقع يقرأ الملف بأكمله في الذاكرة؟ ربما ببساطة يعود كائن وكيل وتحافظ على القراءة وراء الكواليس كما كنت تستهلك سلسلة؟ (<م> تنويه:.. ليس لدي أي فكرة عما اذا NSString فعلا يفعل ذلك فإنه يمكن تصور ) وهذه النقطة هي: أولا الذهاب مع الطريقة الموثقة للقيام بهذه الأمور. ثم، إذا تبين المعايير أن هذا لا يكون الأداء التي تريدها، وتحسين.

وهناك الكثير من هذه الإجابات وقطع طويلة من التعليمات البرمجية أو قرأوا في الملف بأكمله. أود أن استخدام الأساليب ج لهذه المهمة بالذات.

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 طول شارع لأننا نريد لافساح المجال لإنهاء فارغة.

يمكن قراءة ملف سطرًا تلو الآخر (أيضًا للملفات الكبيرة جدًا) عن طريق الوظائف التالية:

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

التنفيذ (.م)

#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 هو مفيد للغاية.

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' على أساس، لماذا لا تستخدم fgets؟

ونعم، وأنا متأكد ObjC لها انها طريقة خاصة - أنا فقط لا يتقن حتى الآن ما يكفي لمعرفة ما هو عليه:)

ومن الإجابةAdam روزنفيلد، فإن سلسلة تنسيق fscanf سيتم تغيير مثل أدناه:

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

وأنها ستعمل في OSX، لينكس، ويندوز نهايات الخط.

<ع> استخدام فئة أو التمديد لجعل حياتنا أسهل قليلا.

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

وملف. م:

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

والفضل يعود إلىAdam روزنفيلد و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