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

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