Java Von System.in wirklich Zeichenweise einlesen

Kampfgnom

Lt. Commander
Registriert
Jan. 2005
Beiträge
1.075
Hi Leute,

ich bin gerade damit beschäftigt eine Kommandozeile in Java zu schreiben.

Diese soll Tab-Completion und auch eine Command-History enthalten. Nun stellt sich die Frage:
Wie lese ich von System.in zeichenweise ein?

Code:
public class Console implements Runnable {
		
	InputStream in;
	PrintStream out;
	
	public Console(InputStream in, PrintStream out) {
		this.in = in;
		this.out = out;
	}
	
	public void run() {
		while(true) {
			out.print("\nPrompt> ");
			
			char c = 0;
			String command = "";
			while(c != '\n') {
				try {
					c = (char) in.read();
				} catch (IOException e) {
					e.printStackTrace();
				}
				if(c == '\t') {
					String completer = ConsoleHelperFacility.wordcompleter(command);
					command += completer;
					out.print(completer);
				}
                                else {
				        out.write(c);
				        command += Character.toString(c);
                                }
			}
			
		}
	}
}
Nun stellt sich das Problem, dass der InputStream die Zeichen anscheinend erst enthält, sobald der User 'Enter' drückt. Sprich: Es wird eigentlich doch wieder nur Zeilenweise eingelesen.

Joa, danke schonmal ;-)

mfg Kampfgnom

EDIT: Ich habe noch ein wenig genauer gegooglet, und bin zu dem Ergebnis gekommen, dass es schlicht und ergreifend nicht ohne weiteres möglich ist.
Interessante Seiten zu dem Thema sind vor allem:

http://www.darkcoding.net/software/non-blocking-console-io-is-not-possible/
http://stackoverflow.com/questions/...from-the-console-in-java-as-the-user-types-it
http://jline.sourceforge.net/

Ich mag Java.... *kotz*
 
Zuletzt bearbeitet:
So isses Kampfgnome. Da hilft nur der Umweg über ein selbst geschriebenes Skript welches dir den String nach und nach auseinander baut und die Inhalte verarbeitet. Wie du es letztendlich gelöst hast, wenn du es denn gelöst hast, würde mich aber trotzdem interessieren. Vielleicht postest du 2 Wörter dazu wenn es soweit ist. Mir fällt nämlich momentan in Java nix sinnvolleres ein, d.h. nicht dass es nichts besseres gibt als ein String zu diskonkatenieren.
 
Firestorm schrieb:
Da hilft nur der Umweg über ein selbst geschriebenes Skript welches dir den String nach und nach auseinander baut und die Inhalte verarbeitet.
Das wird im Fall des TS nix bringen, denn er will ja ne Autocomplete-Funktion, die währen dem eintippen schon Vorschläge bringt. (z.B. so wie bei google).


@TS
wieso benutzt du denn nicht jLine wenn du schon den Link dazu postest? Das hört sich doch gut an.
Wenn du es selber umsetzen willst, und es in Java nicht sowas wie ReadKey() gibt, dann könntest du ja versuchen nen KeyListener zu schreiben: klick
Weiß nicht ob sowas in Java möglich ist, ich hab mich noch nie so richtig mit java beschäftigt.
 
Nicht umbedingt Grantig.

