objective c - ¿Cómo leer datos de NSFileHandle línea por línea?
objective-c cocoa (10)
Tengo un archivo de texto que tiene datos como dados
p.ej
PUFGUjVRallYZDNaazFtVjVObU1zWm5ZcUJUYU5ORk4zbGthNHNDVUdSMlFVQmpSVEoxUUNSallYaFhkanBITXBGR1NTQnpZRTltZE1OalVzSkdXQ0Z6WXR0V2RpTmpTdXgwTWs5V1lZSkZiWjFXT29OV2JSVlhaSTUwYUpwR040UUZXTzVHVXFoWFVRcFdWNHdVTUJ0Q1VHSmxXVlJVTlJCMVE1VTFWV
PUFGUjVRallYZDNaazFtVjVObU1zWm5ZcUJUYU5ORk4zbGthNHNDVUdSMlFVQmpSVEoxUUNSallYaFhkanBITXBGR1NTQnpZRTltZE1OalVzSkdXQ0Z6WXR0V2RpTmpTdXgwTWs5V1lZSkZiWjFXT29OV2JSVlhaSTUwYUpwR040UUZXTzVHVXFoWFVRcFdWNHdVTUJ0Q1VHSmxXVlJVTlJCMVE1VTFWV
Ahora quiero leer datos línea por línea. Eso significa que primero quiero leer
PUFGUjVRallYZDNaazFtVjVObU1zWm5ZcUJUYU5ORk4zbGthNHNDVUdSMlFVQmpSVEoxUUNSallYaFhkanBITXBGR1NTQnpZRTltZE1OalVzSkdXQ0Z6WXR0V2RpTmpTdXgwTWs5V1lZSkZiWjFXT29OV2JSVlhaSTUwYUpwR040UUZXTzVHVXFoWFVRcFdWNHdVTUJ0Q1VHSmxXVlJVTlJCMVE1VTFWV
y luego el próximo restante. ¿¿Alguien tiene alguna idea??
Aquí hay un método que utilicé para leer una línea individual desde un NSInputStream. Tenga en cuenta que está optimizado para legibilidad y no para velocidad. ;-)
- (NSString*) readLine: (NSInputStream*) inputStream {
NSMutableData* data = [NSMutableData data];
uint8_t oneByte;
do {
int actuallyRead = [inputStream read: &oneByte maxLength: 1];
if (actuallyRead == 1) {
[data appendBytes: &oneByte length: 1];
}
} while (oneByte != ''/n'');
return [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
Comencé un proyecto GitHub basado en el código fuente de . Le invitamos a mejorar el código. Por ahora puedo leer archivos hacia adelante y hacia atrás.
Descubrí que GitX utiliza un lector de línea también.
Consulte el repositorio de Brotherbard en GitHub o el sitio web de Michael Stapelberg .
@Joe Yang
¡Bonito! Voy a echar un vistazo más de cerca los próximos días.
Me alegraría si quiere bifurcar mi repositorio en GitHub y enviarme una solicitud de extracción .
La respuesta a esta pregunta para archivos de texto GRANDES no requiere una función personalizada. Objective-C es un superconjunto de c, y por lo tanto tiene los métodos c para hacer justamente esto.
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);
}
Tenga en cuenta que fgetln no mantendrá su carácter de nueva línea. Además, tenemos +1 la longitud de str porque queremos dejar espacio para la terminación NULL.
Me encuentro con una situación similar con otras circunstancias, y esta es mi solución en Swift 3. Suponiendo que el archivo de texto sea utf8.
extension FileHandle {
func enumerateLines(_ block: @escaping (String, UnsafeMutablePointer<Bool>) -> Void) {
// find the end of file
var offset = self.offsetInFile
let eof = self.seekToEndOfFile()
self.seek(toFileOffset: offset)
let blockSize = 1024
var buffer = Data()
// process to the end of file
while offset + UInt64(buffer.count) < eof {
var found = false
// make sure buffer contains at least one CR, LF or null
while !found && offset + UInt64(buffer.count) < eof {
let block = self.readData(ofLength: blockSize)
buffer.append(block)
for byte in block {
if [0x0d, 0x0a, 0x00].contains(byte) {
found = true ; break
}
}
}
// retrieve lines within the buffer
var index = 0
var head = 0 // head of line
var done = false
buffer.enumerateBytes({ (pointer, count, stop) in
while index < count {
// find a line terminator
if [0x0d, 0x0a, 0x00].contains(pointer[index]) {
let lineData = Data(pointer[head ..< index])
if let line = String(bytes: lineData, encoding: .utf8) {
block(line, &stop) // stop requested
if pointer[index] == 0x0d && index+1 < count && pointer[index+1] == 0x0a {
index += 2 ; head = index
}
else { index += 1 ; head = index }
if stop { done = true ; return } // end of enumerateLines
}
else { return } // end of enumerateLines
}
else { index += 1 }
}
})
offset += UInt64(head)
buffer.replaceSubrange(0 ..< head, with: Data())
if done { // stop requested
self.seek(toFileOffset: offset)
return
}
}
}
Aquí está el uso:
let fileURL = Bundle.main.url(forResource: "huge_file", withExtension: "txt")!
let fileHandle = try! FileHandle(forReadingFrom: fileURL)
fileHandle.enumerateLines { (line, stop) in
if someCondition { stop.pointee = true }
print(line)
}
/* let remaining = fileHandle.readDataToEndOfFile() */
https://gist.github.com/codelynx/c1de603a85e7503fe9597d027e93f4de
Modifiqué el FileReader a una categoría NSFileHandle, espero que pueda ayudar a otros
@interface NSFileHandle (Readline)
- (NSString*)readLine;
- (NSString*)readLineBackwards;
@end
#import "NSFileHandle+Readline.h"
#import "NSDataExtensions.h"
@implementation NSFileHandle (Readline)
- (NSString*)readLine {
NSString * _lineDelimiter = @"/n";
NSData* newLineData = [_lineDelimiter dataUsingEncoding:NSUTF8StringEncoding];
NSMutableData* currentData = [[NSMutableData alloc] init];
BOOL shouldReadMore = YES;
NSUInteger _chunkSize = 10;
while (shouldReadMore) {
NSData* chunk = [self readDataOfLength:_chunkSize]; // always length = 10
if ([chunk length] == 0) {
break;
}
// Find the location and length of the next line delimiter.
NSRange newLineRange = [chunk rangeOfData:newLineData];
if (newLineRange.location != NSNotFound) {
// Include the length so we can include the delimiter in the string.
NSRange subDataRange = NSMakeRange(0, newLineRange.location + [newLineData length]);
unsigned long long newOffset = [self offsetInFile] - [chunk length] + newLineRange.location + [newLineData length];
[self seekToFileOffset:newOffset];
chunk = [chunk subdataWithRange:subDataRange];
shouldReadMore = NO;
}
[currentData appendData:chunk];
}
NSString* line = [currentData stringValueWithEncoding:NSASCIIStringEncoding];
return line;
}
- (NSString*)readLineBackwards {
NSString * _lineDelimiter = @"/n";
NSData* newLineData = [_lineDelimiter dataUsingEncoding:NSUTF8StringEncoding];
NSUInteger _chunkSize = 10;
NSMutableData* currentData = [[NSMutableData alloc] init];
BOOL shouldReadMore = YES;
while (shouldReadMore) {
unsigned long long offset;
NSUInteger currentChunkSize = _chunkSize;
if ([self offsetInFile] <= _chunkSize) {
offset = 0;
currentChunkSize = [self offsetInFile];
shouldReadMore = NO;
} else {
offset = [self offsetInFile] - _chunkSize;
}
NSLog(@"seek to offset %qu, offset in file is %qu", offset, [self offsetInFile]);
[self seekToFileOffset:offset];
NSData* chunk = [self readDataOfLength:currentChunkSize];
NSRange newLineRange = [chunk rangeOfDataBackwardsSearch:newLineData];
if (newLineRange.location == NSNotFound) {
[self seekToFileOffset:offset];
}
if (newLineRange.location != NSNotFound) {
NSUInteger subDataLoc = newLineRange.location;
NSUInteger subDataLen = currentChunkSize - subDataLoc;
chunk = [chunk subdataWithRange:NSMakeRange(subDataLoc, subDataLen)];
NSLog(@"got chunk data %@", [chunk stringValueWithEncoding:NSASCIIStringEncoding]);
shouldReadMore = NO;
[self seekToFileOffset:offset + newLineRange.location];
}
[currentData prepend:chunk];
}
NSString* line = [[NSString alloc] initWithData:currentData encoding:NSASCIIStringEncoding];
return [line autorelease];
}
@end
//
// NSDataExtensions.m
// LineReader
//
// Created by Tobias Preuss on 08.10.10.
// Copyright 2010 Tobias Preuss. All rights reserved.
//
#import "NSDataExtensions.h"
// -----------------------------------------------------------------------------
// NSData additions.
// -----------------------------------------------------------------------------
/**
Extension of the NSData class.
Data can be found forwards or backwards. Further the extension supplies a function
to convert the contents to string for debugging purposes.
@param Additions Category labeled Additions.
@returns An initialized NSData object or nil if the object could not be created.
*/
@implementation NSData (Additions)
/**
Returns a range of data.
@param dataToFind Data object specifying the delimiter and encoding.
@returns A range.
*/
- (NSRange)rangeOfData:(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++) {
// The current character matches.
if (((char*)bytes)[index] == ((char*)searchBytes)[searchIndex]) {
// Store found location if not done earlier.
if (foundRange.location == NSNotFound) {
foundRange.location = index;
}
// Increment search character index to check for match.
searchIndex++;
// All search character match.
// Break search routine and return found position.
if (searchIndex >= searchLength) {
return foundRange;
}
}
// Match does not continue.
// Return to the first search character.
// Discard former found location.
else {
searchIndex = 0;
foundRange.location = NSNotFound;
}
}
return foundRange;
}
- (NSRange)rangeOfDataBackwardsSearch:(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};
if (length < searchLength) {
return foundRange;
}
for (NSUInteger index = length - searchLength; index >= 0;) {
// NSLog(@"%c == %c", ((char*)bytes)[index], ((char*)searchBytes)[searchIndex]); /* DEBUG LOG */
if (((char*)bytes)[index] == ((char*)searchBytes)[searchIndex]) {
// The current character matches.
if (foundRange.location == NSNotFound) {
foundRange.location = index;
}
index++;
searchIndex++;
if (searchIndex >= searchLength) {
return foundRange;
}
}
else {
// Decrement to search backwards.
if (foundRange.location == NSNotFound) {
// Skip if first byte has been reached.
if (index == 0) {
foundRange.location = NSNotFound;
return foundRange;
}
index--;
}
// Jump over the former found location
// to avoid endless loop.
else {
index = index - 2;
}
searchIndex = 0;
foundRange.location = NSNotFound;
}
}
return foundRange;
}
- (NSString*)stringValueWithEncoding:(NSStringEncoding)encoding {
return [[NSString alloc] initWithData:self encoding:encoding];
}
@end
// -----------------------------------------------------------------------------
// NSMutableData additions.
// -----------------------------------------------------------------------------
/**
Extension of the NSMutableData class.
Data can be prepended in addition to the append function of the framework.
@param Additions Category labeled Additions.
@returns An initialized NSMutableData object or nil if the object could not be created.
*/
@implementation NSMutableData (Additions)
/**
Inserts the data before the data of the object.
@param data Data to be prepended.
*/
- (void)prepend:(NSData*)data {
NSMutableData* concat = [NSMutableData dataWithData:data];
[concat appendData:self];
[self setData:concat];
}
@end
Reescribí esto para que sea compatible con ARC:
//
// DDFileReader.m
// PBX2OPML
//
// Created by michael isbell on 11/6/11.
// Copyright (c) 2011 BlueSwitch. All rights reserved.
//
//DDFileReader.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) {
return nil;
}
lineDelimiter = @"/n";
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];
currentOffset = 0ULL;
}
- (NSString *) readLine {
if (currentOffset >= totalFileLength) { return nil; }
NSData * newLineData = [lineDelimiter dataUsingEncoding:NSUTF8StringEncoding];
[fileHandle seekToFileOffset:currentOffset];
NSMutableData * currentData = [[NSMutableData alloc] init];
BOOL shouldReadMore = YES;
@autoreleasepool {
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];
}
}
NSString * line = [[NSString alloc] initWithData:currentData encoding:NSUTF8StringEncoding];
return line;
}
- (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
Si su archivo es pequeño, entonces el método de @ mipadi probablemente estará bien. Sin embargo, si su archivo es grande (> 1 MB, ¿tal vez?), Entonces puede considerar leer el archivo línea por línea. Una vez escribí una clase para hacer eso, que pegaré aquí:
//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
//DDFileReader.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
Entonces, para usar esto, harías:
DDFileReader * reader = [[DDFileReader alloc] initWithFilePath:pathToMyFile];
NSString * line = nil;
while ((line = [reader readLine])) {
NSLog(@"read line: %@", line);
}
[reader release];
O (para 10.6+ e iOS 4+):
DDFileReader * reader = [[DDFileReader alloc] initWithFilePath:pathToMyFile];
[reader enumerateLinesUsingBlock:^(NSString * line, BOOL * stop) {
NSLog(@"read line: %@", line);
}];
[reader release];
Feliz Navidad. :)
También puede consultar la biblioteca CGIStream que creé para mi proyecto de servidor HTTP en https://github.com/xcvista/ohttpd2/tree/master/CGIStream . En lugar de descriptores de archivos, este código opera en NSInputStream. Es esencialmente un clon de Objective-C de System.IO.StreamReader
y System.IO.StreamWriter
del marco de System.IO.StreamWriter
de Microsoft.
Funcionará no solo con archivos sino también con conectores de red. Lo uso para manejar el protocolo HTTP, que es el nombre del prefijo CGI.
NSString *fh = [NSString stringWithContentsOfFile:filePath encoding:fileEncoding error:NULL];
for (NSString *line in [fh componentsSeparatedByString:@"/n"]) {
// Do something with the line
}
No hay API en Cocoa, o construcciones de lenguaje integradas, para leer un archivo línea por línea.