Hash Map für Keys?

Hallo.
Ich habe eine alternative gesucht für das ständige

...
...

und dachte mir man könnte doch einfach eine HashMap anlegen:

damit hätte man dann jeweils einen einzeiler:

keys.put(e.getKeyCode(), true);
//und in keyReleased:
keys.put(e.getKeyCode(), false);

Dort wo man es braucht holt man sich den key eben ganz einfach über keys.get(KeyEvent.X);
Hab das ganze schon ausprobiert, funktioniert auch wie es soll…
aber meine typische frage: Ist das so ok? Vor allem in Grafikanwendungen, wo performance einer
der wichtisgten punkte ist? Oder dauert es einfach zu lange so eine HashMap referenz zu übergeben bzw damit umzugehen?

Vielen Dank!

Du musst nicht davon ausgehen, dass DAS dein Performance-Bottleneck wird :wink:

Oder, anders ausgedrückt: Machs erstmal schön und schaue später, obs zu langsam wird. Gerade Key Events wird es ja nicht hunderte pro Sekunde geben; da würde ich mir um einen simplen Lookup keine Gedanken machen. In einem MouseMotionListener oder der paintComponent sieht das schon anders aus, aber selbst da würde ich zuerst den schönen Weg wählen. Meistens sind Bottlenecks wo ganz anders, als man vermutet…

KeyEvents gibt es schon viele (wenn man eine Taste gedrüüüüüüüüüüüüüüückt hält), und vermutlich ging es auch um die Frage, ob die Abfrage performancekritisch sein könnte, beim Vergleich von

boolean xPressed = false

void keyPressed() {
    if(e.getKeyCode() == KeyEvent.X) xPressed = true;
}

void keyReleased() {
    if(e.getKeyCode() == KeyEvent.X) xPressed = false;
}

void gameLoop() {
    while (true) {
       if (xPressed) ...
    }
}

vs. der Set mit

void gameLoop() {
    while (true) {
       if (pressedKeys.contains(KeyEvent.X) ...
    }
}

Aber der Rest stimmt schon. Zum “schöner” auch noch der Punkt: Meistens kann man statt einer Map<X, Boolean> einfach einen Set nehmen, und statt einer Abfrage wie

Boolean b = map.get(x);
if (b != null && b) doSomething();

dann eben einfach

if (set.contains(x)) doSomething();

schreiben. (Intern sind die meisten Sets zwar doch wieder Maps, aber … darum geht es ja nicht…).

Set?.. schande über mich, noch nie gehört :o
muss ich gleich mal lesen.

Und/Oder du versuchst es zu kapseln, indem du gegen ein Interface programmierst:

     boolean isKeyPressed(int keycode);
}```

Deine erste implementierung könnte dann so aussehen, dass du eben die Map nimmst. Wird das dann doch zum Bottleneck, dann kannst du einfach die Implementierung austauschen, ohne x Codestellen anpassen zu müssen.

Tatsächlich hatte ich … “vorhin” (beim Versuch, einzuschlafen) auch darüber nachgedacht. In diesem Fall könnte man auch eine eigene Set-Implementierung in Betracht ziehen, die im Hintergrund einen Array aus booleans hat (sooo viele KeyCodes gibt’s nicht :D), aber das mit dem KeyHandler ist natürlich nochmal einen Schritt klarer.

Naja, leider sind manche Keycodes im 65000 Bereich und da find ich das anlegen eines 66000 int arrays schon hässlich: http://docs.oracle.com/javase/6/docs/api/constant-values.html#java.awt.event.KeyEvent.KEY_FIRST

Achja, übrigens mach Swing das auch mit HashMaps, da heissen sie halt InputMaps oder ActionMaps.

Ja, war nur ein hypothetisches Gedankenspiel. Vermutlich stellt sich die Frage nach der Performance hier gar nicht.

Was muss ich denn hier lesen? Boolean Wrapper? Boolean Arrays? HashMaps/-Sets? Please help! MAW.: Gott bewahre! Set ist allerdings gar nicht mal so verkehrt, aber es sollte ein doch bitte ein BitSet sein. :wink:

import java.awt.event.KeyListener;
import java.util.BitSet;

public final class KeyAdapter implements KeyListener {
	private static final int NUM_CODES = 255; // | 65535 | Integer.MAX_VALUE

	private final BitSet downKeys = new BitSet(NUM_CODES + 1);

	@Override
	public void keyTyped(KeyEvent e) {
		// nothing
	}

	@Override
	public void keyReleased(KeyEvent e) {
		int key = e.getKeyCode();
		if(key >= 0 && key <= NUM_CODES) {
			downKeys.clear(key);
		}
	}

	@Override
	public void keyPressed(KeyEvent e) {
		int key = e.getKeyCode();
		if(key > 0 && key <= NUM_CODES) {
			downKeys.set(key);
		}
	}

	public boolean isKeyDown(int keyCode) {
		if(keyCode < 0 || keyCode > NUM_CODES) {
			throw new IllegalArgumentException("key out of range " + keyCode);
		}
		return downKeys.get(keyCode);
	}
}```Mit NUM_CODES kann man die Range der vervendeten Codes bestimmen. 256 sind in vielen Fällen ausreichend, Java Codes sind bisher immer < 65536, und Integer - Die Zeiten, in denen so viele Key-Definitionen erreicht werden können, dürften längst vorbei sein - wäre abenteuerlich übertrieben. An Speicher benötigt Bitset nur die (Anzahl der Bits + 7) / 8 Bytes plus geringfügig Overhead, ein Boolean-Array dagegen für Anzahl der Bits * 1 (werden als bytes abgelegt)!