Es gibt eine Dirty-Lösung dazu, die zwar eine miese Komplexität hat aber sie funktioniert. Ich könnte mir vorstellen, dass wenn du irgendein Command eingibst, dann eine Taste betätigst, wie TAB, dass dann ein Key-Event ausgelöst wird, welcher nichts anderes tut als den bisher eingegeben String mit einer Arraylist aus anderen Strings matched und bei einem Treffer den eingegeben String replaced mit dem "gematchten". Problem ist wie gesagt nur die Komplexität. O(n) ist ziemlich mies wenn du jedes mal die Liste durchlaufen musst um zu schauen ob es einen String gibt der mit gewissen Charactern startet. Die Klasse String bietet meines Wissens eine solche Funktion an, also sowas wie String.startsWith("xyz"). Was du machen kannst, was aber sicherlich irgendwie ein bisschen abgefahrerener wäre aber deine Laufzeit reduzieren würde(sofern du rel. viele Einträge in der ArrayList hast, ich sag mal so arrList.size() > 20) wäre Folgendes:
Jeder Buchstabe, also Charakter hat einen IntWert in Java. Wenn du den ersten Buchstaben also von deinem String ausließt, dessen IntWert auf den Index setzt wo alle Commands die du implementiert hast mit dem selben Buchstaben beginnen, so sparst du dir das komplette durchlaufen der Liste und läufst nur von dort an weg wo du es wirklich brauchst, bzw. dort wo dann deine realistischen Matches auftreten können. Nachteil ist ganz klar dass deine Liste etwas länger ist als ursprünglich notwendig. Aber dabei könntest du dir einen schlauen Algorithmus ausdenken, der dir den Index berechnet, sodass deine Liste künstlich klein gehalten wird. Beispielsweise der IntWert/4. Wenn du eine Implementierung einer double linked List brauchst, die du selbst noch beliebig erweitern kannst, sag bescheid, ich hatte mal vor längerer Zeit eine geschrieben.

Das wären meine Gedanken soweit... Würde mich freuen wenn du uns aktuell hälst.
 
Zuletzt bearbeitet:
Ich glaub du verstehst sein Problem nicht.
Er hängt doch gerade fest, weil er keine Möglichkeit hat die einzelnen chars einzeln auszulesen.
Es wird nur immer zeilenweise ausgelesen, was eine Autovervollständigung sinnlos macht.

arrList.size() > 20)
Ich weiß ja nicht wie schnell Java ist, aber ich würd eher sagen bis arrList.size() < 200000 sollten keine Performanceprobleme auftreten.
Habs mal schnell ausprobiert. In C# ist ne Liste mit 200000 Einträgen in 44,9 ms durchlaufen.
 
Ne ich glaub du blickst es nicht. Hast du schonmal eine Unix Konsole in der Hand gehabt? - Scheinbar nicht, denn sonst wüsstest du dass "TAB" eine Art der Autovervollständigung ist. Ausserdem scheinst du nicht sonderlich Java bewandert zu sein, denn sonst wüsstest du dass jeder String als Array behandelt wird, bei dem ich problemlos auf jedes Zeichen des String zugreifen kann. Die Klasse String bietet sogar noch bei weitem mehr. Unter anderem lässt sich prüfen ob eine charSequence im String enthalten ist und diese auch gleich gematched werden kann. Vielleicht solltest du nochmals meinen letzten Beitrag lesen.

Hi Leute,

ich bin gerade damit beschäftigt eine Kommandozeile in Java zu schreiben.

Diese soll Tab-Completion und auch eine Command-History enthalten.

Ja und wie groß ist eine Liste mit 200000 Einträgen? Und wieviel Overflow hast du dabei?! Schonmal was von gutem Programmierstil gehört? Dann kommt noch das matchen dazu, die Verweise, etc... Effizienz ist was Anderes! Deshalb mein Vorschlag mit dem Algorithmus der die Indeces halbiert bzw. viertelt, eben eine Art von HashWert berechnet (Algos wie Chained Hashing könnten sich hier in einer abgewandelten Form auch eignen).

Achja und wenn du mit System.in nicht zurechtkommen solltest oder dir die Art und Weise nicht taugt, dann nimm nen BufferedInputReader oder steig auf eine externe lib um die sich IOTools schimpft um vom guten Herrn Prof. Ratz umgesetzt wurde.

Falls du noch Anregungen zu deiner Command History brauchst, kannst gerne Fragen...
 
Zuletzt bearbeitet:
Ja also erstmal danke für eure Antworten.

Ich habe ja schon in meinem ersten Beitrag geschrieben, dass es durchaus Lösungen gibt, die aber alle nicht trivial sind.
Ich bin nur Mitarbeiter des Projektes und habe keinerlei Entscheidungsgewalt, aber JLine werde ich bei meinen Leuten wohl durchsetzen können.


