Pourquoi Ruby n'a-t-il pas un vrai StringBuffer ou StringIO ?
Question
J'ai récemment lu un joli poste sur l'utilisation StringIO
en Rubis.Ce que l'auteur ne mentionne pas cependant, c'est que StringIO
est juste un "I." Il n'y a pas de "O." Toi ne peut pas faites ceci, par exemple :
s = StringIO.new
s << 'foo'
s << 'bar'
s.to_s
# => should be "foo\nbar"
# => really is ''`
Ruby a vraiment besoin d'un StringBuffer, tout comme celui de Java.Les StringBuffers remplissent deux objectifs importants.Premièrement, ils vous permettent de tester la moitié de la sortie de ce que fait StringIO de Ruby.Deuxièmement, ils sont utiles pour construire de longues chaînes à partir de petites pièces – ce que Joel nous rappelle encore et encore est par ailleurs très très lent.
Existe-t-il un bon remplacement ?
Il est vrai que les chaînes dans Ruby sont mutables, mais cela ne signifie pas que nous devons toujours compter sur cette fonctionnalité.Si stuff
est volumineux, les performances et les besoins en mémoire de celui-ci, par exemple, sont vraiment mauvais.
result = stuff.map(&:to_s).join(' ')
La façon "correcte" de procéder en Java est la suivante :
result = StringBuffer.new("")
for(String s : stuff) {
result.append(s);
}
Bien que mon Java soit un peu rouillé.
La solution
J'ai regardé la documentation Ruby pour StringIO
, et il semble que ce que tu veux soit StringIO#string
, pas StringIO#to_s
Changez donc votre code en :
s = StringIO.new
s << 'foo'
s << 'bar'
s.string
Autres conseils
Comme les autres objets de type IO dans Ruby, lorsque vous écrivez dans une IO, le pointeur de caractère avance.
>> s = StringIO.new
=> #<StringIO:0x3659d4>
>> s << 'foo'
=> #<StringIO:0x3659d4>
>> s << 'bar'
=> #<StringIO:0x3659d4>
>> s.pos
=> 6
>> s.rewind
=> 0
>> s.read
=> "foobar"
J'ai fait quelques benchmarks et l'approche la plus rapide consiste à utiliser le String#<<
méthode.En utilisant StringIO
est un peu plus lent.
s = ""; Benchmark.measure{5000000.times{s << "some string"}}
=> 3.620000 0.100000 3.720000 ( 3.970463)
>> s = StringIO.new; Benchmark.measure{5000000.times{s << "some string"}}
=> 4.730000 0.120000 4.850000 ( 5.329215)
Concaténation de chaînes à l'aide de String#+
La méthode est l’approche la plus lente à plusieurs niveaux :
s = ""; Benchmark.measure{10000.times{s = s + "some string"}}
=> 0.700000 0.560000 1.260000 ( 1.420272)
s = ""; Benchmark.measure{10000.times{s << "some string"}}
=> 0.000000 0.000000 0.000000 ( 0.005639)
Je pense donc que la bonne réponse est que l'équivalent de Java StringBuffer
utilise simplement String#<<
en Rubis.
Votre exemple fonctionne en Ruby - je viens de l'essayer.
irb(main):001:0> require 'stringio'
=> true
irb(main):002:0> s = StringIO.new
=> #<StringIO:0x2ced9a0>
irb(main):003:0> s << 'foo'
=> #<StringIO:0x2ced9a0>
irb(main):004:0> s << 'bar'
=> #<StringIO:0x2ced9a0>
irb(main):005:0> s.string
=> "foobar"
À moins que la raison pour laquelle vous utilisez to_s ne me manque, cela affiche simplement l'identifiant de l'objet.
Eh bien, un StringBuffer n'est pas aussi nécessaire dans Ruby, principalement parce que les chaînes dans Ruby sont mutables...ainsi, vous pouvez créer une chaîne en modifiant la chaîne existante au lieu de construire de nouvelles chaînes à chaque concat.
À noter que vous pouvez également utiliser une syntaxe de chaîne spéciale dans laquelle vous pouvez créer une chaîne qui fait référence à d'autres variables dans la chaîne, ce qui permet une construction de chaîne très lisible.Considérer:
first = "Mike"
last = "Stone"
name = "#{first} #{last}"
Ces chaînes peuvent également contenir des expressions, pas seulement des variables...tel que:
str = "The count will be: #{count + 1}"
count = count + 1