【问题标题】:efficient large string (in) equality function高效的大字符串 (in) 相等函数
【发布时间】:2011-12-13 06:25:48
【问题描述】:

我需要比较非常大的基于文件的等长字符串以获得简单的相等性,而无需先计算哈希。

我想使用字符串中的数据进行看似随机的大跳跃,以便即使在开头和结尾方式相同的字符串中,我也可以快速确定不等式测试。也就是说,我想在整个范围内跳跃,以某种方式大部分或完全避免多次击中同一个角色。

由于字符串是基于文件的并且非常大,我不希望我的跳转太大,因为这会破坏磁盘。

在我的程序中,字符串是由文件支持的简单字符序列,大小小于 2gig,但很少一次完全在内存中。

然后在尝试了一段时间后,我假设它们是相等的,我只是按顺序迭代。

我的字符串类变体都有一个 int length() 和 char charAt() 函数的基本接口,假设 java chars,通常但不总是 ascii。

任何想法, 安迪

【问题讨论】:

  • 这些字符串有很长的公共前缀是否很常见?如果不是这样,仅从头到尾逐个字符进行比较可能比随机跳转更有效(节省您计算跳转偏移量,允许读取最佳大小的片段并最大限度地减少搜索)。
  • 因此,如果字符串可能(但不一定)相等,您希望该方法返回 true。您不想从一开始就按顺序比较字符,因为 (1) 您假设字符串的开头和结尾是相同的,并且 (2) 您希望最坏情况下的时间性能明显快于 O(n)。但为什么它必须是随机的呢?
  • 实际上,我认为这更像是一个统计问题而不是编程问题。您正在做的是从样本(您随机跳转到的字符)中推断总体(整个字符串)。

标签: java string algorithm hash


【解决方案1】:

构建一些关于你的巨型字符串的元数据。

假设您将它们分成逻辑页面或块。您选择一个块大小,当您将一个块加载到内存中时,您对其进行哈希处理,将此哈希存储在查找表中。

当你去比较两个文件时,你可以先比较已知的小节哈希,然后再去磁盘获取更多。

这应该可以很好地平衡缓存和消除对磁盘访问的需求,而不会给您带来太多开销。

【讨论】:

  • 顺便说一句,都是很好的答案。这是最好的,因为我的问题已经说明,但对于我的特定情况,另一个可能更好,这比问题陈述更复杂。
  • 不是有可能两个不同的字符串返回相同的哈希(根据java中的equals和hashcode协定)!??它不会破坏平等检查吗?
  • @AbdullahKhan 绝对有机会。但它不会破坏它。它只允许您使用文件一小部分的哈希值,因此您不必从磁盘加载整个文件。文件很大。哈希很小。让我们看看哈希是否首先匹配,如果匹配,我们进行相等性检查。但是,如果哈希不首先匹配,我们就不会加载文件。
【解决方案2】:

可能没有一个简单且最佳的单一解决方案。这是我的两分钱:

如果您能够进行一些预先计算并存储数据,请将space-time tradeoff 用作glowcoder suggested

标准的 O(n) 解决方案是对每个字符进行逐个字符的常规比较,但在这种情况下,您需要更高效的方法。一种可能的解决方案是定义步长,例如10,然后每隔 10 个字符比较一次。与使用 random 相比,它的优点是您将节省几个计算随机性的周期,并且您也不会两次比较一个字符,因为它永远不会发生冲突。这种解决方案的问题是字符串是否有一个通常相等的长前缀。

如果在随机字符的字符串比较中存在较大的前缀和后缀,正如您所提到的,可能会加快速度。但是,如果您无法将所有信息保存在内存中,那么从磁盘读取就会出现问题,您最终可能会从磁盘读取速度很慢,并且如果您不幸在不同盘片之间进行大量切换。

【讨论】:

  • 我想这可能是我真正的想法。
【解决方案3】:

CPU 和 HDD 喜欢按顺序读取数据;更容易缓存和处理。

所以你的基本算法将是

选择一个 ?16KB 的 CHUNK 大小? 选择要比较每个 CHUNK 多少个 COMPARES、字符/字节?128?,确保 CHUNK 是 COMPARES 的倍数 从文件 1 中顺序读取一个 CHUNK 从文件 2 中顺序读取一个 CHUNK 随机(但按顺序)比较这两个块 重复直到 EOF 或比较不相等或其他满意度指标

static int CHUNK = 4096 * 16;
static int COMPARES = 128;
static int CMP_STEP = CHUNK / COMPARES
static Random RND = new Random();
static boolean AreFilesProbablyEqual(FileReader readerA, FileReader readerB) { 
 char[] buffA = new char[CHUNK];
 char[] buffB = new char[CHUNK];
 int readA = 0;
 int readB = 0;
 while(readA != -1) { // read a CHUNK at a time
  readA = readA.read(buffA);
  readB = readB.read(buffB);
  if(readA != readB) return false; // size mismatch files are not equal
  if(readA > 0) { // work through the chunk and randomly but sequentially compare
   for(int i = 0; i < readA; i = i + CMP_STEP) {
    int range = Math.min(readA - i, CMP_STEP);
    int idx = RND.next(range) + i;
    if(buffA[idx] != buffB[idx]) return false;
   }
  }
 }
 return true; // they are PROBABLY be equal
}

