Ruby之symbol研究

來源:互聯網
上載者:User

       這是一個研究筆記,主要是為了向同好請教。除了這個開頭以外,沒有多餘的廢話,也就免了其他的客套。請大家不要抱怨可讀性不好。

1. 在一個名字或者字串前面加上冒號,得到一個symbol對象。還可以通過String#to_sym、Fixnum#to_sym和String#intern得到。

2. 一般用symbol做hash的key,號稱是為了節省記憶體,提高執行效率。

3. 為什麼可以節省記憶體?Ruby中的String是可變對象,這一點跟Java、C#、Python都不一樣。注意跟某些C++標準庫中的COW的basic_string<T>也不一樣。Ruby中每一個String都可以就地改變。可能是因為這個原因,Ruby中兩個內容相同的字串文本量實際上是兩個不同的對象。

    a = "hello"
    b = "hello"

    雖然倆字串內容都一樣,但是你比一下a和b,就知道a.object_id != b.object_id,它們指向的不是同一個對象。結果反而很像未經string pooling最佳化的C語言的行為。到底immutable好還是mutable好,或者還是貌似聰明的COW好,見仁見智了。不過Ruby的設計在把字串用作hash key的時候毛病就大了。比如你寫:

    h["ruby"].name   = "Ruby"
    h["ruby"].author = "matz"
    h["ruby"].birth_year = 1995

的時候,"ruby"這個字串動態產生了三次,佔用三倍記憶體。這就嚴重地浪費了記憶體。而用:ruby做為key,因為在整個運行過程中,Ruby runtime保證名為:ruby的symbol對象只有一個,所以就不用產生三個,節省記憶體。

4. 為什麼可以提高執行效率?顯然的原因是免得多次動態產生'ruby'字串了。還不單如此,Hash的key值應該是常量,所以Ruby的Hash對於作為key的String對象都要施加保護,所謂保護,也就是把String凍結了,免得你之後還改變其值。保護當然是有代價的,symbol無需保護,當然是能提高效率的。附帶說明,其他mutable的對象也可以作為hash的key,這是Ruby設計得比較奇怪的地方。在irb裡運行以下代碼,你會發現Ruby的Hash丟值。

    h = Hash.new
    L = [1, 2]
    h[L] = "A big object!"
    L << 3 # 居然能改!
    h[L]   # ==> nil,找不到了,似乎正常
    # 可是
    h[[1, 2]]  # ==> nil,居然還是找不到
    # 看看keys
    h.keys     # ==> {[1, 2, 3]} 似乎還在裡面
    h[[1, 2, 3]] # ==> nil
    # 可是
    h          # ==> {[1, 2, 3]=>'A big object'},明明在這裡,就是找不到

    h.rehash  # ==> 這樣就會一切恢複正常。
   
    這一點上Python的設計要比較容易理解,list根本就是unhashable的,不能用來做hash的key。

        回過頭來在說提高效率的事。Symbol效率提高還有第三個原因,那是因為symbol本質上不比一個整數多出多少東西,用Symbol#to_i可以得到一個在整個程式中唯一的整數。Hash完全可以利用這個整數來產生hash值,那豈不是比根據字串內容去算hash值快得多?這還是小意思,既然這個整數是唯一的,那麼產生一個唯一的hash值也就是小菜一碟,要是能保證hash值唯一,那還是什麼hash表,根本就變成數組了。Hash表還可能會衝突,數組根本不會衝突,百分之百保證O(1),當然快。我沒看Ruby源碼,不知道是不是這麼處理的。

5. 為什麼Ruby runtime可以保證每一個symbol唯一?因為Ruby把symbol存放在運行時維護的一個符號表裡了,而這個符號表實際上是一個atom資料結構,其中儲存著當前所有的程式級的name,確保不出現內容相同的多個對象。幾乎每一個語言和系統都會有這樣一個符號表,只不過象C/C++那樣的語言,這個符號表只是在編譯時間存在,運行時就沒了。而Python、Ruby則在運行時也保留這張表備用。有這樣一個現成的資料結構幹嘛不用?

6. 但是這個表中存放的並不光是我們自己主動產生的symbols,還有Ruby解譯器對當前程式進行詞法分析、文法分析後存在其中的、當前程式的所有名字。這可是Ruby引擎用的東西啊,我們只要加上一個冒號,就讓自己的對象跟Ruby引擎內部使用的對象成鄰居了。所以String#intern這個方法叫做intern(內部化)。

   .NET Framework中String類也有一個Intern方法,意思是一樣一樣一樣的,在李建忠的經典譯本裡翻譯為“駐留”。

7. 可以用Symbol#all_symbols查看當前定義的全部symbol。可以體驗一下自己往符號表中塞一個對象的感覺,想想你寫的程式跟Ruby引擎能幹一樣的事情,應該還是挺爽的。

8. Python中用不著這個,因為字串是immutable的。放下有用沒用不說,有沒有辦法在Python中intern呢?我還沒找到辦法。有沒有Python牛知道?

     補充一下:查到了,Python中做這個事情的函數叫做 intern()。

9. 我覺得Ruby的這個設計是從Perl的glob中簡化而來的。Perl中可以用*a得到對應於符號a的glob,那是一個八爪魚一樣的怪物。Ruby也可以很容易的得到symbol table中的對象,不過沒有把symbol設計成八爪魚。

10. 還有一些小問題沒搞清楚,比如:name跟@name是什麼關係。attr_reader :name,實際上是給attr_reader方法傳了一個symbol作為參數,前者要通過這個symbol找到@name變數,是不是'@' + :name.id2name這麼簡單?大概可以去看看source了。

   

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.