2012-05-10 31 views
6

Tôi có một tệp ASCII trong đó mỗi dòng chứa một bản ghi có độ dài thay đổi. Ví dụ:đọc và viết theo khối trên linux bằng cách sử dụng c

Record-1:15 characters 
Record-2:200 characters 
Record-3:500 characters 
... 
... 
Record-n: X characters 

Vì kích thước tệp khoảng 10GB, tôi muốn đọc bản ghi theo khối. Sau khi đọc, tôi cần phải chuyển đổi chúng, viết chúng vào một tập tin ở định dạng nhị phân.

Vì vậy, để đọc, phản ứng đầu tiên của tôi là tạo ra một mảng char như

FILE *stream; 
char buffer[104857600]; //100 MB char array 
fread(buffer, sizeof(buffer), 104857600, stream); 
  1. Là nó đúng để giả định, linux rằng sẽ phát hành một cuộc gọi hệ thống và lấy toàn bộ 100MB?
  2. Khi các bản ghi được phân cách bằng dòng mới, tôi tìm kiếm ký tự theo ký tự cho một ký tự dòng mới trong bộ đệm và tạo lại mỗi bản ghi.

Câu hỏi của tôi là đây là cách tôi nên đọc theo khối hoặc có cách thay thế tốt hơn để đọc dữ liệu theo khối và hoàn thành mỗi bản ghi? Có cách nào khác để đọc x số lượng các dòng có kích thước biến đổi từ một tệp ASCII trong một cuộc gọi không?

Tiếp theo khi viết, tôi cũng làm như vậy. Tôi có một bộ đệm char viết, mà tôi vượt qua để fwrite để viết một tập hợp toàn bộ các bản ghi trong một cuộc gọi.

fwrite(buffer, sizeof(buffer), 104857600, stream); 

UPDATE: Nếu tôi setbuf (stream, buffer), nơi đệm là bộ đệm char 100MB của tôi, sẽ bỏ trở lại từ bộ đệm hoặc gây ra đĩa IO?

+0

Nhìn vào fgets, nó sẽ nhận được một dòng tại một thời điểm cho bạn nếu bạn muốn. –

+0

Tôi muốn tránh đọc từng dòng, nhưng muốn đọc các dòng có kích thước biến X trong một lần chụp. Ngoài ra, với fgets(), tôi sẽ phải có một bộ đệm trong đó dòng dài nhất sẽ phù hợp. Vì kích thước bản ghi của tôi có thể thay đổi từ vài 100 byte đến 16MB, tôi sẽ lãng phí bộ nhớ. – Jimm

Trả lời

