Question Pourquoi Ruby n'a-t-il pas un vrai StringBuffer ou StringIO?


J'ai récemment lu une belle poster en utilisant StringIO en rubis. Ce que l'auteur ne mentionne pas, c'est que StringIO est juste un "je" 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 comme celui de Java. StringBuffers a deux objectifs importants. Tout d'abord, ils vous permettent de tester la moitié de la production de Ruby's StringIO. 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 très très lent.

Y a-t-il un bon remplacement?

Il est vrai que les chaînes de Ruby sont modifiables, mais cela ne signifie pas que nous devons toujours compter sur cette fonctionnalité. Si stuff est grande, les performances et la mémoire requise, par exemple, sont vraiment mauvaises.

result = stuff.map(&:to_s).join(' ')

La manière "correcte" de faire ceci en Java est:

result = StringBuffer.new("")
for(String s : stuff) {
  result.append(s);
}

Bien que mon Java soit un peu rouillé.


45
2017-08-13 20:19


origine


Réponses:


J'ai regardé la documentation Ruby pour StringIO, et on dirait que ce que vous voulez est StringIO#string, ne pas StringIO#to_s

Ainsi, changez votre code pour:

s = StringIO.new
s << 'foo'
s << 'bar'
s.string

106
2017-08-13 20:41



Comme d'autres objets de type IO dans Ruby, lorsque vous écrivez dans un 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"

Une autre façon de peler ce chat.


30
2017-08-09 19:39



J'ai fait quelques repères et l’approche la plus rapide utilise 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éner des chaînes en utilisant le String#+ méthode est l'approche la plus lente par plusieurs ordres de grandeur:

>> 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)

Donc, je pense que la bonne réponse est que l'équivalent de Java StringBuffer utilise simplement String#<< en rubis.


18
2017-11-07 18:57



Votre exemple fonctionne dans Ruby - je l'ai juste essayé.

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"

Sauf si je manque la raison pour laquelle vous utilisez to_s - qui ne fait que sortir l'identifiant de l'objet.


11
2017-08-13 20:46



Eh bien, un StringBuffer n'est pas tout à fait aussi nécessaire dans Ruby, principalement parce que les chaînes de Ruby sont mutables ... vous pouvez donc créer une chaîne en modifiant la chaîne existante au lieu de construire de nouvelles chaînes avec chaque concat.

Comme note, vous pouvez également utiliser une syntaxe de chaîne spéciale où vous pouvez créer une chaîne de caractères qui référence d'autres variables dans la chaîne, ce qui rend la construction de chaînes très lisible. Considérer:

first = "Mike"
last = "Stone"
name = "#{first} #{last}"

Ces chaînes peuvent également contenir des expressions, pas seulement des variables telles que:

str = "The count will be: #{count + 1}"
count = count + 1

4
2017-08-13 20:31