[Java] Viewer CSV

Je cherche à développeur un Viewer CSV en Java/Swing. Pas de problème, je crée l’interface et je me concentre sur la classe CSVTableModel qui s’occupe de gérer les données à afficher.

Petit rappel, CSVTableModel implémente l’interface TableModel :

[codebox]

/*

  • @# CSVTableModel.java 1.0 3 mai 2006
  • Copyright 2006 Loic Dreux
    */
    package org.csvviewer;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.SwingWorker;
import javax.swing.table.AbstractTableModel;

/**
*
*

  • @author Loic Dreux

  • @version 1.0
    /
    public class CSVTableModel extends AbstractTableModel
    {
    // Charset and decoder for ISO-8859-15
    private static Charset charset = Charset.forName(“ISO-8859-15”);
    private static CharsetDecoder decoder = charset.newDecoder();
    // Pattern used to parse lines
    private static Pattern linePattern = Pattern.compile(".
    \r?\n");
    private static Pattern dataPattern = Pattern.compile(";");

    private boolean isLoaded;

    private Map<Integer, Integer> indexLines;
    private Map<Integer, String[]> buffer;

    private int rowCount;
    private int columnCount;

    private CharBuffer cb;
    private Thread t;

    /**

    • @param csvFile

    • @throws IOException
      */
      public CSVTableModel(File csvFile) throws IOException
      {
      isLoaded = false;

      indexLines = Collections.synchronizedMap(new HashMap<Integer, Integer>());
      buffer = Collections.synchronizedMap(new HashMap<Integer, String[]>());

      columnCount = 1;
      rowCount = 0;

      // Ouvre le fichier et récupère le canal via le flux
      FileInputStream fis = new FileInputStream(csvFile);
      FileChannel fc = fis.getChannel();

      // Récupère la taille du fichier
      int sz = (int) fc.size();
      MappedByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, sz);

      // Décode le fichier dans un CharBuffer
      cb = decoder.decode(bb);

      // Ferme le canal et le flux
      fc.close();

      // On lance le compteur de ligne
      t = new Thread(new CountLinesThread());
      t.start();
      }

    /*

    • (non-Javadoc)
    • @see javax.swing.table.TableModel#getRowCount()
      */
      public int getRowCount()
      {
      return rowCount;
      }

    /*

    • (non-Javadoc)
    • @see javax.swing.table.TableModel#getColumnCount()
      */
      public int getColumnCount()
      {
      return columnCount;
      }

    /*

    • (non-Javadoc)

    • @see javax.swing.table.TableModel#getValueAt(int, int)
      */
      public Object getValueAt(int rowIndex, int columnIndex)
      {
      if (!isLoaded && !buffer.containsKey(rowIndex)) {
      new LoadData(rowIndex, rowIndex).execute();
      return “”;
      }

      if (columnIndex < buffer.get(rowIndex).length)
      return buffer.get(rowIndex)[columnIndex];
      return “”;
      }

    /**

    • @author Loic Dreux

    • @version 1.0
      */
      public class CountLinesThread implements Runnable
      {
      public void run()
      {
      Matcher lm = linePattern.matcher(cb);
      int lines = 0;

       while (lm.find()) {
       	rowCount++;
       	lines++;
      
       	indexLines.put(lines, lm.start());
      
       	if (lm.end() == cb.limit())
       		break;
       	if ((lines % 50) == 0)
       	{
       		CSVTableModel.this.fireTableRowsInserted(lines - 50, lines);
       	}
       }
       CSVTableModel.this.fireTableRowsInserted(lines - (lines % 50),
       		lines);
       new LoadData(1, lines).execute();
      

      }
      }

    private class LoadData extends SwingWorker<Object, Object>
    {
    private int firstRow;
    private int lastRow;
    private boolean allData;

     public LoadData(int firstRow, int lastRow)
     {
     	this(firstRow, lastRow, false);
     }
     
     public LoadData(int firstRow, int lastRow, boolean allData)
     {
     	this.firstRow = firstRow;
     	this.lastRow = lastRow;
     	this.allData = allData;
     }
    
     @Override
     public Object doInBackground()
     {
     	for (int i = firstRow; i <= lastRow; i++) {
     		if (!buffer.containsKey(i)) {
     			Matcher lm = linePattern.matcher(cb.subSequence(indexLines
     					.get(i + 1), cb.length()));
     			if (lm.find()) {
     				buffer.put(i, dataPattern.split(lm.group()));
     				if (buffer.get(i).length > columnCount) {
     					columnCount = buffer.get(i).length;
     					CSVTableModel.this.fireTableStructureChanged();
     				}
     			} else {
     				System.out.println("Erreur : " + i);
     			}
     		}
     	}
     	return null;
     }
    
     @Override
     public void done()
     {
     	CSVTableModel.this.fireTableRowsUpdated(firstRow, lastRow);
     	if(allData)
     	{
     		indexLines = null;
     		cb = null;
     		t = null;
     		System.gc();
     	}
     }
    

    }
    }
    [/codebox]

