อ่านไฟล์ทีละบรรทัดโดยใช้ ifstream ใน C ++


612

เนื้อหาของ file.txt คือ:

5 3
6 4
7 1
10 5
11 6
12 3
12 4

5 3คู่ประสานงานอยู่ที่ไหน ฉันจะประมวลผลข้อมูลนี้ทีละบรรทัดใน C ++ ได้อย่างไร

ฉันสามารถรับบรรทัดแรก แต่ฉันจะรับบรรทัดถัดไปของไฟล์ได้อย่างไร

ifstream myfile;
myfile.open ("text.txt");

คำตอบ:


916

ก่อนอื่นให้สร้างifstream:

#include <fstream>
std::ifstream infile("thefile.txt");

สองวิธีมาตรฐานคือ:

  1. สมมติว่าทุกบรรทัดประกอบด้วยตัวเลขสองตัวและอ่านโทเค็นด้วยโทเค็น:

    int a, b;
    while (infile >> a >> b)
    {
        // process pair (a,b)
    }
    
  2. การแยกตามบรรทัดโดยใช้สตริงสตรีม:

    #include <sstream>
    #include <string>
    
    std::string line;
    while (std::getline(infile, line))
    {
        std::istringstream iss(line);
        int a, b;
        if (!(iss >> a >> b)) { break; } // error
    
        // process pair (a,b)
    }
    

คุณไม่ควรผสม (1) และ (2) เนื่องจากการแจงโดยใช้โทเค็นไม่ได้ขึ้นบรรทัดใหม่ดังนั้นคุณอาจจบด้วยบรรทัดว่างเปล่าหากคุณใช้getline()หลังจากการแยกโทเค็นทำให้คุณจบ บรรทัดแล้ว


1
@EdwardKarak: ฉันไม่เข้าใจความหมายของ "เครื่องหมายจุลภาคเป็นโทเค็น" เครื่องหมายจุลภาคไม่ได้แทนจำนวนเต็ม
Kerrek SB

8
OP ใช้ช่องว่างเพื่อกำหนดจำนวนเต็มสองจำนวน ฉันอยากจะรู้ว่าในขณะที่ (infile >> a >> b) จะทำงานได้ไหมถ้า OP ใช้เครื่องหมายจุลภาคเป็นตัวคั่นเพราะนั่นเป็นสถานการณ์ในโปรแกรมของฉัน
Edward Karak

30
@EdwardKarak: อาดังนั้นเมื่อคุณพูดว่า "token" คุณหมายถึง "delimiter" ขวา. ด้วยเครื่องหมายจุลภาคคุณจะพูดว่า:int a, b; char c; while ((infile >> a >> c >> b) && (c == ','))
Kerrek SB

11
@ KerrekSB: หืม ฉันผิดไป. ฉันไม่รู้ว่าจะทำเช่นนั้นได้ ฉันอาจมีรหัสของตัวเองเพื่อเขียนใหม่
Mark H

4
สำหรับคำอธิบายของการwhile(getline(f, line)) { }สร้างและเกี่ยวกับการจัดการข้อผิดพลาดโปรดดูที่บทความ (ของฉัน) นี้: gehrcke.de/2011/06/… (ฉันคิดว่าฉันไม่จำเป็นต้องมีมโนธรรมที่ไม่ดีโพสต์ที่นี่ วันที่คำตอบนี้)
ดร. Jan-Philip Gehrcke

175

ใช้ifstreamเพื่ออ่านข้อมูลจากไฟล์:

std::ifstream input( "filename.ext" );

หากคุณจำเป็นต้องอ่านทีละบรรทัดให้ทำสิ่งนี้:

for( std::string line; getline( input, line ); )
{
    ...for each line in input...
}

แต่คุณอาจต้องแยกคู่พิกัด:

int x, y;
input >> x >> y;

ปรับปรุง:

ในรหัสของคุณที่คุณใช้ofstream myfile;แต่oในยืนofstream outputหากคุณต้องการที่จะอ่านจากแฟ้ม (input) ifstreamการใช้งาน fstreamหากคุณต้องการใช้งานทั้งการอ่านและการเขียน


