6

この質問の不自然さを許してください。しかし、私は潜在的に大きなファイルをサーバーに送信し、フォーマットを解析させたいWebアプリケーションを持っています。私は Play20 フレームワークを使用していますが、Scala は初めてです。

たとえば、csv がある場合、各行を「,」で分割し、最終的List[List[String]]に各フィールドで を作成したいと思います。

現在、これを行う最善の方法は BodyParser を使用することだと考えています (ただし、間違っている可能性があります)。私のコードは次のようになります。

Iteratee.fold[String, List[List[String]]]() {
  (result, chunk) =>
    result = chunk.splitByNewLine.splitByDelimiter // Psuedocode
}

私の最初の質問は、チャンクが行の途中で分割された以下のような状況にどのように対処するかです:

Chunk 1:
1,2,3,4\n
5,6

Chunk 2:
7,8\n
9,10,11,12\n

2 番目の質問は、自分で BodyParser を作成するのは正しい方法ですか? このファイルを解析するより良い方法はありますか? 私の主な懸念は、ファイルを非常に大きくして、ある時点でバッファをフラッシュし、ファイル全体をメモリに保持しないようにすることです。

4

2 に答える 2

10

csv にエスケープされた改行が含まれていない場合、ファイル全体をメモリに格納せずにプログレッシブ解析を実行するのは非常に簡単です。iteratee ライブラリには、内部にメソッド検索が付属していますplay.api.libs.iteratee.Parsing

def search (needle: Array[Byte]): Enumeratee[Array[Byte], MatchInfo[Array[Byte]]]

Matched[Array[Byte]]ストリームをとに分割しますUnmatched[Array[Byte]]

次に、ヘッダーを受け取る最初の iteratee と、一致しない結果に折り畳まれる別の iteratee を組み合わせることができます。これは次のコードのようになります。

// break at each match and concat unmatches and drop the last received element (the match)
val concatLine: Iteratee[Parsing.MatchInfo[Array[Byte]],String] = 
  ( Enumeratee.breakE[Parsing.MatchInfo[Array[Byte]]](_.isMatch) ><>
    Enumeratee.collect{ case Parsing.Unmatched(bytes) => new String(bytes)} &>>
    Iteratee.consume() ).flatMap(r => Iteratee.head.map(_ => r))

// group chunks using the above iteratee and do simple csv parsing
val csvParser: Iteratee[Array[Byte], List[List[String]]] =
  Parsing.search("\n".getBytes) ><>
  Enumeratee.grouped( concatLine ) ><>
  Enumeratee.map(_.split(',').toList) &>>
  Iteratee.head.flatMap( header => Iteratee.getChunks.map(header.toList ++ _) )

// an example of a chunked simple csv file
val chunkedCsv: Enumerator[Array[Byte]] = Enumerator("""a,b,c
""","1,2,3","""
4,5,6
7,8,""","""9
""") &> Enumeratee.map(_.getBytes)

// get the result
val csvPromise: Promise[List[List[String]]] = chunkedCsv |>>> csvParser

// eventually returns List(List(a, b, c),List(1, 2, 3), List(4, 5, 6), List(7, 8, 9))

もちろん、解析を改善できます。もしそうなら、コミュニティと共有していただければ幸いです。

したがって、Play2 コントローラーは次のようになります。

val requestCsvBodyParser = BodyParser(rh => csvParser.map(Right(_)))

// progressively parse the big uploaded csv like file
def postCsv = Action(requestCsvBodyParser){ rq: Request[List[List[String]]] => 
  //do something with data
}
于 2012-06-15T23:16:18.920 に答える
1

の 2 倍のサイズをメモリに保持してもかまわない場合は、List[List[String]]次のようなボディ パーサーを使用できますplay.api.mvc.BodyParsers.parse.tolerantText

def toCsv = Action(parse.tolerantText) { request =>
  val data = request.body
  val reader = new java.io.StringReader(data)
  // use a Java CSV parsing library like http://opencsv.sourceforge.net/
  // to transform the text into CSV data
  Ok("Done")
}

メモリ消費量を減らしたい場合は、可変データまたは不変データを処理する場合にArray[Array[String]]orを使用することをお勧めします。Vector[Vector[String]]

本当に大量のデータ (または中サイズのデータ​​の要求が失われる) を扱っており、処理を段階的に行うことができる場合は、独自のボディ パーサーのローリングを検討できます。その本体パーサーは を生成しませんList[List[String]]が、代わりに行が来るたびに解析し、各行を増分結果に折り畳みます。ただし、特に CSV で二重引用符を使用してコンマ、改行、または二重引用符を含むフィールドをサポートしている場合は、これを行うのはかなり複雑です。

于 2012-06-14T05:36:50.450 に答える