Forum Programmation.java Java et Regexp

Posté par  (site web personnel) .
Étiquettes : aucune
0
17
sept.
2004
Bonjour,
Je suis actuellement en train d'ecrire un petit programme en Java qui est censé aller récuperer des dvds disponibles sur des sites marchands (une sorte de méta-moteur).
Là où le problème survient, c'est la lenteur avec laquelle mes Regexp sont matchées.

Prenons par exemple le code de la page suivante
http://mediabarre.2xmoinscher.com/DVD/liste.asp?strRech=robin%20des(...)

Et appliquons la regexp suivante :

()(<a TITLE=\"Cliquez[^>]*?href=(\".*?\")>)(.*?).+?Meilleur prix : <span class=\"DVD-TXT\">([^>]+?)


Sous visual-regexp, pas de problème, en 1 fraction de seconde, il m'isole les liens valides, avec leur prix, etc.

En revanche, sous mon programme, il mouline un ptit moment avant de me pondre le résultat, et ca j'aimerai bien savoir pourquoi (je ne parle pas d'optimiser ma regexp, pour ca c'est evident que ce n'est pas au point, mais meme non-optimisé, visual-regexp arrive a matché en 50 millisecondes, donc...)
Bref, ci joint le bout en question de mon code :



String mediabarre = new String("http://mediabarre.2xmoinscher.com/DVD/liste.asp?strRech=(...)");
String keywords=new String("robin des bois");
keywords=URLEncoder.encode(jTextField1.getText());
mediabarre = mediabarre + keywords;
String html="";
String tmp="";

