使用ZipInputStreams和ZipOutpuStreams时,如何避免Scala中的可变变量?

pr1*_*001 16 scala immutability zipinputstream zipoutputstream

我正在尝试读取一个zip文件,检查它是否有一些必需的文件,然后将所有有效文件写入另一个zip文件.java.util.zip基本介绍有很多Java主义,我很想让我的代码更加Scala-native.具体来说,我想避免使用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
Run Code Online (Sandbox Code Playgroud)

我应该补充说我正在使用Scala 2.7.7.

Rex*_*err 34

dI并不认为使用Java类会产生任何特别的错误,这些Java类旨在以其设计的方式以强制性的方式工作.Idiomatic Sc​​ala包括能够按照预期使用惯用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
Run Code Online (Sandbox Code Playgroud)

现在,鉴于此,我们要复制zip文件.我们可以使用的技巧是continually方法collection.immutable.Stream.它的作用是为您执行一个延迟评估的循环.然后,您可以获取并过滤结果以终止并处理您想要的内容.当你有想要成为迭代器的东西时,它是一个方便的模式,但事实并非如此.(如果项目本身的更新,你可以使用.iterateIterableIterator凹口-这通常甚至更好.)下面是应用这种情况下,使用了两次:第一次得到的条目,而一旦数据读取/写块:

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
Run Code Online (Sandbox Code Playgroud)

密切关注.一些线路的尽头!我通常会在一条长线上写这个,但是把它包起来更好,所以你可以在这里看到它.

如果不清楚,让我们解开其中一个用途continually.

Stream.continually(zipIn.read(buffer))
Run Code Online (Sandbox Code Playgroud)

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

.takeWhile(_ != -1)
Run Code Online (Sandbox Code Playgroud)

这指定了必要的次数,返回无限长度的流,但是当它到达时将退出-1.

.foreach(count => zipOut.write(buffer,0,count))
Run Code Online (Sandbox Code Playgroud)

这将处理流,依次获取每个项目(计数),并使用它来写入缓冲区.这有点偷偷摸摸地工作,因为你依赖于zipIn刚被调用以获取流的下一个元素的事实- 如果你再次尝试这样做,而不是一次通过流,它会失败,因为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
Run Code Online (Sandbox Code Playgroud)