จะอ่านไฟล์จาก ZIP โดยใช้ InputStream ได้อย่างไร

ฉันต้องรับเนื้อหาไฟล์จากไฟล์ ZIP (ฉันรู้ชื่อไฟล์เดียวเท่านั้น) โดยใช้ SFTP สิ่งเดียวที่ฉันมีคือ ZIP's 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();
        }


    }
}

person Tony    schedule 26.05.2014    source แหล่งที่มา
comment
ฉันจำเป็นต้องเขียนไฟล์ zip ใหม่จริง ๆ หรือไม่ หากฉันต้องการอ่านเนื้อหาไฟล์ txt เท่านั้น   -  person Tony    schedule 26.05.2014
comment
ไม่มีเหตุผลใดที่ไม่ควรทำงาน คุณเพียงแค่ต้องได้รับ ZIPEntries ทั้งหมดและบันทึกสิ่งเหล่านั้นจากสตรีม   -  person Kenneth Clark    schedule 26.05.2014


คำตอบ (7)


ด้านล่างนี้เป็นตัวอย่างง่ายๆ เกี่ยวกับวิธีการแตกไฟล์ 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

person Kenneth Clark    schedule 26.05.2014

ฉันทำสิ่งนี้แล้ว:

 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 โดยไม่ต้องเขียนไปยังไฟล์อื่น

person Tony    schedule 26.05.2014
comment
แน่นอนว่าเนื้อหาไฟล์ยังคงดาวน์โหลดอยู่ คุณไม่จำเป็นต้องเขียนลงในไฟล์ (ชั่วคราว) - person Martin Prikryl; 27.05.2014
comment
ฉันคิดว่าวิธีแก้ปัญหาโดย @KennethClark ดีกว่า มันใช้งานได้ทั้งกับไฟล์ข้อความและไบนารี่ในขณะที่ไฟล์ของคุณใช้ได้กับไฟล์ข้อความเท่านั้น โปรดทราบว่าแม้ว่าเขาจะจัดเก็บเนื้อหาที่แยกออกมาแล้วลงในไฟล์ แต่เป็นเพียงตัวอย่างวิธีการคัดลอกเนื้อหาไปยังสตรีมอื่น ไม่จำเป็นต้องเป็นสตรีมไฟล์ แต่สามารถเป็นสตรีมหน่วยความจำได้เช่นกัน หรือไม่จำเป็นต้องเป็นสตรีมเลย - person Martin Prikryl; 27.05.2014
comment
อนึ่ง. ขนาดของไฟล์ข้อความภายในไฟล์เก็บถาวรคือประมาณ 1 MB (ข้อความ 111589 แถว) และการอ่าน ( while (sc.hasNextLine()) คำสั่งที่ไม่มี sysout) ใช้เวลา 38 วินาที เป็นเรื่องปกติหรือไม่? - person Tony; 27.05.2014
comment
ลองใช้วิธีแก้ปัญหาของ @KennethClark ฉันนึกภาพได้ว่า Scanner อาจจะช้า - person Martin Prikryl; 27.05.2014

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

เมื่อมอบหมายการอ่านเนื้อหาไปยังตรรกะอื่น อาจจำเป็นต้องล้อม 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();
            }
        });
    }
}
person haui    schedule 01.06.2018
comment
การห่อ FilterInputStream มีประโยชน์อย่างยิ่ง - person Ng Zhong Qin; 05.10.2020

โอพีใกล้แล้ว เพียงแค่ต้องอ่านไบต์ การเรียกไปยัง getNextEntry positions the stream at the beginning of the entry data (เอกสาร) หากนั่นคือรายการที่เราต้องการ (หรือรายการเดียว) แสดงว่า 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)
person Jason Dunkelberger    schedule 05.06.2019

นี่เป็นโซลูชันทั่วไปในการประมวลผลอินพุตสตรีม 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(<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

คลายซิปไฟล์เก็บถาวร (zip) พร้อมรักษาโครงสร้างไฟล์ไว้ในไดเร็กทอรีที่กำหนด บันทึก; รหัสนี้ใช้ deps บน "org.apache.commons.io.IOUtils") แต่คุณสามารถแทนที่ด้วยรหัส 'read-stream' ที่คุณกำหนดเองได้

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

หากเนื้อหา ZIP ของคุณประกอบด้วย 1 ไฟล์ (เช่น เนื้อหา ZIP ของการตอบกลับ 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()
person mtwain    schedule 13.06.2020