Bagaimana cara membaca file menjadi std::string
, yaitu, membaca seluruh file sekaligus?
Mode teks atau biner harus ditentukan oleh pemanggil. Solusinya harus sesuai standar, portabel dan efisien. Seharusnya tidak perlu menyalin data string, dan harus menghindari realokasi memori saat membaca string.
Salah satu cara untuk melakukan ini adalah dengan stat filesize, mengubah ukuran std::string
dan fread()
ke std::string
's const_cast<char*>()
' ed data()
. Ini membutuhkan std::string
data yang berdekatan yang tidak diperlukan oleh standar, tetapi tampaknya menjadi kasus untuk semua implementasi yang diketahui. Yang lebih parah, jika file dibaca dalam mode teks, std::string
ukurannya mungkin tidak sama dengan ukuran file.
Solusi yang sepenuhnya benar, sesuai standar dan portabel dapat dibangun menggunakan std::ifstream
's rdbuf()
menjadi std::ostringstream
dan dari sana menjadi a std::string
. Namun, ini dapat menyalin data string dan / atau mengalokasikan kembali memori yang tidak perlu.
- Apakah semua implementasi perpustakaan standar yang relevan cukup pintar untuk menghindari semua overhead yang tidak perlu?
- Apakah ada cara lain untuk melakukannya?
- Apakah saya melewatkan beberapa fungsi Peningkatan tersembunyi yang sudah menyediakan fungsi yang diinginkan?
void slurp(std::string& data, bool is_binary)
rdbuf
(yang ada di jawaban yang diterima) bukan yang tercepat,read
adalah.Jawaban:
Salah satu caranya adalah menyiram buffer aliran ke dalam aliran memori yang terpisah, dan kemudian mengubahnya menjadi
std::string
:Ini sangat ringkas. Namun, seperti disebutkan dalam pertanyaan ini melakukan salinan yang berlebihan dan sayangnya pada dasarnya tidak ada cara untuk menghilangkan salinan ini.
Satu-satunya solusi nyata yang menghindari salinan berlebihan adalah dengan melakukan pembacaan secara manual dalam satu lingkaran, sayangnya. Karena C ++ sekarang telah menjamin string yang berdekatan, orang dapat menulis yang berikut ini (≥C ++ 14):
sumber
string
. Yaitu membutuhkan memori dua kali lebih banyak dari beberapa opsi lainnya. (Tidak ada cara untuk memindahkan buffer). Untuk file besar ini akan menjadi penalti yang signifikan, bahkan mungkin menyebabkan kegagalan alokasi.Lihat jawaban ini pada pertanyaan serupa.
Demi kenyamanan Anda, saya memposting ulang solusi CTT:
Solusi ini menghasilkan sekitar 20% waktu eksekusi yang lebih cepat daripada jawaban lain yang disajikan di sini, ketika mengambil rata-rata 100 run terhadap teks Moby Dick (1.3M). Tidak buruk untuk solusi C ++ portabel, saya ingin melihat hasil dari mmap'ing file;)
sumber
ifs.seekg(0, ios::end)
sebelumnyatellg
? tepat setelah membuka pointer membaca file di awal dantellg
mengembalikan nolnullptr
oleh&bytes[0]
ios::ate
, jadi saya pikir versi dengan eksplisit pindah ke akhir akan lebih mudah dibacaVarian terpendek: Live On Coliru
Itu membutuhkan header
<iterator>
.Ada beberapa laporan bahwa metode ini lebih lambat daripada mengalokasikan string dan menggunakan
std::istream::read
. Namun, pada kompiler modern dengan optimisasi yang diaktifkan ini tampaknya tidak lagi menjadi masalah, meskipun kinerja relatif dari berbagai metode tampaknya sangat bergantung pada kompiler.sumber
Menggunakan
atau sesuatu yang sangat dekat. Saya tidak memiliki referensi stdlib terbuka untuk memeriksa ulang sendiri.
Ya, saya mengerti saya tidak menulis
slurp
fungsi seperti yang diminta.sumber
operator>>
dibaca menjadistd::basic_streambuf
, ia akan mengkonsumsi (apa yang tersisa) aliran input, sehingga loop tidak perlu.Jika Anda memiliki C ++ 17 (std :: filesystem), ada juga cara ini (yang mendapatkan ukuran file melalui
std::filesystem::file_size
alih-alihseekg
dantellg
):Catatan : Anda mungkin perlu menggunakan
<experimental/filesystem>
danstd::experimental::filesystem
jika perpustakaan standar Anda belum sepenuhnya mendukung C ++ 17. Anda mungkin juga perlu menggantiresult.data()
dengan&result[0]
jika tidak mendukung data st-:: basic_string non-const .sumber
boost::filesystem
Anda juga dapat menggunakan boost jika Anda tidak memiliki c ++ 17Saya tidak memiliki reputasi yang cukup untuk mengomentari secara langsung tentang tanggapan menggunakan
tellg()
.Perlu diketahui bahwa
tellg()
dapat mengembalikan -1 pada kesalahan. Jika Anda melewatkan hasiltellg()
sebagai parameter alokasi, Anda harus memeriksa hasilnya terlebih dahulu.Contoh masalah:
Dalam contoh di atas, jika
tellg()
menemukan kesalahan, ia akan mengembalikan -1. Casting implisit antara ditandatangani (yaitu hasiltellg()
) dan unsigned (yaitu arg kevector<char>
konstruktor) akan menghasilkan vektor Anda keliru mengalokasikan sangat sejumlah besar byte. (Mungkin 4294967295 byte, atau 4GB.)Memodifikasi jawaban paxos1977 untuk akun di atas:
sumber
Solusi ini menambahkan pengecekan kesalahan ke metode berbasis rdbuf ().
Saya menambahkan jawaban ini karena menambahkan pengecekan kesalahan pada metode asli tidak sepele seperti yang Anda harapkan. Metode asli menggunakan operator penyisipan stringstream (
str_stream << file_stream.rdbuf()
). Masalahnya adalah ini menetapkan failbit stringstream ketika tidak ada karakter yang dimasukkan. Itu bisa karena kesalahan atau bisa juga karena file sedang kosong. Jika Anda memeriksa kegagalan dengan memeriksa failbit, Anda akan menemukan false positive ketika Anda membaca file kosong. Bagaimana Anda membedakan kegagalan yang sah untuk menyisipkan karakter apa pun dan "kegagalan" untuk menyisipkan karakter apa pun karena file tersebut kosong?Anda mungkin berpikir untuk secara eksplisit memeriksa file kosong, tapi itu lebih banyak kode dan pengecekan kesalahan yang terkait.
Memeriksa kondisi kegagalan
str_stream.fail() && !str_stream.eof()
tidak berfungsi, karena operasi penyisipan tidak mengatur eofbit (pada ostringstream atau ifstream).Jadi, solusinya adalah mengubah operasi. Alih-alih menggunakan operator penyisipan ostringstream (<<), gunakan operator ekstraksi ifstream (>>), yang memang mengatur eofbit. Kemudian periksa kondisi kegagalan
file_stream.fail() && !file_stream.eof()
.Yang penting, ketika
file_stream >> str_stream.rdbuf()
menemukan kegagalan yang sah, seharusnya tidak pernah menetapkan eofbit (sesuai dengan pemahaman saya tentang spesifikasi). Itu berarti cek di atas cukup untuk mendeteksi kegagalan yang sah.sumber
Sesuatu seperti ini seharusnya tidak terlalu buruk:
Keuntungannya di sini adalah kita melakukan cadangan terlebih dahulu sehingga kita tidak perlu menumbuhkan talinya saat kita membaca banyak hal. Kerugiannya adalah kita melakukannya dengan char. Versi yang lebih cerdas dapat mengambil seluruh baca buf dan kemudian memanggil underflow.
sumber
Ini adalah versi yang menggunakan pustaka sistem file baru dengan pemeriksaan kesalahan yang cukup kuat:
sumber
infile.open
juga dapat menerimastd::string
tanpa mengonversi dengan.c_str()
filepath
bukanstd::string
, itu astd::filesystem::path
. Ternyatastd::ifstream::open
bisa menerima salah satu dari itu juga.std::filesystem::path
secara implisit dapat dikonversi kestd::string
::open
fungsi anggota padastd::ifstream
yang menerimastd::filesystem::path
beroperasi seolah-olah::c_str()
metode dipanggil di jalan. Yang mendasari::value_type
path berada dichar
bawah POSIX.Anda dapat menggunakan fungsi 'std :: getline', dan tentukan 'eof' sebagai pembatas. Kode yang dihasilkan agak tidak jelas:
sumber
Jangan pernah menulis ke buffer char * std :: string's str. Tidak akan pernah! Melakukan hal itu adalah kesalahan besar.
Cadangan () ruang untuk seluruh string di std :: string Anda, baca potongan dari file Anda dengan ukuran yang wajar ke dalam buffer, dan tambahkan () itu. Seberapa besar potongan harus tergantung pada ukuran file input Anda. Saya cukup yakin semua mekanisme portabel dan STL lainnya akan melakukan hal yang sama (namun mungkin terlihat lebih cantik).
sumber
std::string
buffer; dan saya percaya bahwa itu berfungsi dengan benar pada semua implementasi aktual sebelum itustd::string::data()
metode non-const untuk memodifikasi buffer string secara langsung tanpa menggunakan trik seperti&str[0]
.pemakaian:
sumber
Fungsi yang diperbarui yang didasarkan pada solusi CTT:
Ada dua perbedaan penting:
tellg()
tidak dijamin untuk mengembalikan offset dalam byte sejak awal file. Sebagai gantinya, seperti yang ditunjukkan Puzomor Croatia, itu lebih merupakan token yang dapat digunakan dalam panggilan fstream.gcount()
Namun tidak mengembalikan jumlah byte terformat lalu diekstrak. Karena itu kami membuka file, mengekstrak dan membuang semua isinya denganignore()
untuk mendapatkan ukuran file, dan membangun string keluaran berdasarkan itu.Kedua, kami menghindari harus menyalin data file dari a
std::vector<char>
ke astd::string
dengan menulis ke string secara langsung.Dalam hal kinerja, ini harus menjadi yang tercepat mutlak, mengalokasikan string berukuran yang sesuai sebelumnya dan menelepon
read()
sekali. Sebagai fakta yang menarik, menggunakanignore()
dancountg()
bukannyaate
dantellg()
pada gcc mengkompilasi ke hal yang hampir sama , sedikit demi sedikit.sumber
ifs.seekg(0)
alih-alihifs.clear()
(kemudian berfungsi).sumber