Pourquoi ajouter «» à une chaîne économise-t-il de la mémoire?

193

J'ai utilisé une variable contenant beaucoup de données, par exemple String data. Je voulais utiliser une petite partie de cette chaîne de la manière suivante:

this.smallpart = data.substring(12,18);

Après quelques heures de débogage (avec un visualiseur de mémoire), j'ai découvert que le champ objets se smallpartsouvenait de toutes les données data, bien qu'il ne contienne que la sous-chaîne.

Quand j'ai changé le code en:

this.smallpart = data.substring(12,18)+""; 

..le problème a été résolu! Maintenant, mon application utilise très peu de mémoire maintenant!

Comment est-ce possible? Quelqu'un peut-il expliquer cela? Je pense que this.smallpart a continué à faire référence aux données, mais pourquoi?

MISE À JOUR: Comment puis-je effacer la grosse chaîne alors? Est-ce que data = new String (data.substring (0,100)) fera l'affaire?

hsmit
la source
En savoir plus sur votre intention ultime ci-dessous: D'où vient la grosse chaîne en premier lieu? Si vous lisez à partir d'un fichier ou d'une base de données CLOB ou quelque chose, lire uniquement ce dont vous avez besoin pendant l'analyse sera optimal tout autour.
PSpeed
4
Incroyable ... Je travaille en java depuis plus de 4 à 5 ans, c'est encore nouveau pour moi :). merci pour l'info bro.
Part
1
Il y a une subtilité à utiliser new String(String); voir stackoverflow.com/a/390854/8946 .
Lawrence Dol

Réponses:

159

Procédez comme suit:

data.substring(x, y) + ""

crée un nouvel objet String (plus petit) et supprime la référence à la chaîne créée par substring (), permettant ainsi la récupération de place de celui-ci.

La chose importante à réaliser est que cela substring()donne une fenêtre sur une chaîne existante - ou plutôt, le tableau de caractères sous-jacent à la chaîne d'origine. Par conséquent, il consommera la même mémoire que la chaîne d'origine. Cela peut être avantageux dans certaines circonstances, mais problématique si vous souhaitez obtenir une sous-chaîne et supprimer la chaîne d'origine (comme vous l'avez découvert).

Jetez un œil à la méthode substring () dans la source JDK String pour plus d'informations.

EDIT: Pour répondre à votre question supplémentaire, la construction d'une nouvelle chaîne à partir de la sous-chaîne réduira votre consommation de mémoire, à condition de supprimer toutes les références à la chaîne d'origine.

NOTE (janvier 2013). Le comportement ci-dessus a changé dans Java 7u6 . Le modèle de poids mouche n'est plus utilisé et substring()fonctionnera comme prévu.

Brian Agnew
la source
89
C'est l'un des très rares cas où le String(String)constructeur (c'est-à-dire le constructeur String prenant une chaîne en entrée) est utile: new String(data.substring(x, y))fait effectivement la même chose que l'ajout "", mais cela rend l'intention quelque peu plus claire.
Joachim Sauer
3
juste pour être précis, la sous-chaîne utilise l' valueattribut de la chaîne d'origine. Je pense que c'est pourquoi la référence est conservée.
Valentin Rocher
@Bishiboosh - oui, c'est vrai. Je ne voulais pas exposer les particularités de la mise en œuvre, mais c'est précisément ce qui se passe.
Brian Agnew
5
Techniquement, c'est un détail d'implémentation. Mais c'est quand même frustrant et attrape beaucoup de monde.
Brian Agnew
1
Je me demande s'il est possible d'optimiser cela dans le JDK en utilisant des références faibles ou autres. Si je suis la dernière personne à avoir besoin de ce caractère [], et que je n'en ai besoin que d'un peu, créez un nouveau tableau que j'utiliserai en interne.
WW.
28

Si vous regardez la source de substring(int, int), vous verrez qu'elle renvoie:

new String(offset + beginIndex, endIndex - beginIndex, value);

valueest l'original char[]. Vous obtenez donc une nouvelle chaîne, mais avec le même sous-jacent char[].

Lorsque vous le faites, data.substring() + ""vous obtenez une nouvelle chaîne avec un nouveau sous-jacent char[].

En fait, votre cas d'utilisation est la seule situation où vous devez utiliser le String(String)constructeur:

String tiny = new String(huge.substring(12,18));
Pascal Thivent
la source
1
Il y a une subtilité à utiliser new String(String); voir stackoverflow.com/a/390854/8946 .
Lawrence Dol
17

Lorsque vous utilisez substring, il ne crée pas réellement de nouvelle chaîne. Il fait toujours référence à votre chaîne d'origine, avec une contrainte de décalage et de taille.

Donc, pour permettre à votre chaîne d'origine d'être collectée, vous devez créer une nouvelle chaîne (en utilisant new Stringou ce que vous avez).

Chris Jester-Young
la source
5

Je pense que this.smallpart a continué à faire référence aux données, mais pourquoi?

Parce que les chaînes Java sont constituées d'un tableau de caractères, d'un décalage de début et d'une longueur (et d'un hashCode mis en cache). Certaines opérations String, telles que la substring()création d'un nouvel objet String, partagent le tableau de caractères d'origine et ont simplement des champs de décalage et / ou de longueur différents. Cela fonctionne car le tableau de caractères d'une chaîne n'est jamais modifié une fois qu'il a été créé.

