dI nie sądzę, nie ma nic szczególnie złego w używaniu klas Java, które są przeznaczone do pracy w bezwzględnej mody w modzie były zaprojektowane. Idiomatic Scala obejmuje możliwość używania idiomatycznej Java, tak jak było to zamierzone, nawet jeśli style są nieco sprzeczne.
Jednakże, jeśli chcesz - może jako ćwiczenie, a może dlatego, że nieco wyjaśnia logikę - aby zrobić to w bardziej funkcjonalny sposób, możesz to zrobić. W wersji 2.8 jest to szczególnie miłe, więc mimo że używasz 2.7.7, dam odpowiedź 2.8.
Po pierwsze, musimy ustawić się problem, którego nie do końca, ale załóżmy, że mamy coś takiego:
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
Teraz, biorąc pod uwagę to chcemy skopiować plik zip. Sztuczka, którą możemy wykorzystać, to metoda continually
w collection.immutable.Stream
. To, co robi, to wykonać dla ciebie leniwo oszacowaną pętlę. Następnie możesz pobrać i przefiltrować wyniki, aby zakończyć i przetworzyć to, co chcesz. Jest to przydatny wzór do użycia, gdy masz coś, co chcesz być iteratorem, ale tak nie jest. (Jeśli element aktualizuje się, możesz użyć .iterate
w Iterable
lub Iterator
- co zwykle jest jeszcze lepsze.) Oto aplikacja do tego przypadku, używana dwukrotnie: raz, aby uzyskać wpisy, i raz, aby odczytać/zapisać fragmenty danych:
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
Zwróć szczególną uwagę na .
na końcu niektórych linii! Normalnie napiszę to na jednej długiej linii, ale przyjemniej jest ją zawijać, żebyś mógł to wszystko zobaczyć tutaj.
Na wypadek, gdyby nie było to jasne, rozpakujmy jedno z zastosowań continually
.
Stream.continually(zipIn.read(buffer))
Ten prosi wzywasz zipIn.read(buffer)
tyle razy, ile jest to konieczne, przechowywania liczbę całkowitą, która skutkuje.
.takeWhile(_ != -1)
ten określa, ile razy jest to konieczne, wracając strumień nieokreślonej długości, ale która wyjdzie gdy natrafi -1
.
.foreach(count => zipOut.write(buffer,0,count))
Spowoduje to przetworzenie strumienia, pobranie każdego elementu (liczby) i użycie go do zapisania bufora.Działa to w nieco podstępny sposób, ponieważ polegasz na tym, że właśnie został wywołany zipIn
, aby uzyskać następny element strumienia - jeśli spróbujesz to zrobić ponownie, nie podczas pojedynczego przejścia przez strumień, ponieważ buffer
zostanie nadpisany. Ale tutaj jest w porządku.
Jest więc: nieco bardziej kompaktowa, być może łatwiejsza do zrozumienia, być może mniej łatwa do zrozumienia metoda, która jest bardziej funkcjonalna (chociaż wciąż istnieją liczne efekty uboczne). W przeciwieństwie do tego, w 2.7.7 robiłbym to w Javie, ponieważ Stream.continually
nie jest dostępny, a narzut na budowanie niestandardowego Iterator
nie jest wart tego dla tego jednego przypadku. (Byłoby warto, jeśli miałem zamiar zrobić więcej przetwarzania pliku zip i może ponownie użyć kodu, jednak.)
Edycja: spojrzenie-na-available-to-go-Zero metoda jest rodzajem Łagodny w wykrywaniu końca pliku zip. Myślę, że "poprawnym" sposobem jest poczekanie, aż otrzymasz null
z powrotem od getNextEntry
. Mając to na uwadze, edytowałem poprzedni kod (był tam takeWhile(_ => zipIn.available==1)
, który jest teraz takeWhile(_ != null)
) i dostarczono poniżej wersję 2.7.7 opartą na iteratorze (zauważ jak mała jest główna pętla, kiedy przejdziesz przez pracę definiującą te iteratory, które robią co prawda użyć 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
Dlaczego jest null dane? – sblundy
Ponieważ byłem leniwy, a 'new Array [Byte]' powoduje, że kompilator narzeka na alternatywnych konstruktorów. Chyba powinienem używać 'nowego ArrayBuffer [Byte]'. – pr1001
var data = new Array [Byte] (1024) –