¿Por qué Ruby no tiene un StringBuffer o StringIO real?
(5)
Recientemente leí una buena publicación sobre el uso de StringIO
en Ruby. Lo que el autor no menciona, sin embargo, es que StringIO
es solo un "yo". No hay "O". No puedes hacer esto, por ejemplo:
s = StringIO.new
s << ''foo''
s << ''bar''
s.to_s
# => should be "foo/nbar"
# => really is ''''`
Ruby realmente necesita un StringBuffer como el que tiene Java. StringBuffers tiene dos propósitos importantes. En primer lugar, le permiten probar la mitad de salida de lo que hace Ruby''s StringIO. En segundo lugar, son útiles para construir cadenas largas a partir de piezas pequeñas; algo que Joel nos recuerda una y otra vez es muy lento.
¿Hay un buen reemplazo?
Es cierto que las cadenas en Ruby son mutables, pero eso no significa que siempre debemos confiar en esa funcionalidad. Si las stuff
son grandes, los requisitos de rendimiento y memoria de esto, por ejemplo, son realmente malos.
result = stuff.map(&:to_s).join('' '')
La forma "correcta" de hacer esto en Java es:
result = StringBuffer.new("")
for(String s : stuff) {
result.append(s);
}
Aunque mi Java está un poco oxidada.
Bueno, un StringBuffer no es tan necesario en Ruby, principalmente porque las cadenas en Ruby son mutables ... así puedes construir una cadena modificando la cadena existente en lugar de construir nuevas cadenas con cada concat.
Como nota, también puede usar una sintaxis de cadena especial donde puede construir una cadena que haga referencia a otras variables dentro de la cadena, lo que hace que la construcción de la secuencia sea muy legible. Considerar:
first = "Mike"
last = "Stone"
name = "#{first} #{last}"
Estas cadenas también pueden contener expresiones, no solo variables ... como:
str = "The count will be: #{count + 1}"
count = count + 1
Tu ejemplo funciona en Ruby, lo probé.
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"
A menos que me falte la razón por la que está usando to_s, eso solo genera la id del objeto.
Al igual que otros objetos de tipo IO en Ruby, cuando escribe en un IO, el puntero del carácter avanza.
>> s = StringIO.new
=> #<StringIO:0x3659d4>
>> s << ''foo''
=> #<StringIO:0x3659d4>
>> s << ''bar''
=> #<StringIO:0x3659d4>
>> s.pos
=> 6
>> s.rewind
=> 0
>> s.read
=> "foobar"
Otra forma de despellejar a este gato.
Hice algunos puntos de referencia y el enfoque más rápido es usar el método String#<<
. Usar StringIO
es un poco más lento.
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)
La concatenación de cadenas utilizando el método String#+
es el enfoque más lento en muchos órdenes de magnitud:
>> 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)
Así que creo que la respuesta correcta es que el equivalente a StringBuffer
de Java simplemente está usando String#<<
en Ruby.
Miré la documentación de StringIO
para StringIO
, y parece que lo que quieres es StringIO#string
, no StringIO#to_s
Por lo tanto, cambie su código a:
s = StringIO.new
s << ''foo''
s << ''bar''
s.string