我正在尝试读取zip文件,检查它是否包含一些必需的文件,然后将所有有效文件写入另一个zip文件。 basic introduction to 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
我应该补充说我正在使用Scala 2.7.7。
答案 0 :(得分:34)
dI我认为使用Java类并不是特别错误,这些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
方法。它的作用是为您执行一个延迟评估的循环。然后,您可以获取并过滤结果以终止并处理您想要的内容。当你有想要成为迭代器的东西时,它是一个方便的模式,但事实并非如此。 (如果该项目更新,您可以使用.iterate
或Iterable
中的Iterator
- 这通常会更好。)以下是此案例的应用,使用两次:一次获取条目,并且一次读/写数据块:
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)
这指定了必要的次数,返回无限长度的流,但当它到达-1
时将退出。
.foreach(count => zipOut.write(buffer,0,count))
这将处理流,依次获取每个项目(计数),并使用它来写入缓冲区。这有点偷偷摸摸地工作,因为你依赖于刚刚调用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
答案 1 :(得分: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()
}
答案 2 :(得分: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)
答案 3 :(得分: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)
}
}
答案 4 :(得分:1)
没有尾递归,我会避免递归。你会冒着堆栈溢出的风险。您可以将zipIn.read(data)
包裹在scala.BufferedIterator[Byte]
中并从那里开始。