在预处理的大文本文件中搜索一行

时间:2017-09-26 11:50:15

标签: java algorithm file search random-access

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

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;
    }

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

3 个答案:

答案 0 :(得分:3)

数据是不可变的,密钥是唯一的(如问题评论中所述)。

一个简单的解决方案: 编写您自己的哈希代码,以使用行号映射密钥。

这意味着,保留排序,而是按照哈希算法告诉的顺序将数据写入文件。

当查询密钥时,您对密钥进行哈希处理,获取特定的行号,然后读取该值。

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

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

答案 1 :(得分:2)

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

  1. 令n为原始的,不可变的,已排序的文件中的行数。
  2. 让k< n是一个数字(我们稍后会讨论理想数字)。
  3. 将文件分成k个文件,每个文件的行数大致相等(因此每个文件都有n / k行)。这些文件将被称为F1 ... Fk。如果您希望保持原始文件不变,只需将F1 ... Fk视为文件中的行号,将其剪切为段。
  4. 使用k行创建一个名为P的新文件,每行i是Fi的第一个键。
  5. 在寻找密钥时,首先使用 O(logk)对P进行二进制搜索,以找到您需要访问的文件/段(F1 ... Fk)。然后转到该文件/段并在其中搜索。
  6. 如果k足够大,那么Fi(n / k)的大小将足够小以加载到HashMap并使用 O(1)检索密钥。如果仍然不实用,请进行 O(log(n / k))的二进制搜索。
  7. 总搜索次数为 O(logk)+ O(log(n / k)),这是对 O(logn)的改进,这是您原来的溶液

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

答案 2 :(得分:1)

这个怎么样?

#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;
}