c++ - समय - हम पढ़ाई क्यों करते हैं




सी++ में एक std:: स्ट्रिंग में पूरी फ़ाइल को पढ़ने का सबसे अच्छा तरीका क्या है? (8)

मैं एक std::string में फ़ाइल कैसे std::string , यानी, पूरी फ़ाइल को एक बार में पढ़ा जाए?

कॉलर द्वारा टेक्स्ट या बाइनरी मोड निर्दिष्ट किया जाना चाहिए। समाधान मानक-अनुपालन, पोर्टेबल और कुशल होना चाहिए। यह स्ट्रिंग के डेटा की अनावश्यक रूप से प्रतिलिपि नहीं लेनी चाहिए, और स्ट्रिंग को पढ़ने के दौरान इसे स्मृति के पुनर्विक्रय से बचना चाहिए।

ऐसा करने का एक तरीका const_cast<char*>() को स्टेट करना होगा, std::string 's const_cast<char*>() 'ed data() में std::string और fread() आकार बदलें। इसके लिए std::string के डेटा को संगत होने की आवश्यकता होती है जिसे मानक द्वारा आवश्यक नहीं है, लेकिन यह सभी ज्ञात कार्यान्वयन के लिए मामला प्रतीत होता है। क्या बुरा है, अगर फ़ाइल पाठ मोड में पढ़ी जाती है, तो std::string का आकार फ़ाइल के आकार के बराबर नहीं हो सकता है।

std::ifstream 's rdbuf() का उपयोग std::ostringstream और वहां से std::string में एक पूरी तरह से सही, मानक-अनुपालन और पोर्टेबल समाधान का निर्माण किया जा सकता है। हालांकि, यह स्ट्रिंग डेटा और / या यादृच्छिक रूप से स्मृति को पुन: आवंटित कर सकता है। क्या सभी प्रासंगिक मानक लाइब्रेरी कार्यान्वयन सभी अनावश्यक ओवरहेड से बचने के लिए पर्याप्त स्मार्ट हैं? क्या ऐसा करने का कोई और तरीका है? क्या मुझे कुछ छुपा बूस्ट फ़ंक्शन याद आया जो पहले से ही वांछित कार्यक्षमता प्रदान करता है?

कृपया अपना सुझाव दिखाएं कि इसे कैसे कार्यान्वित किया जाए।

void slurp(std::string& data, bool is_binary)

उपर्युक्त चर्चा को ध्यान में रखते हुए।


आप 'std :: getline' फ़ंक्शन का उपयोग कर सकते हैं, और 'eof' को डिलीमीटर के रूप में निर्दिष्ट कर सकते हैं। परिणामस्वरूप कोड थोड़ा अस्पष्ट है हालांकि:

std::string data;
std::ifstream in( "test.txt" );
std::getline( in, data, std::string::traits_type::to_char_type( 
                  std::string::traits_type::eof() ) );

उपयोग

#include <iostream>
#include <sstream>
#include <fstream>

int main()
{
  std::ifstream input("file.txt");
  std::stringstream sstr;

  while(input >> sstr.rdbuf());

  std::cout << sstr.str() << std::endl;
}

या कुछ बहुत करीब है। मेरे पास डबल-चेक करने के लिए एक stdlib संदर्भ खुला नहीं है।

हाँ, मैं समझता हूं कि मैंने slurp फ़ंक्शन को नहीं पूछा था।


और सबसे तेज़ (जो मुझे पता है, स्मृति-मैप की गई फ़ाइलों को छूट):

string str(static_cast<stringstream const&>(stringstream() << in.rdbuf()).str());

स्ट्रिंग स्ट्रीम के लिए अतिरिक्त हेडर <sstream> आवश्यकता है। ( operator << बाद static_cast आवश्यक है operator << एक सादे पुराने ostream& हम जानते हैं कि वास्तव में यह एक stringstream& इसलिए कास्ट सुरक्षित है।)