BTW.: Zu Performance gehört afaik auch der Speicherverbrauch und nicht nur die Geschwindigkeit. Wie dem auch sei. Ich hatte in dieser Klasse anfangs auch ein Boolean-Array definiert, ein Set oder eine Map brauchts da ganz gewiss nicht (zu langsam und zu hoher Speicherverbrauch). Bitset ist zwar geringfügig langsamer (Nanobereich) verbraucht aber definitiv nur ein Achtel des Speichers eines Boolean-Arrays.

@Spacerat
Koenntest du vielleicht ganz ganz kurz erklaeren was in dem Code genau passiert. Habe davon noch nie gehoert…
Nimmst du das nur weil es weniger Speicher braucht? Mehr Speicher heisst doch afaik nicht laengere Abfragezeit oder sowas oder?

Evtl. wirds klarer, wenn ich mal die Version mit dem Boolean-Array poste, welche sich kaum von dem oberen unterscheidet.

import java.awt.event.KeyListener;

public final class KeyAdapter implements KeyListener {
	private static final int NUM_CODES = 255; // | 65535 | Integer.MAX_VALUE

	private final boolean[] downKeys = new boolean[NUM_CODES + 1];

	@Override
	public void keyTyped(KeyEvent e) {
		// nothing
	}

	@Override
	public void keyReleased(KeyEvent e) {
		int key = e.getKeyCode();
		if(key > 0 && key < NUM_CODES) {
			downKeys[key] = false;
		}
	}

	@Override
	public void keyPressed(KeyEvent e) {
		int key = e.getKeyCode();
		if(key > 0 && key < NUM_CODES) {
			downKeys[key] = true;
		}
	}

	public boolean isKeyDown(int keyCode) {
		if(keyCode < 0 || keyCode > NUM_CODES) {
			throw new IllegalArgumentException("key out of range " + keyCode);
		}
		return downKeys[keyCode];
	}
}```
Das Boolean-Array dürfte von allen Versionen ((Hash)Set/-Map, BitSet, Array) das schnellste sein. BitSet ist nur geringfügig langsamer aber verbraucht am wenigsten Speicher. Sets/Maps aber sind nicht nur langsamer als BitSet, sie verbrauchen auch noch mindestens 8 mal soviel Speicher, wie ein Array.
Die Geschwindigkeit hängt von den Zugriffsalgorithmen in der Klasse ab. Bei Arrays wäre das eine simple Indizierung, bei Sets/Maps kommt evtl. noch die Bildung eines Hashcodes dazu und bei BitSet statt der Bildung des HC eine Indizierung auf das entsprechende long (KeyCode / 64) und eine boolsche Verknüpfung (BitNr. KeyCode % 64).

Hier mal ein interessanter Beitrag zu Bytearrays:
http://chrononsystems.com/blog/hidden-evils-of-javas-byte-array-byte

Mehr Speicher heisst nicht zwangsläufig mehr Zugriffszeit. Mehr Speicher könnte aber irgendwann höhere VirtualMem-Auslastung und dadurch höhere Auslagerungszeiten bedeuten. Wenn dann die Reduzierung des Speicherverbrauchs die Anwendung im Nanosekundenbereich verlangsamt, sollte man diese Reduzierung auch durchführen (optimieren).

Höchstwahrscheinlich nur unter der Annahme das alles in den Cache passt, ansonsten sind simple logische Operationen um einen großen Faktor schneller als Zugriffe in den Hauptspeicher. Bei den wenigen wirklich verwendeten ASCII-Werten stellt sich die Frage jetzt nicht wirklich.

Und noch ein allgemeiner Tipp, wenn jmd mit wirklich großen Bitmaps arbeiten muss (Indexe usw.), meist sind große Bitmaps eher dünn besetzt und können komprimiert werden. Das Java BitSet tut dies nicht, eine gute Alternative zur Implementierung der Standardbibliothek ist GitHub - lemire/javaewah: A compressed alternative to the Java BitSet class.

[QUOTE=ThreadPool]Höchstwahrscheinlich nur unter der Annahme das alles in den Cache passt[/QUOTE]Eigentlich nur unter der Annahme, dass alles in den Hauptspeicher passt, also so, das möglichst wenig ausgelagert werden muss (Mein letzter Post, letzter Absatz). Ein Cache (Prozessorcache) ist in Java relativ uninteressant, weil da ausschliesslich der JIT optimiert bzw optimieren sollte.
Für Tastaturabfragen wird man wohl kaum mit größeren Bitmaps als 8192 Bytes großen arbeiten, also reicht da BitSet völlig aus. Das mit der gepackten BM ist aber dennoch ein netter Tipp, die dürfte ebenfalls schneller und platzsparender als jedwede Collection sein.