Tout ce passe bien pour les fichiers modestes (500 lignes). Mais je souhaite faire un code qui fonctionne pour de gros fichiers (500000) lignes. Et là, c’est la catastrophe, le fait de tout sauvegarder dans une HashTable<Integer, String[]> nuit au performance.

J’avais testé avec un buffer qui calculait seulement les lignes à afficher et qui en sauvegardait que 1000 en mémoire, c’était plus lent à l’affichage : la table affichait pendant 1/2s des valeurs vide avant de mettre les bonnes valeurs, mais il y avait une plus grande réactivité car le calcul se faisait toujours dans un Thread séparé ; et l’application prenait beaucoup moins de mémoire.

Là, je ne sais plus trop quoi faire… Quelqu’un à une idée de comment stocké tout ça ? Je suppose que ca ne doit pas être trop difficile, faudrai que je regarde du coté de HSSQL, un SGBD léger en Java pour voir comment il stocke ces infos.

.Net supporte le mode virtuel par defaut dans les DataGridView, tu stockes deux “pages” de donnees et tu chope la suite a la volee, normalement ca rame pas a moins de vraiment y aller a mega fond dans le “page down” et tu peux toujours gerer un seek a des endroits arbitraire. C’est facile a faire. Si t’as un controlle qui fait ca comme il faut c’est rapide a coder/adapter en s’appuyant sur l’exemple ci dessous. Je l’ai fait pour un truc avec un backend super mega lent et ca marche plutot tres bien. Sinon va falloir refaire a la main. Cherche “datagridview virtual mode” sur ton moteur de rech favorit, tu verras.

Genre:
http://windowssdk.msdn.microsoft.com/libra…8c7c79345f6.asp

Il voulait pas du Java lui plutôt ? :stuck_out_tongue:
Enfin, ptete ca marche pareil.

Chai pas j’ai vu

En sous titre alors j’ai repondu :stuck_out_tongue:

.NET/Java, même combat, mon problème c’est comment lire un CSV rapidement sans bousiller la mémoire en Prog Objet, les Objets sont différents, le principe reste le même.
J’ai pas encore eu le temps d’approfondir ton lien, GloP, mais je vais essayer de charger les quelques pages suivantes/précédente en cache, faut voir comment intégrer ça proprement en Swing.

A mon avis il faut commencer par faire un premier parcours pour indexer le premier caractere de chaque ligne (récupérer simplement leur emplacement à l’intérieur du fichier, sans mémoriser le texte entier).
Ensuite pour faire de la pagination, tu récupères simplement en mémoire le contenu des lignes de m à n en utilisant ces offsets…
Tu peux aussi adapter l’algo à la taille du fichier… Du genre, en dessous de 150 ko, tu parses tout le fichier d’un coup, et au dessus tu fais du chargement à la demande en indexant les début de ligne.

[quote=« girafologue, post:6, topic: 28802 »]A mon avis il faut commencer par faire un premier parcours pour indexer le premier caractere de chaque ligne (récupérer simplement leur emplacement à l’intérieur du fichier, sans mémoriser le texte entier).
Ensuite pour faire de la pagination, tu récupères simplement en mémoire le contenu des lignes de m à n en utilisant ces offsets…
Tu peux aussi adapter l’algo à la taille du fichier… Du genre, en dessous de 150 ko, tu parses tout le fichier d’un coup, et au dessus tu fais du chargement à la demande en indexant les début de ligne.[/quote]

En ce moment, je me casse les dents pour sauvegarder les offset du charbuffer que j’ai en mémoire, je continue les tests. Sinon, excellente idée de modifier le traitement selon la taille du fichier, car jusque 60000, il n’y a pas de problème pour tout mettre en mémoire dans une ArrayList<ArrayList> (la JVM bouffe 100Mo par contre) après, c’est l’overflow :stuck_out_tongue:

[quote=“GloP, post:4, topic: 28802”]Chai pas j’ai vu

En sous titre alors j’ai repondu :P[/quote]
My bad, les sous-titres apparaissent pas dans le mail de notification, du coup j’avais pas vu.