2013-05-16 16 views
3

我試圖在循環中逐行讀取std :: ifstream文件。在同一個循環中,我試圖找到兩個標籤,其中包含一個我希望作爲整體閱讀的塊。如何在文本模式下安全地混合使用std :: ifstream的tellg,seekg和read(*,n)方法

我想,我可以通過seekg跟蹤塊的開始和結束位置,並在找到兩個位置後使用read(*,end-start)來讀取塊。

但是,tellg返回流的位置,但由於文件已在文本模式下打開[所以我可以調用getline]並使用\ r \ n作爲行結束,參數「字符數」的ifstream's-read-method是指從\ r \ n轉換爲\ n後的數字,以便我讀取的字符數超過了我預期的n個字符,其中n是兩個標記之間的行數。

很明顯,有很多解決方法,但我正在尋找一個很好且直觀的解決方案。有什麼建議麼?

EDIT1 @ 130507: 我的目標是留在超速的標準庫流和青睞內存,我需要解析和處理兩部分,周邊部分和標籤之間的塊。

我希望有一些可用的東西,如在已打開的文本模式流中切換到二進制模式,或者有一些(基類)原始讀取方法不會像讀取或某些映射器那樣進行字符轉換允許在字符轉換之前和之後映射流指示之間的方法,但到目前爲止找不到任何東西。

下面是一些代碼:

std::ifstream testDataFileStream; 
testDataFileStream.open(testDataFileName, std::ios_base::in); 
testDataFileStream.unsetf(std::ios::skipws); // No white space skipping 
if (testDataFileStream) { 
    std::string line; 
    while (getline(testDataFileStream, line)) 
     if (line == "starttag") 
      break; 
    if (line == "starttag") 
    { 
     std::ifstream::pos_type cmdStartPos = testDataFileStream.tellg(); 
     std::ifstream::pos_type cmdEndPos; 
     while (getline(testDataFileStream, line)) 
      if (line == "endtag") 
       break; 
      else 
       cmdEndPos = testDataFileStream.tellg(); 
     if (line == "endtag") 
     { 
      std::streamsize nofBytesToRead = cmdEndPos - cmdStartPos; 

      // now, one possible workaround follows, however, it's obviously quite lame 
      testDataFileStream.close(); 
      testDataFileStream.open(testDataFileName, std::ios_base::in | std::ios::binary); 
      testDataFileStream.seekg(cmdStartPos); 
      std::string cmdsString; 
      cmdsString.resize(nofBytesToRead+1); 
      testDataFileStream.read(&cmdsString[0], nofBytesToRead); 
     } else {} 
    } else {} 
    testDataFileStream.close(); 
} else {} 

一個testfile的可能看起來如下:

some text 
more text 
starttag 
much stuff on many lines 
endtag 
even more text 
+0

所以tellg和seekg得到在文本模式下真正棘手 –

+0

文本模式下的流傾向於相對位置,而不是絕對的。執行'seekg(tellg())'會將當前流的位置替換爲未知位置。 它看起來有點低效率的,但我不知道會發生,如果不是什麼: 'testDataFileStream.seekg(cmdStartPos);' 你這樣做: '而(!testDataFileStream.tellg()= cmdStartPos)testDataFileStream.unget() ;' –

回答

0

你似乎(我)已經選擇了一個比較困難的問題解決辦法。

由於您要掃描文件尋找標籤,爲什麼不保留數據您掃描標籤?也就是說,掃描並丟棄數據,直到找到開始標記,然後繼續掃描並從那裏保存數據,直到找到結束標記。

+0

嗨傑瑞。我希望有更簡單的方法,比如說,在已經打開的文本模式流中切換到二進制模式,或者使用某些(基類)原始讀取方法,它不會像讀取或某些內容那樣進行字符轉換映射器方法,它允許在字符轉換之前和之後映射流之間的映射。 我喜歡你的方法的簡單性,但不喜歡當我使用stringstream或其他類型來保留行的時候出現的重新分配開銷。 –

+0

@wonkorealtime:您是否測試過發現重新分配的實際開銷? (我有 - 當你還在做I/O時,它通常太小而無法衡量)。 –

1

在文本模式下打開文件時會發生字符翻譯。

您可以用二進制模式打開文件。 ios::binary

+0

對不起,我沒有說清楚,我需要不在文本模式中的標籤之間的外部部分,所以我認爲只要有一種簡單的方法來翻譯讀取的行二進制之後;有這樣的功能嗎? –