Abgesehen davon: Die Wordcompletion funktioniert schon. Das ganze ist etwas komplizierter als eine einfache Liste...
Das ganze ist ein komplettes Package, welches ungefähr wie Apache Commons CLI aufgebaut mit Sequenzen von möglichen Befehlen:

Code:
		seq = new SequenceElement();
		seq.add("project");
		cho = new ChoiceElement();
		cho.add("new");
		cho.add("open");
		cho.add("addfile");
		cho.add("removefile");
		seq.add(cho);
		seq.add(new FileElement());
		wordCompleter.add(seq);

Und: Wer hat denn bitte mehrere hunterttausend Befehle oder deren Parameter in seiner Command-Line? Das ist einfach niemals der Fall.
Und: Effiziente Datenstrukturen schreibt man doch wirklich nicht mehr selber. Eine Java ArrayList<String> ist intern doch wohl schon mit Hashmaps organisiert hoffe ich mal. Wenn es wirklich darauf ankommt verwende ich dann aber so oder so C++ :p
 
Zuletzt bearbeitet:
denn sonst wüsstest du dass "TAB" eine Art der Autovervollständigung ist.
Hab ich das jemals angezweifelt? Ich hab doch geschrieben, dass er ne Autocomplete funktion will :rolleyes:


denn sonst wüsstest du dass jeder String als Array behandelt
Natürlich weiß ich das, und dazu muss ich nichtmal in Java bewandert sein, denn das ist in vielen anderen Programmiersprachen nicht anders.



Achja und wenn du mit System.in nicht zurechtkommen solltest oder dir die Art und Weise nicht taugt
Und genau das ist der springede Punkt!
Mit System.in kann man anscheinend nicht ohne weiteres Zeichenweise einlesen.
--> Der InputStream ist erst gefüllt, wenn der User Enter drückt

Wenn der User den Befehl schon komplett eingegeben hat, dann brauchts doch eine Autocompletion mehr.
Dann wäre es extrem sinnlos den eh schon komplett vorhandenen string Zeichenweise einzulesen und zu matchen.


Mal ein Beispiel für dich:

Der User gibt ein "a" in die Konsole ein.
TAB-Completion sollte jetzt Vorschläge bringen, wie z.B. Auto, Affe, Amsel ...

Das Problem ist nur, im InputStream steht zu diesem Zeitpunkt garnichts, er ist leer.
Also fehlt die Grundlage um Vorschläge zu bringen.

Jetzt gibt der User das Wort komplett ein und drückt anschließend Enter:

affe <enter>

tja, jetzt steht im InputStream "affe".
Du schlägst vor, man könnte den String jetzt Zeichenweise durchlaufen (wie einen char Array) und dann matchen.
Das bringt aber nix, denn das Wort ist ja eh schon komplett, da brauchts keine Autovervollständigung mehr.


Deswegen hatte ich ja nen KeyListener vorgeschlagen, denn dann erhält man jeden eingegebenen char zum Zeitpunkt der Eingabe und nicht erst, wenn der User Enter drückt.
 
Das du die Befüllung im Sinne einer gültigen Tasteneingabe umleiten musst ist klar. Das hab ich auch nie angezweifelt. Trotzdem lässt sich eine solche Variante sehr einfach realisieren.

Mal ein Beispiel für dich:

Der User gibt ein "a" in die Konsole ein.
TAB-Completion sollte jetzt Vorschläge bringen, wie z.B. Auto, Affe, Amsel ...

Das Problem ist nur, im InputStream steht zu diesem Zeitpunkt garnichts, er ist leer.
Also fehlt die Grundlage um Vorschläge zu bringen.

Jetzt drückt der User "Tab", die Eingabe wird umgeleitet und als notwendiges Command zum Befüllen des Inputstreams interpretiert. Der Inputstream ist voll mit einem Fragment aus dem eingegeben Teil des Satzes. Restliches Prozedere siehe oben. Wenn ich Zeit hab, sprich: Semesterferien implementier ich die Geschichte mal und beweis dir dass es ohne weiteres geht.


@Kampfgnome: Schön dass du eine geeignete Variante im Einsatz hast.
 
Zurück
Oben