北極熊身上遭噴黑字「T-34」 專家震驚:失去保護色恐難覓食

摘錄自2019年12月4日上報報導

隨著全球暖化加劇,近幾個月以來常見北極熊出現在俄羅斯北方。但是近期在社群媒流傳的影片可見,有隻北極熊身體側邊被噴上黑色字母和數字「T-34」,目前專家正在調查這部影片的確切拍攝地點是否位於俄羅斯,並且警告這樣的行為可能會影響北極熊的生活技能,使牠們失去原有的保護色,難以在白雪覆蓋的地區捕獵食物。

《英國廣播公司》(BBC)報導,這部影片被世界自然基金會(World Wildlife Fund,WWF)成員卡夫里(Sergey Kavry)發布在Facebook,之後又被當地媒體廣泛分享。卡夫里說,這部影片被分享在通訊軟體WhatsApp的一個俄羅斯東部楚科奇自治區(Chukotka)當地居民的群組裡,而負責監控野生動物的科學家也不會以噴漆的方式將北極熊進行編號。

由於「T-34」戰車是蘇聯在第二次世界大戰(World War Two)戰勝德國納粹的重要關鍵,卡夫里說:「我不知道這段影片究竟是在哪個區域、地區,或哪裡的附近拍攝。如果這是個傳遞軍事訊息的舉動……某種程度來說是對歷史不尊重。」

俄羅斯北方生態問題研究所(Institute Of The Biological Problems Of The North)科學家科赫涅夫(Anatoly Kochnev)指出,北極熊不太可能在未被麻醉的情況下,任人在牠身上噴字。

科赫涅夫提到,這隻北極熊被噴漆的當下可能無法活動,或是可能靜止不動,因為牠被噴上的字樣均勻,而且大小相同。此外,他認為這可能發生在俄羅斯北部地區新地島(Novaya Zemlya),因為先前有專家團隊將北極熊進行麻醉,阻止牠們在人類居住地遊蕩。

科赫涅夫說,如今要將這隻北極熊的噴漆洗掉可能要花上好幾週的時間,而且這會對牠的白色保護色造成影響,使牠難以在冰雪覆蓋的地方順利捕食獵物。

俄羅斯媒體推測,在北極熊身上噴漆的舉動,可能是基於附近居民對於許多北極熊屢次闖入人類居住地的恐懼與不滿。隨著全球暖化日漸加劇,新地島2月宣布進入緊急狀態,因為有超過50隻北極熊出現在該地區,牠們在人口密集居住的建築物周遭漫步、尋找食物。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

聯合國:反疫苗運動 助長薩摩亞麻疹疫情惡化

摘錄自2019年12月6日中央通訊社綜合報導

聯合國兒童基金會(UNICEF)太平洋島嶼負責人今天(5日)表示,社群媒體巨頭必須嚴厲取締反疫苗接種貼文,這些貼文助長薩摩亞(Samoa)致命麻疹疫情惡化。

UNICEF地區代表耶特(Sheldon Yett)表示,推特(Twitter)、臉書(Facebook)和Instagram(IG)等網路平台上「極不負責任」的反疫苗接種訊息,加劇了薩摩亞爆發的麻疹疫情,自10月中旬以來已造成62人死亡。耶特告訴法新社:「很明顯地它們必需負起企業責任並展開行動,確保那些人民,特別是弱勢族群能獲得正確資訊,讓孩童得以存活。」

在麻疹疫情爆發前,薩摩亞的疫苗接種率降至只剩略超過30%,遠低於公認最佳接種率90%,這也使得該海島國家極易受到感染。世界衛生組織(WHO)把矛頭指向反疫苗宣傳運動。耶特表示,這項運動主要是由海外倡議人士在網路上展開。

「很不幸的是,這項運動在薩摩亞找到願意相信的民眾,那裡有一部分人懷疑醫療保健服務品質,且可能不信任當地(疫苗)供應者。」他說,來自諸如美國和澳洲等富裕已開發國家的運動人士,在網路上張貼反疫苗訊息,他們必須意識到自己的所作所為會對開發中國家帶來衝擊。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

澳洲野火持續燃燒 摧毀約四成台灣面積

整理:劉妙慈(環境資訊中心實習編輯)

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

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

英國政府日前給倫敦市劃撥了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網頁設計已成為網頁設計推薦首選

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

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

基於 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的時間調度器,以前沒接觸過,對着代碼鼓搗了半天,查閱了部分博客,最後總結出自己的見解,新項目一般是不會用這種老掉牙的時間調度器了,但是維護老項目還是用的着的。就當筆記記錄一下了,自己寫的才是符合自己的思路走向的。有時間再補上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

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邏輯回歸實戰

前情提要:

上一篇主要介紹了邏輯回歸中,相對理論化的知識,這次主要是對上篇做一點點補充,以及介紹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網頁設計已成為網頁設計推薦首選

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

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