Bagaimana cara membaca file dari ZIP menggunakan InputStream?

Saya harus mendapatkan konten file dari arsip ZIP (hanya satu file, saya tahu namanya) menggunakan SFTP. Satu-satunya hal yang saya miliki adalah InputStream ZIP. Sebagian besar contoh menunjukkan cara mendapatkan konten menggunakan pernyataan ini:

ZipFile zipFile = new ZipFile("location");

Tapi seperti yang saya katakan, saya tidak memiliki file ZIP di mesin lokal saya dan saya tidak ingin mendownloadnya. Apakah InputStream cukup untuk dibaca?

UPD: Inilah yang saya lakukan:

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 stream = new ZipInputStream(channelSftp.get("file.zip"));
            ZipEntry entry = zipStream.getNextEntry();
            System.out.println(entry.getName); //Yes, I got its name, now I need to get content
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            session.disconnect();
            channelSftp.disconnect();
            channel.disconnect();
        }


    }
}

person Tony    schedule 26.05.2014    source sumber
comment
Apakah saya benar-benar perlu menulis file zip baru, jika saya hanya perlu membaca konten file txt-nya?   -  person Tony    schedule 26.05.2014
comment
Tidak ada alasan yang tidak berhasil, Anda hanya perlu mendapatkan semua ZIPEntries dan menyimpannya dari aliran   -  person Kenneth Clark    schedule 26.05.2014


Jawaban (7)


Di bawah ini adalah contoh sederhana tentang cara mengekstrak File ZIP, Anda perlu memeriksa apakah file tersebut adalah direktori. Tapi ini yang paling sederhana.

Langkah yang Anda lewatkan adalah membaca aliran masukan dan menulis konten ke buffer yang ditulis ke aliran keluaran.

// Expands the zip file passed as argument 1, into the
// directory provided in argument 2
public static void main(String args[]) throws Exception
{
    if(args.length != 2)
    {
        System.err.println("zipreader zipfile outputdir");
        return;
    }

    // create a buffer to improve copy performance later.
    byte[] buffer = new byte[2048];

    // open the zip file stream
    InputStream theFile = new FileInputStream(args[0]);
    ZipInputStream stream = new ZipInputStream(theFile);
    String outdir = args[1];

    try
    {

        // now iterate through each item in the stream. The get next
        // entry call will return a ZipEntry for each file in the
        // stream
        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);

            // Once we get the entry from the stream, the stream is
            // positioned read to read the raw data, and we keep
            // reading until read returns 0 or less.
            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
            {
                // we must always close the output file
                if(output!=null) output.close();
            }
        }
    }
    finally
    {
        // we must always close the zip file.
        stream.close();
    }
}

Kutipan kode berasal dari situs berikut:

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

person Kenneth Clark    schedule 26.05.2014

Ya, saya sudah melakukan ini:

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

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

Ini membantu saya membaca konten ZIP tanpa menulis ke file lain.

person Tony    schedule 26.05.2014
comment
Jelas isi filenya masih terdownload. Anda hanya tidak perlu menulisnya ke file (sementara). - person Martin Prikryl; 27.05.2014
comment
Saya pikir solusi dari @KennethClark lebih baik. Ini berfungsi baik untuk file teks dan biner, sedangkan milik Anda hanya berfungsi untuk file teks, imho. Perhatikan bahwa saat dia menyimpan konten yang diekstraksi ke file, itu hanyalah contoh cara menyalin konten ke aliran lain. Itu tidak harus berupa aliran file, bisa juga berupa aliran memori, atau tidak harus berupa aliran sama sekali. - person Martin Prikryl; 27.05.2014
comment
Omong-omong. Ukuran file teks di dalam arsip adalah sekitar 1 MB (111589 baris teks). Dan membaca (pernyataan while (sc.hasNextLine()) tanpa sysout) membutuhkan waktu 38 detik. Apakah itu normal? - person Tony; 27.05.2014
comment
Coba solusi @KennethClark. Saya dapat membayangkan bahwa Scanner mungkin lambat. - person Martin Prikryl; 27.05.2014

