2009-07-05 5 views
14

Ich bin neu in Java Programmierung, aber ein erfahrener C++ Programmierer. Ich lernte, wie man GUIs mit Swing programmiert. Ich habe mich gefragt, wie ressourcenintensiv (Laufzeit und Arbeitsspeicher) ActionListener sind? Gibt es eine allgemeine Richtlinie für die Gesamtzahl der Listener, die in einem bestimmten Programm erstellt werden sollten? Wie viele, bis die Leistung beeinträchtigt wird?Wie ressourcenintensiv sind Listener in Java?

Ich lerne gerade Java durch die Deitel Developer Series Java für Programmierer Buch. In einem bestimmten Beispiel haben sie ein Array von JRadioButtonItems als private Variable für die Klasse. Sie hatten auch eine ItemHandler-Klasse erstellt, die von der ActionListener-Klasse, die eine lineare Suche auf dem gesamten Array von Optionsfeldern durchführte, erweitert wurde, um die ausgewählte auszuwählen und den Status des Programms entsprechend zu ändern. Alle Optionsfelder im Array haben denselben Aktion Listener. Dies schien ziemlich ineffizient zu sein, um eine lineare Suche nach Informationen durchzuführen, so dass ich die ActionListener-Klasse neu geschrieben hatte, um den vorgeschlagenen Wert im Konstruktor zu ändern, und jedem Optionsfeld einen eigenen ActionListener mit dem vorgeschlagenen Wert zu geben, der vom Konstruktor übergeben wurde eine lineare Suche durchführen. Welche Methode wäre leistungsfähiger? Ich entschuldige mich dafür, dass ich wie ein Noob aussah, ich versuche nur, eine gute Gewohnheit für die Programmierung in Java zu entwickeln. Beigefügt ist ein kleines Beispiel für den Code. Vielen Dank.

/************************************************************************ 
    Original code in Deitel book with linear search of selected Radio button in Actionlistener 
    ****************************************************************************/ 
    import java.awt.Color; 
import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener; 

import javax.swing.ButtonGroup; 
import javax.swing.JFrame; 
import javax.swing.JLabel; 
import javax.swing.JMenu; 
import javax.swing.JMenuBar; 
import javax.swing.JMenuItem; 
import javax.swing.JRadioButtonMenuItem; 


public class MenuTest extends JFrame{ 
    private final Color colorValues[] = {Color.BLACK, Color.WHITE, Color.GREEN}; 
    private JRadioButtonMenuItem colorItems[];  
    private ButtonGroup colorButtonGroup; 


    public MenuTest(){ 
     super("Menu Test"); 
     JMenu fileMenu = new JMenu("File"); 

     JMenuBar bar = new JMenuBar(); 
     setJMenuBar(bar); 
     bar.add(fileMenu); 

     String colors[] = {"Black", "White", "Green"}; 
     JMenu colorMenu = new JMenu("Color"); 
     colorItems = new JRadioButtonMenuItem[colors.length]; 
     colorButtonGroup = new ButtonGroup(); 

     ItemHandler itemHandler = new ItemHandler(); 

     for(int count = 0; count < colors.length; count++){ 
      colorItems[count] = new JRadioButtonMenuItem(colors[count]); 
      colorMenu.add(colorItems[count]); 
      colorButtonGroup.add(colorItems[count]); 
      colorItems[count].addActionListener(itemHandler); 
     } 

     colorItems[0].setSelected(true); 
     fileMenu.add(colorMenu); 
     fileMenu.addSeparator(); 

    } 

    private class ItemHandler implements ActionListener{ 
     public void actionPerformed(ActionEvent event){ 
      for(int count = 0; count < colorItems.length; count++){ 
       if(colorItems[count].isSelected()){ 
        getContentPane().setBackground(colorValues[count]); 
       } 
      } 
     } 
    } 


