Jak odczytać pojedynczy znak z konsoli w Javie (jak go wpisuje użytkownik)?


110

Czy istnieje łatwy sposób na odczytanie pojedynczego znaku z konsoli, gdy użytkownik wpisuje go w Javie? Czy to możliwe? Próbowałem z tymi metodami, ale wszystkie czekają, aż użytkownik naciśnie klawisz Enter :

char tmp = (char) System.in.read();
char tmp = (char) new InputStreamReader(System.in).read ();
char tmp = (char) System.console().reader().read();           // Java 6

Zaczynam myśleć, że System.in nie jest świadomy danych wejściowych użytkownika, dopóki nie zostanie naciśnięty klawisz Enter .

Odpowiedzi:


57

To, co chcesz zrobić, to przestawić konsolę w tryb „surowy” (pominięcie edycji linii i brak konieczności wprowadzania klawisza Enter) w przeciwieństwie do trybu „gotowanego” (wymagana edycja linii z klawiszem enter). W systemach UNIX polecenie „stty” może zmienić tryby.

Teraz, w odniesieniu do Javy ... zobacz Nieblokujące wejście konsoli w Pythonie i Javie . Fragment:

Jeśli twój program musi być oparty na konsoli, musisz przełączyć terminal z trybu liniowego na tryb znakowy i pamiętać o przywróceniu go przed zamknięciem programu. Nie ma przenośnego sposobu na zrobienie tego w różnych systemach operacyjnych.

Jedną z sugestii jest użycie JNI. Ponownie, to nie jest zbyt przenośne. Kolejną sugestią na końcu wątku i wspólną z powyższym postem jest przyjrzenie się użyciu jCurses .


4
JCurses też nie jest zbyt przenośny ... Z JCurses README: "JCurses składa się z dwóch części: części niezależnej od platformy i części zależnej od platformy, która składa się z natywnej biblioteki współdzielonej, udostępniającej prymitywne operacje wejścia i wyjścia dla pierwszej części . ”
— Ryan Fernandes

6
@RyanFernandes brzmi dla mnie całkiem przenośnie - pojedyncze narzędzie, które można uruchomić na wielu systemach (przy użyciu różnych zależności)
— Antoniossss

25

Musisz przełączyć konsolę do trybu surowego. Nie ma wbudowanego niezależnego od platformy sposobu, aby się tam dostać. jCurses może być jednak interesujące.

W systemie Unix może to zadziałać:

String[] cmd = {"/bin/sh", "-c", "stty raw </dev/tty"};
Runtime.getRuntime().exec(cmd).waitFor();

Na przykład, jeśli chcesz wziąć pod uwagę czas między naciśnięciami klawiszy, oto przykładowy kod, aby się tam dostać.


Działało dobrze dla mnie pod Linuksem
— MrSmith42

4
Pracował również na Macu. Prawdopodobnie chcesz wspomnieć, że stty cooked </dev/ttynależy to uruchomić, gdy program musi powrócić do trybu buforowanego, a na pewno przed zakończeniem programu.
— Kelvin

15

Nie ma przenośnego sposobu odczytywania surowych znaków z konsoli Java.

Niektóre obejścia zależne od platformy zostały przedstawione powyżej. Ale aby być naprawdę przenośnym, musiałbyś porzucić tryb konsoli i użyć trybu okienkowego, np. AWT lub Swing.


22
Nie bardzo rozumiem, dlaczego na przykład Mono (lub CLR) ma to, System.Console.ReadKeyco działa na wszystkich platformach. Java dystrybuuje również JVM i JRE dla każdej platformy z bibliotekami i implementacjami zależnymi od platformy, więc to nie jest wymówka.
— Martin Macak

13

Napisałem klasę RawConsoleInput w Javie, która używa JNA do wywoływania funkcji systemu operacyjnego Windows i Unix / Linux.

  • W systemie Windows używa _kbhit()i _getwch()z msvcrt.dll.
  • W tcsetattr()systemie Unix używa do przełączania konsoli w tryb niekanoniczny, System.in.available()do sprawdzania, czy dane są dostępne i System.in.read()do odczytu bajtów z konsoli. A CharsetDecodersłuży do konwersji bajtów na znaki.

Obsługuje nieblokujące wejście i miksowanie trybu surowego i normalnego wejścia w trybie liniowym.


Jak mocno zostało to przetestowane / przetestowane pod kątem stresu?
— Załóż pozew Moniki

2
@QPaysTaxes Testowanie warunków skrajnych jest trudne dla danych wejściowych konsoli. Myślę, że w tym przypadku ważniejsze byłoby przetestowanie go w różnych środowiskach (różne wersje Windows / Linux, 64/32 bit, Linux przez SSH, Telnet, port szeregowy lub konsolę desktopową itp.). Jak dotąd używam go tylko w moich prywatnych narzędziach testowych. Ale kod źródłowy jest stosunkowo mały w porównaniu do innych rozwiązań (takich jak JLine2, który wykorzystuje Jansi). Więc niewiele może się nie udać. Napisałem to, ponieważ JLine2 nie obsługuje wprowadzania pojedynczych znaków bez blokowania.
— Christian d'Heureuse

To właśnie miałem na myśli mówiąc o testach wytrzymałościowych - to prawdopodobnie niewłaściwe słowo; mój błąd. W każdym razie fajnie! Ukradłem ^ H ^ H ^ H ^ H ^ H ^ Użyłem go w moim projekcie szkolnym i pomogło to wielu.
— Załóż pozew Moniki

Hej - ta klasa wygląda świetnie. Jednak: nie mogę zmusić go do działania .. jak mam go używać? Zetknąłem się z blokowaniem System.in do momentu naciśnięcia CTRL + D (na Linuksie), a teraz czytam o trybach konsoli i polubieniach. Myślę, że szukam RawConsoleInput - ale jak go używać?
— Igor

@Igor Po prostu wywołaj RawConsoleInput.read (boolean), aby odczytać znak klawiatury. Jest to udokumentowane w kodzie źródłowym (RawConsoleInput.java).
— Christian d'Heureuse

8

Użyj jline3 :

Przykład:

Terminal terminal = TerminalBuilder.builder()
    .jna(true)
    .system(true)
    .build();

// raw mode means we get keypresses rather than line buffered input
terminal.enterRawMode();
reader = terminal .reader();
...
int read = reader.read();
....
reader.close();
terminal.close();

Odkryłem, że rozwiązania oparte na RawConsoleInput nie działają na MacOS High Sierra; jednak działa to doskonale.
— RawToast

jline ma praktycznie wszystko, czego potrzebujesz do stworzenia interaktywnego systemu konsoli / terminala. Działa świetnie w Linuksie. Bardziej kompletny przykład można znaleźć pod adresem: github.com/jline/jline3/blob/master/builtins/src/test/java/org/… . Ma autouzupełnianie, historię, maskę hasła itp.
— lepe
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.