Статьи

Распечатка содержимого файла ZIP с Stream API в Java 8

В Java 8 java.util.zip.ZipFile был оснащен stream методом, который позволяет очень легко перемещаться по записям ZIP-файла. В этом посте я покажу несколько примеров, показывающих, как быстро мы можем перемещаться по записям в ZIP-файле.

Примечание. Для целей данного сообщения в блоге я загрузил одно из своих репозиториев GitHub в виде файла ZIP и скопировал его в c:/tmp .

До Java 7

Чтение записей ZIP-файлов в Java до Java 7 — это что-то вроде хм … сложно? Вот как можно начать ненавидеть Java, глядя на этот код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Zipper {
    public void printEntries(PrintStream stream, String zip)  {
        ZipFile zipFile = null;
        try {
            zipFile = new ZipFile(zip);
            Enumeration<? extends ZipEntry> entries = zipFile.entries();
            while (entries.hasMoreElements()) {
                ZipEntry zipEntry = entries.nextElement();
                stream.println(zipEntry.getName());
            }
        } catch (IOException e) {
            // error while opening a ZIP file
        } finally {
            if (zipFile != null) {
                try {
                    zipFile.close();
                } catch (IOException e) {
                    // do something
                }
            }
        }
    }
}

Java 7

С Java 7 то же самое может быть намного проще — благодаря try-with-resources но мы все еще «вынуждены» использовать Enumeration для навигации по записям в ZIP-файле:

01
02
03
04
05
06
07
08
09
10
11
12
13
public class Zipper {
    public void printEntries(PrintStream stream, String zip) {
        try (ZipFile zipFile = new ZipFile(zip)) {
            Enumeration<? extends ZipEntry> entries = zipFile.entries();
            while (entries.hasMoreElements()) {
                ZipEntry zipEntry = entries.nextElement();
                stream.println(zipEntry.getName());
            }
        } catch (IOException e) {
            // error while opening a ZIP file
        }
    }
}

Использование Stream API

Самое интересное начинается с Java 8. Начиная с Java 8, в java.util.zip.ZipFile появился новый метод stream который возвращает упорядоченный поток по записям в файле ZIP. Это дает много возможностей при работе с ZIP-файлами в Java. Предыдущие примеры можно просто написать следующим образом в Java 8:

01
02
03
04
05
06
07
08
09
10
public class Zipper {
    public void printEntries(PrintStream stream, String zip) {
        try (ZipFile zipFile = new ZipFile(zip)) {
            zipFile.stream()
                    .forEach(stream::println);
        } catch (IOException e) {
            // error while opening a ZIP file
        }
    }
}

С Stream API мы можем играть с ZipFile разными способами. См. ниже…

Фильтрация и сортировка содержимого ZIP файлов

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
public void printEntries(PrintStream stream, String zip) {
    try (ZipFile zipFile = new ZipFile(zip)) {
        Predicate<ZipEntry> isFile = ze -> !ze.isDirectory();
        Predicate<ZipEntry> isJava = ze -> ze.getName().matches(".*java");
        Comparator<ZipEntry> bySize =
                (ze1, ze2) -> Long.valueOf(ze2.getSize() - ze1.getSize()).intValue();
        zipFile.stream()
                .filter(isFile.and(isJava))
                .sorted(bySize)
                .forEach(ze -> print(stream, ze));
    } catch (IOException e) {
        // error while opening a ZIP file
    }
}
 
private void print(PrintStream stream, ZipEntry zipEntry) {
    stream.println(zipEntry.getName() + ", size = " + zipEntry.getSize());
}

Выполняя итерации по записям ZIP, я проверяю, является ли запись файлом и соответствует ли оно заданному имени (для простоты в этом примере это закодировано), а затем сортирую его по размеру с помощью данного компаратора.

Создать индекс файлов ZIP-файла

В этом примере я группирую записи ZIP по первой букве имени файла, чтобы создать индекс Map<String, List<ZipEntry>> . Ожидаемый результат должен выглядеть примерно так:

1
2
a = [someFile/starting/with/an/A]
u = [someFile/starting/with/an/U, someOtherFile/starting/with/an/U]

Опять же, с Stream API это действительно легко:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public void printEntries(PrintStream stream, String zip) {
    try (ZipFile zipFile = new ZipFile(zip)) {
        Predicate<ZipEntry> isFile = ze -> !ze.isDirectory();
        Predicate<ZipEntry> isJava = ze -> ze.getName().matches(".*java");
        Comparator<ZipEntry> bySize =
            (ze1, ze2) -> Long.valueOf(ze2.getSize()).compareTo(Long.valueOf(ze1.getSize()));
 
        Map<String, List<ZipEntry>> result = zipFile.stream()
                .filter(isFile.and(isJava))
                .sorted(bySize)
                .collect(groupingBy(this::fileIndex));
 
        result.entrySet().stream().forEach(stream::println);
 
    } catch (IOException e) {
        // error while opening a ZIP file
    }
}
 
private String fileIndex(ZipEntry zipEntry) {
    Path path = Paths.get(zipEntry.getName());
    Path fileName = path.getFileName();
    return fileName.toString().substring(0, 1).toLowerCase();
}

Найти текст в записи файла ZIP

В последнем примере я ищу @Test текста @Test во всех файлах с расширением java . На этот раз я буду использовать метод lines BufferedReader который возвращает поток строк.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public void printEntries(PrintStream stream, String zip) {
 
    try (ZipFile zipFile = new ZipFile(zip)) {
        Predicate<ZipEntry> isFile = ze -> !ze.isDirectory();
        Predicate<ZipEntry> isJava = ze -> ze.getName().matches(".*java");
 
        List<ZipEntry> result = zipFile.stream()
                .filter(isFile.and(isJava))
                .filter(ze -> containsText(zipFile, ze, "@Test"))
                .collect(Collectors.toList());
 
        result.forEach(stream::println);
 
 
    } catch (IOException e) {
        // error while opening a ZIP file
    }
}
 
private boolean containsText(ZipFile zipFile, ZipEntry zipEntry, String needle) {
    try (InputStream inputStream = zipFile.getInputStream(zipEntry);
         BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
 
        Optional<String> found = reader.lines()
                .filter(l -> l.contains(needle))
                .findFirst();
 
        return found.isPresent();
 
    } catch (IOException e) {
        return false;
    }
}

Резюме

Stream API в Java 8 является мощным решением, которое помогает легко решать относительно простые задачи. И это его сила, на мой взгляд.

Примеры, представленные в этой статье, относительно просты и созданы только для наглядности. Но я надеюсь, что вы их любите и найдете их полезными.

Ресурсы