Cela peut économiser de la mémoire lorsque de nombreuses sous-chaînes font référence à la même chaîne de base sans répliquer les parties qui se chevauchent. Comme vous l'avez remarqué, dans certaines situations, il peut empêcher les données inutiles d'être récupérées.

La façon "correcte" de résoudre ce problème est le new String(String)constructeur, c'est-à-dire

this.smallpart = new String(data.substring(12,18));

BTW, la meilleure solution globale serait d'éviter d'avoir de très grandes chaînes en premier lieu, et de traiter toute entrée en petits morceaux, aa quelques Ko à la fois.

Michael Borgwardt
la source
Il y a une subtilité à utiliser new String(String); voir stackoverflow.com/a/390854/8946 .
Lawrence Dol
5

En Java, les chaînes sont des objets immuables et une fois qu'une chaîne est créée, elle reste en mémoire jusqu'à ce qu'elle soit nettoyée par le ramasse-miettes (et ce nettoyage n'est pas quelque chose que vous pouvez tenir pour acquis).

Lorsque vous appelez la méthode de sous-chaîne, Java ne crée pas de nouvelle chaîne, mais stocke simplement une plage de caractères à l'intérieur de la chaîne d'origine.

Ainsi, lorsque vous avez créé une nouvelle chaîne avec ce code:

this.smallpart = data.substring(12, 18) + ""; 

vous avez en fait créé une nouvelle chaîne lorsque vous avez concaténé le résultat avec la chaîne vide. C'est pourquoi.

Kico Lobo
la source
3

Tel que documenté par jwz en 1997 :

Si vous avez une énorme chaîne, retirez-en une sous-chaîne (), accrochez-vous à la sous-chaîne et permettez à la chaîne plus longue de devenir une ordure (en d'autres termes, la sous-chaîne a une durée de vie plus longue) une façon.

Ken
la source
2

Pour résumer, si vous créez beaucoup de sous-chaînes à partir d'un petit nombre de grandes chaînes, utilisez

   String subtring = string.substring(5,23)

Puisque vous n'utilisez que l'espace pour stocker les grosses chaînes, mais si vous extrayez une poignée de petites chaînes, à partir de pertes de grosses chaînes, alors

   String substring = new String(string.substring(5,23));

Gardera votre mémoire utilisée, car les grosses chaînes peuvent être récupérées lorsqu'elles ne sont plus nécessaires.

Ce que vous appelez new Stringest un rappel utile que vous obtenez vraiment une nouvelle chaîne, plutôt qu'une référence à l'original.

mdma
la source
Il y a une subtilité à utiliser new String(String); voir stackoverflow.com/a/390854/8946 .
Lawrence Dol
2

Tout d'abord, l' appel java.lang.String.substringcrée une nouvelle fenêtre sur l'originalString avec l'utilisation du décalage et de la longueur au lieu de copier la partie importante du tableau sous-jacent.

Si nous regardons de plus près la substringméthode, nous remarquerons un appel de constructeur de chaîneString(int, int, char[]) et le passerons ensemble char[]qui représente la chaîne . Cela signifie que la sous - chaîne occupera autant de mémoire que la chaîne d' origine .

D'accord, mais pourquoi se + ""traduit par une demande de moins de mémoire que sans elle ??

Faire un +on stringsest implémenté via l' StringBuilder.appendappel de méthode. Regarder l'implémentation de cette méthode en AbstractStringBuilderclasse nous dira qu'elle fait finalement arraycopyavec la partie dont nous avons vraiment besoin (la substring).

Toute autre solution de contournement ??

this.smallpart = new String(data.substring(12,18));
this.smallpart = data.substring(12,18).intern();
laika
la source
0

Ajouter "" à une chaîne économisera parfois de la mémoire.

Disons que j'ai une énorme chaîne contenant un livre entier, un million de caractères.

Ensuite, je crée 20 chaînes contenant les chapitres du livre comme sous-chaînes.

Ensuite, je crée 1000 chaînes contenant tous les paragraphes.

Ensuite, je crée 10 000 chaînes contenant toutes les phrases.

Ensuite, je crée 100 000 chaînes contenant tous les mots.

Je n'utilise toujours que 1 000 000 de caractères. Si vous ajoutez "" à chaque chapitre, paragraphe, phrase et mot, vous utilisez 5 000 000 de caractères.

Bien sûr, c'est complètement différent si vous n'extrayez qu'un seul mot de tout le livre, et que tout le livre peut être récupéré, mais ce n'est pas parce que ce mot contient une référence.

Et c'est encore différent si vous avez une chaîne d'un million de caractères et supprimez les tabulations et les espaces aux deux extrémités, faisant dire 10 appels pour créer une sous-chaîne. La façon dont Java fonctionne ou fonctionne évite de copier un million de caractères à chaque fois. Il y a un compromis, et c'est bien si vous savez quels sont les compromis.

gnasher729
la source