अस्थायी को एक चर में ले जाने, एकाधिक लाइनों में विभाजित, हमें एक और अधिक पठनीय कोड मिलता है:

string slurp(ifstream& in) {
    stringstream sstr;
    sstr << in.rdbuf();
    return sstr.str();
}

या, एक बार फिर एक पंक्ति में:

string slurp(ifstream& in) {
    return static_cast<stringstream const&>(stringstream() << in.rdbuf()).str();
}

कभी भी std :: string's const char * buffer में लिखें। कभी नहीं! ऐसा करना एक बड़ी गलती है।

अपने std :: स्ट्रिंग में पूरी स्ट्रिंग के लिए रिजर्व () स्थान, उचित आकार की अपनी फ़ाइल से एक बफर में भाग पढ़ें, और इसे संलग्न करें ()। भाग को कितना बड़ा होना चाहिए आपके इनपुट फ़ाइल आकार पर निर्भर करता है। मुझे पूरा यकीन है कि अन्य सभी पोर्टेबल और एसटीएल-अनुरूप तंत्र एक ही काम करेंगे (फिर भी सुंदर दिख सकते हैं)।


यदि आपके पास सी ++ 17 (std :: फाइल सिस्टम) है, तो इस तरह से भी है (जो फ़ाइल के आकार को std::filesystem::file_size माध्यम से seekg और tellg बजाय प्राप्त tellg ):

#include <filesystem>
#include <fstream>
#include <string>

namespace fs = std::filesystem;

std::string readFile(fs::path path)
{
    // Open the stream to 'lock' the file.
    std::ifstream f{ path };

    // Obtain the size of the file.
    const auto sz = fs::file_size(path);

    // Create a buffer.
    std::string result(sz, ' ');

    // Read the whole file into the buffer.
    f.read(result.data(), sz);

    return result;
}

नोट : यदि आपकी मानक लाइब्रेरी अभी तक पूरी तरह से C ++ 17 का समर्थन नहीं करती है तो आपको <experimental/filesystem> और std::experimental::filesystem का उपयोग करने की आवश्यकता हो सकती है। आपको result.data() को &result[0] को प्रतिस्थापित करने की आवश्यकता हो सकती है यदि यह गैर-कॉन्स std :: basic_string डेटा का समर्थन नहीं करता है।


यह समाधान rdbuf () - आधारित विधि में त्रुटि जांच जोड़ता है।

std::string file_to_string(const std::string& file_name)
{
    std::ifstream file_stream{file_name};

    if (file_stream.fail())
    {
        // Error opening file.
    }

    std::ostringstream str_stream{};
    file_stream >> str_stream.rdbuf();  // NOT str_stream << file_stream.rdbuf()

    if (file_stream.fail() && !file_stream.eof())
    {
        // Error reading file.
    }

    return str_stream.str();
}

मैं यह जवाब जोड़ रहा हूं क्योंकि मूल विधि में त्रुटि-जांच जोड़ना उतना छोटा नहीं है जितना आप उम्मीद करेंगे। मूल विधि स्ट्रिंगस्ट्रीम के सम्मिलन ऑपरेटर ( str_stream << file_stream.rdbuf() ) का उपयोग करती है। समस्या यह है कि जब कोई वर्ण डाला नहीं जाता है तो यह स्ट्रिंगस्ट्रीम की विफलता सेट करता है। यह किसी त्रुटि के कारण हो सकता है या यह फ़ाइल खाली होने के कारण हो सकता है। यदि आप असफलता का निरीक्षण करके असफलताओं की जांच करते हैं, तो जब आप एक खाली फ़ाइल पढ़ते हैं तो आपको झूठी सकारात्मक सामना करना पड़ेगा। फ़ाइल खाली होने के कारण किसी भी अक्षर को सम्मिलित करने के लिए किसी भी अक्षर और "विफलता" को सम्मिलित करने में वैध विफलता को आप कैसे असंतुलित करते हैं?

