首页 > 解决方案 > 为什么`.hasNext()` 会消耗`BufferedReader` 中的元素?

问题描述

问题陈述

在我在 wsl Ubuntu 上执行的编译.jar文件中,我运行命令:task reportToGetUrgencyOfAllTasks它返回一个包含列的任务列表:id, uuid, urgency。我可以检查列表,它被打印到终端,它包含 1793 个任务和 1798 行(一个标题和一些关于需要同步的不相关消息)。当我用它计算对象的数量时,reader.lines().count()它会返回 1798 并按预期使用这些行。

如果我创建一个while循环:

long counter = 0;
while (reader.lines().iterator().hasNext()) {
    counter++;
    System.out.println("counter=" + counter);
}

它列出了数字 1 到 1798,这很奇怪,因为我认为它.hasNext()没有消耗项目,而只是检查下一个元素是否存在,而不是从流中取出它。(如此处建议:https ://hajsoftutorial.com/iterator-hasnext-and-next/ )。我期望一个无限循环的数字,因为迭代器会在hasNext().

那么,如果我想从阅读器中获取所有元素并将它们放入 ArrayList 中:

ArrayList<String> capturedCommandOutput = new ArrayList<String>();
while (reader.lines().iterator().hasNext()) {
    counter++;
    capturedCommandOutput.add(reader.lines().iterator().next());
}

它跳过每隔一行。

问题

为什么hasNext()在这种情况下使用迭代器中的元素?

完整代码 对于 MWE,需要在 WSL Ubuntu 上安装 taskwarrior,但是,我认为这是一个 Java 问题,因为我可以看到阅读器确实包含所有行/信息,因此为了完整性:运行命令的完整方法是:

package customSortServer;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Map;
import java.util.StringJoiner;

public class RunCommandsLongOutput2 {
    /**
     * This executes the commands in terminal. Additionally it sets an environment
     * variable (not necessary for your particular solution) Additionally it sets a
     * working path (not necessary for your particular solution)
     * 
     * @param commandData
     * @param ansYes
     * @throws Exception
     */
    public static ArrayList<String> executeCommands(Command command, Boolean ansYes) {
        ArrayList<String> capturedCommandOutput = new ArrayList<String>();
        File workingDirectory = new File(command.getWorkingDirectory());

        // create a ProcessBuilder to execute the commands in
        ProcessBuilder processBuilder = new ProcessBuilder(command.getCommandLines());

        // this is set an environment variable for the command (if needed)
        if (command.isSetEnvVar()) {
            processBuilder = setEnvironmentVariable(processBuilder, command);
        }

        // this can be used to set the working directory for the command
        if (command.isSetWorkingPath()) {
            processBuilder.directory(workingDirectory);
        }

        // execute the actual commands
        try {
            Process process = processBuilder.start();
            System.out.println("Started");
            if (command.isGetOutput()) {

                // capture the output stream of the command
                BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
//              System.out.println(reader.lines().count());
//              long counter = 0;
                while (reader.lines().iterator().hasNext()) {

                    capturedCommandOutput.add(reader.lines().iterator().next());
                }
//              while (reader.lines().iterator().hasNext()) {
//                  counter++;
//                  System.out.println("line=" + reader.lines().iterator().next());
//              }
            }

            // connect the output of your command to any new input.
            // e.g. if you get prompted for `yes`
            new Thread(new SyncPipe(process.getErrorStream(), System.err)).start();
            new Thread(new SyncPipe(process.getInputStream(), System.out)).start();
            PrintWriter stdin = new PrintWriter(process.getOutputStream());

            // This is not necessary but can be used to answer yes to being prompted
            if (ansYes) {
                stdin.println("yes");
            }

            // write any other commands you want here
            stdin.close();

            // If the command execution led to an error, returnCode!=0, or not (=0).
            int returnCode = process.waitFor();
            System.out.println("Return code = " + returnCode);
        } catch (IOException e1) {
            e1.printStackTrace();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        // return output if required:
        return capturedCommandOutput;
    }

    /**
     * source: https://stackoverflow.com/questions/7369664/using-export-in-java
     * 
     * @param processBuilder
     * @param varName
     * @param varContent
     * @return
     */
    private static ProcessBuilder setEnvironmentVariable(ProcessBuilder processBuilder, Command command) {
        Map<String, String> env = processBuilder.environment();
        env.put(command.getEnvVarName(), command.getEnvVarContent());
        processBuilder.environment().put(command.getEnvVarName(), command.getEnvVarContent());
        return processBuilder;
    }
}

生成命令的方法是:

    public static void createCommandToGetUrgencyList(HardCoded hardCoded) {

        // create copy command
        Command command = new Command();
        String[] commandLines = new String[2];
        commandLines[0] = "task";
        commandLines[1] = hardCoded.getGetUrgencyReportName();
        command.setCommandLines(commandLines);
        command.setEnvVarContent("/var/taskd");
        command.setEnvVarName("TASKDDATA");
        command.setWorkingPath("/usr/share/taskd/pki");
        command.setGetOutput(true);

        // execute command to copy file
        ArrayList<String> urgencyList = RunCommandsLongOutput2.executeCommands(command, false);
        System.out.println("The urgency list has length = "+urgencyList.size());
        for (int i = 0; i < urgencyList.size(); i++) {
            System.out.println("The output of the command =" + urgencyList.get(i));
        }
    }

标签: javaiteratornext

解决方案


您创建多个Iterators - 在循环的条件中创建一个,然后在循环的每次迭代中再创建一个。

它应该是:

Iterator<String> iter = reader.lines().iterator();
long counter = 0;
while (iter.hasNext()) {
    counter++;
    System.out.println("counter=" + counter);
    capturedCommandOutput.add(iter.next());
}

由于每一个Iterator都是从不同的Stream<String>(由 返回lines())生成的,因此可以iterator()在每个 s 上调用终端操作 ( ) ,Stream但是当您调用s的方法IteratorhashNext()或数据 - .next()IteratorBufferedReader

正如 Javadoclines()所说:

终端流操作执行过程中不得对阅读器进行操作。否则,终端流操作的结果是不确定的。

iterator()是一个终端操作,只要你迭代Iterator它返回的,你仍然没有完成终端操作。因此,在完成Iterator. 第二次调用lines()算作对阅读器的操作。


推荐阅读