Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/scala/19.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
在使用ZipInputStreams和ZipOutpuStreams时,如何避免Scala中的可变变量?_Scala_Immutability_Zipinputstream_Zipoutputstream - Fatal编程技术网

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

在使用ZipInputStreams和ZipOutpuStreams时,如何避免Scala中的可变变量?,scala,immutability,zipinputstream,zipoutputstream,Scala,Immutability,Zipinputstream,Zipoutputstream,我正在尝试读取一个zip文件,检查它是否有一些必需的文件,然后将所有有效文件写入另一个zip文件。有很多Java ISM,我想让我的代码更具Scala本地性。具体来说,我希望避免使用vars。以下是我所拥有的: val fos = new FileOutputStream("new.zip"); val zipOut = new ZipOutputStream(new BufferedOutputStream(fos)); while (zipIn.available == 1) { va

我正在尝试读取一个zip文件,检查它是否有一些必需的文件,然后将所有有效文件写入另一个zip文件。有很多Java ISM,我想让我的代码更具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。

如果没有尾部递归,我会避免递归。您将面临堆栈溢出的风险。您可以将zipIn.read(data)包装在一个
scala.BufferedIterator[Byte]
中,然后从那里开始。

使用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()
}

我不认为使用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文件。我们可以使用的技巧是
collection.immutable.Stream
中的
continuously
方法。它所做的是为您执行一个延迟评估的循环。然后,您可以获取并过滤结果以终止并处理所需内容。当你想要成为一个迭代器时,这是一个方便的模式,但它不是。(如果项目本身更新,您可以在
Iterable
Iterator
中使用
.iterate
——这通常更好。)下面是本例的应用程序,使用了两次:一次获取条目,一次读取/写入数据块:

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
请密切注意某些行末尾的
!我通常会把它写在一条长线上,但最好是把它包起来,这样你就可以在这里看到所有的内容

为了防止不清楚,让我们继续解开
的一个用法

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

.takeWhile(_ != -1)
这指定需要多少次,返回长度不定的流,但当它碰到
-1
时将退出

.foreach(count => zipOut.write(buffer,0,count))
这将处理流,依次获取每个项(计数),并使用它写入缓冲区。这有点诡秘,因为您依赖于刚刚调用了
zipIn
来获取流的下一个元素这一事实——如果您尝试再次这样做,而不是在流的一次传递中,它将失败,因为
buffer
将被覆盖。但这里没关系

所以,这就是:一种更紧凑、更容易理解、更不容易理解、更实用的方法(尽管仍然有大量的副作用)。相比之下,在2.7.7中,我实际上会用Java的方式来实现,因为
Stream.continuously
是不可用的,对于这种情况,构建自定义
迭代器的开销是不值得的。(不过,如果我打算做更多的zip文件处理,并且可以重用代码,那就值得了。)


编辑:查找可用的归零方法对于检测zip文件的结尾有点古怪。我认为“正确”的方法是等待从
getNextEntry
返回
null
。考虑到这一点,我编辑了前面的代码(有一个
takeWhile(=>zipIn.available==1)
,现在是
takeWhile(!=null)
),并在下面提供了一个基于2.7.7迭代器的版本(注意,一旦您完成了定义迭代器的工作,主循环是多么小,它确实使用了vars):


我会尝试这样的方法(是的,与我的想法几乎相同):

它可以简化如下,但我不太喜欢它。我希望
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)
基于:


因为我太懒了,
newarray[Byte]
导致编译器抱怨替代构造函数。我想我应该使用
newarraybuffer[Byte]
.var data=newarraybuffer[Byte](1024)Ok。。。你是说没有更好的方法了吗?对不起,我花了几分钟想了些什么。谢谢,看起来很不错。不幸的是,我应该指定我仍然在2.7.7。另外,在2.7.7和2.8中,这将如何爆发?我对尾部递归不是很熟悉。谢谢。@pr1001 Scala 2.8尽可能优化尾部调用,以避免堆栈溢出。对于什么是tail call的介绍,我建议您阅读以下条目:谢谢,Rex,这是一个非常好的答案。ZipIter的最新版本有一个严重的bug。调用getNextEntry实际上会推进流指针,因此您的条目引用的内容与流设置的内容不同。例如,如果你有A.txt B.txt,你会得到A.txt的条目,但读了B.txt,然后得到B.txt的条目,我认为什么也读不到。@BillBarksdale-很好的回答!我正在修复它。无法计算此评论的格式,而是将其作为答案发布:p尽管如此,这似乎不能让您轻松获得实际的文件内容。。。多么痛苦的界面
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)
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)
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)
  }
}