2008-10-06 28 views
51

Cách đơn giản nhất (ít nhất là lỗi, ít dòng mã nhất, tuy nhiên bạn muốn diễn giải nó) để mở tệp trong C và đọc nội dung của tệp đó thành một chuỗi (char * , char [], bất cứ điều gì)?Cách dễ nhất để lấy nội dung của tệp trong C

+2

"cách đơn giản nhất" và "ít xảy ra lỗi" thường là sự đối lập lẫn nhau. –

+7

"cách đơn giản nhất" và "ít bị lỗi nhất" thực sự đồng nghĩa trong sách của tôi. Ví dụ, câu trả lời trong C# là 'string s = File.ReadAllText (filename);'.Làm thế nào có thể được đơn giản hơn và dễ bị lỗi hơn? –

Trả lời

75

Tôi có xu hướng chỉ tải toàn bộ bộ đệm dưới dạng bộ nhớ thô vào bộ nhớ và tự phân tích cú pháp. Bằng cách đó, tôi có quyền kiểm soát tốt nhất những gì lib tiêu chuẩn thực hiện trên nhiều nền tảng.

Đây là sơ khai tôi sử dụng cho việc này. bạn cũng có thể kiểm tra mã lỗi cho fseek, ftell và fread. (bỏ qua cho rõ ràng).

char * buffer = 0; 
long length; 
FILE * f = fopen (filename, "rb"); 

if (f) 
{ 
    fseek (f, 0, SEEK_END); 
    length = ftell (f); 
    fseek (f, 0, SEEK_SET); 
    buffer = malloc (length); 
    if (buffer) 
    { 
    fread (buffer, 1, length, f); 
    } 
    fclose (f); 
} 

if (buffer) 
{ 
    // start to process your data/extract strings here... 
} 
+0

Tuyệt vời, hoạt động như một sự quyến rũ (và khá đơn giản để theo dõi). Cảm ơn! –

+2

Tôi cũng sẽ kiểm tra giá trị trả về của fread, vì nó có thể không thực sự đọc toàn bộ tập tin do lỗi và những gì không. – freespace

+1

Dọc theo các dòng của những gì freespace nói, bạn có thể muốn kiểm tra để đảm bảo các tập tin không phải là rất lớn. Giả sử, một người nào đó đã quyết định nuôi một tệp 6 GB vào chương trình đó ... – rmeador

7

Nếu "đọc nội dung của nó thành một chuỗi" có nghĩa là các tập tin không chứa các ký tự với mã 0, bạn cũng có thể sử dụng getdelim() chức năng, mà một trong hai chấp nhận một khối bộ nhớ và reallocates nó nếu cần thiết, hoặc chỉ cấp phát toàn bộ bộ đệm cho bạn và đọc tệp vào bộ đệm cho đến khi nó gặp dấu phân tách hoặc kết thúc tệp. Chỉ cần vượt qua '\ 0' làm dấu phân tách để đọc toàn bộ tệp.

Chức năng này có sẵn trong thư viện GNU C, http://www.gnu.org/software/libc/manual/html_mono/libc.html#index-getdelim-994

Mẫu mã có thể trông đơn giản như

