Objective-C: Bir dosyayı satır satır okuma


140

Objective-C büyük metin dosyaları ile başa çıkmak için uygun yolu nedir? Diyelim ki her satırı ayrı ayrı okumalıyım ve her satırı bir NSString olarak ele almak istiyorum. Bunu yapmanın en etkili yolu nedir?

Bir çözüm NSString yöntemini kullanmaktır:

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

ve sonra satırları bir satırsonu ayracıyla ayırın ve sonra dizideki öğelerin üzerinde yineleyin. Ancak, bu oldukça verimsiz görünüyor. Dosyayı bir kerede okumak yerine, her satırda numaralandırılmış olarak bir akış olarak işlemenin kolay bir yolu yok mu? Java'nın java.io.BufferedReader'ı gibi.


1
Biraz geç, ancak her satırı 'read' dizesine okumak istediğinizi varsayarak [NSScanner scanUpToString: @ "\ n" intoString: & read] 'a bakın.
hauntsaninja

Lütfen bu benzer soruya bir göz atın . Dosyaları satır satır okumakla ilgilenen bir proje kurdum .
JJD

Yanıtlar:


63

Bu harika bir soru. Sanırım @Diederik'in iyi bir cevabı var, ancak Cocoa'nın tam olarak yapmak istediğiniz şey için bir mekanizmaya sahip olmaması talihsiz bir durum.

NSInputStreamN bayt yığınlarını (çok benzer java.io.BufferedReader) okumanıza izin verir , ancak bunu NSStringkendi başınıza bir dönüştürmeniz , ardından yeni satırları (veya başka herhangi bir sınırlayıcıyı) taramanız ve sonraki okuma için kalan karakterleri kaydetmeniz veya daha fazla karakter okumanız gerekir. henüz yeni satır okunmamışsa. ( daha sonra 'ya dönüştürebileceğiniz birini NSFileHandleokumanıza izin verir , ancak aslında aynı işlemdir.)NSDataNSString

Apple, ayrıntıları doldurmanıza yardımcı olabilecek bir Akış Programlama Kılavuzu'na sahiptir ve bu SO sorusu , uint8_t*arabelleklerle uğraşacaksanız da yardımcı olabilir .

Eğer (özellikle programın farklı yerlerinde) bu şekilde sık sık dizeleri okurken edilecek gidiyoruz sizin için ayrıntıları işlemek, hatta sınıflara ki bir sınıfta bu davranışı kapsayan için iyi bir fikir olacağını NSInputStream(o oluyor olacak şekilde tasarlanmıştır alt sınıfta ) ve tam olarak ne istediğinizi okumanıza olanak tanıyan yöntemler ekleme.

Kayıt için, bu eklemek için güzel bir özellik olacağını düşünüyorum ve bunu mümkün kılan bir şey için bir geliştirme isteği dosyalayacağım. :-)


Düzenle: Bu isteğin zaten var olduğu ortaya çıktı. Bunun için 2006'dan kalma bir Radar var (rdar: Apple-dahili insanlar için // 4742914).


10
Dave DeLong'un bu soruna kapsamlı yaklaşımını buradan görebilirsiniz: stackoverflow.com/questions/3707427#3711079
Quinn Taylor

Ayrıca düz NSData ve bellek eşleme kullanmak da mümkündür. Dave DeLong'un NSFileHandle uygulamasıyla aynı API'ya sahip örnek kodla bir cevap oluşturdum: stackoverflow.com/a/21267461/267043
Bjørn Olav Ruud

95

Bu, genel okuma a için çalışacaktır Stringdan Text. Daha uzun metin (büyük metin boyutu) okumak istiyorsanız , arabellek gibi diğer kişilerin belirttiği yöntemi kullanın (metnin boyutunu bellek alanındaki ayırın) .

Diyelim ki bir Metin Dosyası okudunuz.

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

Yeni çizgiden kurtulmak istiyorsun.

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

İşte aldın.


17
i 70 mb dosya var, dosyayı okumak için bu kodu kullanarak hep bana lineer olarak bellek artırır. biri bana yardım edebilir mi?
GameLoading

37
Bu soruya cevap değil. Soru, bellek kullanımını azaltmak için bir dosyayı satır satır
okumaktı

34

Bu hile yapmalı:

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

Aşağıdaki gibi kullanın:

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

Bu kod, bir seferde 4095'e kadar dosyadan satırsonu olmayan karakterleri okur. 4095 karakterden daha uzun bir satırınız varsa, satırsonuna veya dosya sonuna kadar okunmaya devam eder.

Not : Bu kodu test etmedim. Lütfen kullanmadan önce test edin.


1
değiştir [sonuç appendFormat: "% s", buffer]; için [sonuç appendFormat: @ "% s", arabellek];
Codezy

1
boş satırları veya tek bir satırsonu karakterinden oluşan satırları kabul etmek için biçimi nasıl değiştirirsiniz?
jakev

812 satırdan sonra bu benim için erken duruyor. 812. satır "... 3 daha" dir ve bu da okuyucuya boş dizeler çıkarır.
sudo

