何时在Ruby中使用符号代替字符串?


Answers:


175

TL; DR

一个简单的经验法则是,每次需要内部标识符时都使用符号。对于Ruby <2.2,仅在符号不是动态生成时才使用符号,以避免内存泄漏。

完整答案

不将其用于动态生成的标识符的唯一原因是由于内存问题。

这个问题很常见,因为许多编程语言没有符号,只有字符串,因此字符串也被用作代码中的标识符。您应该担心符号的含义,不仅是在使用符号时。符号应作为标识符。如果您遵循这种理念,那么您很有可能会做正确的事。

符号和字符串的实现之间有几个区别。关于符号最重要的是它们是不可变的。这意味着它们永远不会改变其价值。因此,符号的实例化速度比字符串快,并且某些操作(如比较两个符号)也更快。

符号是不可变的这一事实使Ruby在每次引用该符号时都可以使用同一对象,从而节省了内存。因此,每次解释器读取:my_key它时,都可以从内存中获取它,而无需再次实例化它。这比每次初始化一个新字符串都便宜。

您可以获取已使用命令实例化的所有符号的列表Symbol.all_symbols

symbols_count = Symbol.all_symbols.count # all_symbols is an array with all 
                                         # instantiated symbols. 
a = :one
puts a.object_id
# prints 167778 

a = :two
puts a.object_id
# prints 167858

a = :one
puts a.object_id
# prints 167778 again - the same object_id from the first time!

puts Symbol.all_symbols.count - symbols_count
# prints 2, the two objects we created.

对于2.2之前的Ruby版本,一旦实例化一个符号,该内存将永远不会再释放。释放内存的唯一方法是重新启动应用程序。因此,符号使用不当也是导致内存泄漏的主要原因。生成内存泄漏的最简单方法是to_sym对用户输入数据使用此方法,因为此数据将始终更改,因此在软件实例中将永远使用新的内存部分。Ruby 2.2引入了符号垃圾收集器,它释放了动态生成的符号,因此不再需要动态创建符号而产生的内存泄漏。

回答您的问题:

如果我的应用程序或脚本中至少有两个相同的字符串,是否必须使用符号而不是字符串?

如果您要查找的是在代码内部使用的标识符,则应使用符号。如果要打印输出,即使字符串出现多次,也应使用字符串,即使在内存中分配了两个不同的对象。

原因如下:

  1. 打印符号比打印字符串要慢,因为它们被强制转换为字符串。
  2. 有很多不同的符号会增加应用程序的整体内存使用量,因为它们从未被释放。而且,您永远不会同时使用代码中的所有字符串。

用例@AlanDert

@AlanDert:如果我多次在haml代码中使用%input {type::checkbox}之类的东西,该怎么用作为复选框?

我可以。

@AlanDert:但是要在html页面上打印符号,应该将其转换为字符串,不是吗?那么使用它有什么意义呢?

输入的类型是什么?您要使用的输入类型或要显示给用户的输入内容的标识符?

的确,它有时会变成HTML代码,但是在您编写代码的那一刻,这意味着要成为一个标识符-它标识您需要哪种输入字段。因此,它会在您的代码中反复使用,并且始终具有与标识符相同的字符“字符串”,并且不会产生内存泄漏。

也就是说,为什么我们不评估数据以查看字符串是否更快?

这是我为此创建的一个简单基准:

require 'benchmark'
require 'haml'

str = Benchmark.measure do
  10_000.times do
    Haml::Engine.new('%input{type: "checkbox"}').render
  end
end.total

sym = Benchmark.measure do
  10_000.times do
    Haml::Engine.new('%input{type: :checkbox}').render
  end
end.total

puts "String: " + str.to_s
puts "Symbol: " + sym.to_s

三个输出:

# first time
String: 5.14
Symbol: 5.07
#second
String: 5.29
Symbol: 5.050000000000001
#third
String: 4.7700000000000005
Symbol: 4.68

因此,使用smbols实际上比使用字符串快一点。这是为什么?这取决于HAML的实现方式。我需要稍微了解一下HAML代码才能看到,但是如果您继续在标识符的概念中使用符号,则您的应用程序将更快,更可靠。当出现问题时,对其进行基准测试并获得答案。


