Search code examples
javafilezipbufferedreaderfile-read

BufferedReader readLine returns null


I have a zip file that contains 9 files that I want to read. So I implemented the following function:

public static Map<String, BufferedReader> unzipFile(InputStream zippedFile) throws IOException {
  ZipInputStream zipInputStream = new ZipInputStream(zippedFile);
  HashMap<String, BufferedReader> result = new HashMap<>(9);
  for (ZipEntry zipEntry = zipInputStream.getNextEntry(); zipEntry != null; zipEntry = zipInputStream.getNextEntry()) {
    if (zipEntry.isDirectory()) {
      continue;
    }
    result.put(zipEntry.getName(), new BufferedReader(new InputStreamReader(zipInputStream)));
  }
  return result;
}

Other signatures in the same class (same method called):

  public static Map<String, BufferedReader> unzipFile(String zippedFile) throws IOException {
    return unzipFile(new File(zippedFile));
  }

  public static Map<String, BufferedReader> unzipFile(File zippedFile) throws IOException {
    return unzipFile(new FileInputStream(zippedFile));
  }

My idea was to get the Map that returns from this method and be able to read it somewhere else on my code. The problem is that every time I call result.get("filename").readLine() outside the for loop in this method, it returns null.

Here a simple unit test for this method - that is currently failing in the last Assert.assertNotNull:

  @Test
  public void unzipFileTest() throws Exception  {
    Map<String, BufferedReader> result = FileHandlerUtility.unzipFile(TestUtil.FILE_SAMPLE_NAME);
    Assert.assertNotNull(result);
    Assert.assertEquals(result.size(), 9);
    Assert.assertNotNull(result.get("Car.txt"));
    Assert.assertNotNull(result.get("Client.txt"));
    Assert.assertNotNull(result.get("Client.txt").readLine());
  }

I guess there might be something related with the scope of the variable created, since, when debugging, I've noticed I can get the contents of the file inside the for loop. However, I did not want to mix this zip extraction method with the method that gets the content and parses it. Also, I do not want to save the extracted files to disc and reopen them later.

So, how could I fill this Map with BufferedReaders that wouldn't return null on readLine calls?


Solution

  • ZipInputStream

    Everytime you iterate the entry pointer, you lose the entry pointer as only one stream is allowed per ZipInputStream object.

    try (ZipInputStream is = new ZipInputStream(Zippy.class.getResourceAsStream("file.zip"))) {
                ZipEntry entry;
                while ((entry = is.getNextEntry()) != null) {
                    if (!entry.isDirectory()) {
                        // do your logic here (get the entry name)
                    }                
                    is.closeEntry();
            }
        }
    

    from the javadocs:

    closeEntry() -Closes the current ZIP entry and positions the stream for reading the next entry.

    getNextEntry() - Reads the next ZIP file entry and positions the stream at the beginning of the entry data.
    

    Basically you can only have one entry open at any given time.

    So if you want to do what you are doing, the best you can do is save the name of the zip entry and iterate through the ZipInputStream to move the pointer to the right place and then you can use ZipInputStream.read() to get the bytes.

    Zip File

    If you can use a ZipFile object (instead of ZipInputStream) you can do what you are trying to accomplish.

     static void loadMap() throws IOException {
        ZipFile file = new ZipFile("testzip.zip");
        Enumeration<? extends ZipEntry> entries = file.entries();
        while (entries.hasMoreElements()) {
            ZipEntry entry = entries.nextElement();
            if (!entry.isDirectory()) {
                streamMap.put(entry.getName(), new BufferedReader(new InputStreamReader(file.getInputStream(entry))));
            }
    
        }
    
    }
    
    public static void main(String[] args) throws IOException {
        loadMap();
    
        Iterator<BufferedReader> iter = streamMap.values().iterator();
        while (iter.hasNext()) {            
            BufferedReader reader = iter.next();
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        }
    }
    
    
    OUTPUT:
    file 2: First line!
    file 2: Second Line!
    file 1: First line!
    file 1 : Second Line!
    BUILD SUCCESSFUL (total time: 0 seconds)
    

    You just have to remember to hold the zip file reference and call file.close(); when you are done with it.