    public static void main(String args[]){ 
     MenuTest menuFrame = new MenuTest(); 
     menuFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     menuFrame.setSize(600,400); 
     menuFrame.setVisible(true); 
     menuFrame.getContentPane().setBackground(menuFrame.colorValues[0]); 
    } 
} 
    /************************************************************************ 
    My Code redefined version of Deitel's w/o linear search in ActionListener 
    ************************************************************************/ 

     import java.awt.Color; 
     import java.awt.event.ActionEvent; 
     import java.awt.event.ActionListener; 

     import javax.swing.ButtonGroup; 
     import javax.swing.JFrame; 
     import javax.swing.JLabel; 
     import javax.swing.JMenu; 
     import javax.swing.JMenuBar; 
     import javax.swing.JMenuItem; 
     import javax.swing.JRadioButtonMenuItem; 

     public class MenuTest extends JFrame{ 
     private final Color colorValues[] = {Color.BLACK, Color.WHITE, Color.GREEN}; 
     private JRadioButtonMenuItem colorItems[];  
     private ButtonGroup colorButtonGroup; 


     public MenuTest(){ 
      super("Menu Test"); 
      JMenu fileMenu = new JMenu("File"); 

      JMenuBar bar = new JMenuBar(); 
      setJMenuBar(bar); 
      bar.add(fileMenu); 

      String colors[] = {"Black", "White", "Green"}; 
      JMenu colorMenu = new JMenu("Color"); 
      colorItems = new JRadioButtonMenuItem[colors.length]; 
      colorButtonGroup = new ButtonGroup(); 

      ItemHandler itemHandler = new ItemHandler(); 

      for(int count = 0; count < colors.length; count++){ 
       colorItems[count] = new JRadioButtonMenuItem(colors[count]); 
       colorMenu.add(colorItems[count]); 
       colorButtonGroup.add(colorItems[count]); 
       colorItems[count].addActionListener(new ItemHandler(colorValues[count])); 
      } 

      colorItems[0].setSelected(true); 
      fileMenu.add(colorMenu); 
      fileMenu.addSeparator(); 

     } 

     private class ItemHandler implements ActionListener{ 
      private Color setColor; 
      public ItemHandler(Color inColor){ 
       super(); 
       setColor = inColor; 
      } 
      public void actionPerformed(ActionEvent event){ 
       getContentPane().setBackground(setColor); 
       repaint(); 
      } 
     } 
     public static void main(String args[]){ 
      MenuTest menuFrame = new MenuTest(); 
      menuFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
      menuFrame.setSize(600,400); 
      menuFrame.setVisible(true); 
      menuFrame.getContentPane().setBackground(menuFrame.colorValues[0]); 
     } 
    } 

Antwort

17

CPU-Auslastung: nahe bei keiner. Listener werden nur aufgerufen, wenn der Status des Objekts, auf das sie gehört werden, geändert wird. Sie "hören" nicht wirklich. Das Objekt, auf das sie hören, ruft sie bei Bedarf an. (Hinweis: Dies ist eine Vereinfachung)

Speicherauslastung: Meistens hat ein ActionListener keinen Status. Daher ist die gesamte Speicherauslastung für jedes Objekt minimal. In Ihrem Beispiel ist State, da Sie ein SetColor-Feld haben. Die Speichernutzung ist jedoch gering.

IMO, Hörer sind effizient und Sie können so viele verwenden, wie Sie möchten.

4

Zuhörer sind sehr kostengünstig im Vergleich zu den Komponenten, an die sie angeschlossen sind. Es ist unwahrscheinlich, dass nur Hunderte von Zuhörern einen großen Unterschied zu CPU oder Speicher machen. Die Gesamtzahl der Arbeitshörer ist jedoch wichtiger. Wenn Sie ein Leistungsproblem feststellen, können Sie einen CPU/Speicherprofiler verwenden, um die Ursache zu ermitteln und zu optimieren. Es ist nicht etwas, über das Sie sich im Vorfeld Sorgen machen sollten.

1

Denken Sie daran, dass das Abhören nicht dasselbe ist wie ein hektisches Warten. Der Zuhörer wird in einer Liste von interessierten Parteien über Proxy-Methoden irgendeiner Art registriert. Es gibt sehr wenig verschwendete CPU-Zeit in den meisten Listener-Mustern.

