В каких условиях блокирует Java Scanner.hasNextLine()?
В javadoc для Scanner.hasNextLine()
указано:
Возвращает true, если на входе этого сканера есть еще одна строка. Этот метод может блокироваться во время ожидания ввода. Сканер не продвигайтесь мимо каких-либо входных данных.
В каких условиях метод блокирует?
Ответы
Ответ 1
Это зависит от источника, из которого сканер получает вход.
Например, если это файл, доступен весь вход, поэтому hasNextLine()
не будет блокироваться (поскольку он может знать с уверенностью, когда достигнут конец файла, и больше нет ввода.
С другой стороны, если источником является стандартный ввод, всегда может быть больше ввода - пользователь может всегда вводить больше ввода - поэтому hasNextLine()
будет блокироваться до тех пор, пока пользователь не будет вводить новую строку ввода.
Ответ 2
Как решить, будет ли он блокироваться?
Чтобы решить, будет ли hasNextLine
заблокирован или нет, к сожалению, это не поддерживается.
Это связано с тем, что базовые источники не всегда предоставляют API для поиска в потоке. Иными словами, реализация hasNextLine
вызывает методы, которые сами могут блокировать, поэтому проблема является своего рода неотъемлемой.
Итак, что делать?
Если это действительно необходимый прецедент, я бы рекомендовал один из следующих способов:
-
Убедитесь, что условия соответствуют значению hasNextLine
. Предоставляйте сканеру только те источники, которые имеют определенный конец (например, файл или строку) и никогда не имеют вход с открытым концом, например System.in
.
Если это часть API, вы можете обернуть сканер в свой класс, который предоставляет только "безопасные" конструкторы.
-
Сбросьте свой собственный класс с нуля, который имеет тип метода willHasNextLineBlock
. Вероятно, это может быть реализовано несколько с помощью InputStream.available.
В категории супер уродливых обходных решений мы находим:
-
Попытка вызвать hasNextLine
в отдельном потоке и посмотреть, возвращается ли он в разумные сроки, следующим образом:
boolean wouldBlock = false;
Thread t = new Thread(() -> s.hasNextLine());
t.start();
try {
t.join(100);
} catch (InterruptedException e) {
wouldBlock = true;
}
-
Используйте настраиваемый поток ввода (что-то вроде peekable stream, к которому можно было бы войти, прежде чем вызывать hasNextLine
. это
CustomStream wrapped = new CustomStream(originalSource)
Scanner s = new Scanner(wrapped);
...
if (wrapped.hasNextLine())
// s.hasNextLine would not block
else
// s.hasNextLine would block
(Обратите внимание, однако, что это несколько небезопасно, поскольку сканер может буферизовать некоторые данные из CustomStream
.)
Ответ 3
Предполагая, что "решите, будет ли он блокироваться", вы имеете в виду, что хотите знать, когда он будет работать.
Посмотрите, где вход назначается в методе hasNextLine
String result = findWithinHorizon(linePattern(), 0);
Теперь рассмотрим метод findWithinHorizon
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;
}
Как вы можете видеть, он будет бесконечно вращаться до тех пор, пока не будет достигнут конец, или пока не удастся его прочитать.
findPatternInBuffer
- частный метод класса Scanner
, который пытается прочитать ввод.
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;
}
Я опубликовал весь метод, чтобы дать вам лучшее представление о том, что я имел в виду под "преуспеть для чтения".