Java实现80亿长字符串子串查找

4 篇文章 0 订阅

前言

  • 本博客文章只有代码块,一些输出和 try...catch 等内容没有写在这里。
  • 本博客文章的目的是熟悉 FileReaderFileWriter 这两个类库的操作,Java新手,如有不妥之处请指正!

生成一个80亿长的字符串

  • 80亿个ASCII码字符占用的空间大约为8G,所以只能分步写入硬盘。由于硬盘的速度较慢,所以一次写几个字节至文件是不太划算的,所以我们需要建立一个缓冲区,一次将缓冲区内所有的数据都写入硬盘,这样效率比较高。
  • 建立缓冲区并随机设置数据

    final int BUFFER_LEN = 8 * 1000 * 1000;
    char[] buffer = new char[BUFFER_LEN]; // 缓冲区大小为16M
    
    for (int i = 0; i < BUFFER_LEN; ++i) {
      buffer[i] = (char) (Math.random() * 10 + '0'); // 随机设为'0'到'9'中的值
    }
  • FileWriter 开始写入文件。注意!上一步设置数据时我设置的都是标准ASCII字符,而 FileWriter 在我的平台(win10x64)上写入文件用的是 utf-8 编码,一个标准ASCII字符输出到文件只会占用一个字节。所以我缓冲区的数据写入到文件中就只会占用8M的空间。

    fw = new FileWriter("big.txt");
    for (int i = 0; i < 1000; ++i) {
      fw.write(buffer);
    }
  • 最后别忘了关闭文件

    fw.flush();
    fw.close();
  • 在我的固态硬盘上总共用了25秒,速度还可以。


插入多个子字符串

  • 上一步我生成的字符都为数字,所以这次插入的子串只要为英文字母就可以避免冲突。这里插入100个随机字符串,长度为10~100随机。

  • 生成随机子串。考虑到调试问题,这里的随机子串是固定的。

    final int subNum = 100;
    char[][] subStrBuf = new char[subNum][];
    Random r = new Random(11111111);
    
    for (int i = 0; i < subNum; ++i) {
      int len = r.nextInt(90);
      subStrBuf[i] = new char[len];
      for (int j = 0; j < len; ++j) {
          subStrBuf[i][j] = (char) (r.nextInt(26) + 'a');
      }
    }
  • 从原文件读取,读9次缓冲区就按顺序写入上一步创建的随机子串,同时写入目标文件。因为我没办法获取原文件的字符数量,只好这样。总共需要读取1000次缓冲区,所以所有的子串都能写入目标文件。

    FileReader fr = new FileReader("big.txt");
    FileWriter fw = new FileWriter("big_sub.txt");
    // 建立缓冲区和计数器
    final int bufLen = 8 * 1000 * 1000 + 1;
    char[] cbuf = new char[bufLen];
    int n = 0;
    // 开始读取
    int len;
    while ((len = fr.read(cbuf)) != -1) {
      fw.write(cbuf, 0, len);
      // 读9个块写入一次子串
      if (n % 9 == 0 && n / 9 < subNum) {
          fw.write(subStrBuf[n / 9]);
      }
      n += 1;
  • 关闭文件

    fr.close();
    fw.flush();
    fw.close();
  • 这次需要读取+写入,所以耗时会比第一部慢,耗时71秒。


从文件中查找子串

  • 思路是分块从文件中读取内容到缓冲区,然后再缓冲区内进行子串的查找。考虑到子串可能跨缓冲区,所以从第二次及以后的读入操作,都要保留上一次缓冲区的末尾内容,这个末尾的长度要高于子串的长度。

  • 准备工作。设置一个数组来存放查找位置,找到多个子串的最大长度来确定缓冲区末尾的长度,以及打开文件。这里的 subStrBuf 是一个二维数组(char[][]),存放着多个随机子字符串,具体定义可以看上一步。

    long[] indexs = new long[subStrBuf.length];
    for (int i = 0; i < subStrBuf.length; ++i) {
      indexs[i] = -1;
    }
    
    int maxLen = 0;
    for (int i = 0; i < subStrBuf.length; ++i) {
      if (subStrBuf[i].length > maxLen) {
          maxLen = subStrBuf.length;
      }
    }
    
    FileReader fr = new FileReader(fileName);
  • 定义缓冲区

    final int bufLen = 8 * 1000 * 1000;
    char[] cbuf = new char[bufLen];
  • 读取内容到缓冲区并获取子串的位置。

    long n = 0;  // n存放当前的文件的绝对位置
    while (true) {
      int len; // len存放读取到的内容的长度,为-1时代表文件结束
      if (n == 0) {
          len = fr.read(cbuf);
      } else {
          // 保留上一个缓冲区的末尾作为这次缓冲区的头部。
          for (int i = 0; i < maxLen; ++i) {
              cbuf[i] = cbuf[bufLen - maxLen + i];
          }
          len = fr.read(cbuf, maxLen, bufLen - maxLen);
      }
      if (len == -1) break; // 文件结束
    
      // 遍历子字符串
      for (int i = 0; i < subStrBuf.length; ++i) {
          // 如果位置值不是-1就代表查找到了,不用再查
          if (indexs[i] == -1) {
              // 自己写的Find函数,获取子串在cbuf中的位置
              int tempIndex = Find(cbuf, subStrBuf[i]);
              if (tempIndex != -1) {
                  // n为cbuf在整个文件中的位置
                  indexs[i] = n + tempIndex;
              }
          }
      }
      n += len;
    } // 文件结束
  • Find函数的定义。

    private static int Find(char[] buf, char[] subStr) {
      // i为母串的位置
      for (int i = 0; i < buf.length; ++i) {
          // j为子串的位置
          int j;
          for (j = 0; j < subStr.length; ++j) {
              // 判断越界
              if (i + j >= buf.length) return -1;
              // 判断是否是子串
              if (buf[i + j] != subStr[j]) break;
          }
          // 循环正常退出就说明是子串,返回位置
          if (j == subStr.length) return i;
      }
      return -1;
    }
  • 因为这个原始查找算法的时间复杂度很高(O(n^2)),所以程序运行的时间为11分钟。


结果感言

  • 算法很重要啊。如果查找算法足够优秀,那从文件中查找子串的时间会大大缩小。
  • 我的硬盘是固态硬盘(读500M/s,写400M/s),机械硬盘(一般读150M,写100M)的运行时间应该会慢很多。
  • 刚开始的时候完全没必要将文件大小设为8G,这样调试什么的很不方便。
  • fr.read(buf, offset, length); 这个函数我一直以为是 fr.read(buf, start, end);,结果一运行就报下标越界,找了许久才发现是用错了。IDE给参数提示的时候一定要认真啊。
  • 这整个程序用的是C语言的贴近底层的风格,完全没有体现出Java这种高级面向对象语言的优点,失败啊。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值