8
votes

Pourquoi .NET crée-t-il de nouvelles sous-chaînes au lieu de pointer sur les chaînes existantes?

à partir d'un look bref à l'aide de réflecteur, il ressemble à string.substring () attribue la mémoire pour chaque sous-chaîne. Suis-je raison que c'est le cas? Je pensais que cela ne serait pas nécessaire car les cordes sont immuables.

Mon objectif sous-jacent était de créer un iEnumerable Split (cette chaîne, char) Méthode d'extension qui n'attribue aucune mémoire supplémentaire.


2 commentaires

Je n'y ai pas réfléchi très fort, ou j'ai regardé la mise en œuvre de StressBuilder avec un réflecteur, mais une méthode iEnumerable Split (Cette méthode StringBuilder, Char)?


Si string.substring () n'allouez pas une nouvelle mémoire, la chaîne ne sera pas immuable


5 Réponses :


2
votes

impossible sans piquer à l'intérieur .NET à l'aide de classes de chaîne. Vous devrez transmettre des références à un tableau qui était mutable et assurez-vous que personne ne vissait.

.NET créera une nouvelle chaîne chaque fois que vous le demandez. Une seule exception à ceci est des chaînes internées créées par le compilateur (et peut être effectuée par vous) qui sont placées en mémoire une fois, puis des pointeurs sont établis à la chaîne pour des raisons de mémoire et de performances.


0 commentaires

0
votes

Parce que les chaînes sont immuables dans .NET, chaque opération de chaîne qui entraîne un nouvel objet de chaîne allouera un nouveau bloc de mémoire pour le contenu de la chaîne.

En théorie, il pourrait être possible de réutiliser la mémoire lors de l'extraction d'une sous-chaîne, mais qui ferait la collecte des ordures très compliquée: que si la chaîne d'origine est collectée par une poubelle? Qu'arriverait la sous-chaîne qui partage un morceau de celui-ci?

Bien sûr, rien n'empêche l'équipe de BCL .NET pour modifier ce comportement dans les futures versions de .NET. Cela n'aurait aucun impact sur le code existant.


5 commentaires

La chaîne de Java le fait en fait de cette façon: les substrings sont simplement des pointeurs dans la chaîne d'origine. Toutefois, cela signifie également que lorsque vous prenez une sous-chaîne de 200 caractères d'une chaîne de 200 mib, la chaîne de 200 MIB se situera toujours en mémoire tant que la petite sous-chaîne n'est pas collectée à la poubelle.


Je pense que cela pourrait avoir une incidence sur le code existant étant donné qu'il est conçu autour de ce comportement. Si les gens supposent que la colline de la chaîne empêche d'être dupliquée et que ce comportement a été arrêté, cela pourrait entraîner des applications de travail pour s'arrêter avec des exceptions de mémoire.


Comment pouvez-vous concevoir autour de ce comportement? En raison de l'immuabilité des chaînes, il n'y a vraiment aucun moyen de créer un code qui briserait si la mise en œuvre interne de la classe de chaîne change.


Les opérations de chaîne .NET créent en effet de nouveaux objets à chaîne, mais ce n'est pas car les cordes sont immuables. En fait, c'est parce que les chaînes sont immuables que les opérations de chaîne pourraient réutiliser des objets de chaîne actuelle au lieu de créer de nouveaux.


Si C # a utilisé cette approche, il ne ferait aucune collecte des ordures différentes. La chaîne d'origine aurait de multiples références à celle-ci, et il ne serait donc pas prélevé que tous les sous-chaînes basées sur elle étaient également inaccessibles. Par conséquent, que dit Joey. Java a une sous-chaîne plus rapide, une utilisation potentiellement plus élevée de la mémoire et C # a une substration lente, une utilisation potentiellement plus efficace de la mémoire.



1
votes

Chaque chaîne doit avoir ses propres données de chaîne, avec la manière dont la classe de chaîne est implémentée.

Vous pouvez créer votre propre structure de sous-chaîne qui utilise une partie d'une chaîne: P>

