How to terminate a loop that is reading an input? - java

I'm wondering how terminate a loop when the end of input is reached. I searched a lot for this but the only solutions I encounter envolve using Scanner which I'm not using. Instead, I am using the following function that reads each line of the input however I'm not quite understanding how can I end a loop that is constantly reading random numbers which means I can't simply put a clause on the while(clause) to reach the end of the loop.
CODE:
The loop that i'm talking about:
public static void main(String[] args) {
String str = "";
while (true){
str = readLn(200);
}
}
The method using for read lines:
static String readLn (int maxLg){ //utility function to read from stdin
byte lin[] = new byte [maxLg]; int lg = 0, car = -1;
String line = "";
try {
while (lg < maxLg){
car = System.in.read();
if ((car < 0) || (car == '\n')) break; lin [lg++] += car;
} }
catch (IOException e){
return (null);
}
if ((car < 0) && (lg == 0)) return (null); // eof
return (new String (lin, 0, lg));
}

If a stream is closed, the .read() call will return -1, which causes car to be less than 0, which causes the method to return; null if nothing is read yet, otherwise a string with the contents you got so far.
Even if you loop this method, it'll just keep returning null - once a stream starts returning -1 it will continue to do so.
Most likely, the stream is NOT, in fact, 'closed'. System.in doesn't close just because you wish it so, or because you stop typing. Maybe you'll type some more.
One easy way to 'close' system.in is to pipe a file into your process. something like:
echo "hello" | java YourApp
if you insist on keyboard input, you're looking at CTRL+Z or CTRL+D depending on OS in order to get standard in to be considered 'closed', and a little praying.
the while loop in your main should either break if str is null, or needs to be a do/while construct, which whiles as long as str != null, or just while on str != null, but then make sure to initialize str to a nonnull value or it won't even enter the while in the first place.
The while loop in your readLn method is already causing that loop to end if standard in is closed.

For your special case (there are more efficient ways to read a string from the stdin), the return value in readLn is either a string or null for eof.
So you can terminate the loop if the returned value is null:
while (true){
str = readLn(200);
if (str == null) {
break;
}
}

Related

Code after while loop never executes

Obviously, my real code is more complex, but here's an example:
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in).useDelimiter("\n");
String[] cmdSplit = null;
while (true) {
while (input.hasNext()) {
cmdSplit = input.next().split("\\s+");
System.out.println("stuff");
}
for (int i = 0; i < cmdSplit.length; i++) System.out.println(cmdSplit[i]);
}
}
}
In the above example, the code takes input from System.in, splits it, and should output each piece. However, for some reason, the code after the inner while loop never executes. If I replace while with if, it works. If you test it, you can see it doesn't run infinitely, because it only prints "stuff" once, showing the loop runs once. What is the issue with the while loop?
Reading from System.in is different than reading from a file or other fixed-size source of input. The input doesn't necessarily exist until you create it, and so attempts to read it need to block until the input actually arrives (i.e. you type it). Try typing another line - you'll see the stuff message again; that will allow .hasNext() to return because there is now input.
To have .hasNext() return false the input source needs to be closed. For a command line application you can do this by sending the EOF signal (Ctrl+D on Linux) which tells the process stdin has no more input. That's not generally how you want a program to work, though, so if your intent is to only read one line and then move on, you should in fact be using an if instead of a while as you've tried to do. Later if you need to read more input you'll call .hasNext() again and your program will block there until the user passes more input.
As #user7 mentions your outer while (true) combined with while(input.hasNext()) is redundant. If you want to read only once get rid of the while (true) and use if (input.hasNext()). Otherwise if you want to read forever just combine the two loops:
while (input.hasNext()) {
cmdSplit = input.next().split("\\s+");
System.out.println("stuff");
for (int i = 0; i < cmdSplit.length; i++) System.out.println(cmdSplit[i]);
} // Loop will terminate once stdin is closed, e.g. by the user sending EOF.
Yes , your code won't go to the for loop because the Scanner.hasNext() will always listen to the console for inputs.
You have to break the loop in order to come out and go to the for loop.
Scanner input = new Scanner(System.in).useDelimiter("\n");
String[] cmdSplit = null;
while (true) {
while (input.hasNext()) {
cmdSplit = input.next().split("\\s+");
System.out.println("stuff");
break;
}
for (String element : cmdSplit) {
System.out.println(element);
}
}
The reason it is printing "stuff" only one time is because the hasNext() returned false.
Let me explain what I have observed.
To get "stuff" printed indefinately the assignment has to be removed. meaning once you assigned the input the scanner does not have any more token
The java.util.Scanner.hasNext() method Returns true if this scanner has another token in its input.
This will print indefinitely
while (input.hasNext()) {
// cmdSplit = input.next().split("\\s+");
System.out.println("stuff");
}

