在互聯網應用中,流量洪峯是常有的事情。在應對流量洪峯時,通用的處理模式通常有排隊、限流,這樣能夠很是直接有效的保護系統,防止系統被打爆。另外,經過限流技術手段,可讓整個系統的運行更加平穩。今天要與你們分享一下限流算法和C#版本的組件。html
1、令牌桶算法:前端
令牌桶算法的基本過程以下:java
- 假如用戶配置的平均發送速率爲r,則每隔1/r秒速率將一個令牌被加入到桶中;
- 假設桶最多能夠存發b個令牌。當桶中的令牌達到上限後,丟棄令牌。
- 當一個有請求到達時,首先去令牌桶獲取令牌,可以取到,則處理這個請求
- 若是桶中沒有令牌,那麼請求排隊或者丟棄
工做過程包括3個階段:產生令牌、消耗令牌和判斷數據包是否經過。其中涉及到2個參數:令牌產生的速率和令牌桶的大小,這個過程的具體工做以下。python
- 產生令牌:週期性的以固定速率向令牌桶中增長令牌,桶中的令牌不斷增多。若是桶中令牌數已到達上限,則丟棄多餘令牌。
- 消費 令牌:業務程序根據具體業務狀況消耗桶中的令牌。消費一次,令牌桶令牌減小一個。
- 判斷是否經過:判斷是否已有令牌桶是否存在有效令牌,當桶中的令牌數量能夠知足需求時,則繼續業務處理,不然將掛起業務,等待令牌。
下面是C#的一個實現方式算法
class TokenBucketLimitingService: ILimitingService
{
private LimitedQueue<object> limitedQueue = null;
private CancellationTokenSource cancelToken;
private Task task = null;
private int maxTPS;
private int limitSize;
private object lckObj = new object();
public TokenBucketLimitingService(int maxTPS, int limitSize)
{
this.limitSize = limitSize;
this.maxTPS = maxTPS;springif (this.limitSize <= 0)
this.limitSize = 100;
if(this.maxTPS <=0)
this.maxTPS = 1;編程limitedQueue = new LimitedQueue<object>(limitSize);
for (int i = 0; i < limitSize; i++)
{
limitedQueue.Enqueue(new object());
}
cancelToken = new CancellationTokenSource();
task = Task.Factory.StartNew(new Action(TokenProcess), cancelToken.Token);
}windows/// <summary>
/// 定時消息令牌
/// </summary>
private void TokenProcess()
{
int sleep = 1000 / maxTPS;
if (sleep == 0)
sleep = 1;數組DateTime start = DateTime.Now;
while (cancelToken.Token.IsCancellationRequested ==false)
{
try
{
lock (lckObj)
{
limitedQueue.Enqueue(new object());
}
}
catch
{
}
finally
{
if (DateTime.Now - start < TimeSpan.FromMilliseconds(sleep))
{
int newSleep = sleep - (int)(DateTime.Now - start).TotalMilliseconds;
if (newSleep > 1)
Thread.Sleep(newSleep - 1); //作一下時間上的補償
}
start = DateTime.Now;
}
}
}緩存public void Dispose()
{
cancelToken.Cancel();
}/// <summary>
/// 請求令牌
/// </summary>
/// <returns>true:獲取成功,false:獲取失敗</returns>
public bool Request()
{
if (limitedQueue.Count <= 0)
return false;
lock (lckObj)
{
if (limitedQueue.Count <= 0)
return false;object data = limitedQueue.Dequeue();
if (data == null)
return false;
}return true;
}
}
public interface ILimitingService:IDisposable
{
/// <summary>
/// 申請流量處理
/// </summary>
/// <returns>true:獲取成功,false:獲取失敗</returns>
bool Request();
}
public class LimitingFactory
{
/// <summary>
/// 建立限流服務對象
/// </summary>
/// <param name="limitingType">限流模型</param>
/// <param name="maxQPS">最大QPS</param>
/// <param name="limitSize">最大可用票據數</param>
public static ILimitingService Build(LimitingType limitingType = LimitingType.TokenBucket, int maxQPS = 100, int limitSize = 100)
{
switch (limitingType)
{
case LimitingType.TokenBucket:
default:
return new TokenBucketLimitingService(maxQPS, limitSize);
case LimitingType.LeakageBucket:
return new LeakageBucketLimitingService(maxQPS, limitSize);
}
}
}
/// <summary>
/// 限流模式
/// </summary>
public enum LimitingType
{
TokenBucket,//令牌桶模式
LeakageBucket//漏桶模式
}
public class LimitedQueue<T> : Queue<T>
{
private int limit = 0;
public const string QueueFulled = "TTP-StreamLimiting-1001";public int Limit
{
get { return limit; }
set { limit = value; }
}public LimitedQueue()
: this(0)
{ }public LimitedQueue(int limit)
: base(limit)
{
this.Limit = limit;
}public new bool Enqueue(T item)
{
if (limit > 0 && this.Count >= this.Limit)
{
return false;
}
base.Enqueue(item);
return true;
}
}
調用方法:
var service = LimitingFactory.Build(LimitingType.TokenBucket, 500, 200);
while (true)
{
var result = service.Request();
//若是返回true,說明能夠進行業務處理,不然須要繼續等待
if (result)
{
//業務處理......
}
else
Thread.Sleep(1);
}
2、漏桶算法
聲明一個固定容量的桶,每接受到一個請求向桶中添加一個令牌,當令牌桶達到上線後請求丟棄或等待,具體算法以下:
- 建立一個固定容量的漏桶,請求到達時向漏桶添加一個令牌
- 若是請求添加令牌不成功,請求丟棄或等待
- 另外一個線程以固定的速率消費桶裏的令牌
工做過程也包括3個階段:產生令牌、消耗令牌和判斷數據包是否經過。其中涉及到2個參數:令牌自動消費的速率和令牌桶的大小,個過程的具體工做以下。
- 產生令牌:業務程序根據具體業務狀況申請令牌。申請一次,令牌桶令牌加一。若是桶中令牌數已到達上限,則掛起業務後等待令牌。
- 消費令牌:週期性的以固定速率消費令牌桶中令牌,桶中的令牌不斷較少。
- 判斷是否經過:判斷是否已有令牌桶是否存在有效令牌,當桶中的令牌數量能夠知足需求時,則繼續業務處理,不然將掛起業務,等待令牌。
C#的一個實現方式:
class LeakageBucketLimitingService: ILimitingService
{
private LimitedQueue<object> limitedQueue = null;
private CancellationTokenSource cancelToken;
private Task task = null;
private int maxTPS;
private int limitSize;
private object lckObj = new object();
public LeakageBucketLimitingService(int maxTPS, int limitSize)
{
this.limitSize = limitSize;
this.maxTPS = maxTPS;if (this.limitSize <= 0)
this.limitSize = 100;
if (this.maxTPS <= 0)
this.maxTPS = 1;limitedQueue = new LimitedQueue<object>(limitSize);
cancelToken = new CancellationTokenSource();
task = Task.Factory.StartNew(new Action(TokenProcess), cancelToken.Token);
}private void TokenProcess()
{
int sleep = 1000 / maxTPS;
if (sleep == 0)
sleep = 1;DateTime start = DateTime.Now;
while (cancelToken.Token.IsCancellationRequested == false)
{
try
{if (limitedQueue.Count > 0)
{
lock (lckObj)
{
if (limitedQueue.Count > 0)
limitedQueue.Dequeue();
}
}
}
catch
{
}
finally
{
if (DateTime.Now - start < TimeSpan.FromMilliseconds(sleep))
{
int newSleep = sleep - (int)(DateTime.Now - start).TotalMilliseconds;
if (newSleep > 1)
Thread.Sleep(newSleep - 1); //作一下時間上的補償
}
start = DateTime.Now;
}
}
}public void Dispose()
{
cancelToken.Cancel();
}public bool Request()
{
if (limitedQueue.Count >= limitSize)
return false;
lock (lckObj)
{
if (limitedQueue.Count >= limitSize)
return false;return limitedQueue.Enqueue(new object());
}
}
}
調用方法:
var service = LimitingFactory.Build(LimitingType.LeakageBucket, 500, 200);
while (true)
{
var result = service.Request();
//若是返回true,說明能夠進行業務處理,不然須要繼續等待
if (result)
{
//業務處理......
}
else
Thread.Sleep(1);
}
兩類限流算法雖然很是類似,可是仍是有些區別的,供你們參考!
- 漏桶算法可以強行限制數據的傳輸速率。在某些狀況下,漏桶算法不可以有效地使用網絡資源。由於漏桶的漏出速率是固定的。
- 令牌桶算法可以在限制數據的平均傳輸速率的同時還容許某種程度的突發傳輸.
-
C# DataGridView綁定List對象時,利用BindingList來實現增刪查改
當DataGridView的DataSource是DataTable的時候,DataTable的數據改變時,DataGridView的數據會隨之改變,無需從新綁定到DataGridView。 當DataGridView的DataSource是泛型List,當List的數據改變時,則須要先將DataGridView的DataSource設置爲new List<T>(),再將改變後的List<T>賦給DataGridView的DataSource。綁定List時,注意:切莫將DataGridView的DataSource設置爲Null,不然會破壞DataGridView的列結構。
若是要對綁定在DataGridView中的List<T>進行數據的添加刪除,先要把List<T>轉換成BindingList<T>,再進行綁定:DataGridView.DataSource=new BindingList<T>(new List<T>)。不然的話會產生許多意想不到的錯誤。 如:初始綁定空數據後再添加數據綁定後,卻取不到DataGridView.CurrentCell屬性。
IList<T> list= new List<T>();
DataGridView.DataSource=list;//DataGridView的行不能添加刪除
DataGridView.DataSource=new BindingList<T>(list);//DataGridView的行能夠添加刪除(只有容許添加行、刪除行)
示例代碼:
public partial class ucServer : UserControl
{
private List<ServerInfo> serverList;
private BindingList<ServerInfo> dataBindings;public ucServer(List<ServerInfo> serverList)
{
InitializeComponent();
if (serverList == null)
serverList = new List<ServerInfo>();
this.serverList = serverList;
dataBindings = new BindingList<ServerInfo>(this.serverList);
}private void ucChecker_Load(object sender, EventArgs e)
{
this.dgParams.DataSource = dataBindings;
}private void llDownloadUrl_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
MessageBox.Show("請設置下載地址。");
}private void llHelp_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
MessageBox.Show("請設置幫助信息。");
}public bool IsValid()
{
return true;//CheckService.Check(this.component);
}private void btnAdd_Click(object sender, EventArgs e)
{
ServerInfo info = new ServerInfo() { OSType="Windows", Ports="8000-9999"};dataBindings.Add(info);
}private void btnDel_Click(object sender, EventArgs e)
{
//容許刪除多行DataGridViewSelectedRowCollection rows = this.dgParams.SelectedRows;
foreach (DataGridViewRow row in rows)
{
this.dataBindings.RemoveAt(row.Index);
}
}}
-
.net中ThreadPool與Task的認識總結
線程池和Task是多線程編程中兩個常用的技術,你們在熟悉不過了。他們有什麼關聯關係?Task又是怎麼工做的呢?估計不少時候會犯糊塗。經過翻閱資料,終於弄明白了,與你們分享一下。
工做線程與I/O線程
在ThreadPool中有這樣一個方法:public static bool SetMaxThreads(int workerThreads, int completionPortThreads);
此方法中有兩個參數:workerThreads和completionPortThreads。這兩個參數引伸出了兩個概念:輔助線程(也叫工做線程)和異步 I/O 線程。這兩個線程有什麼區別麼?經過查閱資料,咱們能夠了解到,工做線程其實就是咱們編碼主動向ThreadPool中建立的線程。而I/O線程是線程池中預先保留出來的部分線程,這部分線程的做用是爲了分發從IOCP(I/O completion port) 中的回調。
那麼什麼是IOCP回調呢?
在CLR內部,系統維護了一個IOCP(I/O completion port),它提供了處理多個異步I/O請求的線程模型。咱們能夠把IOCP看作是一個消息隊列。當一個進程建立了一個IOCP,即建立了一個隊列。當異步I/O請 求完成時,設備驅動程序就會生成一個I/O完成包,將它按照FIFO方式排隊列入該完成端口。以後,會由I/O線程提取完成I/O請求包,並調用以前的委託。注意:異步調用服務時,回調函數都是運行於CLR線程池的I/O線程當中。
I/O線程是由CLR調用的,一般狀況下,咱們不會直接用到它 。可是線程池中區分它們的目的是爲了不線程都去處理I/O回調而被耗盡,從而引起死鎖。在編程時,開發人員須要關注的是確保I/O線程返回到線程池,I/O回調代碼應該作儘可能小的工做,並儘快返回到線程池,不然I/O線程會很快消耗光。若是回調代碼中的工做不少的話,應該考慮把工做拆分到一個工做者線程中去。不然,I/O線程被耗盡,大量工做線程空閒,可能致使死鎖。
再補充一下,當執行I/O操做的時候,不管是同步I/O操做仍是異步I/O操做,都會調用的Windows的API方法,好比,當讀取文件時,調用ReadFile函數。該方法會將你的當前線程從用戶態轉變成內核態,會生成一個I/O請求包,並初始化這個請求包。ReadFile會向內核傳遞,根據這個請求包,windows內核知道須要將這個I/O操做發送給哪一個硬件設備。這些I/O操做會進入設備本身的處理隊列中,該隊列由這個設備的驅動程序維護。
若是此時是同步I/O操做,那麼在硬件設備操做I/O的時候,發出I/O請求的線程因爲無事可作被windows變成睡眠狀態,當硬件設備完成操做後,再喚醒這個線程。這種方式很是直接,可是性能不高,若是請求數不少,那麼休眠的線程數也不少,浪費了大量資源。
若是是異步I/O操做(.Net中,異步的I/O操做大部分爲BeginXXX的形式 ),該方法在Windows把I/O請求包發送到設備的處理隊列後就返回。同時,在調用異步I/O操做時,即調用BeginXXX方法的時候,須要傳入一個委託,該委託方法會隨着I/O請求包一路傳遞到設備的驅動程序。在設備處理完I/O請求包後,將該委託再放到CLR線程池隊列。
總結來講,IOCP(I/O completion port)中有2個隊列,一個是先進先出的隊列,存放的是IO完成包,即已經完成的IO操做須要執行回調方法。還有一個隊列是線程隊列,IOCP會預分配一些線程在這個隊列中,這樣會比即時建立線程處理I/O請求速度更快。這個隊列是後進先出的,好處是下一個請求的到來可能仍是用以前的線程來處理,就不須要進行線程上下文切換,提升了性能。
這裏有一個IOCP的解釋,寫的很好。http://gamebabyrocksun.blog.163.com/blog/static/57153463201036104134250/
Task的運行原理分析
Task與ThreadPool什麼關係呢?簡單來講,Task是基於ThreadPool實現的,固然被標記爲LongRunning的Task(單首創建線程實現)除外。Task被建立後,經過TaskScheduler執行工做項的分配。TaskScheduler會把工做項存儲到兩類隊列中: 全局隊列與本地隊列。全局隊列被設計爲FIFO的隊列。本地隊列存儲在線程中,被設計爲LIFO.
當主程序建立了一個Task後,因爲建立這個Task的線程不是線程池中的線程,則TaskScheduler 會把該Task放入全局隊列中。
若是這個Task是由線程池中的線程建立,而且未設置TaskCreationOptions.PreferFairness標記(默認狀況下未設置),TaskScheduler 會把該Task放入到該線程的本地隊列中。若是設置了TaskCreationOptions.PreferFairness標記,則放入全局隊列。
官方的解釋是: 最高級任務(即不在其餘任務的上下文中建立的任務)與任何其餘工做項同樣放在全局隊列上。 可是,嵌套任務或子任務(在其餘任務的上下文中建立)的處理方式大不相同。 子任務或嵌套任務放置在特定於執行父任務的線程的本地隊列上。 父任務多是最高級任務,也多是其餘任務的子任務。
那麼任務放入到兩類隊列中後,是如何被執行的呢?
當線程池中的線程準備好執行更多工做時,首先查看本地隊列。 若是工做項在此處等待,直接經過LIFO的模式獲取執行。 若是沒有,則向全局隊列以FIFO的模式獲取工做項。若是全局隊列也沒有工做項,則查看其餘線程的本地隊列是否有可執行工做項,若是存在可執行工做項,則以FIFO的模式出隊執行。
C# 排序技術研究與對比
1、 排序場景
- 一維數組排序
- 多維數組排序
- 集合排序,例如Dictionary ,List<T>, 自定義類集合排序等
- DataTable排序
2、 排序實現和測試
1. 一維數組排序
1.1 一維數組排序特色
元素之間是一維線性的關係,每一個元素只有一個下標,在排序場景下,每一個元素的數據類型是一致的。例如:
1.2 C# 一維數組排序實現
A:調用Array.Sort方法實現數組排序,不限制元素數據類型,底層基於對IComparable的接口實現
B:使用Linq實現排序
1.2 測試結果對比
1000條數據(GUID)
10000數據(GUID)
100000數據(GUID)
Array.Sort
2ms
35ms
420ms
Linq
4ms
74ms
738ms
能夠看出, Array.Sort排序優於Linq的性能(越底層的結構,排序的性能越好)。
2. 多維數組排序
2.1 多維數組排序特色
數組能夠具備多個維度,支持多行多列,各個維度的數據類型能夠不一樣。
在此文中,交錯數組不在研究範圍內,主要研究的是不一樣數據類型的矩陣數組,這樣更加貼近咱們在實際場景中的數據。例如:
2.2 C# 多維數組排序實現
Step1:定義一個對象排序類ObjectComparer,實現IComparer接口,主要負責數組中某個列的排序,
若是要排序的列是int類型,進行以下比較便可:其餘類推。
ObjectComparer類結構:
Step2:定義維度順序整形數組:tagObjArray,實例化ObjectComparer對象,將要排序的數組做爲參數傳遞給ObjectComparer的構造函數。
Step3:調用Array.Sort方法排序,參數:維度順序整形數組:tagObjArray和ObjectComparer對象。
3. 集合排序
3.1.ArrayList 類
使用大小可按需動態增長的數組。
3.2 List 類
可經過索引訪問的對象的強類型列表。提供用於對列表進行搜索、排序和操做的方法,在決定使用 List 仍是使用 ArrayList 類(二者具備相似的功能)時, List 類在大多數狀況下執行得更好而且是類型安全的。例子中的Sort其實調用的是String.Sort方法。
3.3 List和ArrayList性能測試對比
ArrayList
List
100000
498 ms
538ms
3.4 Dictionary類/SortedDictionary類
從數據結構上來講都屬於Hashtable,對關鍵字(鍵值)進行散列操做,適合鍵值對的存取,排序可使用LINQ實現,建議使用SortedDictionary替換。
Dictionary和HashTable內部實現差很少,但前者無需裝箱拆箱操做,效率略高一點
3.5 HashTable類
Hashtable 主要用於鍵值快速查找,卻沒有提供排序的方法,因此它的排序須要借住數組或其它集合來實現。
HashTable中的key/value均爲object類型,由包含集合元素的存儲桶組成。存儲桶是 HashTable中各元素的虛擬子組,與大多數集合中進行的搜索和檢索相比,存儲桶可令搜索和檢索更爲便捷。每一存儲桶都與一個哈希代碼關聯,該哈希代碼是使用哈希函數生成的並基於該元素的鍵。HashTable的優勢就在於其索引的方式,速度很是快。若是以任意類型鍵值訪問其中元素會快於其餘集合,特別是當數據量特別大的時候,效率差異尤爲大。
HashTable的應用場合有:對象緩存,樹遞歸算法的替代,和各類需提高效率的場合。
3.6 Stack類
Stack,棧,表示對象的簡單的後進先出非泛型集合。Push方法入棧,Pop方法出棧。
3.7 Queue類
隊列,先進先出。enqueue方法入隊列,dequeue方法出隊列。
3.8 自定義類集合
Step1:定義自定義類:Person
Step2:構造實體類集合:List<Person> persons
Step3:排序方法實現:
一、直接排序
二、Person實現IComparable接口,直接調用Sort方法排序
直接調用Sort方法排序
三、Linq實現排序
測試結果對比:
1.直接排序
2.實現IComparable接口
3.Linq
100000
75ms
99ms
29ms
4. DataTable排序
4.1 DataTable特色
(1)DataTable 對象是按條件區分大小寫的。(若是有兩個 DataTable對象分別爲「mydatatable」和「Mydatatable」,則搜索表的字符串被認爲是區分大小寫的。若是隻有「mydatatable」而不存在「Mydatatable」,則該搜索表的字符串不區分大小寫)。
(2)以編程方式建立 DataTable,須先經過將 DataColumn 對象添加到 DataColumnCollection(經過 Columns 屬性訪問)中來定義其架構。
(3)向 DataTable 添加行,須使用 NewRow 方法返回新的 DataRow 對象。(DataTable 可存儲的最大行數是 16,777,216)。
(4)DataTable 也包含可用於確保數據完整性的 Constraint 對象的集合
(5)DataTable 事件(RowChanged、RowChanging、RowDeleting 和 RowDeleted)可用於肯定對錶進行更改的時間
4.2 DataTable排序實現
Step1: 構造DataTable
Step2:DataView排序
Step2:DataTable.Select排序
4.3 測試結果對比
1.DataView排序
2.DataTable.Select排序
100000
526 ms
368ms
對比下自定義類存儲100000條相同數據的排序結果:
測試結果對比:
1.直接排序
2.實現IComparable接口
3.Linq
100000
75ms
99ms
29ms
3、 排序效率總結
一、 在數組排序中,建議使用Array.Sort 方式,優於LINQ方式
二、 在自定義類排序時,推薦使用LINQ方式
三、 DataTable和自定義類存儲同類型數據時,自定義類的排序總體優於DataTable方式。
四、 DataTable排序時,推薦使用DataTable.Select排序方式。
五、 Dictionary和HashTable內部實現差很少,但Dictionary無需裝箱拆箱操做,效率略高一點。數據量較大時,建議採用HashTable。
六、 ArrayList集合的排序性能優於List集合。
七、 Stack和Queue集合用於棧和隊列操做。
基於.net的通用內存緩存模型組件
談到緩存,咱們天然而然就會想到緩存的好處,好比:
- 下降高併發數據讀取的系統壓力:靜態數據訪問、動態數據訪問
- 存儲預處理數據,提高系統響應速度和TPS
- 下降高併發數據寫入的系統壓力
- 提高系統可用性,後臺宕機後,系統還存在可用的機會
緩存技術一直是優化程序性能的一個重要手段,在互聯網技術體系中,也不例外。可是在分佈式架構下,你們開始更多的使用分佈式緩存,好比Redis、MemcacheD等等,對進程內的內存緩存使用的愈來愈少。其主要緣由無外乎幾點:
一是,數據不能作到強一致性,程序內存數據緩存同步的週期相對分佈緩存更慢一些。
二是,須要對緩存的各類同步策略進行封裝,並控制同步時機。進程內緩存的使用比分佈式緩存的使用具備更高的技術門檻。沒有分佈緩存使用簡單。
雖然分佈式緩存具備很是多很好的特性,可是當徹底拋棄了程序內存緩存後,分佈式緩存將會被濫用,應用程序甚至過分的依賴分佈式緩存。筆者認爲,任何一種技術的濫用,都將可能致使系統架構在健壯性上存在缺陷。分佈式緩存雖然很好用,性能也不錯,可是與進程內存緩存比起來,性能仍是差了好多個數量級。要想把系統的性能作到極致,僅僅依賴Redis等分佈式緩存還不不夠的,還須要充分利用進程內存緩存。
緩存技術,從出現到如今,總結來講,已有四個階段的發展:本地緩存、分佈式緩存、彈性緩存平臺,彈性應用平臺。本地緩存的特色是數據存儲在應用代碼所在內存空間,可提供快速的數據訪問,納秒級性能。缺點也很明顯,數據沒法分佈式共享,無容錯處理。分佈式緩存的特色是數據在固定數目的集羣節點間分佈存儲,緩存容量可擴展(靜態擴展),可是擴展過程當中需大量配置,無容錯機制。彈性緩存平臺的特性是數據在集羣節點間分佈存儲,基於冗餘機制實現高可用性。其優勢是可動態擴展,具備容錯能力,可是複製備份會對系統性能形成必定影響。彈性應用平臺的特色是彈性緩存與代碼執行的組合體,將業務邏輯代碼轉移到數據所在節點執行,極大地下降數據傳輸開銷,提高系統性能。縱觀整個緩存技術的發展,經歷了從分散到集中,又到集中並分散的一個過程。彈性應用平臺做爲最終的緩存解決方案,已經不只僅停留在緩存技術自己,而是更多的考慮瞭如何更好的與業務代碼無縫集成,並提供進程內存級別的性能。
基於此,咱們規劃設計了一個通用的內存緩存組件。經過此組件,能夠實現各類內存數據的緩存,以及緩存數據同步等,並提供了分佈式緩存數據同步到進程內存的方案。此組件與傳統的緩存組件有很大的不一樣,它是對進程內存緩存的使用和同步作了抽象和總結,直接提供了一套模型。上文也提到,使用進程內存緩存最大的挑戰在與數據同步,那麼,咱們先看一下影響進程內存緩存同步的一些因素。經過下圖,咱們看到在同步策略、同步時機、同步模式上都有不少選擇。進程內存緩存的使用,其實就是下面三個維度組合處理的一個結果。
在實際的業務中,同步策略更多的會基於時間、數據版本或者時間來作,而後選擇合適的同步時機和模式來執行數據同步。因此,在組件的封裝上,咱們支持了三種應用模型:
- 對緩存數據標記有效期,過時自動清理
- 緩存數據時,同時緩存數據版本,定時校驗或實時校驗數據版本,發現版本不一致時清理或從新同步緩存數據
- 緩存數據並訂閱數據變化通知,當收到變化通知後,更新緩存數據
模型一:對緩存數據標記有效期,過時自動清理
此模型主要適用於, 好比:字符串資源信息查詢、App充電地圖數據、App最新動態、高頻率日誌記錄、配置中心數據的緩存等等。
-
數據實時性要求很低,訪問頻率很高,變化頻率較小的數據查詢
-
訪問頻率很高,查詢參數基本一致的數據查詢
-
訪問頻率很高,容許丟失的輔助信息寫入
代碼示例:
//nuget:Teld.Core//引用:Teld.Core.Cache.ServiceEx.dll// 建立一個帶過時時間的本地內存容器using(var container = LocalCacheService.CreateExpirationContainer("TTP-Cache-CFG")){//向容器中增長項目,3秒鐘的有效期container.Add("Name", "張三", new TimeSpan(0, 0, 3));//想容器中增長項目,永久有效container.Add("Address", "鑫盛大廈1號樓12層北特來電");}模型二:緩存數據時,同時緩存數據版本,定時校驗或實時校驗數據版本,發現版本不一致時清理或從新同步緩存數據
此模型主要適用於, 好比:幫助查詢等。
- 數據實時性要求不高,訪問頻率很高的數據查詢
- 訪問頻率很高,查詢參數基本一致的數據查詢
代碼示例1: 定時同步,每分鐘執行一次版本同步
static voidMain(string[] args)
{
//建立緩存數據同步代理
CacheValidateEventHandler<UserInfo> handler = newCacheValidateEventHandler<UserInfo>(SyncUserData);//建立一個帶版本校驗的本地內存緩存容器,每隔60s,自動進行一次數據全量同步using(varcontainer = LocalCacheService.CreateVersionContainer<UserInfo>("TTP-Cache-User", handler, SyncTime.Timing, SyncModel.All, 60)){
container.SyncFailed += Container_SyncFailed; //數據同步失敗,事件通知
container.SyncData(); //當即執行一次數據同步
var user = container.Get("Name"); //從緩存中獲取數據
Console.WriteLine(JsonConvert.SerializeObject(user));
}
Console.ReadLine();
}//數據同步,返回全量或者增量數據,並返回數據的最新版本
static Dictionary<string, UserInfo> SyncUserData(CacheValidateEventArgs e,out stringnewVersion)
{
//經過e.Version獲取上次同步的數據數據
Dictionary<string, UserInfo> result = new Dictionary<string, Cache.UserInfo>();
Random r = new Random(DateTime.Now.Second);result.Add("Name", new Cache.UserInfo() { Name = "Name", Age =r.Next(1,20) , IsMale= true , LastModifyTime = DateTime.Now});newVersion = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff");
return result;
}代碼示例2: 調用緩存Get方法時,自動同步緩存
static void Main (string[] args)
{ //建立緩存數據同步代理
CacheValidateEventHandler<UserInfo> handler = new CacheValidateEventHandler<UserInfo>(SyncUserData);
//建立一個帶版本校驗的本地內存緩存容器
varcontainer = LocalCacheService.CreateVersionContainer<UserInfo>("TTP-Cache-User", handler, SyncTime.GetCheck, SyncModel.All);
container.SyncFailed += Container_SyncFailed; //數據同步失敗,事件通知
var user = container.Get("Name"); //從緩存數據中獲取數據
Console.WriteLine(JsonConvert.SerializeObject(user));
Console.ReadLine();
}
模型三:緩存數據並訂閱數據變化通知,當收到變化通知後,更新緩存數據
此模型主要適用於, 好比:電站狀態緩存等。
- 數據實時性要求比較高,訪問頻率很高的數據查詢
代碼示例1: 帶事件更新通知的緩存
public void GetString_Invoke()
{
//建立一個帶MQ變化通知的本地內存緩存容器
using (var container = LocalCacheService.CreateEventContainer<string>("TTP-Cache-EventCacheUnitTest",
(CacheValidateEventArgs e, out string newVersion) =>
{
newVersion = Guid.NewGuid().ToString();
return BuildStringData();
}, SyncModel.All, 1))
{
container.SyncData(); //爲容器初始化數據
var data = container.Get("lastModifytime"); //獲取數據項
Assert.IsNotNull(data);
var data1 = container.Get("lastModifytime");
Assert.AreEqual(data, data1);
//發送數據項的更新通知事件
LocalCacheService.SendDataChangedEvent(container.Name, "lastModifytime");
Thread.Sleep(5000);
var data2 = container.Get("lastModifytime");
Assert.AreNotEqual(data2, data);
}
}
代碼示例2:數據刪除的事件通知
public void GetString_Delete(){//建立一個帶MQ變化通知的本地內存緩存容器using (var container = LocalCacheService.CreateEventContainer<string>("TTP-Cache-EventCacheUnitTest",(CacheValidateEventArgs e, out string newVersion) =>{newVersion = Guid.NewGuid().ToString();return BuildStringData();}, SyncModel.All, 1)){container.SyncData(); //爲容器初始化數據var data = container.Get("lastModifytime"); //獲取數據項Assert.IsNotNull(data);var data1 = container.Get("lastModifytime");Assert.AreEqual(data, data1);LocalCacheService.SendDataChangedEvent(container.Name,"lastModifytime", EventType.Delete); //發送數據項的刪除通知事件Thread.Sleep(5000);var data2 = container.Get("lastModifytime");Assert.IsNull(data2);}}以上是此緩存組件應用的三種模型。三種模型,可經過 LocalCacheService 建立。其代碼以下:
public class LocalCacheService
{/// <summary>/// 建立過時自動清理的本地內存緩存容器/// </summary>/// <param name="key">容器標識,三段式命名,全局惟一:TTP-Resource-DataCache</param>/// <param name="limitSize">限制使用的內存大小(M)</param>public static IExpirationCacheContainer CreateExpirationContainer(string key,long? limitSize =null){if (string.IsNullOrEmpty(key)){throw new ArgumentException("Key值不能爲空!", nameof(key));}return new InMemoryExpirationContainer(key, limitSize);}/// <summary>/// 建立基於版本比較的本地內存緩存容器/// </summary>/// <typeparam name="T"></typeparam>/// <param name="key">容器標識,三段式命名,全局惟一:TTP-Resource-DataCache</param>/// <param name="handler">基於版本比較的數據處理器</param>/// <param name="syncTime">同步時機</param>/// <param name="model">同步模式</param>/// <param name="syncTimeMin">同步時機爲Timing時,同步時間間隔</param>/// <returns></returns>public static IVersionCacheContainer<T> CreateVersionContainer<T>(string key,CacheValidateEventHandler<T> handler, SyncTime syncTime =SyncTime.Invoke,SyncModel model = SyncModel.All,int syncTimeSec=180) where T : class{if (string.IsNullOrEmpty(key)){throw new ArgumentException("Key值不能爲空!", nameof(key));}if (handler == null){throw new ArgumentNullException(nameof(handler));}if (syncTimeSec == 0)syncTimeSec = 180;return new InMemoryVersionCacheContainer<T>(key, handler, syncTime, model,TimeSpan.FromSeconds(syncTimeSec));}/// <summary>/// 建立基於事件通知的本地內存緩存容器/// </summary>/// <typeparam name="T"></typeparam>/// <param name="key">容器標識,三段式命名,全局惟一:TTP-Resource-DataCache</param>/// <param name="handler">基於版本比較的數據處理器</param>/// <param name="model">同步模式</param>/// <param name="syncTimeMin">同步時機爲Timing時,同步時間間隔</param>/// <returns></returns>public static IVersionCacheContainer<T> CreateEventContainer<T>(stringkey,CacheValidateEventHandler<T> handler, SyncModel model = SyncModel.All, intsyncTimeSec = 180) where T : class{if (string.IsNullOrEmpty(key)){throw new ArgumentException("Key值不能爲空!", nameof(key));}if (handler == null){throw new ArgumentNullException(nameof(handler));}if (syncTimeSec == 0)syncTimeSec = 180;return new InMemoryEventCacheContainer<T>(key,model,handler,TimeSpan.FromSeconds(syncTimeSec));}}同步模式和同步時機的定義以下:
/// <summary>
/// 同步模式
/// </summary>
public enum SyncModel : int
{
All, //全量同步,清楚歷史緩存信息,從新插入
Increase, //增量同步,同步變化部分,不對歷史緩存數據清理
Clear //僅清理歷史數據
}/// <summary>
/// 同步時機
/// </summary>
public enum SyncTime : int
{
Invoke, //調用方執行SyncData方法主動同步
Timing, //定時同步
GetCheck //Get方法是自動同步
}以上是咱們在進程內存組件的一些實踐心得,更多技術細節,歡迎同窗們來電溝通。微信號:vveiliang
Scala學習筆記:重要語法特性
1.變量聲明
Scala 有兩種變量, val 和 var val的值聲明後不可變,var可變val msg: String = "Hello yet again, world!"
或者類型推斷
val msg = "Hello, world!"
2.函數定義
若是函數僅由一個句子組成,你能夠可選地不寫大括號。def max2(x: Int, y: Int) = if (x > y) x else y
3.for循環
打印每個命令行參數的方法是:args.foreach(arg => println(arg))
若是函數文本由帶一個參數的一句話組成,
args.foreach(println)
Scala 裏只有一個指令式 for的函數式近似。
for (arg <- args)
println(arg)<- 的左邊是變量,右邊是數組。
再好比帶類型的參數化數組
val greetStrings = new Array[String](3)
greetStrings(0) = "Hello"
greetStrings(1) = ", "
greetStrings(2) = "world!\n"
for (i <- 0 to 2)
print(greetStrings(i))注意這裏的數組定義,只要new的時候帶類型Array[String]就好了,val後面自動推斷類型。
注意這裏的數組索引用的是()而不是java裏面的[]。
由於scala裏面根本沒有傳統意義上的操做符,取而代之的是他們均可以轉換爲方法。例如greetStrings(i)能夠轉換成 greetStrings.apply(i),
greetStrings(0) = "Hello" 將被轉化爲 greetStrings.update(0, "Hello")儘管實例化以後你沒法改變 Array 的長度,它的元素值倒是可變的。所以,Array 是可變的對象。
4.List對象
建立一個 List 很簡單。 List裏面元素不可變。val oneTwoThree = List(1, 2, 3)
List有個叫「 :::」的方法實現疊加功能。
val oneTwo = List(1, 2)
val threeFour = List(3, 4)
val oneTwoThreeFour = oneTwo ::: threeFour//結果是List(1, 2, 3, 4)
Cons 把一個新元素組合到已有 List的最前端,而後返回結果 List。 例如,若執行這個腳本:
val twoThree = list(2, 3)
val oneTwoThree = 1 :: twoThree
println(oneTwoThree)
//你會看到: List(1, 2, 3)
一個簡單的需記住的規則:若是一個方法被用做操做符標註,如 a* b,那麼方法被左操做數調用,就像 a.*(b)——除非方法名以冒號結尾。這種狀況下,方法被右操做數調用。所以, 1 :: twoThree 裏, ::方法被 twoThree 調用,傳入 1,像這樣: twoThree.::(1)。
類 List 沒有提供 append 操做,由於隨着列表變長 append 的耗時將呈線性增加,而使用::作前綴則僅花費常量時間。若是你想經過添加元素來構造列表,你的選擇是把它們前綴進去,當你完成以後再調用 reverse;5.元組
與列表同樣,元組也是不可變的,但與列表不一樣,元組能夠包含不一樣類型的元素。val pair = (99, "Luftballons", 55)
println(pair._1)
println(pair._2)
println(pair._3)注意這裏第一個元素是從_1開始而不像List那樣從0開始。
6.Set和Map
var jetSet = Set("Boeing", "Airbus")
jetSet += "Lear"默認set或者HashSet可變。即jetSet = jetSet + "Lear"
若是要用不可變的set或者HashSet,要import
import scala.collection.immutable.HashSet
val hashSet = HashSet("Tomatoes", "Chilies")
println(hashSet + "Coriander")//這裏就不能再賦值給hashSet了
7.訪問級別
Public 是 Scala 的缺省訪問級別。C++中struct默認是public,class默認是private。java類中的變量默認是default類型,只容許在同一個包內訪問,通常用的時候跟private差很少。因此java用的時候要想用public必需要指出。
class ChecksumAccumulator {
private var sum = 0
...}
8.靜態對象:object
Scala 比 Java 更面向對象的一個方面是 Scala 沒有靜態成員。替代品是, Scala 有單例對象: singleton object。除了用 object 關鍵字替換了 class 關鍵字之外,單例對象的定義看上去就像是類定義。import scala.collection.mutable.Map
object ChecksumAccumulator {
private val cache = Map[String, Int]()
def calculate(s: String): Int =
if (cache.contains(s))
cache(s)
else {
.....
}能夠以下方式調用 ChecksumAccumulator單例對象的calculate方法:
ChecksumAccumulator.calculate("Every value is an object.")
也不用在實例化了。
類和單例對象間的一個差異是,單例對象不帶參數,而類能夠。由於你不能用new關鍵字實例化一個單例對象, 你沒機會傳遞給它參數。9.伴生對象
當單例對象與某個類共享同一個名稱時,他被稱做是這個類的伴生對象: companion object。你必須在同一個源文件裏定義類和它的伴生對象。類被稱爲是這個單例對象的伴生類: companion class。類和它的伴生對象能夠互相訪問其私有成員。
不與伴生類共享名稱的單例對象被稱爲孤立對象: standalone object。 因爲不少種緣由你會用到它10.Main函數
要執行Scala程序,你必定要提供一個有main方法(僅帶一個參數, Array[String],且結果類型爲 Unit的孤立單例對象名。好比下面這個例子;import ChecksumAccumulator.calculate
object Summer {
def main(args: Array[String]) { //對比java裏面的public static voidmain(String[] args){}
for (arg <- args)
println(arg + ": " + calculate(arg))
}
}11.另外一種Main函數
Application 特質:在單例對象名後面寫上「 extends Application」 。而後取代main 方法.import ChecksumAccumulator.calculate
object FallWinterSpringSummer extends Application {
for (season <- List("fall", "winter", "spring"))
println(season +": "+ calculate(season))
}效果和main函數同樣。不過它也有些缺點。首先, 若是想訪問命令行參數的話就不能用它,由於args數組不可訪問。第二,若是你的程序是多線程的就須要顯式的 main 方法。最後,某些JVM的實現沒有優化被 Application 特質執行的對象的初始化代碼。所以只有當你的程序相對簡單和單線程狀況下你才能夠繼承 Application 特質。
12.帶參數類聲明
Java 類具備能夠帶參數的構造器,而 Scala 類能夠直接帶參數。 Scala 的寫法更簡潔——類參數能夠直接在類的主體中使用;不必定義字段而後寫賦值函數把構造器的參數複製到字段裏。(不須要構造函數)
例如如下分數構造器:class Rational(n: Int, d: Int) {
require(d != 0)
override def toString = n +"/"+ dval numer: Int = n
val denom: Int = d
def add(that: Rational): Rational =
new Rational(
numer * that.denom + that.numer * denom,
denom * that.denom
}require方法帶一個布爾型參數。若是傳入的值爲真,require將正常返回。反之,require將經過拋出IllegalArgumentException來阻止對象被構造。 這裏使用了重載。重載了類自帶的toString函數。 這裏that也就是隨便起了個名字的變量,不是關鍵字。this是關鍵字。好比下面這個函數:
def lessThan(that: Rational) =
this.numer * that.denom < that.numer * this.denom這裏的this能夠省略。但下面這個就不能省略了:
def max(that: Rational) =
if (this.lessThan(that)) that else this13.if返回值
Scala 的 if 能夠產生值(是能返回值的表達式)。因而 Scala 持續了這種趨勢讓 for, try 和 match 也產生值。while不產生值,因此用得少。若是實在想用while,在純函數式編程的時候能夠考慮遞歸。
指令式風格:var filename = "default.txt"
if (!args.isEmpty)
filename = args(0)函數式風格:
val filename =
if (!args.isEmpty) args(0)
else "default.txt"使用 val 而不是 var 的第二點好處是他能更好地支持等效推論。不管什麼時候均可以用表達式替代變量名。
如要替代 println(filename),你能夠這麼寫:println(if (!args.isEmpty) args(0) else "default.txt")
14.break 和 continue
scala裏面也沒有break以及continue。若是想要用到他們的功能,可使用增長布爾變量控制到循環語句判斷中,相似:var foundIt = false while (i < args.length && !foundIt) { }
15.for過濾器
在for裏面還支持過濾器:val filesHere = (new java.io.File(".")).listFiles //路徑名的目錄中的文件的數組。
for (file <- filesHere if file.getName.endsWith(".scala"))
println(file)甚至多個過濾器:
for (
file <- filesHere
if file.isFile;
if file.getName.endsWith(".scala")
) println(file)16.for循環生成新集合
for {子句} yield {循環體} 製造新集合
例如:def scalaFiles =
for {
file <- filesHere
if file.getName.endsWith(".scala")
} yield file這樣每一步就不是打印一個file,而是將file存儲起來,最終產生一個Array[File]
17.try catch finally
try {
val f = new FileReader("input.txt")
openFile(file)
} catch {
case ex: FileNotFoundException => new FileReader("input.txt")
//注意這裏依然有返回值。使用=>符號
case ex: IOException => // Handle other I/O error
}
finally {
file.close() // 確保關閉文件。
}這裏catch {
case ex: 。。。
case ex: 。。。
}對比java,catch是這樣用的
catch (Exception e) { 。。。。} 並且常常在catch裏面throw。scala通常不使用throw。還有,finally裏最好只作一些關閉或打印之類的操做,不要有反作用的表達式,這樣會有無謂的返值。
18.switch語句
match語句就像java裏的switch語句。val firstArg = if (args.length > 0) args(0) else ""
firstArg match {
case "salt" => println("pepper")
case "chips" => println("salsa")
case "eggs" => println("bacon")
case _ => println("huh?")
}差異:
一、Java 的 case 語句裏面的整數類型和枚舉常量。而這裏能夠是任意類型,甚至正則匹配
二、在每一個可選項的最後並無 break。取而代之, break是隱含的。
三、match 表達式也能產生值:例如能夠這樣:val friend =
firstArg match {
case "salt" => "pepper"
case "chips" => "salsa"
case _ => "huh?"
}
println(friend)19.函數嵌套定義
函數:函數式編程風格的一個重要設計原則:程序應該被解構成若干小的函數, 每一個完成一個定義良好的任務。在java中一般這樣作:def processFile(filename: String, width: Int) {
...
for (line <- source.getLines)
processLine(filename, width, line)
}
private def processLine(filename:String, width:Int, line:String) {
....
}Scala 提供了另外一種方式:你能夠把函數定義在另外一個函數中。就好象本地變量那樣,這種本地函數僅在包含它的代碼塊中可見。
def processFile(filename: String, width: Int) {
def processLine(filename:String, width:Int, line:String) {
...
}
val source = Source.fromFile(filename)
for (line <- source.getLines) {
processLine(filename, width, line)
}
}20.Lambda函數
Scala 擁有第一類函數: first-class function。你不只能夠定義函數和調用它們,還能夠把函數寫
成沒有名字的文本: (跟python裏面的lambda函數差很少)scala> var increase = (x: Int) => x + 1
scala> increase(10)
res0: Int = 11若是你想在函數文本中包括超過一個語句,用大括號包住函數體,當函數值被調用時,全部的語句將被執行,而函數的返回值就是最後一行產生的那個表達式。
scala> increase = (x: Int) => {
println("We")
println("are")
println("here!")
x + 1
}21.集合通配符:_
Scala 提供了許多方法去除冗餘信息並把函數文本寫得更簡短。好比去除參數類型以及被推斷的參數以外的括號:scala> someNumbers.filter(x => x > 0)
若是想讓函數文本更簡潔,能夠把下劃線當作一個或更多參數的佔位符,只要每一個參數在函數文
本內僅出現一次。 :scala> someNumbers.filter(_ > 0)
還可使用一個下劃線替換整個參數列表。叫偏應用函數
scala> def sum(a: Int, b: Int, c: Int) = a + b + c
通常調用能夠這樣:
scala> sum(1, 2, 3)
用偏函數取而代之:
scala> val a = sum _ //請記住要在函數名和下劃線之間留一個空格
scala> a(1, 2, 3)再好比
someNumbers.foreach(println _)
22.閉包
閉包:是指能夠包含自由(未綁定到特定對象)變量的代碼塊
任何帶有自由變量的函數文本,如(x: Int) => x + more,都是開放術語:因爲函數值是關閉這個開放術語(x: Int) => x + more 的行動的最終產物, 獲得的函數值將包含一個指向捕獲的 more 變量的參考, 所以被稱爲閉包。scala> var more = 1
scala> val addMore = (x: Int) => x + more
scala> addMore(10)
res19: Int = 11每次函數被調用時都會建立一個新閉包。每一個閉包都會訪問閉包建立時活躍的 more 變量。
23.重複參數
想要標註一個重複參數,在參數的類型以後放一個星號。例如:scala> def echo(args: String*) =
for (arg <- args) println(arg)這樣定義, echo 能夠被零個至多個 String 參數調用:
scala> echo()
scala> echo("one")
scala> echo("hello", "world!")可是若是有一個數組變量
scala> val arr = Array("What's", "up", "doc?"),則不能像scala> echo(arr)這樣調用。
你須要在數組參數後添加一個冒號和一個_*符號,像這樣:scala> echo(arr: _*)
24.curry化
Scala 容許你建立新的「感受像是原生語言支持」的控制抽象:scala提供curry 化:
Scala裏的Curry化能夠把函數從接收多個參數轉換成多個參數列表。若是要用一樣的一組實參屢次調用一個函數,能夠用curry化來減小噪音,讓代碼更有味道。咱們要編寫的方法不是接收一個參數列表,裏面有多個參數,而是有多個參數列表,每一個裏面能夠有一個或多個參數。也就是說,寫的不是def foo(a: Int, b: Int, c: Int){},而是 def foo(a: Int)(b: Int)(c: Int){}。能夠這樣調用這個方法,好比:foo(1)(2)(3)、foo(1){2}{3},甚至這樣foo{1}{2}{3}。例如,傳統函數以下:
scala> def plainOldSum(x: Int, y: Int) = x + y
scala> plainOldSum(1, 2) //res5: Int = 3
scala容許你使用curry化的新型函數:
scala> def curriedSum(x: Int)(y: Int) = x + y
scala> curriedSum(1)(2) //res5: Int = 3結果同樣。
25.帶函數參數的函數
高階函數: higher-order function——帶其它函數作參數的函數def filesMatching(query: String,
matcher: (String, String) => Boolean) = {
for (file <- filesHere; if matcher(file.getName, query))
yield file
}這裏matcher實際上是一個函數,這裏作了filesMatching函數的參數。
(String, String) => Boolean)表示matcher函數的參數是(String, String)類型,而返回值是Boolean 類型
你能夠經過讓多個搜索方法調用它,並傳入合適的函數:def filesEnding(query: String) =
filesMatching(query, _.endsWith(_))這就至關於
def filesEnding(query: String) =
for (file <- filesHere; if file.getName.endsWith(query))
yield file由於上面matcher: (String, String)裏面兩個參數, matcher(file.getName, query)
因此 _.endsWith(_)裏面的第一個_對應於字符串file.getName,第二個_對應於字符串query,
連在一塊兒就是file.getName.endsWith(query)相似的
def filesContaining(query: String) =
filesMatching(query, _.contains(_))就至關於
def filesContaining(query: String) =
for (file <- filesHere; if file.getName.contains(query))
yield file26.傳名參數
Scala中容許無參數的函數做爲另外一函數的參數傳遞進去,也就是傳名參數(call-by-name)
定義一個函數 myAssert ,而其參數則爲傳名參數。在這裏,咱們想要實現的是斷言,能夠將傳名參數寫成函數文本的格式:(…) => Type ,即參數列表 => 類型。def myAssert(check: () => Boolean) =
if(!check()){
println("OK ...")
throw new AssertionError
}上面的函數定義了一個當客戶代碼傳入的函數值(這裏咱們用()指明,表明省略了該函數的參數列表。調用方式以下:
scala> myAssert(() => 5 < 3)
客戶端代碼中的 () => 5 < 3 彷佛有點繁瑣,若是可以直接傳入 5 < 3 之類的布爾表達式就更好了。這是能夠實現的。只須要將函數定義 def 中空參數列表即小括號對()去掉,直接用 => 而不是()=> 就能夠了。此外,if 判斷中的 check 後面的()也要同時去掉。修改後的代碼以下:
def myAssert(check: => Boolean) = if(!check){ println("OK ...") throw new AssertionError } myAssert(5 < 3)
-