0

Как считать файл из ZIP с помощью InputStream?

16

Проблема с читением содержимого файла из ZIP-архива по SFTP с использованием InputStream

Мне нужно получить содержимое файла из ZIP-архива (в архиве только один файл, я знаю его имя) с использованием SFTP. У меня есть только InputStream ZIP-архива. Большинство примеров показывают, как получить содержимое с помощью следующего заявления:

ZipFile zipFile = new ZipFile("location");

Однако, как я уже сказал, у меня нет ZIP-файла на локальной машине, и я не хочу его скачивать. Достаточно ли InputStream для чтения?

UPD: Вот как я это делаю:

import java.util.zip.ZipInputStream;

import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;

public class SFTP {

    public static void main(String[] args) {
        String SFTPHOST = "host";
        int SFTPPORT = 3232;
        String SFTPUSER = "user";
        String SFTPPASS = "mypass";
        String SFTPWORKINGDIR = "/dir/work";
        Session session = null;
        Channel channel = null;
        ChannelSftp channelSftp = null;
        try {
            JSch jsch = new JSch();
            session = jsch.getSession(SFTPUSER, SFTPHOST, SFTPPORT);
            session.setPassword(SFTPPASS);
            java.util.Properties config = new java.util.Properties();
            config.put("StrictHostKeyChecking", "no");
            session.setConfig(config);
            session.connect();
            channel = session.openChannel("sftp");
            channel.connect();
            channelSftp = (ChannelSftp) channel;
            channelSftp.cd(SFTPWORKINGDIR);
            ZipInputStream zipStream = new ZipInputStream(channelSftp.get("file.zip"));
            ZipEntry entry = zipStream.getNextEntry();
            System.out.println(entry.getName()); // Да, я получил его имя, теперь мне нужно получить содержимое
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            session.disconnect();
            channelSftp.disconnect();
            channel.disconnect();
        }
    }
}

Проблема в том, что я не знаю, как получить содержимое файла из ZipInputStream. Может кто-то подсказать, как это можно сделать?

5 ответ(ов)

0

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

Шаг, который вы упускаете, заключается в чтении входного потока и записи содержимого в буфер, который затем записывается в выходной поток.

// Расширяет zip-файл, переданный в качестве первого аргумента, в
// каталог, указанный в качестве второго аргумента
public static void main(String args[]) throws Exception
{
    if(args.length != 2)
    {
        System.err.println("zipreader zipfile outputdir");
        return;
    }

    // создаем буфер для улучшения производительности копирования
    byte[] buffer = new byte[2048];

    // открываем поток zip-файла
    InputStream theFile = new FileInputStream(args[0]);
    ZipInputStream stream = new ZipInputStream(theFile);
    String outdir = args[1];

    try
    {
        // теперь итерируемся по каждому элементу в потоке. Вызов getNextEntry
        // вернет ZipEntry для каждого файла в потоке
        ZipEntry entry;
        while((entry = stream.getNextEntry()) != null)
        {
            String s = String.format("Entry: %s len %d added %TD",
                            entry.getName(), entry.getSize(),
                            new Date(entry.getTime()));
            System.out.println(s);

            // Как только мы получаем запись из потока, поток
            // позиционируется для чтения и мы продолжаем читать
            // пока read не вернет 0 или меньше.
            String outpath = outdir + "/" + entry.getName();
            FileOutputStream output = null;
            try
            {
                output = new FileOutputStream(outpath);
                int len = 0;
                while ((len = stream.read(buffer)) > 0)
                {
                    output.write(buffer, 0, len);
                }
            }
            finally
            {
                // мы всегда должны закрывать выходной файл
                if(output != null) output.close();
            }
        }
    }
    finally
    {
        // мы всегда должны закрывать zip-файл
        stream.close();
    }
}

Этот фрагмент кода был взят с сайта:

http://www.thecoderscorner.com/team-blog/java-and-jvm/12-reading-a-zip-file-from-java-using-zipinputstream#.U4RAxYamixR

0

Конечно! Ваш код для чтения содержимого ZIP-файла выглядит вполне уместно. Вот перевод вашего ответа на русский, оформленный в стиле StackOverflow:


Я сделал следующее:

zipStream = new ZipInputStream(channelSftp.get("Port_Increment_201405261400_2251.zip"));
zipStream.getNextEntry();

sc = new Scanner(zipStream);
while (sc.hasNextLine()) {
    System.out.println(sc.nextLine());
}

Этот подход позволяет мне читать содержимое ZIP-файла, не сохраняя его в другой файл.