1
Boş satırları geçmiş bir onay ekledim: int fscanResult = fscanf (dosya, "% 4095 [^ \ n]% n% * c", buffer, & charsRead); if (fscanResult == 1) {[sonuç appendFormat: @ "% s", arabellek]; } else {if (feof (dosya)) {break; } else if (ferror (dosya)! = 0) {break; } fscanf (dosya, "\ n", nil, & charsRead); break; }
Go Rose-Hulman

1
Eğer fscanf belgelerini doğru okuyorsam, "%4095[^\n]%n%*c"sessizce her karakterin okunmasıyla bir karakter tüketir ve atar. Bu biçim, satırların arabellek uzunluğundan daha kısa olacağını varsayar.
Blago

12

Mac OS X Unix, Objective-C C süper setidir, bu yüzden sadece eski okulu fopenve fgetsitibaren kullanabilirsiniz <stdio.h>. Çalışması garanti.

[NSString stringWithUTF8String:buf]C dize dönüştürür NSString. Diğer kodlamalarda dize oluşturma ve kopyalamadan oluşturma yöntemleri de vardır.


[anonim yorumu kopyalamak] karakteri fgetsiçerecektir '\n', bu nedenle dizeyi dönüştürmeden önce bunu kaldırmak isteyebilirsiniz.
Kornel

9

NSInputStreamDosya akışları için temel bir uygulamaya sahip olanı kullanabilirsiniz . Bir arabelleğe ( read:maxLength:yöntem) bayt okuyabilirsiniz . Yeni satırlar için arabelleği kendiniz taramanız gerekir.


6

Cocoa / Objective-C'deki metin dosyalarını okumanın uygun yolu Apple'ın String programlama kılavuzunda belgelenmiştir. Dosya okuma ve yazma bölümü tam aradığınız şey olmalıdır. Not: "Çizgi" nedir? Bir dizenin "\ n" ile ayrılmış iki bölümü mü? Veya "\ r"? Veya "\ r \ n"? Ya da belki aslında paragrafların peşindesiniz? Önceden bahsedilen kılavuz ayrıca bir dizgiyi satırlara veya paragraflara bölme hakkında bir bölüm içerir. (Bu bölüme "Paragraflar ve Satır Kesmeleri" denir ve yukarıda işaret ettiğim sayfanın soldaki menüsünde bağlantılıdır. Ne yazık ki bu site birden fazla URL göndermeme izin vermiyor henüz güvenilir bir kullanıcı değil.)

Knuth'u yorumlamak için: erken optimizasyon tüm kötülüklerin köküdür. Sadece "tüm dosyayı belleğe okumanın" yavaş olduğunu varsaymayın. Kıyasladınız mı? Tüm dosyayı gerçekten belleğe okuduğunu biliyor musunuz ? Belki sadece bir proxy nesnesi döndürür ve dizeyi tüketirken sahne arkasında okumaya devam ediyor? ( Feragatname: NSString'in bunu gerçekten yapıp yapmadığı hakkında hiçbir fikrim yok. Akla yatkın bir şekilde olabilir. ) Mesele şudur: ilk önce bir şeyler yapmak için belgelenmiş yolla gidin. Daha sonra, kıyaslamalar bunun istediğiniz performansa sahip olmadığını gösteriyorsa, optimize edin.


CRLF (Windows) satır sonlarından bahsettiğiniz için: Aslında bu, Objective-C yöntemini bozan bir durumdur. -stringWithContentsOf*Ardından gelen yöntemlerden birini kullanırsanız -componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet], ve öğelerini ayrı ayrı görür \rve \nher satırdan sonra boş bir satır ekler.
Siobhán

Bununla birlikte, fgets çözümü sadece CR dosyalarında başarısız oluyor. Ancak bunlar (teorik olarak) günümüzde nadirdir ve fgets hem LF hem de CRLF için çalışır.
Siobhán

6

Bu yanıtların çoğu uzun kod parçalarıdır veya tüm dosyada okurlar. Bu görev için c yöntemlerini kullanmayı seviyorum.

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'ın yeni satır karakterinizi tutmayacağını unutmayın. Ayrıca, str uzunluğunu + 1'liyoruz çünkü NULL sonlandırması için yer açmak istiyoruz.


4

Bir dosyayı satır satır okumak için (aşırı büyük dosyalar için de) aşağıdaki işlevlerle yapılabilir:

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

Veya:

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

Bunu sağlayan DDFileReader sınıfı şöyledir:

