【问题标题】:Java Swing: Awfully slow cursor response when hovering over rectanglesJava Swing:将鼠标悬停在矩形上时,光标响应非常慢
【发布时间】:2018-02-27 16:07:41
【问题描述】:

下面是用户将鼠标光标移动到一堆矩形上的代码,并且光标应该在矩形上更改为手形光标,并在不在给定矩形顶部时恢复为默认的普通光标。

问题: 解决方案有效,但速度慢得令人痛苦。它们有很多“剥猫皮”的方法,因此我们可以对问题进行多种变体建模,解决方案可能多种多样,但不变的是,我们必须循环使用迭代器覆盖每个矩形,并使用 contains() 方法确定 JPanel 上的给定点是否确实在矩形内,并相应地更改光标。

就像需要实现的声音一样简单,即在将光标悬停在特定矩形上时更改光标,随着我添加不同的形状和更多的矩形,程序开始显着变慢。

下面的代码只是显示了与 x 轴对齐的矩形和与 y 轴对齐的矩形。我 将它们拆分(仍然可以将它们组合成一个列表)成两个矩形列表。我使用 while 循环在另一个列表中迭代这两个列表,每个列表在适当的位置使用 break 关键字。

我避免只使用一个大列表来保存两种类型的矩形(或不同类型的形状),因为

  1. 我需要每隔一段时间添加不同的形状,将不同的形状分类在自己的列表中会更好、更易读。
  2. 我本能地尝试通过为不同的形状使用不同的列表来缩短一个漫长的过程,如果可能,只迭代正确的列表而不迭代其他不必要的形状。即一个大列表将线性增长并迭代***所有形状***以始终找到正确的形状***。***不是一个大列表似乎不是一个那么聪明的实现吗?希望尽我所能避免 One-Large-List 的努力不是过早优化的情况!!!这是我想使用线程的一点to loop over different list concurrently or simultaneously but one thread misbehaves.

然后,我在不同的列表中对形状进行分类,因为下面的示例有两个列表。但是这个技巧也失败了,因为我必须按顺序迭代每个列表。所以我在另一个列表中有一个 while 循环。我没有避免迭代在不必要的列表上,因为一个循环必须在另一个循环中(或在另一个循环中开始),那么内部循环(或后面的循环)会支付不适当的性能开销,因为第一个循环是完全不必要的如果我们可以事先确定预先知道某个形状属于某个组。标记您,以确定鼠标光标是否悬停在属于圆形列表或矩形列表的形状上,这是我们需要事先了解的时间> !!这样我们就可以遍历特定的列表。 甚至会变得更好,如果此时您仍然可以按照我的推理,事先知道一个形状属于哪个列表必须在没有的情况下完成contains() 方法,因为在迭代 inside List 时会使用 contains() !!!

总而言之,下面的代码只是对两个列表的线性迭代。要访问第二个列表,您必须通过第一个。有没有办法可以先通过迭代第一个列表?

如果我所有的解释和探索都是错误的,没有意义。 问题。 那么,如何改善下面代码的光标响应。

编辑

很抱歉发布了无法编译的代码,我已经从这个代码 sn-p 继续前进,并且正在玩我的新玩具,称为线程,直到我最终把自己绑在一个我无法解开自己的结中。事实是我选择线程,因为我希望选择矩形并使用 setRect() 方法移动它们。我设想在预定运动中移动各种形状,即仿射变换可能需要线程由于绘画、重新绘画、搜索和所有方式我从一些多线程中受益的辛勤工作。无论如何,下面的代码编译并且光标响应实际上是 good!!!.Ouch!我有一个类似的实现,但它的缓慢可能是由绘制矩形的其他类引起的,不像在这个 SSCCE 中它们是由 for 循环绘制的。

