【问题标题】:search a line in preprocessed big text file在预处理的大文本文件中搜索一行
【发布时间】:2018-03-07 15:14:47
【问题描述】:

我有一个包含 100,000+ 行的数据文件,每行只包含两个字段,用逗号分隔的键和值,并且所有键都是唯一。我想从这个文件中按键查询值。将其加载到地图是毫无问题的,因为这会消耗太多内存(代码将在嵌入式设备上运行)并且我不希望涉及数据库。到目前为止,我所做的是在我的 PC 中预处理文件,即对行进行排序,然后在预处理文件中使用如下所示的二进制搜索:

public long findKeyOffset(RandomAccessFile raf, String key)
            throws IOException {
        int blockSize = 8192;
        long fileSize = raf.length();
        long min = 0;
        long max = (long) fileSize / blockSize;
        long mid;
        String line;
        while (max - min > 1) {
            mid = min + (long) ((max - min) / 2);
            raf.seek(mid * blockSize);
            if (mid > 0)
                line = raf.readLine(); // probably a partial line
            line = raf.readLine();
            String[] parts = line.split(",");
            if (key.compareTo(parts[0]) > 0) {
                min = mid;
            } else {
                max = mid;
            }
        }
        // find the right line
        min = min * blockSize;
        raf.seek(min);
        if (min > 0)
            line = raf.readLine();
        while (true) {
            min = raf.getFilePointer();
            line = raf.readLine();
            if (line == null)
                break;
            String[] parts = line.split(",");
            if (line.compareTo(parts[0]) >= 0)
                break;
        }
        raf.seek(min);
        return min;
    }

我认为有比这更好的解决方案。谁能给我一些启示?

【问题讨论】:

  • 使用恒定时间排序算法怎么样?
  • “将其加载到地图是不可能的,因为这会消耗太多内存 [...] 到目前为止,我所做的是在我的 PC 中预处理文件,即对行,然后使用如下所示的二进制搜索“如果您的设备有足够的内存来对文件内容进行排序,那么它也有足够的内存来将其保存在地图中。
  • @TimothyTruckle 我在我的电脑上对其进行排序,然后将其复制到设备上。
  • 将数据存储在适合嵌入式设备的数据库中,例如sqlite.org/whentouse.html 而不是在平面文件中并使用数据库 API 来检索数据?
  • @toongeorges 感谢您的提示,但我暂时不考虑 DB。也许我可以使用数据库的算法来索引文件以供以后查询。

标签: java algorithm file search random-access


【解决方案1】:

一种针对特定约束优化性能的简单算法:

  1. 设 n 为原始、不可变、已排序文件中的行数。
  2. 让 k
  3. 将文件分成 k 个文件,每个文件的行数大致相等(因此每个文件有 n/k 行)。这些文件将被称为 F1...Fk。如果您希望保持原始文件完整,只需将 F1...Fk 视为文件中的行号,将其切割成段。
  4. 创建一个名为 P 的新文件,其中有 k 行,每行 i 是 Fi 的第一个键。
  5. 在查找密钥时,首先使用 O(logk) 对 P 进行二进制搜索,以找到您需要转到的文件 /segment (F1...Fk)。然后转到该文件/段并在其中搜索。
  6. 如果 k 足够大,那么 Fi (n/k) 的大小将足够小,可以加载到 HashMap 并使用 O(1) 检索键。如果仍然不实用,请进行 O(log(n/k)) 的二分查找。

总搜索量将是 O(logk)+O(log(n/k)),这是对您原来的 O(logn) 的改进解决方案。

我建议找到一个足够大的 k 以允许您将特定的 Fi 文件/段加载到 HashMap 中,并且不会太大而无法填满设备上的空间。最平衡的 k 它是 sqrt(n),这使得解决方案在 O(log(sqrt(n))) 中运行,但这可能是一个相当大的 P 文件。如果你得到一个 k 允许你将 P 和 Fi 加载到 HashMap 中进行 O(1) 检索,那将是最好的解决方案。

【讨论】:

  • 感谢您的想法,我会尝试并考虑更多方法。
  • @jfly,有什么我可以为您改进此解决方案的吗?
  • 我在想 :)
【解决方案2】:

数据是不可变的,键是唯一的(如问题的 cmets 中所述)。

一个简单的解决方案:编写你自己的哈希码来映射键和行号。

这意味着,不要进行排序,而是按照散列算法告诉的顺序将数据写入文件。

查询key时,对key进行hash,得到具体的行号,然后读取value。

理论上,您的问题有一个 O(1) 的解决方案。


确保散列算法的冲突较少,但我认为根据您的具体情况,一些冲突应该没问题。示例:3 个键映射到相同的行号,因此您将所有三个键都写在同一行,当搜索到任何冲突的键时,您将从该行读取所有 3 个条目。然后在整条线上进行线性(又名 O(3) 也就是常数时间)搜索。

【讨论】:

  • 是的,我之前也是这么想的,就像内存中的HashMap一样散列到文件中。我google了一下,所有结果都是关于文件的哈希,这个方法应该被别人使用。
  • @jfly:我没有用谷歌搜索你的问题——这对我来说只是直观的。现在,您不必将二进制搜索代码放在嵌入式设备中,而是必须编写基于哈希的搜索代码。文件应该是相同的大小,因为文件中的数据没有改变。而且你显然不能在时间和空间上比 O(1) 做得更好,就像这种基于哈希的解决方案一样。
  • 是的,这让我想起了我在学校学习的哈希表碰撞处理,时间过得真快!
【解决方案3】:

这个呢?

#include <iostream>
#include <fstream>
#include <boost/algorithm/string.hpp>
#include <vector>

using namespace std;

int main(int argc, char *argv[])
{
    ifstream f(argv[1],ios::ate);
    if (!f.is_open())
        return 0;
    string key(argv[2]),value;

    int max = f.tellg();
    int min = 0,mid = 0;
    string s;
    while(max-min>1)
    {
        mid = min + (max - min )/2;
        f.seekg(mid);
        f >> s;
        std::vector<std::string> strs;

        if (!f)
        {
            break;
        }
        if (mid)
        {
            f >> s;
        }
        boost::split(strs, s, boost::is_any_of(","));
        int comp = key.compare(strs[0]);
        if ( comp < 0)
        {
            max = mid;
        }
        else if (comp > 0)
        {
            min = mid;
        }
        else
        {
            value = strs[1];
            break;
        }
    }
    cout<<"key "<<key;
    if (!value.empty())
    {
        cout<<" found! value = "<<value<<endl;
    }
    else
    {
        cout<<" not found..."<<endl;
    }

    f.close();
    return 0;
}

【讨论】:

  • 这不就是二分查找吗?
  • 很公平。但是,为了使其对原始海报更有用 - 您是否考虑将其发布在 Java 中,这个问题被标记的语言?
猜你喜欢
  • 1970-01-01
  • 2010-11-29
  • 2013-12-30
  • 2012-03-10
  • 1970-01-01
  • 2021-08-05
  • 2022-11-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多