让 InputStream 读取不止一次,不管 markSupported()

Posted

技术标签:

【中文标题】让 InputStream 读取不止一次,不管 markSupported()【英文标题】:Getting an InputStream to read more than once, regardless of markSupported() 【发布时间】:2011-12-05 16:28:29 【问题描述】:

我需要能够多次重复使用java.io.InputStream,我认为下面的代码可以工作,但它只能在第一次工作。

代码


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
    
  

注意 ArrayTable 是我的一个类,它使用数组来提供处理表格的接口。

问题


在这个程序中,数据库在调用reloadDatabase() 方法后立即直接显示给用户,因此任何涉及将初始读取保存到内存中的对象的解决方案都是无用的,因为这不会刷新数据(把它想象成一个浏览器;当你按下“刷新”时,你希望它再次获取信息,而不仅仅是显示它第一次获取的信息)。 如何多次阅读java.io.InputStream

【问题讨论】:

一种方法是读取整个byte[] 并将它们用作ByteArrayInpoutStream 的构造函数。这些资源有多大(通常)? 你所说的'使用..多次..'是什么意思?你在哪里使用多次使用的代码?需要更明确的输入。 @AndrewThompson 很大。我们的想法是能够将 Excel 或(在这种情况下)CSV 电子表格加载和重新加载到该程序中,以便外部程序可以对其进行编辑以进行自定义。 我的意思是我希望方法loadDatabaseToArrayTable() 被多次调用,并且每次都有效。当我第二次或更长时间调用它时,fileScanner.hasNextLine() 不断返回false,但第一次,它完美运行。 How to Cache InputStream for Multiple Use 的可能重复项 【参考方案1】:

您不一定能多次读取 InputStream。有些实现支持它,有些不支持。您正在做的是检查 markSupported 方法,如果您可以两次读取相同的流,这确实是一个指标,但是您忽略了结果。您必须调用该方法来查看您是否可以读取两次流,如果不能,请进行其他安排。

编辑(回应评论):当我写下我的答案时,我的“其他安排”是获得一个新的 InputStream。但是,当我在您的 cmets 中阅读您关于您想要做什么的问题时,我不确定这是否可能。对于操作的基础知识,您可能需要 RandomAccessFile (至少这是我的第一个猜测,如果它有效,那将是最简单的) - 但是您将遇到文件访问问题。您有一个应用程序主动写入文件,而另一个应用程序正在读取该文件,您将遇到问题 - 确切的问题将取决于操作系统,因此无论哪种解决方案都需要更多测试。我建议针对这一点提出一个关于 SO 的单独问题,尝试过该问题的人或许可以为您提供更多见解。

【讨论】:

我应该做哪些“其他安排”? 查看我的编辑,编写[文件等]的应用程序只会定期(大约每月一次或两次)这样做,就像旧网页一样更新(手动从另一个用户那里)。查看原始输入流并创建一个从同一源中提取的新输入流真的那么难吗? @Yishai 如果不支持“多次阅读”会影响 reset() 方法吗?请评论【参考方案2】:

您永远不会标记要重置的流

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

但是,您可以保留原始 ArrayTable 的副本,并在需要时复制它(甚至是创建的字符串来重建它)

此代码创建字符串并将其缓存,因此您可以安全地丢弃输入流并使用readCSV 构建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);
  

但是,如果您需要新信息,则需要创建一个新流以再次读取

【讨论】:

对不起,我忘了复制整个构造函数^^; firstDBInputStream 在初始化后立即被标记,记住数据库对我没有任何好处。存储在 ArrayTable 中的数据库会立即显示在程序的主窗口中,并且在重新读取之前一直保持这种状态,因此每当用户要求重新读取数据库时,我都需要重新读取数据库。 @Supuhstar 我的观点是,您可以保留对完全读取的字符串的引用,从而允许您在不再需要流时丢弃流。检查我的编辑 感谢您的澄清,但在这种特殊情况下它仍然对我没有帮助。 @Supuhstar 只是在您需要读取更新时重新创建输入流。没有别的办法 我想我必须想办法做到这一点。问题是它可以是任何输入流,包括java.io.FileInputStreamjava.util.jar.JarInputStream 等等

以上是关于让 InputStream 读取不止一次,不管 markSupported()的主要内容,如果未能解决你的问题,请参考以下文章

解决HttpServletRequest InputStream只能读取一次问题

InputStream中的read方法

从 InputStream 读取时出现 IOException

httpServletRequest中的流只能读取一次的原因

httpServletRequest中的流只能读取一次的原因

两次读取 InputStream 的一部分