Чтение ZIP-файла с использованием Java API от Clojure
Я пытаюсь переписать следующий фрагмент в ближайшем будущем, но все это выглядит ужасно, может быть, кто-то предложит более элегантное решение?
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class ZipFileRdrExp {
public static void main(String[] args) {
try {
FileInputStream fis = new FileInputStream("C:\\MyZip.zip");
ZipInputStream zis = new ZipInputStream(fis);
ZipEntry ze;
while((ze=zis.getNextEntry())!=null){
System.out.println(ze.getName());
zis.closeEntry();
}
zis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Вот моя уродливая попытка с двойным вызовом getNextEntry:
(ns app.core
(:import
(java.io FileInputStream FileNotFoundException IOException File)
(java.util.zip ZipInputStream ZipEntry)))
(defn- read-zip [zip-file]
(let [fis (FileInputStream. zip-file)
zis (ZipInputStream. fis)]
(loop [ze (.getNextEntry zis)]
(when ze
(println (.getName ze))
(.closeEntry zis)
(recur (.getNextEntry zis))))
(.close zis)))
5 ответов
Я хотел бы пойти с чем-то вроде следующего:
(defn entries [zipfile]
(lazy-seq
(if-let [entry (.getNextEntry zipfile)]
(cons entry (entries zipfile)))))
(defn walkzip [fileName]
(with-open [z (ZipInputStream. (FileInputStream. fileName))]
(doseq [e (entries z)]
(println (.getName e))
(.closeEntry z))))
РЕДАКТИРОВАТЬ: приведенный выше код был в конечном итоге проверен и исправлен.
РЕДАКТИРОВАТЬ: следующее работает, как ожидалось, и это гораздо более кратким, хотя он использует другой API Java
(defn entries [zipfile]
(enumeration-seq (.entries zipfile)))
(defn walkzip [fileName]
(with-open [z (java.util.zip.ZipFile. fileName)]
(doseq [e (entries z)]
(println (.getName e)))))
Это более простой пример:
(defn filenames-in-zip [filename]
(let [z (java.util.zip.ZipFile. filename)]
(map #(.getName %) (enumeration-seq (.entries z)))))
Это похоже на приведенный выше код, но здесь нет причин использовать with-open. В этом примере возвращается последовательность данных, которую вы можете затем распечатать или, что еще лучше, отформатировать. Лучше иметь функцию, которая извлекает данные, просто возвращать данные, а не иметь побочный эффект печати, содержащийся внутри этой функции.
Если вы хотите распечатать содержимое, вы можете использовать
(pprint (filenames-in-zip "my.zip"))
и это даст вам хороший список.
Это похоже на ответ Скуро, который использует ZipInputStream
, но чуть более краткое определение entries
,
(defn entries [zip-stream]
(take-while #(not (nil? %))
(repeatedly #(.getNextEntry zip-stream))))
(defn walkzip [fileName]
(with-open [z (ZipInputStream. (FileInputStream. fileName))]
(doseq [e (entries z)]
(println (.getName e))
(.closeEntry z))))
Или, если вы действительно хотите извлечь файлы, вам нужна другая вспомогательная функция для копирования. Я использовал clojure.java.io
для сокращения кода, но то же самое может быть достигнуто без этой зависимости.
(require '[clojure.java.io :as io])
(defn entries [zip-stream]
(take-while #(not (nil? %))
(repeatedly #(.getNextEntry zip-stream))))
(defn copy-file [zip-stream filename]
(with-open [out-file (file-out-stream filename)]
(let [buff-size 4096
buffer (byte-array buff-size)]
(loop [len (.read zip-stream buffer)]
(when (> len 0)
(.write out-file buffer 0 len)
(recur (.read zip-stream buffer)))))))
(defn extract-stream [zip-stream to-folder]
(let [extract-entry (fn [zip-entry]
(when (not (.isDirectory zip-entry))
(let [to-file (io/file to-folder
(.getName zip-entry))
parent-file (io/file (.getParent to-file))]
(.mkdirs parent-file)
(copy-file zip-stream to-file))))]
(->> zip-stream
entries
(map extract-entry)
dorun)))
Это фактически эквивалентно простому разархивированию файла с unzip
полезность. Прелесть этого в том, что поскольку записи находятся в ленивом следствии, вы можете filter
или же drop
или же take
к вашему сердцу (или требованиям) контента. Ну, я уверен, что вы можете. На самом деле еще не пробовал:)
Также сделайте примечание. Вы ДОЛЖНЫ обработать последовательность внутри функции, в которой вы открываете поток zip!!!
Clojure-Contrib имеет библиотеки IO и Jar, которые делают код короче:
(require 'clojure.contrib.jar
'clojure.contrib.io)
(import [java.util.jar JarFile])
(defn- read-zip [zip-file]
(clojure.contrib.jar/filenames-in-jar (JarFile. (clojure.contrib.io/file zip-file))))
Предостережение: Функция filenames-in-jar не перечисляет записи каталога в zip-файле, только имена реальных файлов.
Мое предпочтительное решение - создать
lazy-seq
из
[#ZipEntry, #InputStream]
из zip-файла.
(defn lazy-zip
"returns a lazy-seq of [entry inputstream] for a zip file
The zipfile will be closed when the seq is exhausted. All processing has to be done transient through `map` or similar methods."
[filename]
(let [zf (java.util.zip.ZipFile. filename)]
(letfn [(helper [entries]
(lazy-seq
(if-let [s (seq entries)]
(cons [(first entries)
(.getInputStream zf (first entries))]
(helper (rest entries)))
(do (println "closing zipfile") (.close zf) nil))))]
(helper (->> zf (.stream) (.toArray))))))
Вот тест, который показывает использование:
(deftest test-lazy-zip
(testing "sample zip is read correctly"
(is (=
'(["sample.xml" "<?xml version=\"1.0\" encoding=\"UTF-8\">" "<foo>" "<bar>" "<baz>The baz value</baz>" "</bar>" "</foo>"])
(map (fn [[entry reader]]
(into [(.getName entry)]
(line-seq (stream-to-buffered-reader reader))))
(lazy-zip "sample.xml.zip"))))))