17

zip ファイルを読み取ろうとしており、必要なファイルがいくつかあることを確認してから、有効なすべてのファイルを別の zip ファイルに書き出そうとしています。java.util.zipの基本的な紹介には多くの Java 主義が含まれており、自分のコードをより Scala ネイティブにしたいと考えています。具体的には、 の使用は避けたいと思いますvars。ここに私が持っているものがあります:

val fos = new FileOutputStream("new.zip");
val zipOut = new ZipOutputStream(new BufferedOutputStream(fos));

while (zipIn.available == 1) {
  val entry = zipIn.getNextEntry
  if (entryIsValid(entry)) {
    zipOut.putNewEntry(new ZipEntry("subdir/" + entry.getName())
    // read data into the data Array
    var data = Array[Byte](1024)
    var count = zipIn.read(data, 0, 1024)
    while (count != -1) {
      zipOut.write(data, 0, count)
      count = zipIn.read(data, 0, 1024)
    }
  }
  zipIn.close
}
zipOut.close

私は Scala 2.7.7 を使用していることを付け加えておきます。

4

5 に答える 5

35

d設計された方法で命令型に動作するように設計されたJavaクラスを使用することに特に問題はないと思います。慣用的なScalaには、スタイルが少し衝突する場合でも、意図したとおりに慣用的なJavaを使用できることが含まれています。

ただし、演​​習として、またはロジックがわずかに明確になっているために、より機能的な変数のない方法でこれを実行したい場合は、そうすることができます。2.8では特に素晴らしいので、2.7.7を使用していても、2.8の答えを出します。

まず、問題を設定する必要がありますが、完全には設定していませんが、次のようなものがあるとします。

import java.io._
import java.util.zip._
import scala.collection.immutable.Stream

val fos = new FileOutputStream("new.zip")
val zipOut = new ZipOutputStream(new BufferedOutputStream(fos))
val zipIn = new ZipInputStream(new FileInputStream("old.zip"))
def entryIsValid(ze: ZipEntry) = !ze.isDirectory

これを前提として、zipファイルをコピーします。使用できるトリックは、のcontinuallyメソッドですcollection.immutable.Stream。それが行うことは、遅延評価されたループを実行することです。次に、結果を取得してフィルタリングし、必要なものを終了して処理できます。イテレータにしたいものがある場合に使用するのに便利なパターンですが、そうではありません。(アイテムがそれ自体を更新する場合は.iterateIterableまたはで使用できますIterator。通常はさらに優れています。)これがこの場合のアプリケーションです。2回使用されます。1回はエントリの取得に、もう1回はデータのチャンクの読み取り/書き込みに使用されます。

val buffer = new Array[Byte](1024)
Stream.continually(zipIn.getNextEntry).
  takeWhile(_ != null).filter(entryIsValid).
  foreach(entry => {
    zipOut.putNextEntry(new ZipEntry("subdir/"+entry.getName))
    Stream.continually(zipIn.read(buffer)).takeWhile(_ != -1).
      foreach(count => zipOut.write(buffer,0,count))
  })
}
zipIn.close
zipOut.close

.いくつかの行の終わりに細心の注意を払ってください!私は通常これを1つの長い行に書き込みますが、ここですべてを確認できるように折り返す方がよいでしょう。

明確でない場合に備えて、の使用法の1つを開梱しましょうcontinually

Stream.continually(zipIn.read(buffer))

zipIn.read(buffer)これは、結果の整数を格納して、必要な回数だけ呼び出しを続けるように要求します。

.takeWhile(_ != -1)

これは、必要な回数を指定し、長さが不定のストリームを返しますが、。にヒットすると終了し-1ます。

.foreach(count => zipOut.write(buffer,0,count))

これにより、ストリームが処理され、各アイテムが順番に(カウント)取得され、それを使用してバッファーが書き込まれます。これは少し卑劣な方法で機能します。これは、ストリームの次の要素を取得するために呼び出されたばかりの事実に依存しているためですzipIn。ストリームを1回通過するのではなく、もう一度これを実行しようとすると、失敗します。buffer上書きされます。しかし、ここでは大丈夫です。

つまり、それは次のとおりです。少しコンパクトで、おそらく理解しやすく、おそらくより機能的で理解しにくい方法です(ただし、まだ多くの副作用があります)。対照的に、2.7.7では、利用できないため、実際にはJavaの方法で実行します。この場合Stream.continually、カスタムを構築するオーバーヘッドは価値がありません。Iterator(ただし、より多くのzipファイル処理を実行し、コードを再利用できれば、それだけの価値があります。)


