Использование памяти CHCSVWriter для записи файлов CSV большего размера

#ios #objective-c #csv #memory-management #nsoutputstream

#iOS #objective-c #csv #управление памятью #nsoutputstream

Вопрос:

Я хочу свести к минимуму использование памяти при записи данных в файл CSV.

Для больших таблиц он использует больше памяти, даже если это временно.

Может кто-нибудь подсказать, как уменьшить использование памяти?

Возможно, я мог бы разделить действие для таблиц большего размера, записать больше файлов, а затем объединить их, но я еще не пробовал этого, возможно, я упускаю что-то очевидное.

Вот используемый в настоящее время код:

  @autoreleasepool {
    NSOutputStream *csvStream = [[NSOutputStream alloc] initToMemory];
    [csvStream open];

    CHCSVWriter *writer = [[CHCSVWriter alloc] initWithOutputStream:csvStream encoding:NSUTF8StringEncoding delimiter:';'];
    NSArray *keySortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"self" ascending:YES]];
    if (writeHeader==YES) {
        //> write header
        NSMutableDictionary *firstRow = [[self sharedUploadManager].modifiedRows firstObject];
        if (firstRow==nil) {
            result = NO;
            return resu<
        }

        NSArray *orderedKeys = [[firstRow allKeys] sortedArrayUsingDescriptors:keySortDescriptors];
        for (NSString *columnName in  orderedKeys) {
            [writer writeField:columnName];
        }
    }
    [writer finishLine];

    @autoreleasepool {
        //> write the rows
        for (NSMutableDictionary *row in [self sharedUploadManager].modifiedRows) {

            NSArray *orderedKeys = [[row allKeys] sortedArrayUsingDescriptors:keySortDescriptors];

            for (NSString *key in orderedKeys ) {

                NSString *field = [row objectForKey:key];
                if ([field isKindOfClass:[NSNull class]]) {
                    [writer writeField:nil];
                } else {
                    [writer writeField:field];
                }
            }

            //> finish the line
            [writer finishLine];
        }
    }

    [writer closeStream];

    NSData *buffer = [csvStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey];
    NSString *output = [[NSString alloc] initWithData:buffer encoding:NSUTF8StringEncoding];

    if (![[NSFileManager defaultManager] fileExistsAtPath:csvPath]) {
        [[NSFileManager defaultManager] createFileAtPath:csvPath contents:nil attributes:nil];
    }

    BOOL res = [[output dataUsingEncoding:NSUTF8StringEncoding] writeToFile:csvPath atomically:NO];

    if (!res) {
        NSLog(@"Error Creating CSV File path = %@", csvPath);
    } else{
        NSLog(@"Data saved! File path = %@", csvPath);

    }
}
  

Я также пробовал эту логику раньше — немного чище, но с тем же результатом:

 NSOutputStream *csvStream = [[NSOutputStream alloc] initToFileAtPath:csvPath append:YES];
[csvStream open];

CHCSVWriter *writer = [[CHCSVWriter alloc] initWithOutputStream:csvStream encoding:NSUTF8StringEncoding delimiter:';'];

if (writeHeader==YES) {
    //> write header
    NSMutableDictionary *firstRow = [rows firstObject];
    if (firstRow==nil) {
        result = NO;
        return resu<
    }

    NSArray *orderedKeys = [[firstRow allKeys] sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"self" ascending:YES]]];

    for (NSString *columnName in  orderedKeys) {
        [writer writeField:columnName];
    }
    [writer finishLine];
}


//> write the rows
for (NSMutableDictionary *row in rows) {

    NSArray *orderedKeys = [[row allKeys] sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"self" ascending:YES]]];

    for (NSString *key in orderedKeys ) {

        NSString *field = [row objectForKey:key];
        if ([field isKindOfClass:[NSNull class]]) {
            [writer writeField:nil];
        }
        else {
            [writer writeField:field];
        }

    }
    //> finish the line
    [writer finishLine];
}
[writer closeStream];
  

Ответ №1:

Если вы не хотите использовать много памяти при создании большого файла CSV, не создавайте поток вывода на основе памяти. Создайте выходной поток в реальный файл. Тогда данные CSV будут записаны в файл, а не в память. Тогда файл может быть гигабайтным и использовать очень мало памяти.

Дополнительным преимуществом этого является отсутствие необходимости доступа к данным буфера, создания из них строки (теперь использование памяти удваивается), а затем записи строки в файл.

 NSOutputStream *csvStream = [NSOutputStream outputStreamToFileAtPath:csvPath append:NO];
[csvStream open];
CHCSVWriter *writer = [[CHCSVWriter alloc] initWithOutputStream:csvStream encoding:NSUTF8StringEncoding delimiter:';'];

// write your CSV entries

[writer closeStream];
  

Вот и все. Для создания файла не требуется никакого другого кода.

В дополнение к этим изменениям вам необходимо изменить, где вы используете пул автоматического выпуска. Это должно быть внутри внешнего for цикла.

 //> write the rows
for (NSMutableDictionary *row in [self sharedUploadManager].modifiedRows) {
    @autoreleasepool {
        NSArray *orderedKeys = [[row allKeys] sortedArrayUsingDescriptors:keySortDescriptors];

        for (NSString *key in orderedKeys ) {

            NSString *field = [row objectForKey:key];
            if ([field isKindOfClass:[NSNull class]]) {
                [writer writeField:nil];
            } else {
                [writer writeField:field];
            }
        }

        //> finish the line
        [writer finishLine];
    }
}
  

Это гарантирует, что память автоматически выпущенных объектов очищается после каждой строки.

Комментарии:

1. Здравствуйте, спасибо за ответ, но я на самом деле тоже это пробовал, все еще продолжает расти для файлов большего размера, этот опубликованный код был просто попыткой посмотреть, работает ли что-то по-другому… Я вижу, что распределение увеличивается во время сеанса отладки и с помощью инструмента распределения. При выделении кажется, что writeField потребляет много, поэтому при почти миллионе строк приложение вылетает. Сейчас это необычная ситуация для приложения, но однажды это может случиться, и я пытаюсь с этим справиться.

2. @maddy Я только что заметил, что в вашей логике не используется append, я попытаюсь посмотреть, что с этим произойдет.

3. Нет, все то же самое… Сами файлы не такие большие, кажется, есть утечка, но я не могу ее обнаружить… Самый большой файл составляет около 20 МБ.

4. Спасибо, я сделал это для обоих циклов for здесь (измененные объекты также могут быть большими), теперь это выглядит лучше, но объем памяти все еще увеличивается.