1

Es gibt eine Tradition in Java ME, weniger Klassen zu verwenden, da der Aufwand beim Herunterladen einer anderen Klassendatei erheblich ist. Daher erhalten Sie manchmal Java-Code, der die Anzahl der Listenertypen versucht und reduziert und bedingte Logik im Listener verwendet. Für die Desktop-Java-Anwendung ist die Größe nicht wirklich ein Problem, aber manchmal sieht man, dass sie in diesen Bereich eindringt.

Nicht wirklich in der Antwort auf die Frage, aber sobald Sie soweit gegangen sind, werden Sie feststellen, dass Sie den Umfang der meisten Felder von MenuTest als Methodenvariablen im Konstruktor reduzieren können.

Ich neige dazu, anonyme Listener zu verwenden, da sie meist zu einfach zu benennen sind.

Der angezeigte Code verwendet Aktionslistener, um zu erkennen, ob die Menüeinträge aktiviert sind.

Im Konstruktor wird der ausgewählte Menüeintrag festgelegt, dann greift er in der Hauptmethode auf das Feld colorValues des Objekts zu und legt den Wert des Hintergrunds fest. Offensichtlich versagt dies sowohl beim Verbergen von Informationen als auch beim Verkapseln von Verhalten.

Wenn Sie stattdessen ChangeListener s verwenden, um zu prüfen, ob ein Menüelement als ausgewählt festgelegt ist, haben Sie nicht zwei separate Logikelemente, um den gleichen Status beizubehalten wie die Farbe als Reaktion auf den Aufruf von setSelected, und Sie müssen das Feld colorValues nicht an den Anrufer weitergeben.

for (int count = 0; count < colors.length; count++){ 
    colorItems[count] = new JRadioButtonMenuItem(colors[count]); 
    colorMenu.add(colorItems[count]); 
    colorButtonGroup.add(colorItems[count]); 

    final Color color = colorValues[count]; 

    colorItems[count].addChangeListener (new ChangeListener() { 
     public void stateChanged (ChangeEvent event){ 
      if (((AbstractButton) event.getSource()).isSelected()) 
       getContentPane().setBackground (color); 
     } 
    }); 
} 

colorItems[0].setSelected(true); 

// no call to setBackgroundColour() in main() 

Sie können auch eine zweite letzte Variable haben die colorItem und zu vermeiden, dass die Besetzung auf event.getSource() zu halten.

Es wäre auch möglich, den Änderungslistener zu implementieren, um auch auf Änderungen zu achten, die die Hintergrundfarbe für den Rahmen festlegen, und den entsprechenden Menüeintrag auszuwählen, wenn die Farbe mit setBackground() geändert wird.

Durch den Aufruf von setBackground() wird das Panel automatisch als beschädigt markiert, so dass Sie das Repaint danach nicht mehr aufrufen müssen.

0

Nun, betrachten Sie die grundlegendste Listener-Implementierung: alles, was Sie tun, ist im Grunde ein Objekt zu einer Liste von Listenern hinzufügen, nichts mehr. Eine Callback-Funktion registrieren, wenn Sie möchten. So ist die Umsetzung dieser Linie:

someObject.addListener(new someAnonymousListener{ ... }); 

im Wesentlichen wäre:

someListenerList.add(new someAnonymousListener{ ... }); 

Bisher kein Schaden kein Foul. Das Objekt sitzt nur in einer Liste mit allen anderen registrierten Zuhörern.

jedoch Hinweis, was passiert, wenn ein Ereignis auf dem Objekt ausgelöst wird:

for (SomeListener l : someListenerList) { 
    l.doSomeAction(); 
} 

Huch. Hier sollten Sie vorsichtig sein. Stellen Sie sicher, dass Ihr Listener nichts zu komplex macht oder Sie einen Engpass sehen, der beim Profiling auftaucht.

