为什么java.awt.Graphics.drawLine特别慢?

alm*_*OSU 13 java swing graphics2d

我正在尝试实现以下'网格'布局.

CompleteImage

该类正在扩展java.awt.Canvas,并在paint函数中绘制这些形状(或行).为什么选择帆布?点击这里,尝试初始化做类似的事情.

更新了MCVE代码以获取上述"布局":

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;

@SuppressWarnings("serial")
public class SO_MCVE extends JPanel {

    private DrawingCanvas _drawingCanvas = null;

    private JButton repaintBtn;

    public SO_MCVE() {
        super(new BorderLayout());

        _drawingCanvas = new DrawingCanvas();
        _drawingCanvas.setSize(new Dimension(600, 600));

        JLabel repaintLabel = new JLabel(
                "<html><div style=\"text-align: center;\">" +
                "REPAINT</html>");
        repaintLabel.setHorizontalAlignment(
                SwingConstants.CENTER);

        repaintBtn = new JButton();
        repaintBtn.add(repaintLabel);
        repaintBtn.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {

                _drawingCanvas.triggerRepaint();
            }
        });

        add(_drawingCanvas, BorderLayout.CENTER);
        add(repaintBtn, BorderLayout.PAGE_END);
    }

    private static void createAndShowGUI() {
        JFrame frame = new JFrame("StackOverflow MCVE for drawLine");

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(new SO_MCVE());
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {

        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                UIManager.put("swing.boldMetal", Boolean.FALSE);
                createAndShowGUI();
            }
        });
    }
}

@SuppressWarnings("serial")
class DrawingCanvas extends Canvas {

    public static final Color lightGreen = new Color(0, 255, 0, 180);
    public static final BasicStroke STROKE1PX = new BasicStroke(1.0f);
    public static final BasicStroke STROKE3PX = new BasicStroke(3.0f);

    private static final int LEFT = 50;
    private static final int RIGHT = 550;
    private static final int TOP = 50;
    private static final int BOTTOM = 550;

    private static final double WIDTH = 500.00d;
    private static final double HEIGHT = 500.00d;

    public DrawingCanvas() {

        setBackground(Color.BLACK);
    }

    public void paint(Graphics g) {
        update(g);
    }

    public void triggerRepaint() {
        repaint();
    }

    public void update(Graphics g) {

        Graphics2D g2 = (Graphics2D) g;
        Dimension dim = getSize();
        int w = (int) dim.getWidth();
        int h = (int) dim.getHeight();

        // Clears the rectangle that was previously drawn
        g2.setPaint(Color.BLACK);
        g2.fillRect(0, 0, w, h);

        drawLines(g2, w, h);
    }

    /** Draw the lines marking the x-y limits **/
    private void drawLines(Graphics2D g2, int w, int h) {

        long start = System.nanoTime();
        System.out.println("Start of drawLines(): " + start);

        // Thick lines
        g2.setPaint(Color.GREEN);
        g2.setStroke(STROKE3PX);
        g2.drawLine(LEFT, 0, LEFT, h);
        g2.drawLine(RIGHT, 0, RIGHT, h);
        g2.drawLine(0, TOP, w, TOP);
        g2.drawLine(0, BOTTOM, w, BOTTOM);

        System.out.println("Done drawing thick lines!");
        long end = System.nanoTime();
        System.out.println("Time taken (ns): " +
                (end - start) + ", Time taken(ms): " +
                ((end - start)/1000/1000));
        start = end;

        // Thin vertical lines
        g2.setPaint(lightGreen);
        g2.setStroke(STROKE1PX);
        int wInc = ((int) WIDTH) / 50;
        for(int i = LEFT; i <= RIGHT; i += wInc) {
            g2.drawLine(i, TOP, i, BOTTOM);
        }

        System.out.println("Done drawing vertical lines!");
        end = System.nanoTime();
        System.out.println("Time taken (ns): " +
                (end - start) + ", Time taken(ms): " +
                ((end - start)/1000/1000));
        start = end;

        // Thin horizontal lines
        g2.setPaint(lightGreen);
        g2.setStroke(STROKE1PX);
        int hInc = ((int) HEIGHT) / 50;
        for(int i = TOP; i <= BOTTOM; i += hInc) {
            g2.drawLine(LEFT, i, RIGHT, i);
        }

        System.out.println("Done drawing horizontal lines!");
        end = System.nanoTime();
        System.out.println("Time taken (ns): " +
                (end - start) + ", Time taken(ms): " +
                ((end - start)/1000/1000));

        System.out.println();
    }
}
Run Code Online (Sandbox Code Playgroud)