आप स्पष्ट रूप से एक खाली फ़ाइल की जांच करने के लिए सोच सकते हैं, लेकिन यह अधिक कोड और संबंधित त्रुटि जांच है।

विफलता की स्थिति की जांच str_stream.fail() && !str_stream.eof() काम नहीं करती है, क्योंकि सम्मिलन ऑपरेशन eofbit (ostringstream पर और न ही ifstream पर) सेट नहीं करता है।

तो, समाधान ऑपरेशन को बदलने के लिए है। Ostringstream के सम्मिलन ऑपरेटर (<<) का उपयोग करने के बजाय, ifstream के निष्कर्षण ऑपरेटर (>>) का उपयोग करें, जो eofbit सेट करता है। फिर file_stream.fail() && !file_stream.eof() स्थिति file_stream.fail() && !file_stream.eof()

महत्वपूर्ण बात यह है कि, जब file_stream >> str_stream.rdbuf() को वैध विफलता का सामना करना पड़ता है, तो इसे कभी भी ईफिट सेट नहीं करना चाहिए (विनिर्देश की मेरी समझ के अनुसार)। इसका मतलब है कि उपरोक्त जांच वैध विफलताओं का पता लगाने के लिए पर्याप्त है।


इस उत्तर को एक समान प्रश्न पर देखें।

आपकी सुविधा के लिए, मैं सीटीटी के समाधान को दोबारा पोस्ट कर रहा हूं:

string readFile2(const string &fileName)
{
    ifstream ifs(fileName.c_str(), ios::in | ios::binary | ios::ate);

    ifstream::pos_type fileSize = ifs.tellg();
    ifs.seekg(0, ios::beg);

    vector<char> bytes(fileSize);
    ifs.read(bytes.data(), fileSize);

    return string(bytes.data(), fileSize);
}

मोबी डिक (1.3 एम) के पाठ के खिलाफ 100 रनों का औसत लेने के दौरान, इस समाधान के परिणामस्वरूप यहां दिए गए अन्य उत्तरों की तुलना में इस समाधान के लगभग 20% तेज निष्पादन समय हुआ। पोर्टेबल सी ++ समाधान के लिए बुरा नहीं है, मैं फ़ाइल को mmaping के परिणाम देखना चाहता हूं;)


tellg() का उपयोग कर प्रतिक्रियाओं पर सीधे टिप्पणी करने के लिए मेरे पास पर्याप्त प्रतिष्ठा नहीं है।

कृपया ध्यान रखें कि tellg() त्रुटि पर -1 लौटा सकता है। यदि आप आवंटन पैरामीटर के रूप में tellg() के परिणाम को पारित कर रहे हैं, तो आपको पहले परिणाम की जांच करनी चाहिए।

समस्या का एक उदाहरण:

...
std::streamsize size = file.tellg();
std::vector<char> buffer(size);
...

उपर्युक्त उदाहरण में, अगर tellg() एक त्रुटि का सामना करता है तो यह -1 लौटाएगा। हस्ताक्षर के बीच लागू कास्टिंग (यानी tellg() का परिणाम) और हस्ताक्षरित (यानी vector<char> कन्स्ट्रक्टर के लिए vector<char> ) परिणामस्वरूप आपके वेक्टर गलती से बाइट्स की एक बड़ी संख्या आवंटित करेंगे। (शायद 42 9 4 9 672 9 5 बाइट्स, या 4 जीबी।)

उपरोक्त के लिए खाते में paxos1977 के उत्तर को संशोधित करना:

string readFile2(const string &fileName)
{
    ifstream ifs(fileName.c_str(), ios::in | ios::binary | ios::ate);

    ifstream::pos_type fileSize = ifs.tellg();
    if (fileSize < 0)                             <--- ADDED
        return std::string();                     <--- ADDED

    ifs.seekg(0, ios::beg);

    vector<char> bytes(fileSize);
    ifs.read(&bytes[0], fileSize);

    return string(&bytes[0], fileSize);
}




file-io