2013-02-12 44 views
12

Cách tốt nhất để đọc tệp ngược trong C là gì? Tôi biết lúc đầu bạn có thể nghĩ rằng điều này là không sử dụng bất cứ điều gì, nhưng hầu hết các bản ghi vv nối thêm các dữ liệu gần đây nhất ở phần cuối của tập tin. Tôi muốn đọc trong văn bản từ tập tin ngược, đệm nó vào dòng - đó làĐọc một tệp văn bản ngược trong C

abc
def
writing

nên đọc writing, def, abc trong dòng.

Cho đến nay tôi đã cố gắng:

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

    void read_file(FILE *fileptr) 
    { 
     char currentchar = '\0'; 
     int size = 0; 

     while(currentchar != '\n') 
     { 
      currentchar = fgetc(fileptr); printf("%c\n", currentchar); 
      fseek(fileptr, -2, SEEK_CUR); 
      if(currentchar == '\n') { fseek(fileptr, -2, SEEK_CUR); break; } 
      else size++; 

     } 
     char buffer[size]; fread(buffer, 1, size, fileptr); 
     printf("Length: %d chars\n", size); 
     printf("Buffer: %s\n", buffer); 


    } 


    int main(int argc, char *argv[]) 
    { 
     if(argc < 2) { printf("Usage: backwards [filename]\n"); return 1; } 

     FILE *fileptr = fopen(argv[1], "rb"); 
     if(fileptr == NULL) { perror("Error:"); return 1; } 

     fseek(fileptr, -1, SEEK_END); /* Seek to END of the file just before EOF */ 
     read_file(fileptr); 


     return 0; 


    } 

Trong một nỗ lực để đơn giản là đọc một dòng và đệm nó. Xin lỗi vì mã của tôi thật khủng khiếp, tôi rất bối rối. Tôi biết rằng bạn thường phân bổ bộ nhớ cho toàn bộ tệp và sau đó đọc dữ liệu, nhưng đối với các tệp lớn liên tục thay đổi, tôi nghĩ sẽ tốt hơn nếu đọc trực tiếp (đặc biệt nếu tôi muốn tìm kiếm văn bản trong tệp).

Cảm ơn trước

* Xin lỗi quên đề cập đến điều này sẽ được sử dụng trên Linux, vì vậy dòng mới chỉ là NL không CR. *

+2