Arayüz Dosyası (.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

Uygulama (.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

Ders Dave DeLong tarafından yapıldı


4

@PorneL'nin dediği gibi, C api çok kullanışlıdır.

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

Diğerleri hem NSInputStream hem de NSFileHandle'ın yanıtladığı gibi, iyi seçeneklerdir, ancak NSData ve bellek eşleme ile oldukça kompakt bir şekilde de yapılabilir:

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

Bu cevap ObjC DEĞİL, C.

ObjC 'C' tabanlı olduğu için neden fgets kullanmıyorsunuz?

Ve evet, eminim ObjC'nin kendi yöntemi var - henüz ne olduğunu bilecek kadar yetkin değilim :)


5
Objective-C'de nasıl yapacağınızı bilmiyorsanız, neden cevap olmadığını söylüyorsunuz? Aksi takdirde, düz C'ye düşmemenin birçok nedeni vardır. Örneğin, C işlevleri char * ile çalışır ancak farklı kodlamalar gibi başka bir şeyi okumak çok daha fazla iş gerektirir. Ayrıca, NSString nesneleri istiyor. Hepsi, bunu kendiniz yuvarlamak sadece daha fazla kod değil, aynı zamanda hataya açıktır.
Quinn Taylor

3
Sana% 100 katılıyorum, ancak (bazen) hızlı çalışan bir cevap almanın, onu uygulamanın ve daha doğru bir alternatif ortaya çıktığında bunu kullanmanın daha iyi olduğunu gördüm. Bu özellikle prototip oluştururken, işe yarayacak bir şey elde etme ve daha sonra oradan ilerleme fırsatı verirken önemlidir.
KevinDTimm

3
Az önce başladığımın farkına vardım ki "Bu cevap" değil "Cevap". Doh! Katılıyorum, işe yaramayan zarif bir koddan daha iyi çalışan bir kesmek daha iyidir. Seni küçümsemedim, ama Objective-C'nin muhtemelen ne olabileceğini bilerek bir tahminde bulunmak da çok yararlı değil. Buna rağmen, çaba sarf etmek her zaman bilen ve yardım etmeyen birinden daha iyidir ... ;-)
Quinn Taylor

Bu soruya bir cevap sağlamaz. Bir yazardan eleştiri veya açıklama istemek için gönderilerinin altına bir yorum bırakın.
Robotik Kedi

1
@KevinDTimm: Katılıyorum; Sadece 5 yaşında bir cevap olduğunu fark etmediğim için üzgünüm. Belki bu bir metasorudur; düzenli kullanıcılardan çok eski sorular incelenmek üzere işaretlenebilmeli mi?
Robotic Cat

0

@Adam Rosenfield'ın cevabından, biçimlendirme dizesi fscanfaşağıdaki gibi değiştirilecekti:

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

osx, linux, windows line end'lerinde çalışacaktır.


0

Hayatımızı biraz daha kolaylaştırmak için kategori veya uzantı kullanmak.

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 ve Dave DeLong kodu çok yararlı buldum . Bu soruna bir çözüm arıyordum, ancak büyük dosyaları \r\nsadece\n .

Birden fazla karakterle ayrıştırılırsa, yazılan kod bir hata içeriyor. Kodu aşağıdaki gibi değiştirdim.

.h dosyası:

#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 dosyası:

#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

Bunu ekliyorum çünkü denediğim diğer tüm cevaplar şu ya da bu şekilde kısa düştü. Aşağıdaki yöntem, büyük dosyaları, keyfi uzun satırları ve boş satırları işleyebilir. Gerçek içerikle test edilmiştir ve çıktıdan satırsonu karakteri çıkarır.

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

Kredi @Adam Rosenfield ve @sooop'a gidiyor


0

Bu cevapların birçoğunu bir kerede bir yığın almak yerine tüm metin dosyasını belleğe okumaya güveniyorum. İşte bellek etkisini düşük tutmak için FileHandle kullanarak güzel modern Swift'teki çözümüm:

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
    }

}

Bunun satır sonunda satır başını koruduğunu unutmayın, bu nedenle ihtiyaçlarınıza bağlı olarak kodu kaldırmak için kodu ayarlamak isteyebilirsiniz.

Kullanım: Hedef metin dosyanıza bir dosya tanıtıcısı açın ve readLineuygun bir maksimum uzunlukla arayın - 1024 düz metin için standarttır, ancak daha kısa olacağını bilmeniz durumunda açık bıraktım. Komutun dosyanın sonuna taşmayacağına dikkat edin, bu nedenle tüm öğeyi ayrıştırmak istiyorsanız elinize ulaşmadığınızı manuel olarak kontrol etmeniz gerekebilir. Aşağıda bir dosyanın nasıl açılacağını myFileURLve sonuna kadar satır satır nasıl okunacağını gösteren bazı örnek kodlar verilmiştir .

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

İşte küçük dosyalar için kullandığım güzel ve basit bir çözüm:

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

Bir kerede bir satırın nasıl okunacağını soruyordu, böylece tüm içeriği belleğe okumuyor. Çözümünüz, tüm içeriğe sahip bir dize oluşturur ve ardından bunları satırlara böler.
David

-7

Bu komut dosyasını kullanın, harika çalışıyor:

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'ın söylediği, bunun OP'nin bellek kullanımını azaltma isteğini ele almamasıdır. OP yöntemi (tüm dosyayı belleğe yükleyen) nasıl kullanacağını sormuyordu, büyük metin dosyaları için bellek dostu alternatifler istiyordu. Bellek sorunu yaratan çok gigabaytlık metin dosyalarına sahip olmak oldukça mümkündür.
Joshua Nozzi
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.