2010-08-01 5 views
4

Я пытаюсь прочитать весь контент из текстового файла. Вот код, который я написал.Чтение всего содержимого из текстового файла - C

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

#define PAGE_SIZE 1024 

static char *readcontent(const char *filename) 
{ 
    char *fcontent = NULL, c; 
    int index = 0, pagenum = 1; 
    FILE *fp; 
    fp = fopen(filename, "r"); 

    if(fp) { 
     while((c = getc(fp)) != EOF) { 
      if(!fcontent || index == PAGE_SIZE) { 
       fcontent = (char*) realloc(fcontent, PAGE_SIZE * pagenum + 1); 
       ++pagenum; 
      } 
      fcontent[index++] = c; 
     } 
     fcontent[index] = '\0'; 
     fclose(fp); 
    } 
    return fcontent; 
} 

static void freecontent(char *content) 
{ 
    if(content) { 
     free(content); 
     content = NULL; 
    } 
} 

Это использование

int main(int argc, char **argv) 
{ 
    char *content; 
    content = readcontent("filename.txt"); 
    printf("File content : %s\n", content); 
    fflush(stdout); 
    freecontent(content); 
    return 0; 
} 

Поскольку я новичок в C, интересно выглядит ли этот код совершенным? Вы видите какие-либо проблемы/улучшения?

Компилятор используется: GCC. Но этот код, как ожидается, будет кросс-платформой.

Любая помощь будет оценена по достоинству.

Редактировать

Вот обновленный код с fread и ftell.

static char *readcontent(const char *filename) 
{ 
    char *fcontent = NULL; 
    int fsize = 0; 
    FILE *fp; 

    fp = fopen(filename, "r"); 
    if(fp) { 
     fseek(fp, 0, SEEK_END); 
     fsize = ftell(fp); 
     rewind(fp); 

     fcontent = (char*) malloc(sizeof(char) * fsize); 
     fread(fcontent, 1, fsize, fp); 

     fclose(fp); 
    } 
    return fcontent; 
} 

Мне интересно, какая будет относительная сложность этой функции?

+2

Я вряд ли думаю, что использование имени файла принесет вам много удовольствия –

+1

ahh .. Извините за это. Я тестировал и забыл удалить его. Очень жаль. –

+0

Я думаю, что в общем вы должны попытаться работать в фиксированных кусках; поэтому в этом случае вы будете читать в PAGE_SIZE байтах за раз (или меньше, если это последний кусок) и печатать каждый кусок по мере их чтения. – wj32

ответ

7

Вы должны попытаться взглянуть на функции fsize (О FSIZE смотрите обновления ниже) и fread. Это может быть огромным улучшением производительности.

Используйте fsize чтобы получить размер файла, который вы читаете. Используйте этот размер, чтобы сделать только один адрес памяти. (О fsize, см. Обновление ниже. Идея получить размер файла и сделать один выделение по-прежнему равна).

Используйте fread, чтобы выполнить чтение файла. Это намного быстрее, чем чтение одного файла в charecter.

Что-то вроде этого:

long size = fsize(fp); 
fcontent = malloc(size); 
fread(fcontent, 1, size, fp); 

Update

Не уверен, что FSIZE является кросс-платформенный, но вы можете использовать этот метод, чтобы получить размер файла:

fseek(fp, 0, SEEK_END); 
size = ftell(fp); 
fseek(fp, 0, SEEK_SET); 
+0

Спасибо. Я искал документацию 'fsize', но не смог найти ее. Является ли это независимой от платформы функцией? Как 'fsize' может определить размер файла без чтения всего файла? –

+0

Только что обновил мой ответ с заменой fsize :) –

+0

'fsize' выглядит как Windows. 'stat (2)' - эквивалент UNIX. – Wang

2

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

Но еще лучше: stat(2) Размер файла и выделение один раз (с дополнительной комнатой, если размер файла нестабилен).

Кроме того, почему вы не используете fgets(3) вместо того, чтобы читать символ по символу или, еще лучше, mmap(2) все это (или соответствующий фрагмент, если он слишком велик для памяти).

2

Это, вероятно, медленнее, и, конечно, более сложная, чем:

while((c = getc(fp)) != EOF) { 
    putchar(c); 
} 

, который делает то же самое, что ваш код.

0

В системах POSIX (например, linux) вы можете получить тот же эффект при системном вызове mmap, который отображает весь ваш файл в памяти. У него есть возможность сопоставить этот файл copy на записи, поэтому вы должны перезаписать свой файл, если вы измените буфер.

Это, как правило, намного эффективнее, поскольку вы оставляете столько, сколько сможете, в систему. Не нужно делать realloc или тому подобное.

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

+0