6
  1. Có, fread sẽ tìm nạp toàn bộ nội dung cùng một lúc. (Giả sử đó là một tập tin bình thường.) Nhưng nó sẽ không đọc 105 MB trừ khi bản thân tập tin là 105 MB, và nếu bạn không kiểm tra giá trị trả về, bạn không có cách nào biết được bao nhiêu dữ liệu đã thực sự đọc, hoặc nếu có là một lỗi.

  2. Sử dụng fgets (xem man fgets) thay vì fread. Điều này sẽ tìm kiếm các ngắt dòng cho bạn.

    char linebuf[1000]; 
    FILE *file = ...; 
    while (fgets(linebuf, sizeof(linebuf), file) { 
        // decode one line 
    } 
    
  3. Có vấn đề với mã của bạn.

    char buffer[104857600]; // too big 
    

    Nếu bạn cố gắng cấp phát bộ đệm lớn (105 MB chắc chắn là lớn) trên ngăn xếp, khi đó chương trình sẽ không thành công và chương trình của bạn sẽ bị lỗi. Nếu bạn cần một bộ đệm lớn, bạn sẽ phải phân bổ nó trên heap với malloc hoặc tương tự. Tôi chắc chắn sẽ tiếp tục sử dụng stack cho một chức năng duy nhất trong hàng chục KB, mặc dù bạn có thể có thể lấy đi một vài MB trên hầu hết các hệ thống Linux.

Thay vào đó, bạn chỉ có thể mmap toàn bộ tệp vào bộ nhớ. Điều này sẽ không cải thiện hoặc làm giảm hiệu suất trong hầu hết các trường hợp, nhưng nó dễ dàng hơn để làm việc với.

int r, fdes; 
struct stat st; 
void *ptr; 
size_t sz; 

fdes = open(filename, O_RDONLY); 
if (fdes < 0) abort(); 
r = fstat(fdes, &st); 
if (r) abort(); 
if (st.st_size > (size_t) -1) abort(); // too big to map 
sz = st.st_size; 
ptr = mmap(NULL, sz, PROT_READ, MAP_SHARED, fdes, 0); 
if (ptr == MAP_FAILED) abort(); 
close(fdes); // file no longer needed 

// now, ptr has the data, sz has the data length 
// you can use ordinary string functions 

Lợi thế của việc sử dụng mmap là chương trình của bạn sẽ không hết bộ nhớ. Trên hệ thống 64 bit, bạn có thể đặt toàn bộ tệp vào không gian địa chỉ của bạn cùng lúc (ngay cả tệp 10 GB) và hệ thống sẽ tự động đọc các phần mới khi chương trình của bạn truy cập vào bộ nhớ.Các đoạn cũ sẽ tự động bị loại bỏ và đọc lại nếu chương trình của bạn cần chúng một lần nữa.

Đó là cách rất hay để cày qua các tệp lớn.

0

ý kiến ​​của tôi là sử dụng fgets(buff) để tự động phát hiện dòng mới.

và sau đó sử dụng strlen(buff) cho đếm kích thước bộ đệm,

if((total+strlen(buff)) > 104857600) 

sau đó viết trong đoạn mới ..

Nhưng kích thước của đoạn sẽ khó có 104.857.600 byte.

CMIIW

+0

Trong trường hợp buff của tôi là mảng char. Nhưng tài liệu fgets nói rằng nó hoạt động trên một dòng của FILE loại. – Jimm

+0

[fgets] (http://www.cplusplus.com/reference/clibrary/cstdio/fgets/) có 3 tham số 'char *', 'int', và' FILE * '. Vì vậy, đối với trường hợp của bạn đặt bộ đệm của bạn tại 'char *' –

2

Nếu có thể, bạn có thể thấy rằng tệp mmap sẽ dễ dàng nhất. mmap ánh xạ một phần (của một) tệp vào bộ nhớ để toàn bộ tệp có thể được truy cập cơ bản dưới dạng một mảng byte. Trong trường hợp của bạn, bạn có thể không có khả năng lập bản đồ toàn bộ tập tin cùng một lúc nó sẽ giống như thế:

#include <stdio.h> 
#include <sys/stat.h> 
#include <sys/types.h> 
#include <unistd.h> 
#include <sys/mman.h> 


/* ... */ 

struct stat stat_buf; 
long pagesz = sysconf(_SC_PAGESIZE); 
int fd = fileno(stream); 
off_t line_start = 0; 
char *file_chunk = NULL; 
char *input_line; 
off_t cur_off = 0; 
off_t map_offset = 0; 
/* map 16M plus pagesize to ensure any record <= 16M will always fit in the mapped area */ 
size_t map_size = 16*1024*1024+pagesz; 
if (map_offset + map_size > stat_buf.st_size) { 
    map_size = stat_buf.st_size - map_offset; 
} 
fstat(fd, &stat_buf); 
/* map the first chunk of the file */ 
file_chunk = mmap(NULL, map_size, PROT_READ, MAP_SHARED, fd, map_offset); 
// until we reach the end of the file 
while (cur_off < stat_buf.st_size) { 
    /* check if we're about to read outside the current chunk */ 
    if (!(cur_off-map_offset < map_size)) { 
    // destroy the previous mapping 
    munmap(file_chunk, map_size); 
    // round down to the page before line_start 
    map_offset = (line_start/pagesz)*pagesz; 
    // limit mapped region to size of file 
    if (map_offset + map_size > stat_buf.st_size) { 
     map_size = stat_buf.st_size - map_offset; 
    } 
    // map the next chunk 
    file_chunk = mmap(NULL, map_size, PROT_READ, MAP_SHARED, fd, map_offset); 
    // adjust the line start for the new mapping 
    input_line = &file_chunk[line_start-map_offset]; 
    } 
    if (file_chunk[cur_off-map_offset] == '\n') { 
    // found a new line, process the current line 
    process_line(input_line, cur_off-line_start); 
    // set up for the next one 
    line_start = cur_off+1; 
    input_line = &file_chunk[line_start-map_offset]; 
    } 
    cur_off++; 
} 

Hầu hết các biến chứng là để tránh làm cho một ánh xạ quá lớn. Bạn có thể ánh xạ toàn bộ tệp bằng cách sử dụng

char *file_data = mmap(NULL, stat_buf.st_size, PROT_READ, MAP_SHARED, fd, 0); 
Các vấn đề liên quan