【问题标题】:Using seekg() in text mode在文本模式下使用 seekg()
【发布时间】:2014-11-21 06:56:47
【问题描述】:

在尝试以文本模式 (Windows) 读取简单的 ANSI 编码文本文件时,我遇到了 seekg()tellg() 的一些奇怪行为;任何时候我尝试使用 tellg(),保存它的值(作为 pos_type),然后再寻找它,我总是会在流中比我停止的位置更靠前。 p>

最后我做了一个健全性检查;即使我只是这样做......

int main()
{
   std::ifstream dataFile("myfile.txt",
         std::ifstream::in);
   if (dataFile.is_open() && !dataFile.fail())
   {
      while (dataFile.good())
      {
         std::string line;
         dataFile.seekg(dataFile.tellg());
         std::getline(dataFile, line);
      }
   }
}

...然后,最终,在文件的更深处,行被截断了一半。为什么会发生这种情况?

【问题讨论】:

  • 你用的是什么编译器?
  • 我正在使用 MinGW 的 C++ 编译器。
  • 不使用binary会怎样?
  • 啊,对不起; std::ifstream::binary 不应该在那里,我的错。这仅在文本模式下发生;在二进制模式下,它工作正常。
  • 您无法可靠地寻找文本流。 CRT 缓冲流,因此它可以可靠地将字节转换为字符。所以你确实看到流的位置太靠前了。对此没有创可贴,只要您期望 CRT 为您返回字符,您就无法使其工作。

标签: c++


【解决方案1】:

这个问题是由 libstdc++ 使用当前剩余缓冲区与lseek64 之间的差异来确定当前偏移量引起的。

缓冲区是使用read的返回值设置的,对于windows上的文本模式文件,它返回endline转换后已放入缓冲区的字节数(即2字节的\r\n endline被转换到\n,windows 似乎也在文件末尾附加了一个虚假的换行符)。

lseek64 但是(使用 mingw 会导致调用 _lseeki64)返回当前的绝对文件位置,一旦减去这两个值,您最终会得到一个偏移量,其中每个剩余的换行符都会偏移 1文本文件(+1 表示额外的换行符)。

以下代码应该会显示问题,您甚至可以使用单个字符且没有换行符的文件,因为 windows 会插入额外的换行符。

#include <iostream>
#include <fstream>

int main()
{
  std::ifstream f("myfile.txt");

  for (char c; f.get(c);)
    std::cout << f.tellg() << ' ';
}

对于具有单个 a 字符的文件,我得到以下输出

2 3

第一次调用tellg 时明显减 1。在第二次调用之后,文件位置是正确的,因为在考虑了额外的换行符之后已经到达末尾。

除了以二进制模式打开文件外,您还可以通过禁用缓冲来规避此问题

#include <iostream>
#include <fstream>

int main()
{
  std::ifstream f;
  f.rdbuf()->pubsetbuf(nullptr, 0);
  f.open("myfile.txt");

  for (char c; f.get(c);)
    std::cout << f.tellg() << ' ';
}

但这远非理想。

希望 mingw / mingw-w64 或 gcc 可以解决此问题,但首先我们需要确定谁将负责修复它。我想基本问题是 lseek 的 MS 实现,它应该根据文件的打开方式返回适当的值。

【讨论】:

  • 我复制粘贴并执行了您的代码,但从 tellg() 得到了不同的结果......不过,仍然关闭。您的第二个示例实际上演示了您之前概述的每个换行符 +1 的问题。因此,似乎只使用二进制模式是理想的解决方案。
  • @Michael 我向 libstdc++ 提交了一个错误报告并发布在邮件列表中,mingw-w64 现在也意识到了这个问题,但不清楚它是否会修复以及由谁修复。跨度>
【解决方案2】:

感谢这个,虽然这是一个非常古老的帖子。我被这个问题困扰了一个多星期。这是我网站上的一些代码示例(菜单版本 1 和 2)。版本 1 使用此处介绍的解决方案,以防万一有人想看到它。

:)

void customerOrder::deleteOrder(char* argv[]){
std::fstream newinFile,newoutFile;
newinFile.rdbuf()->pubsetbuf(nullptr, 0);
newinFile.open(argv[1],std::ios_base::in);
if(!(newinFile.is_open())){
     throw "Could not open file to read customer order. ";
} 
newoutFile.open("outfile.txt",std::ios_base::out);
if(!(newoutFile.is_open())){
     throw "Could not open file to write customer order. ";
} 
newoutFile.seekp(0,std::ios::beg);
std::string line;
int skiplinesCount = 2;

if(beginOffset != 0){
    //write file from zero to beginoffset and from endoffset to eof   If to delete is non-zero
    //or write file from zero to beginoffset    if to delete is non-zero and last record 
    newinFile.seekg (0,std::ios::beg);
    // if  primarykey < largestkey , it's a middle record
    customerOrder order;
    long tempOffset(0);

    int largestKey = order.largestKey(argv);
    if(primaryKey < largestKey) {
        //stops right before "current..." next record.
            while(tempOffset < beginOffset){
              std::getline(newinFile,line);
      newoutFile << line << std::endl;
              tempOffset = newinFile.tellg();
        }
        newinFile.seekg(endOffset);
        //skip two lines between records.
            for(int i=0; i<skiplinesCount;++i) {
             std::getline(newinFile,line);
            }
        while( std::getline(newinFile,line) ) {
            newoutFile << line << std::endl;
        } 
    } else if (primaryKey == largestKey){
        //its the last record.
        //write from zero to beginoffset. 
           while((tempOffset < beginOffset) && (std::getline(newinFile,line)) ) {
              newoutFile << line << std::endl;
              tempOffset = newinFile.tellg();
           } 
} else {
        throw "Error in delete key" 
    }
}  else {
//its the first record.
//write file from endoffset to eof 
//works with endOffset - 4  (but why??)
    newinFile.seekg (endOffset);  
    //skip two lines between records.
    for(int i=0; i<skiplinesCount;++i) {
        std::getline(newinFile,line);
    }
    while(std::getline(newinFile,line)) {
        newoutFile << line << std::endl;
   } 
}
newoutFile.close();
newinFile.close();

}

beginOffset 是文件中的一个特定点(每条记录的开头),endOffset 是记录的结尾,在另一个函数中用tellg(findFoodOrder)计算我没有添加它,因为它可能会变得很长,但是你可以在我的网站上找到它(在:菜单版本 1 链接下):

http://www.buildincode.com

【讨论】:

  • 请谨慎链接到您自己在不同网站上的内容,您不想成为spammer。您应该在此处包含大部分内容,并且仅将链接用作参考。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-05-11
  • 1970-01-01
  • 2011-06-07
  • 2019-09-25
  • 2010-10-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多