Bagaimana cara membaca konten file menjadi string di C?
97
Apa cara paling sederhana (rawan kesalahan, baris kode paling sedikit, bagaimanapun Anda ingin menafsirkannya) untuk membuka file di C dan membaca isinya menjadi string (char *, char [], apa saja)?
"cara paling sederhana" dan "paling tidak rawan kesalahan" sering kali berlawanan satu sama lain.
Andy Lester
15
"cara paling sederhana" dan "paling tidak rawan kesalahan" sebenarnya sama dalam buku saya. Misalnya jawaban di C # adalah string s = File.ReadAllText(filename);. Bagaimana itu bisa lebih sederhana dan lebih rentan kesalahan?
Mark Lakata
Jawaban:
146
Saya cenderung hanya memuat seluruh buffer sebagai potongan memori mentah ke dalam memori dan melakukan parsing sendiri. Dengan cara itu saya memiliki kendali terbaik atas apa yang dilakukan lib standar pada berbagai platform.
Ini adalah rintisan yang saya gunakan untuk ini. Anda mungkin juga ingin memeriksa kode kesalahan untuk fseek, ftell dan fread. (dihilangkan untuk kejelasan).
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...
}
Saya juga akan memeriksa nilai kembalian fread, karena mungkin tidak benar-benar membaca seluruh file karena kesalahan dan apa yang tidak.
freespace
6
seperti kata rmeador, fseek akan gagal pada file> 4GB.
KPexEA
6
Benar. Untuk file besar, solusi ini menyebalkan.
Nils Pipenbrinck
33
Karena ini adalah halaman arahan, saya ingin menunjukkan bahwa freadtidak menghentikan nol string Anda. Hal ini dapat menyebabkan masalah.
ivan-k
19
Seperti yang dikatakan @Manbroski, buffer harus '\ 0' diakhiri. Jadi saya akan mengubah buffer = malloc (length + 1);dan menambahkan setelah fclose: buffer[length] = '\0';(divalidasi oleh Valgrind)
soywod
26
Solusi lain, sayangnya sangat bergantung pada OS, adalah pemetaan memori file. Manfaat umumnya mencakup kinerja membaca, dan penggunaan memori yang berkurang karena tampilan aplikasi dan cache file sistem operasi sebenarnya dapat berbagi memori fisik.
Kode POSIX akan terlihat seperti ini:
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 di sisi lain sedikit lebih rumit, dan sayangnya saya tidak memiliki kompiler di depan saya untuk diuji, tetapi fungsinya disediakan oleh CreateFileMapping() dan MapViewOfFile().
Jangan lupa untuk memeriksa nilai kembali dari panggilan sistem tersebut!
Toby Speight
3
harus menggunakan off_t daripada int saat memanggil lseek ().
ivan.ukr
1
Perhatikan bahwa jika tujuannya adalah untuk menangkap konten file secara stabil di memori pada saat tertentu, solusi ini harus dihindari, kecuali Anda yakin bahwa file yang sedang dibaca ke memori tidak akan diubah oleh proses lain selama interval dimana peta akan digunakan. Lihat posting ini untuk informasi lebih lanjut.
pengguna001
13
Jika "membaca isinya menjadi string" berarti file tidak berisi karakter dengan kode 0, Anda juga dapat menggunakan fungsi getdelim (), yang menerima blok memori dan mengalokasikannya kembali jika perlu, atau hanya mengalokasikan seluruh buffer untuk Anda, dan membaca file ke dalamnya hingga menemukan pembatas atau akhir file yang ditentukan. Cukup berikan '\ 0' sebagai pemisah untuk membaca seluruh file.
Saya pernah menggunakan ini sebelumnya! Ini bekerja dengan sangat baik, dengan asumsi file yang Anda baca adalah teks (tidak berisi \ 0).
Efemien
BAGUS! Menyimpan banyak masalah saat menghirup seluruh file teks. Sekarang jika ada cara yang sangat sederhana serupa untuk membaca aliran file biner hingga EOF tanpa memerlukan karakter pembatas!
anthony
6
Jika filenya adalah teks, dan Anda ingin mendapatkan baris demi baris teks, cara termudah adalah dengan menggunakan fgets ().
char buffer[100];
FILE *fp = fopen("filename", "r"); // do not use "rb"while (fgets(buffer, sizeof(buffer), fp)) {
... do something
}
fclose(fp);
Jika Anda membaca file khusus seperti stdin atau pipa, Anda tidak akan bisa menggunakan fstat untuk mendapatkan ukuran file sebelumnya. Selain itu, jika Anda membaca file biner, widget akan kehilangan informasi ukuran string karena karakter '\ 0' yang disematkan. Cara terbaik untuk membaca file adalah dengan menggunakan read dan realloc:
#include<stdio.h>#include<unistd.h>#include<errno.h>#include<string.h>intmain(){
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);
return0;
}
Ini adalah O (n ^ 2), di mana n adalah panjang file Anda. Semua solusi dengan suara positif lebih banyak dari ini adalah O (n). Harap jangan gunakan solusi ini dalam praktiknya, atau gunakan versi yang dimodifikasi dengan pertumbuhan multiplikatif.
Clark Gaebel
2
realloc () dapat memperpanjang memori yang ada ke ukuran baru tanpa menyalin memori lama ke bagian memori baru yang lebih besar. hanya jika ada panggilan intervening ke malloc () yang akan dibutuhkan untuk memindahkan memori dan membuat solusi ini O (n ^ 2). di sini, tidak ada panggilan ke malloc () yang terjadi di antara panggilan ke realloc () jadi solusinya akan baik-baik saja.
Jake
2
Anda dapat membaca langsung ke buffer "str" (dengan offset yang sesuai), tanpa perlu menyalin dari "buf" perantara. Namun teknik itu umumnya akan mengalokasikan memori yang diperlukan untuk konten file. Juga hati-hati terhadap file biner, printf tidak akan menanganinya dengan benar, dan Anda mungkin tidak ingin mencetak biner!
anthony
4
Catatan: Ini adalah modifikasi dari jawaban yang diterima di atas.
Berikut cara melakukannya, lengkap dengan pengecekan error.
Saya telah menambahkan pemeriksa ukuran untuk berhenti ketika file lebih besar dari 1 GiB. Saya melakukan ini karena program menempatkan seluruh file ke dalam string yang mungkin menggunakan terlalu banyak ram dan membuat komputer crash. Namun, jika Anda tidak peduli, Anda dapat menghapusnya dari kode.
#include<stdio.h>#include<stdlib.h>#define FILE_OK 0#define FILE_NOT_EXIST 1#define FILE_TO_LARGE 2#define FILE_READ_ERROR 3char * c_read_file(constchar * f_name, int * err, size_t * f_size){
char * buffer;
size_t length;
FILE * f = fopen(f_name, "rb");
size_t read_length;
if (f) {
fseek(f, 0, SEEK_END);
length = ftell(f);
fseek(f, 0, SEEK_SET);
// 1 GiB; best not to load a whole large file in one stringif (length > 1073741824) {
*err = FILE_TO_LARGE;
returnNULL;
}
buffer = (char *)malloc(length + 1);
if (length) {
read_length = fread(buffer, 1, length, f);
if (length != read_length) {
free(buffer);
*err = FILE_READ_ERROR;
returnNULL;
}
}
fclose(f);
*err = FILE_OK;
buffer[length] = '\0';
*f_size = length;
}
else {
*err = FILE_NOT_EXIST;
returnNULL;
}
return buffer;
}
Dan untuk memeriksa kesalahan:
int err;
size_t f_size;
char * f_data;
f_data = c_read_file("test.txt", &err, &f_size);
if (err) {
// process error
}
else {
// process datafree(f_data);
}
Hanya satu pertanyaan: bufferAnda dialokasikan dengan malloc(length +1), tidak dibebaskan. Apakah itu sesuatu yang harus dilakukan oleh konsumen metode ini, atau tidak ada kebutuhan untuk free()memori yang dialokasikan?
Pablosproject
jika kesalahan belum terjadi, gratis (f_data); harus dipanggil. terima kasih telah menunjukkan hal itu
Joe Cool
2
Jika Anda menggunakan glib, maka Anda dapat menggunakan g_file_get_contents ;
Ini bukan kode C. Pertanyaannya tidak diberi tag sebagai C ++.
Gerhardh
@Gerhardh Respon cepat untuk pertanyaan sembilan tahun lalu ketika saya mengedit! Meskipun bagian fungsinya murni C, saya minta maaf atas jawaban saya yang tidak mau dijalankan-di-c.
BaiJiFeiLong
Pertanyaan kuno ini terdaftar di bagian atas pertanyaan aktif. Saya tidak mencarinya.
Gerhardh
1
Kode ini membocorkan memori, jangan lupa untuk membebaskan memori malloc'd Anda :)
ericcurtin
1
// 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 endsize_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 streamreturn shaderSource;
}
Ini adalah solusi yang cukup kasar karena tidak ada yang diperiksa terhadap null.
Ini hanya dengan file berbasis disk. Ini akan gagal untuk pipa bernama, input standar, atau aliran jaringan.
anthony
Ha, juga kenapa aku datang kesini! Tapi saya pikir Anda perlu mengakhiri string dengan null, atau mengembalikan panjang yang glShaderSourcesecara opsional mengambil.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
0
Saya akan menambahkan versi saya sendiri, berdasarkan jawaban di sini, hanya untuk referensi. Kode saya mempertimbangkan sizeof (char) dan menambahkan beberapa komentar padanya.
// Open the file in read mode.
FILE *file = fopen(file_name, "r");
// Check if there was an error.if (file == NULL) {
fprintf(stderr, "Error: Can't open file '%s'.", file_name);
exit(EXIT_FAILURE);
}
// Get the file length
fseek(file, 0, SEEK_END);
long length = ftell(file);
fseek(file, 0, SEEK_SET);
// Create the string for the file contents.char *buffer = malloc(sizeof(char) * (length + 1));
buffer[length] = '\0';
// Set the contents of the string.
fread(buffer, sizeof(char), length, file);
// Close the file.
fclose(file);
// Do something with the data.// ...// Free the allocated string space.free(buffer);
Harap jangan mengalokasikan semua memori yang menurut Anda akan Anda perlukan di muka. Ini adalah contoh sempurna dari desain yang buruk. Anda harus mengalokasikan memori saat digunakan kapan pun memungkinkan. Akan menjadi desain yang bagus jika Anda mengharapkan file tersebut sepanjang 10.000 byte, program Anda tidak dapat menangani file dengan ukuran lain, dan Anda tetap memeriksa ukuran dan kesalahannya, tetapi bukan itu yang terjadi di sini. Anda benar-benar harus mempelajari cara membuat kode C dengan benar.
string s = File.ReadAllText(filename);
. Bagaimana itu bisa lebih sederhana dan lebih rentan kesalahan?Jawaban:
Saya cenderung hanya memuat seluruh buffer sebagai potongan memori mentah ke dalam memori dan melakukan parsing sendiri. Dengan cara itu saya memiliki kendali terbaik atas apa yang dilakukan lib standar pada berbagai platform.
Ini adalah rintisan yang saya gunakan untuk ini. Anda mungkin juga ingin memeriksa kode kesalahan untuk fseek, ftell dan fread. (dihilangkan untuk kejelasan).
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... }
sumber
fread
tidak menghentikan nol string Anda. Hal ini dapat menyebabkan masalah.buffer = malloc (length + 1);
dan menambahkan setelah fclose:buffer[length] = '\0';
(divalidasi oleh Valgrind)Solusi lain, sayangnya sangat bergantung pada OS, adalah pemetaan memori file. Manfaat umumnya mencakup kinerja membaca, dan penggunaan memori yang berkurang karena tampilan aplikasi dan cache file sistem operasi sebenarnya dapat berbagi memori fisik.
Kode POSIX akan terlihat seperti ini:
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 di sisi lain sedikit lebih rumit, dan sayangnya saya tidak memiliki kompiler di depan saya untuk diuji, tetapi fungsinya disediakan oleh
CreateFileMapping()
danMapViewOfFile()
.sumber
Jika "membaca isinya menjadi string" berarti file tidak berisi karakter dengan kode 0, Anda juga dapat menggunakan fungsi getdelim (), yang menerima blok memori dan mengalokasikannya kembali jika perlu, atau hanya mengalokasikan seluruh buffer untuk Anda, dan membaca file ke dalamnya hingga menemukan pembatas atau akhir file yang ditentukan. Cukup berikan '\ 0' sebagai pemisah untuk membaca seluruh file.
Fungsi ini tersedia di GNU C Library, http://www.gnu.org/software/libc/manual/html_mono/libc.html#index-getdelim-994
Kode contoh mungkin terlihat sesederhana
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 */
sumber
Jika filenya adalah teks, dan Anda ingin mendapatkan baris demi baris teks, cara termudah adalah dengan menggunakan fgets ().
char buffer[100]; FILE *fp = fopen("filename", "r"); // do not use "rb" while (fgets(buffer, sizeof(buffer), fp)) { ... do something } fclose(fp);
sumber
Jika Anda membaca file khusus seperti stdin atau pipa, Anda tidak akan bisa menggunakan fstat untuk mendapatkan ukuran file sebelumnya. Selain itu, jika Anda membaca file biner, widget akan kehilangan informasi ukuran string karena karakter '\ 0' yang disematkan. Cara terbaik untuk membaca file adalah dengan menggunakan read dan 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; }
sumber
Catatan: Ini adalah modifikasi dari jawaban yang diterima di atas.
Berikut cara melakukannya, lengkap dengan pengecekan error.
Saya telah menambahkan pemeriksa ukuran untuk berhenti ketika file lebih besar dari 1 GiB. Saya melakukan ini karena program menempatkan seluruh file ke dalam string yang mungkin menggunakan terlalu banyak ram dan membuat komputer crash. Namun, jika Anda tidak peduli, Anda dapat menghapusnya dari kode.
#include <stdio.h> #include <stdlib.h> #define FILE_OK 0 #define FILE_NOT_EXIST 1 #define FILE_TO_LARGE 2 #define FILE_READ_ERROR 3 char * c_read_file(const char * f_name, int * err, size_t * f_size) { char * buffer; size_t length; FILE * f = fopen(f_name, "rb"); size_t read_length; if (f) { fseek(f, 0, SEEK_END); length = ftell(f); fseek(f, 0, SEEK_SET); // 1 GiB; best not to load a whole large file in one string if (length > 1073741824) { *err = FILE_TO_LARGE; return NULL; } buffer = (char *)malloc(length + 1); if (length) { read_length = fread(buffer, 1, length, f); if (length != read_length) { free(buffer); *err = FILE_READ_ERROR; return NULL; } } fclose(f); *err = FILE_OK; buffer[length] = '\0'; *f_size = length; } else { *err = FILE_NOT_EXIST; return NULL; } return buffer; }
Dan untuk memeriksa kesalahan:
int err; size_t f_size; char * f_data; f_data = c_read_file("test.txt", &err, &f_size); if (err) { // process error } else { // process data free(f_data); }
sumber
buffer
Anda dialokasikan denganmalloc(length +1)
, tidak dibebaskan. Apakah itu sesuatu yang harus dilakukan oleh konsumen metode ini, atau tidak ada kebutuhan untukfree()
memori yang dialokasikan?Jika Anda menggunakan
glib
, maka Anda dapat menggunakan 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); } }
sumber
Baru saja diubah dari jawaban yang diterima di atas.
#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); }
sumber
// 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; }
Ini adalah solusi yang cukup kasar karena tidak ada yang diperiksa terhadap null.
sumber
glShaderSource
secara opsional mengambil.Saya akan menambahkan versi saya sendiri, berdasarkan jawaban di sini, hanya untuk referensi. Kode saya mempertimbangkan sizeof (char) dan menambahkan beberapa komentar padanya.
// Open the file in read mode. FILE *file = fopen(file_name, "r"); // Check if there was an error. if (file == NULL) { fprintf(stderr, "Error: Can't open file '%s'.", file_name); exit(EXIT_FAILURE); } // Get the file length fseek(file, 0, SEEK_END); long length = ftell(file); fseek(file, 0, SEEK_SET); // Create the string for the file contents. char *buffer = malloc(sizeof(char) * (length + 1)); buffer[length] = '\0'; // Set the contents of the string. fread(buffer, sizeof(char), length, file); // Close the file. fclose(file); // Do something with the data. // ... // Free the allocated string space. free(buffer);
sumber
mudah dan rapi (dengan asumsi konten di file kurang dari 10.000):
void read_whole_file(char fileName[1000], char buffer[10000]) { FILE * file = fopen(fileName, "r"); if(file == NULL) { puts("File not found"); exit(1); } char c; int idx=0; while (fscanf(file , "%c" ,&c) == 1) { buffer[idx] = c; idx++; } buffer[idx] = 0; }
sumber