gpt4 book ai didi

Java Swing Timer 比预期慢

转载 作者:行者123 更新时间:2023-11-30 08:22:37 26 4
gpt4 key购买 nike

我正在编写一个必须每隔一段时间重新绘制的 swing 程序。出于某种原因,javax.swing.Timer 似乎每 800-900 毫秒才重绘一次,即使我指定了较低的延迟也是如此。 (例如 100 毫秒)我认为延迟可能是由于 repaint() 方法需要 800 毫秒才能运行,但我计时它只需要 .3 毫秒。我尝试使用 Thread.sleep() 并在所需的时间间隔重新绘制。任何人都可以帮助解释为什么会这样吗?

我正在尝试做的事情似乎是 javax.swing.Timer 的预期目的,所以我很困惑为什么它会大大降低代码速度。主驱动类:

import java.awt.Dimension;

import javax.swing.JFrame;


public class GUIDriver{

public AnimationPanel animationPanel;

public static void main(String[] args){
GUIDriver gui = new GUIDriver();
gui.createAndShowGUI();

}

public void createAndShowGUI(){
animationPanel = AnimationPanel(50);
JFrame frame = new JFrame("Animations");

frame.setContentPane(animationPanel);
frame.setPreferredSize(new Dimension(1015, 840));

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

frame.pack();
frame.validate();
frame.doLayout();
frame.setVisible(true);
animationPanel.draw(); //only here when using Thread.sleep() method
}


}

自定义 JPanel 扩展:

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.Point2D;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Scanner;
import java.text.SimpleDateFormat;

import javax.imageio.ImageIO;
import javax.swing.JPanel;
import javax.swing.Timer;

/**
* This extension of the JPanel overrides the paintComponent method to provide a framework for
* creating 2-D animations.
*/
@SuppressWarnings("serial")
public class AnimationPanel extends JPanel implements ActionListener{

public ArrayList<LeakInstance> data;
public Timer timer;
public SimpleDateFormat dateOutput = new SimpleDateFormat("MM/dd/yyyy");
BufferedImage background;


public Color lineColor = Color.black;
public Color under = Color.blue;
public Color over = Color.red;
public Font defaultFont = new Font("default", Font.BOLD, 14);

public float cutoff = 50;

public int timeStep = 0;
public long startTime = 0;

public Graphics2D screen;

public AnimationPanel(float cutoff) {
super();
setLayout(null);
read("Data.txt");
this.cutoff = cutoff;
timer = new Timer(100, this); //commented out when using Thread.sleep()
try {
background = ImageIO.read(new File("img.png"));
} catch (IOException e) {
e.printStackTrace();
}
repaint();
timer.start(); //commented out when using Thread.sleep()
}


/**
* This method overrides JPanel's paintComponent method in order to customize the rendering of 2-D graphics
* and thus make animation possible. It is not called directly; it is called by repaint() which fully refreshes
* the screen.
*/
@Override
public void paintComponent(Graphics g) {
ArrayList<String> drawn = new ArrayList<String>();
screen = (Graphics2D)g;
RenderingHints renderHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
renderHints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
screen.setRenderingHints(renderHints);


screen.drawImage(background.getScaledInstance(1000, 800, Image.SCALE_SMOOTH), 0, 0, this);

g.setFont(defaultFont);

screen.setColor(Color.orange);
screen.fillRect(485, 735, 100, 20); //cover old date
screen.setColor(lineColor);
screen.drawString(dateOutput.format(data.get(timeStep).getDate()), 500, 750);
screen.drawString(Integer.toString(timeStep), 300, 750);
System.out.println((System.nanoTime() - startTime)/1e9f);
startTime = System.nanoTime();

float x, y;
float z;
int xoffset, yoffset;
for(int i = 0; drawn.size() < 24 && (timeStep-i) > -1; i++){
if(!drawn.contains(data.get(timeStep-i).getName())){
xoffset = 0;
yoffset = 15;
String name = data.get(timeStep-i).getName();
drawn.add(name);
x = data.get(timeStep-i).getLocation().x;
y = data.get(timeStep-i).getLocation().y;
z = data.get(timeStep-i).getZ();

if(z > cutoff)
screen.setColor(over);
else
screen.setColor(under);
switch(name){
//various cases to change x or y offset
}
screen.drawString(Float.toString(z), x+xoffset, y+yoffset);
screen.setColor(lineColor);
screen.drawLine((int)x-2, (int)y, (int)x+2,(int) y);
screen.drawLine((int)x, (int)y-2, (int)x, (int)y+2);
}
}
}

public void draw(){
try{
for(; timeStep < data.size()-1; timeStep++){
Thread.sleep(100);
repaint();
}
}catch(Exception e){
e.printStackTrace();
}
}

public void read(String filename){
File file = new File(filename);
data = new ArrayList<MyType>(100);
try(Scanner scan = new Scanner(file)){
while(scan.hasNextLine())
data.add(new MyType(scan.next(), scan.next(), scan.nextFloat(),
new Point2D.Float(scan.nextFloat(), scan.nextFloat())));
}catch (Exception e){
e.printStackTrace();
}
Collections.sort(data);
}


@Override
public void actionPerformed(ActionEvent e) {
if(e.getSource().equals(timer)){
if(timeStep < data.size()-1){
timeStep++;
repaint();
}
else
timer.stop();
}
}
}

