Les dessous des Strings. - Java - Programmation
Marsh Posté le 28-03-2004 à 14:05:14
nraynaud a écrit :
|
Et il se comporte différemment du
Code :
|
?
Marsh Posté le 28-03-2004 à 14:22:28
c'est vrai que c'est un peu débile cette histoire de final
sinon, les StringBuffer sont synchronisés, ce qui est bien en soit, mais flingue les performances dès qu'on travaille un peu avec
Marsh Posté le 28-03-2004 à 19:42:46
Ah je pensais que ct un topic cochon ...
(En fait drapo ...)
Marsh Posté le 28-03-2004 à 20:18:28
joli titre et joli topic.
Taz a écrit : c'est vrai que c'est un peu débile cette histoire de final |
+1 c'est bizarre ...
Taz a écrit : sinon, les StringBuffer sont synchronisés, ce qui est bien en soit, mais flingue les performances dès qu'on travaille un peu avec |
Y a StringBuilder dans le jdk 1.5 qui n'est pas synchronisé ... Et bon, même si c'est un peu plus lourd, on peut pas dire que la synchro des StringBuffer flingue les perfs.
R3g a écrit :
? |
surement vu que dans ce cas, la classe String n'a aucune garantie que tu ne vas pas modifier le tableau de char ....
Marsh Posté le 28-03-2004 à 20:43:20
benou a écrit : |
ben si, j'avais fait un programme qui faisait pas mal de traitement de texte (regex; split, join, append, etc) et en utilisant un StringBuffer fais maison, j'ai gagné 20% de vitesse de traitement. l'arrivée d'un StringBuilder est une très bonne chose, j'espère que son usage sera rapidement répandu.
edit : il est ou le sujet java1.5 ?
Marsh Posté le 28-03-2004 à 21:02:23
ReplyMarsh Posté le 28-03-2004 à 21:05:44
Taz a écrit : |
normalement, une synchronisation non utilisé ne coûte rien (mais j'ai la flemme d'aller rechercher le paplard l'expliquant).
Je te soupçonne plutôt d'avoir fait des recopies de buffer implicites (comme j'ai expliqué en heut).
Sinon, je vous rappelle une certaine tradition hérité de smalltalk : on écrit du code dans le langage cible (java en l'occurence) pour faire plaisir au gugus, et on refait tout en natif par en-dessous, pour des raisons de perfs. Donc il faut se méfier un peu (encore que dans le monde du JIT, cette tradition devrait diminuer).
[+1] sur benou pour String(char, int, int), il fait une copie du buffer (de la partie utile du buffer).
Marsh Posté le 28-03-2004 à 21:14:51
nraynaud a écrit : normalement, une synchronisation non utilisé ne coûte rien (mais j'ai la flemme d'aller rechercher le paplard l'expliquant). |
peut être, le fait est quand faisant mon propre StringBuffer et simplement, j'ai eu de meilleurs performances. maintenant, si tu me dis que c'est pas la synchronisation, ça veut dire que le comportement est un peu chiatique par défaut
Marsh Posté le 28-03-2004 à 23:17:57
Bonne initiative
Marsh Posté le 28-03-2004 à 23:37:40
machinbidule1974 a écrit : Bonne initiative |
j'en ai encore plein des comme ça ...
Marsh Posté le 29-03-2004 à 00:24:40
mazda3 a écrit : Ah je pensais que ct un topic cochon ... |
j'aurait proposé: les dessous de palm string
Marsh Posté le 29-03-2004 à 00:25:48
ReplyMarsh Posté le 18-04-2004 à 13:59:41
ajout de "La chûte du communisme"
Marsh Posté le 18-04-2004 à 14:52:31
'La chute du communisme' plutôt ?
mais pour le topic.
Marsh Posté le 18-04-2004 à 14:56:50
TBone a écrit : 'La chute du communisme' plutôt ? |
ah ? tu crois ? bon, j'édite alors !
Marsh Posté le 29-04-2004 à 23:08:46
rajouture de "Afficher ses convictions" (toString()) et "Petite leçon d'universalisme" (+)
comme d'hab, j'attends les critiques et je corrige les fautes.
Marsh Posté le 30-04-2004 à 08:27:52
nraynaud a écrit : ah ? tu crois ? bon, j'édite alors ! |
bah fais-le alors
Marsh Posté le 30-04-2004 à 08:49:40
j'ai pas bien compris le dernier paragraphe
Citation : Il peut être rentable dans certains cas de recréer la fonction printOn(Writer w) de smalltalk pour certaines hiérarchies d'objets qui devronts s'écrire sur un Writer (la classe StringWriter fait le lien entre StringBuffer et le système des Writers utilisé en entrée-sorties). Attention cependant à ne pas marcher sur les pieds de l'infrastructure de sérialisation mise en place avec l'interface Serializable. |
Marsh Posté le 30-04-2004 à 17:08:48
benou a écrit : j'ai pas bien compris le dernier paragraphe
|
si tu dois écrire une aggrégation sur un Writer, c'est un peu nul de prendre le toString() de chaque noeud et de l'écrire dans le writer, ça a toutes les chances de créer des objets inutiles donc on fait s'écrire chaque objet sur le writer passé en paramètre, sans concaténation préalable de petits bouts de chaine. Parce que toString() a toutes les chances de d'impliquer des +.
La classique hiérarchie des expressions dans un compilateur est le cas typique : le toString() des opérateurs binaires est :
Code :
|
ce qui veut dire pour mettre "(((3+4)*5)/6)" dans un fichier, la création de 3 StringBuffers.
avec writeOn() :
Code :
|
plouf, ça va direct au bon endroit.
Pour retrouver la facilité de debuggage d'une contaténation de Strings, voici la recette que j'ai appliquée récement :
Code :
|
Marsh Posté le 30-04-2004 à 18:58:23
pour ca, plutot que d'utiliser un StringWritter, tu as aussi le CharArrayWriter avec sa méthode writeTo(Writer out) qui permet de deverser son contenu dans au autre Writer.
Perso, j'ai aussi développé une classe super pratique (je ne comprend pas que je l'ai jamais vu ailleur) : C'est un "buffer" qui fait à la fois OutputStream et InputStream tu peux écrire dedans (il hérite de OutputStream) et au moment où tu veux lire ce qui est dedans, tu peux en tirer un Inputstream qui se sert du même buffer que le outputstream => pas de recopie inutiles. Tu peux continuer à écrire dedans et récupérer d'autres inputstream plus tard ...
Il y aurait moyen de faire la même chose avec des writer/reader avec un tableau de char en interne pour optimiser lputot que de passer par les convertisseur flux binaire <--> flux de char
Marsh Posté le 19-07-2004 à 17:22:42
petit problème :
je récupère dans une variable un truc comme ca :
c:\toto\prout\bidule\monjeu.jad (tel quel)
hors c la merde, le \t me fout des tabs...
je voudrais donc faire un replaceAll("\\\\","\\\\" ) pour remplacer les \ en \\, mais ca marche pas...
que faire ?
sic :
String tata = titi.getAbsolutePath().replaceAll("\\\\","\\\\\\\\" ); marche....
Marsh Posté le 19-07-2004 à 18:36:47
Jubijub > utiliser la classe File qui est faite pour manipuler des noms de fichier justement ?
Marsh Posté le 19-07-2004 à 20:49:26
oula...
je dois construire une ligne de commande qui donne un truc genre (ca donne l'idée) (c laid, c pas cross platform, chef de projet insisde):
cd d:\java\j2me\motorola\wtk1\bin && emulator.exe -xprefs:T720 -xdescriptor:c:\projet\test\jeudebile.jad...
sachant que je choppe ces paramètres de partout dans mon appli...et que je parse le rep de l'ému pour sortir le chemin de l'exe...
m'enfin ca ca marche...c le passage laid de mon stage : je modifie du code jbuilder laid fait par un gars qui pane rien à l'objet (mon prédecesseur), et moi, humble débutant stagiaire, je dois tout modifier...
l'ancien sys d'exécution crée un batch en remplissant les lignes...le truc laid à en mourrir...mon chef de projet a voulu qu'on passe par des processus....
voilà
Marsh Posté le 19-07-2004 à 21:57:25
euh, non c'est dans ta variable tu recuperes un tab et pas un '\\' suivi d'un 't'.
Marsh Posté le 19-07-2004 à 22:08:20
ben tu dis que tu recuperes une chaine qui contient "blabla\tblabla" , si c'est le cas, y'a pas de tab là dedans; je veux dire que je vois pas bien ou est le probleme
Marsh Posté le 19-07-2004 à 23:15:37
ben si la chaine est :
c:\toto
elle contient \t, qui est le caractère d'échapement du tab...
donc si tu la manipule directement sans la retoucher, Java échappe le t, et ca donne :
c: toto
alors que si auparavant tu neutralises les \ en les doublant (\\) et ben ca marche...saulement pour les remplacer, faut aussi échaper ceux que tu mets...donc au final pour avoir 2 \\, g du foutre \\\\\\\\
Marsh Posté le 19-07-2004 à 23:31:38
Jubijub a écrit : ben si la chaine est : |
le COMPILO remplace les \t par des tabs dans les chaînes constantes littérales. A l'exécution, si tu récupères un \t au cours d'un parsing de XML par exemple, rien ne sera remplacé.
Marsh Posté le 19-07-2004 à 23:36:20
Jubijub a écrit : ben si la chaine est : |
ben non.
Marsh Posté le 20-07-2004 à 09:53:05
alors un chemin est une constante litérale :
Code :
|
Output :
c:\bidouille\Test\ oto.txt
bilan, ca revient au même...
Marsh Posté le 20-07-2004 à 10:20:36
moi tout ce qui m'intéressait ct d'éviter ce comportement...ct ma question, et g trouvé la réponse...
si g compris ta remarque, la String dans le sys.out.println est un littéral, et donc est convertie par le compilo, ce qui occasionne le \t ...
le fait est qu'en aggrégeant les chemins pour construire ma ligne de commande, si je remplace pas les \ par des \\, les \ disparaissent....ca me faisait le coup pour le jad, à la fin la ligne faisait :
blablabla -Xdescriptor:c:bidouilletestfichier.jad
et ca ct pas en faisait un sys.out.println, mais en le sortant dans un exec()
Marsh Posté le 20-07-2004 à 10:23:46
tu es *vraiment* lourd !
Citation : 3.10.5 String Literals |
Marsh Posté le 20-07-2004 à 10:30:33
Je dois etre très con mais je vois pas en quoi ca concerne mon pb...
Code :
|
Si je remplace pas les \, g des merdes de partout...
Marsh Posté le 20-07-2004 à 11:49:22
nraynaud a écrit : bon, moi j'abandonne. |
+1
Marsh Posté le 28-03-2004 à 05:51:54
Les String en java ont une implémentation assez particulière, dont voici le détail.
Partageons mes frères
Les chaînes sont immuables, une fois la chaîne construite il n'est plus possible de la modifier, il n'existe d'ailleur pas de méthodes dans la classe String pour ça. On ne peut donc que créer une nouvelle chaine à partir de l'ancienne (par exemple avec substring()). Ceci est fait ainsi pour des raisons de performances, en particulier dans un contexte multithread.
La structure en mémoire des chaînes est la suivante :
on a un tout petit objet (4 mots mémoire) de la classe String qui pointe vers un tableau de char. Dans le petit objet de la classe String on a les champs suivants :
On passe rapidement sur le hash : comme il est très fréquement accédé sur les chaînes, ça vaut le coup de faire un cache (on parcourt ainsi qu'une seule fois le tableau de chars pour le calculer).
Le champ value est le tableau des caratères de la chaîne.
Le champ offset est l'index du premier caractère de la chaîne dans le tableau value.
Le champ count est le nombre de caractères dans la chaîne (on a toujours value.length >= count + offset).
Ne me demandez pas pourquoi les 3 premiers champs ne sont pas "final", je m'en étonne moi-même.
La classe String est aussi munie d'un constructeur très utile, visible uniquement dans son package :
Toute la feinte consiste à partager au maximum les tableaux de char entre plusieurs chaînes, ainsi faire un substring c'est allouer un objet de 4 mots mémoire, et rien de plus, surtout pas faire une recopie de chaîne :
On alloue un nouveau petit objet et on partage le tableau avec l'ancienne chaîne.
JLS
Le lendemain du grand soir
Les StringBuffer partagent eux-aussi leur tableau de char avec les strings qu'ils génèrent par toString() !
Comment font-ils puisque les StringBuffer sont modifiables, eux ?
Ils possèdent une variable booleenne nommée shared qui dit si leur buffer est partagé avec des sous-chaînes ou non. Lorsqu'on extrait pour la première fois une chaîne du buffer, on met shared à true. Si une modification des caractères existants intervient quand shared est à true (par setChar() ou insert(), par exemple), on copie le buffer, on remet shared à false et on modifie la nouvelle copie (lassant du même coup l'ancien buffer aux chaînes qui se le partagent). On notera que l'opération la plus courante sur un StringBuffer, append() ne nécessite jamais de recopie.
La chute du communisme
Bon, partager c'est bien tout ça, mais si j'ai une chaîne de 10 caratères dont le value fait 35000 chars (venant par exemple d'un StringBuffer) et que toutes les autres chaînes partageant le value sont morte, ça bouffe pas loin de 35ko de trop. C'est pour ça qu'il existe le constructeur String(String), qui crée une nouvelle chaîne en copiant la partie utile du value et c'est son seul rôle bah oui, les chaîne sont immuables, pas de risque de bousiller une instance, donc pas besoin de les copier. Autant vous dire que ce constructeur est très rarement utilisé, et dans des cas bien spécifiques.
Internalisation
Il existe une fonctionalité assez particulière des chaînes qui est l'internalisation. La spec de java précise que toutes les chaînes littérales représentant la même séquence de caratères sont identiques (l'opérateur == renvoie true). Comme la JVM ne peut pas savoir à l'avance si 2 classes ne contiennent pas la même chaine littérale dans leur code source (les classes sont chargées au fil des besoins), le système maintient un pool de chaînes uniques qu'il utilise pour ce besoin. Ce pool est accessible dans votre programme, par la méthode intern() de la classe String, qui vous renvoie la chaîne du système représentant la même séquence de caractère que votre chaîne, les opérations de comparaison s'en trouveront fortement accélérées.
JLS
Afficher ses convictions
Une des rares méthodes de la classe Object est toString() sensée convertir l'objet en String, selon une méthode par défaut choisie par l'utilisateur. Elle est utilisée en débuggage et par les afficheurs par défaut des interfaces homme-machine. Il est donc important que chaque classe la re-définisse suivant ses besoins.
JLS
Petite leçon d'universalisme
En java, si l'un des arguments de + est une String, l'opérateur devient un opérateur universel de concaténations de chaînes. Son autre argument est d'abord converti en chaîne selon la méthode suivante :
- null est converti en "null"
- sur les objets on appelle toString(), si ça renvoit null, on utilise "null".
- les types primitif sont boxés (int val -> Integer(val)) et on appelle toString() dessus.
Ensuite, les 2 arguments sont concaténés dans une nouvelle chaîne (ce n'est donc pas un opérateur commutatif).
Une utilisation non-judicieuse de + entraîne une pénalité sur l'allocateur de mémoire qui peut être importante. C'est pourquoi il vaut mieux utiliser un StringBuffer quand on commence à concaténer dans plusieurs instructions.
Il est à noter que les + d'une expression sont tous poussés dans un grand StringBuffer, sans surcoût pour l'allocateur (mais pas entre les instructions) :
est équivalent à :
Notons que v1+= v2 est équivalent à v1 = v1 + v2. Mais que c'est mal (because création de chaînes intermédiares). On remplaçera donc systématiquement (je n'ai encore jamais vu de cas où ça ne valait pas le coup) les += par un StringBuffer explicite.
Il peut être rentable dans certains cas de recréer la fonction printOn(Writer w) de smalltalk pour certaines hiérarchies d'objets qui devronts s'écrire sur un Writer (la classe StringWriter fait le lien entre StringBuffer et le système des Writers utilisé en entrée-sorties). Attention cependant à ne pas marcher sur les pieds de l'infrastructure de sérialisation mise en place avec l'interface Serializable.
JLS
Tips'n tricks
- utilisez String.valueOf(obj) au lieu de obj.toString() si obj peut être null
- var1 + var2 + var3 +var4 + " prout" est optimisé en StringBuffer par le compilateur (c'est même conseillé dans la norme), si tout est écrit dans la même expression.
- utilisez un StringBuffer pour le toString() de vos tableaux et collections (le concat() d'une String provoque obligatoirement une allocation suivie d'une recopie des tableaux de char).
- si vous allez faire beaucoup de equals() sur la même variable String, faites-le sur sa version internalisée (bon, en réalité, il doit y avoir un problème de conception si vous faites tout le temps le même code).
- Si vous avez une idée de se que vous allez mettre dans un StringBuffer, donnez-lui la taille estimée dès le début par StringBuffer(int length) (par défaut c'est 16, doublement à chaque fois que c'est trop petit) ; au lieu de, par exemple utiliser l'opérateur +.
- pensez au partage d'instances, n'aillez pas peur de découper vos chaînes, ça coûte pas grand'chose.
- n'utilisez pas += pour rallonger une chaîne.
Voiloù, j'espère que ces petits trucs vous seront utiles.
PS : comme la question est récurrente sur le forum, je reprécise : transformer une chaîne représentant une date en objet de type Date se fait avec un SimpleDateFormat.
Message édité par nraynaud le 28-03-2005 à 17:04:41
---------------
trainoo.com, c'est fini