8
โซลูชันของคุณดีขึ้นเล็กน้อย: ตัวแปรบรรทัดของคุณไม่สามารถมองเห็นได้หลังจากอ่านไฟล์ตรงกันข้ามกับโซลูชันที่สองของ Kerrek SB ซึ่งเป็นโซลูชันที่ดีและเรียบง่ายเช่นกัน
DanielTuzes

3
getlineกำลังstring ดูอยู่ดังนั้นอย่าลืม#include <string>
mxmlnkn

55

การอ่านไฟล์ทีละบรรทัดใน C ++ สามารถทำได้หลายวิธี

[เร็ว] วนซ้ำด้วย std :: getline ()

วิธีที่ง่ายที่สุดคือเปิด std :: ifstream และ loop โดยใช้ std :: getline () รหัสสะอาดและง่ายต่อการเข้าใจ

#include <fstream>

std::ifstream file(FILENAME);
if (file.is_open()) {
    std::string line;
    while (std::getline(file, line)) {
        // using printf() in all tests for consistency
        printf("%s", line.c_str());
    }
    file.close();
}

[เร็ว] ใช้ file_description_source ของ Boost

ความเป็นไปได้อีกอย่างหนึ่งคือการใช้ห้องสมุด Boost แต่รหัสจะได้รับรายละเอียดมากขึ้น ประสิทธิภาพค่อนข้างคล้ายกับรหัสข้างต้น (วนกับ std :: getline ())

#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream.hpp>
#include <fcntl.h>

namespace io = boost::iostreams;

void readLineByLineBoost() {
    int fdr = open(FILENAME, O_RDONLY);
    if (fdr >= 0) {
        io::file_descriptor_source fdDevice(fdr, io::file_descriptor_flags::close_handle);
        io::stream <io::file_descriptor_source> in(fdDevice);
        if (fdDevice.is_open()) {
            std::string line;
            while (std::getline(in, line)) {
                // using printf() in all tests for consistency
                printf("%s", line.c_str());
            }
            fdDevice.close();
        }
    }
}

[เร็วที่สุด] ใช้รหัส C

หากประสิทธิภาพมีความสำคัญต่อซอฟต์แวร์ของคุณคุณอาจพิจารณาใช้ภาษา C รหัสนี้อาจเร็วกว่ารุ่น C ++ 4-5 เท่าข้างต้นดูมาตรฐานด้านล่าง

FILE* fp = fopen(FILENAME, "r");
if (fp == NULL)
    exit(EXIT_FAILURE);

char* line = NULL;
size_t len = 0;
while ((getline(&line, &len, fp)) != -1) {
    // using printf() in all tests for consistency
    printf("%s", line);
}
fclose(fp);
if (line)
    free(line);

เกณฑ์มาตรฐาน - อันไหนเร็วกว่ากัน?

ฉันได้ทำการวัดประสิทธิภาพด้วยโค้ดด้านบนและผลลัพธ์นั้นน่าสนใจ ฉันทดสอบโค้ดด้วยไฟล์ ASCII ที่มี 100,000 บรรทัด, 1,000,000 บรรทัดและ 10,000,000 บรรทัดข้อความ ข้อความแต่ละบรรทัดมีค่าเฉลี่ย 10 คำ โปรแกรมจะคอมไพล์ด้วย-O3การออปติไมซ์และเอาท์พุทของมันถูกส่งต่อไป/dev/nullเพื่อเอาตัวแปรเวลาการบันทึกออกจากการวัด สุดท้าย แต่ไม่ท้ายสุดแต่ละโค้ดจะบันทึกแต่ละบรรทัดด้วยprintf()ฟังก์ชันเพื่อความมั่นคง

ผลลัพธ์แสดงเวลา (เป็นมิลลิวินาที) ที่แต่ละส่วนของรหัสเอาไปอ่านไฟล์

ความแตกต่างด้านประสิทธิภาพระหว่างวิธี C ++ สองวิธีนั้นน้อยที่สุดและไม่ควรสร้างความแตกต่างในทางปฏิบัติ ประสิทธิภาพของรหัส C คือสิ่งที่ทำให้เกณฑ์มาตรฐานน่าประทับใจและสามารถเป็นตัวเปลี่ยนเกมในแง่ของความเร็ว

                             10K lines     100K lines     1000K lines