@andrewcockerham您提供的链接无效(错误404)。您必须从链接中删除最后一个/(在之后strings)。那就是:www.reactive.io/tips/2009/01/11/the-difference-between-ruby-符号和串
阿图尔Khanduri

14

简而言之,符号是一个名称,由字符组成,但不可变。相反,字符串是字符的有序容器,允许更改其内容。


4
+1。符号和字符串是完全不同的东西。除非使用了不好的语言(例如,“符号只是不可变的字符串”的谬误),否则对于使用哪种字体确实没有任何困惑。
约尔格W¯¯米塔格

@JörgWMittag:是的。
鲍里斯·史提尼克

5
您有一个要点,但是不要回答提出的问题。OP混淆了字符串和符号,仅仅告诉它是不同的还不够-您应该帮助他理解它们的相似之处和不同之处
fotanus

1
@JörgWMittag似乎在整个网络上都在发生,除非您仔细阅读文档或有幸找到愿意照料解释事实的人。
sargas

8
  1. Ruby符号是具有O(1)比较的对象

为了比较两个字符串,我们可能需要查看每个字符。对于长度为N的两个字符串,这将需要进行N + 1比较(计算机科学家将其称为“ O(N)时间”)。

def string_comp str1, str2
  return false if str1.length != str2.length
  for i in 0...str1.length
    return false if str1[i] != str2[i]
  end
  return true
end
string_comp "foo", "foo"

但是,由于:foo的每个外观都指向同一对象,因此我们可以通过查看对象ID来比较符号。我们可以通过一次比较(计算机科学家将其称为“ O(1)时间”)来完成此操作。

def symbol_comp sym1, sym2
  sym1.object_id == sym2.object_id
end
symbol_comp :foo, :foo
  1. Ruby符号是自由格式枚举中的标签

在C ++中,我们可以使用“枚举”来表示相关常量的族:

enum BugStatus { OPEN, CLOSED };
BugStatus original_status = OPEN;
BugStatus current_status  = CLOSED;

但是因为Ruby是一种动态语言,所以我们不必担心声明BugStatus类型或跟踪合法值。相反,我们将枚举值表示为符号:

original_status = :open
current_status  = :closed

3 Ruby符号是一个不变的唯一名称

在Ruby中,我们可以更改字符串的内容:

"foo"[0] = ?b # "boo"

但是我们不能更改符号的内容:

:foo[0]  = ?b # Raises an error
  1. Ruby符号是关键字参数的关键字

在将关键字参数传递给Ruby函数时,我们使用符号指定关键字:

# Build a URL for 'bug' using Rails.
url_for :controller => 'bug',
        :action => 'show',
        :id => bug.id
  1. Ruby符号是哈希键的绝佳选择

通常,我们将使用符号来表示哈希表的键:

options = {}
options[:auto_save]     = true
options[:show_comments] = false

5

这是我在代码学院发现的一个不错的字符串vs符号基准测试:

require 'benchmark'

string_AZ = Hash[("a".."z").to_a.zip((1..26).to_a)]
symbol_AZ = Hash[(:a..:z).to_a.zip((1..26).to_a)]

string_time = Benchmark.realtime do
  1000_000.times { string_AZ["r"] }
end

symbol_time = Benchmark.realtime do
  1000_000.times { symbol_AZ[:r] }
end

puts "String time: #{string_time} seconds."
puts "Symbol time: #{symbol_time} seconds."

输出为:

String time: 0.21983 seconds.
Symbol time: 0.087873 seconds.

2
让我们不要忽略这是十分之一秒的事实。
Casey's

都是相对的。有时候很重要。
Yurii

2
一百万次迭代中的百分之一秒?我认为,如果这是为您提供的最佳优化,则您的程序已经进行了很好的优化。
Casey

0
  • 使用符号作为哈希键标识符

    {key: "value"}

  • 符号允许您以不同的顺序调用方法

     def write(文件:,数据:,模式:“ ascii”)
          #为简洁起见
     结束
     写入(数据:123,文件:“ test.txt”)
  • 冻结以字符串形式保存并节省内存

    label = 'My Label'.freeze

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.