這是一個(gè)研究筆記,主要是為了向同好請教。除了這個(gè)開(kāi)頭以外,沒(méi)有多余的廢話(huà),也就免了其他的客套。請大家不要抱怨可讀性不好。
1. 在一個(gè)名字或者字符串前面加上冒號,得到一個(gè)symbol對象。還可以通過(guò)String#to_sym、Fixnum#to_sym和String#intern得到。
2. 一般用symbol做hash的key,號稱(chēng)是為了節省內存,提高執行效率。
3. 為什么可以節省內存?Ruby中的String是可變對象,這一點(diǎn)跟Java、C#、Python都不一樣。注意跟某些C++標準庫中的COW的basic_string<T>也不一樣。Ruby中每一個(gè)String都可以就地改變??赡苁且驗檫@個(gè)原因,Ruby中兩個(gè)內容相同的字符串文本量實(shí)際上是兩個(gè)不同的對象。
a = "hello"
b = "hello"
雖然倆字符串內容都一樣,但是你比一下a和b,就知道a.object_id != b.object_id,它們指向的不是同一個(gè)對象。結果反而很像未經(jīng)string pooling優(yōu)化的C語(yǔ)言的行為。到底immutable好還是mutable好,或者還是貌似聰明的COW好,見(jiàn)仁見(jiàn)智了。不過(guò)Ruby的設計在把字符串用作hash key的時(shí)候毛病就大了。比如你寫(xiě):
h["ruby"].name = "Ruby"
h["ruby"].author = "matz"
h["ruby"].birth_year = 1995
的時(shí)候,"ruby"這個(gè)字符串動(dòng)態(tài)生成了三次,占用三倍內存。這就嚴重地浪費了內存。而用:ruby做為key,因為在整個(gè)運行過(guò)程中,Ruby runtime保證名為:ruby的symbol對象只有一個(gè),所以就不用生成三個(gè),節省內存。
4. 為什么可以提高執行效率?顯然的原因是免得多次動(dòng)態(tài)生成‘ruby‘字符串了。還不單如此,Hash的key值應該是常量,所以Ruby的Hash對于作為key的String對象都要施加保護,所謂保護,也就是把String凍結了,免得你之后還改變其值。保護當然是有代價(jià)的,symbol無(wú)需保護,當然是能提高效率的。附帶說(shuō)明,其他mutable的對象也可以作為hash的key,這是Ruby設計得比較奇怪的地方。在irb里運行以下代碼,你會(huì )發(fā)現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 # ==> 這樣就會(huì )一切恢復正常。
這一點(diǎn)上Python的設計要比較容易理解,list根本就是unhashable的,不能用來(lái)做hash的key。
回過(guò)頭來(lái)在說(shuō)提高效率的事。Symbol效率提高還有第三個(gè)原因,那是因為symbol本質(zhì)上不比一個(gè)整數多出多少東西,用Symbol#to_i可以得到一個(gè)在整個(gè)程序中唯一的整數。Hash完全可以利用這個(gè)整數來(lái)產(chǎn)生hash值,那豈不是比根據字符串內容去算hash值快得多?這還是小意思,既然這個(gè)整數是唯一的,那么產(chǎn)生一個(gè)唯一的hash值也就是小菜一碟,要是能保證hash值唯一,那還是什么hash表,根本就變成數組了。Hash表還可能會(huì )沖突,數組根本不會(huì )沖突,百分之百保證O(1),當然快。我沒(méi)看Ruby源碼,不知道是不是這么處理的。
5. 為什么Ruby runtime可以保證每一個(gè)symbol唯一?因為Ruby把symbol存放在運行時(shí)維護的一個(gè)符號表里了,而這個(gè)符號表實(shí)際上是一個(gè)atom數據結構,其中存儲著(zhù)當前所有的程序級的name,確保不出現內容相同的多個(gè)對象。幾乎每一個(gè)語(yǔ)言和系統都會(huì )有這樣一個(gè)符號表,只不過(guò)象C/C++那樣的語(yǔ)言,這個(gè)符號表只是在編譯時(shí)存在,運行時(shí)就沒(méi)了。而Python、Ruby則在運行時(shí)也保留這張表備用。有這樣一個(gè)現成的數據結構干嘛不用?
6. 但是這個(gè)表中存放的并不光是我們自己主動(dòng)生成的symbols,還有Ruby解釋器對當前程序進(jìn)行詞法分析、語(yǔ)法分析后存在其中的、當前程序的所有名字。這可是Ruby引擎用的東西啊,我們只要加上一個(gè)冒號,就讓自己的對象跟Ruby引擎內部使用的對象成鄰居了。所以String#intern這個(gè)方法叫做intern(內部化)。
.NET Framework中String類(lèi)也有一個(gè)Intern方法,意思是一樣一樣一樣的,在李建忠的經(jīng)典譯本里翻譯為“駐留”。
7. 可以用Symbol#all_symbols查看當前定義的全部symbol??梢泽w驗一下自己往符號表中塞一個(gè)對象的感覺(jué),想想你寫(xiě)的程序跟Ruby引擎能干一樣的事情,應該還是挺爽的。
8. Python中用不著(zhù)這個(gè),因為字符串是immutable的。放下有用沒(méi)用不說(shuō),有沒(méi)有辦法在Python中intern呢?我還沒(méi)找到辦法。有沒(méi)有Python牛知道?
補充一下:查到了,Python中做這個(gè)事情的函數叫做 intern()。
9. 我覺(jué)得Ruby的這個(gè)設計是從Perl的glob中簡(jiǎn)化而來(lái)的。Perl中可以用*a得到對應于符號a的glob,那是一個(gè)八爪魚(yú)一樣的怪物。Ruby也可以很容易的得到symbol table中的對象,不過(guò)沒(méi)有把symbol設計成八爪魚(yú)。
10. 還有一些小問(wèn)題沒(méi)搞清楚,比如:name跟@name是什么關(guān)系。attr_reader :name,實(shí)際上是給attr_reader方法傳了一個(gè)symbol作為參數,前者要通過(guò)這個(gè)symbol找到@name變量,是不是‘@‘ + :name.id2name這么簡(jiǎn)單?大概可以去看看source了。
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=1255966
IDG投資,直接引進(jìn)北美IT技術(shù)成立的培訓中心 已經(jīng)培養了5000多名學(xué)員,平均工資4000以上. | 熱點(diǎn)圖書(shū)連載、試讀 名人堂/書(shū)友會(huì )/特色書(shū)架 | 高薪?jīng)]有捷徑——唯有專(zhuān)業(yè)享受高薪 簽北京就業(yè)合同 |
| Csdn提供的廣告 | | Csdn Tag |