char* buffer = NULL; 
size_t len; 
ssize_t bytes_read = getdelim(&buffer, &len, '\0', fp); 
if (bytes_read != -1) { 
    /* Success, now the entire file is in the buffer */ 
+1

Tôi đã sử dụng nó trước đây! Nó hoạt động rất độc đáo, giả sử tập tin bạn đang đọc là văn bản (không chứa \ 0). – ephemient

+0

NICE! Tiết kiệm rất nhiều vấn đề khi slurping trong toàn bộ tập tin văn bản. Bây giờ nếu có một cách cực kỳ đơn giản tương tự như đọc một luồng tệp nhị phân cho đến EOF mà không cần bất kỳ ký tự phân cách nào! – anthony

17

khác, không may đánh giá cao hệ điều hành phụ thuộc, giải pháp là bộ nhớ ánh xạ tập tin. Những lợi ích thường bao gồm hiệu suất của việc đọc, và giảm sử dụng bộ nhớ khi các ứng dụng xem và bộ nhớ cache của hệ điều hành thực sự có thể chia sẻ bộ nhớ vật lý.

đang POSIX sẽ trông như thế này:

int fd = open("filename", O_RDONLY); 
int len = lseek(fd, 0, SEEK_END); 
void *data = mmap(0, len, PROT_READ, MAP_PRIVATE, fd, 0); 

Windows trên Mặt khác là phức tạp hơn chút, và tiếc là tôi không có một trình biên dịch trước mặt tôi để kiểm tra, nhưng các chức năng được cung cấp bởi CreateFileMapping()MapViewOfFile().

+0

Đừng quên kiểm tra giá trị trả lại từ các cuộc gọi hệ thống đó! –

6

Nếu tệp là văn bản và bạn muốn nhận dòng văn bản theo dòng, cách dễ nhất là sử dụng fgets().

char buffer[100]; 
FILE *fp = fopen("filename", "r");     // do not use "rb" 
while (fgets(buffer, sizeof(buffer), fp)) { 
... do something 
} 
fclose(fp); 
5

Nếu bạn đang đọc file đặc biệt như stdin hoặc một đường ống, bạn sẽ không thể sử dụng fstat để có được kích thước tập tin trước đó. Ngoài ra, nếu bạn đang đọc một tập tin nhị phân fgets là sẽ mất thông tin kích thước chuỗi vì nhúng '\ 0' ký tự. Cách tốt nhất để đọc một tập tin sau đó là sử dụng đọc và realloc:

#include <stdio.h> 
#include <unistd.h> 
#include <errno.h> 
#include <string.h> 

int main() { 
    char buf[4096]; 
    ssize_t n; 
    char *str = NULL; 
    size_t len = 0; 
    while (n = read(STDIN_FILENO, buf, sizeof buf)) { 
     if (n < 0) { 
      if (errno == EAGAIN) 
       continue; 
      perror("read"); 
      break; 
     } 
     str = realloc(str, len + n + 1); 
     memcpy(str + len, buf, n); 
     len += n; 
     str[len] = '\0'; 
    } 
    printf("%.*s\n", len, str); 
    return 0; 
} 
+0

Đây là O (n^2), trong đó n là độ dài của tệp của bạn. Tất cả các giải pháp với nhiều upvotes hơn này là O (n). Xin vui lòng không sử dụng giải pháp này trong thực tế, hoặc sử dụng một phiên bản sửa đổi với sự tăng trưởng nhân. –

+1

realloc() có thể mở rộng bộ nhớ hiện tại sang kích thước mới mà không cần sao chép bộ nhớ cũ sang một bộ nhớ lớn hơn mới. chỉ khi có các cuộc gọi can thiệp đến malloc() nó sẽ cần phải di chuyển bộ nhớ xung quanh và làm cho giải pháp này O (n^2). ở đây, không có lệnh gọi hàm malloc() nào xảy ra giữa các lệnh gọi đến realloc() để giải pháp sẽ ổn. – Jake

+2

Bạn có thể đọc trực tiếp vào bộ đệm "str" ​​(với độ lệch thích hợp), mà không cần phải sao chép từ một "buf" trung gian. Tuy nhiên, kỹ thuật đó thường sẽ cấp phát bộ nhớ cần thiết cho nội dung tập tin. Ngoài ra xem ra cho các tập tin nhị phân, printf sẽ không xử lý chúng một cách chính xác, và bạn có thể không muốn in nhị phân anyway! – anthony

0
// Assumes the file exists and will seg. fault otherwise. 
const GLchar *load_shader_source(char *filename) { 
    FILE *file = fopen(filename, "r");    // open 
    fseek(file, 0L, SEEK_END);      // find the end 
    size_t size = ftell(file);      // get the size in bytes 
    GLchar *shaderSource = calloc(1, size);  // allocate enough bytes 
    rewind(file);         // go back to file beginning 
    fread(shaderSource, size, sizeof(char), file); // read each char into ourblock 
    fclose(file);         // close the stream 
    return shaderSource; 
} 

Đây là một giải pháp khá thô bởi vì không có gì được kiểm tra đối chiếu null.

+0

Điều này sẽ chỉ với các tệp dựa trên đĩa. Nó sẽ thất bại cho các đường ống được đặt tên, đầu vào tiêu chuẩn hoặc luồng mạng. – anthony

+0

Ha, cũng là lý do tôi đến đây! Nhưng tôi nghĩ rằng bạn cần hoặc là null chấm dứt chuỗi, hoặc trả về độ dài mà 'glShaderSource' tùy chọn mất. –

1

Nếu bạn đang sử dụng glib, thì bạn có thể sử dụng g_file_get_contents;

gchar *contents; 
GError *err = NULL; 

g_file_get_contents ("foo.txt", &contents, NULL, &err); 
g_assert ((contents == NULL && err != NULL) || (contents != NULL && err == NULL)); 
if (err != NULL) 
    { 
    // Report error to user, and free error 
    g_assert (contents == NULL); 
    fprintf (stderr, "Unable to read file: %s\n", err->message); 
    g_error_free (err); 
    } 
else 
    { 
    // Use file contents 
    g_assert (contents != NULL); 
    } 
} 
0

Chỉ cần sửa đổi từ câu trả lời được chấp nhận ở trên.

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

char *readFile(char *filename) { 
    FILE *f = fopen(filename, "rt"); 
    assert(f); 
    fseek(f, 0, SEEK_END); 
    long length = ftell(f); 
    fseek(f, 0, SEEK_SET); 
    char *buffer = (char *) malloc(length + 1); 
    buffer[length] = '\0'; 
    fread(buffer, 1, length, f); 
    fclose(f); 
    return buffer; 
} 

int main() { 
    char *content = readFile("../hello.txt"); 
    printf("%s", content); 
} 
+0

Đây không phải là mã C. Câu hỏi không được gắn thẻ là C++. – Gerhardh

+0

@Gerhardh Vì vậy, phản ứng nhanh chóng với câu hỏi chín năm trước khi tôi đang chỉnh sửa! Mặc dù phần chức năng là C thuần khiết, tôi xin lỗi vì câu trả lời không-chạy-trên-c của tôi. – BaiJiFeiLong

+0

Câu hỏi cổ này được liệt kê ở đầu câu hỏi đang hoạt động. Tôi đã không tìm kiếm nó. – Gerhardh

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