標籤: 網頁設計公司

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

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

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

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

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

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

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

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

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

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

    【其他文章推薦】

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

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

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

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

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

  • 廣汽三菱收購進口三菱 或著重發展新能源汽車

    日前,從廣汽三菱汽車有限公司瞭解到,該公司已經完成對三菱汽車銷售有限公司的100%股份的收購。值得關注的是完成收購後的廣汽三菱下一步的汽車整體佈局。

    廣汽三菱汽車有限公司副總經理、三菱汽車銷售有限公司董事長杜志堅對外透露了廣汽三菱的新能源汽車發展的幾個方向。

    首先,基於三菱的插電式混合動力的優勢,廣汽三菱首推插電式混動車。據杜志堅描述這款新車的三個優勢,第一,這款車充滿電、加滿油,續航里程超過800公里,根本不用擔心去哪裡回不來;第二,燃油經濟性,百公里油耗不超過2L;第三,解決了對充電樁的依賴,只要提供一個插座,車隨時可以充電。

    其次,廣汽三菱的首款國產新能源汽車將基於插電式混合動力汽車歐藍德研發。據瞭解,目前廣汽三菱已經進口了10輛歐藍德,正處於廠商進行驗證測試階段。這款車的儘快導入,應該說對整個廣汽三菱產品線的提升、科技感提升、品牌提升都將有巨大的幫助。
     

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

    【其他文章推薦】

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

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

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

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

    ※別再煩惱如何寫文案,掌握八大原則!

  • FastDFS圖片服務器單機安裝步驟(修訂版)

    FastDFS圖片服務器單機安裝步驟(修訂版)

    前面已經講 ,通過此文章可以了解FastDFS組件中單機安裝流程。

    單機版架構圖

    以下為單機FastDFS安裝步驟

    一、環境準備

    CentOS 7.X

    使用的版本: libfastcommon-1.0.41.tar.gz

    使用的版本: fastdfs-6.01.tar.gz

    使用的版本:fastdfs-nginx-module-1.21.tar.gz

    使用的版本: nginx-1.16.1.tar.gz

    二、安裝過程

    1、安裝 libfastcommon-1.0.41.tar.gz

    tar -zxvf libfastcommon-1.0.41.tar.gz
    cd libfastcommon-1.0.41/
    ./make.sh
    ./make.sh install

    2、安裝 FastDFS

    tar -zxvf  fastdfs-6.01.tar.gz
    cd fastdfs-6.01/
    ./make.sh
    ./make.sh install

    準備配置文件

    cp /etc/fdfs/tracker.conf.sample /etc/fdfs/tracker.conf
    cp /etc/fdfs/storage.conf.sample /etc/fdfs/storage.conf
    cp /etc/fdfs/client.conf.sample /etc/fdfs/client.conf
    cd /opt/apps/fastdfs-6.01/conf
    cp http.conf mime.types /etc/fdfs/

    Tracker Server 配置

    vim /etc/fdfs/tracker.conf
    修改配置如下:
    #tracker server端口號
    port=22122
    #存儲日誌和數據的根目錄
    base_path=/opt/fastdfs/tracker
    #HTTP服務端口
    http.server_port=80
    開放防火牆端口

    1、打開跟蹤端口

    vim /etc/sysconfig/iptables

    2、添加以下端口行:

    -A INPUT -m state --state NEW -m tcp -p tcp --dport 22122 -j ACCEPT

    3、重啟防火牆

    service iptables restart
    啟動Tracker
    /etc/init.d/fdfs_trackerd start

    Storage Server 配置

    vim /etc/fdfs/storage.conf
    修改配置如下:
    #storage server端口號
    port=23000
    #數據和日誌文件存儲根目錄
    base_path=/opt/fastdfs/storage
    #第一個存儲目錄
    store_path0=/opt/fastdfs/storepath0
    #tracker服務器IP和端口
    tracker_server=192.168.0.1:22122
    #http訪問文件的端口(默認8888,看情況修改,和nginx中保持一致)
    http.server_port=8888
    開放防火牆端口

    1、打開跟蹤端口

    vim /etc/sysconfig/iptables

    2、添加以下端口行:

    -A INPUT -m state --state NEW -m tcp -p tcp --dport 23000 -j ACCEPT

    3、重啟防火牆

    service iptables restart
    啟動Storage
    /etc/init.d/fdfs_storaged start
    查看集群狀態
     fdfs_monitor /etc/fdfs/storage.conf list
    

    查看狀態是否正常

    Storage 1:
    id = 6.0.36.243
    ip_addr = 6.0.36.243 (anantes-651-1-49-net.w2-0.abo.wanadoo.fr) ACTIVE

    Client配置

    vim /etc/fdfs/client.conf
    
    修改配置如下:
    #
    base_path=/opt/apps/fastdfs/client
    #tracker服務器IP和端口
    tracker_server=192.168.0.1:22122 
    
    上傳一個圖片測試是否能上傳成功
     fdfs_upload_file /etc/fdfs/client.conf test.jpg
    

    test.jpg 是測試本地上傳的圖片,路徑請填寫正確

    3、安裝Nginx和 fastdfs-nginx-module

    #解壓fastdfs-nginx-module
    tar -zxvf fastdfs-nginx-module-1.21.tar.gz
    cd fastdfs-nginx-module-1.21/
    cp ./src/mod_fastdfs.conf /etc/fdfs
    #解壓nginx
    tar -zxvf nginx-1.16.1.tar.gz
    cd nginx-1.16.1/
    #安裝nginx_http_image_filter_module
    yum -y install gd-devel
    yum -y install zlib zlib-devel openssl openssl--devel pcre pcre-devel
    #添加模塊
    ./configure --add-module=../fastdfs-nginx-module-1.21/src --prefix=/usr/local/nginx --with-http_image_filter_module 
    #編譯nginx
    make
    #安裝nginx
    make install
    

    查看是否安裝成功

    /usr/local/nginx/sbin/nginx -V
    

    查看插件是否安裝成功

    [root@FastDFS nginx-1.16.1]# /usr/local/nginx/sbin/nginx -V
    nginx version: nginx/1.16.1
    built by gcc 4.8.5 20150623 (Red Hat 4.8.5-11) (GCC) 
    configure arguments: --add-module=../fastdfs-nginx-module-1.21/src --prefix=/usr/local/nginx --with-http_image_filter_module
    [root@FastDFS nginx-1.16.1]# 
    

    修改Nginx訪問

    vim /etc/fdfs/mod_fastdfs.conf

    修改配置如下:

    #
    connect_timeout=10
    #tracker服務器IP和端口
    tracker_server=192.168.0.1:22122
    #是否啟用group組名
    url_have_group_name=true
    #
    store_path0=/opt/fastdfs/storepath0

    修改Nginx配置:

    vim /usr/local/nginx/conf/nginx.conf
    

    修改配置如下:

    server {
        listen       80;
        server_name  localhost;
    
        #charset koi8-r;
    
        #access_log  logs/host.access.log  main;
    
        location / {
            root   html;
            index  index.html index.htm;
        }
        #圖片帶壓縮訪問
        location ~ /group1/M00/(.*)\.(jpg|gif|png)!format=([0-9]+)_([0-9]+) {
            alias  /home/fastdfs/storage/data/;
            ngx_fastdfs_module;
            set $w $3;
            set $h $4;
    
            rewrite group1/M00(.+)\.(jpg|gif|png)!format=([0-9]+)_([0-9]+)$ group1/M00$1.$2 break;
    
            image_filter resize $w $h;
            image_filter_buffer 5M;
        }
        #主圖訪問
        location ~ /group([0-9])/M00/(.+)\.?(.+) {
            alias /home/fastdfs/storage/data/;
            ngx_fastdfs_module;
        }
    ...
    }
    

    啟動Nginx

    #啟動
    /usr/local/nginx/sbin/nginx
    #停止
    /usr/local/nginx/sbin/nginx -s stop
    #重啟
    /usr/local/nginx/sbin/nginx -s reload

    通過以上配置完成FastDFS的搭建。

    測試圖片訪問

    圖片訪問示例:

    主圖訪問

    http://218.2.204.124:30308/group1/M00/00/03/BgAk813IvTCAIxxxAAD44NFKFPc908.png

    壓縮圖片 (主圖后加 !format=寬度_高度)訪問

    http://218.2.204.124:30308/group1/M00/00/03/BgAk813IvTCAIxxxAAD44NFKFPc908.png!format=400_10

    未解決的問題

    壓縮圖片使用主圖后?format=寬度_高度

    本文由博客一文多發平台 發布!

    再次感謝!!! 您已看完全文,歡迎關注微信公眾號猿碼 ,你的支持是我持續更新文章的動力!

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

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

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

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

    ※教你寫出一流的銷售文案?

  • 基於 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 是電子產業重要的元件?

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

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

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

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

  • 手寫jwt驗證,實現java和node無縫切換

    手寫jwt驗證,實現java和node無縫切換

    前言

    前端時間和我寫了一個簡易用戶管理後台,功能其實很簡單,涉及到的技術棧有:vue+elementUI,java+spring MVC以及node+egg,數據庫用的mysql,簡單方便。

    一開始是我是只負責前端,但是前端開發的的速度太快,老是沒事,加上他小子並沒有接觸過實戰的項目,又怕他出亂子,所以考慮我也寫一個後端,

    開始考慮的是用python+django,但是還是在半途放棄了,因為總感覺Django不過靈活,使用起來非常彆扭,也可能是用scrapy寫爬蟲寫多了,難以理解django的框架設計,總是會把他想象成一個scrapy架構,也導致代碼寫的很亂,下面上一張scrapy架構圖。

    所以最後考慮的用node,Express和Koa框架學習過node應該都知道,我以前也用過express(寫過一個小demo),但是給我的感覺這到像是一個只能有5年以上node開發經驗才能玩的轉的,因為express是非常的精簡的,安裝它后,會發現它幾乎不會為你提供任何編碼規範,也沒有約束你的框架應該怎麼設計,以至於新手下載完成后完全不知道自己應該幹嘛,甚至不知道直接的文件應該寫在哪,沒有框架本身的約定,標準的MVC模型有着千奇百怪的寫法,所以我覺得沒有一定的架構思想和經驗是很難駕馭的。

    koa我並沒有直接的使用過,只是聽說是express的原班人馬打造的,中間件是基於洋蔥圈模型實現的。下面上一張圖洋蔥圈模型圖。

    而是後來直接就接觸的egg,egg標語是“為企業級框架和應用而生”,

    廢話說的有點多了,重點是通一套前端代碼,開發了兩套後端代碼,功能是完全一致的,後端都實現了相應的jwt驗證功能,–>,密鑰都是我們約定好的固定值,但是最後發現一樣的密鑰,相同的數據,使用的jwt包不同產生的結果也不同,這也就導致兩端之間無法相互切換,每次切換必須從登陸重新開始,這不太符合邏輯,而且重要的是後面的安排有需要這樣的功能,無法做到無縫切換就直接導致實現不了下面的功能。

    提綱內容

    • jwt實現原理
    • jwt未能解決的疑惑
    • base64和base64url

    jwt實現原理

    其實作為一個前端開發人員,jwt實現原理我是沒必要懂得的,但是如果你不限於此,這算是個必不可少的內容了吧。

    先上一張圖。

    圖中数字1是後端使用jwt工具包生成的token,通常是由三部分組成,也就是token中間的兩個“.”將其分為三部分,第一部分對應的是右邊的数字2部分,然後依次對應。

    這三部分分別是頭部、有效載荷、簽名。

    頭部:alg是指說用到的算法,type當然是令牌類型

    有效荷載:sub所簽發的用戶,name是簽發者的姓名,lat是這簽發時間,exp是指到期時間,當然還有一些其他的,這些數據都是非必要數據,實則只有exp可能有用,因為有效數據實際都是在data裏面,當然你也可以不這麼做。

    簽名:前兩者都是通過base64url編碼過的,而非是算法加密的,所以幾乎是透明的。但是簽名是默認是通過hsa256算法加密的 ,加密的規則是:

    HMACSHA256(
      base64UrlEncode(header) + "." +
      base64UrlEncode(payload),
      your-256-bit-secret
    )

    如何驗證token呢?那更加簡單了,就是用前端傳回來的token前兩部分和服務器存的秘鑰再次加密一次,然後做base64url處理和第三部分比較,一樣代表這個token是自己簽發的,不一樣代表是偽造的,完了過後再將有效載荷部分進行base64url反編碼,就得到exp,然後和當前時間比較是否過期。

    jwt未能解決的疑惑

    經過測試,發現三個問題。

    1、 node和java使用對應的jwt工具包生成的token不相同,這裡是指在一系列參數完全相同的情況下。

    2、 還發現用java生成的token在jwt官網解析時候輸入秘鑰結果的到的簽名和自己的簽名不一樣,但是node是一樣的,於是就產生了java工具包在輸入簽名的時候對密鑰進行了其他處理的想法,但是他並沒有找出做出來怎麼樣的處理,也就是說我們在生成簽名的時候根本就連密鑰都是不一樣的。

    3、 不管是node還是java生成的token,我們用原來一模一樣的數據和一樣的算法公式都產生不了和工具包生成一樣的簽名,也就是可以懷疑在進行hsa256算法加密前確實對秘鑰進行處理了。

    base64和base64url

    base64就是一種二進制編碼方式,原理很簡單,先準備一個包含64個字符的數組:

    ['A','B','C',...'a','b','c',...'0','1','2',...'+','/']

    然後對二進制數據進行處理,每三個字節一組,一共24bit,一個字節8bit嘛,然後再將24分為4組,每組正好6bit。6bit的話就剛好能表示64個字符。如果編碼字符不是3的倍數,就會剩下一個字節或者兩個字節,這個時候就在後面填充\x00,再在編碼末尾加上一個或者兩個=號。解碼的時候制動力去掉就好了。

    base64url編碼就是將字符編碼成url能傳遞的參數,因為base64編碼會出現+號和/號,然後就會在url中出現問題,所以base64url其實就是將+和/分別變成-和_

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

    【其他文章推薦】

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

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

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

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

    ※別再煩惱如何寫文案,掌握八大原則!

  • Spring Boot 2.X(十六):應用監控之 Spring Boot Actuator 使用及配置

    Spring Boot 2.X(十六):應用監控之 Spring Boot Actuator 使用及配置

    Actuator 簡介

    Actuator 是 Spring Boot 提供的對應用系統的自省和監控功能。通過 Actuator,可以使用數據化的指標去度量應用的運行情況,比如查看服務器的磁盤、內存、CPU等信息,系統的線程、gc、運行狀態等等。

    Actuator 通常通過使用 HTTP 和 JMX 來管理和監控應用,大多數情況使用 HTTP 的方式。

    Actuator 端點說明

    端點 描述
    auditevents 獲取當前應用暴露的審計事件信息
    beans 獲取應用中所有的 Spring Beans 的完整關係列表
    caches 獲取公開可以用的緩存
    conditions 獲取自動配置條件信息,記錄哪些自動配置條件通過和沒通過的原因
    configprops 獲取所有配置屬性,包括默認配置,显示一個所有 @ConfigurationProperties 的整理列版本
    env 獲取所有環境變量
    flyway 獲取已應用的所有Flyway數據庫遷移信息,需要一個或多個 Flyway Bean
    liquibase 獲取已應用的所有Liquibase數據庫遷移。需要一個或多個 Liquibase Bean
    health 獲取應用程序健康指標(運行狀況信息)
    httptrace 獲取HTTP跟蹤信息(默認情況下,最近100個HTTP請求-響應交換)。需要 HttpTraceRepository Bean
    info 獲取應用程序信息
    integrationgraph 显示 Spring Integration 圖。需要依賴 spring-integration-core
    loggers 显示和修改應用程序中日誌的配置
    logfile 返回日誌文件的內容(如果已設置logging.file.name或logging.file.path屬性)
    metrics 獲取系統度量指標信息
    mappings 显示所有@RequestMapping路徑的整理列表
    scheduledtasks 显示應用程序中的計劃任務
    sessions 允許從Spring Session支持的會話存儲中檢索和刪除用戶會話。需要使用Spring Session的基於Servlet的Web應用程序
    shutdown 關閉應用,要求endpoints.shutdown.enabled設置為true,默認為 false
    threaddump 獲取系統線程轉儲信息
    heapdump 返回hprof堆轉儲文件
    jolokia 通過HTTP公開JMX bean(當Jolokia在類路徑上時,不適用於WebFlux)。需要依賴 jolokia-core
    prometheus 以Prometheus服務器可以抓取的格式公開指標。需要依賴 micrometer-registry-prometheus

    Actuator 使用及配置

    快速使用

    項目依賴

    <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!-- actuator -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
    </dependencies>

    配置文件

    management.endpoints.enabled-by-default=true
    #啟動所有端點
    management.endpoints.web.exposure.include=*
    #自定義管理端點路徑
    #management.endpoints.web.base-path=/manage

    Spring Boot 2.X 中,Actuator 默認只開放 health 和 info 兩個端點。

    添加management.endpoints.web.exposure.include=*配置后啟動應用,訪問 http://127.0.0.1:8080/actuator 我們可以看到所有的 Actuator 端點列表。

    如果將management.endpoints.enabled-by-default設置為false,則禁用所有端點,如需啟用則如下:

    management.endpoints.enabled-by-default=false
    management.endpoint.info.enabled=true

    禁用的端點將從應用程序上下文中完全刪除。如果只想更改公開端點,使用include和exclude屬性。使用如下:

    management.endpoints.web.exposure.include=*
    management.endpoints.web.exposure.exclude=env,beans

    management.endpoints.web.base-path=/manage 配置表示將 /actuator 路徑重定義為 /manage。

    常用端點詳解

    health

    主要用來檢測應用的運行狀況,是使用最多的一個監控點。監控軟件通常使用該接口實時監測應用運行狀況,在系統出現故障時把報警信息推送給相關人員,如磁盤空間使用情況、數據庫和緩存等的一些健康指標。
    默認情況下 health 端點是開放的,訪問 http://127.0.0.1:8080/actuator/health 即可看到應用運行狀態。

    {"status":"UP"}

    如果需要看到詳細信息,則需要做添加配置:

    management.endpoint.health.show-details=always

    訪問返回信息如下:

    {"status":"UP","details":{"diskSpace":{"status":"UP","details":{"total":180002725888,"free":8687988736,"threshold":10485760}}}}

    info

    查看應用信息是否在 application.properties 中配置。如我們在項目中配置是:

    info.app.name=Spring Boot Actuator Demo
    info.app.version=v1.0.0
    info.app.description=Spring Boot Actuator Demo

    啟動項目,訪問 http://127.0.0.1:8080/actuator/info 返回信息如下:

    {"app":{"name":"Spring Boot Actuator Demo","version":"v1.0.0","description":"Spring Boot Actuator Demo"}}

    env

    通過 env 可以獲取到所有關於當前 Spring Boot 應用程序的運行環境信息,如:操作系統信息(systemProperties)、環境變量信息、JDK 版本及 ClassPath 信息、當前啟用的配置文件(activeProfiles)、propertySources、應用程序配置信息(applicationConfig)等。

    可以通過 http://127.0.0.1:8080/actuator/env/{name} ,name表示想要查看的信息,可以獨立显示。

    beans

    訪問 http://127.0.0.1:8080/actuator/beans 返回部分信息如下:

    {
        "contexts": {
            "Spring Boot Actuator Demo": {
                "beans": {
                    "endpointCachingOperationInvokerAdvisor": {
                        "aliases": [
                        ],
                        "scope": "singleton",
                        "type": "org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationInvokerAdvisor",
                        "resource": "class path resource [org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfiguration.class]",
                        "dependencies": [
                            "environment"
                        ]
                    },
                    "defaultServletHandlerMapping": {
                        "aliases": [
                        ],
                        "scope": "singleton",
                        "type": "org.springframework.web.servlet.HandlerMapping",
                        "resource": "class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]",
                        "dependencies": [
                        ]
                    },
                    ...
                }
            }
        }
    }

    從返回的信息中我們可以看出主要展示了 bean 的別名、類型、是否單例、類的地址、依賴等信息。

    conditions

    通過 conditions 可以在應用運行時查看代碼了某個配置在什麼條件下生效,或者某個自動配置為什麼沒有生效。

    訪問 http://127.0.0.1:8080/actuator/conditions 返回部分信息如下:

    {
        "contexts": {
            "Spring Boot Actuator Demo": {
                "positiveMatches": {
                    "SpringBootAdminClientAutoConfiguration": [
                        {
                            "condition": "OnWebApplicationCondition",
                            "message": "@ConditionalOnWebApplication (required) found 'session' scope"
                        },
                        {
                            "condition": "SpringBootAdminClientEnabledCondition",
                            "message": "matched"
                        }
                    ],
                    "SpringBootAdminClientAutoConfiguration#metadataContributor": [
                        {
                            "condition": "OnBeanCondition",
                            "message": "@ConditionalOnMissingBean (types: de.codecentric.boot.admin.client.registration.metadata.CompositeMetadataContributor; SearchStrategy: all) did not find any beans"
                        }
                    ],
                    ...
                }
            }
        }
    }

    loggers

    獲取系統的日誌信息。

    訪問 http://127.0.0.1:8080/actuator/loggers 返回部分信息如下:

    {
        "levels": [
            "OFF",
            "ERROR",
            "WARN",
            "INFO",
            "DEBUG",
            "TRACE"
        ],
        "loggers": {
            "ROOT": {
                "configuredLevel": "INFO",
                "effectiveLevel": "INFO"
            },
            "cn": {
                "configuredLevel": null,
                "effectiveLevel": "INFO"
            },
            "cn.zwqh": {
                "configuredLevel": null,
                "effectiveLevel": "INFO"
            },
            "cn.zwqh.springboot": {
                "configuredLevel": null,
                "effectiveLevel": "INFO"
            },
            ...
        }
    }

    mappings

    查看所有 URL 映射,即所有 @RequestMapping 路徑的整理列表。

    訪問 http://127.0.0.1:8080/actuator/mappings 返回部分信息如下:

    {
        "contexts": {
            "Spring Boot Actuator Demo": {
                "mappings": {
                    "dispatcherServlets": {
                        "dispatcherServlet": [
                            {
                                "handler": "ResourceHttpRequestHandler [class path resource [META-INF/resources/], class path resource [resources/], class path resource [static/], class path resource [public/], ServletContext resource [/], class path resource []]",
                                "predicate": "/**/favicon.ico",
                                "details": null
                            },
                            ...
                        ]
                    }
                }
            }
        }
    }

    heapdump

    訪問:http://127.0.0.1:8080/actuator/heapdump會自動生成一個 GZip 壓縮的 Jvm 的堆文件 heapdump,我們可以使用 JDK 自帶的 Jvm 監控工具 VisualVM 打開此文件查看。如圖:

    VisualVM下載:https://visualvm.github.io/download.html

    threaddump

    獲取系統線程的轉儲信息,主要展示了線程名、線程ID、線程的狀態、是否等待鎖資源等信息。在工作中,我們可以通過查看線程的情況來排查相關問題。

    訪問 http://127.0.0.1:8080/actuator/threaddump 返回部分信息如下:

    {
        "threads": [
            {
                "threadName": "DestroyJavaVM",
                "threadId": 40,
                "blockedTime": -1,
                "blockedCount": 0,
                "waitedTime": -1,
                "waitedCount": 0,
                "lockName": null,
                "lockOwnerId": -1,
                "lockOwnerName": null,
                "inNative": false,
                "suspended": false,
                "threadState": "RUNNABLE",
                "stackTrace": [
                ],
                "lockedMonitors": [
                ],
                "lockedSynchronizers": [
                ],
                "lockInfo": null
            },
            ...
        ]
    }

    shutdown

    開啟可以接口關閉 Spring Boot 應用,要使用這個功能需要做如下配置:

    management.endpoint.shutdown.enabled=true

    可以通過 post(僅支持 post) 請求訪問 http://127.0.0.1:8080/actuator/shutdown 關閉應用。

    metrics

    訪問 http://127.0.0.1:8080/actuator/metrics 可以獲取系統度量指標信息項如下:

    {
        "names": [
            "jvm.memory.max",
            "jvm.threads.states",
            "jvm.gc.pause",
            "http.server.requests",
            "process.files.max",
            "jvm.gc.memory.promoted",
            "system.load.average.1m",
            "jvm.memory.used",
            "jvm.gc.max.data.size",
            "jvm.memory.committed",
            "system.cpu.count",
            "logback.events",
            "tomcat.global.sent",
            "jvm.buffer.memory.used",
            "tomcat.sessions.created",
            "jvm.threads.daemon",
            "system.cpu.usage",
            "jvm.gc.memory.allocated",
            "tomcat.global.request.max",
            "tomcat.global.request",
            "tomcat.sessions.expired",
            "jvm.threads.live",
            "jvm.threads.peak",
            "tomcat.global.received",
            "process.uptime",
            "tomcat.sessions.rejected",
            "process.cpu.usage",
            "tomcat.threads.config.max",
            "jvm.classes.loaded",
            "jvm.classes.unloaded",
            "tomcat.global.error",
            "tomcat.sessions.active.current",
            "tomcat.sessions.alive.max",
            "jvm.gc.live.data.size",
            "tomcat.threads.current",
            "process.files.open",
            "jvm.buffer.count",
            "jvm.buffer.total.capacity",
            "tomcat.sessions.active.max",
            "tomcat.threads.busy",
            "process.start.time"
        ]
    }

    對應訪問 names 中的指標,可以查看具體的指標信息。如訪問 http://127.0.0.1:8080/actuator/metrics/jvm.memory.used 返回信息如下:

    {
        "name": "jvm.memory.used",
        "description": "The amount of used memory",
        "baseUnit": "bytes",
        "measurements": [
            {
                "statistic": "VALUE",
                "value": 1.16828136E8
            }
        ],
        "availableTags": [
            {
                "tag": "area",
                "values": [
                    "heap",
                    "nonheap"
                ]
            },
            {
                "tag": "id",
                "values": [
                    "Compressed Class Space",
                    "PS Survivor Space",
                    "PS Old Gen",
                    "Metaspace",
                    "PS Eden Space",
                    "Code Cache"
                ]
            }
        ]
    }

    示例代碼

    參考文檔

    https://docs.spring.io/spring-boot/docs/2.2.1.RELEASE/reference/html/production-ready-features.html

    非特殊說明,本文版權歸 所有,轉載請註明出處.

    原文標題:Spring Boot 2.X(十六):應用監控之 Spring Boot Actuator 使用及配置

    原文地址:

    如果文章對您有幫助,請掃碼關注下我的公眾號,文章持續更新中…

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

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

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

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

    ※教你寫出一流的銷售文案?

  • 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 是電子產業重要的元件?

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

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

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

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

  • 深入理解計算機系統 — 信息的表示和處理

    深入理解計算機系統 — 信息的表示和處理

    1. 信息的存儲

        大多數計算機使用 8 位的塊,或者字節,作為最小的尋址內存單位,而非訪問內存中單獨的位,機器級程序將內存視為一個非常大的字節數組,稱為 虛擬內存 ,內存的每個字節都用一個唯一的数字標識,稱為它的 地址 。以 C 語言的指針為例,指針使用時指向某一個存儲塊的首字節的 虛擬地址 ,C 編譯器將指針和其類型信息結合起來,這樣即可以根據指針的類型,生成不同的機器級代碼來訪問存儲在指針所指向位置處的值。每個程序對象可以簡單視為一個字節塊,而程序本身就是一個字節序列。

    1.1 十六進製表示法

        一個字節由 8 位組成。用二進製表示即 00000000 ~ 11111111 。十進製表示為 0 ~ 255 。由於兩者表示要麼過於冗餘,要麼轉換不遍,因此通常使用十六進制來表示一個字節。這幾種進制的轉換在此就不多說了。

    1.2 字數據大小

        每台計算機都會有一個字長(此處字長非字節長度),指明 指針數據的標稱大小(nominal size),因為虛擬地址是以這樣的一個字來進行編碼的,所以字長決定的最重要的一個系統參數即是虛擬地址空間的最大大小。 對於一個字長為 w 位的機器而言,虛擬地址的範圍為 0 ~ (2 ^w )- 1 ,程序最多訪問 2 ^ w 個字節。以 32 位機器為例,32位字長限制虛擬地址空間為 (2 ^32) -1 ,程序最多訪問 2 ^ 32 個字節,大約為 4 x 10^9 字節,即4 GB ( 根據 2 ^ 10 (1024) 約等於 10 ^ 3 (1000) ,可以得到 2 ^ 32 =  4 * 2^30 = 4 * 10 ^ 9 ) 。64位機器的限制虛擬地址空間為 16 EB。大約為 1.84 x 10 ^9 。

    1.3 尋址和字節順序

        對於跨越多個字節的對象,我們必須建立兩個規則:這個對象的地址是什麼以及在內存中如何排列這些字節。在幾乎所有的機器上,多字節對象都被存儲為連續的字節序列,對象的地址為這個字節序列中最小的字節地址。以 int 類型為例,假定int 大小為32 位,有變量 int x = 0x01234567 。若 x 的地址為 0x100 ,則 x 的 4 個字節將被存儲在 0x100 , 0x101 , 0x102, 0x103 的位置,此時 4個字節的值分別為 0x01, 0x23, 0x45, 0x67,那麼在內存中的排列順序有如下兩種情況,

    • 大端法:最高有效字節放在最前面的方式稱為大端法,即將一個数字的最高位字節放在最小的字節地址。
    • 小端法:最低有效字節放在最前面的方式稱為小端法,即將一個数字的最低位字節放在最小的字節地址。

      以上面的 x 為例,x 的最高位字節是 0x01 ,將其放在最小的字節地址即 0x100。x 的最低位字節為 0x67 ,將其放在最小的字節地址 0x100 。即大小端對應高低位字節。對於我們來說,機器的字節順序是完全不可見的,我們大部分情況下也無需關心其字節順序,但是在不同類型的機器之間通過網絡傳遞二進制數據的時候,如小端法機器傳送數據給大端法機器時,接受方接收到的字節序會變成反序,為了避免這種問題的產生,發送方和接收方都需要遵循一個網絡規則,發送方將二進制數據轉換成網絡標準,接收方再將這個網絡標準的字節序轉換成自己的字節序。此外,我們在閱讀機器級代碼的時候,可能會出現如下的情況:

      暫時忽略這條指令的意義,可以看到左邊6個字節分別為 01 05 43 0b 20 00 ,而右邊的指令中的地址為 0x200b43,可以看到從左邊的第三個字節開始,43 0b 20 是右邊指令地址的倒序,因此在閱讀這種機器級代碼的時候,也需要注意字節序的問題。此外還存在一種情況。如下圖所示。

        我們可以看到, show_bytes 這個函數可以打印出 start 指針指向的地址開始的 len 個字節內容,且不受字節序的影響,那麼它是如何做到的呢?在 show_int 函數中,可以看到它將 參數 x 的地址強制類型轉換為了 byte_pointer , 即 unsigned char * 。通過強制類型轉換的 start 指針指向的仍是 x 的最低字節地址,但是其類型改變了,通過其類型編譯器會認為該指針指向的對象大小為 1 個字節,此時將該指針進行 ++ 操作可以得到順延下一個字節的內容,從而得到對應的整個對象的字節序列中每個字節的內容而不受字節序影響。

    1.4 字符串

        在C語言中,字符串被編碼為一個以 null (其值為0 )字符結尾的字符數組。每個字符都有某個標準編碼來表示,最常見的則是 ASCII 字符碼。假如我們調用 show_bytes(“12345”, 6),那麼會輸出 31 32 33 34 35 00 。可以看到最後打印出了一個終止符,所以通常 C 字符串的長度為實際字符串長度 + 1。 在C 標準庫中的 strlen 函數可以傳入一個字符串得出其長度,這裏的長度即是實際長度,不包含終止符。

    2. 整數表示

        在本章節中,介紹了編碼整數的兩種不同的方式,一種只能表示非負數,另一種則能夠表示負數,正數和零。接下來逐一進行介紹。

    2.1 整型數據類型

        C語言中,整數有多種數據類型,如下圖所示,此外可以通過加上 unsigned 符號來限定該數據類型為非負數。這些數據類型有的是根據機器的字長(32位和64位)決定其實際最大值和最小值的範圍。我們可以看到,圖中最小值和最大值的取值範圍是不對稱的,負數的取值範圍比正數大一,當我們考慮如何表現負數時,會看到為什麼會這樣。

        關於無符號整數的編碼,其實與普通的十進制正數轉換成二進制沒有什麼區別,假設字長 w = 32 位,轉換后大於 32 位的数字將被捨去。這裏主要介紹一下關於有符號数字的編碼,通常計算機使用的編碼錶示方式為 補碼 ,在這個表示方式中,將字的最高有效位(即符號位)表示為負權,權重為 – 2^(w-1) ,當 w 位的值為 1 時表示為負數,反之為正數。以 -1 為例,-1 的補碼為1111 1111  …. …. 1111 ,即 -2^31 + 2^30 + … + 2^0 = -1 ,通常我們看到一個負數想要直接將其使用補碼錶示還是有些不方便的,因此我們可以先使用原碼錶示,所謂原碼和普通的十進制數轉二進制數沒有區別,只不過最高位用來表示符號位,然後再求其反碼,即符號位不變,其餘位取反加 1,就可以得到這個負數的補碼了,還是以 -1 舉例, -1 的原碼為 1000 0000 …. 0001 ,其反碼的值為 1111 1111 …. 1111 ,與 -1 的補碼值是相同的。而正數的補碼為其本身,不需要做這種轉換。

        那麼為什麼要使用補碼這種表示方式呢,首先,二進制補碼可以使正負數相加時仍然採用正常加法的邏輯,不需要做特殊的處理,此外,如果不採用補碼錶示,採用原碼的表示方法,那麼會出現幾個問題,正負零的存在,以及提高了減法的計算複雜度,而補碼可以十分簡單的計算正負數相加,只需求出兩者的補碼對其進行加法,更多關於補碼的解釋可以參考  。

    PS: 為什麼正負數補碼相加會得到正確的結果,這裏個人的見解是:由於補碼最高位為負權,而正數與負數補碼相加相當於正數去抵消這個負權。比如 -16 的補碼為 1111 …. 1111 0000,加上正數 1,由於正數的補碼為本身,所以等價於 -16 + 1  == (-2^31 + 2^30 + … + 2^4 ) +  2^ 0 ,相當於多了一個 2^0 的正權去抵消其最高位的負權。

    2.2 有符號數和無符號數之間的轉換

        C語言允許各種不同的数字類型之間進行強制類型轉換, 如 int x= -1 ; unsigned y = (unsigned) x ; 此時會將 x 的值強制類型轉換成 unsigned 類型然後賦值給 y ,那麼此時 y 的值是多少呢?可以通過打印兩者的十六進制值來看有什麼區別。下面為 test.c 的代碼:

    int main()

    {

    int x = -1;

    unsigned y = (unsigned) x;

    printf(“%x \n”, x);

    printf(“%x \n”, x);

    return 0;

    }

    此處為編譯后可執行文件的輸出結果:

    ffffffff
    ffffffff

    可以看到, x 和 y 的十六進制值是相同的,這也說明了,強制類型轉換並不會改變數據底層的位表示,只是改變了解釋位模式的方式。我們可以利用 printf 的指示符進一步驗證這個結果,使用 %d (有符號十進制), %u (無符號十進制), 來打印 x 和 y 的值。以下是代碼:

    int main()

    {

    int x = 1;

    unsigned y = (unsigned) x;

    printf(“x format d = %d , format u = %u \n”, x, x);

    printf(“y format d = %d , format u = %u \n”, y, y);

    return 0;

    }

    這是編譯后可執行文件的對應輸出:

    x format d = -1 , format u = 4294967295
    y format d = -1 , format u = 4294967295

     我們可以看到,我們使用指示符控制了解釋這些位的方式,得到的結果是一致的。

    2.3 整數運算

     關於整數的運算,主要就是加減乘除四種運算,補碼的加減乘除都比較簡單明了,這裏主要說一下除法的舍入問題,首先,我們先確認下 C 語言中的舍入方式,在 C 語言中,浮點數被賦值給整數時,小數位總是被捨去,如

    float f = 1.5;

    int x = f ;

    printf(“%d \n “, x);

    輸出的結果為:

    1

    當 f 為負數時結果又是如何呢 ?

    float f = -1.5 ;

    int x = f;

    printf(“%d \n”, x);

    輸出的結果為:

    -1

    因此我們可以認為,C語言的舍入方式為向零舍入。接下來看一下除法的舍入問題。此處先以除以 2 的冪的無符號除法為例,

    上圖表示 12340 / 2^k 的時候二進制與對應的十進制的表示,此時的舍入是完全沒有問題的。接下來看下除以 2 的冪的有符號除法。

        當k = 4 的時候,-12340 / 2^ 4 == -771.25,此時的正確舍入值應該為 -771,但是其卻舍入成了 -772。這是因為,如果我們單純使用右移來進行除法的時候,其舍入方式為向下舍入,即總是往更小值的方向舍入,在沒有小數位的情況下是正確的,但是如果有小數位的時候,如 -771.25 舍入為 -772, 771.25 舍入為 771。而C語言的舍入方式為向零舍入,即總是往靠近零的值舍入,如 771.25 舍入為 771, -771.25 舍入為 -771。那麼如何實現這種舍入方式呢。當被除數為負數時,我們可以通過加上一個偏置值來糾正這種不正確的舍入方式。

        我們可以觀察一下上圖的有符號除法例子,可以發現,當右移的 k 位單獨拿出來,不為 0 的時候,會導致舍入結果不正確,這是因為,k 位的值不為 0 的時候,表示該結果有小數,所以可以通過 (x + (1 << k) – 1) >> k 得到正確的結果, (1 << k) – 1 可以獲得 k 個 1,x 加上 k 個 1 可以使捨去的 k 位不為 0 時產生進位,x >> k 的結果加一,從而使舍入正確。

    關於整數的表示和運算,個人覺得有幾個需要關注的點,一是溢出問題,由於使用有限的位來表示整數,所以當数字過大的時候可能會產生溢出,溢出的位會被捨去,但是有符號數的溢出可能會使符號位被置反,如 0111 1111 …. 1111 + 1 = 1000 0000 …. 0000,0111 1111 …. 1111 為 INT_MAX , INT_MAX + 1 會得到 INT_MIN。此外,無符號數與有符號數進行比較的時候,會使有符號數強制轉換為無符號數,如果有以下循環代碼:

    for(size_t i = 10; i >= 0 ; i–);

    由於 i 為無符號數,當 i == 0 的時候,判斷還會繼續循環下去, 0 – 1  = -1 , -1 的補碼錶示為 1111 1111 …. 1111 , 剛好是無符號數的最大值,會導致死循環。因此也需要注意一切與無符號類型數據的運算,以及強制類型轉換可能出現的問題。

    3. 浮點數

        終於來到了這一章的重點內容之一(其實感覺這本書哪裡都挺重要的),這裏主要介紹浮點數是如何表示的,並且介紹浮點數舍入的問題(和上面講到的舍入不大一樣),浮點數的表示及其運算標準稱為 IEEE754 標準,初看可能會讓你覺得有些晦澀難懂,但是理解之後會覺得設計的十分巧妙。

    3.1 定點表示法

        首先讓我們先看下十進制的浮點數是如何表示的,浮點數的定義與小數點息息相關,定義在小數點左邊的数字的權是 10 的正冪,右邊的数字為 10 的負冪,如 12.34 表示 1 * 10^ 1 + 2 * 10^0 + 3 * 10 ^-1 + 4 * 10 ^ -2 = 12又34/100,同理可以得到二進制的浮點數表示,即定義在小數點左邊的数字的權是 2 的正冪,右邊的数字為 2 的負冪,如 101.11 = 1 * 2^2 + 0 * 2^1 + 1 * 2^0 + 1 * 2^-1 + 1 * 2^-2 。這種浮點數的表示方法是有缺陷的,無法精準的表示特定的数字,以 1/5 為例,可以用 十進制数字 0.2 表示,但是我們無法用二進制數字錶示它,只能近似的表示它,通過增加二進製表示的長度可以提升表示的精度。如下圖所示。

    3.2 IEEE754標準

        在前面談到的定點表示法不能有效的表示一個比較大的数字,例如 5 x 2^100 是用 101 後面跟隨 100 個零的位模式,我們希望能夠通過給定 x 和 y 的值來表示如 x * 2 ^y 的数字。IEEE754 標準使用 V = ( – 1)^S * M * 2^E 的形式來表示一個數。

    • 符號(Sign): S 決定這個數是負數(S = 1 )還是正數 (S = 0), 對於數值為 0 的符號位做特殊解釋。
    • 尾數(Significand): M 是一個二進制小數,範圍為 1 ~ 2 – e , 或者是 0 ~ 1 – e 。
    • 階碼(Exponent): E 的作用是對浮點數進行加權,這個權重是 2 的 E 次冪(E 可能為負數)。

    通過將浮點數的位劃分為三個字段,分別對這些值進行編碼:

    • 一個單獨的符號位 S 。
    • k 位的階碼字段 ,exp = e(0) e(1) e(2) … e(k-1) ,exp 用來編碼階碼 E。
    • n 位的小数字段 ,   frac = f(n-1) … f(1) f(0) ,frac 用來編碼尾數 M。

    下圖是該標準下封裝到字中的兩種最常見的格式。

    此外,根據階碼值(exp),被編碼的值可以分為下圖幾種情況(階碼值全為 0 ,階碼值全為 1 , 階碼值不全為 0 也不全為 1):

    接下來對這幾種格式進行一一介紹~:

    • 規格化浮點數 : 這是最普遍的情況,當 exp 的值不全為 0 也不全為 1 時,就屬於這種情況,這種情況下,階碼值 E = e – bias ,其中 e 為無符號數,即 exp 的值,而 bias 是一個 2^(k-1) – 1 的偏置值(單精度為 127,雙精度為 1023),而小数字段 frac 被解釋為描述小數值 f ,其中 0 <= f < 1,其二進製表示為 0.f(n-1)…f(1)f(0) 的数字,也就是二進制小數點在最高有效位的左邊的形式。尾數定義為 M = 1 + f 。 有時候這種方式也叫做 隱含 1 開頭的表示(implied leading  1),因為這種定義我們可以把 M 看成一個二進製表示為 1.f(n-1) … f(1)f(0) 的数字。既然我們總是能調整階碼 E ,使得尾數 M 在範圍 1 <= M < 2 之中(假設沒有溢出),那麼這樣可以節約一個位,因為第一位總是為 1 。
    • 非規格化浮點數 : 當 exp 的值全為 0 的時候,所表示的浮點數為非規格化類型,E = 1 – bias ,而尾數的值為 M = f 。不含開頭的 1 。非規格化有兩種用途,首先它提供了表示  0 的方法,因為規格化數使得 M >=  1,所以不能表示 0 ,另外非規格化數另一個功能則是表示那些非常接近於 0.0 的數,他們提供了一種屬性,稱為逐漸溢出,其中,可能的數值均勻分佈接近於 0.0 。
    • 特殊值 : 最後一類數值是指當階碼全為 1 的時候出現的。當小數域全為 0 時,表示為無窮大/小,當我們將兩個非常大的數相乘時,或者除以零時,無窮能夠表示溢出的結果。當小數域為非 0 時,結果為 NaN(Not a Number),一些運算的結果不能為實數或者無窮時,會返回 NaN,比如 根號 -1 ,或者 無窮減無窮。此外,在某些應用中也可以用來表示未初始化的數值。

        首先,通過一個字長為 8 位的例子,來看一下IEEE754標準實際上使用時是如何表示的 :

        上圖為展示了假定 w = 8 的字長,k = 4 的階碼位以及 n = 3 的小數位。偏移量為 2 ^ ( k -1 ) -1 = (2 ^ 3) – 1 = 7。圖中分別展示了非規格化數,規格化數以及特殊值是如何編碼的,以及如何結合在一起表示 V = (2^E) * M。我們可以看到,從最大非規格化數到最小規格化數,其值的轉變十分平滑,從 7/512 到 8/512 。這得益於非規格化數的 E 定義為 1 – bias ,最大的非規格化數的階碼值 E 與最小的規格化數的階碼值 E 是相等的,兩者唯一的區別在於 M 值,規格化數尾數 M = 1 + f ,而非規格化的尾數 M = f ,因為非規格化值是用於表示 [0, 1] 區間的小數的,當 f 達到最大值時, f 接近於 1 ,此時最大的非規格化數再進一位,小數 M 只能表示為 1 ,因為此時限制於 f 的位數,沒有比 f 大又比 1 小的小數值 ,進位後轉換成了規格化數,此時 f = 0 , 在階碼值 E 相等的情況下,讓規格化的 M = 1 + f 恰好可以使兩者進行平滑的轉換。

        假如我們使非規格化數的 E = 0 – bias = -7 ,那麼會導致最大非規格化數和最小規格化數的粒度過大,兩者的值分別為 7/1024 和 8/512 。這種定義可以彌補非規格化數的尾數沒有隱含的 1 。通過上述的例子,我們可以發現 ,假如我們把上述的例子按無符號整數表示的話,會發現它的值是有序上升的,這不是偶然的,IEEE 格式如此設計就是為了浮點數能夠使用整數排序函數進行排序。

        通過練習將整數值轉換為浮點數值形式對理解浮點數很有用,以 12345(十進制) 為例,其二進製表示為 1100 0000 1110 01 . 0  ,通過將小數點左移 13 位得到 1.1000000111001 * 2^13 ,我們丟棄開頭的 1 (這裏的 1 就是規格化數隱含的 1),構造小数字段,當 f 不足 23 位的時候,往後填充 0 ,即 M = 1 + f = 1 + 1000 0001 1100 1000 0000 000 ,當 f 大於 23 位的時候,f 多出的位會被捨棄(這裏可以看出浮點數的兩個性質,以 int 類型和 float 類型舉例,當 int 值 大於 2^24 的時候,int 轉換成 float 兩者很有可能值會不相等,因為多出的部分被捨棄了,二是 float 可以表示的數值遠遠大於 int 類型,V =  (-1 ^ S)  * M * 2^E  ,E 最高可以等於 127 ,float 的最大值為 (2^127) * (1 + f),而 int 最大值為 (2^31) -1。

    3.3 舍入

        浮點數的舍入方式有四種,分別是向上舍入,向下舍入,向零舍入,向偶數舍入。下圖是幾種舍入方式的例子 :

    偶數舍入是浮點數默認的舍入方式,可以看到,向偶數舍入時,當小數值為中間值時,會使最低有效数字總為偶數,如 2.5 和 1.5 都舍入為 2 。為什麼使用向偶數舍入呢,假設我們採用向上舍入,用這種方法舍入一組數值,會在計算這些值的平均值中引入統計偏差。我們採用這種方式舍入得到的平均值總是比這些數本身的平均值要略高一些,反之向下舍入亦然,向偶數舍入則可以使在 50% 的時間內向上舍入,50% 的時間內向下舍入。

    4. 小結

      • 計算機將信息編碼為位(bit),通常組織成字節序列,有不同的編碼方式來表示整數,實數和字符串。不同的計算機模型在編碼数字和多字節數據中的字節順序時使用不同的約定。
      • 絕大部分機器使用補碼來編碼整數。對於浮點數使用 IEEE754 標準來編碼。
      • 在進行對無符號和有符號整數進行強制類型轉換時,底層的位模式是不變的。(浮點數與整數轉換則會進行 改變,如 float f = 1.25; int x = f; 此時打印兩者的十六進制值,可以分別輸出為 f = 92463258 ,x = 1 )
      • 由於編碼的長度有限,當超出表示範圍時,有限長度會引起數值溢出,如 x * x 可能會得到負數。當浮點數非常接近於 0.0 時,轉換成 0 時也會產生下溢。
      • 使用補碼運算 ~x + 1 = -x (不適用於 INT_MIN) 。可以通過 (2^k) – 1 生成一個 k 位的掩碼。
      • 浮點數不具備結合率,因為可能發生溢出或者舍入,從而失去精度。如(le20 * le20) * le-20 = 正無窮,而 le20 * (le20 * le-20) = le20 。此外也不具備分配性,如 le20 * (le20 – le20) = 0.0 ,而 le20 * le20 – le20 * le20 = NaN。

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

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

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

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

    ※教你寫出一流的銷售文案?

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

    計算機專業學生常用網站

    這篇文章我很早之前就想寫了,但奈何一直沒有合適的時間,也不知道應該怎樣去表達。現在自己是一個大三的學生,學習了兩年多的計算機,我不知道自己算不算合格,和那些初中就搞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 是電子產業重要的元件?

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

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

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

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

  • Net Core Identity 身份驗證:註冊、登錄和註銷 (簡單示例)

    一、前言

      一般我們自己的系統都會用自己設置的一套身份驗證授權的代碼,這次用net core的identity來完成簡單的註冊、登錄和註銷。

    二、數據庫

      首先就是創建上下文,我這裏簡單的建了Users和UserClaim表,要是沒有UserClaim等下的登錄操作是會報錯的,應該是有身份認證方面的關係。

        public class DataBaseContext : DbContext
        {
            public DataBaseContext(DbContextOptions<DataBaseContext> options)
            : base(options)
            { }
            public DbSet<User> Users { get; set; }
            public DbSet<IdentityUserClaim<string>> UserClaim { get; set; }
        }
        public class User : IdentityUser
        {
            public string companyId { get; set; }
            public string PassWord { get; set; }
        }

      這裏User繼承了IdentityUser,IdentityUser中就用很多的基礎字段,像是UserName等所以我們可以再User類中擴展我們的字段。

      add-migration Init和update-database Init再控制台執行,生成表。

    三、Startup註冊服務

      在ConfigureServices中註冊如下

      1、數據庫上下文連接

      //添加數據庫連接
      services.AddDbContext<DataBaseContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

      2、添加標識服務,包括默認的UI、令牌提供和身份驗的cookie,並且添加identity信息存儲的實體框架實現,用於關聯數據庫創建用戶獲取用戶信息等。AddDefaultIdentity一個相當於AddIdentity、AddDefaultUI和AddDefaultTokenProviders三個。如果User不繼承IdentityUser使用AddEntityFrameworkStores會報錯。

      services.AddDefaultIdentity<User>().AddEntityFrameworkStores<DataBaseContext>();

      3、添加Identity的選項,可以設定密碼的強度、長度、使用字符、密碼輸入錯誤次數等等。

        services.Configure<IdentityOptions>(options =>
        {
            // 密碼設置
            options.Password.RequireDigit = false;
            options.Password.RequireLowercase = false;
            options.Password.RequireNonAlphanumeric = false;
            options.Password.RequireUppercase = false;
            options.Password.RequiredLength = 1;
            options.Password.RequiredUniqueChars = 1;
    
            // 鎖定設置
            options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
            options.Lockout.MaxFailedAccessAttempts = 5;
            options.Lockout.AllowedForNewUsers = true;
    
            // 用戶設置
            options.User.AllowedUserNameCharacters =
            "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+";
            options.User.RequireUniqueEmail = false;
        });

      4、配置應用程序的cookie

        services.ConfigureApplicationCookie(options =>
        {
            // Cookie設置
            options.Cookie.HttpOnly = true;
            options.ExpireTimeSpan = TimeSpan.FromMinutes(5);
    
            options.LoginPath = "/Login/Index";
            options.AccessDeniedPath = "/Home/Index";
            options.SlidingExpiration = true;
        });

      在Config中註冊身份認證

      app.UseAuthentication();

    四、簡單的登錄、註冊和註銷

      既然是簡單的例子,那我是真的怎麼簡單怎麼來,代碼也就沒幾條。

      先創建一個Home控制器並加上[Authorize]特性,沒得到驗證的就統統無法訪問。按照上面的Startup.cs已經將UserManager依賴注入了,它是用來管理用戶的比如註冊啥的。

      [Authorize]
        public class HomeController : Controller
        { 
            private UserManager<User> userManager;public HomeController(UserManager<User> _userManager)
            {
                userManager = _userManager;
            }
            public async Task<IActionResult> Index()
            {
                var res = await userManager.GetUserAsync(HttpContext.User);
                return View();
            }
        }

      然後創建一個Login控制器,我們可以在裏面寫登錄、註冊和註銷的方法。Login控制器除了UserManager外還要注入SignManager,它是用來用戶登錄、註銷等操作的。

        public class LoginController : Controller
        {
            //用於提供持久性存儲的用戶信息
            private UserManager<User> userManager;
            private SignInManager<User> signManager;
            public LoginController(UserManager<User> _userManager,SignInManager<User> _signManager)
            {
                userManager = _userManager;
                signManager = _signManager;
            }
        }

      1、註冊

       註冊直接用CreateAsync方法,會自動在數據庫創建用戶。SignInAsync方法用於剛註冊的馬上用戶登錄。

            public async Task<IActionResult> Register()
            {
                var user = new User() { UserName = "xu2", PhoneNumber = "123", companyId = "1" };
                var result = await userManager.CreateAsync(user, "123");
                await signManager.SignInAsync(user, true);
                if (result.Succeeded)
                    return Redirect("/Home/Index");
                return Redirect("/Login/Index");
            }

      2、登錄

      登錄不能用SignInAsync了,要用PasswordSignInAsync密碼登錄

            public async Task<IActionResult> Index()
            {
                var s = await signManager.PasswordSignInAsync("xu", "123", true, false);
                return View();
            }

      3、註銷

      這邊直接return view()是無法註銷的,因為cookie會被重新加載,需要return Redirect(“/Home/Index”)重定向

            public async Task<IActionResult> LogOut()
            {
                await signManager.SignOutAsync();
                return View();
            }

      4、獲取當前登錄用戶

        var res = await userManager.GetUserAsync(HttpContext.User);

     

     

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

    【其他文章推薦】

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

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

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

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

    ※別再煩惱如何寫文案,掌握八大原則!