談表示式樹的快取(2) 由表示式樹生成字串

2021-04-28 02:22:34 字數 3610 閱讀 4510

談到使用表示式樹作為key進行快取,您腦海中最早浮現出來的解決方案是什麼?老趙看來,大部分朋友的第一反應自然就是將作為key的表示式樹,使用一定規則生成乙個字串。簡而言之,這個生成字串的規則f需要能夠保證:

在同乙個快取空間內,同樣的表示式樹能夠生成相同的字串。

在同乙個快取空間內,不同的表示式樹生成不同的字串。

似乎有些羅嗦,朋友們明白便是。其中「在同乙個快取空間」的前提,其實只是放寬了後續要求的條件。因為在不同的快取空間內,即使不同的表示式樹生成了同樣的字串,它們也不會衝突;同理,不同快取空間內的相同表示式樹,也不一定非要得到相同的字串——事實上,不同的快取空間很可能對於字串有不同的要求,這一點強求不得。

的例子中,構建「getuserbyid_100」和「getuserbyname_jeffz」兩個字串一般已經足夠了。因為我們往往會將它們放在同乙個快取空間中(例如,userservice中的cache容器),而與其它的快取空間(例如adminservice)完全隔離。而如果打破了這個限制,那麼這樣的字串生成規則就不夠用了,我們就要設計新的字串規則(如userservice_getuserbyid_100)。

expression

> exp1 = i => i;

expression

> exp2 = i => i;

console.writeline(exp1.tostring());

console.writeline(exp2.tostring());

您會發現兩個明顯不同的表示式樹tostring後得到了同樣的內容。表示式樹的tostring方法是丟失資訊的。例如,如果表示式樹中涉及方法呼叫,那麼tostring也只會包含方法名,而無法表現出方法所屬的類,以及它的返回值。如果要把乙個表示式樹完整地生成字串,自然要用到expressionvisitor。我們這裡把轉化用的類稱為是******keybuilder:

public class 

******keybuilder : expressionvisitor

public string key }

private

stringbuilder m_builder;

}

visit方法的訪問是乙個遞迴的過程,其中會呼叫不同的visit***方法,而各visit***方法又會再次呼叫visit方法,最終遍歷整個表示式樹,而我們要做的,就是在遍歷時記錄足夠的資訊,使得兩個表示式樹「當且僅當」完全相同時才生成同樣的字串。嗯?「在同一快取空間內」不見了?沒錯,為了保證解決方案的通用性,我們在這裡假設快取區間只有乙個。

方便起見,我們先準備一些輔助方法,它們會將「各資訊」一一放入stringbuilder中:

protected virtual 

******keybuilder accept(int value)

protected virtual

******keybuilder accept(bool value)

protected virtual

******keybuilder accept(type type)

protected virtual

******keybuilder accept(memberinfo member)

return this.accept(member.declaringtype).accept(member.name);

}protected virtual

******keybuilder accept(object value)

然後再遍歷每個節點的時候,我們將資料不斷地推入:

protected override 

expression visit(expression exp)

protected override

expression visitbinary(binaryexpression b)

protected override

expression visitconstant(constantexpression c)

protected override

expression visitmemberaccess(memberexpression m)

...

完整的**不止如此,雖然我們不需要覆蓋(override)所有expressionvisitor的方法,但是一些必要的元素還是不可或缺的。不過,關鍵之處並不在這裡。假設我們的visit***方法已經能夠完整地描述各資料,但是那些accept方法足夠詳細嗎?答案是否定的,至少之前的**便有幾點問題:

在描述乙個type時,fullname提供的資訊完整嗎?是否需要assemblyqualifiedname?(這點請朋友們自行思考,或者一起討論一下)。

在描述乙個memberinfo時,難道只記錄它的declaringtype和name就夠了嗎?

在描述乙個object時,會使用tostring方法來進行記錄。

顯而易見,第三個問題是無法滿足要求的。因此,如果您需要在正式場合使用這個方法,就必須根據您自己的需求來修改一下這方面的問題——例如,使用serialize?亦或是,約定在此出現的每個相同型別的物件,它們的tostring方法都進行了合適地過載。

如果您的「嗅覺」比較靈敏,應該已經發現這個解決方案的缺點了:那就是字串會特別龐大。這點並非無法改進,例如您可以把一些重複的,占用資料量大的資訊替換成資料量小的資訊——其實就是傳統的進行資料壓縮的演算法。不過這方面程式設計相對較為複雜,且屬於優化階段而不能說明解決方案的真正思路,因此這就留給朋友們作為練習吧。

如果您感興趣的話,還可以看一下http://code.msdn.microsoft.com/exprserialization

,它提供了表示式樹的完整的序列化功能,它可以把乙個表示式樹物件與xml進行雙向轉化。不過其字串體積也無可避免的龐大,誰讓表示式樹天生就那麼複雜呢?

當然,如之前所說,key的生成規則與快取的劃分密切相關。換句話說,如果您能在專案裡對快取空間進行適當的劃分,那麼在這樣的前提下您也可以使用「不那麼詳細」的生成規則,這有可能會進一步壓縮字串的體積。

實現了******keybuilder,那麼******keycache的編寫自然易如反掌,不加贅述:

public class 

******keycache

: iexpressioncache

where t : class

}finally

this.m_rwlock.enterwritelock();

tryvalue = creator(key);

this.m_storage.add(cachekey, value);

return value;

}finally

}}

那麼這個解決方案的時間複雜度是多少呢?假設表示式樹有m個節點,快取裡有n個物件。那麼從理論上說,構造乙個key的時間複雜度是o(m),而通過key從字典裡進行查詢的時間複雜度是o(1),因此該解決方案的時間複雜度是o(m)。

不過這是個理論值,其實際的結果呢?大家不妨思考一下,老趙在介紹完全部5種解決方案之後會單獨開篇討論一下這方面的問題。

談表示式樹的快取(2) 由表示式樹生成字串

談到使用表示式樹作為key進行快取,您腦海中最早浮現出來的解決方案是什麼?老趙看來,大部分朋友的第一反應自然就是將作為key的表示式樹,使用一定規則生成乙個字串。簡而言之,這個生成字串的規則f需要能夠保證 在同乙個快取空間內,同樣的表示式樹能夠生成相同的字串。在同乙個快取空間內,不同的表示式樹生成不...

表示式 表示式樹 表示式求值

總時間限制 1000ms 記憶體限制 65535kb 描述 眾所周知,任何乙個表示式,都可以用一棵表示式樹來表示。例如,表示式a b c,可以表示為如下的表示式樹 a b c 現在,給你乙個中綴表示式,這個中綴表示式用變數來表示 不含數字 請你將這個中綴表示式用表示式二叉樹的形式輸出出來。輸入輸入分...

Lambda表示式表示式樹

在c 3.0中,繼匿名方法之後出現了lambda 表示式,使表達更為簡潔 快捷。lambda 表示式使用lambda 運算子 來定義,語法如下 引數列表 lambda 運算子的左邊是輸入引數,定義lambda表示式的接收引數列表,右邊包含表示式或語句塊,表示將表示式的值或語句塊返回的值傳給左邊的引數...