使用Scala Iterator使用RegEx匹配将大数据流(从字符串)分解成多个块,然后对这些块进行操作?

时间:2019-07-14 04:17:38

标签: regex scala stream iterator chunking

我目前正在使用一种不太像Scala的方法来解析大型Unix邮箱文件。我仍在学习该语言,并想挑战自己以找到更好的方法,但是,我不相信我对Iterator可以做什么以及如何有效使用它有扎实的了解。

我当前正在使用 org.apache.james.mime4j,并且我使用org.apache.james.mime4j.mboxiterator.MboxIterator从文件中获取java.util.Iterator,如下所示:

 // registers an implementation of a ContentHandler that
 // allows me to construct an object representing an email
 // using callbacks
 val handler: ContentHandler = new MyHandler();

 // creates a parser that parses a SINGLE email from a given InputStream
 val parser: MimeStreamParser = new MimeStreamParser(configBuilder.build());
 // register my handler
 parser.setContentHandler(handler);

 // Get a java.util.Iterator
 val iterator = MboxIterator.fromFile(fileName).build();
 // For each email, process it using above Handler
 iterator.forEach(p => parser.parse(p.asInputStream(Charsets.UTF_8)))

根据我的理解,Scala Iterator更加健壮,并且可能具有更多类似的处理能力,特别是因为我将无法始终将整个文件容纳在内存中。

我需要构建自己的MboxIterator版本。我翻阅了MboxIterator的源代码,能够找到一个很好的RegEx模式来确定各个电子邮件的开头,但是,从现在开始,我一直在空白。

我这样创建了RegEx:

 val MESSAGE_START = Pattern.compile(FromLinePatterns.DEFAULT, Pattern.MULTILINE);

我想做的事情(根据我到目前为止的了解):

  • 从MBOX文件构建FileInputStream
  • 使用Iterator.continually(stream.read())浏览流
  • 使用.takeWhile()继续阅读直到流结束
  • 使用MESSAGE_START.matcher(someString).find()之类的内容对流进行分块,或使用它来查找与消息分开的索引
  • 读取创建的块,或读取创建的索引之间的位

我觉得我应该可以使用map()find()filter()collect()来完成此任务,但是我被事实抛弃了他们只给我Int一起工作。

我该怎么做?

编辑:

在对该主题进行了更多思考之后,我想到了另一种描述我思考我需要做的事情:

  1. 我需要继续从流中读取内容,直到获得与RegEx匹配的字符串为止

  2. 也许group是先前读取的字节?

  3. 将其发送以在某处进行处理

  4. 以某种方式将其从作用域中删除,以便下次我遇到比赛时不会将其分组

  5. 继续读取流,直到找到下一个匹配项。

  6. 利润?

编辑2:

我想我越来越近了。使用这样的方法可以让我得到一个迭代器。但是,有两个问题:1.这是否浪费内存?这是否意味着所有内容都已读入内存? 2.我仍然需要找出一种将{em>除以match的方法,但是仍将其包含在返回的迭代器中。

def split[T](iter: Iterator[T])(breakOn: T => Boolean): 
    Iterator[Iterator[T]] =
        new Iterator[Iterator[T]] {
           def hasNext = iter.hasNext

           def next = {
              val cur = iter.takeWhile(!breakOn(_))
              iter.dropWhile(breakOn)
              cur
            }
 }.withFilter(l => l.nonEmpty)  

1 个答案:

答案 0 :(得分:0)

如果我的理解正确,那么您想懒散地分割由正则表达式可识别的模式分隔的大文件。

您可以尝试为每个请求返回一个Iterator,但是正确的迭代器管理并不容易。

我倾向于对客户端隐藏所有文件和迭代器管理。

class MBox(filePath :String) {
  private val file   = io.Source.fromFile(filePath)
  private val itr    = file.getLines().buffered
  private val header = "From .+ \\d{4}".r  //adjust to taste

  def next() :Option[String] =
    if (itr.hasNext) {
      val sb = new StringBuilder()
      sb.append(itr.next() + "\n")
      while (itr.hasNext && !header.matches(itr.head))
        sb.append(itr.next() + "\n")
      Some(sb.mkString)
    } else {
      file.close()
      None
    }
}

测试:

val mbox = new MBox("so.txt")
mbox.next()
//res0: Option[String] =
//Some(From MAILER-DAEMON Fri Jul  8 12:08:34 2011
//some text AAA
//some text BBB
//)

mbox.next()
//res1: Option[String] =
//Some(From MAILER-DAEMON Mon Jun  8 12:18:34 2012
//small text
//)

mbox.next()
//res2: Option[String] =
//Some(From MAILER-DAEMON Tue Jan  8 11:18:14 2013
//some text CCC
//some text DDD
//)

mbox.next()  //res3: Option[String] = None

每个打开的文件中只有一个Iterator,并且仅在其上调用安全方法。仅在请求时才实现(加载)文件文本,并且客户端将仅获取请求的内容(如果有)。如果更适用,则可以将每一行作为集合String的一部分返回,而不是将它们放在一个长Seq[String]中。