同时,如果有人有办法通过线程获得这种良好的性能,我们将不胜感激。提前致谢。

    import java.awt.Color;
    import java.awt.Cursor;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.Point;
    import java.awt.Rectangle;
    import java.awt.event.MouseEvent;
    import java.awt.event.MouseListener;
    import java.awt.event.MouseMotionListener;
    import java.awt.geom.Rectangle2D;
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    import javax.swing.*;

    public class FlyingSaucerTwo extends JPanel {
     Rectangle2D.Double rec;
     Rectangle2D.Double rec1; 
     List<Rectangle2D.Double> recList;
     List<Rectangle2D.Double> recList2;

     Rectangle2D.Double mouseBoxx;  
     int f = 10;
     int g = 0;
     int s = 10;
     int y = 5;
     int z = 500;

     public FlyingSaucerTwo(){

    //FlyingSaucer needs to quickly identify specific points over given areas
    //enclosed in rectangles.They use a 'divide and conquer' approach where 
    //different types of rectangles are first identified and a worker thread
    //assigned to each category

     mouseBoxx = new Rectangle.Double();
     recList = new ArrayList<>();
     recList2 = new ArrayList<>();

     for(int i = 0; i < 15; i++){
         rec = new Rectangle2D.Double(2+f,10+g,5,1000);       
         f +=50;
         recList.add(rec);                
     }
     f = 10;

     for(int i = 0; i < 20; i++){
         rec1 = new Rectangle2D.Double(2+y,10+s,1000,5);       
         s +=35;
         recList2.add(rec1);                
     }
     s = 10;
    }


    public static void main(String[] args) {
        JFrame frame = new JFrame();
        FlyingSaucerTwo fs = new FlyingSaucerTwo();
        Laser laser = new Laser(fs);
        fs.addMouseMotionListener(laser);
        fs.addMouseListener(laser);
        frame.getContentPane().add(fs);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(700,700);
        frame.setVisible(true);     
   }

   //@Override
    protected void paintComponent(Graphics g) {
     super.paintComponent(g); 
     ((Graphics2D)g).setColor(Color.RED);
     int a = 10;
     int b = 10;

     for(int i = 0;i < recList.size();i++){               
       ((Graphics2D)g).draw(recList.get(i));
     }

     for(int i = 0;i < recList2.size();i++){               
        ((Graphics2D)g).draw(recList2.get(i));
     }
    }
    }

     class Laser implements MouseListener,MouseMotionListener{
      Rectangle2D.Double mouseBox;
      List<Rectangle2D.Double> recxList;
      Rectangle2D.Double recx;
      List<Rectangle2D.Double> recyList;
      Rectangle2D.Double recy;
      FlyingSaucerTwo fs;

     public Laser(FlyingSaucerTwo fs){
      this.fs = fs;
     }

     @Override 
     public void mouseClicked (MouseEvent e) { }
     @Override 
     public void mousePressed (MouseEvent e) { }
     @Override 
     public void mouseReleased(MouseEvent e) { }
     @Override 
     public void mouseEntered (MouseEvent e) { }
     @Override 
     public void mouseExited  (MouseEvent e) { }
     @Override 
     public void mouseDragged (MouseEvent e) { }

     @Override
      public void mouseMoved(MouseEvent e) {   
        SwingUtilities.invokeLater(new Runnable() { 
               @Override
               public void run() { 
                Point p = e.getPoint();
                recxList = fs.recList;                        
                recyList = fs.recList2; 
                Iterator <Rectangle2D.Double> recX = recxList.iterator();
                //FIRST LOOP over Y axis rectangles
                while(recX.hasNext()){
                     recx = recX.next();
                     if( recx.contains(p)){           
                         fs.setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR)); 

                         break;                     
                     }
                     else if(recyList.size()>=0){
                         Iterator <Rectangle2D.Double> recY = recyList.iterator(); 
                             //SECOND LOOP over X axis rectangles
                            while(recY.hasNext()){
                                 recy = recY.next();
                                 if( recy.contains(p)){           
                                     fs.setCursor(Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR));                                        
                                     break;                     
                                 }
                                 else{
                                     fs.setCursor(Cursor.getDefaultCursor());
                                 }
                            }
                     }
                     else{
                         fs.setCursor(Cursor.getDefaultCursor());
                     }
                }                      
            }
        });
    }
    }        

【问题讨论】:

  • 不运行代码 - 立即考虑需要对每个 mouseMoved 事件执行搜索 - 相反,您可以使用 Swing Timer 来弹出一个小的延迟,例如 250-500 毫秒之间的移动,因此您基本上是在等待机会主义时刻进行搜索。还要考虑先比较最后一个已知的“好”结果,所以如果用户没有移动到最后找到的矩形之外,就不要再做搜索了
  • 您也可以考虑研究一种算法,该算法根据与最后一个已知点的距离为您提供矩形列表,因此您在相对“接近”光标的范围内搜索 - 但我只有在你有一场体面的比赛后才会这样做
  • 我不知道你的代码为什么这么慢。看起来你只有 35 个对象。当您将光标悬停在各种组件上时,Swing 可以毫无问题地处理光标的变化。而且我们无法测试您的代码,因为它无法编译。发布适当的minimal reproducible example 来演示问题。在我看来,您的分而治之方法太复杂了。尝试使用单个数组解决问题并仅遍历该数组。
  • 或者使用基于图形的模型,这样可以让您更轻松地根据矩形潜入某个区域
  • 线程用于在后台执行长时间运行的任务。在这种情况下,您希望光标立即更改,这意味着逻辑需要快速实时执行,而不是在后台执行。即使它是一个简单的线性搜索,我在搜索数百个对象的 ArrayList 时也从未遇到过问题。无论如何,您发布的代码不使用线程,并且 invokeLater(...) 是不必要的,因为从侦听器调用的代码已在 EDT 上执行。