Loop with std::getline()         105ms          894ms          9773ms
Boost code                       106ms          968ms          9561ms
C code                            23ms          243ms          2397ms

ป้อนคำอธิบายรูปภาพที่นี่


1
จะเกิดอะไรขึ้นถ้าคุณลบการซิงโครไนซ์ของ C ++ กับ C บนเอาต์พุตคอนโซล คุณอาจจะวัดเป็นข้อเสียที่รู้จักกันของพฤติกรรมเริ่มต้นของVSstd::cout printf
user4581301

2
ขอขอบคุณที่แจ้งข้อกังวลนี้ ฉันได้ทำการทดสอบซ้ำและประสิทธิภาพยังคงเหมือนเดิม ฉันได้แก้ไขรหัสเพื่อใช้printf()ฟังก์ชั่นในทุกกรณีเพื่อความมั่นคง ฉันได้ลองใช้std::coutในทุกกรณีแล้วและมันก็ไม่ได้ต่างอะไรอย่างแน่นอน ตามที่ฉันได้อธิบายไว้ในข้อความผลลัพธ์ของโปรแกรมจะเป็นไปเพื่อ/dev/nullให้เวลาในการพิมพ์เส้นไม่ถูกวัด
HugoTeixeira

6
Groovy ขอบคุณ สงสัยว่าการชะลอตัวอยู่ที่ไหน
user4581301

4
สวัสดี @HugoTeixeira ฉันรู้ว่านี่เป็นเธรดเก่าฉันพยายามจำลองผลลัพธ์ของคุณและไม่เห็นความแตกต่างที่สำคัญระหว่าง c และ c ++ github.com/simonsso/readfile_benchmarks
Simson

โดยค่าเริ่มต้น, C ++ cstdioในลำธารออกมาจะตรงกับ std::ios_base::sync_with_stdio(false)คุณควรจะได้พยายามกับการตั้งค่า ฉันเดาว่าคุณจะได้รับการแสดงที่ดีขึ้นมาก (ไม่รับประกันว่าจะมีการกำหนดตามการนำไปใช้เมื่อมีการสลับการซิงโครไนซ์)
Fareanor

11

เนื่องจากพิกัดของคุณอยู่รวมกันเป็นคู่ทำไมไม่เขียนโครงสร้างให้พวกเขา?

struct CoordinatePair
{
    int x;
    int y;
};

จากนั้นคุณสามารถเขียนโอเปอเรเตอร์การโอเวอร์โหลดสำหรับ istreams:

std::istream& operator>>(std::istream& is, CoordinatePair& coordinates)
{
    is >> coordinates.x >> coordinates.y;

    return is;
}

จากนั้นคุณสามารถอ่านไฟล์พิกัดตรงไปยังเวกเตอร์ดังนี้:

#include <fstream>
#include <iterator>
#include <vector>

int main()
{
    char filename[] = "coordinates.txt";
    std::vector<CoordinatePair> v;
    std::ifstream ifs(filename);
    if (ifs) {
        std::copy(std::istream_iterator<CoordinatePair>(ifs), 
                std::istream_iterator<CoordinatePair>(),
                std::back_inserter(v));
    }
    else {
        std::cerr << "Couldn't open " << filename << " for reading\n";
    }
    // Now you can work with the contents of v
}

1
จะเกิดอะไรขึ้นเมื่อมันเป็นไปไม่ได้ที่จะอ่านสองintราชสกุลจากกระแสในoperator>>? เราจะทำให้มันทำงานร่วมกับตัวแยกวิเคราะห์ย้อนรอยได้อย่างไร (เช่นเมื่อoperator>>ล้มเหลวให้ย้อนกระแสไปยังตำแหน่งก่อนหน้านี้ว่าคืนค่าเท็จหรืออะไรทำนองนั้น)
fferri

หากไม่สามารถอ่านintโทเค็นสองโทงได้isสตรีมจะประเมินfalseและลูปการอ่านจะสิ้นสุด ณ จุดนั้น คุณสามารถตรวจจับสิ่งนี้ได้operator>>โดยการตรวจสอบค่าส่งคืนของการอ่านแต่ละรายการ is.clear()หากคุณต้องการที่จะย้อนกลับสตรีมคุณจะเรียก
Martin Broadhurst

