Самый экономичный способ чтения и хранения списка строк в C

Я хотел бы знать, какой самый эффективный способ чтения и хранения списка строк в C.

Каждая строка может иметь разную длину, поэтому предварительное выделение большого 2D-массива было бы расточительным. Я также хочу избежать отдельного malloc для каждой строки, так как строк может быть много.

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

Можно ли хранить все строки отдельно с помощью одного выделения точно нужного размера?

Одна из идей, которые у меня есть, состоит в том, чтобы хранить их непрерывно в буфере, а затем иметь массив char *, указывающий на разные части в буфере, в котором будут '\ 0's для разграничения. Я надеюсь, что есть лучший способ.

struct list {
  char *index[32];
  char buf[];
};

Структура данных и строки будут строго доступны только для чтения.


person cloudhead    schedule 12.03.2014    source источник
comment
как насчет списка ссылок смежных буферов - чтобы вам не пришлось дорого перераспределять   -  person amdixon    schedule 12.03.2014
comment
@amdixon он не хочет malloc для каждой строки.   -  person RedX    schedule 12.03.2014
comment
@amdixon да, проблема в том, что я хотел бы иметь возможность использовать один free() для всего этого, если это возможно.   -  person cloudhead    schedule 12.03.2014
comment
Если вы никогда не собираетесь изменять длину какой-либо из строк, тогда ваша идея удивительно элегантна.   -  person Bathsheba    schedule 12.03.2014
comment
Решение во многом зависит от того, является ли сохраненный список строк статическим и доступен ли он только для чтения в конце, или если вы хотите иметь возможность добавлять/удалять/обновлять строки. Итак, какие операции должны быть доступны после создания списка строк?   -  person Flovdis    schedule 12.03.2014
comment
@Flovdi они доступны только для чтения.   -  person cloudhead    schedule 12.03.2014
comment
@cloudhead Хранение всех строк в одной — это нормально, если вы сохраните их количество в начале или, например, вы можете добавить в конец несколько дополнительных «\0». Вам нужна дополнительная информация, чтобы определить, следует ли прекратить чтение строк.   -  person Michael    schedule 12.03.2014
comment
Единственная проблема @Bathsheba - это индекс фиксированного размера ..   -  person cloudhead    schedule 12.03.2014
comment
Хотя наличие большого буфера с разделителем '\0' работает для постоянных строк и используется во многих (особенно старых) программах на языке C, вы должны поддерживать согласованность буфера, и это может быть очень сложно, если вам нужно изменить размер любой строки. . Я считаю, что вам лучше использовать альтернативный распределитель памяти, который мог бы резервировать фрагменты памяти из ОС и предоставлять их вашему приложению на гранулированной основе.   -  person Gerardo Lima    schedule 12.03.2014


Ответы (5)


Вот умеренно эффективный формат, предполагающий, что вы заранее знаете длину всех строк:

|| total size |  string 1 | string 2 | ........ | string N | len(string N) | ... | len(string 2) | len(string 1) ||

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

person Kerrek SB    schedule 12.03.2014
comment
Интересно, а как это выглядит на C? Я все еще хотел бы иметь рабочие символы * для строк, а не полностью непрозрачный буфер. - person cloudhead; 12.03.2014
comment
Вы можете хранить последовательные строки с разделителями NUL, поэтому вы можете просто взять смещение (buffer[offset]) и передать его в функцию, ожидающую char* - person Aktau; 12.03.2014
comment
@cloudhead: Как говорит Актау, вам решать, хотите ли вы включить нулевой терминатор. Это конечно вариант. А вычисление смещения k-й строки обходится дешево. - person Kerrek SB; 12.03.2014

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

person Mauren    schedule 12.03.2014

Найти количество и общую длину всех строк:

int num = 0;
int len = 0;
char* string = GetNextString(input);
while (string)
{
    num += 1;
    len += strlen(string);
    string = GetNextString(input);
}
Rewind(input);

Затем выделите следующие два буфера:

int*  indexes = malloc(num*sizeof(int));
char* strings = malloc((num+len)*sizeof(char));

Наконец заполните эти два буфера:

int index = 0;
for (int i=0; i<num; i++)
{
    indexes[i] = index;
    string = GetNextString(input);
    strcpy(strings+index,string);
    index += strlen(string)+1;
}

После этого вы можете просто использовать strings[indexes[i]] для доступа к i-й строке.

person barak manos    schedule 12.03.2014

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

Вы можете создать массив указателей для строк и вычислить разницу между указателями, чтобы получить размеры строк. Таким образом, вы сохраняете нулевой байт как конечный маркер.

Вот полный пример:

