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 类并没有什么特别的错误,这些 Java 类被设计为按照它们设计的方式以命令式方式工作。惯用的 Scala 包括能够按预期使用惯用的 Java,即使样式确实有点冲突。

但是,如果您希望(可能是作为练习,或者可能因为它确实稍微阐明了逻辑)以更实用的无 var 方式执行此操作,您可以这样做。在 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 文件。我们可以使用的技巧continuallycollection.immutable.Stream. 它所做的是为您执行一个惰性求值循环。然后,您可以获取并过滤结果以终止和处理您想要的结果。当您想要成为迭代器时使用它是一种方便的模式,但事实并非如此。(如果项目自行更新,您可以使用.iterateinIterableIterator-- 这通常会更好。)这是此案例的应用程序,使用了两次:一次用于获取条目,一次用于读取/写入数据块:

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

密切注意.一些行的末尾!我通常会把它写在一条长线上,但最好把它包起来,这样你就可以在这里看到所有内容。

以防万一不清楚,让我们解压缩continually.

Stream.continually(zipIn.read(buffer))

这要求根据需要继续调用zipIn.read(buffer)多次,存储结果的整数。

.takeWhile(_ != -1)

这指定了需要多少次,返回一个不定长度的流,但是当它到达 a 时它将退出-1

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

这会处理流,依次获取每个项目(计数),并使用它来写入缓冲区。这有点偷偷摸摸地工作,因为你依赖于zipIn刚刚被调用来获取流的下一个元素的事实——如果你再次尝试这样做,而不是单次通过流,它会失败,因为buffer将被覆盖。但是这里没关系。

所以,它就是:一种更紧凑、可能更容易理解、可能不太容易理解的更实用的方法(尽管仍然有很多副作用)。相比之下,在 2.7.7 中,我实际上会以 Java 的方式来做,因为Stream.continually它不可用,而且在Iterator这种情况下构建自定义的开销是不值得的。(但是,如果我要进行更多的 zip 文件处理并且可以重用代码,那将是值得的。)


编辑:寻找可用的归零方法对于检测 zip 文件的结尾有点不稳定。我认为“正确”的方法是等到您nullgetNextEntry. 考虑到这一点,我编辑了之前的代码(有 atakeWhile(_ => zipIn.available==1)现在是 a 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

使用 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

我会尝试这样的事情(是的, 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)

它可以像下面这样简化,但我不是很喜欢它。我宁愿read不能返回 0 ...

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

基于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 回答