很难判断此处所问的问题。此问题模棱两可、模糊不清、不完整、过于宽泛或过于修辞,无法以其当前形式合理地回答。若要获得澄清此问题以便重新打开的帮助,请单击visit the help center。
12年前就关闭了。
有没有一种方法,我可以优化这个代码,以不运行内存不足?
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Random;
import java.util.Stack;
public class TilePuzzle {
private final static byte ROWS = 4;
private final static byte COLUMNS = 4;
private static String SOLUTION = "123456789ABCDEF0";
private static byte RADIX = 16;
private char[][] board = new char[ROWS][COLUMNS];
private byte x; // Row of the space ('0')
private byte y; // Column of the space ('0') private String representation;
private boolean change = false; // Has the board changed after the last call to toString?
private TilePuzzle() {
this(SOLUTION);
int times = 1000;
Random rnd = new Random();
while(times-- > 0) {
try {
move((byte)rnd.nextInt(4));
}
catch(RuntimeException e) {
}
}
this.representation = asString();
}
public TilePuzzle(String representation) {
this.representation = representation;
final byte SIZE = (byte)SOLUTION.length();
if (representation.length() != SIZE) {
throw new IllegalArgumentException("The board must have " + SIZE + "numbers.");
}
boolean[] used = new boolean[SIZE];
byte idx = 0;
for (byte i = 0; i < ROWS; ++i) {
for (byte j = 0; j < COLUMNS; ++j) {
char digit = representation.charAt(idx++);
byte number = (byte)Character.digit(digit, RADIX);
if (number < 0 || number >= SIZE) {
throw new IllegalArgumentException("The character " + digit + " is not valid.");
} else if(used[number]) {
throw new IllegalArgumentException("The character " + digit + " is repeated.");
}
used[number] = true;
board[i][j] = digit;
if (digit == '0') {
x = i;
y = j;
}
}
}
}
/**
* Swap position of the space ('0') with the number that's up to it.
*/
public void moveUp() {
try {
move((byte)(x - 1), y);
} catch(IllegalArgumentException e) {
throw new RuntimeException("Move prohibited " + e.getMessage());
}
}
/**
* Swap position of the space ('0') with the number that's down to it.
*/
public void moveDown() {
try {
move((byte)(x + 1), y);
} catch(IllegalArgumentException e) {
throw new RuntimeException("Move prohibited " + e.getMessage());
}
}
/**
* Swap position of the space ('0') with the number that's left to it.
*/
public void moveLeft() {
try {
move(x, (byte)(y - 1));
} catch(IllegalArgumentException e) {
throw new RuntimeException("Move prohibited " + e.getMessage());
}
}
/**
* Swap position of the space ('0') with the number that's right to it.
*/
public void moveRight() {
try {
move(x, (byte)(y + 1));
} catch(IllegalArgumentException e) {
throw new RuntimeException("Move prohibited " + e.getMessage());
}
}
private void move(byte movement) {
switch(movement) {
case 0: moveUp(); break;
case 1: moveRight(); break;
case 2: moveDown(); break;
case 3: moveLeft(); break;
}
}
private boolean areValidCoordinates(byte x, byte y) {
return (x >= 0 && x < ROWS && y >= 0 && y < COLUMNS);
}
private void move(byte nx, byte ny) {
if (!areValidCoordinates(nx, ny)) {
throw new IllegalArgumentException("(" + nx + ", " + ny + ")");
}
board[x][y] = board[nx][ny];
board[nx][ny] = '0';
x = nx;
y = ny;
change = true;
}
public String printableString() {
StringBuilder sb = new StringBuilder();
for (byte i = 0; i < ROWS; ++i) {
for (byte j = 0; j < COLUMNS; ++j) {
sb.append(board[i][j] + " ");
}
sb.append("\r\n");
}
return sb.toString();
}
private String asString() {
StringBuilder sb = new StringBuilder();
for (byte i = 0; i < ROWS; ++i) {
for (byte j = 0; j < COLUMNS; ++j) {
sb.append(board[i][j]);
}
}
return sb.toString();
}
public String toString() {
if (change) {
representation = asString();
}
return representation;
}
private static byte[] whereShouldItBe(char digit) {
byte idx = (byte)SOLUTION.indexOf(digit);
return new byte[] { (byte)(idx / ROWS), (byte)(idx % ROWS) };
}
private static byte manhattanDistance(byte x, byte y, byte x2, byte y2) {
byte dx = (byte)Math.abs(x - x2);
byte dy = (byte)Math.abs(y - y2);
return (byte)(dx + dy);
}
private byte heuristic() {
byte total = 0;
for (byte i = 0; i < ROWS; ++i) {
for (byte j = 0; j < COLUMNS; ++j) {
char digit = board[i][j];
byte[] coordenates = whereShouldItBe(digit);
byte distance = manhattanDistance(i, j, coordenates[0], coordenates[1]);
total += distance;
}
}
return total;
}
private class Node implements Comparable<Node> {
private String puzzle;
private byte moves; // Number of moves from original configuration
private byte value; // The value of the heuristic for this configuration.
public Node(String puzzle, byte moves, byte value) {
this.puzzle = puzzle;
this.moves = moves;
this.value = value;
}
@Override
public int compareTo(Node o) {
return (value + moves) - (o.value + o.moves);
}
}
private void print(Map<String, String> antecessor) {
Stack toPrint = new Stack();
toPrint.add(SOLUTION);
String before = antecessor.get(SOLUTION);
while (!before.equals("")) {
toPrint.add(before);
before = antecessor.get(before);
}
while (!toPrint.isEmpty()) {
System.out.println(new TilePuzzle(toPrint.pop()).printableString());
}
}
private byte solve() {
if(toString().equals(SOLUTION)) {
return 0;
}
PriorityQueue<Node> toProcess = new PriorityQueue();
Node initial = new Node(toString(), (byte)0, heuristic());
toProcess.add(initial);
Map<String, String> antecessor = new HashMap<String, String>();
antecessor.put(toString(), "");
while(!toProcess.isEmpty()) {
Node actual = toProcess.poll();
for (byte i = 0; i < 4; ++i) {
TilePuzzle t = new TilePuzzle(actual.puzzle);
try {
t.move(i);
} catch(RuntimeException e) {
continue;
}
if (t.toString().equals(SOLUTION)) {
antecessor.put(SOLUTION, actual.puzzle);
print(antecessor);
return (byte)(actual.moves + 1);
} else if (!antecessor.containsKey(t.toString())) {
byte v = t.heuristic();
Node neighbor = new Node(t.toString(), (byte)(actual.moves + 1), v);
toProcess.add(neighbor);
antecessor.put(t.toString(), actual.puzzle);
}
}
}
return -1;
}
public static void main(String... args) {
TilePuzzle puzzle = new TilePuzzle();
System.out.println(puzzle.solve());
}
}
1条答案
按热度按时间hpcdzsge1#
∮问题是
根本原因是您在
toProcess
队列和antecessor
Map中创建和存储了大量String对象。看看你的算法,看看你是否真的需要存储超过200万个节点和500万个字符串。
∮调查
这很难发现,因为程序很复杂。实际上,我甚至没有试图理解所有的代码。相反,我使用了**VisualVM**-一个Java分析器、采样器和CPU/内存使用监视器。
我启动了它:
然后看了一下内存的使用情况,我注意到的第一件事是(显而易见的)你正在创建大量的对象。
这是应用程序的截图:
正如您所看到的,使用的内存量是巨大的,在短短的40秒内,就消耗了2GB,并且填满了整个堆。
死胡同
我最初认为这个问题与
Node
类有关,因为即使它实现了Comparable
,但它没有实现equals
。但这不是问题所在。
实际的问题原来是上面说的那个。
变通方案
如前所述,真正的解决方案是重新思考你的算法,在此期间,无论做什么,都只会拖延问题的解决。
但是变通方法可能是有用的,一个是重用您生成的字符串,您非常密集地使用
TilePuzzle.toString()
方法;这经常导致创建重复的字符串。因为你要生成字符串排列,所以你可以在几秒钟内创建许多
12345ABCD
字符串,如果它们是同一个字符串,那么用相同的值创建数百万个示例是没有意义的。String.intern()
**方法允许字符串被重用,文档中说:返回字符串对象的规范表示形式。
字符串池最初为空,由String类私有地维护。
调用intern方法时,如果池中已经包含了等于该String对象的字符串(由equals()方法确定),则返回池中的字符串;否则,将该String对象添加到池中,并返回对该String对象的引用。
对于常规应用程序,使用
String.intern()
可能不是一个好主意,因为它不允许示例被GC回收,但在本例中,由于您在Map和Queue中保存了引用,因此它是有意义的。所以做出这个改变:
差不多解决了记忆问题。
这是修改后的截图:
现在,即使在几分钟之后,堆使用量也不会达到100 MB。
额外备注
备注#1
您使用异常来验证移动是否有效,这是正常的;但当你抓住他们时,你却忽略了他们
如果你根本不使用它们,你可以通过不创建异常来节省大量的计算,否则你会创建数百万个未使用的异常。
进行此更改:
并使用验证代替:
备注2
你要为每个新的
TilePuzzle
示例创建一个新的Random
对象,如果你在整个程序中只使用一个对象会更好,毕竟你只使用了一个线程。备注3
这个解决方法解决了堆内存问题,但是创建了另一个涉及PermGen的问题。我只是增加了PermGen的大小,如下所示:
备注4
输出有时为49,有时为50。矩阵的打印方式如下:
五十次