【问题标题】:Resetting FileInputStream in Java so I can run multiple instances of another program in a test program在 Java 中重置 FileInputStream,以便我可以在测试程序中运行另一个程序的多个实例
【发布时间】:2017-02-21 12:20:21
【问题描述】:

我的问题是我被指派修改和改进一个执行 LZW 压缩的程序。据我所知,我的程序运行良好,但依赖于 System.in 和 System.out 重定向输入文件和输出文件。对于我的测试程序,我运行了一个充满文件的文件夹,并且对于每个文件,对程序运行 4 个不同的测试。为了模拟命令行输入/输出重定向,在每个文件的循环的第一次迭代中,我执行以下操作:

FileOutputStream fos = new FileOutputStream(compressOut); // compressOut is compressed file
PrintStream ps = new PrintStream(fos);
System.setOut(ps);
FileInputStream fis = new FileInputStream(file); // file is file to be compressed
System.setIn(fis);
fis.mark(0);// mark not supported so this is actually left over from my test code -- wanted to show I tried to do this

第一次运行我正在测试的程序时,它运行良好并返回一个压缩比并准备将其写入程序输出文件。

但是,在第二次程序调用时(如果我要消除所有额外的调用,则 for 循环的第二次迭代)它崩溃并返回输入流为空。我还没有机会看看输出流是否会做同样的事情,但我试图重置流的方法如下:

在每次调用程序调用之前,我都有以下代码块,我**认为可以解决问题,但无济于事:

//System.setIn(null); // Tried - didn't work
//System.setOut(null); // Tried - didn't work
fos.close();
ps.close();
fis.close();
fos = new FileOutputStream(compressOut);
ps = new PrintStream(fos);
System.setOut(ps);
fis = new FileInputStream(file);
System.setIn(fis);
//fis.reset(); // Tried - didn't work

我尝试了各种不同方法的组合来重置输入流,但每个解决方案仍然返回相同的结果,一条错误消息表明它正在从空输入流中读取 - 一个错误意味着该流是在文件末尾(或文件中没有任何内容)。

我能得到的唯一其他错误是,如果我尝试使用 mark(0),则在调用 reset 时不支持 mark();和重置();方法。

我已经阅读完毕,但找不到任何可靠的答案来说明如何让它发挥作用。该程序将StdIn 转换为BufferedInputStreamStdOut 转换为BufferedOutputStream,因此我需要一种兼容并提供类似于StdIn/StdOut 重定向的性能的方法。

TL;DR:我需要重置我的 FileInputStream,但使用 mark() reset() 或重新初始化 FileInputStream 都无法这样做