public struct SubString {

   private string _str;
   private int _offset, _len;

   public SubString(string str, int offset, int len) {
      _str = str;
      _offset = offset;
      _len = len;
   }

   public int Length { get { return _len; } }

   public char this[int index] {
      get {
         if (index < 0 || index > len) throw new IndexOutOfRangeException();
         return _str[_offset + index];
      }
   }

   public void WriteToStringBuilder(StringBuilder s) {
      s.Write(_str, _offset, _len);
   }

   public override string ToString() {
      return _str.Substring(_offset, _len);
   }

}


2 commentaires

Qu'en est-il d'une sous-chaîne dans une autre sous-chaîne?


Oui, il est facile pour la structure de sous-chaîne de créer un autre qui fait partie de lui-même.



24
votes

une raison pour laquelle la plupart des langues avec des chaînes immuables créent de nouvelles sous-chaînes plutôt que de se référer à des chaînes existantes sont parce que cela interférera avec les ordures collectant ces chaînes plus tard.

Que se passe-t-il si une chaîne est utilisée pour sa sous-chaîne, mais la chaîne plus grande devient inaccessible (sauf par la sous-chaîne). La chaîne plus grande sera irrévicile, car cela invaliderait la sous-chaîne. Ce qui semblait être un bon moyen de sauvegarder la mémoire à court terme devient une fuite de mémoire à long terme.


2 commentaires

Je pensais que la raison principale était en matière d'algorithmes sur les cordes. Si vous pouvez supposer en toute sécurité qu'une chaîne ne changera jamais, vous pouvez transmettre des références en toute sécurité et elle est également intrinsèquement threadsafe. Je suppose que les liens avec la collecte des ordures aussi.


@Spence - c'est une raison de l'immuabilité. Ce n'est pas une raison pour éviter les tampons partagés entre les chaînes. Une fois que vous avez immutabilité et GC, vous pouvez facilement mettre en œuvre des tampons partagés dans les coulisses sans rompre la sécurité du fil ni les algorithmes existants.



0
votes

Ajout au point que les chaînes sont immuables, vous devriez être que l'extrait suivant générera plusieurs instances de chaîne en mémoire.

String s1 = "Hello", s2 = ", ", s3 = "World!";
String res = s1 + s2 + s3;


5 commentaires

Cela ressemble à quelque chose que les personnes compilatrices pourraient optimiser.


Ce n'est pas un problème avec le compilateur, c'est un choix fabriqué dans la conception de la langue. Java a les mêmes règles pour les chaînes. System.Text.StringBuilder est une bonne classe à utiliser qui simule les chaînes "mutables".


Mal - S1 + S2 + S3 est transformé en un seul appel à String.ConCat. C'est pourquoi il n'est pas préférable d'utiliser String.Format ou StringBuilder (qui sont à la fois relativement lents), pour un maximum de 4 chaînes. Regardez l'IL pour voir ce que le compilateur fait et utilise un profileur pour savoir ce qui fonctionne bien dans votre programme. Sinon, vous pourriez aussi bien dire "regarder, c'est une chaussure! Il a enlevé sa chaussure et c'est un signe que d'autres qui le suivraient devraient faire de même!" Veuillez poster des réponses factuelles au lieu de mythiques.


Le commentaire de Ian Boyd a raison (sauf que les gens du compilateur s'en occupent déjà en version 1.)


Selon la référence C # Langue, l'opérateur + sur une chaîne est défini comme suit: Opérateur de chaîne + (chaîne x, chaîne y); Opérateur de chaîne + (chaîne x, objet y); Opérateur de chaîne + (objet x, chaîne y); Bien que la mise en œuvre de l'opérateur puisse utiliser la méthode Concat, elle ne change pas le fait que + est un opérateur binaire; Par conséquent, S1 + S2 + S3 serait l'équivalent de string.concat (string.concat (S1, S2), S3) avec un nouvel objet à chaîne renvoyé pour chaque appel à concat ()