我有一个大日志文件,其中记录按时间排序。每行都有时间。我需要在时间T1和时间T2之间找到所有记录(T1 <= T2)。我可以逐行扫描整个文件并找到T1的起始行,将其复制到缓冲区然后扫描下一行直到我到达结束时间T2。这可行,但效率不高。
我想知道我是否可以使用二进制搜索来定位时间T1和T2的行。但我不确定如何确定以下内容:
lseek()
的偏移量?可以在文件上使用二进制搜索吗?
答案 0 :(得分:1)
如果您有足够的地址空间,请考虑使用内存映射文件。它们往往是最简单,最有效的方法之一。使用boost :: iostreams :: mapped_file来实现可移植性。
答案 1 :(得分:1)
让我们假设,您的线条在平均长度附近都是合理的(意味着没有线占据日志的一半左右),这将使二元搜索变得可行。
接下来我还假设您将拥有以下功能:
//find the first start of a new log line after (or including) position start
//return the last position of the file if no start could be found
streampos findNextLineStart(ifstream &file, streampos start);
//extract the data as a timestamp from a line
int extractDate(ifstream &file, streampos lineStart);
通过这些功能,我们可以实现以下功能:
//find the position of the first line whose date is bigger than the given
streampos lower_bound(ifstream &file, int date)
{
file.seekg(0, ios::end);
streampos begin = 0,
end = file.tellg();
while(begin < end)
{
streampos cur = (begin + end) / 2;
streampos start = findNextLineStart(file, cur);
//was a line start found?
if(start < end)
{
int lineDate = extractDate(file, start);
if(lineDate < date)
begin = start;
else
end = start;
}
else
//narrow the bound as no line was found
end = cur;
}
return begin;
}
我不保证这个工作(在所有角落的情况下),但它草拟了整体实现。一个人会使用另一个函数upper_bound
,你可以使用那些函数来开始和结束你的界限。
答案 2 :(得分:1)
首先,您可能不希望进行二进制搜索以查找范围中的最后一条记录。一旦找到T1,你就会线性地读取记录,直到找到一个超出所需范围的记录,所以你真的只需要找到该范围内的第一条记录。
此外,您不需要通过查找确切的第n / 2条记录来实现二进制搜索。如果您只是寻找两个当前边界之间的字节,找到下一个完整记录并从中更新边界,那么这应该没问题。
答案 3 :(得分:0)
你不需要中间线。相反,您可以使用中间的字符,然后一次向后移动一个字符,直到找到换行符;然后你知道你有当前行的开头。如果该行的时间戳在将来太远,那么您可以丢弃该行及其后的所有内容。如果它的时间戳过去太远,请丢弃它以及之前的所有内容。您可以重复此操作,直到找到所需的行。
这是标准的二进制搜索算法。你真的不需要二进制搜索中的中间线 - 它就足以拥有大约中间的东西。在某些极端情况下,某些线条比其他线条 更长,但可能会很慢。但通常它会足够快。