为什么 Ruby 没有真正的 StringBuffer 或 StringIO?

Why doesn't Ruby have a real StringBuffer or StringIO?

提问人:James A. Rosen 提问时间:8/14/2008 最后编辑:Ajedi32James A. Rosen 更新时间:4/20/2019 访问量:31369

问:

我最近读了一篇关于在 Ruby 中使用 Ruby 的好文章。然而,作者没有提到的是,这只是一个“我”。没有“O”。您不能这样做,例如:StringIOStringIO

s = StringIO.new
s << 'foo'
s << 'bar'
s.to_s
# => should be "foo\nbar"
# => really is ''`

Ruby 确实需要一个 StringBuffer,就像 Java 一样。StringBuffers 有两个重要用途。首先,它们让你测试 Ruby 的 StringIO 的输出一半。其次,它们对于从小零件建立长弦很有用——乔尔一遍又一遍地提醒我们,否则会非常非常缓慢。

有好的替代品吗?

Ruby 中的字符串确实是可变的,但这并不意味着我们应该始终依赖该功能。例如,如果很大,则对性能和内存的要求非常糟糕。stuff

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

在 Java 中执行此操作的“正确”方法是:

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

虽然我的Java有点生疏。

Ruby 字符串 IO 缓冲

评论

0赞 Dan Rosenstark 5/6/2009
“超级女仆?”从来没听说过她。我也从来没有真正相信过 StringBuffers,但我总是使用它们,因为害怕有人看到我的代码。但实际上,这些东西加起来了吗?
3赞 Stephen Eilert 8/31/2010
可能是“太空球”的参考。
2赞 Andrew Grimm 5/9/2011
超级女仆已被删除,作为摆脱亵渎的附带损害。
0赞 Theo 5/9/2011
字符串连接示例不等同于 Java 代码。正如你所提到的,Ruby 字符串是可变的,所以在 Ruby 中,你只需执行: .你可以放心地依赖 Ruby 字符串是可变的,它不会改变,因为它会破坏现有的每个 Ruby 应用程序。stuff.inject('') { |res, s| res << s.to_s }
1赞 hcarreras 7/17/2014
我真的不明白为什么 StringIO 没有to_s方法。这是一个管理字符串的类,所以如果你想要那个字符串,你必须专门要求它。它应该有一个to_s方法,因为它是 ruby 约定,但它没有。(如果我错了,有人可以纠正我)

答:

3赞 Mike Stone 8/14/2008 #1

好吧,StringBuffer 在 Ruby 中并不是那么必要,主要是因为 Ruby 中的字符串是可变的......因此,您可以通过修改现有字符串来构建字符串,而不是使用每个连接构造新字符串。

需要注意的是,您还可以使用特殊的字符串语法,您可以在其中构建一个字符串,该字符串引用字符串中的其他变量,这使得字符串构造具有非常强的可读性。考虑:

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

这些字符串还可以包含表达式,而不仅仅是变量......如:

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

评论

1赞 James A. Rosen 6/9/2009
这当然是正确的,它非常适合短插值。不过,对于构建像 HTML 页面这样的长字符串来说,这很糟糕。查看 en.wikipedia.org/wiki/Schlemiel_the_painter%27s_Algorithm
1赞 Earl Jenkins 11/3/2011
当然,但是对于构建 HTML 页面,为什么不使用为该功能构建的东西,例如 HAML 或 ERB?
1赞 CJ. 12/4/2013
re: “Schlemiel the Painter's algorithm” 如果您使用上面的 StringIO,则 Joel Spolsky 的批评不适用。StringIO 有一个像文件一样的搜索指针。无需每次都重新计算字符串的末尾。对于更长的字符串,您可以使用 %{ } 或像 Earl 建议的那样的库。
126赞 Mike Stone 8/14/2008 #2

我查看了 的 ruby 文档,看起来您想要的是 StringIO#string,而不是StringIOStringIO#to_s

因此,请将代码更改为:

s = StringIO.new
s << 'foo'
s << 'bar'
s.string
13赞 palmsey 8/14/2008 #3

您的示例在 Ruby 中有效 - 我刚刚尝试过。

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"

除非我错过了您使用 to_s 的原因 - 那只会输出对象 ID。

37赞 Colin Curtin 8/10/2009 #4

与 Ruby 中的其他 IO 类型对象一样,当您写入 IO 时,字符指针会前进。

>> s = StringIO.new
=> #<StringIO:0x3659d4>
>> s << 'foo'
=> #<StringIO:0x3659d4>
>> s << 'bar'
=> #<StringIO:0x3659d4>
>> s.pos
=> 6
>> s.rewind
=> 0
>> s.read
=> "foobar"

评论

0赞 James A. Rosen 8/10/2009
我真的不需要这个,因为有 ,但我总是喜欢知道不止一种方法来做某事。+1StringIO#read
0赞 James A. Rosen 8/10/2009
咳咳,我的意思是StringIO#string
26赞 jmanrubia 11/8/2012 #5

我做了一些基准测试,最快的方法是使用该方法。使用速度有点慢。String#<<StringIO

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)

使用该方法是最慢的方法,其速度要高出许多数量级:String#+

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)

所以我认为正确的答案是,与 Java 等价的只是在 Ruby 中使用。StringBufferString#<<

评论

1赞 Jared Beck 3/17/2014
请问这个基准测试使用了哪个版本的 ruby?
1赞 Nikkolasg 12/18/2014
哇。所以这应该是正确的答案,因为 String 是最快的。在 ruby 2.1.5 上测试,结果相同。
0赞 akostadinov 12/16/2016
当 Ruby 使字符串不可变时会发生什么?这样的微优化最终是地狱。
0赞 nothing-special-here 2/13/2017
如果要向很长的字符串添加一些字符,会发生什么情况?我认为那时会更快StringIO
0赞 KARASZI István 3/15/2020
字符串连接解决方案不适用于冻结的字符串文字,但有效。StringIO