什么时候在Ruby中使用符号而不是string?

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

TL; DR

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

完整答案

不使用它们来dynamic生成标识符的唯一原因是由于内存问题。

这个问题是很常见的,因为许多编程语言没有符号,只有string,因此string也被用作代码中的标识符。 你应该担心什么符号的意思是 ,不仅当你应该使用符号 。 符号被认为是标识符。 如果你遵循这个理念,那么你很可能会做正确的事情。

符号和string的实现有几个不同之处。 关于符号最重要的是它们是不变的 。 这意味着他们永远不会改变自己的价值。 正因为如此,符号的实例化速度比string更快,而且比较两个符号的操作也更快。

符号是不可变的这一事实允许Ruby在每次引用符号时使用相同的对象,从而节省内存。 所以每当解释器读取:my_key它可以从内存中取出,而不是再次实例化它。 这比每次初始化一个新string都要less一些。

您可以使用Symbol.all_symbols命令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方法to_sym处理用户input数据,因为这些数据总是会改变的,所以在软件实例中将永远使用一部分内存。 Ruby 2.2引入了符号垃圾收集器 ,它可以释放dynamic生成的符号,所以dynamic创build符号所产生的内存泄漏不再成为问题。

回答你的问题:

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

如果你正在寻找的是在你的代码内部使用的标识符,你应该使用符号。 如果你正在打印输出,你应该使用string,即使它出现多次,甚至在内存中分配两个不同的对象。

这是推理:

  1. 打印符号会比打印string慢,因为它们被转换为string。
  2. 有很多不同的符号将增加您的应用程序的整体内存使用率,因为他们从来没有被释放。 而且你从来没有同时使用你的代码中的所有string。

用例由@AlanDert

@AlanDert:如果我在haml代码中使用了多次类似%input {type::checkbox}的东西,我应该怎样用作checkbox?

我可以。

@AlanDert:但是要在html页面上打印出一个符号,它应该被转换为string,不是吗? 那么使用它的意义何在?

什么是input的types? 您要使用的inputtypes的标识符或您想要显示给用户的东西?

确实,它会在某个时候变成HTML代码,但是现在你正在编写代码的那一行,这意味着它是一个标识符 – 它确定了你需要什么types的input字段。 因此,它在代码中反复使用,并且始终与标识符具有相同的“string”字符,并且不会生成内存泄漏。

这就是说,为什么我们不评估数据,看看string是否更快?

这是我为此创build的简单基准:

 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实际上比使用string要快一点。 这是为什么? 这取决于HAML的实施方式。 我需要对HAML代码进行一些修改才能看到,但是如果在标识符的概念中继续使用符号,那么您的应用程序将更快更可靠。 当问题发生时,将其作为基准并获得答案。

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

这是一个很好的string与符号基准我在codecademyfind:

 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. 
  1. Ruby符号是一个O(1)比较的对象

为了比较两个string,我们可能需要查看每个字符。 对于长度为N的两个string,这将需要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符号是自由forms枚举中的标签

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

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

但是因为Ruby是一种dynamic语言,所以我们不用担心声明一个BugStatustypes,或者追踪合法的值。 相反,我们将枚举值表示为符号:

 original_status = :open current_status = :closed 

3. Ruby符号是一个常量,唯一的名字

在Ruby中,我们可以改变一个string的内容:

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

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

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

将关键字parameter passing给Ruby函数时,我们使用符号指定关键字:

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

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

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