Under what conditions does Java's Scanner.hasNextLine() block?

The javadoc for Scanner.hasNextLine() states:
Returns true if there is another line in the input of this scanner.
This method may block while waiting for input. The scanner does
not advance past any input.
Under what conditions will the method block?
It depends on the source that the Scanner gets the input from.
For example, if it's a file, the entire input is available, so hasNextLine() wouldn't block (since it can know with certainty when the end of the file is reached and there's no more input.
On the other hand, if the source is standard input, there can always be more input - the user can always type more input - so hasNextLine() would block until the user types in a new line of input.
How to decide if it will block?
To decide if hasNextLine will block or not is unfortunately not a supported use case.
This is because the underlying sources doesn't always provide an API for peeking in the stream. Put differently, the implementation of hasNextLine calls methods that themselves may block so the problem is sort of inherent.
So, what to do?
If this is indeed a required use case, I would recommend one of the following approaches:
Make sure the conditions are right for the hasNextLine. Only provide the scanner with sources that have a definite end (such as a file or string) and never an "open ended" input such as System.in.
If this is part of an API you could wrap the scanner in your own class that only exposes "safe" constructors.
Roll your own class from scratch that does have a willHasNextLineBlock type of method. This could probably be implemented somewhat robustly using InputStream.available.
Under the category of super ugly workarounds we find:
Making an attempt at calling hasNextLine in a separate thread and see if it returns within reasonable time, as follows:
boolean wouldBlock = false;
Thread t = new Thread(() -> s.hasNextLine());
t.start();
try {
t.join(100);
} catch (InterruptedException e) {
wouldBlock = true;
}
Use a custom input stream (something like a peekable stream that one could tap into before calling hasNextLine. Something like the this
CustomStream wrapped = new CustomStream(originalSource)
Scanner s = new Scanner(wrapped);
...
if (wrapped.hasNextLine())
// s.hasNextLine would not block
else
// s.hasNextLine would block
(Note however that this is somewhat unsafe, since the scanner may have buffered some data from the CustomStream.)
Assuming by "decide if it will block" you mean that you want to know when it will bock.
Have a look at where the input is assigned in the hasNextLine method
String result = findWithinHorizon(linePattern(), 0);
Now, have a look at the findWithinHorizon method
public String findWithinHorizon(Pattern pattern, int horizon) {
ensureOpen();
if (pattern == null)
throw new NullPointerException();
if (horizon < 0)
throw new IllegalArgumentException("horizon < 0");
clearCaches();
// Search for the pattern
while (true) { //it may block here if it never break
String token = findPatternInBuffer(pattern, horizon);
if (token != null) {
matchValid = true;
return token;
}
if (needInput)
readInput();
else
break; // up to end of input
}
return null;
}
As you can see, it will loop infinitely until the end is reached, or until it succeed to read.
findPatternInBuffer is a private method of the Scanner class that try to read the input.
private String findPatternInBuffer(Pattern pattern, int horizon) {
matchValid = false;
matcher.usePattern(pattern);
int bufferLimit = buf.limit();
int horizonLimit = -1;
int searchLimit = bufferLimit;
if (horizon > 0) {
horizonLimit = position + horizon;
if (horizonLimit < bufferLimit)
searchLimit = horizonLimit;
}
matcher.region(position, searchLimit);
if (matcher.find()) {
if (matcher.hitEnd() && (!sourceClosed)) {
// The match may be longer if didn't hit horizon or real end
if (searchLimit != horizonLimit) {
// Hit an artificial end; try to extend the match
needInput = true;
return null;
}
// The match could go away depending on what is next
if ((searchLimit == horizonLimit) && matcher.requireEnd()) {
// Rare case: we hit the end of input and it happens
// that it is at the horizon and the end of input is
// required for the match.
needInput = true;
return null;
}
}
// Did not hit end, or hit real end, or hit horizon
position = matcher.end();
return matcher.group();
}
if (sourceClosed)
return null;
// If there is no specified horizon, or if we have not searched
// to the specified horizon yet, get more input
if ((horizon == 0) || (searchLimit != horizonLimit))
needInput = true;
return null;
}
I posted the whole method to give you a better idea of what I meant by "succeed to read".

Reading from InputStream - stuck in cycle

I have this piece of code:
(this code is inside another cycle, that is cycling 3 times)
...
text = "";
while((c = is.read())!=-1){
if(prev == '\r' && c == '\n'){
break;
}
text = text + (char) c;
prev = (char) c;
}
System.out.println(text);
...
is is InputStream, c is int, prev is char
With this code I build up a string from the InputStream. The reading should stop everytime when I get \r\n. Then it start's over again. Everything works fine except one thing. The stream I get looks like this:
1233\r\n544\r\nX
There is no delimeter after this input stream
With this I get the string 1233 from the first cycle and string 544 from the second cycle. But I won't get the last X, because the cycle won't stop there - and I don't know why. I thought that with is.read()=!-1 the cycle should stop when the stream ends. But it doesn't. The program is stuck inside that cycle.
Your question is unclear but here goes:
while( ( c = is.read() ) != -1 )
{
if(prev == '\r' && c == '\n')
{
break;
}
text = text + (char) c;
prev = (char) c;
}
Notice the order of execution. Check for \r\n and exit loop then append current character to text.
Do you see anything wrong with this logic?
Also you said
the cycle should stop when the stream ends. But it doesn't. The
program is stuck inside that cycle.
If the last two bytes are never \r\n or if the stream never closes it will never end and it will drop the last \n either way!
So which is it the loop never ends or the \n never gets appended?
If you want the loop to end at the end of the stream and at when a \r\n is detected you need to re-order your logic.
Garbage In Garbage Out:
Assuming that there are actually \r\n pairs in your InputStream. Are you sure they are there?, Step debugging would tell you for certain!
public static void main(final String[] args)
{
final InputStream is = System.in;
final StringBuilder sb = new StringBuilder(1024);
try
{
int i;
while ((i = is.read()) >= 0)
{
sb.append((char)i);
if (sb.substring(sb.length()-2).equals("\r\n"))
{
break;
}
}
}
catch (final IOException e)
{
throw new RuntimeException(e);
}
System.out.print(sb.toString());
}
You need to stop and learn how to use the step debugger that is in
your IDE. This would not be a question if you just stepped through
your code and put a few break points where things were not as you
wanted or expected them.

Is this a good way of parsing a string?

My program reads lines from a plain text file w/ lines formatted: <integer>;<integer>%n, where ; is the delimiter. It compares the two parsed integers against 2 other known values and increments tallyArray[i] if they match.
I currently use:
try {
scan = new Scanner(new BufferedReader(new FileReader("LogFileToBeRead.txt")));
for (int i = 0; i < tallyArraySize; i++) {
explodedLogLine = scan.nextLine().split(";");
if (IntReferenceVal1 == Integer.parseInt(explodedLogLine[0]) && IntReferenceVal2 == Integer.parseInt(explodedLogLine[1])) {
tallyArray[i]++;
}
}
} finally {
if (scan != null) { scan.close(); }
}
I was wondering if there were any serious faults with this method. It does not need to be production-quality.
Also, is there a standard way of parsing a string like this?
EDIT: We can assume the text file is perfectly formatted. But I see the importance for accounting for possible exceptions.
You are not handling NumberFormatExceptions thrown by the Integer.parseInt() method calls. If there's one bad line, execution exits your for loop.
You aren't vetting the integrity of the file you are reading from. If there isn't a ; character or if the Strings aren't actually numbers, execution simply exits the code block you posted.
If you can assume the file is perfectly formatted, and you're set on using a Scanner, you can add ; as a delimiter to the Scanner:
scan = new Scanner(new BufferedReader(new FileReader("LogFileToBeRead.txt")));
scan.useDelimiter(Pattern.compile("(;|\\s)"));
for (int i = 0; i < tallyArraySize; i++) {
int ref1 = scan.nextInt();
int ref2 = scan.nextInt();
if (IntReferenceVal1 == ref1 &&
IntReferenceVal2 == ref2) {
tallyArray[i]++;
}
}
And simply call Scanner.nextInt() twice for each line.
According to me There are three flaws in the program.
Delimiter ; what if there is delimiter is removed by accident or added by accident
There should be check on explodedLogLine that it is of length 2 and it is not null otherwise it will result in unexpected runtime error
You should catch NumberFormatException format exception since you can never be sure that Input is always a number
A simple illustration below gives you idea how things will go wrong.
String str = "3;;3";
System.out.println(Arrays.toString(str.split(";")));
This code will print [3, , 3] in such case your program will produce NumberFormatException as "" string can not be parsed to Integer.

Java Scanner strange behavior

I have a java scanner and two loops to handle user input, However it throws an NoSuchElement exception the second it hits the first loop with out asking for any input from the user.
Scanner Guess_input = new Scanner( System.in );
while (guess > 0){
failure = true;
while(failure)
{
System.out.println("Please input");
try
{
if (Guess_input.nextLine().length() == 1 && guesses.size() >= 1) {
guesses.add(Guess_input.nextLine());
System.out.println("You guessed" + guesses.get(guesses.size()) + "");
}
else if (Guess_input.nextLine().length() == 0) {
System.err.println("ERROR:");
Guess_input.nextLine(); //Clean Buffer
failure = true;
}
else
{
System.err.println("ERROR");
Guess_input.nextLine(); //Clean Buffer
failure = true;
}
}
catch(InputMismatchException ime)
{
System.err.println("error");
}
finally
{
Guess_input.close();
}
}
}
From the java documentation, when using the next() method of the Scanner class, you'll get
NoSuchElementException - if no such tokens are available
Whenever you call the nextLine() method, you are supposed to enter a String. You should first store the result of nextLine() in local variable unless that's what you want.
Another problem is that your try catch finally is done in your while loop. It means that for each iteration, your finally bloc will be executed everytime, so you'll think that there is an exception, while might be none. Apply these changes
try {
while (guess > 0) {
while (.....) {
.....
}
}
} catch (...){
....
}
finally{ .... }
The errant statement is guesses.get(guesses.size()). In Java lists use zero-based indexes, i.e. the index of the first element is always 0 and the last element is size - 1. By definition the size of a list is an invalid index.
You probably should just hold the next line in its own variable before adding it to the list so that your sysout statement can just reference the variable instead of pulling the value back out of the list. But the easy solution is to just change the code to guesses.get(guesses.size() - 1)
You're calling guesses.nextLine() way too many times. Every call to nextLine() will block the app and expect input. Furthermore, theres other issues to worry about there... like other people pointed out.
I'll stick to the scanner though.

Categories

Resources