ในoperator>>นั้นจะถูกต้องมากขึ้นที่จะพูดis >> std::ws >> coordinates.x >> std::ws >> coordinates.y >> std::ws;ตั้งแต่มิฉะนั้นคุณจะสมมติว่าสตรีมอินพุตของคุณอยู่ในโหมดข้ามช่องว่าง
Darko Veberic

7

การขยายคำตอบที่ยอมรับถ้าอินพุตคือ:

1,NYC
2,ABQ
...

คุณจะยังสามารถใช้ตรรกะเดียวกันได้เช่นนี้

#include <fstream>

std::ifstream infile("thefile.txt");
if (infile.is_open()) {
    int number;
    std::string str;
    char c;
    while (infile >> number >> c >> str && c == ',')
        std::cout << number << " " << str << "\n";
}
infile.close();

2

แม้ว่าจะไม่จำเป็นต้องปิดไฟล์ด้วยตนเอง แต่ก็ควรทำเช่นนั้นหากขอบเขตของตัวแปรไฟล์ใหญ่กว่า:

    ifstream infile(szFilePath);

    for (string line = ""; getline(infile, line); )
    {
        //do something with the line
    }

    if(infile.is_open())
        infile.close();

ไม่แน่ใจว่านี่สมควรได้รับการลงคะแนนเสียง OP ขอวิธีในการรับแต่ละบรรทัด คำตอบนี้ทำเช่นนั้นและให้คำแนะนำที่ดีในการทำให้แน่ใจว่าไฟล์ปิดอยู่ สำหรับโปรแกรมอย่างง่ายอาจไม่จำเป็น แต่อย่างน้อยนิสัยที่ดีในการสร้าง อาจปรับปรุงได้โดยการเพิ่มโค้ดสองสามบรรทัดเพื่อประมวลผลแต่ละบรรทัดที่ดึงออกมา แต่โดยรวมแล้วเป็นคำตอบที่ง่ายที่สุดสำหรับคำถาม OPs
Xandor

2

คำตอบนี้มีไว้สำหรับ visual studio 2017 และหากคุณต้องการอ่านจากไฟล์ข้อความตำแหน่งที่สัมพันธ์กับแอปพลิเคชันคอนโซลที่คอมไพล์ของคุณ

ก่อนอื่นให้ใส่เท็กซ์ไฟล์ของคุณ (test.txt ในกรณีนี้) ลงในโฟลเดอร์โซลูชันของคุณ หลังจากรวบรวมไฟล์ข้อความในโฟลเดอร์เดียวกันกับ applicationName.exe

C: \ Users \ "ชื่อผู้ใช้" \ แหล่ง \ Repos \ "solutionName" \ "solutionName"

#include <iostream>
#include <fstream>

using namespace std;
int main()
{
    ifstream inFile;
    // open the file stream
    inFile.open(".\\test.txt");
    // check if opening a file failed
    if (inFile.fail()) {
        cerr << "Error opeing a file" << endl;
        inFile.close();
        exit(1);
    }
    string line;
    while (getline(inFile, line))
    {
        cout << line << endl;
    }
    // close the file stream
    inFile.close();
}

1

นี่เป็นโซลูชันทั่วไปสำหรับการโหลดข้อมูลลงในโปรแกรม C ++ และใช้ฟังก์ชัน readline สิ่งนี้สามารถแก้ไขได้สำหรับไฟล์ CSV แต่ตัวคั่นเป็นช่องว่างที่นี่

int n = 5, p = 2;

int X[n][p];

ifstream myfile;

myfile.open("data.txt");

string line;
string temp = "";
int a = 0; // row index 

while (getline(myfile, line)) { //while there is a line
     int b = 0; // column index
     for (int i = 0; i < line.size(); i++) { // for each character in rowstring
          if (!isblank(line[i])) { // if it is not blank, do this
              string d(1, line[i]); // convert character to string
              temp.append(d); // append the two strings
        } else {
              X[a][b] = stod(temp);  // convert string to double
              temp = ""; // reset the capture
              b++; // increment b cause we have a new number
        }
    }

  X[a][b] = stod(temp);
  temp = "";
  a++; // onto next row
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.