最佳答案

我遇到的主要问题是

screen.drawImage(background.getScaledInstance(1000, 800, Image.SCALE_SMOOTH), 0, 0, this);

这导致 repaint 事件持续发生,并将更新重绘时间缩短到大约 0.25 毫秒

当我预先缩放图像时(我必须将其类型更改为 Image),例如...

try {
background = ImageIO.read(new File("C:\\Users\\Shane Whitehead\\Dropbox\\Wallpapers\\5781217115_4182ee16da_o.jpg"));
background = background.getScaledInstance(1000, 800, Image.SCALE_SMOOTH);
} catch (IOException e) {
e.printStackTrace();
}

我能够获得 0.1 毫秒的重绘时间。

我尝试在 data 列表中使用 100、1000 和 10、000 个元素,没有太大问题(只是为了好玩,我尝试了 100、000,但重绘时间仍然为 0.1 毫秒)

您需要特别注意确保油漆工艺得到很好的优化

根据评论更新

However it still doesn't explain why with a paint method that took ~.3ms was causing the timer to take ~800ms. Or why using javax.swing.Timer seems to be an order of magnitude slower than using Thread.sleep()

实际上是这样,但您需要了解事件调度线程、事件队列和RepaintManager 的工作原理。

基本上...

  • paintComponent 被调用,您调用 getScaledInstance,它通过 ImageObserver 触发一个 repaint 请求。 getScaledInstance 很慢,慢了 100 毫秒。 RepaintManager 优化重绘请求并尝试减少在 EDT 上发布的绘画事件的数量,这有时是好事,有时是坏事。这些绘画事件放在事件队列中
  • javax.swing.Timer 触发一个事件,这个事件被放入事件队列,由EDT处理。现在,这就是它变得“有点奇怪”的地方。 javax.swing.Timer 经过优化,“如果 coalesce 为真(这是默认设置),则只允许一个 Runnable 在 EventQueue 上排队并等待” - 因此,如果事件队列中已经存在预先存在的“计时器”事件,则不会发布新事件。
  • actionPerformed 方法(最终)被调用,也在 EDT 的上下文中,占用(即使是非常少量的时间)处理其他事件......所有这些加起来。 ..
  • 和/或再次调用paintComponent,重复...

因此,可能发生的情况是,由 getScaledInstance 引起的重复更新相隔的距离足以阻止 RepaintManager 优化这些调用,这给美国东部时间,Timer 正在滴答作响,但由于事件处理速度可能不够快,一些事件被丢弃,从长远来看,这会导致“油漆”间隔更远分开。

Thread 方法没有遇到这些问题的原因是它可以用新的绘制请求向 EDT 发送垃圾邮件,而不考虑事件队列的状态...

另外,我可以打破你的Thread 更新,这意味着在处理完data 列表中的所有项目之前,不会绘制任何内容,请查看Initial Threads。了解更多详情

有关在 Swing 中绘画如何工作的更多详细信息,请查看 Painting in AWT and Swing

您可能还想看看 The Perils of Image.getScaledInstance()Java: maintaining aspect ratio of JPanel background image有关缩放的更多详细信息...

更新了一些额外的测试

所以我加了...

long scaleStart = System.currentTimeMillis();
screen.drawImage(background.getScaledInstance(getWidth(), getHeight(), Image.SCALE_SMOOTH), 0, 0, this);
System.out.println("Scaled in: " + ((System.currentTimeMillis()- scaleStart) / 1000f));

要测试缩放过程,这通常需要大约 200-240 毫秒。其余的绘制过程只增加了大约 10ms。

我是在使用 timer.setCoalesce(false); 时这样做的,所以我没有从关闭合并中获得额外的好处。

通过预缩放图像,我得到了一个恒定的 0.1 毫秒更新(使用和不使用 timer.setCoalesce(false);)

关于Java Swing Timer 比预期慢,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24250717/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com