注意此代码是在浏览器中编写的,未经测试,因此可能存在语法错误。

【讨论】:

    【解决方案4】:
    1. 比较整个块。比较整个内存块的成本低于读取块的成本。因此,我应该建议,如果您阅读一个块,请完整比较其内容。
    2. 您应该阅读区块。从文件中读取总是意味着读取磁盘块。因此,如果您从文件中读取,请尝试读取完整的块。如果您知道(或可以推断)读取的块有多大,那就更好了。使你的块大小。
    3. 选择您的积木。当您在内存中比较所有块时,从头开始读取每个块是没有意义的。因此,您可以尝试“扩展策略”。从块 0 开始,然后尝试使用 1,如果它们仍然相等,尝试使用 3,使用 7,依此类推。也就是说,与每个块相比,使“块偏移”更大。它可以是指数的(每次将 block_offset 乘以 2),但要考虑到这种方法具有文件开头的特权(也许您可以在通过文件中间后减少偏移量)。

    元数据

    说:如果您对文件有任何控制权(即,您正在生成它们),您应该提取一些元数据并使其可用。比如哈希什么的。

    当然,如果您多次处理一个文件(或一个文件块),您应该尝试生成该元数据。

    希望对你有帮助!

    【讨论】:

    • 哦!如果您想知道一次从磁盘中检索了多少数据(您的最佳块大小),也许您可​​以使用(在 Java 中)InputStream.available()。如果我不记得不好,那会告诉您可以要求读取多少字节而不会被阻止。在 FileInputStream 中,这意味着我可以请求多少字节而不会被从磁盘读取操作阻塞。
    【解决方案5】:

    使用您的操作系统

    您是否尝试比较由您的操作系统计算得出的校验和,例如 md5sum

    大多数现代操作系统都有用于计算文件校验和的实用程序,并且由内核完成通常非常快。

    文件系统

    某些文件系统(brtfs、ZFS、...)具有存储在每个块中的数据的校验和。有了这样的文件系统,计算整个超大文件的校验和应该不难。

    我想知道这样的工具...

    以编程方式

    • 使用与平台上可用的 CPU 一样多的线程
      ExecutorService e = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    • 在每个线程中以只读方式打开两个文件并将文件的非重叠段映射到MappedByteBuffers:

      FileChannel fc1 = new RandomAccessFile(new File("/path/to/file1"), "ro").getChannel(); MappedByteBuffer mem1 = fc1.map(FileChannel.MapMode.READ_ONLY, offset, BUFFER_SIZE); FileChannel fc2 = new RandomAccessFile(new File("/path/to/file2"), "ro").getChannel(); MappedByteBuffer mem2 = fc2.map(FileChannel.MapMode.READ_ONLY, offset, BUFFER_SIZE);

    • 致电Arrays.equals(mem1.array(), mem2.array())

    现在不是跳转到文件中的随机字节,而是跳转到文件的顺序偏移量,在 number_of_available_cores 中比较每个线程的 BUFFER_SIZE 个字节块同时线程。

    BUFFER_SIZE 调整为磁盘上的块大小,以及 虚拟内存 中的页面大小应该会产生非常理想的加速。整个比较中最大的放缓将来自 Virtual MemoryPAGE FAULTSSWAPPING,以及最糟糕的 THRASHING强>。

    here for more information about monitoring VirtMem performance of your code on Linux。在 Windows 上 VMMap 可能会有所帮助。另见this TechNet article on the various counters available in WindowsThis article explaining VirtMem workings on Windows

    以上也意味着顺序处理而不是随机跳转会产生更好的结果,因为它会导致更少的 PAGE_FAULTS 并最小化 VirtMem 页面 THRASHING

    将位向量保存在已经验证的块的内存中,您可以计算出相等的精确确定性。然后,当决​​定比较整个文件时,您所要做的就是访问文件的 尚未访问 块。

    【讨论】:

    • 重新。 md5:你注意到问题第一句中的without first calculating a hash了吗? I would like to know of such tools - 这是报价单吗?从哪里来?
    • 我确实读过这个问题,是的,但是在回答的过程中(我首先做了 programatically 部分)肯定忘记了 no hash 要求。其他两个部分稍后出现,但被认为更有效的是上面介绍的。 I would like to know of such tools 是呼救声,而不是引用。
    • [diginoise] would like to know of such tools 一个肤浅的搜索刚刚发现了一个 pipermail message 提到 ZFS 用户工具。对于传统存储设备,有诸如 READ 之类的标准化命令,以及诸如 READ LONG 之类的不那么标准化的命令。 (问题提到长度相等的 strings,否则长度将成为一个很好的元数据进行比较。)
    猜你喜欢
    • 2019-04-10
    • 1970-01-01
    • 1970-01-01
    • 2018-06-11
    • 2014-10-21
    • 1970-01-01
    • 1970-01-01
    • 2020-07-30
    • 1970-01-01
    相关资源
    最近更新 更多