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

时间:2013-05-18 05:35:04

标签: ruby symbols

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

5 个答案:

答案 0 :(得分:166)

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引入了symbol garbage collector,它释放了动态生成的符号,因此动态创建符号所产生的内存泄漏不再是一个问题。

回答你的问题:

  

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

如果您要查找的是在代码内部使用的标识符,则应使用符号。如果你正在打印输出,你应该使用字符串,即使它出现不止一次,甚至在内存中分配两个不同的对象。

以下是推理:

  1. 打印符号将比打印字符串慢,因为它们被转换为字符串。
  2. 拥有大量不同的符号会增加应用程序的整体内存使用量,因为它们永远不会被释放。并且您永远不会同时使用代码中的所有字符串。
  3. @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代码上查看一下,但是如果你继续在标识符的概念中使用符号,那么你的应用程序将更快更可靠。当问题发生时,对其进行基准测试并获得答案。

答案 1 :(得分:13)

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

答案 2 :(得分:8)

  1. Ruby符号是具有O(1)比较的对象
  2. 要比较两个字符串,我们可能需要查看每个字符。对于两个长度为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符号是自由格式枚举中的标签
    2. 在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符号是关键字参数
      2. 的关键字

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

        # Build a URL for 'bug' using Rails.
        url_for :controller => 'bug',
                :action => 'show',
                :id => bug.id
        
        1. Ruby符号是散列键的最佳选择
        2. 通常,我们使用符号来表示哈希表的键:

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

答案 3 :(得分: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."

输出结果为:

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

答案 4 :(得分:0)

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

    {key: "value"}

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

     def write(file:, data:, mode: "ascii")
          # removed for brevity
     end
     write(data: 123, file: "test.txt")
  • 冻结以字符串形式保存并节省内存

    label = 'My Label'.freeze