上面显示的代码的问题是,每当我调用时,渲染这些行需要一段时间(大约3秒)repaint().
按"重新绘制"按钮以在MCVE中触发重绘.
线条将逐一慢慢绘制,如下图所示:

半

所以问题是:
有什么理由为什么drawLine这么慢?我尝试在类似的for循环中使用g2.draw(一些Ellipse2D.Double ..)绘制尽可能多的(如果不是更多)椭圆,并且没有问题.

注意:使用jre1.7.0_25,Windows 7,Eclipse
使用System.nanoTime()进行简单基准测试:

Done drawing thick lines!
Time taken (ns): 8858966, Time taken(ms): 8
Done drawing vertical lines!
Time taken (ns): 3649188968, Time taken(ms): 3649
Done drawing horizontal lines!
Time taken (ns): 106730282, Time taken(ms): 106
Run Code Online (Sandbox Code Playgroud)

注意:绘制'细垂直线'是永远的!

更新:
注意:使用jre1.8.0_11,Windows 7,Eclipse
使用System.nanoTime()进行简单基准测试:

Done drawing thick lines!
Time taken (ns): 110027, Time taken(ms): 0
Done drawing vertical lines!
Time taken (ns): 185567, Time taken(ms): 0
Done drawing horizontal lines!
Time taken (ns): 195419, Time taken(ms): 0
Run Code Online (Sandbox Code Playgroud)

注意:使用jre1.8.0_45,Windows 7,Eclipse
使用System.nanoTime()进行简单基准测试:

Done drawing thick lines!
Time taken (ns): 6716121, Time taken(ms): 6
Done drawing vertical lines!
Time taken (ns): 2427676380, Time taken(ms): 2427
Done drawing horizontal lines!
Time taken (ns): 83030042, Time taken(ms): 83
Run Code Online (Sandbox Code Playgroud)

显然,jre1.8.0_11工作得很好?

我如何运行不同的jre版本(不确定我是否正确执行!):

VersionSwitch

谢谢!:)

Ran*_*niz 4

按照 Michael Dibbets 的建议进行操作并使用缓冲。

我将the SO_MCVE.update(Graphics g)方法替换为绘制到屏幕外缓冲区的方法,然后绘制缓冲区:

public void update(Graphics g) {

    Graphics2D g2 = (Graphics2D) g;
    Dimension dim = getSize();
    int w = (int) dim.getWidth();
    int h = (int) dim.getHeight();

    // Create the buffer
    BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
    Graphics2D ig2 = image.createGraphics();

    // Paint everythign on the buffer
    // Clears the rectangle that was previously drawn
    ig2.setPaint(Color.BLACK);
    ig2.fillRect(0, 0, w, h);

    drawLines(ig2, w, h);

    // Paint the buffer
    g2.drawImage(image, 0, 0, null);
}
Run Code Online (Sandbox Code Playgroud)

drawLines() 开始:1832687816359773
粗线绘制完毕!
所用时间(ns):2212913,所用时间(ms):2
绘制垂直线完成!
花费的时间(ns):37676442,花费的时间(ms):37
完成绘制水平线!
所用时间(ns):6453455,所用时间(ms):6

如果您想提高效率,您可以将缓冲图像存储为属性,并在尺寸发生变化时替换它。

也就是说,非常有趣的是,绘制垂直线比绘制水平线需要更长的时间。在深入研究了SunGraphics2D调试器和分析器的内部结构之后,我仍然无法解释它。

  • 请注意,Swing 控件默认情况下是双缓冲的,因此您也可以扩展 JPanel 并通过重写的 PaintComponent 方法进行绘图。请务必调用 super.paintComponent 并恢复初始颜色和描边。当我这样做时,垂直线的绘制时间约为 16 毫秒。也许这可以解释为什么我在Oracle的数据库中找不到这个bug;我想现在大多数人都在使用 Swing 而不是直接使用 AWT。 (3认同)
  • 谢谢,解决方案很好。我**仍然好奇****为什么“绘制垂直线”需要这么长时间**,但你的回答确实解决了我的问题,所以我接受了它。谢谢!:) (2认同)
  • 可能是我们机器上的 java2d 版本存在错误...(我在 Windows 8 上使用 JDK1.7.0_60)。当我使用 JVisualVM CPU 分析器时,我发现仅绘制垂直线时,有 25050 个对 sun.java2d.loops.Blit$GeneralMaskBlit.Blit(...) 的调用,几乎占用了所有的绘制时间。我无法通过谷歌搜索找到有关此特定问题的任何信息。 (2认同)
  • 我怀疑水平线在内存中是连续的。您通常不会关心 Java 中的缓存未命中,但也许这一次您会关心。 (2认同)