Bạn có thể có thể [bộ nhớ ánh xạ tệp] (http://en.wikipedia.org/wiki/Memory-mapped_file) và sử dụng số học con trỏ để "đọc" tệp. Có thể đơn giản hơn là phải liên tục nhảy xung quanh với con trỏ tập tin. –

+0

Từ tiêu chuẩn C: 'Một luồng nhị phân không cần hỗ trợ một cách có ý nghĩa các cuộc gọi fseek với giá trị của SEEK_END.' –

+0

Có lẽ bạn có thể đăng nhập vào DB thay vì tệp đơn giản? –

Trả lời

7

Tôi khuyên bạn nên xác định kích thước tệp di động (hy vọng) nhiều hơn vì fseek(binaryStream, offset, SEEK_END) không được đảm bảo để hoạt động. Xem mã bên dưới. Tôi tin rằng các tệp phải ít nhất được đệm tối thiểu ở cấp hạt nhân (ví dụ: đệm ít nhất một khối cho mỗi tệp theo mặc định), do đó, tìm kiếm sẽ không phải chịu số lượng I/O phụ đáng kể và chỉ nên nâng cấp vị trí tệp trong nội bộ. Nếu bộ đệm mặc định không thỏa đáng, bạn có thể thử sử dụng setvbuf() để tăng tốc I/O.

#include <limits.h> 
#include <string.h> 
#include <stdio.h> 

/* File must be open with 'b' in the mode parameter to fopen() */ 
long fsize(FILE* binaryStream) 
{ 
    long ofs, ofs2; 
    int result; 

    if (fseek(binaryStream, 0, SEEK_SET) != 0 || 
     fgetc(binaryStream) == EOF) 
    return 0; 

    ofs = 1; 

    while ((result = fseek(binaryStream, ofs, SEEK_SET)) == 0 && 
     (result = (fgetc(binaryStream) == EOF)) == 0 && 
     ofs <= LONG_MAX/4 + 1) 
    ofs *= 2; 

    /* If the last seek failed, back up to the last successfully seekable offset */ 
    if (result != 0) 
    ofs /= 2; 

    for (ofs2 = ofs/2; ofs2 != 0; ofs2 /= 2) 
    if (fseek(binaryStream, ofs + ofs2, SEEK_SET) == 0 && 
     fgetc(binaryStream) != EOF) 
     ofs += ofs2; 

    /* Return -1 for files longer than LONG_MAX */ 
    if (ofs == LONG_MAX) 
    return -1; 

    return ofs + 1; 
} 

/* File must be open with 'b' in the mode parameter to fopen() */ 
/* Set file position to size of file before reading last line of file */ 
char* fgetsr(char* buf, int n, FILE* binaryStream) 
{ 
    long fpos; 
    int cpos; 
    int first = 1; 

    if (n <= 1 || (fpos = ftell(binaryStream)) == -1 || fpos == 0) 
    return NULL; 

    cpos = n - 1; 
    buf[cpos] = '\0'; 

    for (;;) 
    { 
    int c; 

    if (fseek(binaryStream, --fpos, SEEK_SET) != 0 || 
     (c = fgetc(binaryStream)) == EOF) 
     return NULL; 

    if (c == '\n' && first == 0) /* accept at most one '\n' */ 
     break; 
    first = 0; 

    if (c != '\r') /* ignore DOS/Windows '\r' */ 
    { 
     unsigned char ch = c; 
     if (cpos == 0) 
     { 
     memmove(buf + 1, buf, n - 2); 
     ++cpos; 
     } 
     memcpy(buf + --cpos, &ch, 1); 
    } 

    if (fpos == 0) 
    { 
     fseek(binaryStream, 0, SEEK_SET); 
     break; 
    } 
    } 

    memmove(buf, buf + cpos, n - cpos); 

    return buf; 
} 

int main(int argc, char* argv[]) 
{ 
    FILE* f; 
    long sz; 

    if (argc < 2) 
    { 
    printf("filename parameter required\n"); 
    return -1; 
    } 

    if ((f = fopen(argv[1], "rb")) == NULL) 
    { 
    printf("failed to open file \'%s\'\n", argv[1]); 
    return -1; 
    } 

    sz = fsize(f); 
// printf("file size: %ld\n", sz); 

    if (sz > 0) 
    { 
    char buf[256]; 
    fseek(f, sz, SEEK_SET); 
    while (fgetsr(buf, sizeof(buf), f) != NULL) 
     printf("%s", buf); 
    } 

    fclose(f); 
    return 0; 
} 

Tôi chỉ thử nghiệm trên cửa sổ này với 2 trình biên dịch khác nhau.

+0

Cảm ơn vì điều đó, nó hoạt động thực sự tốt (tôi không bao giờ có thể đi lên với bản thân mình, xoắn não của tôi xung quanh rất nhiều!). – Joshun

+0

Không có prob. Tôi đã sửa một lỗi nhỏ để nó có thể xử lý đúng các trường hợp mà dòng cuối cùng không kết thúc bằng ''\ n'' (ban đầu nó sẽ được nối với dòng trước đó, nếu có). –

+0

@AlexeyFrunze, cuối cùng có cần buf miễn phí không? – scorpiozj

9

Bạn chỉ cần nhập đầu vào thông qua chương trình tac, giống như cat nhưng ngược lại!

http://linux.die.net/man/1/tac

+1

+1 Và tôi chỉ tìm hiểu về 'tac' bây giờ, điều đó thật tuyệt vời. – Mike

+0

Có vẻ tốt, nó có chức năng C không? – Joshun

+0

Bạn chắc chắn có thể tìm thấy mã nguồn cho 'tac' ở đâu đó trên t'internet. Ví dụ ở đây: http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/tac.c –

4

Có khá nhiều cách bạn có thể làm điều này, nhưng đọc một byte tại một thời điểm chắc chắn là một trong những sự lựa chọn nghèo.

Đọc cuối cùng, nói, 4KB và sau đó đi bộ trở lại từ ký tự cuối cùng đến dòng mới trước đó sẽ là lựa chọn của tôi.

Một tùy chọn khác là mmap tệp và chỉ giả vờ rằng tệp là một cục bộ nhớ và quét ngược lại trong đó. [Bạn có thể biết mmap bạn cũng đang đọc ngược, để làm cho dữ liệu tìm nạp trước cho bạn].

Nếu tệp VERY lớn (vài gigabyte), bạn có thể chỉ muốn sử dụng một phần nhỏ của tệp trong mmap.

+0

Cảm ơn, tôi sẽ thử 'mmaping' nó – Joshun

0

FSEEKing cho mỗi byte âm thanh PAINFULLY chậm.

Nếu bạn đã có bộ nhớ, chỉ cần đọc toàn bộ tệp vào bộ nhớ và ngược lại hoặc quét ngược lại.

Một tùy chọn khác sẽ là các tệp ánh xạ bộ nhớ Windows.

+2

các tệp ánh xạ bộ nhớ không phải là cửa sổ- tính năng cụ thể :) –

1

Nếu bạn muốn tìm hiểu làm thế nào để làm điều đó, đây là một ví dụ Debian/Ubuntu (ví khác như distro RPM dựa, điều chỉnh khi cần thiết):

~$ which tac 
/usr/bin/tac 
~$ dpkg -S /usr/bin/tac 
coreutils: /usr/bin/tac 
~$ mkdir srcs 
~$ cd srcs 
~/srcs$ apt-get source coreutils 

(Clip apt-get đầu ra)

~/srcs$ ls 
coreutils-8.13 coreutils_8.13-3.2ubuntu2.1.diff.gz coreutils_8.13-3.2ubuntu2.1.dsc coreutils_8.13.orig.tar.gz 
~/srcs$ cd coreutils-8.13/ 
~/srcs/coreutils-8.13$ find . -name tac.c 
./src/tac.c 
~/srcs/coreutils-8.13$ less src/tac.c 

Không quá dài, trên 600 dòng, và trong khi nó gói một số tính năng nâng cao và sử dụng chức năng từ các nguồn khác, việc triển khai đệm ngược dòng có vẻ như trong tệp nguồn tac.c.

Các vấn đề liên quan