为什么 .hasNext() 会消耗来自 BufferedReader 的元素?
Why does `.hasNext()` consume elements from `BufferedReader`?
问题陈述
在我在 wsl Ubuntu 上执行的已编译 .jar
文件中,我 运行 命令:task reportToGetUrgencyOfAllTasks
其中 returns 包含列的任务列表:id, uuid, urgency
。我可以检查列表,它被打印到终端,它由 1793 个任务和 1798 行组成(一个 header 和一些关于需要同步的无关消息。)。当我用 reader.lines().count()
计算 objects 的数量时,它 returns 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()
.
之后永远停留在第一个元素处
那么如果我想从 reader 中取出所有元素并将它们放入 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 问题,因为我可以看到 reader 确实包含所有 lines/information,因此为了完整性:运行命令的完整方法是:
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:
*
* @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));
}
}
您创建多个 Iterator
s - 一个在循环条件中,然后在循环的每次迭代中再创建一个。
应该是:
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()
上调用终端操作 (iterator()
) Stream
s,但是当您调用 Iterator
s 的方法(hashNext()
或 next()
)时,Iterator
s 使用来自其单一来源的数据数据 - BufferedReader
.
正如 lines()
的 Javadoc 所说:
The reader must not be operated on during the execution of the terminal stream operation. Otherwise, the result of the terminal stream operation is undefined.
iterator()
是一个终端操作,只要在它返回的Iterator
上进行迭代,还是终端操作没有结束。因此,在完成 Iterator
之前,不应对 reader 进行操作。第二次调用 lines()
算作在 reader.
上操作
您正在一次又一次地从流创建流和迭代器。
调用 reader.lines()
每次都会创建一个 新流 - 它必须这样做,因为流不可重复使用。
在流上调用 iterator()
是一个 终端操作。
所以您正在做的是在 reader 中的剩余元素上创建一个流,然后在其上创建一个迭代器。
迭代器契约没有说它不会消耗流中的任何元素。它说的是它不消耗迭代器本身中的任何元素。也就是说,如果您使用
Iterator<String> iter = reader.lines().iterator();
并且您调用 iter.hasNext()
,您总是可以期望 reader 中可用的第一行是您从 iter
中读取的元素 - 而不是从任何其他迭代器中读取的元素同样的 reader.
实现这一点的一种方法是在您第一次调用 hasNext()
时从流中读取一个元素并将其缓冲。一旦你调用 next()
它会给你那个缓冲的元素。这维护了迭代器契约——但它仍然从 reader.
中读取一行
现在,如果您创建另一个流和另一个迭代器,它将只消耗下一行,依此类推。
您应该只在 reader 上调用一次 lines()
,并且您应该只在结果流上调用一次 iterator()
- 或者您应该只使用 "conservative"使用 readLine()
直到返回 null 的方法。
问题陈述
在我在 wsl Ubuntu 上执行的已编译 .jar
文件中,我 运行 命令:task reportToGetUrgencyOfAllTasks
其中 returns 包含列的任务列表:id, uuid, urgency
。我可以检查列表,它被打印到终端,它由 1793 个任务和 1798 行组成(一个 header 和一些关于需要同步的无关消息。)。当我用 reader.lines().count()
计算 objects 的数量时,它 returns 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()
.
那么如果我想从 reader 中取出所有元素并将它们放入 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 问题,因为我可以看到 reader 确实包含所有 lines/information,因此为了完整性:运行命令的完整方法是:
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:
*
* @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));
}
}
您创建多个 Iterator
s - 一个在循环条件中,然后在循环的每次迭代中再创建一个。
应该是:
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()
上调用终端操作 (iterator()
) Stream
s,但是当您调用 Iterator
s 的方法(hashNext()
或 next()
)时,Iterator
s 使用来自其单一来源的数据数据 - BufferedReader
.
正如 lines()
的 Javadoc 所说:
The reader must not be operated on during the execution of the terminal stream operation. Otherwise, the result of the terminal stream operation is undefined.
iterator()
是一个终端操作,只要在它返回的Iterator
上进行迭代,还是终端操作没有结束。因此,在完成 Iterator
之前,不应对 reader 进行操作。第二次调用 lines()
算作在 reader.
您正在一次又一次地从流创建流和迭代器。
调用 reader.lines()
每次都会创建一个 新流 - 它必须这样做,因为流不可重复使用。
在流上调用 iterator()
是一个 终端操作。
所以您正在做的是在 reader 中的剩余元素上创建一个流,然后在其上创建一个迭代器。
迭代器契约没有说它不会消耗流中的任何元素。它说的是它不消耗迭代器本身中的任何元素。也就是说,如果您使用
Iterator<String> iter = reader.lines().iterator();
并且您调用 iter.hasNext()
,您总是可以期望 reader 中可用的第一行是您从 iter
中读取的元素 - 而不是从任何其他迭代器中读取的元素同样的 reader.
实现这一点的一种方法是在您第一次调用 hasNext()
时从流中读取一个元素并将其缓冲。一旦你调用 next()
它会给你那个缓冲的元素。这维护了迭代器契约——但它仍然从 reader.
现在,如果您创建另一个流和另一个迭代器,它将只消耗下一行,依此类推。
您应该只在 reader 上调用一次 lines()
,并且您应该只在结果流上调用一次 iterator()
- 或者您应该只使用 "conservative"使用 readLine()
直到返回 null 的方法。