Đọc từng dòng tệp bằng cách sử dụng ifstream trong C ++


612

Nội dung của file.txt là:

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

Trường hợp 5 3là một cặp tọa độ. Làm cách nào để xử lý dòng dữ liệu này theo dòng trong C ++?

Tôi có thể nhận được dòng đầu tiên, nhưng làm cách nào để có được dòng tiếp theo của tệp?

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

Câu trả lời:


916

Đầu tiên, tạo một ifstream:

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

Hai phương pháp tiêu chuẩn là:

  1. Giả sử rằng mỗi dòng bao gồm hai số và đọc mã thông báo bằng mã thông báo:

    int a, b;
    while (infile >> a >> b)
    {
        // process pair (a,b)
    }
    
  2. Phân tích cú pháp dựa trên dòng, sử dụng luồng chuỗi:

    #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)
    }
    

Bạn không nên kết hợp (1) và (2), vì phân tích cú pháp dựa trên mã thông báo không ngấu nghiến dòng mới, do đó bạn có thể kết thúc bằng các dòng trống giả nếu bạn sử dụng getline()sau khi trích xuất dựa trên mã thông báo đã đưa bạn đến cuối dòng đã.


1
@EdwardKarak: Tôi không hiểu "dấu phẩy là mã thông báo" nghĩa là gì. Dấu phẩy không đại diện cho số nguyên.
Kerrek SB

8
OP đã sử dụng một khoảng trắng để phân định hai số nguyên. Tôi muốn biết nếu while (infile >> a >> b) có hoạt động không nếu OP sử dụng dấu phẩy là dấu phân cách, bởi vì đó là kịch bản trong chương trình của riêng tôi
Edward Karak

30
@EdwardKarak: Ah, vì vậy khi bạn nói "mã thông báo", bạn có nghĩa là "dấu phân cách". Đúng. Với dấu phẩy, bạn sẽ nói:int a, b; char c; while ((infile >> a >> c >> b) && (c == ','))
Kerrek SB

11
@KerrekSB: Hừ. Tôi đã sai. Tôi không biết nó có thể làm điều đó. Tôi có thể có một số mã của riêng tôi để viết lại.
Đánh dấu H

4
Để được giải thích về while(getline(f, line)) { }cấu trúc và liên quan đến việc xử lý lỗi, vui lòng xem bài viết này (của tôi): gehrcke.de/2011/06/. (Tôi nghĩ rằng tôi không cần phải có lương tâm xấu khi đăng bài này ở đây, nó thậm chí còn hơi trước ngày trả lời này).
Tiến sĩ Jan-Philip Gehrcke

175

Sử dụng ifstreamđể đọc dữ liệu từ một tệp:

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

Nếu bạn thực sự cần đọc từng dòng, thì hãy làm điều này:

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

Nhưng có lẽ bạn chỉ cần trích xuất các cặp tọa độ:

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

Cập nhật:

Trong mã của bạn, bạn sử dụng ofstream myfile;, tuy nhiên otrong ofstreamlà viết tắt cho output. Nếu bạn muốn đọc từ tập tin (đầu vào) sử dụng ifstream. Nếu bạn muốn cả đọc và viết sử dụng fstream.


8
Giải pháp của bạn được cải thiện một chút: biến dòng của bạn không hiển thị sau khi đọc tệp trái ngược với giải pháp thứ hai của Kerrek SB, đây cũng là giải pháp tốt và đơn giản.
DanielTuzes

3
getlineđã string thấy , vì vậy đừng quên#include <string>
mxmlnkn

55

Đọc một dòng tệp theo dòng trong C ++ có thể được thực hiện theo một số cách khác nhau.

[Nhanh] Lặp lại với std :: getline ()

Cách tiếp cận đơn giản nhất là mở std :: ifstream và loop bằng các lệnh gọi std :: getline (). Mã sạch và dễ hiểu.

#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();
}

[Nhanh] Sử dụng tệp_descrip_source của Boost

Một khả năng khác là sử dụng thư viện Boost, nhưng mã sẽ dài dòng hơn một chút. Hiệu suất khá giống với mã ở trên (Lặp lại với 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();
        }
    }
}

[Nhanh nhất] Sử dụng mã C

Nếu hiệu suất là quan trọng đối với phần mềm của bạn, bạn có thể xem xét sử dụng ngôn ngữ C. Mã này có thể nhanh hơn 4-5 lần so với các phiên bản C ++ ở trên, xem điểm chuẩn bên dưới

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);

Điểm chuẩn - Cái nào nhanh hơn?

Tôi đã thực hiện một số điểm chuẩn hiệu suất với mã ở trên và kết quả rất thú vị. Tôi đã kiểm tra mã với các tệp ASCII chứa 100.000 dòng, 1.000.000 dòng và 10.000.000 dòng văn bản. Mỗi dòng văn bản chứa trung bình 10 từ. Chương trình được biên dịch với -O3tối ưu hóa và đầu ra của nó được chuyển tiếp /dev/nullđể loại bỏ biến thời gian ghi nhật ký khỏi phép đo. Cuối cùng, nhưng không kém phần quan trọng, mỗi đoạn mã ghi lại từng dòng với printf()chức năng để thống nhất.

