【问题标题】:How resource intensive are Listeners in java?Java 中的侦听器占用多少资源?
【发布时间】:2010-11-08 04:31:06
【问题描述】:

我是 Java 编程新手,但我是一位经验丰富的 C++ 程序员。我正在学习如何使用 swing 编程 GUI。我想知道 ActionListener 的资源密集型(运行时和内存)如何?是否有关于在特定节目中应该创建的听众总数的一般指导方针?多少直到性能受到影响?

我目前正在通过 Deitel Developer Series Java for Programmers 一书学习 Java。在一个特定的例子中,他们有一个 JRadioButtonItems 数组作为类的私有变量。他们还创建了一个从 ActionListener 类扩展而来的 ItemHandler 类,该类对整个单选按钮数组进行线性搜索,以确定被选中的单选按钮并相应地更改程序的状态。数组中的所有单选按钮共享同一个动作监听器。这对于进行信息的线性搜索似乎相当低效,因此我重写了 ActionListener 类以在构造函数中接受要修改的建议值,并为每个单选按钮提供其自己的 ActionListener 以及构造函数传入的建议值以避免进行线性搜索。哪种方法在性能方面会更好?我很抱歉听起来像个菜鸟,我只是想养成一套好的 Java 编程习惯。附件是代码的一个小例子。谢谢。

    /************************************************************************
    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]);
        }
    }

【问题讨论】:

    标签: java user-interface swing


    【解决方案1】:

    CPU 使用率:几乎没有。只有当他们监听的对象的状态发生变化时才会调用监听器。他们并没有真正“听”。他们正在监听的对象在需要时调用它们。 (注意:这是一个简化)

    内存使用:大多数情况下,ActionListener 没有状态。因此,总持久内存使用量是任何对象所需的最小值。在您的示例中,有 is 状态,因为您有一个 setColor 字段。但是内存使用率很低。

    IMO,侦听器效率很高,您可以使用任意数量的。

    【讨论】:

      【解决方案2】:

      与它们所附加的组件相比,侦听器的成本非常低。仅仅拥有数百个侦听器不太可能对 CPU 或内存产生太大影响。然而,听众所做的总工作更为重要。如果您发现性能问题,您可以使用 CPU/内存分析器来识别和优化原因。这不是您需要预先担心太多的事情。

      【讨论】:

        【解决方案3】:

        这里更大的问题是设置时间,以及较小程度的永久代内存。

        对于实际的对象大小,您可以进行粗略的影响分析。一个对象大约 20 个字节。假设你有 10,000 个,那就是 200K。在一台 1GB 的机器上,你的机器内存的 0.02% 都在监听器上运行——我不会担心在手机上。

        更大的图景是您加载了多少类。我的实验(在某处的 Usenet 上)为每个加载的相对较小的匿名内部类提供了大约 2K 的开销。其中 10,000 个是 20MB。我们 1GB 机器的 2%。对于广泛分布的软件,我会担心这一点,但对于中型公司或部门中的几十台机器,我会担心。即便如此,获得真正有效且可维护的软件显然更为重要(显然,85% 的软件开发成本用于维护(根据某种定义),而大多数项目从未达到这个程度)。

        加载代码是一项相对昂贵的操作。虽然幸运的是我们现在有了 jars 而不是打开一个新的套接字来为每个类文件执行 HTTP 1.0 请求(小程序启动很慢 - 这是怎么发生的?)。尽管如此,必须查找字符串、解析方法、验证字节码等。在我们产生编译器费用之前,它仍然被解释了 1,500 次迭代。

        一旦你有了工作(直到它在生产中才工作)、可维护的软件,那么你可能想要考虑微调性能(尽管自相矛盾的是,最好有提示)。调整最慢的部分。使用 GHz 的处理器,对每一次鼠标点击的响应往往足够快。慢的往往是打开新对话框等操作。

        因此,对于我们来说,我们希望减少设置时间,但在事件触发时间我们可能效率很低。 50ms 是我们应该瞄准的响应时间,在 1GHz 机器上是 20,000,000 个周期(很多!)。因此,一个适当的性能策略是使用几种类型的侦听器。使用单个侦听器类型(可能是实例)来侦听大量模型。忽略事件参数(对于状态更改侦听器来说通常是个好主意)并检查需要做什么(记住我们有足够的时间)。

        我觉得有必要在这里重复一遍。让你的代码好看。寻找许多输入侦听器(和命令)。状态变化监听器应该忽略他们的论点。

        【讨论】:

        • 10,000 个对象 @ 20 个字节!= 20K(应该是 200K)。
        • @Kevin Brock Erm,是的。哦。 0.02%。
        【解决方案4】:

        我是一名 5 年的 Swing 程序员。根据我的经验,听众在哪里从来都不是问题。当侦听器不再对事件感兴趣时,取消注册侦听器很重要。

        请记住,在 GUI 应用程序中,响应时间比内存更重要。 你可能对我的文章感兴趣:

        Use Weak Listeners to avoid Memory Leaks
        Know when your Object gets Garbage Collected
        Long-Lived Models may cause Memory Leaks

        【讨论】:

          【解决方案5】:

          请记住,聆听与忙碌的等待不同。侦听器通过某种代理方法注册在感兴趣方列表上。在大多数侦听器模式中浪费的 CPU 时间很少。

          【讨论】:

            【解决方案6】:

            在 Java ME 中有使用较少类的传统,因为下载另一个类文件的开销很大,因此有时您会获得尝试减少侦听器类型数量的 Java 代码,并在侦听器中使用条件逻辑。对于桌面 Java 应用程序的大小并不是真正的问题,但您有时会看到它泄漏到该空间中。

            不是真的回答这个问题,但是一旦你走到了那一步,你会发现你可以将 MenuTest 的大多数字段的范围缩小为构造函数中的方法变量。

            我倾向于使用匿名侦听器,因为大多数情况下它们太简单而无法命名。

            显示的代码使用动作侦听器来检测菜单项是否被“执行”。

            在构造函数中设置选定的菜单项,然后在 main 方法中访问对象的colorValues 字段,并设置背景的值。显然,这在信息隐藏或封装行为方面都失败了。

            如果您改为使用ChangeListeners 来检查菜单项是否设置为选中,那么您没有两个单独的逻辑片段来保持与响应调用设置颜色相同的状态到setSelected,您不必将colorValues 字段泄露给调用者。

            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() 
            

            您还可以使用第二个最终变量来保存 colorItem 并避免对 event.getSource() 进行强制转换。

            还可以实现更改侦听器以侦听设置框架上背景颜色的更改,并在使用 setBackground() 更改颜色时选择适当的菜单项。

            调用 setBackground() 会自动将面板标记为损坏,因此之后无需调用 repaint。

            【讨论】:

              【解决方案7】:

              好吧,考虑最基本的侦听器实现:您所做的基本上就是将一个对象添加到侦听器列表中,仅此而已。注册一个回调函数,如果你愿意的话。所以这一行的实现:

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

              基本上是:

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

              到目前为止,没有伤害没有犯规。该对象只是与所有其他已注册的侦听器一起位于列表中。

              但是,请注意在对象上触发事件时会发生什么:

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

              哎呀。这是你应该小心的地方。确保您的侦听器没有做任何太复杂的事情,否则您将看到瓶颈,这将在分析期间出现。

              底线:只需将监听器放在对象上几乎是免费的。只需确保您没有注册太多听众,并且每个听众都保持其操作简单明了。

              【讨论】:

              • 您提出了一个好问题:听众通常应该做的很少。如果他们需要做一些复杂的事情,他们应该产生一个新线程,最好使用 SwingWorker 类,然后立即返回。
              猜你喜欢
              • 2022-11-23
              • 2010-09-14
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2020-05-20
              • 2011-09-07
              • 2014-02-02
              • 1970-01-01
              相关资源
              最近更新 更多