Scala:读取枚举器[T]的一些数据并返回剩余的枚举器[T]

时间:2012-06-08 13:57:45

标签: scala playframework playframework-2.0 enumerator iterate

我正在使用playframework的异步I / O库,它使用Iteratees和Enumerators。我现在有一个Iterator [T]作为数据接收器(简化说它是一个Iterator [Byte],它将其内容存储到一个文件中)。这个Iterator [Byte]被传递给处理写作的函数。

但是在写之前我想在文件开头添加一些统计信息(为简化说它是一个字节),所以我在将迭代器传递给write函数之前按以下方式传递它:

def write(value: Byte, output: Iteratee[Byte]): Iteratee[Byte] =
    Iteratee.flatten(output.feed(Input.El(value)))

当我现在从磁盘读取存储的文件时,我得到了一个Enumerator [Byte]。 首先,我想读取并删除其他数据,然后我想将其余的Enumerator [Byte]传递给处理读取的函数。 所以我还需要转换枚举器:

def read(input: Enumerator[Byte]): (Byte, Enumerator[Byte]) = {
   val firstEnumeratorEntry = ...
   val remainingEnumerator = ...
   (firstEnumeratorEntry, remainingEnumerator)
}

但我不知道如何做到这一点。如何从枚举器中读取一些字节并获取剩余的枚举器?

使用InputStream替换带有OutputStream和Enumerator [Byte]的Iteratee [Byte],这将非常简单:

def write(value: Byte, output: OutputStream) = {
    output.write(value)
    output
}
def read(input: InputStream) = (input.read,input)

但我需要播放框架的异步I / O.

3 个答案:

答案 0 :(得分:3)

我想知道你是否可以从另一个角度解决你的目标。

该函数将使用剩余的枚举器,我们称之为remaining,大概它适用于迭代器来处理余数:remaining |>> iteratee产生另一个迭代。让我们调用生成的迭代iteratee2 ...你能检查一下你是否可以获得iteratee2的引用?如果是这种情况,那么您可以使用第一个迭代head获取并处理第一个字节,然后将headiteratee2组合到flatMap中:

val head = Enumeratee.take[Byte](1) &>> Iteratee.foreach[Byte](println)
val processing = for { h <- head; i <- iteratee2 } yield (h, i)
Iteratee.flatten(processing).run

如果你无法掌握iteratee2 - 如果你的调查员与你没有实施的枚举者结合就是这种情况 - 那么这种方法将不起作用。

答案 1 :(得分:1)

这是通过在Iteratee和适当的(种类)状态累加器(此处为元组)内进行折叠来实现此目的的一种方法

我去了routes文件,第一个字节将被读作Char,另一个将作为UTF-8字节串附加到String

  def index = Action {
    /*let's do everything asyncly*/
    Async {
      /*for comprehension for read-friendly*/
      for (
        i <- read; /*read the file */
        (r:(Option[Char], String)) <- i.run /*"create" the related Promise and run it*/
      ) yield Ok("first : " + r._1.get + "\n" + "rest" + r._2) /* map the Promised result in a correct Request's Result*/
    }
  }


  def read = {
    //get the routes file in an Enumerator
    val file: Enumerator[Array[Byte]] = Enumerator.fromFile(Play.getFile("/conf/routes"))

    //apply the enumerator with an Iteratee that folds the data as wished
    file(Iteratee.fold((None, ""):(Option[Char], String)) { (acc, b) =>
       acc._1 match {
         /*on the first chunk*/ case None => (Some(b(0).toChar), acc._2 + new String(b.tail, Charset.forName("utf-8")))
         /*on other chunks*/ case x => (x, acc._2 + new String(b, Charset.forName("utf-8")))
       }
    })

  }

修改

我发现了使用Enumeratee的另一种方式,但它需要创建2 Enumerator s(一个短暂的)。但它更优雅一点。我们使用“类型”枚举,但Traversal使用比Enumeratee(chunck级别)更精细的级别。 我们使用只需1个字节的take 1然后关闭流。另一方面,我们使用drop只删除第一个字节(因为我们使用的是枚举器[Array [Byte]])

此外,现在read2的签名比你想要的更接近,因为它返回2个枚举器(距离Promise,Enumerator不远)

def index = Action {
  Async {
    val (first, rest) = read2
    val enee = Enumeratee.map[Array[Byte]] {bs => new String(bs, Charset.forName("utf-8"))}

    def useEnee(enumor:Enumerator[Array[Byte]]) = Iteratee.flatten(enumor &> enee |>> Iteratee.consume[String]()).run.asInstanceOf[Promise[String]]

    for {
      f <- useEnee(first);
      r <- useEnee(rest)
    } yield Ok("first : " + f + "\n" + "rest" + r)
  }
}

def read2 = {
  def create = Enumerator.fromFile(Play.getFile("/conf/routes"))

  val file: Enumerator[Array[Byte]] = create
  val file2: Enumerator[Array[Byte]] = create

  (file &> Traversable.take[Array[Byte]](1), file2 &> Traversable.drop[Array[Byte]](1))

}

答案 2 :(得分:1)

实际上我们喜欢Iteratee因为它们构成了。因此,不是从原始的Enumerator创建多个andThen,而是依次编写两个Iteratees(先读和读取),并将其与单个枚举器一起提供。

为此你需要一个顺序组合方法,现在我称之为object Iteratees { def andThen[E, A, B](a: Iteratee[E, A], b: Iteratee[E, B]): Iteratee[E, (A,B)] = new Iteratee[E, (A,B)] { def fold[C]( done: ((A, B), Input[E]) => Promise[C], cont: ((Input[E]) => Iteratee[E, (A, B)]) => Promise[C], error: (String, Input[E]) => Promise[C]): Promise[C] = { a.fold( (ra, aleft) => b.fold( (rb, bleft) => done((ra, rb), aleft /* could be magicop(aleft, bleft)*/), (bcont) => cont(e => bcont(e) map (rb => (ra, rb))), (s, err) => error(s, err) ), (acont) => cont(e => andThen[E, A, B](acont(e), b)), (s, err) => error(s, err) ) } } } 。这是一个粗略的实现。请注意,返回未使用的输入有点苛刻,也许可以使用基于Input类型的类型类自定义行为。它也不处理将剩余的东西从第一个迭代器传递到第二个迭代器(练习:)。

object Application extends Controller {

  def index = Action { Async {

    val strings: Enumerator[String] = Enumerator("1","2","3","4")
    val takeOne = Cont[String, String](e => e match {
      case Input.El(e) => Done(e, Input.Empty)
      case x => Error("not enough", x)
    })
    val takeRest = Iteratee.consume[String]()
    val firstAndRest = Iteratees.andThen(takeOne, takeRest)

    val futureRes = strings(firstAndRest) flatMap (_.run)

    futureRes.map(x => Ok(x.toString)) // prints (1,234)
  } }

}

现在您可以使用以下内容:

{{1}}