Kết quả cho thấy thời gian (tính bằng ms) mà mỗi đoạn mã đã dùng để đọc các tệp.

Sự khác biệt về hiệu năng giữa hai cách tiếp cận C ++ là tối thiểu và không nên tạo ra bất kỳ sự khác biệt nào trong thực tế. Hiệu suất của mã C là những gì làm cho điểm chuẩn ấn tượng và có thể là một thay đổi trò chơi về tốc độ.

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

nhập mô tả hình ảnh ở đây


1
Điều gì xảy ra nếu bạn loại bỏ đồng bộ hóa của C ++ với C trên đầu ra của bàn điều khiển? Bạn có thể đo lường một thiệt thòi tiếng của hành vi mặc định std::coutvs printf.
dùng4581602

2
Cảm ơn đã mang mối quan tâm này. Tôi đã làm lại các bài kiểm tra và hiệu suất vẫn như cũ. Tôi đã chỉnh sửa mã để sử dụng printf()hàm trong mọi trường hợp cho thống nhất. Tôi cũng đã thử sử dụng std::couttrong mọi trường hợp và điều này hoàn toàn không có sự khác biệt. Như tôi vừa mô tả trong văn bản, đầu ra của chương trình đi để /dev/nullthời gian in các dòng không được đo.
HugoTeixeira

6
Groovy. Cảm ơn. Tự hỏi sự chậm lại ở đâu.
dùng4581602

4
Xin chào @HugoTeixeira Tôi biết đây là một chủ đề cũ, tôi đã cố gắng sao chép kết quả của bạn và không thể thấy bất kỳ sự khác biệt đáng kể nào giữa c và c ++ github.com/simonsso/readfile_benchmark
Simson

Theo mặc định, các luồng vào C ++ được đồng bộ hóa với cstdio. Bạn nên thử với thiết lập std::ios_base::sync_with_stdio(false). Tôi đoán bạn sẽ có được hiệu suất tốt hơn nhiều (Mặc dù điều đó không được đảm bảo vì nó được xác định theo triển khai khi tắt đồng bộ hóa).
Fareanor

11

Vì tọa độ của bạn thuộc về nhau như cặp, tại sao không viết một cấu trúc cho chúng?

struct CoordinatePair
{
    int x;
    int y;
};

Sau đó, bạn có thể viết một toán tử trích xuất quá tải cho iuxs:

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

    return is;
}

Và sau đó bạn có thể đọc một tệp tọa độ thẳng vào một vectơ như thế này:

#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
Điều gì xảy ra khi không thể đọc hai intmã thông báo từ luồng trong operator>>? Làm thế nào người ta có thể làm cho nó hoạt động với trình phân tích cú pháp quay lui (nghĩa là khi operator>>thất bại, quay ngược lại luồng đến vị trí trước đó trả về false hoặc một cái gì đó tương tự)?
fferri

Nếu không thể đọc hai intmã thông báo, thì isluồng sẽ đánh giá falsevà vòng đọc sẽ chấm dứt tại thời điểm đó. Bạn có thể phát hiện điều này trong operator>>bằng cách kiểm tra giá trị trả về của các lần đọc riêng lẻ. Nếu bạn muốn cuộn lại luồng, bạn sẽ gọi is.clear().
Martin Broadhurst

trong operator>>đó là chính xác hơn để nói is >> std::ws >> coordinates.x >> std::ws >> coordinates.y >> std::ws;vì nếu không bạn đang giả định rằng dòng đầu vào của bạn đang ở chế độ khoảng trắng bỏ qua.
Darko Veberic

7

Mở rộng trên câu trả lời được chấp nhận, nếu đầu vào là:

1,NYC
2,ABQ
...

bạn vẫn có thể áp dụng logic tương tự, như thế này:

#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

Mặc dù không cần phải đóng tệp theo cách thủ công nhưng bạn nên làm như vậy nếu phạm vi của biến tệp lớn hơn:

    ifstream infile(szFilePath);

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

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

Không chắc chắn điều này xứng đáng bỏ phiếu xuống. OP yêu cầu một cách để có được từng dòng. Câu trả lời này thực hiện điều đó và đưa ra một mẹo tuyệt vời để đảm bảo tập tin đóng lại. Đối với một chương trình đơn giản, nó có thể không cần thiết nhưng tối thiểu là một thói quen TUYỆT VỜI để hình thành. Nó có thể được cải thiện bằng cách thêm vào một vài dòng mã để xử lý các dòng riêng lẻ mà nó kéo nhưng nói chung là câu trả lời đơn giản nhất cho câu hỏi OP.
Xandor

2

Câu trả lời này dành cho visual studio 2017 và nếu bạn muốn đọc từ tệp văn bản, vị trí nào có liên quan đến ứng dụng bảng điều khiển được biên dịch của bạn.

đầu tiên đặt textfile của bạn (test.txt trong trường hợp này) vào thư mục giải pháp của bạn. Sau khi biên dịch, giữ tập tin văn bản trong cùng thư mục với applicationName.exe

C: \ Users \ "tên người dùng" \ source \ 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

Đây là một giải pháp chung để tải dữ liệu vào chương trình C ++ và sử dụng chức năng đọc. Điều này có thể được sửa đổi cho các tệp CSV, nhưng dấu phân cách là một khoảng trắng ở đây.

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
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.