1

正如傑裏棺材Terenty Rezman建議,在tellg()/seekg()方法 帶你進入雜草。至於要解析的所有線條和做就starttag/endtag阻止某些 特定的解析,我建議你: -

  • 一行文本模式讀行的文件
  • 保持當你輸入的軌跡和離開這些塊
  • 當您正在讀取一行中的行 時,以某種適當的方式「組裝」一個塊。
  • 做任何事情都是正確的與每個人的塊內和塊外線
  • 無論什麼是正確的塊與每當你完成一個。
  • 並隨時處理解析錯誤。

下面是一個粗略的例子。它跳過空行,但假定 不是空行中的填充,只是標記。它假定 塊不能嵌套:

#include <fstream> 
#include <iostream> 

enum parse_error 
{ 
    none, 
    open_fail, 
    nested_starttag, 
    orphan_endtag, 
    orphan_starttag 
}; 

void handle_out_of_block_line(std::string const & line) 
{ 
    std::cout << "Read out-of-block line: \"" << line << '\"' << std::endl; 
} 

void handle_in_block_line(std::string const & line, std::string & block) 
{ 
    std::cout << "Read in-block line: \"" << line << '\"' << std::endl; 
    block += line + '\n'; 
} 

void handle_block(std::string const & block) 
{ 
    std::cout << "Got block {\n" << block << "}" << std::endl; 
} 

parse_error parse(std::string const & filename) 
{ 
    std::ifstream ifs(filename); 
    if (!ifs) { 
     std::cerr << 
     "error: cannot open \"" << filename << "\" for reading" << std::endl; 
     return parse_error::open_fail; 
    } 
    bool in_block = 0; 
    std::string line; 
    std::string block; 
    while(getline(ifs,line),ifs) { 
     if (line.empty()) { 
      continue; // Skip empty line. 
     } 
     if (line == "starttag") { 
      if (in_block) { 
       std::cerr << "error: starttag within starttag" << std::endl; 
       return parse_error::nested_starttag; 
      } 
      in_block = true; 
      block.clear(); 
     } 
     if (in_block) { 
      handle_in_block_line(line,block); 
     } else { 
      handle_out_of_block_line(line); 
     } 
     if (line == "endtag") { 
      if (!in_block) { 
       std::cerr << "error: ophan endtag" << std::endl; 
       return parse_error::orphan_endtag; 
      } 
      in_block = false; 
      handle_block(block); 
     } 
    } 
    if (in_block) { 
     std::cerr << "error: ophan starttag" << std::endl; 
     return parse_error::orphan_starttag; 
    } 
    return parse_error::none; 
} 

int main(int argc, char const *argv[]) 
{ 
    return parse(argv[1]); 
} 

輸入例如,包含此文件:

some text 
more text 
starttag 
much stuff 
on many lines 
endtag 
even more text 

它輸出這樣的:

Read out-of-block line: "some text" 
Read out-of-block line: "more text" 
Read in-block line: "starttag" 
Read in-block line: "much stuff " 
Read in-block line: "on many lines" 
Read in-block line: "endtag" 
Got block { 
starttag 
much stuff 
on many lines 
endtag 
} 
Read out-of-block line: "even more text" 
1

要在這裏傑裏棺材的做法擴大是一個簡單的例子。通過使用C++ 11的std::move避免了額外的分配。但請注意,getline()將導致其std::string參數的重複重新分配,特別是對於長線。如果你真的關心內存管理,你應該考慮將數據讀入固定大小的緩衝區。

總之,這裏的代碼:

#include <fstream> 
#include <iostream> 
#include <vector> 
#include <utility> 

int main() { 
    std::ifstream testDataFileStream; 
    testDataFileStream.open("data.txt", std::ios_base::in); 
    testDataFileStream.unsetf(std::ios::skipws); // No white space skipping 
    if (testDataFileStream) { 
     std::vector<std::string> buffer; 
     std::string line; 
     bool found = false; 
     while (getline(testDataFileStream, line)) { 
      if (line == "starttag") 
       found = true; 
      if (found) { 
       buffer.push_back(std::move(line)); 
       if (line == "endtag") 
        found = false; 
      } 
     } 
     for (std::string & s : buffer) { 
      std::cout << s << std::endl; 
     } 
    } 
} 
相關問題