ZipInputStream adalah InputStream dengan sendirinya dan mengirimkan konten setiap entri setelah setiap panggilan ke getNextEntry(). Perhatian khusus harus diberikan, jangan sampai menutup aliran tempat konten dibaca, karena sama dengan aliran 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("Read " + direct + "bytes content.");
    }
}

Saat mendelegasikan konten bacaan ke logika lain, ZipInputStream perlu dibungkus dengan FilterInputStream untuk menutup entri saja, bukan seluruh aliran seperti pada:

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();
            }
        });
    }
}
person haui    schedule 01.06.2018
comment
Pembungkus FilterInputStream sangat membantu. - person Ng Zhong Qin; 05.10.2020

OP sudah dekat. Hanya perlu membaca byte. Panggilan ke getNextEntry positions the stream at the beginning of the entry data (dokumen). Jika itu entri yang kita inginkan (atau satu-satunya entri), maka InputStream berada di tempat yang tepat. Yang perlu kita lakukan hanyalah membaca byte entri yang didekompresi.

byte[] bytes = new byte[(int) entry.getSize()];
int i = 0;
while (i < bytes.length) {
    // .read doesn't always fill the buffer we give it.
    // Keep calling it until we get all the bytes for this entry.
    i += zipStream.read(bytes, i, bytes.length - i);
}

Jadi jika byte ini benar-benar berupa teks, maka kita dapat mendekode byte tersebut menjadi sebuah String. Saya hanya berasumsi pengkodean utf8.

new String(bytes, "utf8")

Catatan tambahan: Saya pribadi menggunakan Apache commons-io IOUtils untuk mengurangi hal-hal tingkat rendah semacam ini. Dokumen untuk ZipInputStream.read sepertinya menyiratkan bahwa pembacaan akan berhenti di akhir entri zip saat ini. Jika itu benar, maka pembacaan entri tekstual saat ini adalah satu baris dengan IOUtils.

String text = IOUtils.toString(zipStream)
person Jason Dunkelberger    schedule 05.06.2019

Berikut solusi yang lebih umum untuk memproses inputstream zip dengan BiConsumer. Solusinya hampir sama dengan yang digunakan oleh 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();
                }
            });
        }
    }
}

Anda dapat menggunakannya hanya dengan menelepon

readZip(<some inputstream>, (entry, is) -> {
    /* don't forget to close this stream after processing. */
    is.read() // ... <- to read each entry
});
person ThomasCh    schedule 19.08.2019

Buka zip arsip (zip) dengan mempertahankan struktur file ke dalam direktori tertentu. Catatan; kode ini menggunakan deps pada "org.apache.commons.io.IOUtils"), tetapi Anda dapat menggantinya dengan kode 'read-stream' khusus Anda

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("The requested zip-entry '" + zipEntry.getName() + "' does not belong to the requested destination");
      }
      if (zipEntry.isDirectory())
      {
        Files.createDirectories(resolvedPath);
      } else
      {
        if(!Files.isDirectory(resolvedPath.getParent()))
        {
          Files.createDirectories(resolvedPath.getParent());
        }
        try (FileOutputStream outStream = new FileOutputStream(resolvedPath.toFile()))
        {
          IOUtils.copy(zis, outStream);
        }
      }
    }
  }
}
person T.KH    schedule 24.04.2020

Jika konten ZIP Anda terdiri dari 1 file (misalnya, konten zip respons HTTP), Anda dapat membaca konten teks menggunakan Kotlin sebagai berikut:

@Throws(IOException::class)
fun InputStream.readZippedContent() = ZipInputStream(this).use { stream ->
     stream.nextEntry?.let { stream.bufferedReader().readText() } ?: String()
}

Fungsi ekstensi ini membuka ritsleting entri ZIP pertama dari file Zip dan membaca konten sebagai teks biasa.

Penggunaan:

val inputStream: InputStream = ... // your zipped InputStream
val textContent = inputStream.readZippedContent()
person mtwain    schedule 13.06.2020