标签: java swing arraylist jpanel java-2d


【解决方案1】:

恕我直言,您的内部代码应该是这样的:

Cursor cursor = Cursor.getDefaultCursor();
Iterator <Rectangle2D> recs = rowBuffY.iterator();
//FIRST LOOP over Y axis rectangles
while(recs.hasNext()){
    selectRec = recs.next();
    if( selectRec.contains(p)){           
        cursor = Cursor.getPredefinedCursor(Cursor.S_RESIZE_CURSOR)); 
        dragging = false;
        moveLine = true;
        break;                     
    }
}
Iterator <Rectangle2D> recX = rowBuffX.iterator(); 
//SECOND LOOP over X axis rectangles
while(recX.hasNext()){
     selectRec = recX.next();
     if( selectRec.contains(p)){           
     cursor = Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR);
     dragging = false;
     moveLine = true;
     break;                     
}
tpp.setCursor(cursor);

此代码只检查每个矩形一次,并优先选择 X 轴矩形,而您的代码检查每个 X 轴矩形 N 次(对于鼠标未悬停的每个 Y 轴矩形)。

【讨论】:

  • 我拥有的和你拥有的没有显着差异。你只检查每个矩形一次,我也是如此,这不是最佳方法。更好的方法是避免进行详尽的蛮力搜索,并进行较小的搜索,这将更快,当然,随着矩形的增加,性能下降!它本质上是一样的,仔细观察,你会注意到你的代码也会检查 Y 轴矩形N次,然后移动到X轴矩形。没有一个矩形应该优先于另一个!因为所有矩形被用户指向的概率都相同!
  • @Afroid1000 不,您为每个 Y 轴矩形检查每个 X 轴矩形一次。这意味着您的示例代码已经检查了多达 300 个(20 乘以 15)个矩形(如果未找到匹配项),而我的代码最多检查 35 个矩形(20 加 15)。
  • 我明白了!每次外部循环仅检查 一个 X 轴矩形时,内部循环都会完全遍历 所有 Y 轴矩形。您确实是正确的。现在您有了更好的线性模型它顺序检查,如果每个循环同时搜索,每个循环都由它自己的线程进行搜索,那不是更好吗?此 SSCCE 无法正确重现我的项目中的糟糕性能,该项目是单线程的,其中矩形是由其他专业类绘制的。但是非常感谢,
  • @Afroid1000, there is no significant difference betwin what I have and what you have. - 完全正确。这就是为什么你要保持代码简单,直到你有一个被证明的问题。如果您无法发布展示问题的 SSCCE,那么我们将无法提供帮助,因为我们无法准确猜测您在做什么。
  • wouldn't it be even better if each loop was searched concurrently, - 当您的任务不是 CPU 密集型时,线程再次发挥作用。例如,如果您想从 Internet 读取多个 URL。当一个线程阻塞等待响应时,另一个线程正忙于读取数据。
【解决方案2】:

正如 Thomas Kläger 所建议的,您应该取消嵌套循环。

如果仍然存在问题,可能与您调用 setCursor() 过多有关。特别是,在嵌套循环示例中,如果光标不在矩形中,您将调用 setCursor() 来设置默认光标数百次。每次调用 setCursor() 时,都会重新绘制光标,这是一个耗时的过程。

对于每个 mousemoved 事件,您最多需要设置一次光标。这样做的一种方法是在遍历循环时设置一个关于所需游标类型的布尔值,然后根据布尔值仅在两个循环退出后在末尾设置游标。为了提高效率,您还可以检查当前光标是否已经是您想要的光标,只有在需要更改时才调用 setCursor()。

【讨论】:

  • 我可以确认setCursor 很慢。使用带有JMH 的小型基准测试,它比Rectangle2D.contains(Point)慢 3_000 倍(如果鼠标不在面板上)和 10_000 倍(如果鼠标在面板上)之间
  • 嵌套循环确实是个坏主意,布尔值确实会更有效@ThomasKlager 感谢您的指导。
猜你喜欢
  • 1970-01-01
  • 2017-08-14
  • 2012-03-01
  • 2012-09-22
  • 1970-01-01
  • 2012-12-29
  • 2021-11-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多