Jedna ze základních vlastností Javy je práce se ZIP archívy, ať už jsou to knihovny tříd a spustitelné JARy, webové aplikace (war), nebo třeba JEE bumbrlíčci (ear). Není tedy divu, že i přímo v základním API je implementována práce s těmito archívy. Slouží k tomu třídy v balíku java.util.zip a nejzajímavější z nich jsou ZipOutputStream a ZipInputStream.
Příkladem budiž vytvoření zipu:
//Vytvorime Zip ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("javlog.zip")); //V Zipu chceme mit jeden textovy soubor zos.putNextEntry(new ZipEntry("priznani.txt")); //naplnime obsah textoveho souboru zos.write("Máme rádi Javu!".getBytes()); //zavrem entry (priznani.txt) zos.closeEntry(); //zavrem stream zos.close();a jeho rozbalení:
ZipInputStream zis = new ZipInputStream(new FileInputStream("javlog.zip")); ZipEntry zipEntry; //budem predpokladat, ze v ZIPu mame jen textove soubory a tak je vypisem do konzole while ((zipEntry = zis.getNextEntry()) != null) { System.out.println("Soubor: " + zipEntry.getName()); BufferedReader br = new BufferedReader(new InputStreamReader(zis)); String line; while ((line = br.readLine()) != null) { System.out.println(line); } System.out.println(); zis.closeEntry(); } zis.close();
Jak je vidět, není tenhle přístup úplně přímočarý. Tím pádem je i větší prostor pro zavlečení nějaké chybky. Víte například, jak rozlišíte při vytváření ZIPu prázdný soubor od prázdného adresáře?
Tento základní přístup se často hodí v případě, kdy obsah, který chcete do zipu vložit, nemáte ve file systému, ale např. ho generujete přímo ve webové aplikaci. Pak daný ZipOutputStream vytvoříte nad OutputStreamem servletu a naládujete do něj data přímo z paměti nebo z databáze.
To co vývojář aplikace často potřebuje, je možnost jednoduše zazipovat část souborového systému (složku a všechno co je v ní), a nebo ze ZIPu zase všechno někam rozbalit. Samozřejmě, že se dají na internetu najít knihovny, které tuto funkcionalitu poskytují, ale částo jsou kanón na vrabce (opravdu potřebujete aby knihovna uměla i bzip2, 7z, arj a cab?), nebo naopak příliš jednoduché a nezvládnou operaci typu – sbal mi část filesystému a k tomu přidej tenhle vygenerovaný (v paměti) PDF report.
Takže by se v těchto případech hodilo malé rozšíření Zip*Stream tříd. Aby na jednu stranu bylo možné jednoduše pracovat přímo se souborovým systémem a na druhou, aby nebyl programátor ochuzen o to, co mu ZipStreamy umožňují. Hodily by se nám tedy metody:
void ZipOutputStream.put(File fileOrFolderToZip}; void ZipInputStream.unzip(File folderWhereToStoreUnzippedFiles);
A protože originální Zip*Stream třídy nejsou finální, nic nám nebrání v rozšíření. Má implementace přidává ještě dodatečné parametry, aby bylo použití univerzálnější.
cz.cacek.javlog.zip.ZipOutputStream extends java.util.zip.ZipOutputStream:
/** * Adds file or directory (with its content) to this {@link ZipOutputStream}. * If the given {@link File} doesn't exist, it does nothing and returns. * The path of the given {@link File} in the result zip is determined only * from the file name * and from the provided basePath. It doesn't take the file path into consideration * (but the folder structure under the given folder is preserved). * * @param file * {@link File} (normal or directory) to add to the stream * (must not be null) * @param basePath * base path (path prefix) for added {@link File} (may be null) * @param contentOnly * determines if the folder name (in case the file * parameter is directory) should be added to the zip path. If * true is provided only the folder content is added and the * folder itself is not included. * @throws IOException */ public void put(final File file, final String basePath, boolean contentOnly) throws IOException;cz.cacek.javlog.zip.ZipInputStream extends java.util.zip.ZipInputStream:
/** * Unzips the stream to the given base folder. If the baseFolder parameter * is null, the default (working) directory is used instead. If the * baseFolder doesn't exist it is created. * * @param baseFolder * base folder to unpack (may be null). * @param allowAbsolutePath * flag which says if handling of absolute paths is allowed in * the zip. If the flag is * true and ZipEntry contains absolute path, the baseFolder * parameter is not used and File is created from the absolute * path in the ZipEntry. * @throws IllegalArgumentException * base folder can't be created or file denoted by baseFolder * parameter already exists, * but it's not a directory. * @throws IOException */ public void unzip(final File baseFolder, final boolean allowAbsolutePath) throws IllegalArgumentException, IOException;
A použití? Chceme například rozbalit war soubor do nějaké složky, upravit obsah a zase ho zabalit:
final File baseFolder = new File("temp-webapp"); //folder to unpack the zip content (will be created) final File warFile = new File("myapp.war"); //file to unpack //unpack ZIP ZipInputStream zis = new ZipInputStream(new FileInputStream(warFile)); zis.unzip(baseFolder, false); zis.close(); //TODO make some magic with files in the temp-webapp folder new FileOutputStream(new File(baseFolder, "newEmptyFile.xxx")).close(); //repack the ZIP (WAR) file ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(warFile)); zos.put(baseFolder, null, true); //pack only the content of the folder - 3rd parameter is true zos.close(); //TODO remove the baseFolder //delete(baseFolder);
Toť vše, ani to nebolelo.
Upravené ZipStreamy připravené k použití najdete v tomto jaru (3 třídy + zdrojáky), nebo v zazipovaném Eclipse projektu i s JUnit testy.
A co používáte k práci s ZIP archívy vy? Podělte se v komentářích.
Komentáře
Ma celkem pouzitelne API, ale vlastnost ktera se mi libila nejvic je schopnost pracovat se ZIPem jako s adresarem.
Funguje to i rekurzivne - ZIP zazipovany v ZIPu ktery je zazipovany v ZIPu. Idealni pro problem ktery jsem resil - otevrit EAR, najit WAR a precist a zapsat JAR v /WEB-INF/lib/ (vsechny 3 jsou vlastne ZIP)
Když se pracuje jen v Javě na straně vytváření archivu i rozbalování, tak to je v pohodě - prostě tam bude UTF-8. Ale když to pak chce člověk rozbalit na českých Windows ... tak se ucho utrhlo.
@Arnost: Možnosti Truezipu vypadadají dobře. Pro mě je to v současné době onen zmíněný kanón na vrabce, ale je dobré o tomhle projektu vědět.