简体   繁体   English

如何避免java.lang.StackOverflowError?

[英]How to avoid java.lang.StackOverflowError?

I implemented a flood fill algorithm to my paint application. 我对我的绘画应用程序实施了填充算法。 There were no problems for my code on that algorithm. 我的代码在该算法上没有问题。

When I test the program, I noticed that the flood fill works fine for small enclosed areas but when the flood fill applied to large areas, I got java.lang.StackOverflowError and the large area was half filled after repainting. 当我测试程序时,我注意到泛洪填充适用于较小的封闭区域,但是当泛洪填充应用于较大区域时,出现java.lang.StackOverflowError,并且大面积填充后重新涂满了一半。 I know that Java have limited call stack for recursive methods, I'm not sure how can I optimize my code to cope with this problem, should resizing my bufferedimage necessary? 我知道Java对递归方法的调用堆栈有限,我不确定如何优化我的代码以解决此问题,是否需要调整我的bufferedimage的大小?

Code: 码:

import java.awt.*;

import java.awt.event.*;

import java.awt.image.BufferedImage;

import javax.swing.*;

public class MinimumVerifiableExample extends JFrame {
    private static final long serialVersionUID = 1L;

    private final int WIDTH = 800;
    private final int HEIGHT = 600;

    private PaintPanel panel;
    private JButton button;

    private MinimumVerifiableExample() {
        super("Paint App Plus");

        panel = new PaintPanel();
        button = new JButton("Fill with mouse click");

        button.addActionListener(e -> {
            panel.setFloodFill(Color.RED);
        });

        setSize(WIDTH, HEIGHT);
        setLocationRelativeTo(null);

        setLayout(new BorderLayout());

        add(panel, BorderLayout.CENTER);
        add(button, BorderLayout.SOUTH);

        setResizable(false);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(() -> {
            MinimumVerifiableExample frame = new MinimumVerifiableExample();
            frame.setVisible(true);
        });
    }

    private class PaintPanel extends JComponent implements MouseListener, MouseMotionListener {
        private static final long serialVersionUID = 1L;

        private final int canvasWidth = 784;
        private final int canvasHeight = 526;

        private BufferedImage canvas;
        private boolean floodFill;
        private Color fillColour;

        private boolean painting;
        private int prevX;
        private int prevY;
        private int curX;
        private int curY;

        private PaintPanel() {
            canvas = new BufferedImage(canvasWidth, canvasHeight, BufferedImage.TYPE_INT_RGB);
            floodFill = false;
            fillColour = null;

            painting = false;

            Graphics2D paintBrush = canvas.createGraphics();

            paintBrush.setColor(Color.WHITE);
            paintBrush.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
            paintBrush.dispose();

            addMouseListener(this);
            addMouseMotionListener(this);
        }

        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.setColor(Color.WHITE);
            g.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
            g.drawImage(canvas, getInsets().left, getInsets().top, canvasWidth, canvasHeight, this);
        }

        public void setFloodFill(Color fillColour) {
            floodFill = true;
            this.fillColour = fillColour;
        }

        private void floodFill(int x, int y, Color target, Color previous) {
            if (x > canvas.getWidth() || x < 1 || y > canvas.getHeight() || y < 1)
                return;

            if (canvas.getRGB(x, y) != previous.getRGB())
                return;

            previous = new Color(canvas.getRGB(x, y));
            canvas.setRGB(x, y, target.getRGB());

            floodFill(x + 1, y, target, previous);
            floodFill(x, y + 1, target, previous);
            floodFill(x - 1, y, target, previous);
            floodFill(x, y - 1, target, previous);
        }

        private void updateBoard() {
            Graphics2D paintBrush = canvas.createGraphics();
            paintBrush.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            paintBrush.setPaint(Color.BLACK);

            paintBrush.setStroke(new BasicStroke(10, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
            paintBrush.drawLine(prevX, prevY, curX, curY);

            paintBrush.dispose();
        }

        public void mousePressed(MouseEvent e) {
            if (floodFill) {
                floodFill(e.getX(), e.getY(), fillColour, new Color(canvas.getRGB(e.getX(), e.getY())));
                repaint();

                floodFill = false;
                return;
            }

            if (painting) return;

            prevX = e.getX();
            prevY = e.getY();

            painting = true;
        }

        public void mouseReleased(MouseEvent e) {
            if (!painting) return;

            curX = e.getX();
            curY = e.getY();

            painting = false;
        }

        public void mouseDragged(MouseEvent e) {
            curX = e.getX();
            curY = e.getY();

            if (!painting) return;

            updateBoard();
            repaint();

            prevX = curX;
            prevY = curY;
        }

        public void mouseClicked(MouseEvent e) {}
        public void mouseEntered(MouseEvent e) {}
        public void mouseExited(MouseEvent e) {}
        public void mouseMoved(MouseEvent e) {}
    }
}

Solution: 解:

    private class StackItem {
        private final int x;
        private final int y;
        private final Color previous;

        public StackItem(int x, int y, Color previous) {
            this.x = x;
            this.y = y;
            this.previous = previous;
        }
    }

    private void floodFill(final int initialX, final int initialY, final Color target, final Color previous) {
        Stack<StackItem> stack = new Stack<>();
        stack.push(new StackItem(initialX, initialY, previous));

        while (!stack.isEmpty()) {
            StackItem stackItem = stack.pop();
            if (stackItem.x > canvas.getWidth() || stackItem.x < 1 || stackItem.y > canvas.getHeight() || stackItem.y < 1)
                continue;

            if (canvas.getRGB(stackItem.x, stackItem.y) != stackItem.previous.getRGB())
                continue;

            Color previousColor = new Color(canvas.getRGB(stackItem.x, stackItem.y));
            canvas.setRGB(stackItem.x, stackItem.y, target.getRGB());

            stack.push(new StackItem(stackItem.x + 1, stackItem.y, previousColor));
            stack.push(new StackItem(stackItem.x, stackItem.y + 1, previousColor));
            stack.push(new StackItem(stackItem.x - 1, stackItem.y, previousColor));
            stack.push(new StackItem(stackItem.x, stackItem.y - 1, previousColor));

        }


    }

Please, pardon the use of continue . 请原谅continue使用。 I wanted to keep the structure of original solution similar to this one. 我想保持原始解决方案的结构与此类似。 I recommend though to restrain from using it. 我建议尽管限制使用它。

As you can see, this is a direct approach on how to translate recursion into a loop. 如您所见,这是直接将递归转换为循环的方法。 Instead of using JVM stack, which has limited size, we are using a collection, which uses JVMs heap. 我们使用的是使用JVM堆的集合,而不是使用大小有限的JVM堆栈。

Class StackItem is simply a representation of all arguments of a recursive function. StackItem只是递归函数的所有参数的表示。 Argument target does not change, so it is not part of it. 参数target不会更改,因此它不是其中的一部分。 Every recursive call is a equal to pushing new argument to our Stack structure. 每个递归调用都等于将新参数推入我们的Stack结构。 Every invocation of a "recursive" function is equal to poping the argument from top and executing logic using this argument. 每次调用“递归”函数都等于从顶部弹出参数,并使用该参数执行逻辑。

The simplest solution is to carefully inspect the stack trace and detect the repeating pattern of line numbers. 最简单的解决方案是仔细检查堆栈跟踪并检测行号的重复模式。 These line numbers indicate the code being recursively called. 这些行号表示要递归调用的代码。 Once you detect these lines, you must carefully inspect your code and understand why the recursion never terminates. 一旦检测到这些行,就必须仔细检查代码并了解为什么递归永远不会终止。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM