# Java - Zipper et dézipper des fichiers et des répertoires
Il existe deux APIs en java pour zipper/dézipper des fichiers:
- l'API
java.util.zip
qui utilise desInputStream
et desOutputStream
, - l'API
java.nio.file
qui utilise l'abstractionFileSystem
disponible depuis Java 7.
L'abstraction java.nio.file.FileSystem
est globalement plus simple à utiliser puisqu'elle
permet de réfléchir directement au niveau des fichiers sans se soucier de transférer leur contenu
byte par byte.
# Zipper un fichier
Dans ce premier exemple, un fichier some-file-1.txt
est zipper dans une archive
some-file.zip
, créée pour l'occasion. A noter que le nom du fichier dans l'archive
est modifié pour some-file-1-in-zip.txt
:
@Test
@Order(1)
public void zipFile() {
Path zipPath = getTempPath("some-file.zip");
Path inputFilePath = getResourcePath("some-file-1.txt");
try (FileInputStream fis = new FileInputStream(inputFilePath.toFile());
FileOutputStream fos = new FileOutputStream(zipPath.toFile());
ZipOutputStream zos = new ZipOutputStream(fos)) {
ZipEntry entry = new ZipEntry("some-file-1-in-zip.txt");
zos.putNextEntry(entry);
copy(fis, zos);
} catch (IOException e) {
LOG.error("The file can't be added to the zip", e);
}
assertThat(zipPath).exists();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
En pratique, il suffit d'ouvrir un InputStream
sur le fichier à compresser et à ZipOutputStream
sur le fichier zipper puis de copier le contenu de l'un à l'autre. Ici, la fonction copy
utilisée entre les streams est la suivante:
private void copy(InputStream inputStream, OutputStream outputStream) throws IOException {
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) >= 0) {
outputStream.write(buffer, 0, length);
}
}
2
3
4
5
6
A noter que les répertoires et les fichiers contenu dans le zip sont représentées par des ZipEntry
qui représente un chemin relatif dans le zip. Il faut donc toujours indiquer au ZipOutputStream
dans quelle ZipEntry
doivent se trouver les données transférées.
TIP
Les InputStream
et OutputStream
implémentent l'interface Closeable
, il est donc conseillé
de les ouvrir avec un try-with-resource (opens new window)
TIP
Dans l'exemple ci-dessus, un FileOutputStream
a été directement utilisé, mais il est tout autant
possible d'utiliser un BufferedOutputStream
pour nourrir le ZipOutputStream
.
# Zipper un répertoire
Pour zipper un répertoire, il suffit de répéter l'opération précédente pour l'ensemble des fichiers
du répertoire. On peut utiliser Files.walk()
pour parcourir récursivement
les fichiers d'un répertoire:
@Test
@Order(2)
public void zipDirectory() {
Path zipPath = getTempPath("some-directory.zip");
Path inputDirectoryPath = getResourcePath("some-directory");
try (FileOutputStream fos = new FileOutputStream(zipPath.toFile());
ZipOutputStream zos = new ZipOutputStream(fos)) {
Files.walk(inputDirectoryPath)
.filter(someFileToZip -> !someFileToZip.equals(inputDirectoryPath))
.forEach(
someFileToZip -> {
Path pathInZip = inputDirectoryPath.relativize(someFileToZip);
if (Files.isDirectory(someFileToZip)) {
addDirectory(zos, pathInZip);
} else {
addFile(zos, someFileToZip, pathInZip);
}
});
} catch (IOException e) {
LOG.error("The file can't be added to the zip", e);
}
assertThat(zipPath).exists();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Dans l'exemple ci-dessus, les fichiers sont ajoutés dans le zip
en concervant leur nom et leur chemin relatif au répertoire compressé, d'où le
inputDirectoryPath.relativize(path)
.
Les fichiers et les répertoires sont ajoutés différemment au zip. Pour les répertoires, il faut penser
à simplement ajouter le ZipEntry
correspondant en s'assurant que son nom se termine bien
par un /
:
private void addDirectory(ZipOutputStream zos, Path relativeFilePath) {
try {
ZipEntry entry = new ZipEntry(relativeFilePath.toString() + "/");
zos.putNextEntry(entry);
zos.closeEntry();
} catch (IOException e) {
LOG.error("Unable to add directory {} to zip", relativeFilePath, e);
}
}
2
3
4
5
6
7
8
WARNING
Si le nom du ZipEntry
ne se termine pas par /
, il ne sera pas considérée comme un répertoire
mais comme un fichier vide.
Pour les fichiers, le code utilisé dans la première section est repris sous forme de fonction:
private void addFile(ZipOutputStream zos, Path filePath, Path zipFilePath) {
try (FileInputStream fis = new FileInputStream(filePath.toFile())) {
ZipEntry entry = new ZipEntry(zipFilePath.toString());
zos.putNextEntry(entry);
copy(fis, zos);
} catch (IOException e) {
LOG.error("Unable to add file {} to zip", zipFilePath, e);
}
}
2
3
4
5
6
7
8
# Dézipper
Dézipper les fichiers correspond à l'opération inverse, les données sont cette fois copiées d'un
ZipInputStream
vers un OutputStream
.
@Test
@Order(3)
public void unzip() {
Path zipPath = getTempPath("some-directory.zip");
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipPath.toFile()))) {
ZipEntry entry = zis.getNextEntry();
while (entry != null) {
Path outputEntryPath = path.resolve(entry.getName());
if (entry.isDirectory() && !Files.exists(outputEntryPath)) {
Files.createDirectory(outputEntryPath);
} else if (!entry.isDirectory()) {
try (FileOutputStream fos = new FileOutputStream(outputEntryPath.toFile())) {
copy(zis, fos);
}
}
entry = zis.getNextEntry();
}
zis.closeEntry();
} catch (IOException e) {
LOG.error("Unable to unzip", zipPath, e);
}
assertThat(getTempPath("some-file-1.txt")).exists();
assertThat(getTempPath("some-file-2.txt")).exists();
assertThat(getTempPath("some-subdirectory/some-file-3.txt")).exists();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Quand les fichiers sont extraits, il faut s'assurer que leur sous-répertoire de destination existe bien
avant de les écrire. Ci-dessus, cela est géré en créant le répertoire correspondant pour chaque ZipEntry
de type répertoire.
# Zipper un répertoire avec FileSystem
L'API java.nio
fournit une abstraction FileSystem
avec une implémentation ZipFileSystem
qui
permet de manipuler un fichier zip comme un système de fichier normal. En utilisant cette API, la notion
de ZipEntry
disparait, et les fichiers sont directement copiés du système de fichier courant à celui
du zip. Pour obtenir le Path
d'un fichier dans le zip, on utilise simplement la fonction FileSystem.getPath
.
@Test
@Order(4)
public void zipDirectoryWithFileSystem() throws IOException {
Path directoryToZip = getResourcePath("some-directory");
Map<String, String> env = new HashMap<>();
env.put("create", "true");
Path zipPath = getTempPath("some-directory-fs.zip");
URI zipUri = URI.create("jar:file:" + zipPath.toString());
try (FileSystem zipFs = FileSystems.newFileSystem(zipUri, env)) {
Files.walk(directoryToZip)
.forEach(
someFileToZip -> {
Path relativeFilePath = directoryToZip.relativize(someFileToZip);
Path filePathInZip = zipFs.getPath("/", relativeFilePath.toString());
try {
if (Files.isDirectory(someFileToZip) && !Files.exists(filePathInZip)) {
Files.createDirectory(filePathInZip);
} else if (!Files.isDirectory(someFileToZip)) {
Files.copy(someFileToZip, filePathInZip, StandardCopyOption.REPLACE_EXISTING);
}
} catch (IOException e) {
LOG.error("Unable to add {} to zip", relativeFilePath, e);
}
});
}
assertThat(zipPath).exists();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Là encore, il faut s'assurer que les répertoires sont bien ajoutés dans le zip. Cette fois, il suffit de les créer directement dans le système de fichier du zip.
# Dézipper avec FileSystem
Dézipper via un FileSystem
est l'opération inverse de la précédente: les fichiers sont copiés du système de fichier
zip vers le système de fichier courant:
@Test
@Order(5)
public void unzipWithFileSystem() {
Path zipPath = getTempPath("some-directory-fs.zip");
URI uri = URI.create("jar:file:" + zipPath.toString());
try (FileSystem zipFs = FileSystems.newFileSystem(uri, new HashMap<>())) {
Files.walk(zipFs.getPath("/"))
.forEach(
someFileInZip -> {
Path absoluteFilePath = getTempPath(someFileInZip.toString().substring(1));
try {
if (Files.isDirectory(absoluteFilePath) && !Files.exists(absoluteFilePath)) {
Files.createDirectory(absoluteFilePath);
} else if (!Files.isDirectory(absoluteFilePath)) {
Files.copy(
someFileInZip, absoluteFilePath, StandardCopyOption.REPLACE_EXISTING);
}
} catch (IOException e) {
LOG.error("Unable to unzip {} to {}", someFileInZip, absoluteFilePath, e);
}
});
} catch (IOException e) {
LOG.error("Unable to unzip {}", zipPath, e);
}
assertThat(getTempPath("some-file-1.txt")).exists();
assertThat(getTempPath("some-file-2.txt")).exists();
assertThat(getTempPath("some-subdirectory/some-file-3.txt")).exists();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27