#include <stdio.h>
#include <memory.h>
#include <stdlib.h>

struct StringMap
{
    char *data;
    char **ptr;
    long cPos;
};


void initStringMap(StringMap *stringMap, long numberOfStrings, long totalCharacters)
{
    stringMap->data = (char*)malloc(sizeof(char)*(totalCharacters+1));
    stringMap->ptr = (char**)malloc(sizeof(char*)*(numberOfStrings+2));
    memset(stringMap->ptr, 0, sizeof(char*)*(numberOfStrings+1));
    stringMap->ptr[0] = stringMap->data;
    stringMap->ptr[1] = stringMap->data;
    stringMap->cPos = 0;
}


void extendString(StringMap *stringMap, char *str, size_t size)
{
    memcpy(stringMap->ptr[stringMap->cPos+1], str, size);
    stringMap->ptr[stringMap->cPos+1] += size;
}


void endString(StringMap *stringMap)
{
    stringMap->cPos++;
    stringMap->ptr[stringMap->cPos+1] = stringMap->ptr[stringMap->cPos];
}


long numberOfStringsInStringMap(StringMap *stringMap)
{
    return stringMap->cPos;
}


size_t stringSizeInStringMap(StringMap *stringMap, long index)
{
    return stringMap->ptr[index+1] - stringMap->ptr[index];
}


char* stringinStringMap(StringMap *stringMap, long index)
{
    return stringMap->ptr[index];
}


void freeStringMap(StringMap *stringMap)
{
    free(stringMap->data);
    free(stringMap->ptr);
}


int main()
{
    // The interesting values
    long numberOfStrings = 0;
    long totalCharacters = 0;

    // Scan the input for required information
    FILE *fd = fopen("/path/to/large/textfile.txt", "r");
    int bufferSize = 4096;
    char *readBuffer = (char*)malloc(sizeof(char)*bufferSize);
    int currentStringLength = 0;
    ssize_t readBytes;
    while ((readBytes = fread(readBuffer, sizeof(char), bufferSize, fd))>0) {
        for (int i = 0; i < readBytes; ++i) {
            const char c = readBuffer[i];
            if (c != '\n') {
                ++currentStringLength;
            } else {
                ++numberOfStrings;
                totalCharacters += currentStringLength;
                currentStringLength = 0;
            }
        }
    }

    // Display the found results
    printf("Found %ld strings with total of %ld bytes\n", numberOfStrings, totalCharacters);

    // Allocate the memory for the resource
    StringMap stringMap;
    initStringMap(&stringMap, numberOfStrings, totalCharacters);

    // read all strings
    rewind(fd);
    while ((readBytes = fread(readBuffer, sizeof(char), bufferSize, fd))>0) {
        char *stringStart = readBuffer;
        for (int i = 0; i < readBytes; ++i) {
            const char c = readBuffer[i];
            if (c == '\n') {
                extendString(&stringMap, stringStart, &readBuffer[i]-stringStart);
                endString(&stringMap);
                stringStart = &readBuffer[i+1];
            }
        }
        if (stringStart < &readBuffer[readBytes]) {
            extendString(&stringMap, stringStart, &readBuffer[readBytes]-stringStart);
        }
    }
    endString(&stringMap);
    fclose(fd);

    // Ok read the list
    numberOfStrings = numberOfStringsInStringMap(&stringMap);
    printf("Number of strings in map: %ld\n", numberOfStrings);
    for (long i = 0; i < numberOfStrings; ++i) {
        size_t stringSize = stringSizeInStringMap(&stringMap, i);
        char *buffer = (char*)malloc(stringSize+1);
        memcpy(buffer, stringinStringMap(&stringMap, i), stringSize);
        buffer[stringSize-1] = '\0';
        printf("string %05ld size=%8ld : %s\n", i, stringSize, buffer);
        free(buffer);
    }

    // free the resource
    freeStringMap(&stringMap);
}

В этом примере читается очень большой текстовый файл, разбивается на строки и создается массив со строкой в ​​каждой строке. Требуется только два вызова malloc. Один для массива указателей и один для блока жала.

person Flovdis    schedule 12.03.2014

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

Первый sizeof(long) bytes хранит количество строк, n. Следующие n long сохраняют смещения в каждой строке от начала string buffer, которое начинается с позиции (n+1)*sizeof(long). Вам не нужно сохранять конечный нуль для каждой строки, но если вы это сделаете, вы можете получить доступ к каждой строке с помощью &str_buffer[offset[i]]. Если вы не сохраните завершающий «\ 0», вам придется скопировать во временный буфер и добавить его самостоятельно.

person Thomas Jay Rush    schedule 28.12.2016