dI ne pense pas qu'il y ait quoi que ce soit particulièrement mal à utiliser des classes Java qui sont conçus pour fonctionner en mode impératif de la façon qu'ils ont été conçus. Idiomatic Scala inclut la possibilité d'utiliser le Java idiomatique comme il était prévu, même si les styles se contrarient un peu. Cependant, si vous le souhaitez - peut-être en tant qu'exercice, ou peut-être parce qu'il clarifie légèrement la logique - vous pouvez le faire d'une manière plus fonctionnelle, sans var-free. En 2.8, c'est particulièrement agréable, donc même si vous utilisez 2.7.7, je vais donner une réponse de 2.8.
D'abord, nous avons besoin de mettre en place le problème, que vous ne l'avez pas tout à fait, mais supposons que nous avons quelque chose comme ceci:
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
Maintenant, compte tenu de ce que nous voulons copier le fichier zip. L'astuce que nous pouvons utiliser est la méthode continually
en collection.immutable.Stream
. Ce qu'il fait est effectuer une boucle paresseusement évaluée pour vous. Vous pouvez ensuite prendre et filtrer les résultats pour terminer et traiter ce que vous voulez. C'est un modèle pratique à utiliser lorsque vous avez quelque chose que vous voulez être un itérateur, mais ce n'est pas le cas. (Si l'article se met à jour, vous pouvez utiliser .iterate
dans Iterable
ou Iterator
- c'est généralement encore mieux.) Voici l'application à ce cas, utilisé deux fois: une fois pour obtenir les entrées, et une fois pour lire/écrire des blocs de données:
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
Portez une attention particulière au .
à la fin de certaines lignes! J'écrirais normalement ceci sur une longue ligne, mais c'est plus agréable de l'avoir envelopper pour que vous puissiez tout voir ici.
Juste au cas où il est difficile, nous allons déballer l'une des utilisations de continually
.
Stream.continually(zipIn.read(buffer))
Cette demande de continuer à appeler zipIn.read(buffer)
pour autant de fois que nécessaire, le stockage du nombre entier qui en résulte.
.takeWhile(_ != -1)
Ceci indique combien de fois sont nécessaires, le retour d'un flux de longueur indéfinie, mais qui va quitter quand il frappe un -1
.
.foreach(count => zipOut.write(buffer,0,count))
Ceci traite le flux, en prenant chaque élément à son tour (le nombre), et en l'utilisant pour écrire le tampon.Cela fonctionne d'une manière un peu sournoise, puisque vous comptez sur le fait que zipIn
vient d'être appelé pour obtenir l'élément suivant du flux - si vous avez essayé de le faire à nouveau, pas en un seul passage dans le flux, cela échouerait car buffer
serait écrasé. Mais ici c'est bon. Donc, voilà: un peu plus compact, peut-être plus facile à comprendre, peut-être moins facile à comprendre, une méthode plus fonctionnelle (bien qu'il y ait encore beaucoup d'effets secondaires). En 2.7.7, en revanche, je le ferais en Java car Stream.continually
n'est pas disponible, et le coût de construction d'un Iterator
personnalisé ne vaut pas le coup pour ce cas. (Il vaudrait la peine si je devais faire plus le traitement des fichiers zip et pourrait réutiliser le code, cependant.)
Edit: La méthode recherche-pour-disponible-to-go-zéro est une sorte de flaky pour détecter la fin du fichier zip. Je pense que le "bon" moyen est d'attendre jusqu'à ce que vous obteniez un null
de getNextEntry
. Dans cet esprit, j'ai édité le code précédent (il y avait un takeWhile(_ => zipIn.available==1)
qui est maintenant un takeWhile(_ != null)
) et fourni une version basée sur l'itérateur 2.7.7 ci-dessous (notez combien la boucle principale est petite, une fois que vous passez à travers le travail de définition les itérateurs, qui n'utilisent certes 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
Pourquoi null données? – sblundy
Parce que j'étais paresseux et 'new Array [Byte]' provoque le compilateur à se plaindre des constructeurs alternatifs. Je suppose que je devrais utiliser 'new ArrayBuffer [Byte]'. – pr1001
var data = new Array [Byte] (1024) –