何时在Ruby中使用符号而不是字符串?

Ala*_*ano 94 ruby symbols

如果我的脚本中至少有两个相同字符串的实例,我应该使用符号吗?

fot*_*nus 169

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.
Run Code Online (Sandbox Code Playgroud)

对于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
Run Code Online (Sandbox Code Playgroud)

三项产出:

# first time
String: 5.14
Symbol: 5.07
#second
String: 5.29
Symbol: 5.050000000000001
#third
String: 4.7700000000000005
Symbol: 4.68
Run Code Online (Sandbox Code Playgroud)

所以使用smbols实际上比使用字符串快一点.这是为什么?这取决于HAML的实现方式.我需要在HAML代码上查看一下,但是如果你继续在标识符的概念中使用符号,那么你的应用程序将更快更可靠.当问题发生时,对其进行基准测试并获得答案.


Bor*_*cky 13

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

  • 你有一个观点,但不要回答所提出的问题.OP将字符串与符号混淆,仅仅告诉它是不同的东西是不够的 - 你应该帮助他理解它们的相似之处以及它们的不同之处 (5认同)
  • +1.符号和字符串是完全不同的东西.关于使用哪一个,确实没有任何混淆,*除非*他们被严格教导(即"一个符号只是一个不可变的字符串"的谬误). (4认同)

小智 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"
Run Code Online (Sandbox Code Playgroud)

但由于foo的每个外观都指向同一个对象,我们可以通过查看对象ID来比较符号.我们可以通过一次比较(计算机科学家称之为"O(1)时间")来做到这一点.

def symbol_comp sym1, sym2
  sym1.object_id == sym2.object_id
end
symbol_comp :foo, :foo
Run Code Online (Sandbox Code Playgroud)
  1. Ruby符号是自由形式枚举中的标签

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

enum BugStatus { OPEN, CLOSED };
BugStatus original_status = OPEN;
BugStatus current_status  = CLOSED;
Run Code Online (Sandbox Code Playgroud)

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

original_status = :open
current_status  = :closed
Run Code Online (Sandbox Code Playgroud)

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

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

"foo"[0] = ?b # "boo"
Run Code Online (Sandbox Code Playgroud)

但是我们无法改变符号的内容:

:foo[0]  = ?b # Raises an error
Run Code Online (Sandbox Code Playgroud)
  1. Ruby符号是关键字参数的关键字

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

# Build a URL for 'bug' using Rails.
url_for :controller => 'bug',
        :action => 'show',
        :id => bug.id
Run Code Online (Sandbox Code Playgroud)
  1. Ruby符号是散列键的绝佳选择

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

options = {}
options[:auto_save]     = true
options[:show_comments] = false
Run Code Online (Sandbox Code Playgroud)


小智 5

这是我在codecademy发现的一个很好的字符串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."
Run Code Online (Sandbox Code Playgroud)

输出是:

String time: 0.21983 seconds.
Symbol time: 0.087873 seconds.
Run Code Online (Sandbox Code Playgroud)

  • 让我们不要忘记这是十分之一秒的事实. (2认同)
  • 超过百万次迭代百分之一秒?如果这是您可以获得的最佳优化,我认为您的程序已经很好地优化了. (2认同)