【问题标题】:Text File Binary Search文本文件二进制搜索
【发布时间】:2025-12-10 11:15:02
【问题描述】:

我有一个如下所示的测试文件:

Ampersand           Gregorina           5465874526370945
Anderson            Bob                 4235838387422002
Anderson            Petunia             4235473838457294
Aphid               Bumbellina          8392489357392473
Armstrong-Jones     Mike                8238742438632892

还有如下代码:

#include <iostream>
#include <string>
#include <fstream>

class CardSearch
{
protected:
    std::ifstream cardNumbers;

public:
    CardSearch(std::string fileName)
    {
        cardNumbers.open(fileName, std::ios::in);

        if (!cardNumbers.is_open())
        {
            std::cout << "Unable to open: " << fileName;
        }
        return;
    }

    std::string Find(std::string lastName, std::string firstName)
    {
        // Creating string variables to hold first and last name
        // as well as card number. Also creating bools to decide whether
        // or not the person has been found or if the last name is the only
        // identifier for a found person
        std::string lN;
        std::string fN;
        std::string creditNumber;
        bool foundPerson = false;

        // By using the seekg and tellg functions, we can find our place
        // in the file and also calculate the amount of lines within the file
        cardNumbers.seekg(0, std::ios::beg);
        cardNumbers.clear();
        std::streamsize first = cardNumbers.tellg();
        cardNumbers.ignore(std::numeric_limits<std::streamsize>::max());
        cardNumbers.clear();
        std::streamsize last = cardNumbers.tellg();
        cardNumbers.seekg(0, std::ios::beg);
        std::streamsize lineNumbers = (last / 57);
        std::streamsize middle;

        while (first <= lineNumbers)
        {
            middle = (first + lineNumbers) / 2;
            // middle * 57 takes us to the beginning of the correct line
            cardNumbers.seekg(middle * 57, std::ios::beg);
            cardNumbers.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

            cardNumbers >> lN >> fN;

            if (lN < lastName)
            {
                first = middle + 1;
            }
            else if (lN > lastName)
            {
                lineNumbers = middle - 1;
            }
            else
            {
                if (fN < firstName)
                {
                    first = middle + 1;
                }
                else if (fN > firstName)
                {
                    lineNumbers = middle - 1;
                }
                else if (fN == firstName)
                {
                    foundPerson = true;
                    break;
                }
            }
        }

        if (foundPerson)
        {
            // When a person is found, we seek to the correct line position and 
            // offset by another 40 characters to receive the card number
            cardNumbers.seekg((middle * 57) + 40, std::ios::beg);
            std::cout << lN << ", " << fN << " ";
            cardNumbers >> creditNumber;
            return creditNumber;
        }
        return "Unable to find person.\n";
    }
};

int main()
{
    CardSearch CS("C:/Users/Rafael/Desktop/StolenNumbers.txt");
    std::string S = CS.Find("Ampersand", "Gregorina");
    std::cout << S;

    std::cin.ignore();
    std::cin.get();

    return 0;
}

我能够检索列表中除第一条记录之外的所有记录。似乎 seekg 正在寻找正确的位置,但 cardNumbers 没有读取正确的信息。当 'middle' 设置为 0 时,seekg 应该搜索到第 0 行,(middle * 57),读取 Ampersand Gregorina 并进行比较。相反,它仍然阅读安德森鲍勃。

关于为什么会发生这种情况的任何想法?

谢谢

【问题讨论】:

  • 这个中间? middle = (first + lineNumbers) / 2;
  • @rafa 您的整个方法存在缺陷。最大的错误是您在以文本模式打开的文件上使用了seekg。当您以这种方式打开文件时,您无法可靠地使用此类功能。 seekg 在以二进制 ios:binary 模式打开的文件上的工作方式与您期望的一样。你的代码注定要失败的原因是,在文本模式下,翻译是针对行尾和 EOF 完成的,你不知道也不想跟踪(如果你会失去理智)试过)。如果您要这样做,请以二进制模式打开文件并从那里解决。
  • @PaulMcKenzie 您的解决方案是正确的,让我得以进步。现在,我对 Armstrong-Jones Mike 有意见。当 middle 设置为 4 时,它会从最后一行读取“73”作为姓氏,而 Mike 在正确的行读取第一行。因此,它显示为无法找到正确的记录
  • @rafa 我向您发布了我的建议,作为seekg 问题的答案。所以你是说还有一个问题?如果是这样,那么也许应该发布另一个问题?另外,57 这个神奇的数字是怎么得出的?
  • 不会让您的 stream.ignore 跳过字符,直到它到达 \n。即它永远不会读取第一行

标签: c++ search fstream seekg


【解决方案1】:

LineNumbers 正在被您的循环修改,从 4 变为 1,再变为 -1。 -1 使您的循环过早终止,因此您无法正确选择第一个条目。

这似乎是一个家庭作业问题,所以我希望你能用它来引导自己找到答案。

【讨论】:

  • 它不应该走那么远。当 middle 为 0 时, seekg 应该将它带到第一行,这是我正在寻找的正确记录。在第二个循环中,当 LineNumbers 仍为 1 时,middle 为 0 但错误地读取了名称。它应该是第一条记录,但它读取的是记录 2,即 Anderson Bob
【解决方案2】:

当使用seekg 等函数时,最好以binary mode 打开文件,而不是像现在的代码那样以文本模式打开文件。换句话说,你应该这样做:

cardNumbers.open(fileName, std::ios::in | std::ios::binary);

原因是在文本模式下打开文件将允许完成行尾翻译。这会渲染诸如 seekgtellg 等函数,充其量是不稳定(或幸运地工作),而在最坏的情况下,对文本处理毫无用处。

当以二进制模式打开文件时,seekg 和其他函数系列按预期工作,因为没有进行行尾转换。您实际上将寻找您指定的文件中的字节偏移量,而不是被行尾翻译抛出。

此外,一旦你这样做了,行中数据的长度不仅包括可见文本,还包括构成行尾序列的不可见字符。因此,您手动计算的 57 在二进制模式下是不正确的——它应该是 58 或 59,这取决于您分别使用的是 Linux / Unix 还是 Windows。

【讨论】: