IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Entrée/Sortie Java Discussion :

[FileReader] Besoin de performance !


Sujet :

Entrée/Sortie Java

  1. #1
    Membre du Club
    Profil pro
    Inscrit en
    Octobre 2003
    Messages
    143
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2003
    Messages : 143
    Points : 68
    Points
    68
    Par défaut [FileReader] Besoin de performance !
    Salut !

    Je dois lire un fichier texte dont les lignes correspondent à des enregistrements à insérer en base. Le fichier est de la forme :

    chp1,1~chp1,2~chp1,3~.....
    chp2,1~chp2,2~chp2,3~.....
    chp3,1 ..
    ....

    Or ce fichier fait 3Go !
    Je dispose certes d'un gros serveur et d'une base Oracle avec le driver jdbc qui va bien. Mais j'aimerais que le traitement ne prenne pas plus de 20 heures.

    Dans un premier temps mon souci est de parcourir le fichier le plus rapidement possible et de spliter la chaîne pour construire ma requête.

    Voici les deux méthodes que j'ai essayées sur un fichier de 5 Mo sur ma vieille bécane (Athlon 2400+, 512 Mo de RAM, OS Ubuntu). La 1ère semble plus performante mais de peu.

    (Précisions pour que vous ne vous demandiez pas ce que je trafique dans mon code : la valeur du champ id servant de clé primaire doit être : ChpCle1 + "_" + ChpCle2)

    1ère méthode :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
     
    	public static void lire1(String fic) {
    		try {
    			java.util.Date d1 = new java.util.Date();
    			String[] libelleChps = new String[7];
    			libelleChps[0] = "id";
    			libelleChps[1] = "ChpCle1";
    			libelleChps[2] = "ChpCle2";
    			libelleChps[3] = "chp1";
    			libelleChps[4] = "chp2";
    			libelleChps[5] = "chp3";
    			libelleChps[6] = "chp4";
     
    			FileReader fr = new FileReader(fic);
    			BufferedReader br = new BufferedReader(fr);
     
    			String ligne = br.readLine();
    			while (ligne != null){
    				String[] tab = ligne.split("~");
    				String req = "INSERT INTO data (id";
    				String val = " VALUES ('" + tab[0] + "_" + tab[1] + "'";
    				for &#40;int i = 0; i < tab.length; i++&#41;&#123;
    					req += "," + libelleChps&#91;i+1&#93;;
    					val += ",'" + tab&#91;i&#93; + "'";
    				&#125;
    				req = req + "&#41;" + val + "&#41;";
    				System.out.println&#40;req&#41;;
    				ligne = br.readLine&#40;&#41;;
    			&#125;
    			br.close&#40;&#41;;
    			fr.close&#40;&#41;;
     
    			java.util.Date d2 = new java.util.Date&#40;&#41;;
    			double tps = d2.getTime&#40;&#41; - d1.getTime&#40;&#41;;
    			System.out.println&#40;"MILLISECONDES &#58; " + tps&#41;;
    			System.out.println&#40;"SECONDES &#58; " + &#40;tps/1000&#41;&#41;;
    		&#125; 
    		catch &#40;Exception e&#41; &#123;
    			e.printStackTrace&#40;&#41;;
    		&#125;
    	&#125;

    2ème métode :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
     
    	public static void lire2&#40;String fic&#41; &#123;
    		try &#123;
    			java.util.Date d1 = new java.util.Date&#40;&#41;;
    			String&#91;&#93; libelleChps = new String&#91;7&#93;;
    			libelleChps&#91;0&#93; = "id";
    			libelleChps&#91;1&#93; = "ChpCle1";
    			libelleChps&#91;2&#93; = "ChpCle2";
    			libelleChps&#91;3&#93; = "chp1";
    			libelleChps&#91;4&#93; = "chp2";
    			libelleChps&#91;5&#93; = "chp3";
    			libelleChps&#91;6&#93; = "chp4";
     
    			FileReader fr = new FileReader&#40;fic&#41;;
    			int carac = 0;
    			char delimiter = '\n';
    			String chaine = "";
     
    			carac = fr.read&#40;&#41;;
    			while &#40;carac != -1&#41; &#123;
    				chaine = "";
    				while &#40;carac != &#40;int&#41;delimiter && carac != -1&#41; &#123;
    					chaine += &#40;char&#41; carac;
    					carac = fr.read&#40;&#41;;
    				&#125;
     
    				String&#91;&#93; tab = chaine.split&#40;"~"&#41;;
     
    				String req = "INSERT INTO data &#40;id";
    				String val = " VALUES &#40;'" + tab&#91;0&#93; + "_" + tab&#91;1&#93; + "'";
    				for &#40;int j = 0; j < tab.length; j++&#41; &#123;
    					req += "," + libelleChps&#91;j+1&#93;;
    					val += ",'" + tab&#91;j&#93; + "'";
    				&#125;
    				req += "&#41;" + val + "&#41;";
    				System.out.println&#40;"SQL = " + req&#41;;
    				carac = fr.read&#40;&#41;;
    			&#125;
    			fr.close&#40;&#41;;
     
    			java.util.Date d2 = new java.util.Date&#40;&#41;;
    			double tps = d2.getTime&#40;&#41; - d1.getTime&#40;&#41;;
    			System.out.println&#40;"MILLISECONDES &#58; " + tps&#41;;
    			System.out.println&#40;"SECONDES &#58; " + &#40;tps/1000&#41;&#41;;
    		&#125; 
    		catch &#40;Exception e&#41; &#123;
    			e.printStackTrace&#40;&#41;;
    		&#125;
    	&#125;

    Selon vous quelle est la méthode la plus rapide ? Y-a-t-il plus rapide en Java ?

    PS : Les contrôles et logs erreurs que je dois gérer ne me permettent pas d'utiliser un outil comme SQLLoader.


    Merci d'avance pour vos réponses éclairées

  2. #2
    Expert éminent sénior
    Avatar de adiGuba
    Homme Profil pro
    Développeur Java/Web
    Inscrit en
    Avril 2002
    Messages
    13 938
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur Java/Web
    Secteur : Transports

    Informations forums :
    Inscription : Avril 2002
    Messages : 13 938
    Points : 23 190
    Points
    23 190
    Billets dans le blog
    1
    Par défaut
    Salut,

    Je n'ai pas vraiment analyser ton code, mais dans un premier temps utilise un StringBuffer (ou StringBuilder si tu utilises Java 5.0) au lieu de faire des concaténation de String avec +, surtout dans les boucles...

    a++

  3. #3
    Membre du Club
    Profil pro
    Inscrit en
    Octobre 2003
    Messages
    143
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2003
    Messages : 143
    Points : 68
    Points
    68
    Par défaut
    Ok. Je vais tenter ça. Je te tiens au courant pour savoir si ça améliore les perfs.

    Merci bien

  4. #4
    Membre éclairé

    Profil pro
    Inscrit en
    Mai 2005
    Messages
    414
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2005
    Messages : 414
    Points : 671
    Points
    671
    Par défaut
    tiens un post qui m'interesse vachement.

    Je vais avoir la même problèmatique bientot. On doit nous livrer un batch Java d'intégration de données : 2Go de données à charger : 6 millions de lignes.

    Ces andouilles ont voulu faire ca en Java alors qu'en Cobol ca aurait été tellement mieux et plus rapide visiblement...

    Combien de temps tu mets finalement pour traiter tes 3Go données? parce qu'il va falloir que je case mon batch dans le plan batch de nuit et si ca prend plus de 2 heures c'est pas la peine, je vais leur dire d'aller acheter un bouquin de cobol !

  5. #5
    Membre chevronné
    Homme Profil pro
    Dév. Java & C#
    Inscrit en
    Octobre 2002
    Messages
    1 413
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Suisse

    Informations professionnelles :
    Activité : Dév. Java & C#
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Octobre 2002
    Messages : 1 413
    Points : 1 993
    Points
    1 993
    Par défaut
    La première méthode devrait être le plus rapide car elle utilise la classe BufferedReader. En plus, tu as la possibilité de définir la grandeur du buffer.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    BufferedReader br = new BufferedReader&#40;fr, 10 * 1024&#41;.
    Dans ton cas, j'utiliserai un buffer permettant de lire au moins une centaine de lignes à la fois.

    La remarque de adiGuba sur l'utilisation de StringBuffer ou StringBuilder est justicieuse.
    Bien le bonjour chez vous
    Jowo

  6. #6
    Membre éprouvé

    Homme Profil pro
    Inscrit en
    Mars 2003
    Messages
    291
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Mars 2003
    Messages : 291
    Points : 1 059
    Points
    1 059
    Par défaut
    i++ -> ++i
    Tu gagnes un pouillème mais en additionnant les pouillèmes on finit par gagner du temps
    Après je ne comprends pas pourquoi tu reconstruis ta chaîne champs à chaque fois
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    final String debReq = "INSERT INTO data&#40;id, ChpCle1, ChpCle2, chp1, chp2, chp3, chp4&#41; VALUES&#40;'";
    après dans ta boucle tu n'as plus qu'à créer les valeurs comme tu le fais avec un
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    StringBuffer sb = new StringBuffer&#40;debReq&#41; puis des sq.append&#40;", '"&#41;.append&#40;tab&#91;i&#93;.append&#40;"'&#41;;
    Pour exécuter la requête utilise bien un PreparedStatement
    http://beuss.developpez.com
    Tutoriels PostgreSQL, Assembleur, Eclipse, Java

  7. #7
    Expert confirmé
    Avatar de le y@m's
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2005
    Messages
    2 636
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Produits et services télécom et Internet

    Informations forums :
    Inscription : Février 2005
    Messages : 2 636
    Points : 5 943
    Points
    5 943
    Par défaut
    en java, la comparaison d'une variable avec la valeur 0 est plus per formante que sa comparaison avec une autre valeur donc je pense que tu doit pouvoir modifier ta boucle for pour avoir une comparaison avec 0 (je suis pas sur d'être clair )
    en fait, remplacer :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    for &#40;int i = 0; i < tab.length; i++&#41;
    &#123;
         req += "," + libelleChps&#91;i+1&#93;; 
         val += ",'" + tab&#91;i&#93; + "'"; 
    &#125;
    par quelquechose du genre
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    for &#40;int i = ?; i > 0; --i&#41;     // comparaison avec 0 à chaque intération
    &#123;
         ????;
    &#125;
    si ça peut te faire gagner un petit quelquechose...
    Je ne répondrai à aucune question technique par MP.

    Pensez aux Tutoriels et aux FAQs avant de poster (pour le java il y a aussi JavaSearch), n'oubliez pas non plus la fonction Rechercher.
    Enfin, quand une solution a été trouvée à votre problème
    pensez au tag

    Cours Dvp : http://ydisanto.developpez.com
    Blog : http://yann-disanto.blogspot.com/
    Page perso : http://yann-disanto.fr

  8. #8
    Membre confirmé

    Inscrit en
    Juillet 2002
    Messages
    116
    Détails du profil
    Informations forums :
    Inscription : Juillet 2002
    Messages : 116
    Points : 514
    Points
    514
    Par défaut
    Voici les petites astuces que j'aurais utiliser pour améliorer les performances :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
     
    	public static void lire1&#40;String fic,String fic2&#41; &#123; 
    		try &#123; 
    			java.util.Date d1 = new java.util.Date&#40;&#41;; 
     
    			String&#91;&#93; libelleChps = new String&#91;7&#93;; 
    			libelleChps&#91;0&#93; = "id"; 
    			libelleChps&#91;1&#93; = "id,ChpCle1"; 
    			libelleChps&#91;2&#93; = "id,ChpCle1,ChpCle2"; 
    			libelleChps&#91;3&#93; = "id,ChpCle1,ChpCle2,chp1"; 
    			libelleChps&#91;4&#93; = "id,ChpCle1,ChpCle2,chp1,chp2"; 
    			libelleChps&#91;5&#93; = "id,ChpCle1,ChpCle2,chp1,chp2,chp3"; 
    			libelleChps&#91;6&#93; = "id,ChpCle1,ChpCle2,chp1,chp2,chp3,chp4"; 
     
    			FileReader fr = new FileReader&#40;fic&#41;; 
    			BufferedReader br = new BufferedReader&#40;fr&#41;;
    			FileWriter fw = new FileWriter&#40;fic2&#41;;
    			PrintWriter pw = new PrintWriter&#40;fw&#41;;
     
    			String ligne = null; 
    			while &#40;&#40;ligne=br.readLine&#40;&#41;&#41;!= null&#41;&#123; 
    				String&#91;&#93; tab = ligne.split&#40;"~"&#41;;
    				if&#40;tab.length > 1 && tab.length < libelleChps.length&#41; &#123;
    					StringBuffer req = &#40;new StringBuffer&#40;"INSERT INTO data &#40;"&#41;&#41;.append&#40;libelleChps&#91;tab.length&#93;&#41;.append&#40;"&#41; VALUES &#40;'"&#41;.append&#40;tab&#91;0&#93;&#41;.append&#40;"_"&#41;.append&#40;tab&#91;0&#93;&#41;;
    					int i=0;
    					try&#123; while&#40;true&#41; req.append&#40;"','"+tab&#91;i++&#93;&#41;; &#125;catch&#40;Exception ignore&#41;&#123;&#125; // Exception ArrayOutOfBound permet de sortir de la boucle
    					req.append&#40;"'&#41;"&#41;; 
    					pw.println&#40;req&#41;; 
    				&#125;
    			&#125; 
    			br.close&#40;&#41;; 
    			pw.close&#40;&#41;;
     
    			java.util.Date d2 = new java.util.Date&#40;&#41;; 
    			double tps = d2.getTime&#40;&#41; - d1.getTime&#40;&#41;; 
    			System.out.println&#40;"MILLISECONDES &#58; " + tps&#41;; 
    			System.out.println&#40;"SECONDES &#58; " + &#40;tps/1000&#41;&#41;; 
    		&#125; 
    		catch &#40;Exception e&#41; &#123; 
    			e.printStackTrace&#40;&#41;; 
    		&#125; 
    	&#125;

  9. #9
    Membre actif
    Profil pro
    Inscrit en
    Mars 2005
    Messages
    220
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mars 2005
    Messages : 220
    Points : 266
    Points
    266
    Par défaut
    la fin du message de jowo est tout a fait exacte...

    Si dans un premier temps, tu veux parcourir ton fichier le plus vite possible, limite le nombre d'accès en lecture, c'est gourmand, donc plutot que de lire ligne a ligne, fais toi plaisir et charge directement dans ton buffer un bon bloc de données. Fais le découpage en "lignes a inserer" au sein de ton programme java par la suite.

    Sinon, il est clair que la totallité du sujet donne plein de petites astuces bien utiles...

  10. #10
    Nouveau membre du Club
    Inscrit en
    Juin 2005
    Messages
    23
    Détails du profil
    Informations forums :
    Inscription : Juin 2005
    Messages : 23
    Points : 28
    Points
    28
    Par défaut
    Je ne pense pas que ta méthode soit la plus rapide.

    Tu lis une ligne sur un fichier texte, puis tu génère une requête SQL puis tu exécute la requête SQL.

    ceci pour chaque ligne.

    ton SGBD va donc interpréter, optimiser et exécuter une requete pour chaque ligne de ton fichier, ce qui représente un gros surcoût, surtout au niveau de l'interprétaion et de l'optimisation (calcul du plan d'exécution) des requètes.

    une première solution serait de 'préparer' les quelques requètes dont tu vas avoir besoin, puis de les exécuter autant que tu veux.

    une seconde solution serait d'utiliser les utilitaires que doit fournir Oracle pour intégrer dans la base des enregistrements écrits sur fichier plat (texte).
    ces outils sont en général très performants au niveau de la vitesse de traitement. je ne connais pas Oracle mais informix en avait de très bons.

    tu pourrais alors utiliser Java seulement pour préparer ces fichiers selon un format qui convient à Oracle.

    d'autre part, ton fichier est vraiment gros, as tu prévu une procédure de reprise si tu as un incident au milieu de l'intégration ?

  11. #11
    Expert éminent sénior


    Profil pro
    Inscrit en
    Mai 2003
    Messages
    3 240
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2003
    Messages : 3 240
    Points : 11 101
    Points
    11 101
    Par défaut
    As tu tout de même regarder http://gamache.developpez.com/LivreBD/SQLloader.pdf ?
    Es-tu vraiment sûr que tu ne peux vraiment pas utiliser SQL Loader ?

    Sinon, pour ce qui est JDBC, pense aussi à utiliser les PreparedStatement, en mode Batch. Et si possible, coté Oracle, pense à désactiver les indexes et le fichier de journalisation, pour les réactiver après.

    Si tu peux désactiver le fichier de journalisation sous Oracle, pense aussi à ne pas faire de commit après chaque ligne. Pourquoi pas tous les 1000 lignes ou 10000 lignes ? Et profites en pour sauvegarder dans un fichier où t'es arrivé dans le fichier après chaque commit. Pour pouvoir relancer depuis l'endroit où tu t'étais arrêté.

    Bonne chance.
    Vincent Brabant

    Ne pas me contacter par MP ni par mail pour des questions techniques. Ma liste d'amis restera vide.

    Cours et tutoriels pour apprendre Java , FAQ Java, et Forum Java

  12. #12
    Expert éminent sénior


    Profil pro
    Inscrit en
    Mai 2003
    Messages
    3 240
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2003
    Messages : 3 240
    Points : 11 101
    Points
    11 101
    Par défaut
    Peut être faudrait-il également remplacer ceci:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    String&#91;&#93; tab = ligne.split&#40;"~"&#41;;
    for &#40;int i = 0; i < tab.length; i++&#41;&#123;
      ...
    &#125;
    par
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    String&#91;&#93; tab = ligne.split&#40;"~"&#41;;
    int max = tab.length;
    for &#40;int i = 0; i < max; ++i&#41;&#123;
      ...
    &#125;


    Attention au code proposé par Debernad: Laisser le programme provoquer une exception ArrayOutOfBound est vraiment trop pénalisant du point de vue performance. A éviter

    Je suis tout de même assez curieux de voir combien de temps il faudra pour gérer 3 GO.
    Vincent Brabant

    Ne pas me contacter par MP ni par mail pour des questions techniques. Ma liste d'amis restera vide.

    Cours et tutoriels pour apprendre Java , FAQ Java, et Forum Java

  13. #13
    Membre du Club
    Profil pro
    Inscrit en
    Octobre 2003
    Messages
    143
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2003
    Messages : 143
    Points : 68
    Points
    68
    Par défaut
    Merci à tous pour vos réponses.

    Il est effectivement inutile de reconstruire la partie de la requête spécifiant les champs conccernés par l'INSERT. A chaque boucle seules les valeurs changent. Merci Beuss.

    Je vais m'inspirer de vos divers conseils et vous tiendrez informés sur ce POST.

    Merci encore

  14. #14
    Membre éprouvé

    Profil pro
    Inscrit en
    Mars 2002
    Messages
    652
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations forums :
    Inscription : Mars 2002
    Messages : 652
    Points : 1 151
    Points
    1 151
    Par défaut
    Pour ma part, et sans code à l'appuis, je ferais un thread provider qui prend en charge la lecture du fichier et allimente un pool synchronisé et ensuite, n Thread consumer qui se charge de convertire la ligne lue en requête SQL et de l'executer.

    Le souci vas résider dans ton cas à occuper la CPU pendant les phases de wait I/O à la base à savoir, pas loin de 95% du temps de ton programme je pense

    Quand à la lecture de ton fichier, regarde plutôt du coté des nio que des io !

    Une dernière chose, pense aussi à gérer l'état transactionnel de ton batch. surtout si tu fait du multi-thread.
    Ne fais jamais de batch en mode autoCommit=true
    Clic me...
    CV en ligne

    Il y a 10 types de personnes, celui qui connait le binaire, et l'autre...

    Pas de réponse en MP...Merci

Discussions similaires

  1. Besoin de conseils Algo et performances
    Par JulieBio dans le forum Algorithmes et structures de données
    Réponses: 2
    Dernier message: 14/04/2011, 11h23
  2. besoin de conseil : ajout redondance = performances ?
    Par -=mateo=- dans le forum Langage SQL
    Réponses: 3
    Dernier message: 29/11/2010, 18h08
  3. [Performance] besoin de conseils
    Par Esil2008 dans le forum Administration
    Réponses: 6
    Dernier message: 07/11/2008, 17h27
  4. Besoin d'aide pour l'I.A. d'un puissance 4
    Par Anonymous dans le forum C
    Réponses: 2
    Dernier message: 25/04/2002, 17h05

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo