高效的大字符串(in)相等函数

时间:2011-10-22 17:37:51

标签: java string algorithm hash

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

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

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

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

然后经过一段时间的尝试,我认为他们是平等的,我只是按顺序迭代。

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

任何想法, 安迪

5 个答案:

答案 0 :(得分:2)

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

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

当你去比较两个文件时,你可以在转到磁盘之前先比较已知的子节点哈希值。

这可以为您提供缓存和消除磁盘访问需求之间的良好平衡,而不会给您带来太多开销。

答案 1 :(得分:0)

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

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

标准O(n)解决方案是对每个字符进行常规字符比较,但在这种情况下,您需要更高效的东西。一种可能的解决方案是定义步长,例如, 10,然后比较每10个字符。这比使用随机的优点是你可以节省几个周期来计算随机性,你也不会比较一个字符两次,因为它永远不会碰撞。这种解决方案的问题在于字符串的长前缀通常是相等的。

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

答案 2 :(得分:0)

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

所以你的基本算法将是

选择CHUNK大小?16KB? 选择每个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
}

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

答案 3 :(得分:0)

  1. 比较整个块。比较内存中的整个块的成本低于读取块的成本。所以我建议如果你读一个块,完全比较它的内容。
  2. 你应该阅读必要的块。从文件中读取总是意味着读取磁盘块。因此,如果您从文件中读取,请尝试读取完整的块。如果你知道(或可以推断)块的重要性有多大,那就更好了。让你的大小相当。
  3. 选择您的积分。当你在内存中比较所有块一次时,从一开始就读取每个块是没有意义的。所以你可以尝试一种“扩展策略”。从块0开始,然后尝试1,如果它们保持相等,则尝试使用3,使用7,依此类推。它是,使每个块的“块偏移”更大。它可以是exponencial(每次将block_offset乘以2),但要考虑到这种方法特权文件的开头(也许你可以减少一旦传递到文件中间的偏移量。)
  4. <强>元数据

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

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

    希望它有所帮助!

答案 4 :(得分:0)

使用您的操作系统

您是否尝试比较操作系统计算的md5sum等校验和?

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

文件系统

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

我想知道这些工具......

编程方式

  • 使用与平台上可用的CPU一样多的线程
    ExecutorService e = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
  • 在每个主题中,将这两个文件打开为 READ ONLY ,并将文件的非重叠段映射到MappedByteBuffer s:

    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 调整为磁盘上的块大小,虚拟内存中的页面大小应该会产生更多所需的加速。整个比较中最大的放缓将来自虚拟内存 PAGE FAULTS SWAPPING ,以及最糟糕的 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

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