Hoher Speicherverbrauch beim Zeichnen von Images aus einer ArrayList

JTryn

Mitglied
Hallo,

der Titel beschreibt mein Problem eigentlich schon recht präzise.
Mein ganzes Programm wird extrem langsam, wenn erst mehrere Images gezeichnet werden.

Ich erstelle folgendermaßen eine BufferedImage ArrayList, die die Images beinhaltet:
Codeausschnitt:
Java:
ArrayList<BufferedImage> images = new ArrayList<BufferedImage>();
Die Images werden ganz normal mit images.add() dann hinzugefügt.

Das Zeichnen funktioniert dann folgendermaßen:
Java:
for(int x=0;x < map.length ;x++){
	for(int y=0; y < map[0].length;y++){			
			g.drawImage(images.get(map[x][y][0]), x*32, y*32 this);				
	}
}

"map" ist einfach nur eine int-Matrix, die dann zum Beispiel '0' enthält, was das Tile bezeichnet,
das an 1. Stelle in "images" geladen wurde.

Jedenfalls funktioniert das ganze nur extrem langsam. Woran liegt das und wie könnte ich es
beheben?

Grüße, JTryn
 
Von wievielen Images sprechen wir denn hier?
Was ist map? Was macht images.get(map[x][y][0]) (ist images ne map)?
Wie hoch ist dein Speicherverbrauch?
 
Es handelt sich um etwa ~30 Images, also nichts besonders hohes.
map ist wie gesagt einfach nur eine int-Matrix, die beispielsweise Dimensionen von 25,
die mit "0" gefüllt sind.
Die map baut sich von oben links auf, map[0][0][0] ist dann beispielsweise die obere linke Ecke
der Karte. Dort steht dann irgendein int-Wert, beispielsweise 0.
mit images.get(map[0][0][0]) wird dann der int-Wert an dieser Stelle (0) in der ArrayList abgefragt.
0 in der images-ArrayList ist dann irgendeine Grafik, die angezeigt wird.
Das klappt auch alles super, es werden die richtigen Grafiken angezeigt, nur ist der Speicherverbrauch
sehr hoch, was bei dem, was hier gemacht wird, eigentlich nicht der Fall sein darf.
Dabei spreche ich von ~120.000kb, wenn 25*25 Grafiken (die 32x32 Pixel groß sind) angezeigt
werden - das ist einfach viel zu viel. Bevor die Grafiken angezeigt werden beläuft es sich auf ca.
32.000kb, also das Problem taucht wirklich erst beim Anzeigen auf.

Danke schon einmal für die Antwort 🙂 Hoffe es ist dieses mal verständlich.
 
Wenn du den Speicherverbrauch im Taskmanager anschaust: Der hat nicht viel Aussagekraft. Java nimmt sich (bis zur angegebenen Obergrenze) den Speicher, den es braucht, und gibt ihn erst wieder frei, wenn er für andere Sachen gebraucht wird. Solange keine OutOfMemoryException kommt, ist alles OK. Ggf. auch mal im Profiler anschauen (jVisualVM reicht vielleicht schon)
 
Ja, ich sehe im TaskManager nach. Gut zu wissen, dass Java so arbeitet 🙂 war mir vorher nicht
bewusst. Allerdings bin ich mir sicher, dass hier etwas falsch abläuft, da es sich hier
um den Ausschnitt eines Map Editors handelt. Die Grafiken werden per Maus gesetzt
(MausX-Koordinate in der Karte geteilt durch die Grafikgröße, das selbe für Y und an dieser Stelle
in der Map-Matrix wird dann die ID der ausgewählten Grafik gesetzt).
Je mehr Grafiken ich habe, desto unflüssiger wird das ganze und der Speicherverbrauch höher.

Ist an meiner Vorgehensweise wirklich nichts falsch? Dann kann der Fehler ja nur irgendwo anders
liegen. Mir kommt's so vor, dass performancebedingte Sachen schwer zu beheben sind, wie
könnte ich denn dahinterkommen woran es liegt?
 
