Le compilateur signale les erreurs de syntaxe. Pour les erreurs qui peuvent survenir au cours de l'execution, Java utilise les exceptions.
La pile d'appel (stacktrace), une information précieuse pour le debuggage
Exception : interruption du flux d'execution du programme
Exceptions implicites (dérive de RuntimeException) et exceptions explicites.
Attitude à adopter : laisser filer sauf si :
l'exception est attendue et on sait la traiter
l'erreur révélée par l'exception ne condamne pas le reste du traitement
Lire le chapitre sur les exceptions dans le manuel
Une classe Java qui effectue le travail à partir du fichier CSV :
package fr.crim.a2012.util; import java.io.File; import java.io.IOException; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Scanner; /** * Lit le contenu du fichier CSV lexique pour produire une base SQLite * restreinte contenant une liste de formes uniques associés à leur lemme * et pos le plus fréquent. * Nécessite que le pilote JDBC pour SQLite soit présent dans le classpath * * @author Pierre DITTGEN */ public class Lexique { /** * Convertit le fichier lexique380.txt en une base SQLite prête à être * utilisé. */ private static void createLexiqueDB(String csvSrcFilepath, String sqliteFilepath) throws SQLException, IOException { // Base SQLite temporaire File tmpDB = new File("tempo.sqlite"); if (tmpDB.exists()) { tmpDB.delete(); } Connection tmpConn = DriverManager.getConnection("jdbc:sqlite:"+tmpDB); // Création de la table Statement stmt = tmpConn.createStatement(); stmt.executeUpdate("CREATE TABLE lexique (orth TEXT, lemma TEXT, pos TEXT, freq REAL)"); // Pour l'insertion en base PreparedStatement insertStmt = tmpConn.prepareStatement("INSERT INTO lexique VALUES(?, ?, ?, ?)"); // Ouverture du fichier CSV et lecture Scanner scan = new Scanner(new File(csvSrcFilepath), "ISO-8859-15"); boolean headers = true; int i=0; System.out.println("Chargement du fichier CSV"); while (scan.hasNextLine()) { String line = scan.nextLine(); // Ligne d'en-tête if (headers) { headers = false; continue; } // Découpage en colonne String[] values = line.split("\t"); // Et insertion dans la table insertStmt.setString(1, values[0]); // 1_ortho insertStmt.setString(2, values[2]); // 3_lemme insertStmt.setString(3, values[3]); // 4_cgram insertStmt.setDouble(4, Double.parseDouble(values[7])); // 8_freqlemlivres insertStmt.execute(); i++; if (i % 100 == 0) { System.out.println(i + " lignes traitées"); } } // Ouverture de la nouvelle base SQLite Connection conn = DriverManager.getConnection("jdbc:sqlite:"+sqliteFilepath); stmt = conn.createStatement(); // Création de la table stmt.executeUpdate("CREATE TABLE lexique (orth, lemma, pos)"); // Et de la requête d'insert insertStmt = conn.prepareStatement("INSERT INTO lexique VALUES (?, ?, ?)"); // Remplissage par requête sur la base en mémoire PreparedStatement selectStmt = tmpConn.prepareStatement("SELECT orth, lemma, pos FROM lexique ORDER BY orth, freq DESC"); ResultSet res = selectStmt.executeQuery(); String refOrth = ""; i = 0; System.out.println("Chargement de la base SQLite"); while (res.next()) { String orth = res.getString(1); String lemma = res.getString(2); String pos = res.getString(3); // Pour n'inclure qu'une ligne par orth // celle pour laquelle la fréquence est la plus forte if (!refOrth.equals(orth)) { insertStmt.setString(1, orth); insertStmt.setString(2, lemma); insertStmt.setString(3, pos); insertStmt.execute(); refOrth = orth; } i++; if (i % 100 == 0) { System.out.println(i + " enregistrements traités"); } } // On clot conn.close(); tmpConn.close(); } /** * @param args */ public static void main(String[] args) throws Exception { // charger le pilote SQLite Class.forName("org.sqlite.JDBC"); Long start = System.currentTimeMillis(); createLexiqueDB("rsc/Lexique380.txt", "rsc/lexique.sqlite"); System.out.println("Création de la table en " + (System.currentTimeMillis() - start) + "ms"); } }
Pistes :
Réduire le nombre de lignes pour optimiser les requêtes de sélection
Utiliser les transactions (conn.setAutocommit(false); ... conn.commit())
Utiliser les indexes à bon escient
Utiliser une base en mémoire lorsque celle-ci est temporaire
Truc : pour indiquer à SQLite de réduire la taille d'une base à la taille nécessaire, utiliser l'instruction VACUUM
En SQL :
-- Ajouter un index à une colonne existante CREATE INDEX lexique_orth_idx ON lexique(orth); -- Supprimer un index DROP INDEX lexique_orth_idx
Une classe qui exploite la base de données créée pour renvoyer le lemme et le pos d'une forme passée en paramètre :
package fr.crim.a2012.util; import java.io.File; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class FrenchLemmatizer { private Connection conn; private PreparedStatement selectStmt; /** * Constructeur * @throws SQLException * @throws ClassNotFoundException */ public FrenchLemmatizer(File sqliteFile) throws ClassNotFoundException, SQLException { this(sqliteFile, false); } /** * Constructeur * @param sqliteFile Fichier contenant la base SQLite avec la table lexique * @param inMemory true pour charger la base en mémoire : prend plus de * temps au démarrage mais accélère les requêtes ensuite. * @throws ClassNotFoundException * , SQLException */ public FrenchLemmatizer(File sqliteFile, boolean inMemory) throws ClassNotFoundException, SQLException { // charger le pilote SQLite Class.forName("org.sqlite.JDBC"); // Chargement de la base SQLite en mémoire pour des accès plus rapides ensuite if (inMemory) { conn = DriverManager.getConnection("jdbc:sqlite:"); PreparedStatement stmt; stmt = conn.prepareStatement("ATTACH DATABASE ? AS db"); stmt.setString(1, sqliteFile.getAbsolutePath()); stmt.execute(); execute("CREATE TABLE lexique AS SELECT * FROM db.lexique"); execute("CREATE INDEX orth_idx ON lexique(orth)"); execute("DETACH DATABASE db"); // Utilisation directe de la base SQLite } else { // créer la connexion à la base lexique conn = DriverManager.getConnection("jdbc:sqlite:" + sqliteFile); } // Requête de sélection selectStmt = conn .prepareStatement("SELECT lemma, pos FROM lexique WHERE orth = ?"); } /** * Raccourci d'écriture * @param sql requête à lancer * @throws SQLException */ private boolean execute(String sql) throws SQLException { PreparedStatement stmt = conn.prepareStatement(sql); return stmt.execute(); } /** * La méthode intéressante. Prend une forme en paramètre et renvoie un couple * (lemme, POS) ou NULL si non trouvé */ public LemmaPos getLemmaPos(String orth) { // le code SQL peut lancer des exceptions, elles sont retenues ici try { // passer la forme recherchée à la requête préparée selectStmt.setString(1, orth); // exécuter la requête ResultSet rs = selectStmt.executeQuery(); // si pas de résultat, renvoyer null if (!rs.next()) { return null; } // sinon renvoyer le couple lemme, pos obtenu return new LemmaPos(rs.getString("lemma"), rs.getString("pos")); } catch (SQLException e) { e.printStackTrace(); return null; } } /** * Classe utilitaire pour stocker un coupe lemme, pos * * @author pierre */ public static class LemmaPos { private String lemma; private String pos; /** * Constructeur * * @param lemma * @param pos */ public LemmaPos(String lemma, String pos) { this.lemma = lemma; this.pos = pos; } public String getLemma() { return lemma; } public String getPos() { return pos; } public String toString() { return lemma + " [" + pos + "]"; } } }
package fr.crim.a2012.saxigraph; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.SortedSet; import java.util.TreeSet; /** * Une classe utile pour encapsuler le code relatif à la gestion d'une liste * de fréquence et son affichage * * @author Pierre DITTGEN */ public class FreqMap { private Map<String,Integer> map; /** * Constructeur */ public FreqMap() { map = new HashMap<String,Integer>(); } /** * Remise à zéro */ public void reset() { map.clear(); } /** * Ajout d'un mot * @param mot */ public void add(String mot) { if (map.containsKey(mot)) { map.put(mot, map.get(mot) + 1); } else { map.put(mot, 1); } } /** * Renvoie un itérateur sur les mots en commençant par le + fréquent */ public Iterator<Map.Entry<String,Integer>> getTetesIterator() { SortedSet<Map.Entry<String, Integer>> set = new TreeSet<Map.Entry<String,Integer>>(new Comparateur()); set.addAll(map.entrySet()); return set.iterator(); } /** * Un objet qui compare des couple <Klé,Valeur> dans un SortedSet */ public class Comparateur implements Comparator<Map.Entry<String, Integer>> { @Override /** * Comparateur pour trier les couple clé valeur pour le TreeMap (trieur) * Deux objets sans différence selon le comparateur seront donc considérés * identiques. * On compare d'abord sur la valeur (effectif entier) * Attention, si deux valeurs sont égales selon le comparateur, * alors elles sont condidérées identiques. * Si on veut plus d'un mot par effectif (ex: atelier=1, duchesse=1) * il faut que le comparateur les ditingue aussi sur le mot (la clé) */ public int compare(Entry<String, Integer> e1, Entry<String, Integer> e2) { // on classe dans l'ordre de valeur inverse des Integer int dif = e2.getValue().compareTo(e1.getValue()); if (dif != 0) { return dif; } return e1.getKey().compareTo(e2.getKey()); } } }
Déclaration des attributs :
private static FrenchLemmatizer lemmatizer; private Map<String, FreqMap> dicoP = new HashMap<String, FreqMap>(); private Map<String, FreqMap> dicoSaid = new HashMap<String, FreqMap>();
Fin de balise :
public void endElement(String uri, String localName, String qName) { if ("p".equals(qName)) { dansBalise = false; traiteText(text.toString(), dicoP); } if ("said".equals(qName)) { dansBalise = false; traiteText(text.toString(), dicoSaid); } }
Traitement du texte
/** * La signature de cette méthode à laquelle on passe n'importe quel * dictionnaire permet de factoriser les traitements apportés aux mots avant * de les compter */ private void traiteText(String texte, Map<String, FreqMap> map) { String[] mots = frenchTokenizer.tokenize(texte); // Remplissage de la table de fréquence for (String mot : mots) { mot = mot.toLowerCase(); if (mot.isEmpty()) { continue; } // On examine le pos LemmaPos lemmaPos = lemmaPos(mot); if (lemmaPos != null) { String pos = lemmaPos.getPos(); if ("VER".equals(pos) || "NOM".equals(pos)) { if (map.get(pos) == null) { map.put(pos, new FreqMap()); } map.get(pos).add(lemmaPos.getLemma()); } } } }
Afficher les résultats
/** Trier et afficher les tableaux de mots sur deux colonnes */ public void tetes(int n, FreqMap pMap, FreqMap saidMap) { Iterator<Map.Entry<String, Integer>> itP = pMap.getTetesIterator(); Iterator<Map.Entry<String, Integer>> itSaid = saidMap .getTetesIterator(); afficheColonnes("<p>", "<said>"); String s1, s2; for (int i = 0; i < n; i++) { s1 = s2 = ""; // initialiser à rien si les listes sont inégales if (itP.hasNext()) { s1 = itP.next().toString(); } if (itSaid.hasNext()) { s2 = itSaid.next().toString(); } afficheColonnes(s1, s2); } } /** * Afficher les têtes pour les verbes et substantifs */ public void afficheTetes(int n) { System.out.println("Verbes :"); tetes(n, dicoP.get("VER"), dicoSaid.get("VER")); System.out.println("\nSubstantifs :"); tetes(n, dicoP.get("NOM"), dicoSaid.get("NOM")); } /** * Affichage d'un texte en 2 colonnes tabulées */ private void afficheColonnes(String col1, String col2) { String pad = " "; System.out.println(col1 + pad.substring(col1.length()) + col2); }
Anonymous
Pour la prochaine fois
Exercice - Contexte droit d'un verbe
À partir du code déjà développé, des classes présentées dans cette page, écrire une déclinaison de la classe SAXFrequence qui :
Prend en paramètre du constructeur un verbe à l'infinitif (ex : "vouloir")
Affiche la table de fréquence des mots en contexte droit (on filtrera les mots vides) pour les balises <p> et les balises <said>
Exemple de sortie souhaitée :