更新: 尝试按照类似帖子中的建议将 FIS 包装到 BufferedInputStream 中,其中支持 mark()。但是,mark()reset() 方法仅适用于 BufferedInputStream 的大小(我认为是 4096 字节),这几乎排除了我所有文件中标记/重置功能的潜在用途。它们都大于 4kB :(

更新 2: 我决定将完整的 sn-p 代码从我开始 for 循环的地方发布到发生空流错误的地方,以防任何人都能看到我看不到的东西'吨。这是有问题的代码:

public static void main(String[] args) throws IOException, InterruptedException {
    File programOutputPath = new File("-- the compression file path --");
    File folderPath = new File("-- the relative folder path --");
    File compressOut = new File(folderPath + "compressed.lzw");
    File[] allFiles = folderPath.listFiles(); // get an array of all files
    FileWriter fw = new FileWriter(programOutputPath);
    for(File file : allFiles) {
        FileOutputStream fos = new FileOutputStream(compressOut);
        PrintStream ps = new PrintStream(fos);
        System.setOut(ps);
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
        System.setIn(bis);
        String[] cArgs = new String[2];
        cArgs[0] = "-"; cArgs[1] = "-n";
        String[] dArgs = new String[1];
        dArgs[0] = "+";
        String[] LZWArgs = new String[1];
        LZWArgs[0] = "-";
        System.err.println("File: " + file.toString());
        if(!file.canWrite()) {
            System.err.println("SKIPPED FILE");
        }
        if(file.getName().equalsIgnoreCase(".gitignore") || file.getName().equalsIgnoreCase("compressed.lzw")) continue;
        MyLZW.main(cArgs); // runs fine
        if(decompress) {
            //MyLZW.main(dArgs); // not executing this
        }
        long sizeUnc = file.length();
        long sizeC = compressOut.length();
        double ratio = (double)sizeUnc/(double)sizeC; // compression ratio is correct
        System.err.println("java MyLZW - -r <" + file.getName() + "> " + " compressed.lzw compression ratio:" + ratio ); // works fine
        fw.write("java MyLZW - -n <" + file.getName() + "> " + " compressed.lzw compression ratio:" + ratio + "\n");
        cArgs[1] = "-r";
        bis.close(); // close BufferedInputStream
        bis = new BufferedInputStream(new FileInputStream(file)); // reinitialize BIS
        System.setIn(bis); // setIn to newly initialized BufferedInputStream
        MyLZW.main(cArgs); // crashes here b/c empty input stream

【问题讨论】:

  • “我的程序......依赖 System.in 和 System.out......” 将其更改为依赖 InputStream 和 OutputStream。这样会更加灵活,并且您可以将新流传递给每个调用。
  • @VGR 感谢您的建议,如果一切都失败了,我会尝试这个,但我宁愿不必编辑程序运行的依赖项,因为我按原样获得了依赖项并告诉不要编辑它们。我应该接触的唯一文件是控制压缩的源文件。这更像是一种想弄清楚如何让我的程序以这种方式工作的好奇心,而不是让它以这种方式工作的绝对必要性。简而言之,我主要想弄清楚如何根据未来工作的需要动态更改 StdOut 和 StdIn。非常感谢您的建议:)
  • 如果您想重新启动 FileInputStream,只需创建一个新的(或切换到 RandomAccessFile)。流在设计上只能读取一次 - 标记/重置系统只是在没有其他选择或您只需要倒带几个字节的情况下的拐杖。
  • @Robert 感谢您的回复。我尝试同时使用 FileInputStream 和 BufferedInputStream。在一个例子中,我尝试重新初始化已经声明的 FIS/BIS,并且我还尝试声明一个全新的并将 System.in 切换到那个。他们都在程序第二次运行时读取了一个空的输入流,我确实重新初始化了 BIS/FIS(我都试过了)并将 System.in 设置为适当的参考。这让我很摸不着头脑。

标签: java inputstream fileinputstream system.out system.in


【解决方案1】:

听起来问题出在程序内部;它可能会在后续调用中保留对 System.in 原始值的引用。

另一种可能性是程序正在破坏输入文件(将其替换为空文件)作为副作用。

重复调用System.setIn() 没有问题,并且像您所做的那样创建一个新的FileInputStream 确实会从头开始再次读取文件。因此,问题在于您没有发布的代码。


在程序内部,输入被初始化为一个静态变量,像这样:

private static BufferedInputStream in = new BufferedInputStream(System.in); 

当类被加载时,初始化只发生一次。这是一个糟糕的设计,应该修复它。因为System.in 只读取一次,所以您对System.setIn() 所做的后续更改将被忽略。

如果您无法正确修复它,您可以使用反射重置该变量,但这个问题可能只是糟糕设计的冰山一角。或者,您可以尝试将该程序隔离在其自己的类加载器中,并在您的主进程每次运行该程序时创建一个新的类加载器。

【讨论】:

  • 谢谢,我会仔细研究一下,看看能不能找到什么。您有什么想法可能导致它保留对原始 System.in 的引用吗?我也是这么想的,因为它肯定会到达文件末尾而不是更新。我也可以发布我的代码,但我完全清楚,通过我的代码来查找错误不是任何人的工作或义务。
  • @dddJewelsbbb 看起来MyLZW 被设计为在一个进程中运行一次,因此它可能会使用不良做法,例如将值存储在静态变量中。同样,这个问题在MyLZW 内部,由于你没有发布该代码,我只能猜测。
  • @dddJewelsbbb 这真的取决于如何使用这些静态变量。我很难想象为什么,即使它们是静态的,每次在 MyLZW 上调用 main 时,这些变量也不会被重置。你能看出这是为什么吗?这些变量最初是如何设置的?
  • @dddJewelsbbb 好的,代码清楚地表明:这些变量只初始化一次,在加载类时。这是一个非常糟糕的设计,我只能想到两个解决方案:重写类以使其正常运行,或者在每次运行之前使用反射来重置这些变量。第二种选择很荒谬,这两个变量可能只是众多症状中的第一个,所有症状都源于糟糕的设计。
  • @dddJewelsbbb 第三种选择是将程序加载到单独的类加载器中,并在每次运行后丢弃该加载器。那也很棘手。