An dem Geposteten ist nichts falsch (da gab es auch nicht viel, was hätte falsch sein können - etwas mehr Code würde vielleicht mehr Aufschluss bringen). Wie lädst du denn die Bilder? Sind das vielleicht PNGs mit Transparenz drin?
 
So, ich habe das ganze mal stark abgespeckt.

Fenster (hiervon wird in der Main-Methode ein Objekt erzeugt, dass das ganze einleitet.)
Java:
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.*;

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

	Map currentMap;
	MapView view;

	public MapWindow(){
		
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		this.requestFocus();

		String tilesetFilename = "Tileset.png";
	
		currentMap = new Map(30, 20, tilesetFilename);
		view = new MapView(currentMap, this);

		for(int x=0;x<currentMap.map.length;x++){
			for(int y=0;y<currentMap.map[0].length; y++){
				currentMap.map[x][y][0] = -1;
				currentMap.map[x][y][1] = -1;
				currentMap.map[x][y][2] = -1;
			}
		}
		
		add(view.scroll, BorderLayout.CENTER);

		setSize(1024, 768);
		setTitle("Map Editor");
		
	}
}

Hier werden die Images in die ArrayList geladen und die Matrix erstellt:
(Dass die zwei Dinge in einer Klasse sind ist wohl unpassend, aber jetzt bitte einfach davon
absehen.)
Java:
public class Map {
	String mapName;
	String tilesetFilename;
	int maplayers = 3;
	int[][][] map = new int[0][0][0];
	
	ArrayList<BufferedImage> tileset = new ArrayList<BufferedImage>();
	
	public Map(int mapwidth, int mapheight, String filename){
		
		this.tilesetFilename=filename;
		map = new int[mapwidth][mapheight][maplayers];

		try {
			BufferedImage picTileset=ImageIO.read(new File(filename));
			this.tileset.add(picTileset.getSubimage(0, 0, 32, 32));
			this.tileset.add(picTileset.getSubimage(0, 32, 32, 32));
		}catch (IOException error){
			System.err.println("Tileset "+filename+" not found.");
			error.printStackTrace();
		}
	}
	
	public void setTile(int x, int y, int z, int tileID){
		map[x][y][z]=tileID;
	}
}

Und die Klasse, die es anzeigt.