蛋蛋 發(fā)表于2006-09-21 00:39:00 IP: 199.246.40.*
孟老大猜錯了哈。符號這個(gè)概念是Matz從Lisp里引進(jìn)的??纯碈ommon Lisp的代碼,符號無(wú)處不在哈。:name和@name和name()的關(guān)系是由生成accessor的函數定的,當然不是‘◎’+:name.id2name那么簡(jiǎn)單。用attr_reader這個(gè)函數為例:你把:name這個(gè)符號傳給attr_reader這個(gè)函數。attr_reader根據:name生成name()這個(gè)函數(當然真正的實(shí)現有很多具體考慮):
def name()
@name
end
不信你可以寫(xiě)自己的attr_reader:
class T
def T.my_attr_reader(name)
class_eval <<-READER
def #{name}
@#{name};
end
READER
end
my_attr_reader :name
def set_name(name)
@name=name;
end
end
t = T.new
t.set_name(‘myan‘);
puts t.name
打印結果就是‘myan‘了。
再說(shuō)了,attr_reader()不一定只接受符號哈:
attr_reader :name
attr_reader "name"
attr_reader :name.to_s
attr_reader "name".to_sym
都一樣的。
思考題:寫(xiě)一個(gè)自己的attr_accessor()。
Symbol的意義不僅在于充當一個(gè)名字提高點(diǎn)性能,或者充當某個(gè)對象的標識符。Symbol對象代表代碼解析樹(shù)里的每個(gè)token。比如說(shuō)def foo; "foo"; end。除了"foo",每個(gè)token, def, foo, end什么的都有對應的符號。換句話(huà)說(shuō),符號為我們提供了進(jìn)入Ruby解析樹(shù)的大門(mén)。不定哪天我們就可以利用符號直接操作部分解析的Ruby代碼了。換句話(huà)說(shuō),我們可以超越各式eval, 進(jìn)入人見(jiàn)人耐的macro世界了。慢慢等吧。哈哈哈!
蛋蛋 發(fā)表于2006-09-21 00:49:00 IP: 199.246.40.*
P.S., 孟老大的學(xué)習還是比較仔細和全面的嘛。CSDN不少大嘴巴"專(zhuān)家"明明缺乏對編程語(yǔ)言的基本了解(比如開(kāi)篇就來(lái)什么被解釋的語(yǔ)言就是動(dòng)態(tài)語(yǔ)言一類(lèi)),卻酷耐指點(diǎn)江山,臧否語(yǔ)言,實(shí)在應該好好學(xué)學(xué)孟老大的踏實(shí)哈。CSDN辦個(gè)專(zhuān)題怎么樣?普及一下編程語(yǔ)言的基本知識,比如語(yǔ)義的重要性(設計語(yǔ)言總得知道自己要解決什么問(wèn)題吧?),常見(jiàn)的范式(太多老大言必OO,動(dòng)不動(dòng)就叫囂我的xxx是最好的),類(lèi)型(工業(yè)界和學(xué)術(shù)界的研究熱點(diǎn)哈),常見(jiàn)語(yǔ)言特性(免得universee給closure取了個(gè)別名就開(kāi)始吹噓自己的2B語(yǔ)言了),常見(jiàn)設計思路什么的。像云風(fēng)那樣認真讀過(guò)Programming Language Pragmatics這類(lèi)教材的高手還是應該不少的。程序語(yǔ)言要從娃娃抓起啊。
myan 發(fā)表于2006-09-21 07:39:00 IP: 221.218.165.*
to 蛋蛋:
謝謝你的指教。這樣看來(lái),attr_reader之類(lèi)的函數反而是主要接受字符串了。只不過(guò)"#{sym}"可以自動(dòng)對sym調用to_s轉型。