12

I need to be able to re-use a java.io.InputStream multiple times, and I figured the following code would work, but it only works the first time.

Code


public class Clazz
{
  private java.io.InputStream dbInputStream, firstDBInputStream;
  private ArrayTable db;

  public Clazz(java.io.InputStream defDB)
  {
    this.firstDBInputStream = defDB;
    this.dbInputStream = defDB;
    if (db == null)
      throw new java.io.FileNotFoundException("Could not find the database at " + db);
    if (dbInputStream.markSupported())
      dbInputStream.mark(Integer.MAX_VALUE);
    loadDatabaseToArrayTable();
  }

  public final void loadDatabaseToArrayTable() throws java.io.IOException
  {
    this.dbInputStream = firstDBInputStream;
    if (dbInputStream.markSupported())
      dbInputStream.reset();

    java.util.Scanner fileScanner = new java.util.Scanner(dbInputStream);
    String CSV = "";
    for (int i = 0; fileScanner.hasNextLine(); i++)
      CSV += fileScanner.nextLine() + "\n";
    db = ArrayTable.createArrayTableFromCSV(CSV);
  }

  public void reloadDatabase()//A method called by the UI
  {
    try
    {
      loadDatabaseToArrayTable();
    }
    catch (Throwable t)
    {
      //Alert the user that an error has occurred
    }
  }
}

Note that ArrayTable is a class of mine, which uses arrays to give an interface for working with tables.

Question


In this program, the database is shown directly to the user immediately after the reloadDatabase() method is called, and so any solution involving saving the initial read to an object in memory is useless, as that will NOT refresh the data (think of it like a browser; when you press "Refresh", you want it to fetch the information again, not just display the information it fetched the first time). How can I read a java.io.InputStream more than once?

Ky -
  • 30,724
  • 51
  • 192
  • 308
  • One way is to read the entire `byte[]` and use them as constructor for a `ByteArrayInpoutStream`. How big are these resources (typically)? – Andrew Thompson Oct 11 '11 at 21:56
  • What do you mean by 'using .. multiple times ..?` and where are you employing the code to be used multiple times ? Need more clear inputs. – Bhaskar Oct 11 '11 at 21:57
  • @AndrewThompson quite large. The idea is to be able to load and reload an Excel or (in this case) CSV spreadsheet into this program, so that it can be edited by external programs for customization. – Ky - Oct 11 '11 at 22:04
  • I mean I want the method `loadDatabaseToArrayTable()` to be called more than once, and work each time. When I call it the second or further time, `fileScanner.hasNextLine()` constantly returns `false`, but the first time, it works perfectly. – Ky - Oct 11 '11 at 22:05
  • 64 Meg. of RAM should be sufficient to load Excel files containing 1os of thousands of rows of data. What happened when you tried loading a typical ELS as I described? BTW - RAM is cheap in this day & age, so increasing the max. heap size is usually an option. – Andrew Thompson Oct 11 '11 at 22:09
  • I never mentioned RAM or any sort of memory issues, not to mention the fact that you never described "loading a typical ELS" in this question... So I'm not sure why you're pointing this out. – Ky - Oct 12 '11 at 01:06
  • 1
    possible duplicate of [How to Cache InputStream for Multiple Use](http://stackoverflow.com/questions/924990/how-to-cache-inputstream-for-multiple-use) – naXa stands with Ukraine Sep 17 '14 at 07:15

2 Answers2

9

You can't necessarily read an InputStream more than once. Some implementations support it, some don't. What you are doing is checking the markSupported method, which is indeed an indicator if you can read the same stream twice, but then you are ignoring the result. You have to call that method to see if you can read the stream twice, and if you can't, make other arrangements.

Edit (in response to comment): When I wrote my answer, my "other arrangements" was to get a fresh InputStream. However, when I read in your comments to your question about what you want to do, I'm not sure it is possible. For the basics of the operation, you probably want RandomAccessFile (at least that would be my first guess, and if it worked, that would be the easiest) - however you will have file access issues. You have an application actively writing to a file, and another reading that file, you will have problems - exactly which problems will depend on the OS, so whatever solution would require more testing. I suggest a separate question on SO that hits on that point, and someone who has tried that out can perhaps give you more insight.

Yishai
  • 90,445
  • 31
  • 189
  • 263
  • What "Other arrangements" should I make? – Ky - Oct 11 '11 at 22:06
  • See my edit, the application writing the [file, etc.] only does so periodically (about once or twice a month), updating like an old webpage would (manually from another user). Is it really that hard to look at the original input stream and create a new one that draws from the same source? – Ky - Oct 12 '11 at 02:39
  • @Yishai what if "read more than once" is not supported will that effect the reset() method as well? Comment please – user390525 Apr 14 '16 at 19:52
2

you never mark the stream to be reset

public Clazz(java.io.InputStream defDB)
  {
    firstDBInputStream = defDB.markSupported()?defDB:new BufferedInputStream(defDB);
      //BufferedInputStream supports marking
    firstDBInputStream.mark(500000);//avoid IOException on first reset
  }

public final void loadDatabaseToArrayTable() throws java.io.IOException
  {
    this.dbInputStream = firstDBInputStream;

    dbInputStream.reset();
    dbInputStream.mark(500000);//or however long the data is

    java.util.Scanner fileScanner = new java.util.Scanner(dbInputStream);
    StringBuilder CSV = "";//StringBuilder is more efficient in a loop
    while(fileScanner.hasNextLine())
      CSV.append(fileScanner.nextLine()).append("\n");
    db = ArrayTable.createArrayTableFromCSV(CSV.toString());
  }

however you could instead keep a copy of the original ArrayTable and copy that when you need to (or even the created string to rebuild it)

this code creates the string and caches it so you can safely discard the inputstreams and just use readCSV to build the ArrayTable

  private String readCSV=null;
  public final void loadDatabaseToArrayTable() throws java.io.IOException
  {
    if(readCSV==null){

        this.dbInputStream = firstDBInputStream;


        java.util.Scanner fileScanner = new java.util.Scanner(dbInputStream);
        StringBuilder CSV = "";//StringBuilder is more efficient in a loop
        while(fileScanner.hasNextLine())
          CSV.append(fileScanner.nextLine()).append("\n");

        readCSV=CSV.toString();
        fileScanner.close();

    }
    db = ArrayTable.createArrayTableFromCSV(readCSV);
  }

however if you want new information you'll need to create a new stream to read from again

ratchet freak
  • 47,288
  • 5
  • 68
  • 106
  • sorry, I forgot to finish copying the whole constructor ^^; firstDBInputStream is marked right after is initialized, and remembering the database won't do me any good. The database stored in the ArrayTable is immediately displayed in the program's main window, and stays that way until it is re-read, so I need to re-read the database whenever the user calls for it to be re-read. – Ky - Oct 12 '11 at 01:05
  • @Supuhstar my point stands you can keep the a reference to the fully read in string allowing you to discard the stream as you don't need them anymore. check my edit – ratchet freak Oct 12 '11 at 05:33
  • Thanks for clarifying, but it still doesn't help me in this particular case. – Ky - Oct 12 '11 at 14:11
  • 1
    @Supuhstar just recreate the input stream when you need to read updates. there's no other way – ratchet freak Oct 12 '11 at 14:19
  • I think I'll have to find some way to do that. The problem is that it could be any input stream, including `java.io.FileInputStream`, `java.util.jar.JarInputStream`, et cetera – Ky - Oct 12 '11 at 14:28