Java:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class MapView extends JPanel { 

	private static final long serialVersionUID = 1L;
	
	MapWindow window;
	Map map;
	
	JScrollPane scroll=new JScrollPane();
	
	JViewport viewport;
	RepaintManager m;
	     
	public MapView(Map map, MapWindow mwindow){
		scroll.setViewportView(this);
		window=mwindow;
		setDoubleBuffered(true);
		m = RepaintManager.currentManager(window);
		this.viewport = scroll.getViewport();
		
		addMouseListener(new MouseAdapter()
		{
			@Override
			public void mousePressed(MouseEvent e){
				int keyCode = e.getButton();
				if (e.getX()<window.currentMap.map.length*32 && e.getY()<window.currentMap.map[0].length*32){
					if (keyCode == MouseEvent.BUTTON1){
						drawTile(e.getX(), e.getY(), 0);
					}else if(keyCode == MouseEvent.BUTTON3){
						deleteTile(e.getX(), e.getY(), 0);
					}
				}
			}			
		});
		
		addMouseMotionListener(new MouseMotionAdapter()
		{
			@Override
			public void mouseDragged(MouseEvent e){
				if (e.getX()<window.currentMap.map.length*32 && e.getY()<window.currentMap.map[0].length*32){
					if (SwingUtilities.isLeftMouseButton(e)){
						drawTile(e.getX(), e.getY(), 0);
					}else if(SwingUtilities.isRightMouseButton(e)){
						deleteTile(e.getX(), e.getY(), 0);
					}
				}
			}		
		});
	}
 
	public void paintComponent(Graphics g){
		
		this.viewport = scroll.getViewport();
		
		g.setColor(Color.white);
		g.fillRect(0,0, window.currentMap.map.length*32, window.currentMap.map[0].length*32);
		
		for(int x=0;x < window.currentMap.map.length;x++){
			for(int y=0; y < window.currentMap.map[0].length;y++){
					if(window.currentMap.map[x][y][0] != -1){
						g.drawImage(window.currentMap.tileset.get(window.currentMap.map[x][y][0]), x*32, y*32, this);
					}					
			}
		}
		
		g.setColor(Color.gray);
		for(int i=0; i<window.currentMap.map.length; i++)
		{
			g.drawLine(i*32, 0, i*32, window.currentMap.map[0].length*32);
		}
		for(int j=0; j<window.currentMap.map[0].length; j++)
		{
			g.drawLine(0,j*32, window.currentMap.map.length*32, j*32);
		}
		
		((Graphics2D)g).setStroke(new BasicStroke(2));
		g.setColor(Color.black);
		g.drawLine(0, 0, window.currentMap.map.length * 32, 0);
		g.drawLine(0, 0, 0, window.currentMap.map[0].length * 32);
		g.drawLine(window.currentMap.map.length * 32, 0, window.currentMap.map.length * 32, window.currentMap.map[0].length * 32);
		g.drawLine(0, window.currentMap.map[0].length * 32, window.currentMap.map.length * 32, window.currentMap.map[0].length * 32);
			
	}
		
	public void drawTile(int x, int y, int z){
		x /= 32;
		y /= 32;
		window.currentMap.map[x][y][z] = 0;
			
		Rectangle r=scroll.getViewport().getViewRect();
		int dx=this.scroll.getLocation().x+window.getInsets().left-r.x;
		int dy=this.scroll.getLocation().y+window.getInsets().top-r.y;
		m.addDirtyRegion(window , dx+1+x*32, dy+2+y*32, 32, 32);
		
		System.out.println("Set tile at: "+x+" "+y+" "+z);
	}
	
	public void deleteTile(int x, int y, int z){
		x /= 32;
		y /= 32;
		window.currentMap.map[x][y][z] = -1;
		
		Rectangle r=scroll.getViewport().getViewRect();
		int dx=this.scroll.getLocation().x+window.getInsets().left-r.x;
		int dy=this.scroll.getLocation().y+window.getInsets().top-r.y;
		m.addDirtyRegion(window , dx+1+x*32, dy+2+y*32, 32, 32);

	}
	
}

Sollte sogar aufrufbar sein (falls nötig), als Grafik einfach ein mindestens 64x32 Pixel großes Bild nehmen
und oben bei Tileset.png eben anpassen.
Hier sieht man dann, dass es langsamer wird je mehr man reinmacht und schneller, wenn man die
Grafiken wieder entfernt.
 
Zuletzt bearbeitet:
Ja, daran liegt es, vielen Dank!

Allerdings frage ich mich warum dieses Problem am PC eines Bekannten gar nicht erst auftaucht
und er unabhängig davon ob er die Lösung verwendet oder nicht den selben Speicherverbrauch
hat.
 
Das mit dem Speicher hat damit ja nichts zu tun... und wenn es auf dem anderen PC schneller läuft, könnte das zum Beispiel daran liegen, dass der PC (bzw. die Grafikkarte) schneller ist 😉
 
Daran habe ich natürlich auch schon gedacht, aber das ist noch nicht mal der Fall,
deswegen verwundert mich es so. Der Unterschied zwischen den Systemen ist,
dass ich eine Geforce 9600 und er eine Geforce 7600 hat. Der Prozessor ist
genauso 'noch' ein DualCore wie meiner und auch nur leicht besser.
Vom System her ist es bei ihm WinXP und bei mir Win7, der einzige markante
Unterschied ist, dass er, wenn ich mich recht entsinne, 2GB mehr RAM hat.

Aber gut, mit der Lösung kann ich ja praktisch nichts falsch machen 🙂.
Danke noch einmal!
 

Zurück
Oben