Я думаю, вы смущены тем, что означает копирование на запись. Если файл сопоставляется copy-on-write (private), карта изначально является ссылкой на файл на диске, но любые изменения, которые вы вносите в него, приведут к копированию данных, которые являются локальными для вашего процесса. Если он сопоставлен, то ваши изменения будут записаны в файл и будут видны другими процессами. –

+0

@R. ссылка на файл на диске? уверенный, что все 'mmap' делает это, это идея этого. Я имел в виду, что система может хранить все страницы, которые вы не изменяете в своем кеше страниц, и делиться этим кешем между процессами. Это справедливо для двух ситуаций: (1) до тех пор, пока вы набираете объекты только для чтения или (2), если вы используете copy-on-write, и вы не меняете содержимое. Поэтому, если вы считаете, что вам нужен произвольный доступ ко всему содержимому файла, 'mmap' почти всегда является лучшей стратегией. 'fread' и варианты должны быть ограничены случаями, когда вам нужен только частичный доступ к файлу в данный момент времени. –

1

Это из быстрого чтения, поэтому я, возможно, пропустил несколько вопросов.

Во-первых, a = realloc(a, ...); неправ. Если realloc() не работает, он возвращает NULL, но не освобождает исходную память. Поскольку вы переназначаете до a, исходная память потеряна (т. Е. Это утечка памяти). Правильный способ сделать это: tmp = realloc(a, ...); if (tmp) a = tmp; и т. Д.

Во-вторых, об определении размера файла с помощью fseek(fp, 0, SEEK_END);, обратите внимание, что это может работать или не работать. Если файл не является произвольным доступом (например, stdin), вы не сможете вернуться к началу, чтобы прочитать его. Кроме того, fseek(), за которым следует ftell(), может не дать значимого результата для двоичных файлов. А для текстовых файлов оно может не дать вам правильного количества символов, которые можно прочитать. Есть полезная информация по этой теме на comp.lang.c Часто задаваемые вопросы question 19.2.

Кроме того, в исходном коде, вы не установите index в 0, когда она равна PAGESIZE, так что если длина файла больше 2*PAGESIZE, вы перезаписать буфер.

Ваша freecontent() функция:

static void freecontent(char *content) 
{ 
    if(content) { 
     free(content); 
     content = NULL; 
    } 
} 

бесполезно. Он копирует только content в NULL. Это так же, как если бы вы написали функцию setzero так:

void setzero(int i) { i = 0; } 

Гораздо лучше идея заключается в том, чтобы следить памяти самостоятельно, а не свободного ничего больше или меньше, чем нужно.

Вы не должны отбрасывать возвращаемое значение malloc() или realloc() в C, так как void * неявно преобразуется в любой другой тип указателя объекта в С.

Надежда, что помогает.

+0

'stdin' доступен для поиска, если он ссылается на файл для поиска. Он не доступен для поиска, если это интерактивное устройство, труба и т. Д. 'Fseek' /' ftell' ** является ** надежным в двоичных файлах в любой разумной системе. Да, стандартные дедушки C - в унаследованных реализациях, где двоичные файлы могут иметь случайные конечные нулевые байты, но это 2010 год, и все настоящие современные системы имеют реальные двоичные файлы. Текстовый режим просто не должен использоваться из-за непредсказуемого и ошибочного поведения. Просто разделите '\ r' себя. –

+0

@R ..: На моем Mac 'fseek (stdin, 0, SEEK_END)' преуспевает, 'ftell()' возвращает 0, а затем я могу прочитать столько символов из 'stdin', сколько захочу. На linux 'fseek (stdin, 0, SEEK_END);' приводит к 'Illegal seek' (одна и та же программа). Я бы предпочел использовать метод realloc() ', потому что тогда мне не придется иметь дело с такими вещами, как самозахват' \ r', и он также работает и для файлов, которые не доступны для поиска. –

+0

Если вам не нужен весь файл в памяти, вы должны, вероятно, следовать за ответом msw, который не имеет ошибок и легко доказывает правильность. Кстати, если вы хотите вырезать '\ r' (например, из текстовых файлов Windows), вам все равно придется делать это самостоятельно. Только Windows и устаревшие Mac (pre-OSX) имеют «текстовые режимы» файловых операций, которые мешают данным. POSIX требует, чтобы текстовый режим вел себя одинаково с двоичным режимом, и он работает на OSX, Linux и т. Д. –

1

Одна проблема, которую я вижу здесь, является переменной index, которая не уменьшается. Таким образом, условие if(!fcontent || index == PAGE_SIZE) будет истинным только один раз. Поэтому я считаю, что проверка должна быть как index%PAGE_SIZE == 0 вместо index == PAGE_SIZE.

Смежные вопросы