Untere Zeile: einfach halten einen Listener auf ein Objekt ist fast frei. Stellen Sie nur sicher, dass Sie nicht zu viele Listener registrieren, und dass jeder von ihnen seine Aktionen einfach und auf den Punkt hält.

+0

Sie heben eine gute Frage: Sie können in meinen Artikeln interessiert sein Zuhörer im Allgemeinen sehr wenig tun sollten. Wenn sie etwas Komplexes tun müssen, sollten sie einen neuen Thread erzeugen, idealerweise mit der SwingWorker-Klasse, und dann sofort zurückkehren. –

4

Das viel größere Problem hier ist die Zeit für die Einrichtung und in geringerem Maße für die permanente Speichergeneration.

Für die tatsächlichen Objektgrößen können Sie eine Hüllkurvenauswirkungsanalyse durchführen. Etwa 20 Bytes ein Objekt. Angenommen, Sie haben 10.000 davon, das sind 200.000. Auf einer 1GB-Maschine, die 0,02% der Speicherkapazität Ihrer Maschine ausmacht, würde ich mir in einem Handy keine Sorgen machen.

Das größere Bild ist, wie viele Klassen Sie laden. Mein Experiment (irgendwo im Usenet) ergab ungefähr 2K Overhead für jede relativ kleine anonyme innere geladene Klasse. 10.000 davon sind 20 MB. 2% unserer 1GB-Maschine. Ich würde mir darüber Sorgen machen, wenn es um weit verbreitete Software geht, aber nicht um ein paar Dutzend Maschinen in einer mittelgroßen Firma oder Abteilung. Nichtsdestoweniger ist es wichtiger, eine Software zu bekommen, die tatsächlich funktioniert und wartbar ist (85% der Softwareentwicklungskosten sind anscheinend in Wartung (für eine Definition) und die meisten Projekte kommen nie so weit.

Das Laden des Codes ist eine relativ teure Operation. Glücklicherweise haben wir jetzt jedoch Gläser, anstatt einen neuen Socket zu öffnen, um eine HTTP 1.0-Anfrage für jede Klassendatei auszuführen (Applets waren langsam zu starten - wie ist das passiert?). Dennoch müssen Strings gesucht werden, Methoden aufgelöst werden, Bytecode verifiziert werden usw. Und es wird immer noch für 1500 Iterationen interpretiert, bevor wir die Kosten des Compilers tragen.

Sobald Sie arbeiten (es funktioniert nicht, bis es in der Produktion ist), wartbare Software, dann möchten Sie möglicherweise die Feinabstimmung der Leistung in Betracht ziehen (obwohl, um widersprüchlich zu sein, ist es gut, im Kopf zu haben). Stimmen Sie den langsamsten Teil ab. Bei Prozessoren in GHz tendiert die Reaktion auf jeden kleinen Mausklick dazu, mehr als schnell genug zu gehen. Langsam sind Vorgänge wie das Öffnen eines neuen Dialogfelds.

Also, für uns wollen wir die Rüstzeit reduzieren, aber wir können bei Ereignis Feuerzeit ziemlich ineffizient sein. 50ms ist die Reaktionszeit, die wir anstreben sollten, und das bei einer 1GHz-Maschine 20.000.000 Zyklen (Lose!). Eine angemessen performante Strategie besteht daher darin, wenige Arten von Listenern zu verwenden. Verwenden Sie einen einzelnen Listenertyp (möglicherweise eine Instanz), um eine angemessene Menge an Modellen zu hören. Ignoriere die Event-Argumente (normalerweise eine gute Idee für einen State-Change-Listener) und führe die Überprüfung durch, was zu tun ist (vergiss nicht, dass wir genügend Zeit haben).

Ich fühle mich das Bedürfnis, mich hier zu wiederholen. Machen Sie Ihren Code schön. Gehen Sie für viele Eingabe-Listener (und Befehle). Statusänderungs-Listener sollten ihre Argumente ignorieren.

+0

10.000 Objekte @ 20 Bytes! = 20K (sollte 200K sein). –

+0

@ Kevin Brock Erm, ja. D'oh. 0,02%. –