編集:ゼロを探す方法は、zipファイルの終わりを検出するための一種の不安定な方法です。null「正しい」方法は、から戻るまで待つことだと思いますgetNextEntry。そのことを念頭に置いて、以前のコードを編集し(takeWhile(_ => zipIn.available==1)現在はtakeWhile(_ != null))、以下に2.7.7イテレーターベースのバージョンを提供しました(イテレーターの定義作業を終えたら、メインループがどれほど小さいかに注意してください)。 、確かにvarsを使用します):

val buffer = new Array[Byte](1024)
class ZipIter(zis: ZipInputStream) extends Iterator[ZipEntry] {
  private var entry:ZipEntry = zis.getNextEntry
  private var cached = true
  private def cache { if (entry != null && !cached) {
    cached = true; entry = zis.getNextEntry
  }}
  def hasNext = { cache; entry != null }
  def next = {
    if (!cached) cache
    cached = false
    entry
  }
}
class DataIter(is: InputStream, ab: Array[Byte]) extends Iterator[(Int,Array[Byte])] {
  private var count = 0
  private var waiting = false
  def hasNext = { 
    if (!waiting && count != -1) { count = is.read(ab); waiting=true }
    count != -1
  }
  def next = { waiting=false; (count,ab) }
}
(new ZipIter(zipIn)).filter(entryIsValid).foreach(entry => {
  zipOut.putNextEntry(new ZipEntry("subdir/"+entry.getName))
  (new DataIter(zipIn,buffer)).foreach(cb => zipOut.write(cb._2,0,cb._1))
})
zipIn.close
zipOut.close
于 2010-05-17T15:20:41.913 に答える
2

私はこのようなことを試してみます(はい、sblundyが持っていたのとほとんど同じアイデアです):

Iterator.continually {
  val data = new Array[Byte](100)
  zipIn.read(data) match {
    case -1 => Array.empty[Byte]
    case 0  => new Array[Byte](101) // just to filter it out
    case n  => java.util.Arrays.copyOf(data, n)
  }
} filter (_.size != 101) takeWhile (_.nonEmpty)

以下のように簡略化できますが、私はあまり好きではありません。read0を返せないようにしたい...

Iterator.continually {
  val data = new Array[Byte](100)
  zipIn.read(data) match {
    case -1 => new Array[Byte](101)
    case n  => java.util.Arrays.copyOf(data, n)
  }
} takeWhile (_.size != 101)
于 2010-05-17T16:00:53.330 に答える
2

scala2.8 と末尾再帰呼び出しを使用:

def copyZip(in: ZipInputStream, out: ZipOutputStream, bufferSize: Int = 1024) {
  val data = new Array[Byte](bufferSize)

  def copyEntry() {
    in getNextEntry match {
      case null =>
      case entry => {
        if (entryIsValid(entry)) {
          out.putNextEntry(new ZipEntry("subdir/" + entry.getName()))

          def copyData() {
            in read data match {
              case -1 =>
              case count => {
                out.write(data, 0, count)
                copyData()
              }
            }
          }
          copyData()
        }
        copyEntry()
      }
    }
  }
  copyEntry()
}
于 2010-05-17T14:34:44.660 に答える
2

http://harrah.github.io/browse/samples/compiler/scala/tools/nsc/io/ZipArchive.scala.htmlに基づく:

private[io] class ZipEntryTraversableClass(in: InputStream) extends Traversable[ZipEntry] {
  val zis = new ZipInputStream(in)

  def foreach[U](f: ZipEntry => U) {
    @tailrec
    def loop(x: ZipEntry): Unit = if (x != null) {
      f(x)
      zis.closeEntry()
      loop(zis.getNextEntry())
    }
    loop(zis.getNextEntry())
  }

  def writeCurrentEntryTo(os: OutputStream) {
    IOUtils.copy(zis, os)
  }
}
于 2013-05-27T22:22:57.260 に答える
1

末尾再帰がなければ、再帰は避けます。スタック オーバーフローのリスクがあります。ラップzipIn.read(data)してscala.BufferedIterator[Byte]そこから移動できます。

于 2010-05-17T13:33:16.183 に答える