Как считать файл из ZIP с помощью InputStream?
Проблема с читением содержимого файла из 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 ответ(ов)
Это простой пример того, как извлечь 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();
}
}
Этот фрагмент кода был взят с сайта:
Конечно! Ваш код для чтения содержимого 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
после использования, чтобы избежать утечек ресурсов.
Если у вас есть дополнительные вопросы или нужна помощь, не стесняйтесь спрашивать!
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-потока при завершении работы с конкретной записью.
Вот более универсальное решение для обработки 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
, нужно его закрыть, чтобы избежать утечек ресурсов.
Чтобы разархивировать 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-архив, перебирает его содержимое и извлекает файлы и директории, сохраняя структуру архивированных данных. Важно убедиться, что извлекаемые файлы не выходят за пределы указанной целевой директории, что предотвращает возможность непреднамеренного перезаписи файлов в других местах в файловой системе.
Как прочитать/конвертировать InputStream в строку в Java?
Как преобразовать строку в InputStream в Java?
Нужно ли закрывать ByteArrayInputStream?
Инициализация ArrayList в одну строчку
Создание репозитория Spring без сущности