try{

URL doc1 = new URL(mediabarre);

BufferedReader plop = new BufferedReader(new InputStreamReader(doc1.openStream()));

while((tmp=plop.readLine())!=null)
{
html = html + tmp;
}
plop.close();

Pattern p;
Matcher m;

p = Pattern.compile("()(<a TITLE=\"Cliquez[^>]*?href=(\".*?\")>)(.*?).+?Meilleur prix : <span class=\"DVD-TXT\">([^>]+?)");
m = p.matcher(html);


while (m.find())
{
txt_resultat.setText(txt_resultat.getText().concat("Mediabarre : " + m.group(4) + " pour " + m.group(5) + "\n"));

}




Voilà, si quelqu'un se sent l'âme charitable, je lui en serait très reconnaissant.

Merci
  • # arf...

    Posté par  (site web personnel) . Évalué à 1.

    Bon allez moinssez moi, j'ai pas vu que j'etais dans les journaux... En plus templeet a encore a moitié bouffé mes balises...

    désolé...
    ->[]
  • # html = html + tmp

    Posté par  (site web personnel) . Évalué à 4.

    Ce n'est sans doute pas la raison du plus gros ralentissement, mais tu pourrais déjà utiliser un StringBuffer pour faire la concatenation avec la méthode append(String).

    Tu peux même faire tes match dans la boucle qui récupère le code de la page en fait... (enfin il faut voir le code des pages)
    • [^] # Re: html = html + tmp

      Posté par  (site web personnel) . Évalué à 1.

      Ayé, j'ai mis un stringbuffer, merci beaucoup (j'connais pas encore trop trop java désolé :))
    • [^] # \o/ Pierre Tramo

      Posté par  (site web personnel) . Évalué à 4.

      Moi aussi, le passage

      BufferedReader plop = new BufferedReader(new InputStreamReader(doc1.openStream()));
      while((tmp=plop.readLine())!=null)
      {
      html = html + tmp;
      }
      plop.close();

      me parait très sous-optimal, une boucle de lecture sur le InputStreamReader avec écriture vers un StringWriter, ça serait déjà moins violent. Bon, après , l'expression matchée tiens sur une ligne (ce qui a l'air d'être le cas), tester sur chaque ligne du BufferedReader, c'est effectivement encore mieux, ça évite de stocker tout le doc.


      String mediabarre = new String("http://mediabarre.2xmoinscher.com/DVD/liste.asp?strRech=(...(...))");
      String keywords=new String("robin des bois");
      keywords=URLEncoder.encode(jTextField1.getText());
      mediabarre = mediabarre + keywords;

      Déjà, ça faut pas le laisser passer [:totoz]. Un String mediabarre = "http://mediabarre.2xmoinscher.com/DVD/liste.asp?strRech=(...(...))" + URLEncoder.encode(jTextField1.getText()); sera bien plus efficace. En particulier, les String truc = new String("bidule") sont à proscrire, String truc = "bidule" marchera aussi bien.


      while (m.find())
      {
      txt_resultat.setText(txt_resultat.getText().concat("Mediabarre : " + m.group(4) + " pour " + m.group(5) + "\n"));
      }

      Ça aussi, il faut le revoir. La concaténation de chaînes était très coûteuse, les concaténations en boucle sont catastrophiques pour les perf :

      StringBuffer buffy = new StringBuffer();
      // ou StringBuffer buffy = new StringBuffer(txt_resultat.getText());
      while (m.find())
      {
      buffy.append("Mediabarre : ").append(m.group(4)).append(" pour ").append(m.group(5)).append('\n'));
      }
      txt_resultat.setText(buffy.toString());

      devrait légèrement accélérer les choses (le compilo remplace String s = a + b par String s = new StringBuffer(a).append(b).toString();, il faut toujours l'avoir en tête), d'autant plus que l'interface n'aura pas à être raffraichie à chaque fois.

      Voilà, c'est ce qui me saute aux yeux en première lecture.
      • [^] # Re: \o/ Pierre Tramo

        Posté par  (site web personnel) . Évalué à 0.

        Ayé j'ai tout modifié en conséquence, merci !!

        Bon parcontre mon programme reste toujours très très monstrueusement lent comparé a visual-regexp... :(
        • [^] # Re: \o/ Pierre Tramo

          Posté par  (site web personnel) . Évalué à 1.

          ahh nan c'est bon, j'avais oublié de chercher la regexp a chaque itération plutot que sur le bloc total comme toi et le monsieur au dessus m'avez dit :)

          Là c'est nikel, quasi instantanné !!

          Merci beaucoup :)
      • [^] # Re: \o/ Pierre Tramo

        Posté par  (site web personnel) . Évalué à 1.

        Encore une question cependant :
        si l'expression a matcher tenait sur plusieurs lignes, je n'aurai pas pu réaliser cette modification cruciale (j'ai réellement gagné 40 secondes de traitement), donc comment aurai-je pu faire ?
        • [^] # Re: \o/ Pierre Tramo

          Posté par  (site web personnel) . Évalué à 2.

          Ah oui, 40 secondes, quand même ! Tu peux poster ton code final ?
          • [^] # Re: \o/ Pierre Tramo

            Posté par  . Évalué à 1.

            Oui parce que là on sait pas trop si les ralentissements viennent juste du traitement de la regexp ou s'il faut prendre en compte l'IHM et d'autres trucs annexes.

            D'ailleurs la mise en page du code source est ignoble sur ce forum. En utilisant les balises [pre] ca donne quoi ?
            • [^] # Re: \o/ Pierre Tramo

              Posté par  (site web personnel) . Évalué à 3.

              C'est <pre> qui est utilisé, mais comme chaque retour chariot est doublé d'un <br/> (enfin j'ai pas vérifié mais ça y ressemble), bah y a une ligne sautée inutilement à chaque fois.
              On peut les désactiver, mais ça devient pas pratique pour le texte qui entoure le code...
              Faudrait ptet désactiver les retours chariot spécifiquement dans les <pre></pre> (ou utiliser une mise en syntaxe style wiki), mais je tiens trop à ce qu'il me reste de santé mentale pour me plonger dans le code templite et faire un patch :°)
          • [^] # Re: \o/ Pierre Tramo

            Posté par  (site web personnel) . Évalué à 1.

            Le code "final" qui ne l'est pas vu que c'est brut de décoffrage pour l'instant :)

            http://fuck.the.world.free.fr/RLFind.java(...)

            Voilou
            • [^] # Re: \o/ Pierre Tramo

              Posté par  . Évalué à 1.

              Heu, c'est pas la bonne classe si ?

              Autrement à première vu tu codes avec Emacs ou VI, je te conseille d'essayer Eclipse tant que tu es a apprendre Java. Ca permet de vraiment gagner du temps au début (et même après).
              • [^] # Re: \o/ Pierre Tramo

                Posté par  (site web personnel) . Évalué à 1.

                Si si c'est la bonne classe, pourquoi penses tu celà ? Il y a tout ce dont je parle depuis le début, et la définition de l'interface aussi.

                Sinon là c'est codé avec NetBeans, que j'utilise pas souvent car jle trouve lourd, mais contrairement a Eclipse il a un editeur de GUI qui me facilite la vie pour coder des trucs "rapidement". En l'occurence, il me fallait une GUI rapidement pour tester mon bouzin :)

                Voilou
                • [^] # Re: \o/ Pierre Tramo

                  Posté par  . Évalué à 1.

                  Bon, soit je suis aveugle, soit c'est pas la bonne version sur ton site !

                  Quand je fais un 'wget http://fuck.the.world.free.fr/RLFind.java(...) ' je tombe sur une classe etendant JApplet mais ne contenant aucun code avec des Regexp !!

                  En gros c'est juste une interface avec rien dans les méthodes de callback :).

                  Autrement, je suis peut être aveugle ou j'ai de la merde dans les yeux parceque normalement j'aurais du voir les tags "// GEN-" que met NetBeans dans le code généré avant de poster mon précédent commentaire :).

                  Enfin, j'ai mis un peu de temps à te répondre à cause du déplacement de ton journal dans les forums (ce qui est quand même plus sa place).
                  • [^] # Re: \o/ Pierre Tramo

                    Posté par  (site web personnel) . Évalué à 1.

                    OOps, corrigé :)
                    (c'etait la version 1, je me contentais d'afficher les pages de chacun des sites, la v2 est donc censée filtrer elle meme les résultats avec les regexp)
                  • [^] # Re: \o/ Pierre Tramo

                    Posté par  (site web personnel) . Évalué à 2.

                    Enfin, j'ai mis un peu de temps à te répondre à cause du déplacement de ton journal dans les forums (ce qui est quand même plus sa place).
                    Oui, c'est peut-être plus la place, mais ça serait bien que les modéros n'utilisent pas la fonction de déplacement avant de l'avoir débuggée : y a pas longtemps, le nombre de commentaires affiché ne prenait pas en compte les commentaires postés quand c'était encore un journal, et maintenant le changement efface les informations sur les commentaires lus/non lus, et du coup, la navigation avec la barre ne sert plus à grand chose :/
              • [^] # Re: \o/ Pierre Tramo

                Posté par  . Évalué à 0.

                Emacs avec JDE est très bien pour coder en java
            • [^] # Re: \o/ Pierre Tramo

              Posté par  (site web personnel) . Évalué à 2.

              Bon, on va prendre dans l'ordre :o

              import java.util.*;
              Moi j'évite d'importer des packages entiers. D'une part ça doit ralentir un chouille la résolution de noms au chargement de la classe (j'avais remarqué en désassemblant des .class que le compilo ne remplaçait pas les * par la bonne liste de packages), mais surtout ça rend la maintenance du code plus difficile : si tu reprends ton code quelques mois/années plus tard et que t'as pas un bon IDE sous la main, tu risque de passer du temps à retrouver dans quel package était telle ou telle classe.

              v = new Vector();
              Vector, c'était dans l'ancienne API des collections. Dans l'actuelle, il y a la classe ArrayList qui est équivalente et qu'il vaut mieux utiliser : si plusieurs threads ne risquent pas d'accéder simultanément à une liste, les accès seront plus rapides en utilisant un ArrayList qu'un Vector ; et surtout, la nouvelle API des collections est plus complète, plus souple,... donc autant oublier l'ancienne. Et pour faire propre, on peut découpler le type de structure et son implémentation : List i = new ArrayList();

              jLabel1.setText("Bienvenue sur RLFind, votre meta-moteur de recherche pr\u00e9f\u00e9r\u00e9 !");
              Hmmm pourquoi encoder les caractères accentués ?

              String mediabarre = new String("...");
              Alors je reviens là dessus : on ne fait jamais String s = new String("plop"); (ça crée un fait deux objets String, inutilement), mais String s = "plop" (un seul objet String créé, implicitement).

              StringBuffer buffy=new StringBuffer("");
              Pas besoin de créer une chaîne vide pour initialiser le StringBuffer, new StringBuffer() donnera le même résultat.

                          Pattern p;
              Matcher m;
              p = Pattern.compile("...");
              Vu que le pattern est immuable, il est préférable de le compiler une fois pour toute et non à chaque appel de la méthode jButton1ActionPerformed, en le déclarant comme attribut de classe contstant (static final). Pour les chaînes constantes, c'est aussi plus propre de faire comme ça.

              p = Pattern.compile("dvd");
              C'est une coquille, non ? Ça ne devrait pas être là ?

              Bon, maintenant on va pouvoir commencer à regarder en détail :o
            • [^] # Re: \o/ Pierre Tramo

              Posté par  (site web personnel) . Évalué à 2.

              Bon, ben les 40 secondes devaient venir d'autre part que de la recherche sur tout le contenu. J'ai utilisé ce code :

              Reader reader = new InputStreamReader(doc1.openStream());
              int nbChars;
              char[] chars = new char[1024];
              StringBuffer content = new StringBuffer();

              while ((nbChars = reader.read(chars)) != -1) {
              content.append(chars, 0, nbChars);
              }
              reader.close();

              m = p.matcher(content);

              while (m.find()) {
              buffy.append("Mediabarre : ").append(m.group(4)).append(" pour ").append(m.group(5)).append('\n');
              }

              Et c'est quasi instantanné (et je n'ai pas une machine de guerre), du même ordre que la recherche ligne par ligne (modulo le temps consommé par le réseau et l'interface graphique).

              PS : tu as lu http://java.sun.com/j2se/1.4.2/docs/api/java/net/URLEncoder.html#en(...) ?
              • [^] # Re: \o/ Pierre Tramo

                Posté par  (site web personnel) . Évalué à 1.

                Non mais ta boucle est belle :)
                Moi ma version a 40 seconde etait la 1ere postée initialement dans le topic.
                La tienne est propre, mais moi en fait le truc, c'est que j'ai du mal a bien me servir efficacement de java.io, car j'ai l'impression que pour un truc précis, on peut tout prendre, mais chacun s'instancie un peu differement, ont des noms assez semblables, et j'avoue que je suis perdu comparé a mes fonction io en C qui etaient somme toute assez simples :)

                StringReader, Reader StreamReader, etc, tout se ressemble, il y aurait pas un tuto expliquant quoi utiliser et comment ?

                En tout cas merci beaucoup pour ton aide :)
                • [^] # Re: \o/ Pierre Tramo

                  Posté par  (site web personnel) . Évalué à 2.

                  J'ai pas trouvé de guide, mais en survolant la javadoc du package java.io on peut déjà se faire une idée.
                  L'important à savoir pour commencer, c'est qu'il y a quatre classes (abstraites) de base, selon qu'on manipule des flux d'octets (InputStream, OutputStream) ou des flux de caractères (Reader, Writer). Ensuite on choisit une implémentation selon le support du flux (fichier, String/StringBuffer, tableau d'octets) ou on obtient le flux en retour d'une méthode (sockets), il existe aussi des passerelles de flux d'octets vers flux de caractères. Enfin, il y a les classes (toujours abstraites) Filter* (et quelques autres) qui ajoutent des facilités pour la manipulation d'un flux (quel qu'en soit le support), selon l'implémentation qui correspond au besoin (Buffered*, Print*, ...).
                  Voilà, c'est un bref résumé. Après il y a aussi java.nio et ses Channels mais c'est une autre histoire ;)
                  • [^] # Re: \o/ Pierre Tramo

                    Posté par  (site web personnel) . Évalué à 1.

                    ok merci beaucoup pour tes précisions, en gros il me reste plus qu'a potasser le mode d'emploi quoi :)

                    Bref, encore merci pour ton aide crucial pour le developpement de ma ptite appli !
  • # je peux pas t'aider

    Posté par  (site web personnel) . Évalué à -2.

    mais je ferai ça en python ... en utilisant le sgml/html parser ...
    peut être qques regexp simple ... et c tout ...

    non ? ;-)
    • [^] # Re: je peux pas t'aider

      Posté par  . Évalué à 1.

      s'il s'agit de lui faire changer de langage (ce que je crois n'est pas le sujet du journal), perl me semble bien mieux indiqué pour les regex...
      • [^] # Re: je peux pas t'aider

        Posté par  (site web personnel) . Évalué à 1.

        ce que je crois n'est pas le sujet du journal

        Oui mon programme est en java, et je ne connais pas du tout perl ni python, donc je n'envisage pas un seul instant un autre langage :)
        • [^] # Re: je peux pas t'aider

          Posté par  (site web personnel) . Évalué à -1.

          si tu connais java, tu peux te lancer dans python
          tu mettras autant de temps à faire ta regexp, qu'à comprendre le python ...

          de plus, tu peux faire du java, des class, utiliser des libs du monde java, en python ... avec jython !
  • # WebL

    Posté par  (site web personnel) . Évalué à 3.

    Si c'est pour récupérer des pages HTML et les parser pour en récupérer des infos , je te conseille d'utiliser WebL (http://research.compaq.com/SRC/WebL/(...)) pas dur à maitriser, il est vieux mais très complet.

    Ecrit en java, il peut être appelé depuis ton code, ou appeler ton code existant ( pour pouvoir mettre en base par ex. ).

    Pour lui trouver les noeuds SPAN qui ont un attribut CLASS égal à
    DVD-TXT, c'est "fingers in the nose".

    Je te laisse regarder les exemples pour te faire une idée :
    http://research.compaq.com/SRC/WebL/examples.html(...)
    • [^] # Re: WebL

      Posté par  (site web personnel) . Évalué à 1.

      Je vais jetter un oeil dès que j'ai un moment a moi(mais là le site semble ne pas réagir des masses de chez moi.. ah si c'est bon:-)

      Autrement, comment se fait il qu'avec l'api regex de java ce soit aussi long pour parser ? c'est mon algorithmie qui foire totalement, ou c'est l'api qui est lente ?? Car comparé a visual-regexp par exemple, c'est carrément le lièvre et la tortue...
      • [^] # Re: WebL

        Posté par  (site web personnel) . Évalué à 2.

        Là ça vient clairement de ton code, y a pas photo :p
        Et si tu veux faire un bench, fais bien attention de ne pas inclure le coût de la récupération du document par réseau, et aussi celui de l'interface graphique.
  • # Swing

    Posté par  . Évalué à 1.

    Salut,

    C'est du pinaillage mais dans ton code tu executes des opérations "lentes"
    a l'intérieur du thread Swing.

    Dans Swing, les evenements générés par l'interface
    utilisateur sont reçus par la JVM et stockés dans une file d'evenements. A
    l'intérieur de la JVM, un thread d'expedition d'evenements ( implémenté par la
    class java.awt.EventQueue) surveille cette file et expédie les évenements
    arrivants aux listeners intéressés.

    Le thread Swing s'occupe d'afficher les composants graphiques. Si tu effectues
    des traitements couteux à l'intérieur de ce thread, tu vas empecher la mise
    à jour de l'interface.

    ( Note : la méthode actionPerformed() ici correspond à ta méthode jButton1ActionPerformed() , le listModel est le modele d'une JList et correspond à ton
    composant JTextArea - mais c'est plus explicite avec un listModel )

    1. Exemple de ce qu'il *ne faut pas* faire :
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~


    Ici le thead Swing est bloqué pendant 1 seconde, si tu resize la fenetre par exemple, elle ne serait pas repainte .


    public void actionPerformed(ActionEvent event) {
    try {
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    for (int i = 0; i < 100; i++) {
    val++;
    listModel.addElement(new Integer(val));
    }
    }


    2. Exemple de ce qu'il *ne faut pas* faire :
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    Ici le travail est bien effectué à l'exterieur du thread Swing, mais le modele de liste n'est pas mis à jour à l'intérieur du thead Swing.

    public void actionPerformed(ActionEvent event) {
    new Thread(new Runnable() {
    public void run() {
    try {
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    for (int i = 0; i < 100; i++) {
    val++;
    listModel.addElement(new Integer(val));
    }
    }
    }).start();
    }
    }



    3. La bonne manière de procéder
    ~~~~~~~~~~~~~~~~~~~~~~

    - le travail est effectué à l'extérieur du thread Swing
    - la mise à jour du composant graphique est effectuée à l'intérieur du thread Swing.

    public void actionPerformed(ActionEvent event) {
    new Thread(new Runnable() {
    public void run() {
    try {
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    SwingUtilities.invokeLater(new Runnable() {
    public void run() {
    for (int i = 0; i < 100; i++) {
    val++;
    listModel.addElement(new Integer(val));
    }
    }
    });
    }
    }).start();
    }


    Références
    ~~~~~~~

    Le threading dans Swing :
    http://java.sun.com/products/jfc/tsc/articles/threads/threads1.html(...)

    Les exemples de code et plus d'explication :
    http://www.clientjava.com/blog/2004/08/20/1093059428000.html(...)

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.