【问题标题】:How to read file from ZIP using InputStream?如何使用 InputStream 从 ZIP 中读取文件?
【发布时间】:2014-07-15 04:11:11
【问题描述】:

我必须使用 SFTP 从 ZIP 存档中获取文件内容(只有一个文件,我知道它的名称)。我唯一拥有的是 ZIP 的InputStream。大多数示例显示了如何使用此语句获取内容:

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 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();
        }


    }
}

【问题讨论】:

  • 如果我只需要读取它的txt文件内容,我真的需要编写一个新的zip文件吗?
  • 没有理由不工作,您只需要获取所有 ZIPEntries 并从流中保存它们

标签: java zip inputstream


【解决方案1】:

下面是一个关于如何提取 ZIP 文件的简单示例,您需要检查文件是否为目录。但这是最简单的。

您缺少的步骤是读取输入流并将内容写入缓冲区,然后再写入输出流。

// 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();
    }
}

代码摘自以下网站:

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

【讨论】:

    【解决方案2】:

    好吧,我已经这样做了:

     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 的内容。

    【讨论】:

    • 显然文件内容仍然被下载。您只是不需要将其写入(临时)文件。
    • 我认为@KennethClark 的解决方案更好。它适用于文本和二进制文件,而您的仅适用于文本文件,恕我直言。请注意,虽然他将提取的内容存储到文件中,但这只是如何将内容复制到另一个流的示例。它不必是文件流,也可以是内存流,或者根本不必是流。
    • 顺便说一句。存档内的文本文件大小约为 1 mB(111589 行文本)。阅读(while (sc.hasNextLine()) 没有 sysout 的语句)需要 38 秒。正常吗?
    • 试试@KennethClark 的解决方案。我可以想象Scanner 可能很慢。
    【解决方案3】:

    ZipInputStream 本身就是一个InputStream,并在每次调用getNextEntry() 后传递每个条目的内容。必须特别注意,不要关闭从中读取内容的流,因为它与 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.");
        }
    }
    

    当将读取内容委托给其他逻辑时,可能需要用 FilterInputStream 包装 ZipInputStream 以仅关闭条目而不是整个流,如下所示:

    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();
                }
            });
        }
    }
    

    【讨论】:

    • 包装FilterInputStream特别有用。
    【解决方案4】:

    OP 很接近。只需要读取字节。调用 getNextEntry positions the stream at the beginning of the entry data (docs)。如果这是我们想要的条目(或唯一的条目),那么 InputStream 就在正确的位置。我们需要做的就是读取该条目的解压缩字节。

    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);
    }
    

    所以如果这些字节真的是文本,那么我们可以将这些字节解码为字符串。我只是假设 utf8 编码。

    new String(bytes, "utf8")
    

    旁注:我个人使用 apache commons-io IOUtils 来减少这种较低级别的东西。 ZipInputStream.read 的文档似乎暗示读取将在当前 zip 条目的末尾停止。如果这是真的,那么读取当前的文本条目就是使用 IOUtils 的一行。

    String text = IOUtils.toString(zipStream)
    

    【讨论】:

      【解决方案5】:

      将保留文件结构的存档 (zip) 解压缩到给定目录中。 笔记;此代码在“org.apache.commons.io.IOUtils”上使用 deps),但您可以将其替换为您的自定义“读取流”代码

      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);
              }
            }
          }
        }
      }
      

      【讨论】:

        【解决方案6】:

        这里是使用 BiConsumer 处理 zip 输入流的更通用的解决方案。这和haii使用的解决方案几乎相同

        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(<some inputstream>, (entry, is) -> {
            /* don't forget to close this stream after processing. */
            is.read() // ... <- to read each entry
        });
        

        【讨论】:

          【解决方案7】:

          如果您的 ZIP 内容由 1 个文件组成(例如,HTTP 响应的压缩内容),您可以使用 Kotlin 读取文本内容,如下所示:

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

          此扩展功能解压缩 Zip 文件的第一个 ZIP 条目并将内容读取为纯文本。

          用法:

          val inputStream: InputStream = ... // your zipped InputStream
          val textContent = inputStream.readZippedContent()
          

          【讨论】:

            猜你喜欢
            • 2021-07-05
            • 2021-12-15
            • 2013-08-04
            • 1970-01-01
            • 2013-04-28
            • 2023-04-06
            • 1970-01-01
            • 2018-02-23
            相关资源
            最近更新 更多