Основная идея заключается в том, что мы используем ZipInputStream для извлечения содержимого ZIP-файла напрямую из потока, что экономит место на диске и упрощает обработку данных. Не забудьте закрыть zipStream и Scanner после использования, чтобы избежать утечек ресурсов.

Если у вас есть дополнительные вопросы или нужна помощь, не стесняйтесь спрашивать!

0

ZipInputStream является InputStream и предоставляет содержимое каждой записи после каждого вызова getNextEntry(). Важно помнить, что нельзя закрывать поток, из которого читаются данные, так как это будет означать закрытие самого ZIP-потока.

Вот пример, как можно реализовать чтение ZIP-потока:

public void readZipStream(InputStream in) throws IOException {
    ZipInputStream zipIn = new ZipInputStream(in);
    ZipEntry entry;
    while ((entry = zipIn.getNextEntry()) != null) {
        System.out.println(entry.getName());
        readContents(zipIn);
        zipIn.closeEntry();
    }
}

private void readContents(InputStream contentsIn) throws IOException {
    byte contents[] = new byte[4096];
    int direct;
    while ((direct = contentsIn.read(contents, 0, contents.length)) >= 0) {
        System.out.println("Прочитано " + direct + " байт.");
    }
}

Если требуется делегировать чтение содержимого другой логике, то можно обернуть ZipInputStream в FilterInputStream, чтобы закрывать только текущую запись, а не весь поток:

public void readZipStream(InputStream in) throws IOException {
    ZipInputStream zipIn = new ZipInputStream(in);
    ZipEntry entry;
    while ((entry = zipIn.getNextEntry()) != null) {
        System.out.println(entry.getName());

        readContents(new FilterInputStream(zipIn) {
            @Override
            public void close() throws IOException {
                zipIn.closeEntry();
            }
        });
    }
}

Таким образом, вы сможете правильно управлять ресурсами и избегать ненужного закрытия всего ZIP-потока при завершении работы с конкретной записью.

0

Вот более универсальное решение для обработки InputStream zip-файла с использованием BiConsumer. Это почти то же самое решение, которое предложил haui.

private void readZip(InputStream is, BiConsumer<ZipEntry, InputStream> consumer) throws IOException {
    try (ZipInputStream zipFile = new ZipInputStream(is)) {
        ZipEntry entry;
        while ((entry = zipFile.getNextEntry()) != null) {
            consumer.accept(entry, new FilterInputStream(zipFile) {
                @Override
                public void close() throws IOException {
                    zipFile.closeEntry();
                }
            });
        }
    }
}

Вы можете использовать это, просто вызвав

readZip(<некоторый inputstream>, (entry, is) -> {
    /* Не забудьте закрыть этот поток после обработки. */
    is.read(); // ... <- чтобы прочитать каждую запись
});

Не забудьте, что после завершения работы с InputStream, переданным в BiConsumer, нужно его закрыть, чтобы избежать утечек ресурсов.

0

Чтобы разархивировать ZIP-архив с сохранением структуры папок в указанной директории, вы можете использовать следующий код на Java. Обратите внимание, что этот код зависит от библиотеки org.apache.commons.io.IOUtils, но вы можете заменить её на свой собственный код для чтения потока, если это необходимо.

public static void unzipDirectory(File archiveFile, File destinationDir) throws IOException {
    Path destPath = destinationDir.toPath();
    try (ZipInputStream zis = new ZipInputStream(new FileInputStream(archiveFile))) {
        ZipEntry zipEntry;
        while ((zipEntry = zis.getNextEntry()) != null) {
            Path resolvedPath = destPath.resolve(zipEntry.getName()).normalize();
            // Проверяем, что файлы извлекаются только в целевую директорию
            if (!resolvedPath.startsWith(destPath)) {
                throw new IOException("Запрашиваемый zip-элемент '" + zipEntry.getName() + "' не принадлежит целевой директории");
            }
            if (zipEntry.isDirectory()) {
                // Создаем директорию, если это zip-элемент директории
                Files.createDirectories(resolvedPath);
            } else {
                // Создаем директорию для файла, если она не существует
                if (!Files.isDirectory(resolvedPath.getParent())) {
                    Files.createDirectories(resolvedPath.getParent());
                }
                try (FileOutputStream outStream = new FileOutputStream(resolvedPath.toFile())) {
                    IOUtils.copy(zis, outStream);
                }
            }
        }
    }
}

Этот код открывает ZIP-архив, перебирает его содержимое и извлекает файлы и директории, сохраняя структуру архивированных данных. Важно убедиться, что извлекаемые файлы не выходят за пределы указанной целевой директории, что предотвращает возможность непреднамеренного перезаписи файлов в других местах в файловой системе.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь