A. 阿里sentinel源碼解析
sentinel是阿里巴巴開源的流量整形(限流、熔斷)框架,目前在github擁有15k+的star,sentinel以流量為切入點,從流量控制、熔斷降級、系統負載保護等多個維度保護服務的穩定性。
我們以sentinel的主流程入手,分析sentinel是怎麼搜集流量指標,完成流量整形的。
首先我們先看一個sentinel的簡單使用demo,只需要調用SphU.entry獲取到entry,然後在完成業務方法之後調用entry.exit即可。
SphU.entry會調用Env.sph.entry,將name和流量流向封裝成StringResourceWrapper,然後繼續調用entry處理。
進入CtSph的entry方法,最終來到entryWithPriority,調用InternalContextUtil.internalEnter初始化ThreadLocal的Context,然後調用lookProcessChain初始化責任鏈,最終調用chain.entry進入責任鏈進行處理。
InternalContextUtil.internalEnter會調用trueEnter方法,主要是生成DefaultNode到contextNameNodeMap,然後生成Context設置到contextHolder的過程。
lookProcessChain已經做過優化,支持spi載入自定義的責任鏈bulider,如果沒有定義則使用默認的DefaultSlotChainBuilder進行載入。默認載入的slot和順序可見鎮樓圖,不再細說。
最後來到重頭戲chain.entry進入責任鏈進行處理,下面會按照順序分別對每個處理器進行分析。
首先來到NodeSelectorSlot,主要是獲取到name對應的DefaultNode並緩存起來,設置為context的當前節點,然後通知下一個節點。
下一個節點是ClusterBuilderSlot,繼續對DefaultNode設置ClusterNode與OriginNode,然後通知下一節點。
下一個節點是LogSlot,只是單純的列印日誌,不再細說。
下一個節點是StatisticSlot,是一個後置節點,先通知下一個節點處理完後,
1.如果沒有報錯,則對node、clusterNode、originNode、ENTRY_NODE的線程數、通過請求數進行增加。
2.如果報錯是PriorityWaitException,則只對線程數進行增加。
3.如果報錯是BlockException,設置報錯到node,然後對阻擋請求數進行增加。
4.如果是其他報錯,設置報錯到node即可。
下一個節點是FlowSlot,這個節點就是重要的限流處理節點,進入此節點是調用checker.checkFlow進行限流處理。
來到FlowRuleChecker的checkFlow方法,調用ruleProvider.apply獲取到資源對應的FlowRule列表,然後遍歷FlowRule調用canPassCheck校驗限流規則。
canPassCheck會根據rule的限流模式,選擇集群限流或者本地限流,這里分別作出分析。
passLocalCheck是本地限流的入口,首先會調用選出限流的node,然後調用canPass進行校驗。
會根據以下規則選中node。
1.strategy是STRATEGY_DIRECT。
1.1.limitApp不是other和default,並且等於orgin時,選擇originNode。
1.2.limitApp是other,選擇originNode。
1.3.limitApp是default,選擇clusterNode。
2.strategy是STRATEGY_RELATE,選擇clusterNode。
3.strategy是STRATEGY_CHAIN,選擇node。
選擇好對應的node後就是調用canPass校驗限流規則,目前sentinel有三種本地限流規則:普通限流、勻速限流、冷啟動限流。
普通限流的實現是DefaultController,就是統計當前的線程數或者qps加上需要通過的數量有沒有大於限定值,小於等於則直接通過,否則阻擋。
勻速限流的實現是RateLimiterController,使用了AtomicLong保證了latestPassedTime的原子增長,因此停頓的時間是根據latestPassedTime-currentTime計算出來,得到一個勻速的睡眠時間。
冷啟動限流的實現是WarmUpController,是sentinel中最難懂的限流方式,其實不太需要關注這些復雜公式的計算,也可以得出冷啟動的限流思路:
1.當qps已經達到溫熱狀態時,按照正常的添加令牌消耗令牌即可。
2.當qps處於過冷狀態時,會添加令牌使得演算法繼續降溫。
3.當qps逐漸回升,大於過冷的邊界qps值時,不再添加令牌,慢慢消耗令牌使得逐漸增大單位時間可通過的請求數,讓演算法繼續回溫。
總結出一點,可通過的請求數跟令牌桶剩餘令牌數量成反比,以達到冷啟動的作用。
接下來是集群限流,passClusterCheck是集群限流的入口,會根據flowId調用clusterSerivce獲取指定數量的token,然後根據其結果判斷是否通過、睡眠、降級到本地限流、阻擋。
接下來看一下ClusterService的處理,會根據ruleId獲取到對應的FlowRule,然後調用ClusterFlowChecker.acquireClusterToken獲取結果返回。ClusterFlowChecker.acquireClusterToken的處理方式跟普通限流是一樣的,只是會將集群的請求都集中在一個service中處理,來達到集群限流的效果,不再細說。
FlowSlot的下一個節點是DegradeSlot,是熔斷處理器,進入時會調用performChecking,進而獲取到CircuitBreaker列表,然後調用其tryPass校驗是否熔斷。
來到AbstractCircuitBreaker的tryPass方法,主要是判斷熔斷器狀態,如果是close直接放行,如果是open則會校驗是否到達開啟halfopen的時間,如果成功將狀態cas成halfopen則繼續放行,其他情況都是阻攔。
那怎麼將熔斷器的狀態從close變成open呢?怎麼將halfopen變成close或者open呢?sentinel由兩種熔斷器:錯誤數熔斷器ExceptionCircuitBreaker、響應時間熔斷器ResponseTimeCircuitBreaker,都分析一遍。
當業務方法報錯時會調用Tracer.traceEntry將報錯設置到entry上。
當調用entry.exit時,會隨著責任鏈來到DegradeSlot的exit方法,會遍歷熔斷器列表調用其onRequestComplete方法。
ExceptionCircuitBreaker的onRequestComplete會記錄錯誤數和總請求數,然後調用繼續處理。
1.當前狀態是open時,不應該由熔斷器底層去轉換狀態,直接退出。
2.當前狀態是halfopen時,如果沒有報錯,則將halfopen變成close,否則將halfopen變成open。
3.當前狀態時close時,則根據是否總請求達到了最低請求數,如果達到了話再比較錯誤數/錯誤比例是否大於限定值,如果大於則直接轉換成open。
ExceptionCircuitBreaker的onRequestComplete會記錄慢響應數和總請求數,然後調用繼續處理。
1.當前狀態是open時,不應該由熔斷器底層去轉換狀態,直接退出。
2.當前狀態是halfopen時,如果當前響應時間小於限定值,則將halfopen變成close,否則將halfopen變成open。
3.當前狀態時close時,則根據是否總請求達到了最低請求數,如果達到了話再比較慢請求數/慢請求比例是否大於限定值,如果大於則直接轉換成open。
下一個節點是AuthoritySlot,許可權控制器,這個控制器就是看當前origin是否被允許進入請求,不允許則報錯,不再細說。
終於來到最後一個節點SystemSlot了,此節點是自適應處理器,主要是根據系統自身負載(qps、最大線程數、最高響應時間、cpu使用率、系統bbr)來判斷請求是否能夠通過,保證系統處於一個能穩定處理請求的安全狀態。
尤其值得一提的是bbr演算法,作者參考了tcp bbr的設計,通過最大的qps和最小的響應時間動態計算出可進入的線程數,而不是一個粗暴的固定可進入的線程數,為什麼能通過這兩個值就能計算出可進入的線程數?可以網上搜索一下tcp bbr演算法的解析,十分巧妙,不再細說。
B. 面試中的網紅Vue源碼解析之虛擬DOM,你知多少呢深入解讀diff演算法
眾所周知,在前端的面試中,面試官非常愛考dom和diff演算法。比如,可能會出現在以下場景
滴滴滴,面試官發來一個面試邀請。接受邀請📞
我們都知道, key 的作用在前端的面試是一道很普遍的題目,但是呢,很多時候我們都只浮於知識的表面,而沒有去深挖其原理所在,這個時候我們的競爭力就在這被拉下了。所以呢,深入學習原理對於提升自身的核心競爭力是一個必不可少的過程。
在接下來的這篇文章中,我們將講解面試中很愛考的虛擬DOM以及其背後的diff演算法。 請認真閱讀本文~文末有學習資源免費共享!!!
虛擬DOM是用JavaScript對象描述DOM的層次結構。DOM中的一切屬性都在虛擬DOM中有對應的屬性。本質上是JS 和 DOM 之間的一個映射緩存。
要點:虛擬 DOM 是 JS 對象;虛擬 DOM 是對真實 DOM 的描述。
diff發生在虛擬DOM上。diff演算法是在新虛擬DOM和老虛擬DOM進行diff(精細化比對),實現最小量更新,最後反映到真正的DOM上。
我們前面知道diff演算法發生在虛擬DOM上,而虛擬DOM是如何實現的呢?實際上虛擬DOM是有一個個虛擬節點組成。
h函數用來產生虛擬節點(vnode)。虛擬節點有如下的屬性:
1)sel: 標簽類型,例如 p、div;
2)data: 標簽上的數據,例如 style、class、data-*;
3)children :子節點;
4) text: 文本內容;
5)elm:虛擬節點綁定的真實 DOM 節點;
通過h函數的嵌套,從而得到虛擬DOM樹。
我們編寫了一個低配版的h函數,必須傳入3個參數,重載較弱。
形態1:h('div', {}, '文字')
形態2:h('div', {}, [])
形態3:h('div', {}, h())
首先定義vnode節點,實際上就是把傳入的參數合成對象返回。
[圖片上傳失敗...(image-7a9966-1624019394657)]
然後編寫h函數,根據第三個參數的不同進行不同的響應。
當我們進行比較的過程中,我們採用的4種命中查找策略:
1)新前與舊前:命中則指針同時往後移動。
2)新後與舊後:命中則指針同時往前移動。
3)新後與舊前:命中則涉及節點移動,那麼新後指向的節點,移到 舊後之後 。
4)新前與舊後:命中則涉及節點移動,那麼新前指向的節點,移到 舊前之前 。
命中上述4種一種就不在命中判斷了,如果沒有命中,就需要循環來尋找,移動到舊前之前。直到while(新前<=新後&&舊前<=就後)不成立則完成。
如果是新節點先循環完畢,如果老節點中還有剩餘節點(舊前和舊後指針中間的節點),說明他們是要被刪除的節點。
如果是舊節點先循環完畢,說明新節點中有要插入的節點。
1.什麼是Virtual DOM 和Snabbdom
2.手寫底層源碼h函數
3.感受Vue核心演算法之diff演算法
4.snabbdom之核心h函數的工作原理
1、零基礎入門或者有一定基礎的同學、大中院校學生
2、在職從事相關工作1-2年以及打算轉行前端的朋友
3、對前端開發有興趣人群
C. Hermes源碼分析(二)——解析位元組碼
前面一節 講到位元組碼序列化為二進制是有固定的格式的,這里我們分析一下源碼裡面是怎麼處理的
這里可以看到首先寫入的是魔數,他的值為
對應的二進制見下圖,注意是小端位元組序
第二項是位元組碼的版本,筆者的版本是74,也即 上圖中的4a00 0000
第三項是源碼的hash,這里採用的是SHA1演算法,生成的哈希值是160位,因此佔用了20個位元組
第四項是文件長度,這個欄位是32位的,也就是下圖中的為0aa030,轉換成十進制就是696368,實際文件大小也是這么多
後面的欄位類似,就不一一分析了,頭部所有欄位的類型都可以在 BytecodeFileHeader.h 中看到,Hermes按照既定的內存布局把欄位寫入後再序列化,就得到了我們看到的位元組碼文件。
這里寫入的數據很多,以函數頭的寫入為例,我們調用了visitFunctionHeader方法,並通過byteCodeMole拿到函數的簽名,將其寫入函數表(存疑,在實際的文件中並沒有看到這一部分)。注意這些數據必須按順序寫入,因為讀出的時候也是按對應順序來的。
我們知道react-native 在載入位元組碼的時候需要調用hermes的prepareJavaScript方法, 那這個方法做了些什麼事呢?
這里做了兩件事情:
1. 判斷是否是位元組碼,如果是則調用createBCProviderFromBuffer,否則調用createBCProviderFromSrc,我們這里只關注createBCProviderFromBuffer
2.通過BCProviderFromBuffer的構造方法得到文件頭和函數頭的信息(populateFromBuffer方法),下面是這個方法的實現。
BytecodeFileFields的populateFromBuffer方法也是一個模版方法,注意這里調用populateFromBuffer方法的是一個 ConstBytecodeFileFields對象,他代表的是不可變的位元組碼欄位。
細心的讀者會發現這里也有visitFunctionHeaders方法, 這里主要為了復用visitBytecodeSegmentsInOrder的邏輯,把populator當作一個visitor來按順序讀取buffer的內容,並提前載入到BytecodeFileFields裡面,以減少後面執行位元組碼時解析的時間。
Hermes引擎在讀取了位元組碼之後會通過解析BytecodeFileHeader這個結構體中的欄位來獲取一些關鍵信息,例如bundle是否是位元組碼格式,是否包含了函數,位元組碼的版本是否匹配等。注意這里我們只是解析了頭部,沒有解析整個位元組碼,後面執行位元組碼時才會解析剩餘的部分。
evaluatePreparedJavaScript這個方法,主要是調用了HermesRuntime的 runBytecode方法,這里hermesPrep時上一步解析頭部時獲取的BCProviderFromBuffer實例。
runBytecode這個方法比較長,主要做了幾件事情:
這里說明一下,Domain是用於垃圾回收的運行時模塊的代理, Domain被創建時是空的,並跟隨著運行時模塊進行傳播, 在運行時模塊的整個生命周期內都一直存在。在某個Domain下創建的所有函數都會保持著對這個Domain的強引用。當Domain被回收的時候,這個Domain下的所有函數都不能使用。
未完待續。。。
D. GBDT原理詳解及sklearn源碼解析
以下關於GBM和GBDT的理解來自經典論文[greedy function approximation: a gradient boosting machine],by Jerome H.Friedman,( https://github.com/LouisScorpio/datamining/tree/master/papers )
論文的整體思路:
1.函數空間的數值優化
對中稿信演算法中損失函數的數值優化不是在參數空間,而是在函數空間,數值優化方式為梯度下降;
2.GBM
以加法模型為基礎,使用上述優化方法,構建一套通用梯度提升演算法GBM;
3.不同損失類型的GBM
具體展現使用不同類型的損失時的GBM;
4.GBDT
以CART回歸樹為加法模型的弱分類器,構建演算法模型即GBDT。
首先,考慮加法模型,即最終分類器是由多個弱分類器線性相加的結果,表示為以下形式:
(1)
其中,h(x;a)是弱分類器,是關於輸入特徵x的函數,a是函數的參數(如果h(x;a)為回歸樹,那麼a就是回歸樹中用於分裂的特徵、特徵的分裂點以及樹的葉子節點的分數),M是弱分類器的數量, 為弱分類器的權重(在GBDT中相當於learning_rate,即起到shrinkage的作用)。
假設預測的目標函數為F(x; P),其中P為參數,損失為L,那麼損失函數表示為:
對應參數P的最優解表示為:
考慮使用梯度下降的優化方式,首先計算損失函數 對參數P的梯度 :
然後,對參數P沿著負梯度方向即- 方向更新,更新的步長為:
其中 是在負梯度方向上更新參數的最優步長,通過以下方式線性搜索得到:
從初始值 開始,經過多次這樣的更新迭代之後,參數P的值最終為:
以上為參數空間的數值優化。
在函數空間,假設預測的目賣輪標函數為F(x),損失為L,那麼損失函數表示為:
注意,這里損失函數的參數不再是P,而是函數 。
按照梯度下降的優化方式,這里要計敬前算損失函數 對函數F的梯度 :
然後對函數沿著負梯度方向更新,更新的步長如下:
其中 是在負梯度方向上更新參數的最優步長,通過以下方式線性搜索得到:
經過多次迭代之後,最終的函數結果為:
考慮(1)中的加法模型形式,可以得到
假設損失為L,那麼
根據函數空間的數值優化, 應該對應於負梯度:
在模型訓練時,負梯度 是基於樣本點進行的估計,為了提高泛化能力,一種可行的解決辦法是讓 去擬合負梯度 ,由此得到:
擬合學習到的 作為加法模型的弱學習器。加法模型的步長通過線性搜索的方式得到:
綜上,GBM整個演算法流程如下:
E. c語言暴力破解linux系統密碼文件「/etc/shadow」的程序源代碼
這個文件中的密碼是局神RSA加密演算法好沖加密的,很難破解,你可以用窮舉演算法來試試(^_^)
例如:a a的密碼
b b的密碼
...
aa aa的密碼
bb bb的密碼
...
aaa aaa的密碼
...
只要你還能用友臘殲sudo命令,就能重新輸入密碼:sudo passwd root
F. 哪位大俠有python版本的3DES(雙倍長32位密鑰)的加解密演算法源碼幫忙提供一
#if !defined(_CRYPT3DES_H) #define _CRYPT3DES_H #if !defined(ED_FLAG) #define ED_FLAG #define encrypt 0 #define decrypt 1 #endif #ifndef _WINDOWS_ #include "windows.h" #endif
G. Android socket源碼解析(三)socket的connect源碼解析
上一篇文章著重的聊了socket服務端的bind,listen,accpet的邏輯。本文來著重聊聊connect都做了什麼?
如果遇到什麼問題,可以來本文 https://www.jianshu.com/p/da6089fdcfe1 下討論
當服務端一切都准備好了。客戶端就會嘗試的通過 connect 系統調用,嘗試的和服務端建立遠程連接。
首先校驗當前socket中是否有正確的目標地址。然後獲取IP地址和埠調用 connectToAddress 。
在這個方法中,能看到有一個 NetHooks 跟蹤socket的調用,也能看到 BlockGuard 跟蹤了socket的connect調用。因此可以hook這兩個地方跟蹤socket,不過很少用就是了。
核心方法是 socketConnect 方法,這個方法就是調用 IoBridge.connect 方法。同理也會調用到jni中。
能看到也是調用了 connect 系統調用。
文件:/ net / ipv4 / af_inet.c
在這個方法中做的事情如下:
注意 sk_prot 所指向的方法是, tcp_prot 中 connect 所指向的方法,也就是指 tcp_v4_connect .
文件:/ net / ipv4 / tcp_ipv4.c
本質上核心任務有三件:
想要能夠理解下文內容,先要明白什麼是路由表。
路由表分為兩大類:
每個路由器都有一個路由表(RIB)和轉發表 (fib表),路由表用於決策路由,轉發表決策轉發分組。下文會接觸到這兩種表。
這兩個表有什麼區別呢?
網上雖然給了如下的定義:
但實際上在Linux 3.8.1中並沒有明確的區分。整個路由相關的邏輯都是使用了fib轉發表承擔的。
先來看看幾個和FIB轉發表相關的核心結構體:
熟悉Linux命令朋友一定就能認出這裡面大部分的欄位都可以通過route命令查找到。
命令執行結果如下:
在這route命令結果的欄位實際上都對應上了結構體中的欄位含義:
知道路由表的的內容後。再來FIB轉發表的內容。實際上從下面的源碼其實可以得知,路由表的獲取,實際上是先從fib轉發表的路由字典樹獲取到後在同感加工獲得路由表對象。
轉發表的內容就更加簡單
還記得在之前總結的ip地址的結構嗎?
需要進行一次tcp的通信,意味著需要把ip報文准備好。因此需要決定源ip地址和目標IP地址。目標ip地址在之前通過netd查詢到了,此時需要得到本地發送的源ip地址。
然而在實際情況下,往往是面對如下這么情況:公網一個對外的ip地址,而內網會被映射成多個不同內網的ip地址。而這個過程就是通過DDNS動態的在內存中進行更新。
因此 ip_route_connect 實際上就是選擇一個緩存好的,通過DDNS設置好的內網ip地址並找到作為結果返回,將會在之後發送包的時候填入這些存在結果信息。而查詢內網ip地址的過程,可以成為RTNetLink。
在Linux中有一個常用的命令 ifconfig 也可以實現類似增加一個內網ip地址的功能:
比如說為網卡eth0增加一個IPV6的地址。而這個過程實際上就是調用了devinet內核模塊設定好的添加新ip地址方式,並在回調中把該ip地址刷新到內存中。
注意 devinet 和 RTNetLink 嚴格來說不是一個存在同一個模塊。雖然都是使用 rtnl_register 注冊方法到rtnl模塊中:
文件:/ net / ipv4 / devinet.c
文件:/ net / ipv4 / route.c
實際上整個route模塊,是跟著ipv4 內核模塊一起初始化好的。能看到其中就根據不同的rtnl操作符號注冊了對應不同的方法。
整個DDNS的工作流程大體如下:
當然,在tcp三次握手執行之前,需要得到當前的源地址,那麼就需要通過rtnl進行查詢內存中分配的ip。
文件:/ include / net / route.h
這個方法核心就是 __ip_route_output_key .當目的地址或者源地址有其一為空,則會調用 __ip_route_output_key 填充ip地址。目的地址為空說明可能是在回環鏈路中通信,如果源地址為空,那個說明可能往目的地址通信需要填充本地被DDNS分配好的內網地址。
在這個方法中核心還是調用了 flowi4_init_output 進行flowi4結構體的初始化。
文件:/ include / net / flow.h
能看到這個過程把數據中的源地址,目的地址,源地址埠和目的地址埠,協議類型等數據給記錄下來,之後內網ip地址的查詢與更新就會頻繁的和這個結構體進行交互。
能看到實際上 flowi4 是一個用於承載數據的臨時結構體,包含了本次路由操作需要的數據。
執行的事務如下:
想要弄清楚ip路由表的核心邏輯,必須明白路由表的幾個核心的數據結構。當然網上搜索到的和本文很可能大為不同。本文是基於LInux 內核3.1.8.之後的設計幾乎都沿用這一套。
而內核將路由表進行大規模的重新設計,很大一部分的原因是網路環境日益龐大且復雜。需要全新的方式進行優化管理系統中的路由表。
下面是fib_table 路由表所涉及的數據結構:
依次從最外層的結構體介紹:
能看到路由表的存儲實際上通過字典樹的數據結構壓縮實現的。但是和常見的字典樹有點區別,這種特殊的字典樹稱為LC-trie 快速路由查找演算法。
這一篇文章對於快速路由查找演算法的理解寫的很不錯: https://blog.csdn.net/dog250/article/details/6596046
首先理解字典樹:字典樹簡單的來說,就是把一串數據化為二進制格式,根據左0,右1的方式構成的。
如圖下所示:
這個過程用圖來展示,就是沿著字典樹路徑不斷向下讀,比如依次讀取abd節點就能得到00這個數字。依次讀取abeh就能得到010這個數字。
說到底這種方式只是存儲數據的一種方式。而使用數的好處就能很輕易的找到公共前綴,在字典樹中找到公共最大子樹,也就找到了公共前綴。
而LC-trie 則是在這之上做了壓縮優化處理,想要理解這個演算法,必須要明白在 tnode 中存在兩個十分核心的數據:
這負責什麼事情呢?下面就簡單說說整個lc-trie的演算法就能明白了。
當然先來看看方法 __ip_dev_find 是如何查找
文件:/ net / ipv4 / fib_trie.c
整個方法就是通過 tkey_extract_bits 生成tnode中對應的葉子節點所在index,從而通過 tnode_get_child_rcu 拿到tnode節點中index所對應的數組中獲取葉下一級別的tnode或者葉子結點。
其中查找index最為核心方法如上,這個過程,先通過key左移動pos個位,再向右邊移動(32 - bits)演算法找到對應index。
在這里能對路由壓縮演算法有一定的理解即可,本文重點不在這里。當從路由樹中找到了結果就返回 fib_result 結構體。
查詢的結果最為核心的就是 fib_table 路由表,存儲了真正的路由轉發信息
文件:/ net / ipv4 / route.c
這個方法做的事情很簡單,本質上就是想要找到這個路由的下一跳是哪裡?
在這裡面有一個核心的結構體名為 fib_nh_exception 。這個是指fib表中去往目的地址情況下最理想的下一跳的地址。
而這個結構體在上一個方法通過 find_exception 獲得.遍歷從 fib_result 獲取到 fib_nh 結構體中的 nh_exceptions 鏈表。從這鏈表中找到一模一樣的目的地址並返回得到的。
文件:/ net / ipv4 / tcp_output.c
H. 求RSA加密解密演算法,c++源代碼
//下面程序由520huiqin編寫,已在VC++ 6.0下編譯通過
#include <iostream.h>
#include <math.h>
#include <stdio.h>
typedef int Elemtype;
Elemtype p,q,e;
Elemtype fn;
Elemtype m,c;
int flag = 0;
typedef void (*Msghandler) (void);
struct MsgMap {
char ch;
Msghandler handler;
};
/* 公鑰 */
struct PU {
Elemtype e;
Elemtype n;
} pu;
/* 私鑰 */
struct PR {
Elemtype d;
Elemtype n;
} pr;
/* 判定一個數是否為素數 */
bool test_prime(Elemtype m) {
if (m <= 1) {
return false;
}
else if (m == 2) {
return true;
}
else {
for(int i=2; i<=sqrt(m); i++) {
if((m % i) == 0) {
return false;
break;
}
}
return true;
}
}
/* 將十進制數據轉化為二進制數組 */
void switch_to_bit(Elemtype b, Elemtype bin[32]) {
int n = 0;
while( b > 0) {
bin[n] = b % 2;
n++;
b /= 2;
}
}
/* 候選菜單,主界面 */
void Init() {
cout<<"*********************************************"<<endl;
cout<<"*** Welcome to use RSA encoder ***"<<endl;
cout<<"*** a.about ***"<<endl;
cout<<"*** e.encrypt ***"<<endl;
cout<<"*** d.decrypt ***"<<endl;
cout<<"*** s.setkey ***"<<endl;
cout<<"*** q.quit ***"<<endl;
cout<<"**********************************by*Terry***"<<endl;
cout<<"press a key:"<<endl;
}
/* 將兩個數排序,大的在前面*/
void order(Elemtype &in1, Elemtype &in2) {
Elemtype a = ( in1 > in2 ? in1 : in2);
Elemtype b = ( in1 < in2 ? in1 : in2);
in1 = a;
in2 = b;
}
/* 求最大公約數 */
Elemtype gcd(Elemtype a, Elemtype b) {
order(a,b);
int r;
if(b == 0) {
return a;
}
else {
while(true) {
r = a % b;
a = b;
b = r;
if (b == 0) {
return a;
break;
}
}
}
}
/* 用擴展的歐幾里得演算法求乘法逆元 */
Elemtype extend_euclid(Elemtype m, Elemtype bin) {
order(m,bin);
Elemtype a[3],b[3],t[3];
a[0] = 1, a[1] = 0, a[2] = m;
b[0] = 0, b[1] = 1, b[2] = bin;
if (b[2] == 0) {
return a[2] = gcd(m, bin);
}
if (b[2] ==1) {
return b[2] = gcd(m, bin);
}
while(true) {
if (b[2] ==1) {
return b[1];
break;
}
int q = a[2] / b[2];
for(int i=0; i<3; i++) {
t[i] = a[i] - q * b[i];
a[i] = b[i];
b[i] = t[i];
}
}
}
/* 快速模冪演算法 */
Elemtype molar_multiplication(Elemtype a, Elemtype b, Elemtype n) {
Elemtype f = 1;
Elemtype bin[32];
switch_to_bit(b,bin);
for(int i=31; i>=0; i--) {
f = (f * f) % n;
if(bin[i] == 1) {
f = (f * a) % n;
}
}
return f;
}
/* 產生密鑰 */
void proce_key() {
cout<<"input two primes p and q:";
cin>>p>>q;
while (!(test_prime(p)&&test_prime(q))){
cout<<"wrong input,please make sure two number are both primes!"<<endl;
cout<<"input two primes p and q:";
cin>>p>>q;
};
pr.n = p * q;
pu.n = p * q;
fn = (p - 1) * (q - 1);
cout<<"fn = "<<fn<<endl;
cout<<"input e :";
cin>>e;
while((gcd(fn,e)!=1)) {
cout<<"e is error,try again!";
cout<<"input e :";
cin>>e;
}
pr.d = (extend_euclid(fn,e) + fn) % fn;
pu.e = e;
flag = 1;
cout<<"PR.d: "<<pr.d<<" PR.n: "<<pr.n<<endl;
cout<<"PU.e: "<<pu.e<<" PU.n: "<<pu.n<<endl;
}
/* 加密 */
void encrypt() {
if(flag == 0) {
cout<<"setkey first:"<<endl;
proce_key();
}
cout<<"input m:";
cin>>m;
c = molar_multiplication(m,pu.e,pu.n);
cout<<"c is:"<<c<<endl;
}
/* 解密 */
void decrypt() {
if(flag == 0) {
cout<<"setkey first:"<<endl;
proce_key();
}
cout<<"input c:";
cin>>c;
m = molar_multiplication(c,pr.d,pr.n);
cout<<"m is:"<<m<<endl;
}
/* 版權信息 */
void about() {
cout<<"*********************************************"<<endl;
cout<<"*** by Terry ***"<<endl;
cout<<"*** right 2010,All rights reserved by ***"<<endl;
cout<<"*** Terry,technology supported by weizuo !***"<<endl;
cout<<"*** If you have any question, please mail ***"<<endl;
cout<<"*** to [email protected] ! ***"<<endl;
cout<<"*** Computer of science and engineering ***"<<endl;
cout<<"*** XiDian University 2010-4-29 ***"<<endl;
cout<<"*********************************************"<<endl;
cout<<endl<<endl;
Init();
}
/* 消息映射 */
MsgMap Messagemap[] = {
{'a',about},
{'s',proce_key},
{'d',decrypt},
{'e',encrypt},
{'q',NULL}
};
/* 主函數,提供循環 */
void main() {
Init();
char d;
while((d = getchar())!='q') {
int i = 0;
while(Messagemap[i].ch) {
if(Messagemap[i].ch == d) {
Messagemap[i].handler();
break;
}
i++;
}
}
}
//歡迎分享,盜竊可恥
I. 演算法,源碼及實戰詳解 這本書怎麼樣
本書以Spark 1.4.1版本源碼為切入點,全面並且深入地解析Spark MLlib模塊,著力於探索分布式機器學習的底層實現。
本書循序漸進,首先解析MLlib的底層實現基礎:數據操作及矩陣向量計算操作,該部分是MLlib實現的基礎;
其次再對各個機器學習演算法的理論知識進行講解,並且解析機器學習演算法如何在MLlib中實現分布式計算;然後對MLlib源碼進行詳細的講解;
最後進行MLlib實例的講解。相信通過本書的學習,讀者可全面掌握Spark MLlib機器學習,能夠進行MLlib實戰、MLlib定製開發等。
J. 數據結構演算法實現及解析高一凡那本書里的源代碼,我把.cpp和.h裡面的代碼分別復制到在vc6.0上
不要相信書本里的源代碼,真心不管用,自己寫妥妥的