標籤: 新北清潔

  • 英國計畫開發可以同時照明和給電動汽車充電的街燈

    英國政府日前給倫敦市劃撥了4000萬英鎊資金,供其開發可以同時照明和給電動汽車充電的街燈。

    現在尚不清楚這種街燈將會是什麼樣子的,也不清楚它們將於何時推出。

    寶馬公司也在努力競標成為這個項目的贊助商。但是,倫敦的這個街燈項目尚未宣佈任何具體計畫。

    倫敦還斥資1300萬英鎊啟動了“未來鄉鄰”(neighborhoodsofthefuture)計畫。該計畫旨在給電動汽車司機提供免費的停車位以及交通優先權,例如可以使用公共汽車道,以鼓勵更多的人購買這種更環保的汽車。

    此外,倫敦當局證實,到2020年,該市將會出現3000輛混合動力巴士和300輛零排放單層巴士;而且,該市將會撥款60萬英鎊,讓倫敦的消防隊全部配備電動汽車。

    英國總共挑選了4個城市做試點,給它們分別撥款數百萬英鎊讓其推行電動汽車。其中布里斯托爾市還推出了一項為期四周的先試駕再購買的活動,以促使對電動汽車有興趣的人進行購買。

    英國還劃撥了500萬英鎊研發資金,用於開發其他相關專案。例如,約克市的蓋有太陽頂棚的“停放小轎車換乘公共汽車進城”(parkandride)中心。它的設計就是為了減少空氣污染。

    英國還有一個6000萬英鎊的專案,該專案包括有一個名為goultralowcities的基金。該基金旨在支持電動汽車、投資低排放公共交通以及研發超長續航能力的電池。

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

    新北清潔公司,居家、辦公、裝潢細清專業服務

  • 特斯拉遭知名放空機構盯上!供需恐出包、股價下挫

    特斯拉遭知名放空機構盯上!供需恐出包、股價下挫

    知名放空機構香櫞研究(Citron Research)去(2015)年10月狙擊加拿大專業製藥公司Valeant Pharmaceuticals International Inc.、質疑該公司認列的藥品收入,導致其股價在一天內慘跌19%後,又在去年12月16日將「2016年年度放空標的」頭銜頒給以色列先進駕駛輔助系統(Advanced Driver Assistance Systems;ADAS)大廠Mobileye N.V.,使其股價一路滾落了42%。   現在,Citron把炮口轉向美國豪華電動車製造商特斯拉(Tesla Motors, Inc.),週二(3月1日)透過Twitter聲稱特斯拉供需出狀況、將使其股價在今年底下探100美元。Citron還指出,消息面看來對股價相當不利。   特斯拉1日聞訊逆勢下挫2.91%、收186.35美元。特斯拉預定3月31日首度公開專為大眾設計的次世代電動車「Model 3」。   其實,在特斯拉於2月中公布第4季財報前,Model X休旅車的產能問題、低油價恐衝擊電動車銷售量等疑慮,就不斷壓抑公司股價。   不過,當特斯拉維持2016年強勁的銷售預估不變、還聲稱會在2017年發售Model 3之後,股價就應聲反彈。   特斯拉執行長Elon Musk更對自家公司展現信心。洛杉磯時報、Forbes報導,Musk已在1月29日執行選擇權,斥資350萬美元購入總值1億美元的特斯拉股票(當時其股價還有191.20美元)。   Citron最近雖因準確看空Valeant、Mobileye而受到矚目,但其實據華爾街日報報導,該機構2013年對特斯拉的空方評價卻成效不彰,特斯拉2013年迄今股價仍上漲了37%。   Citron、Musk這次誰勝誰負,還得看特斯拉能否順利拉高Model X與Model S的產能,而在內華達州占地1,000萬平方英尺的電池廠房也需如期完工,才能生產較為平價的大眾車款Model 3。   (本文內容由授權使用;首圖來源: CC BY 2.0)

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

    ※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

    ※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

    ※評比南投搬家公司費用收費行情懶人包大公開

    ※幫你省時又省力,新北清潔一流服務好口碑

  • Nissan、BMW聯手 在美安裝更多電動車充電樁

    Nissan、BMW聯手 在美安裝更多電動車充電樁

    Nissan與BMW宣布,為共同強化各自旗下的電動車產品Nissan LEAF與BMW i3的客戶服務網,將合作在全美19州、共120處安裝雙頭快速充電樁。此舉未來可望刺激美國市場的電動車銷售量,並幫助電動車車主享有更便捷、廣泛的服務。

    Nissan LEAF是目前全球總銷量最好的電動車款之一,充電樁設備分布在社區、企業公司以及Nissan的經銷門市。與BMW的合作,將可加速提升充電樁設備的數量,強化用戶使用經驗同時開拓市場。

    BMW的北美電子移動經理Cliff Fietzek強調,與Nissan的合作還包含遠程行車的服務。一但電動車能提供更遠程的行駛距離,使用者就更能體驗電子移動科技的便利之處。

    這項合作將以Nissan的充電樁技術與BMW的DC快充技術為基礎,每座快充充電樁都有CHAdeMO與SAE Combo雙街頭,輸出功率50kW,可適用於Nissan LEAF與BMW i3兩種車款以及其他擁有快充埠的電動車。50kW的快充設備可在20-30分鐘之內將Nissan LEAF或BMW i3的電池充到80%左右,比目前最常見的Level 2 240V 充電服務的效率更好。

    Nissan與BMW的電動車充電樁服務範圍包括:加州、康乃迪克州、佛羅里達、喬治亞州、伊利諾州、印第安納州、馬里蘭州、明尼蘇達州、密蘇里州、新墨西哥州、內華達州、紐約、南北卡羅萊納州、俄亥俄州、賓州、田納西州、維吉尼亞州以及威斯康辛州。只要透過BWM i3的ConnectedDrive軟體,或者Nissan EZ-Charge智慧型手機app,就能快速找到充電站位置,電費可用Nissan EZ-Charge卡片結帳。

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

    ※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

    ※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

    南投搬家前需注意的眉眉角角,別等搬了再說!

    新北清潔公司,居家、辦公、裝潢細清專業服務

  • 基於 HTML5 + WebGL 實現 3D 挖掘機系統

    基於 HTML5 + WebGL 實現 3D 挖掘機系統

    前言

    在工業互聯網以及物聯網的影響下,人們對於机械的管理,机械的可視化,机械的操作可視化提出了更高的要求。如何在一個系統中完整的显示机械的運行情況,机械的運行軌跡,或者机械的机械動作顯得尤為的重要,因為這會幫助一個不了解這個机械的小白可以直觀的了解机械的運行情況,以及机械的所有可能發生的動作,對於三一或者其它國內國外重工机械的公司能夠有一個更好的展示或者推廣。
    挖掘機,又稱挖掘机械(excavating machinery),從近幾年工程机械的發展來看,挖掘機的發展相對較快,挖掘機已經成為工程建設中最主要的工程机械之一。所以該系統實現了對挖掘機的 3D 可視化,在傳統行業一般都是基於 Web SCADA 的前端技術來實現 2D 可視化監控,而且都是 2D 面板部分數據的監控,從後台獲取數據前台显示數據,但是對於挖掘機本身來說,挖掘機的模型,挖掘機的動作,挖掘機的運行可視化卻是更讓人眼前一亮的,所以該系統對於挖機的 3D 模型做出了動作的可視化,大體包括以下幾個方面:

    • 前進後退 — 用戶可以通過鍵盤 wasd 實現前後左右,或者點擊 2D 界面 WASD 來實現挖機的前進後退。
    • 機身旋轉 — 用戶可以通過鍵盤左右鍵實現機身的旋轉,或者點擊 2D 界面 < > 來實現挖機機身的旋轉。
    • 大臂旋轉 — 用戶可點擊 2D 界面第一個滑塊部分實現大臂的旋轉。
    • 小臂旋轉 — 用戶可點擊 2D 界面第二個滑塊部分實現小臂的旋轉。
    • 挖斗挖掘 — 用戶可點擊 2D 界面第三個滑塊部分實現挖斗部分的旋轉挖掘。
    • 挖機動畫 — 用戶可點擊 2D 界面鏟子圖標,點擊之後系統會把挖機本身幾個動畫做一個串聯展示。

    本篇文章通過對挖掘機可視化場景的搭建,挖機机械動作代碼的實現進行闡述,幫助我們了解如何使用  實現一個挖掘機的可視化。

    預覽地址: 

    界面效果預覽

    挖機机械運動效果

    通過上面 gif 圖片可以看出挖掘機的幾個主要動作。

    挖機挖斗運動效果

    滑動頁面的第三個滑桿控制挖斗的旋轉挖掘。

    挖機機身運動

    通過上面 gif 圖片可以看出挖掘機的前進後退以及機身旋轉幾個運動。

    場景搭建

    該 3D 場景中所有形狀都是用 HT 內部的牆面工具進行構建,通過設置牆面透明屬性 shape3d.transparent 為 true 以及對構建出的牆面進行貼圖來構造出場景中的類似建築的显示效果,具體的樣式可以參考 HT 的 ,場景效果:

    通過上圖我們可以看到場景中有許許多多的牆面建築,所以它們有許多相同的地方,例如樣式以及貼圖都是一樣的,所以在 HT 中可以通過批量的操作對這些牆面進行處理,批量的意思指的是在當前未處理的情況下的牆面圖元是一個個獨立繪製的模型,所以性能會比較差,而當一批圖元聚合成一個大模型進行一次性的繪製時,則會極大提高 WebGL 刷新性能,這就是批量所以要做的事情,具體可以參考 HT 的 

    該系統 2d 面板部分則也是通過 HT 的矢量進行繪製,面板部分主要包括當前挖機的作業情況,工作時間,保修信息,故障信息等,通過二維的方式展示這些數據信息,面板截圖:

    机械運動代碼分析

    該系統中挖機的動作是十分的重要和關鍵的,大小臂運動時液壓杠該如何運動,挖斗運動時液壓桿,旋轉點零件,以及連接到挖鬥上的零部件如何聯動起來是關鍵點,机械動畫中用到大部分數學知識進行點面位置的計算,以下是幾個關鍵的數學知識點作為基礎:

    在數學中,向量(也稱為幾何向量、矢量),指具有大小和方向的量。它可以形象化地表示為帶箭頭的線段。系統中會通過向量的叉乘算出與某個面垂直的向量即法向量,在計算挖斗旋轉時需要計算出與挖斗面垂直的法向量來進行點的計算,HT 中封裝了 ht.Math 的數學函數,裏面的 ht.Math.Vector2 指的即為二維向量,ht.Math.Vector3 則為三維的向量,可以傳入三個數值進行初始化向量,向量的原型中有 cross 方法用來計算兩個向量的法向量,例如以下偽代碼:

    1 var Vector3 = ht.Math.Vector3;
    2 var a = new Vector3([10, 10, 0]);
    3 var b = new Vector3([10, 0, 0]);
    4 var ab = a.clone().cross(b);

    以上代碼中 ab 即為計算法向量,a.clone 是為了避免 cross 運算會修改原本的 a 內容,所以克隆出一個新的向量進行叉乘,以下為示意圖:

    挖斗机械運動分析

    在進行挖斗部分的机械代碼時會將挖斗的位置以及挖斗所有連接點的設備轉化為相對於某個節點的相對位置,例如節點 A 在世界中的坐標為 [100, 100, 100],世界中還有一個節點 B,而且節點 B 的坐標為 [10, 10, 10] 則節點 A 相對於節點 B 的相對位置即為 [90, 90, 90],因為在計算挖斗的位置時,挖機可能此時已經運動到某一點或者旋轉到某一個軸,所以此時不能使用相對世界的坐標,需要使用相對挖機機身的相對坐標來進行計算,代碼中提供了 toLocalPostion(node, worldPosition) 用來將世界的坐標 worldPosition 轉化為相對 node 的相對坐標,以下為代碼實現:

    1 var Matrix4 = ht.Math.Matrix4,
    2 Vector3 = ht.Math.Vector3;
    3 var mat = new Matrix4().fromArray(this.getNodeMat(g3d, node)),
    4 matInverse = new Matrix4().getInverse(mat),
    5 position = new Vector3(worldPosition).applyMatrix4(matInverse);
    6 return position.toArray();

    該函數的返回值即為相對坐標,挖機中需要轉化的坐標為連接着挖斗以及小臂的兩個零部件,系統中用 armHinge 以及 bucketHinge 來分別表示小臂樞紐以及挖斗樞紐這兩個零部件,可以從側面來看挖斗的動作,從下圖可以看出,關鍵點是算出交點 P 的坐標,交點 P 的坐標則是以 armHinge 與 bucketHinge位置為圓心,armHinge 與 bucketHinge 的長度為半徑的兩個圓的交點,而且這兩個圓的圓心在挖斗旋轉的過程中是不斷變化的,所以需要通過數學計算不斷算出交點的位置,以下為示意圖:

    通過上圖可以知道交點的位置有兩個 p1 以及 p2,程序中通過計算圓心 1 與圓心 2 構成的向量 c2ToC1,以下為偽代碼:

    1 var Vector2 = ht.Math.Vector2;
    2 var c2ToC1 = new Vector2({ x: c1.x, y: c1.y }).sub(new Vector2({ x: c2.x, y: c2.y }));

    c1 和 c2 為 armHinge 以及 bucketHinge 的圓心坐標,接下來是計算圓心 2 與點 p1 以及 p2 構成的向量 c2ToP1 以及 c2ToP2,以下為偽代碼:

    1 var Vector2 = ht.Math.Vector2;
    2 var c2ToP1 = new Vector2({ x: p1.x, y: p1.y }).sub(new Vector2({ x: c2.x, y: c2.y }));
    3 var c2ToP2 = new Vector2({ x: p2.x, y: p2.y }).sub(new Vector2({ x: c2.x, y: c2.y }));

    通過上述操作我們可以獲得三個向量 c2ToC1c2ToP1c2ToP2 所以我們可以用到我上述講的向量叉乘的概念進行 p1 與 p2 點的選取,通過向量 c2ToC1 與 c2ToP1,以及向量 c2ToC1 與 c2ToP2 分別進行叉乘得到的結果肯定一個是大於 0 一個小於 0,二維向量的叉乘可以直接把它們視為 3d 向量,z軸補 0 的三維向量,不過二維向量叉乘的結果 result 不是向量而是數值,如果 result > 0 時,那麼 a 正旋轉到 b 的角度為 <180°,如果 k < 0,那麼 a 正旋轉到 b 的角度為 >180°,如果 k = 0 那麼a,b向量平行,所以通過上面的理論知識我們可以知道結果肯定是一個大於 0 一個小於 0,我們可以在程序中測下可以知道我們需要獲取的是大於 0 的那個點 P1,所以每次可以通過上述的方法進行兩個交點的選擇。

    以下為挖斗部分動畫的執行流程圖:

    通過上述運算之後我們可以獲取到最終需要的點 P 坐標,點 P 坐標即為挖斗與小臂連接部分的一個重要點,獲取該點之後我們可以通過 HT 中提供的 lookAtX 函數來實現接下來的操作,lookAtX 函數的作用為讓某個物體看向某一點,使用方式如下:

    1 node.lookAtX(position, 'bottom');

    node 即為需要看向某一個點的節點,position 為看向的點的坐標,第二個參數有六個枚舉值可以選擇,分別為 ‘bottom’,’back’,’front’,’top’,’right’,’left’,第二個參數的作用是當我們需要把某個物體看向某一個點的時候我們也要指定該物體的哪一個面看向該點,所以需要提供第二個參數來明確,獲取到該函數之後我們可以通過將 bucketHinge 看向點 P,armHinge 看向點 P,就可以保持這兩個連接的設備永遠朝向該點,以下為部分偽代碼:

    1 bucketHinge.lookAtX(P, 'front');
    2 armHinge.lookAtX(P, 'bottom');

    所以通過上述操作之後我們已經把挖斗部分的兩個關鍵零件的位置已經擺放正確,接下來是要正確的擺放與挖斗連接的小臂上液壓部分的位置,下一部分為介紹該節點如何進行擺放。

    液壓聯動分析

    在場景中我們可以看到液壓主要分為兩個部分,一部分為白色的較細的液壓桿,一部分為黑色的較厚的液壓桿,白色的液壓桿插在黑色的液壓桿中,所以在小臂或者挖斗旋轉的過程中我們要保持兩個節點始終保持相對的位置,通過上一步驟中我們可以知道 lookAtX 這個函數的作用,所以在液壓桿部分我們也是照樣用該函數來實現。

    在上一步我們獲取到了挖斗旋轉過程中的關鍵點 P,所以在挖斗旋轉的過程我們小臂上的液壓桿也要相應的進行變化,具體的操作就是將小臂的白色液壓桿的位置設置為上步中計算出來的點 P 的位置,當然需要把白色液壓桿的錨點進行相應的設置,之後讓白色液壓桿 lookAt 黑色液壓桿,同時讓黑色液壓桿 lookAt 白色液壓桿,這樣下來兩個液壓桿都在互相看着對方,所以它們呈現出來的效果就是白色液壓桿在黑色液壓桿中進行伸縮,以下為偽代碼:

    1 bucketWhite.p3(P);
    2 bucketWhite.lookAtX(bucketBlack.p3(), 'top');
    3 bucketBlack.lookAtX(P, 'bottom');

    代碼中 bucketWhite 節點即為小臂上白色液壓桿,bucketBlack 節點為小臂上黑色液壓桿,通過以上的設置就可以實現伸縮的動畫效果,以下為液壓的運行圖:

    同理挖機身上的大臂的液壓動作以及機身與大臂連接部分的液壓動作都是使用上面的方法來實現,以下為這兩部分的代碼:


     1 rotateBoom: (rotateVal) = >{
     2     excavatorBoomNode.setRotationX(dr * rotateVal);
     3     let archorVector = [0.5 - 0.5, 0.56 - 0.5, 0.22 - 0.5];
     4     let pos = projectUtil.toWorldPosition(g3d, excavatorBoomNode, archorVector);
     5     boomWhite.lookAtX(boomBlack.p3(), 'bottom');
     6     boomBlack.lookAtX(pos, 'top');
     7 },
     8 rotateArm: (rotateVal) = >{
     9     projectUtil.applyRelativeRotation(excavatorArmNode, excavatorBoomNode, -rotateVal);
    10     let archorVector = [0.585 - 0.5, 0.985 - 0.5, 0.17 - 0.5];
    11     let pos = projectUtil.toWorldPosition(g3d, excavatorArmNode, archorVector);
    12     armWhite.lookAtX(armBlack.p3(), 'bottom');
    13     armBlack.lookAtX(pos, 'top');
    14 }


    我將兩部分的運動封裝為兩個函數 rotateBoom 以及 rotateArm 分別是大臂與機身連接處的液壓運動與大臂上的液壓運動,在該部分中為了精確的獲取看向的點,我通過 toWorldPosition 方法將相對坐標轉化為世界坐標,相對坐標為黑白液壓桿的錨點坐標,轉化為相對大臂或者機身的世界坐標。

    基本運動分析

    挖機的基本運動包括前進後退,機身旋轉,這一部分會相對上面的運動簡單許多,在 HT 的三維坐標系中,不斷修改挖機機身的 x,y,z 的坐標值就可以實現挖機的前進後退,通過修改機身的 y 軸旋轉角度則可以控制機身的旋轉,當然挖機身體上的所有其它零部件需要吸附在機身身上,當機身進行旋轉時其它零部件則會進行相應的旋轉,在進行前進的時候挖機底部的履帶會進行對應的滾動,當然履帶我們這邊是用了一個履帶的貼圖貼在上面,當挖機前進的時候修改貼圖的偏移值就可以實現履帶的滾動,修改偏移值的偽代碼如下:

    1 node.s('shape3d.uv.offset', [x, y]);

    上面的 x,y 分別為 x 軸與 y 軸方向的偏移值,在挖機前進後退的過程中不斷修改 y 的值可以實現履帶的滾動效果,具體的文檔說明可以查看 

    在挖機前進後退的過程中我們可以 wasd 四個鍵同時按下,並且可以對按鍵進行一直的響應,在 js 中可以通過 document.addEventListener(‘keydown’, (e) => {}) 以及 document.addEventListener(‘keyup’, (e) => {}) 進行監聽,但是這隻能每次執行一次需要執行的動作,所以我們可以在外部起一個定時器,來執行 keydown 時候需要不斷執行的動作,可以用一個 keyMap 來記錄當前已經點擊的按鍵,在 keydown 的時候紀錄為 true 在 keyup 的時候記錄為 false,所以我們可以在定時器中判斷這個 bool 值,當為 true 的時候則執行相應的動作,否則不執行,以下為對應的部分關鍵代碼:


     1 let key_pressed = {
     2     65 : {
     3         status: false,
     4         action: turnLeft
     5     },
     6     87 : {
     7         status: false,
     8         action: goAhead
     9     },
    10     68 : {
    11         status: false,
    12         action: turnRight
    13     },
    14     83 : {
    15         status: false,
    16         action: back
    17     },
    18     37 : {
    19         status: false,
    20         action: bodyTurnLeft
    21     },
    22     39 : {
    23         status: false,
    24         action: bodyTurnRight
    25     }
    26 };
    27 setInterval(() = >{
    28     for (let key in key_pressed) {
    29         let {
    30             status,
    31             action
    32         } = key_pressed[key];
    33         if (status) {
    34             action();
    35         }
    36     }
    37 },
    38 50);
    39 document.addEventListener('keydown', (event) = >{
    40     let keyCode = event.keyCode;
    41     key_pressed[keyCode] && (key_pressed[keyCode].status = true);
    42     event.stopPropagation();
    43 },
    44 true);
    45 document.addEventListener('keyup', (event) = >{
    46     let keyCode = event.keyCode;
    47     key_pressed[keyCode] && (key_pressed[keyCode].status = false);
    48     event.stopPropagation();
    49 },
    50 true);


    從上面代碼可以看出我在 key_pressed 變量中記錄對應按鍵以及按鍵對應的 action 動作,在 keydown 與 keyup 的時候對應修改當前 key 的 status 的狀態值,所以可以在 Interval 中根據 key_pressed 這個變量的 status 值執行對應的 action 動作,以下為執行流程圖:

    HT 的輕量化,自適應讓當前系統在手機端也能流暢的運行,當然目前移動端與電腦端的 2D 圖紙部分是加載不同的圖紙,在移動端的 2D 部分只留下操作挖機的操作部分,其它部分進行了相應的捨棄,不然在移動端小屏幕下無法展示如此多的數據,在 3D 場景部分都是共用同一個場景,通過場景搭建部分的批量操作使得 3D 在手機端也十分流暢的運行,以下為手機端運行截圖:

    總結

    物聯網已經融入了現代生活,通過內嵌到机械設備中的电子設備,我們能夠完成對机械設備的運轉、性能的監控,以及對机械設備出現的問題進行及時的預警。在該系統 2D 面板監控部分就是對採集過來的數據進行可視化的展示,而且我們可以藉助大數據和物聯網技術,將一台台机械通過機載控制器、傳感器和無線通訊模塊,與一個龐大的網絡連接,每揮動一鏟、行動一步,都形成數據痕迹。大數據精準描繪出基礎建設開工率等情況,成為觀察固定資產投資等經濟變化的風向標。所以在實現上述挖機動作之後,通過與挖機傳感器進行連接之後,可以將挖掘機此時的真實動作通過數據傳遞到系統,系統則會根據動作進行相應的真實操作,真正實現了挖機與網絡的互聯互通。

    程序運行截圖:

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

    新北清潔公司,居家、辦公、裝潢細清專業服務

  • 自動任務調度 – Timer

    自動任務調度 – Timer

    一、概述:

    最近維護一個老項目,裏面使用的是Timer的時間調度器,以前沒接觸過,對着代碼鼓搗了半天,查閱了部分博客,最後總結出自己的見解,新項目一般是不會用這種老掉牙的時間調度器了,但是維護老項目還是用的着的。就當筆記記錄一下了,自己寫的才是符合自己的思路走向的。有時間再補上Quartz調度器,這個才是現在使用最多的。

    二、常用的三種調度器分類

    Java自帶的java.util.Timer類,這個類允許你調度一個java.util.TimerTask任務。使用這種方式可以讓你的程序按照某一個頻度執行,但不能在指定時間運行。

    使用Quartz,這是一個功能比較強大的的調度器,可以讓你的程序在指定時間執行,也可以按照某一個頻度執行,配置起來稍顯複雜。

    Spring3.0以後自帶的task,可以將它看成一個輕量級的Quartz,而且使用起來比Quartz簡單許多。

    三、使用Spring體系來完成代碼的搭建

    1、代碼結構:

                                      

     

     

     

    2、springContext.xml文件

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"
           default-lazy-init="true">
    
        <!--定義了一個TimerFactoryBean類,並且把ScheduledTimerTask類的實例作為需要調度的task。-->
        <bean id="timerFactory" class="org.springframework.scheduling.timer.TimerFactoryBean" lazy-init="false">
            <property name="scheduledTimerTasks">
                <list>
                    <ref local="scheduledTask1"/>
                    <ref local="scheduledTask2"/>
                </list>
            </property>
        </bean>
    
        <!--利用ScheduledTimerTask類來配置每個task的啟動時間延時,每次啟動之間的間隔,當然還有最重要的是需要運行那個對象,也就是MethodInvokingTimerTaskFactoryBean類的實例-->
        <bean id="scheduledTask1" class="org.springframework.scheduling.timer.ScheduledTimerTask">
            <property name="delay" value="0" />
            <property name="period" value="1000" />
            <property name="timerTask">
                <ref bean="methodInvokingTask1"/>
            </property>
        </bean>
    
        <bean id="scheduledTask2" class="org.springframework.scheduling.timer.ScheduledTimerTask">
            <property name="delay" value="0" />
            <property name="period" value="1000" />
            <property name="timerTask">
                <ref bean="methodInvokingTask2"/>
            </property>
        </bean>
    
        <!--利用spring提供的MethodInvokingTimerTaskFactoryBean類來實現來實現對對task類和方法的聲明,聲明目標對象和方法,從而使spring知道要運行那個類的那個方法-->
        <bean id="methodInvokingTask1" class="org.springframework.scheduling.timer.MethodInvokingTimerTaskFactoryBean">
            <property name="targetObject" ref="myTask1"/>
            <property name="targetMethod" value="run"/>
        </bean>
    
        <bean id="methodInvokingTask2" class="org.springframework.scheduling.timer.MethodInvokingTimerTaskFactoryBean">
            <property name="targetObject" ref="myTask2"/>
            <property name="targetMethod" value="run"/>
        </bean>
    
        <!--被指定自動任務的類對象-->
        <bean id="myTask1" class="com.turtle.test.MyTask">
            <property name="name" value="啟動一"/>
        </bean>
    
        <bean id="myTask2" class="com.turtle.test.MyTask_2">
            <property name="name" value="啟動二"/>
        </bean>
    
    </beans>

     

    3、MyTask文件

    package com.turtle.test;
    
    import java.util.TimerTask;
    
    /**
     * 自定義一個定時任務
     * 推薦是繼承自 TimerTask
     */
    public class MyTask extends TimerTask {
    
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        private static int i = 0;
    
        // 使用線程中的方法  run
        @Override
        public void run() {
            System.out.println("定時任務啟動"+name+"----出現了"+i++);
        }
    }

     

    4、MyTask_2文件

    package com.turtle.test;
    
    import java.util.TimerTask;
    
    /**
     * 自定義一個定時任務
     * 推薦是繼承自 TimerTask
     */
    public class MyTask_2 extends TimerTask {
    
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        private static int i = 0;
    
        // 使用線程中的方法  run
        @Override
        public void run() {
            System.out.println("定時任務啟動"+name+"----出現了"+i++);
        }
    }

     

     

    5、MyTestTask_Test_01

    package com.turtle.test;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class MyTestTask_Test_01 {
        public static void main(String[] args) {
            // 啟動測試
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("springContext.xml");
        }
    }

     

     

    6、結果:

     

                            

    四、總結:

    如果要使用TImer的調度器的話,推薦使用新的ScheduledExecutorService,這個目前沒使用,就沒進行代碼驗證了,推薦一博客,大概看了下,寫得挺好的

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

    ※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

    ※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

    ※評比南投搬家公司費用收費行情懶人包大公開

    ※幫你省時又省力,新北清潔一流服務好口碑

  • C表達式中的彙編指令

    C 表達式中的彙編指令

    asm 為 gcc 中的關鍵字,asm 表達式為在 C代碼中嵌套彙編指令,該表達式只是單純的替換出彙編代碼,並不對彙編代碼的含義進行解析。

    asm 表達式有兩種形式,第二種 asm-qualifiers 包含了 goto 語句。
    第一種形式為常見的用法,AssemblerTemplate 和 OutputOperands 必須存在, 其中 Clobbers 存在需要 InputOperands 也出現。

    asm asm-qualifiers ( AssemblerTemplate 
                     : OutputOperands 
                     [ : InputOperands
                     [ : Clobbers ] ])
    
    asm asm-qualifiers ( AssemblerTemplate 
                          : 
                          : InputOperands
                          : Clobbers
                          : GotoLabels)

    Qualifiers 的類型

    • volatile, 避免編譯器的優化
    • inline, 內斂限定符,最小的體積
    • goto, 包含跳轉指令

    參數

    • AssemblerTemplate
      – 彙編指令模板是包含彙編器指令的文字字符串,編輯器替換引用輸入,編譯器不會解析該指令的含義。
    • OutputOperands
      – 由 AssemblerTemplate 中的指令修改的C變量的逗號分隔列表,允許使用空列表。
    • InputOperands
      – 由 AssemblerTemplate 中的指令讀取的C變量的逗號分隔列表,允許使用空列表。
    • Clobbers
      – 用逗號分隔的寄存器列表或由 AssemblerTemplate 修改的值,不能出現在 OutputOperands 和 InputOperands 中被提及,允許使用空列表。
    • GotoLabels
      – 當使用asm的goto形式時,此部分包含 AssemblerTemplate 中的代碼可能跳轉到的所有C標籤的列表。

    AssemblerTemplate

    彙編指令由一個字符串給出,多條彙編指令結合在一起使用的時候,中間以 \r\t 隔開,如

    asm("inc %0\n\tinc %0" : "=r"(res) : "0"(res));
    
    /APP
    # 11 "asm.c" 1
            inc %rax
            inc %rax
    # 0 "" 2
    /NO_APPs

    需要轉義的字符:%, =, {, }, |

    故在ATT彙編中,對寄存器進行操作的需要雙 %%, 如 inc %%rax.

    OutputOperands

    操作數之間用逗號分隔。 每個操作數具有以下格式:

    [ [asmSymbolicName] ] constraint (cvariablename)
    • asmSymbolicName
      – 為操作數指定名稱,格式為 %[name]
      c // res = num asm("movq %[num], %[res]" : [res] "=r"(res) : [num] "m"(num));
      – 如果未指定名稱使用数字, 從 output 域開始,第一個參數為 %0, 一次類推, 這裏的 res 為 %0, num 為 %1
      c // res = num asm("movq %1, %0" : "=r"(res) : "m"(num));
    • constraint
      – 一個字符串常量,用於指定對操作數的存儲的 , 需要以 “=” 或 “+” 開頭
    • cvariablename
      – 指定一個C左值表達式來保存輸出,通常是一個變量名。 括號是語法的必需部分

    第一個參數為增加可讀性使用的,現在我們有代碼如下

    int64_t res;
    int64_t num = 1;
    
    asm("movq %[num], %[res]" : [res] "=r"(res) : [num] "m"(num));
    asm("movq %1, %0" : "=r"(res) : "m"(num));
    asm("movq %1, %0" : "=m"(res) : "m"(num));
    asm("movq %1, %0" : "=r"(res) : "r"(num));
    
    // 對應的彙編代碼, 只保留asm表達式中的代碼
    # 13 "asm.c" 1
            movq -16(%rbp), %rax  // asm-1
     # 0 "" 2
    /NO_APP
    
    /APP
     # 15 "asm.c" 1
            movq -16(%rbp), %rax  // asm-2
     # 0 "" 2
    /NO_APP
    
    /APP
     # 17 "asm.c" 1
            movq -16(%rbp), -8(%rbp)  // asm-3
     # 0 "" 2
    /NO_APP
    
    /APP
     # 19 "asm.c" 1
            movq %rax, %rax  // asm-4
     # 0 "" 2
    /NO_APP
    1. 使用名稱替換和数字替換效果一樣,見 asm-1 和 asm-2
    2. 約束的用法,這裏使用比較簡單通用的的兩種情況,r 為通過寄存器尋址操作,m 通過內存尋址操作,所以看到當約束了 r 就對應寄存器的操作。
    3. 結果保存在 res 也就是 cvariablename 中

    InputOperands

    輸入操作數使C變量和表達式中的值可用於彙編代碼。

    [ [asmSymbolicName] ] constraint (cexpression)
    • asmSymbolicName 和輸出列表的用法完全一致
    • constraint 約束不能使用 =+. 可以使用 “0”, 這表明在輸出約束列表中(從零開始)的條目,指定的輸入必須與輸出約束位於同一位置。
    int64_t res = 3;
    int64_t num = 1;
    asm("addq %1, %0" : "=g"(res) : "0"(num));
    
    // 輸入輸出位置相同
            movq    $3, -8(%rbp)
            movq    $1, -16(%rbp)
            movq    -16(%rbp), %rax
    /APP
    # 32 "asm.c" 1
            addq %rax, %rax
    # 0 "" 2
    /NO_APP
    • cexpression 可以不為左值,作為彙編表達式的輸入值即可

    Clobbers

    破壞列表,主要用於指示編譯器生成的彙編指令。

    從asm表達式中看到輸出操作數中列出條目的更改編譯器是可以確定的,但內聯彙編代碼可能不僅對輸出進行了修改。 例如,計算可能需要其他寄存器,或者處理器可能會由於特定彙編程序指令而破壞寄存器的值。 為了將這些更改通知編譯器,在Clobber列表中列出這些會產生副作用的條目。 破壞列表條目可以是寄存器名稱,也可以是特殊的破壞列表項(在下面列出)。 每個內容列表條目都是一個字符串常量,用雙引號引起來並用逗號分隔。

    • 寄存器

        ```c
        asm volatile("movc3 %0, %1, %2"
                : /* No outputs. */
                : "r"(from), "r"(to), "g"(count)
                : "%rbx", "%rcx", "%rdx", "memory");
      
        /APP
        # 25 "asm.c" 1
                movc3 %rax, %r8, -72(%rbp)
        # 0 "" 2
        /NO_APP
        ```
      
        可以看到使用到了 rax 寄存器,然後修改程序在 Clobbers 增加 %rax, 結果如下
      
        ```c
        asm volatile("movc3 %0, %1, %2"
                : /* No outputs. */
                : "r"(from), "r"(to), "g"(count)
                : "%rax", "%rbx", "%rcx", "%rdx", "memory");
      
        /APP
        # 25 "asm.c" 1
                movc3 %r8, %r9, -72(%rbp)
        # 0 "" 2
        /NO_APP
        ```
        編譯器在產生的彙編代碼中就未使用 %rax 寄存器了。
    • 特殊破壞列表項
      – “cc”, 表示彙編代碼修改了標誌寄存器
      – “memory”, 為了確保內存中包含正確的值,編譯器可能需要在執行asm之前將特定的寄存器值刷新到內存中

    編譯器為了破壞列表項的值受到破壞,當這些條目是寄存器時,不對其進行使用;為特殊參數時,重新刷新得到最新的值。

    約束

    • 一些基礎的約束
    約束名 說明
    whitespace 空白字符被忽略
    m 允許使用內存操作數,以及機器通常支持的任何類型的地址
    o 允許使用內存操作數,但前提是地址是可偏移的
    V 允許使用內存操作數,不可偏移的內存地址,與 “o’互斥
    r 允許在通用寄存器中使用的寄存器操作數,其中可以指定寄存器,如 a(%rax), b(%rbx)
    i 允許使用立即整數操作數
    n 允許使用具有已知數值的立即整數操作數, ‘I’, ‘J’, ‘K’, … ‘P’ 更應該使用 n
    F 允許使用浮點立即數
    g 允許使用任何寄存器,內存或立即數整數操作數,但非通用寄存器除外
    X 允許任何操作數, ‘0’, ‘1’, ‘2’, … ‘9’
    p 允許使用有效內存地址的操作數
    • 標識符約束
    標識符 說明
    = 表示此操作數是由該指令寫入的:先前的值將被丟棄並由新數據替換
    + 表示該操作數由指令讀取和寫入
    & 表示(在特定替代方法中)此操作數是早期指令操作數,它是在使用輸入操作數完成指令之前寫入的,故輸入操作數部分不能分配與輸出操作數相同的寄存器
    % 表示該操作數與後續操作數的可交換指令

    內核示例

    1. x86 的內存屏障指令。
    // 避免編譯器的優化,聲明此處內存可能發生破壞
    #define barrier() asm volatile("" ::: "memory")
    // 在32位的CPU下,lock 指令為鎖總線,加上一條內存操作指令就達到了內存屏障的作用,64位的cpu已經有新增的 *fence 指令可以使用
    // mb() 執行一個內存屏障作用的指令,為指定CPU操作;破壞列表聲明 cc memory 指示避免編譯器進行優化
    #ifdef CONFIG_X86_32
    #define mb() asm volatile(ALTERNATIVE("lock; addl $0,-4(%%esp)", "mfence", \
                                    X86_FEATURE_XMM2) ::: "memory", "cc")
    #define rmb() asm volatile(ALTERNATIVE("lock; addl $0,-4(%%esp)", "lfence", \
                                    X86_FEATURE_XMM2) ::: "memory", "cc")
    #define wmb() asm volatile(ALTERNATIVE("lock; addl $0,-4(%%esp)", "sfence", \
                                    X86_FEATURE_XMM2) ::: "memory", "cc")
    #else
    #define mb()    asm volatile("mfence":::"memory")
    #define rmb()   asm volatile("lfence":::"memory")
    #define wmb()   asm volatile("sfence" ::: "memory")
    #endif
    1. x86 下獲取 current 的值
    DECLARE_PER_CPU(struct task_struct *, current_task);
    
    #define this_cpu_read_stable(var)   percpu_stable_op("mov", var)
    
    static __always_inline struct task_struct *get_current(void)
    {
            return this_cpu_read_stable(current_task);
    }
    
    #define percpu_stable_op(op, var)           \
    ({                          \
            typeof(var) pfo_ret__;              \
            switch (sizeof(var)) {              \
            case 8:                     \
                    asm(op "q "__percpu_arg(P1)",%0"    \
                    : "=r" (pfo_ret__)          \
                    : "p" (&(var)));            \
                    break;                  \
            }                       \
            pfo_ret__;                  \
    })
    

    current_task 為一個 struct task_struct 類型的指針,追蹤宏調用,在x86-64 下命中了 case 8: 的彙編代碼, 展開的代碼為

    asm("mov" "q ""%%""gs" ":" "%" "P1"",%0" : "=r" (pfo_ret__) : "p" (&(current_task)));
    // 變換一下為
    asm("movq %%gs:%P1, %0" : "=r"(pfo_ret__) : "p"(&(current_task)));

    這行代碼的含義為將 約束輸入部分必須為有效的地址(p約束), 將CPU id(通過段寄存器gs和偏移通過GDT得到,這裏後文分析了)通過寄存器(r約束)賦值給 pfo_ret__.

    參考

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

    ※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

    ※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

    南投搬家前需注意的眉眉角角,別等搬了再說!

    新北清潔公司,居家、辦公、裝潢細清專業服務

  • go中的數據結構-切片slice

    go中的數據結構-切片slice

    1. 部分基本類型

      go中的類型與c的相似,常用類型有一個特例:byte類型,即字節類型,長度為,默認值是0;

    1 bytes = [5]btye{'h', 'e', 'l', 'l', 'o'}

      變量bytes的類型是[5]byte,一個由5個字節組成的數組。它的內存表示就是連起來的5個字節,就像C的數組。

    1.1 字符串

      字符串在Go語言內存模型中用一個2字長(64位,32位內存布局方式下)的數據結構表示。它包含一個指向字符串數據存儲地方的指針,和一個字符串長度數據如下圖:

      s是一個string類型的字符串,因為string類型不可變,對於多字符串共享同一個存儲數據是安全的。切分操作str[i:j]會得到一個新的2字長結構t,一個可能不同的但仍指向同一個字節序列(即上文說的存儲數據)的指針和長度數據。所以字符串切分不涉及內存分配或複製操作,其效率等同於傳遞下標。

    1.2 數組

      數組是內置(build-in)類型,是一組同類型數據的集合,它是值類型,通過從0開始的下標索引訪問元素值。數組類型定義了長度和元素類型。如, [4]int 類型表示一個四個整數的數組,其長度是固定的,長度是數組類型的一部分( [4]int 和 [5]int 是完全不同的類型)。 數組可以以常規的索引方式訪問,表達式 s[n] 訪問數組的第 n 個元素。數組不需要顯式的初始化;數組的零值是可以直接使用的,數組元素會自動初始化為其對應類型的零值。

    1  var a [4]int
    2  a[0] = 1
    3  i := a[0]
    4  // i == 1
    5  // a[2] == 0, int 類型的零值
    6 [5] int {1,2}  //長度為5的數組,其元素值依次為:1,2,0,0,0 。在初始化時沒有指定初值的元素將會賦值為其元素類型int的默認值0,string的默認值是"" 
    7 [...] int {1,2,3,4,5}  //長度為5的數組,其長度是根據初始化時指定的元素個數決定的 
    8 [5] int { 2:1,3:2,4:3}  //長度為5的數組,key:value,其元素值依次為:0,0,1,2,3。在初始化時指定了2,3,4索引中對應的值:1,2,3 
    9 [...] int {2:1,4:3}  //長度為5的數組,起元素值依次為:0,0,1,0,3。由於指定了最大索引4對應的值3,根據初始化的元素個數確定其長度為5賦值與使用

      Go的數組是值語義。一個數組變量表示整個數組,它不是指向第一個元素的指針(不像 C 語言的數組)。 當一個數組變量被賦值或者被傳遞的時候,實際上會複製整個數組。 (為了避免複製數組,你可以傳遞一個指向數組的指針,但是數組指針並不是數組。) 可以將數組看作一個特殊的struct,結構的字段名對應數組的索引,同時成員的數目固定。

    b := [2]string{"Penn", "Teller"}
    b := [...]string{"Penn", "Teller"}

      這兩種寫法, b 都是對應 [2]string 類型。

    2. 切片slice 

    2.1 結構

      切片類型的寫法是[]T ,T是切片元素的類型。和數組不同的是,切片類型並沒有給定固定的長度。切片的字面值和數組字面值很像,不過切片沒有指定元素個數,切片可以通過數組來初始化,也可以通過內置函數make()初始化。

    letters := []string{"a", "b", "c", "d"}   //直接初始化切片,[]表示是切片類型,{"a", "b", "c", "d"},初始化值依次是a,b,c,d.其cap=len=4
    s := letters [:]  //初始化切片s,是數組letters的引用(a slice referencing the storage of x)
    func make([]T, len, cap) []T    //使用內置函數 make 創建
    s :=make([]int,len,cap)    //通過內置函數make()初始化切片s,[]int 標識為其元素類型為int的切片
    s := arr[startIndex:endIndex]   //將arr中從下標startIndex到endIndex-1 下的元素創建為一個新的切片
    s := arr[startIndex:]   //缺省endIndex時將表示一直到arr的最後一個元素
    s := arr[:endIndex]   //缺省startIndex時將表示從arr的第一個元素開始
    s1 := s[startIndex:endIndex]   //通過切片s初始化切片s1

      slice可以從一個數組或一個已經存在的slice中再次聲明。slice通過array[i:j]來獲取,其中i是數組的開始位置,j是結束位置,但不包含array[j],它的長度是j-i

    1 var ar = [10]byte{'a','b','c','d','e','f','g','h','i','j'}   // 聲明一個含有10個元素元素類型為byte的數組
    2 var a, b []byte  // 聲明兩個含有byte的slice
    3 a = ar[2:5]  //現在a含有的元素: ar[2]、ar[3]和ar[4]
    4  
    5 // b是數組ar的另一個slice
    6 b = ar[3:5]// b的元素是:ar[3]和ar[4]

      一個slice是一個數組某個部分的引用。在內存中它是一個包含三個域的結構體:指向slice中第一個元素的指針ptr,slice的長度數據len,以及slice的容量cap。長度是下標操作的上界,如x[i]中i必須小於長度。容量是分割操作的上界,如x[i:j]中j不能大於容量。slice在Go的運行時庫中就是一個C語言動態數組的實現,在$GOROOT/src/pkg/runtime/runtime.h中定義:

    struct    Slice
    {    // must not move anything
        byte*    array;        // actual data
        uintgo    len;        // number of elements
        uintgo    cap;        // allocated number of elements
    };

      數組的slice會創建一份新的數據結構,包含一個指針,一個指針和一個容量數據。如同分割一個字符串,分割數組也不涉及複製操作,它只是新建了一個結構放置三個數據。如下圖:

      示例中,對[]int{2,3,5,7,11}求值操作會創建一個包含五個值的數組,並設置x的屬性來描述這個數組。分割表達式x[1:3]不重新分配內存數據,只寫了一個新的slice結構屬性來引用相同的存儲數據。上例中,長度為2–只有y[0]和y[1]是有效的索引,但是容量為4–y[0:4]是一個有效的分割表達式。

      因為slice分割操作不需要分配內存,也沒有通常被保存在堆中的slice頭部,這種表示方法使slice操作和在C中傳遞指針、長度對一樣廉價。

    2.2 擴容

      其實slice在Go的運行時庫中就是一個C語言動態數組的實現,要增加切片的容量必須創建一個新的、更大容量的切片,然後將原有切片的內容複製到新的切片。在對slice進行append等操作時,可能會造成slice的自動擴容。其擴容時的大小增長規則是:

    • 如果新的大小是當前大小2倍以上,則大小增長為新大小
    • 否則循環以下操作:如果當前大小小於1024,按每次2倍增長,否則每次按當前大小1/4增長。直到增長的大小超過或等於新大小。

      下面的例子將切片 s 容量翻倍,先創建一個2倍 容量的新切片 t ,複製 s 的元素到 t ,然後將 t 賦值給 s : 

    t := make([]byte, len(s), (cap(s)+1)*2) // +1 in case cap(s) == 0
    for i := range s {
            t[i] = s[i]
    }
    s = t

      循環中複製的操作可以由 copy 內置函數替代,返回複製元素的數目。此外, copy 函數可以正確處理源和目的切片有重疊的情況。

    一個常見的操作是將數據追加到切片的尾部。必要的話會增加切片的容量,最後返回更新的切片:

    func AppendByte(slice []byte, data ...byte) []byte {
        m := len(slice)
        n := m + len(data)
        if n > cap(slice) { // if necessary, reallocate
            // allocate double what's needed, for future growth.
            newSlice := make([]byte, (n+1)*2)
            copy(newSlice, slice)
            slice = newSlice
        }
        slice = slice[0:n]
        copy(slice[m:n], data)
        return slice
    }

      Go提供了一個內置函數 append,也實現了這樣的功能。

    func append(s []T, x ...T) []T
    //append 函數將 x 追加到切片 s 的末尾,並且在必要的時候增加容量。
    a := make([]int, 1)
    // a == []int{0}
    a = append(a, 1, 2, 3)
    // a == []int{0, 1, 2, 3}

      如果是要將一個切片追加到另一個切片尾部,需要使用 ... 語法將第2個參數展開為參數列表。

    a := []string{"John", "Paul"}
    b := []string{"George", "Ringo", "Pete"}
    a = append(a, b...) // equivalent to "append(a, b[0], b[1], b[2])"
    // a == []string{"John", "Paul", "George", "Ringo", "Pete"}

      由於切片的零值 nil 用起來就像一個長度為零的切片,我們可以聲明一個切片變量然後在循環 中向它追加數據:

    // Filter returns a new slice holding only
    // the elements of s that satisfy fn()
    func Filter(s []int, fn func(int) bool) []int {
        var p []int // == nil
        for _, v := range s {
            if fn(v) {
                p = append(p, v)
            }
        }
        return p
    }

    3. 使用切片需要注意的陷阱

      切片操作並不會複製底層的數組。整個數組將被保存在內存中,直到它不再被引用。 有時候可能會因為一個小的內存引用導致保存所有的數據。

      如下, FindDigits 函數加載整個文件到內存,然後搜索第一個連續的数字,最後結果以切片方式返回。

    var digitRegexp = regexp.MustCompile("[0-9]+")
    
    func FindDigits(filename string) []byte {
        b, _ := ioutil.ReadFile(filename)
        return digitRegexp.Find(b)
    }

      這段代碼的行為和描述類似,返回的 []byte 指向保存整個文件的數組。因為切片引用了原始的數組, 導致 GC 不能釋放數組的空間;只用到少數幾個字節卻導致整個文件的內容都一直保存在內存里。要修復整個問題,可以將需要的數據複製到一個新的切片中:

    func CopyDigits(filename string) []byte {
        b, _ := ioutil.ReadFile(filename)
        b = digitRegexp.Find(b)
        c := make([]byte, len(b))
        copy(c, b)
        return c
    }

      使用 append 實現一個更簡潔的版本:

        8  func CopyDigitRegexp(filename string) []byte {
        7     b,_ := ioutil.ReadFile(filename)
        6     b = digitRefexp.Find(b)
        5     var c []intb
        4    // for _,v := range b{
        3         c =append(c, b)
        2     //}
        1     return c
        0  }

    4. make和new

    Go有兩個數據結構創建函數:make和new,也是兩種不同的內存分配機制。

    make和new的基本的區別是new(T)返回一個*T,返回的是一個指針,指向分配的內存地址,該指針可以被隱式地消除引用)。而make(T, args)返回一個普通的T。通常情況下,T內部有一些隱式的指針。所以new返回一個指向已清零內存的指針,而make返回一個T類型的結構。更詳細的區別在後面內存分配的學習里研究。

    5. 數組和切片的區別

    • 數組長度不能改變,初始化后長度就是固定的;切片的長度是不固定的,可以追加元素,在追加時可能使切片的容量增大。
    • 結構不同,數組是一串固定數據,切片描述的是截取數組的一部分數據,從概念上說是一個結構體。
    • 初始化方式不同,如上。另外在聲明時的時候:聲明數組時,方括號內寫明了數組的長度或使用...自動計算長度,而聲明slice時,方括號內沒有任何字符。
    • unsafe.sizeof的取值不同,unsafe.sizeof(slice)返回的大小是切片的描述符,不管slice里的元素有多少,返回的數據都是24。unsafe.sizeof(arr)的值是在隨着arr的元素的個數的增加而增加,是數組所存儲的數據內存的大小。

      unsafe.sizeof總是在編譯期就進行求值,而不是在運行時,這意味着,sizeof的返回值可以賦值給常量。  在編譯期求值,還意味着可以獲得數組所佔的內存大小,因為數組總是在編譯期就指明自己的容量,並且在以後都是不可變的。

      unsafe.sizeof(string)時大小始終是16,不論字符串的len有多大,sizeof始終返回16,這是因為字符串類型對應一個結構體,該結構體有兩個域,第一個域是指向該字符串的指針,第二個域是字符串的長度,每個域佔8個字節,但是並不包含指針指向的字符串的內容。

    6. nil

      按照Go語言規範,任何類型在未初始化時都對應一個零值:布爾類型是false,整型是0,字符串是””,而指針,函數,interface,slice,channel和map的零值都是nil。

    interface

      一個interface在沒有進行初始化時,對應的值是nil。也就是說var v interface{},此時v就是一個nil。在底層存儲上,它是一個空指針。與之不同的情況是,interface值為空。比如:

    1 var v *T
    2 var i interface{}
    3 i = v

      此時i是一個interface,它的值是nil,但它自身不為nil。

    string

      string的空值是””,它是不能跟nil比較的。即使是空的string,它的大小也是兩個機器字長的。slice也類似,它的空值並不是一個空指針,而是結構體中的指針域為空,空的slice的大小也是三個機器字長的。

    channel

      channel跟string或slice有些不同,它在棧上只是一個指針,實際的數據都是由指針所指向的堆上面。

      跟channel相關的操作有:初始化/讀/寫/關閉。channel未初始化值就是nil,未初始化的channel是不能使用的。下面是一些操作規則:

    • 讀或者寫一個nil的channel的操作會永遠阻塞。
    • 讀一個關閉的channel會立刻返回一個channel元素類型的零值。
    • 寫一個關閉的channel會導致panic。

    map

      map也是指針,實際數據在堆中,未初始化的值是nil。

     

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

    新北清潔公司,居家、辦公、裝潢細清專業服務

  • 通俗地說邏輯回歸【Logistic regression】算法(二)sklearn邏輯回歸實戰

    通俗地說邏輯回歸【Logistic regression】算法(二)sklearn邏輯回歸實戰

    前情提要:

    上一篇主要介紹了邏輯回歸中,相對理論化的知識,這次主要是對上篇做一點點補充,以及介紹sklearn 邏輯回歸模型的參數,以及具體的實戰代碼。

    1.邏輯回歸的二分類和多分類

    上次介紹的邏輯回歸的內容,基本都是基於二分類的。那麼有沒有辦法讓邏輯回歸實現多分類呢?那肯定是有的,還不止一種。

    實際上二元邏輯回歸的模型和損失函數很容易推廣到多元邏輯回歸。比如總是認為某種類型為正值,其餘為0值。

    舉個例子,要分類為A,B,C三類,那麼就可以把A當作正向數據,B和C當作負向數據來處理,這樣就可以用二分類的方法解決多分類的問題,這種方法就是最常用的one-vs-rest,簡稱OvR。而且這種方法也可以方便得推廣到其他二分類模型中(當然其他算法可能有更好的多分類辦法)。

    另一種多元邏輯回歸的方法是Many-vs-Many(MvM),它會選擇一部分類別的樣本和另一部分類別的樣本來做邏輯回歸二分類。

    聽起來很不可思議,但其實確實是能辦到的。比如數據有A,B,C三個分類。

    我們將A,B作為正向數據,C作為負向數據,訓練出一個分模型。再將A,C作為正向數據,B作為負向數據,訓練出一個分類模型。最後B,C作為正向數據,C作為負向數據,訓練出一個模型。

    通過這三個模型就能實現多分類,當然這裏只是舉個例子,實際使用中有其他更好的MVM方法。限於篇幅這裏不展開了。

    MVM中最常用的是One-Vs-One(OvO)。OvO是MvM的特例。即每次選擇兩類樣本來做二元邏輯回歸。

    對比下兩種多分類方法,通常情況下,Ovr比較簡單,速度也比較快,但模型精度上沒MvM那麼高。MvM則正好相反,精度高,但速度上比不過Ovr。

    2.邏輯回歸的正則化

    所謂正則化,其目的是為了減弱邏輯回歸模型的精度,難道模型的準確度不是越高越好嘛?看看下面這張圖就明白了:

    左邊那個圖就是過擬合的情況,過擬合其實就是模型的精度太過高了,它能非常好得匹配訓練集的數據,但一旦有新的數據,就會表現得很差。

    而我們要的非過擬合的模型是,精度可以差一些,但泛化性能,也就是對新的數據的識別能力,要比較好。

    正則化就是減弱模型精度,提高泛化效果的這個東西。

    3.sklearn各個參數

    def LogisticRegression(penalty='l2', 
                                        dual=False, 
                                        tol=1e-4, 
                                        C=1.0,
                                        fit_intercept=True, 
                                        intercept_scaling=1, 
                                        class_weight=None,
                                        random_state=None, 
                                        solver='warn', 
                                        max_iter=100,
                                        multi_class='warn', 
                                        verbose=0, 
                                        warm_start=False, 
                                        n_jobs=None,
                                        l1_ratio=None
                                        )
    跟線性回歸一比,邏輯回歸的參數那還真是多啊,不過我們一個一個來看看參數都是什麼意思吧。                                 
    
    - dual:對偶或者原始方法,布爾類型,默認為False。Dual只適用於正則化相為l2的‘liblinear’的情況,通常樣本數大於特徵數的情況下,默認為False。
    
    - tol:停止迭代求解的閾值,單精度類型,默認為1e-4。
    
    - C:正則化係數的倒數,必須為正的浮點數,默認為 1.0,這個值越小,說明正則化效果越強。換句話說,這個值越小,越訓練的模型更泛化,但也更容易欠擬合。
    
    - fit_intercept:是否要使用截距(在決策函數中使用截距),布爾類型,默認為True。
    
    - intercept_scaling:官方解釋比較模糊,我說下個人理解。浮點型,默認值是1.0。這個參數僅在“solver”參數(下面介紹)為“liblinear”“fit_intercept ”參數為True的時候生效。作用是給特徵向量添加一個常量,這個常量就是intercept_scaling。比如原本的向量是[x],那麼添加后就變成[x,intercept_scaling]。
    
    - class_weight:分類權重,可以是一個dict(字典類型),也可以是一個字符串"balanced"字符串。默認是None,也就是不做任何處理,而"balanced"則會去自動計算權重,分類越多的類,權重越低,反之權重越高。也可以自己輸出一個字典,比如一個 0/1 的二元分類,可以傳入{0:0.1,1:0.9},這樣 0 這個分類的權重是0.1,1這個分類的權重是0.9。這樣的目的是因為有些分類問題,樣本極端不平衡,比如網絡攻擊,大部分正常流量,小部分攻擊流量,但攻擊流量非常重要,需要有效識別,這時候就可以設置權重這個參數。
    
    - random_state:設置隨機數種子,可以是int類型和None,默認是None。當"solver"參數為"sag"和"liblinear"的時候生效。
    
    - verbose:輸出詳細過程,int類型,默認為0(不輸出)。當大於等於1時,輸出訓練的詳細過程。僅當"solvers"參數設置為"liblinear"和"lbfgs"時有效。
    
    - warm_start:設置熱啟動,布爾類型,默認為False。若設置為True,則以上一次fit的結果作為此次的初始化,如果"solver"參數為"liblinear"時無效。
    
    - max_iter:最大迭代次數,int類型,默認-1(即無限制)。注意前面也有一個tol迭代限制,但這個max_iter的優先級是比它高的,也就如果限制了這個參數,那是不會去管tol這個參數的。
    

    OK,上述就是對一些比較簡略的參數的說明,但是還有幾個重要的參數沒講到,這是因為這幾個參數我覺得需要單獨拎出來講一講。

    sklearn邏輯回歸參數 –penalty

    正則化類型選擇,字符串類型,可選’l1’,’l2’,’elasticnet’和None,默認是’l2’,通常情況下,也是選擇’l2’。這個參數的選擇是會影響到參數’solver’的選擇的,下面會介紹。

    其中’l1’和’l2’。分別對應L1的正則化和L2的正則化,’elasticnet’則是彈性網絡(這玩意我也不大懂),默認是L2的正則化。

    在調參時如果主要的目的只是為了解決過擬合,一般penalty選擇L2正則化就夠了。但是如果選擇L2正則化發現還是過擬合,即預測效果差的時候,就可以考慮L1正則化。另外,如果模型的特徵非常多,我們希望一些不重要的特徵係數歸零,從而讓模型係數稀疏化的話,也可以使用L1正則化。

    penalty參數的選擇會影響我們損失函數優化算法的選擇。即參數solver的選擇,如果是L2正則化,那麼4種可選的算法{‘newton-cg’,‘lbfgs’,‘liblinear’,‘sag’}都可以選擇。但是如果penalty是L1正則化的話,就只能選擇‘liblinear’了。這是因為L1正則化的損失函數不是連續可導的,而{‘newton-cg’,‘lbfgs’,‘sag’}這三種優化算法時都需要損失函數的一階或者二階連續導數。而‘liblinear’並沒有這個依賴。最後還有一個’elasticnet’,這個只有solver參數為’saga’才能選。

    sklearn邏輯回歸參數 –solver

    優化算法參數,字符串類型,一個有五種可選,分別是”newton-cg”,”lbfgs”,”liblinear”,”sag”,”saga。默認是”liblinear”。分別介紹下各個優化算法:

    • a) liblinear:使用了開源的liblinear庫實現,內部使用了坐標軸下降法來迭代優化損失函數。
    • b) lbfgs:擬牛頓法的一種,利用損失函數二階導數矩陣即海森矩陣來迭代優化損失函數。
    • c) newton-cg:也是牛頓法家族的一種,利用損失函數二階導數矩陣即海森矩陣來迭代優化損失函數。
    • d) sag:即隨機平均梯度下降,是梯度下降法的變種,和普通梯度下降法的區別是每次迭代僅僅用一部分的樣本來計算梯度,適合於樣本數據多的時候。
      在優化參數的選擇上,官方是這樣建議的:
    • e)saga:優化的,無偏估計的sag方法。(‘sag’ uses a Stochastic Average Gradient descent, and ‘saga’ uses its improved, unbiased version named SAGA.)
      對小的數據集,可以選擇”liblinear”,如果是大的數據集,比如說大於10W的數據,那麼選擇”sag”和”saga”會讓訓練速度更快。

    對於多分類問題,只有newton-cg,sag,saga和lbfgs能夠處理多項損失(也就是MvM的情況,還記得上面說到的多分類嘛?),而liblinear僅處理(OvR)的情況。啥意思,就是用liblinear的時候,如果是多分類問題,得先把一種類別作為一個類別,剩餘的所有類別作為另外一個類別。一次類推,遍歷所有類別,進行分類。

    這個的選擇和正則化的參數也有關係,前面說到”penalty”參數可以選擇”l1″,”l2″和None。這裏’liblinear’是可以選擇’l1’正則和’l2’正則,但不能選擇None,’newton-cg’,’lbfgs’,’sag’和’saga’這幾種能選擇’l2’或no penalty,而’saga’則能選怎’elasticnet’正則。好吧,這部分還是挺繞的。

    歸納一下吧,二分類情況下,數據量小,一般默認的’liblinear’的行,數據量大,則使用’sag’。多分類的情況下,在數據量小的情況下,追求高精度,可以用’newton-cg’或’lbfgs’以’MvM’的方式求解。數據量一大還是使用’sag’。

    當然實際情況下還是要調參多次才能確定參數,這裏也只能給些模糊的建議。

    sklearn邏輯回歸參數 –multi_class

    multi_class參數決定了我們分類方式的選擇,有 ovr和multinomial兩個值可以選擇,默認是 ovr。
    ovr即前面提到的one-vs-rest(OvR),而multinomial即前面提到的many-vs-many(MvM)。如果是二元邏輯回歸,ovr和multinomial並沒有任何區別,區別主要在多元邏輯回歸上。

    4.sklearn實例

    實例這部分,就直接引用sklearn官網的,使用邏輯回歸對不同種類的鳶尾花進行分類的例子吧。

    import numpy as np
    import matplotlib.pyplot as plt
    from sklearn import linear_model, datasets
    
    # 加載鳶尾花數據
    iris = datasets.load_iris()
    # 只採用樣本數據的前兩個feature,生成X和Y
    X = iris.data[:, :2]  
    Y = iris.target
    
    h = .02  # 網格中的步長
    
    # 新建模型,設置C參數為1e5,並進行訓練
    logreg = linear_model.LogisticRegression(C=1e5)
    logreg.fit(X, Y)
    
    # 繪製決策邊界。為此我們將為網格 [x_min, x_max]x[y_min, y_max] 中的每個點分配一個顏色。
    x_min, x_max = X[:, 0].min() - .5, X[:, 0].max() + .5
    y_min, y_max = X[:, 1].min() - .5, X[:, 1].max() + .5
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
    Z = logreg.predict(np.c_[xx.ravel(), yy.ravel()])
    
    # 將結果放入彩色圖中
    Z = Z.reshape(xx.shape)
    plt.figure(1, figsize=(4, 3))
    plt.pcolormesh(xx, yy, Z, cmap=plt.cm.Paired)
    
    # 將訓練點也同樣放入彩色圖中
    plt.scatter(X[:, 0], X[:, 1], c=Y, edgecolors='k', cmap=plt.cm.Paired)
    plt.xlabel('Sepal length')
    plt.ylabel('Sepal width')
    
    plt.xlim(xx.min(), xx.max())
    plt.ylim(yy.min(), yy.max())
    plt.xticks(())
    plt.yticks(())
    
    plt.show()

    運行上面那段代碼會有如下的結果:

    可以看到,已將三種類型的鳶尾花都分類出來了。

    小結

    邏輯回歸算是比較簡單的一種分類算法,而由於簡單,所以也比較適合初學者初步接觸機器學習算法。學習了之後,對後面一些更複雜的機器學習算法,諸如Svm,或更高級的神經網絡也能有一個稍微感性的認知。

    而實際上,Svm可以看作是邏輯回歸的更高級的演化。而從神經網絡的角度,邏輯回歸甚至可以看作一個最初級,最淺層的神經網絡。

    邏輯回歸就像是金庸小說裏面,獨孤九劍的第一式,最為簡單,卻又是其他威力極大的招式的基礎,其他的招式都又第一式演化而出。

    夯實基礎,才能砥礪前行。

    以上~

    參考文章:

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

    ※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

    ※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

    ※評比南投搬家公司費用收費行情懶人包大公開

    ※幫你省時又省力,新北清潔一流服務好口碑

  • 計算機專業學生常用網站

    計算機專業學生常用網站

    這篇文章我很早之前就想寫了,但奈何一直沒有合適的時間,也不知道應該怎樣去表達。現在自己是一個大三的學生,學習了兩年多的計算機,我不知道自己算不算合格,和那些初中就搞OI,大三實習就業的大佬們相比,我肯定是不行的,但我覺得我這兩年的學習經歷應該更符合一般的計算機專業學生,可能也更有參考性吧。這裏我主要來分享一下我常用的一些網站,這些網站主要受眾還是學生,關注我的人也大都是同輩的學生,大家可以在評論區討論或補充,這篇博客也會一直更新下去。

    1、Google

    這個網站就不必說了吧,我一直有一種觀點,搜索資料對計算機專業的學生來說是一種特別重要的能力。現在的世界有那麼多的知識,誰也不可能都學會,咱們的大腦只是一個cache級別的存儲器,想要獲得知識、解決問題,學會使用搜索引擎必不可少!至於google被牆,使用百度也是可以的,但注意對信息要辨偽存真,多去比較。

    2、GitHub

    這是開發者最為重要的網站了吧,代碼託管網站。大概大一下學期才注意到這個網站,各種資源應有盡有,想要什麼輪子,上去搜就好了。最令我震驚的是,裏面還有這種網課學習資料,清華北大浙大等名校計算機專業所有課程的資料都齊全,互聯網時代真的太便利了。

     

    3、Stack Overflow

    這個網站也挺有用的,之前我查找一個C++問題,搜索的結果都答不到點上,在上面看到一個美國人的回答才如夢初醒。後來學習過程中遇到什麼 問題,上去搜一下,大概率能搜到答案。缺點也很明顯,這是一個英語網站,大多數回答都是英文,所以要有點計算機英語基礎。至於同樣英文網站的GitHub,我只能感慨,中國程序員太厲害了,中文項目一點也不少,可以說GitHub的繁榮離不開中國人。

     

    4、bilibili

    或許最初這隻是一個二次元愛好者的聚集地,但現在B站應該可以算是中國的油管吧。我在高中的時候入坑B站,申請會員需要答題就讓我知道這個網站應該有着素質不錯的成員,果然這裡有無數的沙雕網友逗你開心,逛B站也成為我高中生活最快樂的消遣方式。上了大學才發現B站也可以是一個學習網站,甚至中央都表揚過B站。跟着B站上的老師學了高數、離散數學、線性代數、数字邏輯、概率論、計算機組成原理等等,我一般是在課堂上聽一遍,課後快到考試周了跟着B站視頻複習一遍,很穩!我愛死這個小破站了!(不知道大家有沒有注意到我博客背景上的22娘33娘呢?)

    5、中國大學MOOC

     這個算是對B站的一個補充的,其實B站中很多學校視頻都是轉自這裏的,很多網課其實是侵權的,隨時可能被B站刪掉。雖然有些可能需要收費,但也還能接受,至少質量有保證。

    6、知乎

    為什麼要提知乎這個看似很無關的網站?我主要覺得這是一個可以用來增長見識的社區,雖然現在它一點點的向微博貼吧靠近。

    知乎給自己的定位是一個網絡問答社區,用戶在上面分享自己專業的知識和見解。據統計與計算機相關的用戶佔總用戶很大的比重,可能大家上班都在划水?

    也有很多大佬直接在知乎上寫博客,平時逛逛知乎把握一下行業動向,聽聽大佬吹吹牛逼,很有意思。

    7、博客園

    這個我立足的地方嘛!我在大一下學期才正式開始寫博客,最初搞ACM,師哥讓我們寫題解,最開始真是不愛寫,隨便應付一下,後來發現寫博客的樂趣,一發不可收拾。每天逛一下,大家說話有好聽,我超級喜歡這裏!

    8、CSDN

    這應該是一個邁不過去的話題吧,我也不想修什麼優越感,但是現在CSDN真的令人不悅呀!以前聽前輩講起過它的輝煌,現在也不能說是沒落了吧,依舊有龐大的用戶,龐大的流量,也算是一個成功的商業網址了。CSDN最讓我反感的一點是找一個問題,所有答案都是一樣複製粘貼過來的,很難找到原作者,問轉載者詳情問題詳情也無法解答。我不反對轉載別人的博客,畢竟要有開源精神,但轉載能不能貼一個原文鏈接?還有積分下載,講一個笑話,我一個師哥發了一份JDK1.8,5C幣下載,一年賺了300C幣,這是在收智商稅?

    9、MSDN 我告訴你

    這個網站可就厲害了,整合了許多微軟原版系統鏡像,精簡版本,純凈系統。

    10、W3school

    就算不去學前端,我們多多少少也要了解一些相關知識,這也算是一個前端只是比較全的網站了。

    11、鳩摩搜索

    能夠搜索出不少專業書,配合pandownload使用吧,這個給出pandownload的使用說明

    12、菜鳥教程

     

     有朋友提到了菜鳥教程,這裏也來介紹一下吧,個人覺得菜鳥教程提供了較為全面,系統詳細的教程,廣告少,界面簡約,聽說網站最早是一個人寫的,很牛。這個網站挺適合新手做輔助,個人覺得就是有些教程獲取不到較為詳細的說明。

    13、各大OJ

    北京大學

    杭州电子科技大學

    浙江大學

    codeforces

    PAT

    藍橋杯

     

     

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

    新北清潔公司,居家、辦公、裝潢細清專業服務

  • Java I/O體系從原理到應用,這一篇全說清楚了

    Java I/O體系從原理到應用,這一篇全說清楚了

    本文介紹操作系統I/O工作原理,Java I/O設計,基本使用,開源項目中實現高性能I/O常見方法和實現,徹底搞懂高性能I/O之道

    基礎概念

    在介紹I/O原理之前,先重溫幾個基礎概念:

    • (1) 操作系統與內核

    操作系統:管理計算機硬件與軟件資源的系統軟件
    內核:操作系統的核心軟件,負責管理系統的進程、內存、設備驅動程序、文件和網絡系統等等,為應用程序提供對計算機硬件的安全訪問服務

    • 2 內核空間和用戶空間

    為了避免用戶進程直接操作內核,保證內核安全,操作系統將內存尋址空間劃分為兩部分:
    內核空間(Kernel-space),供內核程序使用
    用戶空間(User-space),供用戶進程使用
    為了安全,內核空間和用戶空間是隔離的,即使用戶的程序崩潰了,內核也不受影響

    • 3 數據流

    計算機中的數據是基於隨着時間變換高低電壓信號傳輸的,這些數據信號連續不斷,有着固定的傳輸方向,類似水管中水的流動,因此抽象數據流(I/O流)的概念:指一組有順序的、有起點和終點的字節集合

    抽象出數據流的作用:實現程序邏輯與底層硬件解耦,通過引入數據流作為程序與硬件設備之間的抽象層,面向通用的數據流輸入輸出接口編程,而不是具體硬件特性,程序和底層硬件可以獨立靈活替換和擴展

    I/O 工作原理

    1 磁盤I/O

    典型I/O讀寫磁盤工作原理如下:

    tips: DMA:全稱叫直接內存存取(Direct Memory Access),是一種允許外圍設備(硬件子系統)直接訪問系統主內存的機制。基於 DMA 訪問方式,系統主內存與硬件設備的數據傳輸可以省去CPU 的全程調度

    值得注意的是:

    • 讀寫操作基於系統調用實現
    • 讀寫操作經過用戶緩衝區,內核緩衝區,應用進程並不能直接操作磁盤
    • 應用進程讀操作時需阻塞直到讀取到數據

    2 網絡I/O

    這裏先以最經典的阻塞式I/O模型介紹:

    tips:recvfrom,經socket接收數據的函數

    值得注意的是:

    • 網絡I/O讀寫操作經過用戶緩衝區,Sokcet緩衝區
    • 服務端線程在從調用recvfrom開始到它返回有數據報準備好這段時間是阻塞的,recvfrom返回成功后,線程開始處理數據報

    Java I/O設計

    1 I/O分類

    Java中對數據流進行具體化和實現,關於Java數據流一般關注以下幾個點:

    • (1) 流的方向
      從外部到程序,稱為輸入流;從程序到外部,稱為輸出流

    • (2) 流的數據單位
      程序以字節作為最小讀寫數據單元,稱為字節流,以字符作為最小讀寫數據單元,稱為字符流

    • (3) 流的功能角色

    從/向一個特定的IO設備(如磁盤,網絡)或者存儲對象(如內存數組)讀/寫數據的流,稱為節點流
    對一個已有流進行連接和封裝,通過封裝后的流來實現數據的讀/寫功能,稱為處理流(或稱為過濾流);

    2 I/O操作接口

    java.io包下有一堆I/O操作類,初學時看了容易搞不懂,其實仔細觀察其中還是有規律:
    這些I/O操作類都是在繼承4個基本抽象流的基礎上,要麼是節點流,要麼是處理流

    2.1 四個基本抽象流

    java.io包中包含了流式I/O所需要的所有類,java.io包中有四個基本抽象流,分別處理字節流和字符流:

    • InputStream
    • OutputStream
    • Reader
    • Writer

    2.2 節點流

    節點流I/O類名由節點流類型 + 抽象流類型組成,常見節點類型有:

    • File文件
    • Piped 進程內線程通信管道
    • ByteArray / CharArray (字節數組 / 字符數組)
    • StringBuffer / String (字符串緩衝區 / 字符串)

    節點流的創建通常是在構造函數傳入數據源,例如:

    FileReader reader = new FileReader(new File("file.txt"));
    FileWriter writer = new FileWriter(new File("file.txt"));

    2.3 處理流

    處理流I/O類名由對已有流封裝的功能 + 抽象流類型組成,常見功能有:

    • 緩衝:對節點流讀寫的數據提供了緩衝的功能,數據可以基於緩衝批量讀寫,提高效率。常見有BufferedInputStream、BufferedOutputStream
    • 字節流轉換為字符流:由InputStreamReader、OutputStreamWriter實現
    • 字節流與基本類型數據相互轉換:這裏基本數據類型數據如int、long、short,由DataInputStream、DataOutputStream實現
    • 字節流與對象實例相互轉換:用於實現對象序列化,由ObjectInputStream、ObjectOutputStream實現

    處理流的應用了適配器/裝飾模式,轉換/擴展已有流,處理流的創建通常是在構造函數傳入已有的節點流或處理流:

    FileOutputStream fileOutputStream = new FileOutputStream("file.txt");
    // 擴展提供緩衝寫
    BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
     // 擴展提供提供基本數據類型寫
    DataOutputStream out = new DataOutputStream(bufferedOutputStream);

    3 Java NIO

    3.1 標準I/O存在問題

    Java NIO(New I/O)是一個可以替代標準Java I/O API的IO API(從Java 1.4開始),Java NIO提供了與標準I/O不同的I/O工作方式,目的是為了解決標準 I/O存在的以下問題:

    • (1) 數據多次拷貝

    標準I/O處理,完成一次完整的數據讀寫,至少需要從底層硬件讀到內核空間,再讀到用戶文件,又從用戶空間寫入內核空間,再寫入底層硬件

    此外,底層通過write、read等函數進行I/O系統調用時,需要傳入數據所在緩衝區起始地址和長度
    由於JVM GC的存在,導致對象在堆中的位置往往會發生移動,移動後傳入系統函數的地址參數就不是真正的緩衝區地址了

    可能導致讀寫出錯,為了解決上面的問題,使用標準I/O進行系統調用時,還會額外導致一次數據拷貝:把數據從JVM的堆內拷貝到堆外的連續空間內存(堆外內存)

    所以總共經歷6次數據拷貝,執行效率較低

    • (2) 操作阻塞

    傳統的網絡I/O處理中,由於請求建立連接(connect),讀取網絡I/O數據(read),發送數據(send)等操作是線程阻塞的

    // 等待連接
    Socket socket = serverSocket.accept();
    
    // 連接已建立,讀取請求消息
    StringBuilder req = new StringBuilder();
    byte[] recvByteBuf = new byte[1024];
    int len;
    while ((len = socket.getInputStream().read(recvByteBuf)) != -1) {
        req.append(new String(recvByteBuf, 0, len, StandardCharsets.UTF_8));
    }
    
    // 寫入返回消息
    socket.getOutputStream().write(("server response msg".getBytes()));
    socket.shutdownOutput();

    以上面服務端程序為例,當請求連接已建立,讀取請求消息,服務端調用read方法時,客戶端數據可能還沒就緒(例如客戶端數據還在寫入中或者傳輸中),線程需要在read方法阻塞等待直到數據就緒

    為了實現服務端併發響應,每個連接需要獨立的線程單獨處理,當併發請求量大時為了維護連接,內存、線程切換開銷過大

    3.2 Buffer

    Java NIO核心三大核心組件是Buffer(緩衝區)、Channel(通道)、Selector

    Buffer提供了常用於I/O操作的字節緩衝區,常見的緩存區有ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer,分別對應基本數據類型: byte, char, double, float, int, long, short,下面介紹主要以最常用的ByteBuffer為例,Buffer底層支持Java堆內(HeapByteBuffer)或堆外內存(DirectByteBuffer)

    堆外內存是指與堆內存相對應的,把內存對象分配在JVM堆以外的內存,這些內存直接受操作系統管理(而不是虛擬機,相比堆內內存,I/O操作中使用堆外內存的優勢在於:

    • 不用被JVM GC線回收,減少GC線程資源佔有
    • 在I/O系統調用時,直接操作堆外內存,可以節省一次堆外內存和堆內內存的複製

    ByteBuffer底層堆外內存的分配和釋放基於malloc和free函數,對外allocateDirect方法可以申請分配堆外內存,並返回繼承ByteBuffer類的DirectByteBuffer對象:

    public static ByteBuffer allocateDirect(int capacity) {
        return new DirectByteBuffer(capacity);
    }

    堆外內存的回收基於DirectByteBuffer的成員變量Cleaner類,提供clean方法可以用於主動回收,Netty中大部分堆外內存通過記錄定位Cleaner的存在,主動調用clean方法來回收;
    另外,當DirectByteBuffer對象被GC時,關聯的堆外內存也會被回收

    tips: JVM參數不建議設置-XX:+DisableExplicitGC,因為部分依賴Java NIO的框架(例如Netty)在內存異常耗盡時,會主動調用System.gc(),觸發Full GC,回收DirectByteBuffer對象,作為回收堆外內存的最後保障機制,設置該參數之後會導致在該情況下堆外內存得不到清理

    堆外內存基於基礎ByteBuffer類的DirectByteBuffer類成員變量:Cleaner對象,這個Cleaner對象會在合適的時候執行unsafe.freeMemory(address),從而回收這塊堆外內存

    Buffer可以見到理解為一組基本數據類型,存儲地址連續的的數組,支持讀寫操作,對應讀模式和寫模式,通過幾個變量來保存這個數據的當前位置狀態:capacity、 position、 limit:

    • capacity 緩衝區數組的總長度
    • position 下一個要操作的數據元素的位置
    • limit 緩衝區數組中不可操作的下一個元素的位置:limit <= capacity

    3.3 Channel

    Channel(通道)的概念可以類比I/O流對象,NIO中I/O操作主要基於Channel:
    從Channel進行數據讀取 :創建一個緩衝區,然後請求Channel讀取數據
    從Channel進行數據寫入 :創建一個緩衝區,填充數據,請求Channel寫入數據

    Channel和流非常相似,主要有以下幾點區別:

    • Channel可以讀和寫,而標準I/O流是單向的
    • Channel可以異步讀寫,標準I/O流需要線程阻塞等待直到讀寫操作完成
    • Channel總是基於緩衝區Buffer讀寫

    Java NIO中最重要的幾個Channel的實現:

    • FileChannel: 用於文件的數據讀寫,基於FileChannel提供的方法能減少讀寫文件數據拷貝次數,後面會介紹
    • DatagramChannel: 用於UDP的數據讀寫
    • SocketChannel: 用於TCP的數據讀寫,代表客戶端連接
    • ServerSocketChannel: 監聽TCP連接請求,每個請求會創建會一個SocketChannel,一般用於服務端

    基於標準I/O中,我們第一步可能要像下面這樣獲取輸入流,按字節把磁盤上的數據讀取到程序中,再進行下一步操作,而在NIO編程中,需要先獲取Channel,再進行讀寫

    FileInputStream fileInputStream = new FileInputStream("test.txt");
    FileChannel channel = fileInputStream.channel();

    tips: FileChannel僅能運行在阻塞模式下,文件異步處理的 I/O 是在JDK 1.7 才被加入的 java.nio.channels.AsynchronousFileChannel

    // server socket channel:
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    serverSocketChannel.bind(new InetSocketAddress(InetAddress.getLocalHost(), 9091));
    
    while (true) {
        SocketChannel socketChannel = serverSocketChannel.accept();
        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
        int readBytes = socketChannel.read(buffer);
        if (readBytes > 0) {
            // 從寫數據到buffer翻轉為從buffer讀數據
            buffer.flip();
            byte[] bytes = new byte[buffer.remaining()];
            buffer.get(bytes);
            String body = new String(bytes, StandardCharsets.UTF_8);
            System.out.println("server 收到:" + body);
        }
    }

    3.4 Selector

    Selector(選擇器) ,它是Java NIO核心組件中的一個,用於檢查一個或多個NIO Channel(通道)的狀態是否處於可讀、可寫。實現單線程管理多個Channel,也就是可以管理多個網絡連接

    Selector核心在於基於操作系統提供的I/O復用功能,單個線程可以同時監視多個連接描述符,一旦某個連接就緒(一般是讀就緒或者寫就緒),能夠通知程序進行相應的讀寫操作,常見有select、poll、epoll等不同實現

    Java NIO Selector基本工作原理如下:

    • (1) 初始化Selector對象,服務端ServerSocketChannel對象
    • (2) 向Selector註冊ServerSocketChannel的socket-accept事件
    • (3) 線程阻塞於selector.select(),當有客戶端請求服務端,線程退出阻塞
    • (4) 基於selector獲取所有就緒事件,此時先獲取到socket-accept事件,向Selector註冊客戶端SocketChannel的數據就緒可讀事件事件
    • (5) 線程再次阻塞於selector.select(),當有客戶端連接數據就緒,可讀
    • (6) 基於ByteBuffer讀取客戶端請求數據,然後寫入響應數據,關閉channel

    示例如下,完整可運行代碼已經上傳github(https://github.com/caison/caison-blog-demo):

    Selector selector = Selector.open();
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    serverSocketChannel.bind(new InetSocketAddress(9091));
    // 配置通道為非阻塞模式
    serverSocketChannel.configureBlocking(false);
    // 註冊服務端的socket-accept事件
    serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    
    while (true) {
        // selector.select()會一直阻塞,直到有channel相關操作就緒
        selector.select();
        // SelectionKey關聯的channel都有就緒事件
        Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
    
        while (keyIterator.hasNext()) {
            SelectionKey key = keyIterator.next();
            // 服務端socket-accept
            if (key.isAcceptable()) {
                // 獲取客戶端連接的channel
                SocketChannel clientSocketChannel = serverSocketChannel.accept();
                // 設置為非阻塞模式
                clientSocketChannel.configureBlocking(false);
                // 註冊監聽該客戶端channel可讀事件,併為channel關聯新分配的buffer
                clientSocketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocateDirect(1024));
            }
    
            // channel可讀
            if (key.isReadable()) {
                SocketChannel socketChannel = (SocketChannel) key.channel();
                ByteBuffer buf = (ByteBuffer) key.attachment();
    
                int bytesRead;
                StringBuilder reqMsg = new StringBuilder();
                while ((bytesRead = socketChannel.read(buf)) > 0) {
                    // 從buf寫模式切換為讀模式
                    buf.flip();
                    int bufRemain = buf.remaining();
                    byte[] bytes = new byte[bufRemain];
                    buf.get(bytes, 0, bytesRead);
                    // 這裏當數據包大於byteBuffer長度,有可能有粘包/拆包問題
                    reqMsg.append(new String(bytes, StandardCharsets.UTF_8));
                    buf.clear();
                }
                System.out.println("服務端收到報文:" + reqMsg.toString());
                if (bytesRead == -1) {
                    byte[] bytes = "[這是服務回的報文的報文]".getBytes(StandardCharsets.UTF_8);
    
                    int length;
                    for (int offset = 0; offset < bytes.length; offset += length) {
                        length = Math.min(buf.capacity(), bytes.length - offset);
                        buf.clear();
                        buf.put(bytes, offset, length);
                        buf.flip();
                        socketChannel.write(buf);
                    }
                    socketChannel.close();
                }
            }
            // Selector不會自己從已selectedKeys中移除SelectionKey實例
            // 必須在處理完通道時自己移除 下次該channel變成就緒時,Selector會再次將其放入selectedKeys中
            keyIterator.remove();
        }
    }

    tips: Java NIO基於Selector實現高性能網絡I/O這塊使用起來比較繁瑣,使用不友好,一般業界使用基於Java NIO進行封裝優化,擴展豐富功能的Netty框架來優雅實現

    高性能I/O優化

    下面結合業界熱門開源項目介紹高性能I/O的優化

    1 零拷貝

    零拷貝(zero copy)技術,用於在數據讀寫中減少甚至完全避免不必要的CPU拷貝,減少內存帶寬的佔用,提高執行效率,零拷貝有幾種不同的實現原理,下面介紹常見開源項目中零拷貝實現

    1.1 Kafka零拷貝

    Kafka基於Linux 2.1內核提供,並在2.4 內核改進的的sendfile函數 + 硬件提供的DMA Gather Copy實現零拷貝,將文件通過socket傳送

    函數通過一次系統調用完成了文件的傳送,減少了原來read/write方式的模式切換。同時減少了數據的copy, sendfile的詳細過程如下:

    基本流程如下:

    • (1) 用戶進程發起sendfile系統調用
    • (2) 內核基於DMA Copy將文件數據從磁盤拷貝到內核緩衝區
    • (3) 內核將內核緩衝區中的文件描述信息(文件描述符,數據長度)拷貝到Socket緩衝區
    • (4) 內核基於Socket緩衝區中的文件描述信息和DMA硬件提供的Gather Copy功能將內核緩衝區數據複製到網卡
    • (5) 用戶進程sendfile系統調用完成並返回

    相比傳統的I/O方式,sendfile + DMA Gather Copy方式實現的零拷貝,數據拷貝次數從4次降為2次,系統調用從2次降為1次,用戶進程上下文切換次數從4次變成2次DMA Copy,大大提高處理效率

    Kafka底層基於java.nio包下的FileChannel的transferTo:

    public abstract long transferTo(long position, long count, WritableByteChannel target)

    transferTo將FileChannel關聯的文件發送到指定channel,當Comsumer消費數據,Kafka Server基於FileChannel將文件中的消息數據發送到SocketChannel

    1.2 RocketMQ零拷貝

    RocketMQ基於mmap + write的方式實現零拷貝:
    mmap() 可以將內核中緩衝區的地址與用戶空間的緩衝區進行映射,實現數據共享,省去了將數據從內核緩衝區拷貝到用戶緩衝區

    tmp_buf = mmap(file, len); 
    write(socket, tmp_buf, len);

    mmap + write 實現零拷貝的基本流程如下:

    • (1) 用戶進程向內核發起系統mmap調用
    • (2) 將用戶進程的內核空間的讀緩衝區與用戶空間的緩存區進行內存地址映射
    • (3) 內核基於DMA Copy將文件數據從磁盤複製到內核緩衝區
    • (4) 用戶進程mmap系統調用完成並返回
    • (5) 用戶進程向內核發起write系統調用
    • (6) 內核基於CPU Copy將數據從內核緩衝區拷貝到Socket緩衝區
    • (7) 內核基於DMA Copy將數據從Socket緩衝區拷貝到網卡
    • (8) 用戶進程write系統調用完成並返回

    RocketMQ中消息基於mmap實現存儲和加載的邏輯寫在org.apache.rocketmq.store.MappedFile中,內部實現基於nio提供的java.nio.MappedByteBuffer,基於FileChannel的map方法得到mmap的緩衝區:

    // 初始化
    this.fileChannel = new RandomAccessFile(this.file, "rw").getChannel();
    this.mappedByteBuffer = this.fileChannel.map(MapMode.READ_WRITE, 0, fileSize);

    查詢CommitLog的消息時,基於mappedByteBuffer偏移量pos,數據大小size查詢:

    public SelectMappedBufferResult selectMappedBuffer(int pos, int size) {
        int readPosition = getReadPosition();
        // ...各種安全校驗
        
        // 返回mappedByteBuffer視圖
        ByteBuffer byteBuffer = this.mappedByteBuffer.slice();
        byteBuffer.position(pos);
        ByteBuffer byteBufferNew = byteBuffer.slice();
        byteBufferNew.limit(size);
        return new SelectMappedBufferResult(this.fileFromOffset + pos, byteBufferNew, size, this);
    }

    tips: transientStorePoolEnable機制
    Java NIO mmap的部分內存並不是常駐內存,可以被置換到交換內存(虛擬內存),RocketMQ為了提高消息發送的性能,引入了內存鎖定機制,即將最近需要操作的CommitLog文件映射到內存,並提供內存鎖定功能,確保這些文件始終存在內存中,該機制的控制參數就是transientStorePoolEnable

    因此,MappedFile數據保存CommitLog刷盤有2種方式:

    • 1 開啟transientStorePoolEnable:寫入內存字節緩衝區(writeBuffer) -> 從內存字節緩衝區(writeBuffer)提交(commit)到文件通道(fileChannel) -> 文件通道(fileChannel) -> flush到磁盤
    • 2 未開啟transientStorePoolEnable:寫入映射文件字節緩衝區(mappedByteBuffer) -> 映射文件字節緩衝區(mappedByteBuffer) -> flush到磁盤

    RocketMQ 基於 mmap+write 實現零拷貝,適用於業務級消息這種小塊文件的數據持久化和傳輸
    Kafka 基於 sendfile 這種零拷貝方式,適用於系統日誌消息這種高吞吐量的大塊文件的數據持久化和傳輸

    tips: Kafka 的索引文件使用的是 mmap+write 方式,數據文件發送網絡使用的是 sendfile 方式

    1.3 Netty零拷貝

    Netty 的零拷貝分為兩種:

    • 1 基於操作系統實現的零拷貝,底層基於FileChannel的transferTo方法
    • 2 基於Java 層操作優化,對數組緩存對象(ByteBuf )進行封裝優化,通過對ByteBuf數據建立數據視圖,支持ByteBuf 對象合併,切分,當底層僅保留一份數據存儲,減少不必要拷貝

    2 多路復用

    Netty中對Java NIO功能封裝優化之後,實現I/O多路復用代碼優雅了很多:

    // 創建mainReactor
    NioEventLoopGroup boosGroup = new NioEventLoopGroup();
    // 創建工作線程組
    NioEventLoopGroup workerGroup = new NioEventLoopGroup();
    
    final ServerBootstrap serverBootstrap = new ServerBootstrap();
    serverBootstrap 
         // 組裝NioEventLoopGroup 
        .group(boosGroup, workerGroup)
         // 設置channel類型為NIO類型
        .channel(NioServerSocketChannel.class)
        // 設置連接配置參數
        .option(ChannelOption.SO_BACKLOG, 1024)
        .childOption(ChannelOption.SO_KEEPALIVE, true)
        .childOption(ChannelOption.TCP_NODELAY, true)
        // 配置入站、出站事件handler
        .childHandler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel ch) {
                // 配置入站、出站事件channel
                ch.pipeline().addLast(...);
                ch.pipeline().addLast(...);
            }
        });
    
    // 綁定端口
    int port = 8080;
    serverBootstrap.bind(port).addListener(future -> {
        if (future.isSuccess()) {
            System.out.println(new Date() + ": 端口[" + port + "]綁定成功!");
        } else {
            System.err.println("端口[" + port + "]綁定失敗!");
        }
    });

    3 頁緩存(PageCache)

    頁緩存(PageCache)是操作系統對文件的緩存,用來減少對磁盤的 I/O 操作,以頁為單位的,內容就是磁盤上的物理塊,頁緩存能幫助程序對文件進行順序讀寫的速度幾乎接近於內存的讀寫速度,主要原因就是由於OS使用PageCache機制對讀寫訪問操作進行了性能優化:

    頁緩存讀取策略:當進程發起一個讀操作 (比如,進程發起一個 read() 系統調用),它首先會檢查需要的數據是否在頁緩存中:

    • 如果在,則放棄訪問磁盤,而直接從頁緩存中讀取
    • 如果不在,則內核調度塊 I/O 操作從磁盤去讀取數據,並讀入緊隨其後的少數幾個頁面(不少於一個頁面,通常是三個頁面),然後將數據放入頁緩存中

    頁緩存寫策略:當進程發起write系統調用寫數據到文件中,先寫到頁緩存,然後方法返回。此時數據還沒有真正的保存到文件中去,Linux 僅僅將頁緩存中的這一頁數據標記為“臟”,並且被加入到臟頁鏈表中

    然後,由flusher 回寫線程周期性將臟頁鏈表中的頁寫到磁盤,讓磁盤中的數據和內存中保持一致,最後清理“臟”標識。在以下三種情況下,臟頁會被寫回磁盤:

    • 空閑內存低於一個特定閾值
    • 臟頁在內存中駐留超過一個特定的閾值時
    • 當用戶進程調用 sync() 和 fsync() 系統調用時

    RocketMQ中,ConsumeQueue邏輯消費隊列存儲的數據較少,並且是順序讀取,在page cache機制的預讀取作用下,Consume Queue文件的讀性能幾乎接近讀內存,即使在有消息堆積情況下也不會影響性能,提供了2種消息刷盤策略:

    • 同步刷盤:在消息真正持久化至磁盤后RocketMQ的Broker端才會真正返回給Producer端一個成功的ACK響應
    • 異步刷盤,能充分利用操作系統的PageCache的優勢,只要消息寫入PageCache即可將成功的ACK返回給Producer端。消息刷盤採用後台異步線程提交的方式進行,降低了讀寫延遲,提高了MQ的性能和吞吐量

    Kafka實現消息高性能讀寫也利用了頁緩存,這裏不再展開

    參考

    《深入理解Linux內核 —— Daniel P.Bovet》

    更多精彩,歡迎關注公眾號 分佈式系統架構

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

    ※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

    ※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

    ※評比南投搬家公司費用收費行情懶人包大公開

    ※幫你省時又省力,新北清潔一流服務好口碑