用雲開發快速製作客戶業務需求收集小程序丨實戰

一、導語

​ 如何省去企業上門(現場)搜集客戶需求的環節,節約企業人力和時間成本,將客戶的業務定製需求直接上傳至雲數據庫?雲開發為我們提供了這個便利!

二、需求背景

​ 作為一名XX公司IT萌萌新,這段時間對小程序開發一直有非常濃厚的興趣,並且感慨於“雲開發·不止於快”的境界。近期工作中,剛好碰見業務部門的一個需求,目的是節約上門跟客戶收集業務定製資料的時間,以往是每變更一次,就需要上門一次,碰見地域較遠的,費時費力,且往往要求幾天內完成上線,時間非常緊迫。因此,結合一直以來對雲開發的各種優勢的了解,我說服公司領導通過小程序·雲開發來實現。

下面是其中一項業務定製界面的展示:
(1)業務對業務流程有簡單說明;
(2)相關業務介紹;
(3)不同客戶輸入個性化需求;
(4)雲存儲後台實現需求表單的收集。

​ 得力於雲開發提供的API和WeUI庫的便利,本項目在我在極短的時間內就實現了比較理想的效果 。接下來,我就從本項目入手,講講我是如何依靠小程序·雲開發將想法快速實現的,其實我也是剛入門沒多久,只是想分享一下自身在學習小程序開發項目中的一些知識點和體會,代碼可能略為粗糙,邏輯也有待優化,歡迎大家在評論區多多交流。

三、開發過程

1、組件

主要使用了官方WeUI擴展能力的一些組件庫來實現主要功能。

核心的WeUI庫主要有 Msg、Picker、圖片的Upload等(以快為目的,節省自己寫CSS樣式的時間,也方便0基礎的同學上手,這裏又體會到了小程序開發的便捷)。

2、實現代碼

本次雲開發包括雲數據庫雲存儲兩大功能:

(1)雲數據庫

雲數據庫的主要就是搜集客戶提交上來的表單信息,包括客戶的聯繫方式和選擇的業務類型等,並存儲在雲數據庫中,方便業務經理搜集需求。

我們來看簡單的實現過程:

首先是表單,用到了 form 表單組件以及它的 bindsubmit 方法,在 wxml 中放置 form 表單:

<form bindsubmit="formSubmit">
    <view class="form">
      <view class="section">
        <picker bindchange="bindPickerGsd" mode="selector" value="{{indexGsd}}" range="{{arrayGsd}}">
          <view class="picker">歸屬縣市</view>
          <view class="picker-content" >{{arrayGsd[indexGsd]?arrayGsd[indexGsd]:"(必填項) 請下拉選擇歸屬地"}}</view> 
        </picker>
      </view>    
      <!---中間部分詳見代碼--->
    </view>

    <view class="footer">
      <button class="dz-btn" formType="submit" loading="{{formStatus.submitting}}" disabled="{{formStatus.submitting}}" bindtap="openSuccess">提交</button>
    </view>
  </form>

表單中除了普通的文本輸入,增加有下拉列表的實現(畢竟客戶有時候是比較懶的)。

來看一下具體代碼:

bindPickerGsd: function (e) {    
  console.log('歸屬地已選擇,攜帶值為', e.detail.value)
  console.log('歸屬地選擇:', this.data.arrayGsd[e.detail.value])    
  this.setData({
     indexGsd: e.detail.value     
   })   
   this.data.formData.home_county = this.data.arrayGsd[e.detail.value]
},

最後表單上傳到雲數據庫:

  // 表單提交
  formSubmit: function (e) {
    var minlength = '';
    var maxlength = '';
    console.log("表單內容",e)
    var that = this;
    var formData = e.detail.value;
    var result = this.wxValidate.formCheckAll(formData);
    
    console.log("表單提交formData", formData);
    console.log("表單提交result", result)
    wx.showLoading({
      title: '發布中...',
    })
    const db = wx.cloud.database()
    db.collection('groupdata').add({
      data: {
        time: getApp().getNowFormatDate(),
        home_county: this.data.formData.home_county,
        group_name: formData.group_name,
        contact_name: formData.contact_name,
        msisdn: formData.msisdn,
        product_name: this.data.formData.product_name,
        word: formData.word,
      },
      success: res => {
        wx.hideLoading()
        console.log('發布成功', res)

      },
      fail: err => {
        wx.hideLoading()
        wx.showToast({
          icon: 'none',
          title: '網絡不給力....'
        })
        console.error('發布失敗', err)
      }
    })
  },
(2)雲存儲

​ 因為業務的定製需要填單客戶所在單位的授權證明材料,因此需要提單人(使用人)上傳證明文件,因此增加了使用雲存儲的功能。

核心代碼:

    promiseArr.push(new Promise((reslove,reject)=>{
        wx.cloud.uploadFile({
            cloudPath: "groupdata/" + group_name + "/" + app.getNowFormatDate() +suffix,
            filePath:filePath
        }).then(res=>{
            console.log("授權文件上傳成功")          
            })
            reslove()
            }).catch(err=>{
            console.log("授權文件上傳失敗",err)
    })

    因為涉及到不同頁面的數據傳遞,即將表單頁面的group_name作為雲存儲的文件夾用於存儲該客戶在表單中上傳的圖片,因此還需要用到getCurrentPages()來進行頁面間的數據傳遞 

    var pages = getCurrentPages();
    var prePage = pages[pages.length - 2];//pages.length就是該集合長度 -2就是上一個活動的頁面,也即是跳過來的頁面
    var group_name = prePage.data.formData.group_name.value//取上頁data里的group_name數據用於標識授權文件所存儲文件夾的名稱

3、待進一步優化

​ 基於時間關係,本次版本僅對需求進行了簡單實現,作為公司一個可靠的項目,還需要關注”客戶隱私”、“數據安全”,以及更人性化的服務。比如:

(1)提單人確認和認證過程

可靠性:增加驗證碼驗證(防止他人冒名登記),以及公司受理業務有個客戶本人提交憑證。

(2)訂閱消息

受理成功后,可以給客戶進行處理結果的反饋,增強感知。

(3)人工客服

進行在線諮詢等。

四、總結

​ 在本次項目開發中,我深刻體會到了雲開發的“快”,特別是雲數據庫的增刪查改功能非常方便。雲開發提供的種種便利,讓我在有新創意的時候,可以迅速採用小程序雲開發快速實現,省時省力,還能免費使用騰訊雲服務器,推薦大家嘗試!

源碼地址

如果你想要了解更多關於雲開發CloudBase相關的技術故事/技術實戰經驗,請掃碼關注【騰訊云云開發】公眾號~

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

【其他文章推薦】

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

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

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

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

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

※試算大陸海運運費!

Cesium坐標系及坐標轉換詳解

前言

Cesium項目中經常涉及到模型加載、瀏覽以及不同數據之間的坐標轉換,弄明白Cesium中採用的坐標系以及各個坐標系之間的轉換,是我們邁向三維GIS大門的前提,本文詳細的介紹了Cesium中採用的兩大坐標系以及之間轉換的各種方法。

Cesium中的坐標系

Cesium中常用的坐標有兩種WGS84地理坐標系和笛卡爾空間坐標系,我們平時常用的以經緯度來指明一個地點就是用的WGS84坐標,笛卡爾空間坐標系常用來做一些空間位置變換如平移旋轉縮放等等。二者的聯繫如下圖。

其中,WGS84地理坐標系包括 WGS84經緯度坐標系(沒有實際的對象)和 WGS84弧度坐標系(Cartographic);

         笛卡爾空間坐標系包括 笛卡爾空間直角坐標系(Cartesian3)、平面坐標系(Cartesian2),4D笛卡爾坐標系(Cartesian4)。

WGS84坐標系

World Geodetic System 1984,是為GPS全球定位系統使用而建立的坐標系統,坐標原點為地球質心,其地心空間直角坐標系的Z軸指向BIH (國際時間服務機構)1984.O定義的協議地球極(CTP)方向,X軸指向BIH 1984.0的零子午面和CTP赤道的交點,Y軸與Z軸、X軸垂直構成右手坐標系。我們平常手機上的指南針显示的經緯度就是這個坐標系下當前的坐標,進度範圍[-180,180],緯度範圍[-90,90]。

WGS84坐標系

Cesium目前支持兩種坐標系WGS84和WebMercator,但是在Cesium中沒有實際的對象來描述WGS84坐標,都是以弧度的方式來進行運用的也就是Cartographic類:

new Cesium.Cartographic(longitude, latitude, height),這裏的參數也叫longitude、latitude,就是經度和緯度,計算方法:弧度= π/180×經緯度角度。

 笛卡爾空間直角坐標系(Cartesian3)

笛卡爾空間坐標的原點就是橢球的中心,我們在計算機上進行繪圖時,不方便使用經緯度直接進行繪圖,一般會將坐標系轉換為笛卡爾坐標系,使用計算機圖形學中的知識進行繪圖。這裏的Cartesian3,有點類似於三維繫統中的Point3D對象,new Cesium.Cartesian3(x, y, z),裏面三個分量x、y、z。

笛卡爾空間直角坐標系

平面坐標系(Cartesian2)

平面坐標系也就是平面直角坐標系,是一個二維笛卡爾坐標系,與Cartesian3相比少了一個z的分量,new Cesium.Cartesian2(x, y)。Cartesian2經常用來描述屏幕坐標系,比如鼠標在電腦屏幕上的點擊位置,返回的就是Cartesian2,返回了鼠標點擊位置的xy像素點分量。

平面坐標系

坐標轉換

經緯度和弧度的轉換

var radians=Cesium.Math.toRadians(degrees);//經緯度轉弧度
var degrees=Cesium.Math.toDegrees(radians);//弧度轉經緯度

WGS84經緯度坐標和WGS84弧度坐標系(Cartographic)的轉換

//方法一:
var longitude = Cesium.Math.toRadians(longitude1); //其中 longitude1為角度

var latitude= Cesium.Math.toRadians(latitude1); //其中 latitude1為角度

var cartographic = new Cesium.Cartographic(longitude, latitude, height);

//方法二:
var cartographic= Cesium.Cartographic.fromDegrees(longitude, latitude, height);//其中,longitude和latitude為角度

//方法三:
var cartographic= Cesium.Cartographic.fromRadians(longitude, latitude, height);//其中,longitude和latitude為弧度

WGS84坐標系和笛卡爾空間直角坐標系(Cartesian3)的轉換

通過經緯度或弧度進行轉換
var position = Cesium.Cartesian3.fromDegrees(longitude, latitude, height);//其中,高度默認值為0,可以不用填寫;longitude和latitude為角度

var positions = Cesium.Cartesian3.fromDegreesArray(coordinates);//其中,coordinates格式為不帶高度的數組。例如:[-115.0, 37.0, -107.0, 33.0]

var positions = Cesium.Cartesian3.fromDegreesArrayHeights(coordinates);//coordinates格式為帶有高度的數組。例如:[-115.0, 37.0, 100000.0, -107.0, 33.0, 150000.0]

//同理,通過弧度轉換,用法相同,具體有Cesium.Cartesian3.fromRadians,Cesium.Cartesian3.fromRadiansArray,Cesium.Cartesian3.fromRadiansArrayHeights等方法

注意:上述轉換函數中最後均有一個默認參數ellipsoid(默認值為Ellipsoid.WGS84)。

通過過度進行轉換

具體過度原理可以參考上邊的注意事項。

var position = Cesium.Cartographic.fromDegrees(longitude, latitude, height);
var positions = Cesium.Ellipsoid.WGS84.cartographicToCartesian(position);
var positions = Cesium.Ellipsoid.WGS84.cartographicArrayToCartesianArray([position1,position2,position3]);

笛卡爾空間直角坐標系轉換為WGS84

直接轉換
var cartographic= Cesium.Cartographic.fromCartesian(cartesian3);

轉換得到WGS84弧度坐標系后再使用經緯度和弧度的轉換,進行轉換到目標值

間接轉換
var cartographic = Cesium.Ellipsoid.WGS84.cartesianToCartographic(cartesian3);
var cartographics = Cesium.Ellipsoid.WGS84.cartesianArrayToCartographicArray([cartesian1,cartesian2,cartesian3]);

平面坐標系(Cartesian2)和笛卡爾空間直角坐標系(Cartesian3)的轉換

平面坐標系轉笛卡爾空間直角坐標系

這裏注意的是當前的點(Cartesian2)必須在三維球上,否則返回的是undefined;通過ScreenSpaceEventHandler回調會取到的坐標都是Cartesian2。

屏幕坐標轉場景坐標-獲取傾斜攝影或模型點擊處的坐標

這裏的場景坐標是包含了地形、傾斜攝影表面、模型的坐標。

通過viewer.scene.pickPosition(movement.position)獲取,根據窗口坐標,從場景的深度緩衝區中拾取相應的位置,返回笛卡爾坐標。

var handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
handler.setInputAction(function (movement) {
     var position = viewer.scene.pickPosition(movement.position);
     console.log(position);
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);

注:若屏幕坐標處沒有傾斜攝影表面、模型時,獲取的笛卡爾坐標不準,此時要開啟地形深度檢測(viewer.scene.globe.depthTestAgainstTerrain = true; //默認為false)。

屏幕坐標轉地表坐標-獲取加載地形后對應的經緯度和高程

這裡是地球表面的世界坐標,包含地形,不包括模型、傾斜攝影表面。

通過viewer.scene.globe.pick(ray, scene)獲取,其中ray=viewer.camera.getPickRay(movement.position)。

var handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
handler.setInputAction(function (movement) {
     var ray = viewer.camera.getPickRay(movement.position);
     var position = viewer.scene.globe.pick(ray, viewer.scene);
     console.log(position);
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);

注:通過測試,此處得到的坐標通過轉換成wgs84后,height的為該點的地形高程值。

屏幕坐標轉橢球面坐標-獲取鼠標點的對應橢球面位置

這裏的橢球面坐標是參考橢球的WGS84坐標(Ellipsoid.WGS84),不包含地形、模型、傾斜攝影表面。

通過 viewer.scene.camera.pickEllipsoid(movement.position, ellipsoid)獲取,可以獲取當前點擊視線與橢球面相交處的坐標,其中ellipsoid是當前地球使用的橢球對象:viewer.scene.globe.ellipsoid,默認為Ellipsoid.WGS84

var handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
handler.setInputAction(function (movement) {
     var position = viewer.scene.camera.pickEllipsoid(movement.position, viewer.scene.globe.ellipsoid);
     console.log(position);
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);

注:通過測試,此處得到的坐標通過轉換成wgs84后,height的為0(此值應該為地表坐標減去地形的高程)。

笛卡爾空間直角坐標系轉平面坐標系

var cartesian2= Cesium.SceneTransforms.wgs84ToWindowCoordinates(viewer.scene,cartesian3)

空間位置變換

經緯度轉換到笛卡爾坐標系后就能運用計算機圖形學中的仿射變換知識進行空間位置變換如平移旋轉縮放。

Cesium為我們提供了很有用的變換工具類:Cesium.Cartesian3(相當於Point3D)Cesium.Matrix3(3×3矩陣,用於描述旋轉變換)Cesium.Matrix4(4×4矩陣,用於描述旋轉加平移變換),Cesium.Quaternion(四元數,用於描述圍繞某個向量旋轉一定角度的變換)。

下面舉個例子:

      一個局部坐標為p1(x,y,z)的點,將它的局部坐標原點放置到loc(lng,lat,alt)上,局部坐標的z軸垂直於地表,局部坐標的y軸指向正北,並圍繞這個z軸旋轉d度,求此時p1(x,y,z)變換成全局坐標笛卡爾坐p2(x1,y1,z1)是多少?

var rotate = Cesium.Math.toRadians(d);//轉成弧度
var quat = Cesium.Quaternion.fromAxisAngle(Cesium.Cartesian3.UNIT_Z, rotate); //quat為圍繞這個z軸旋轉d度的四元數
var rot_mat3 = Cesium.Matrix3.fromQuaternion(quat);//rot_mat3為根據四元數求得的旋轉矩陣
var v = new Cesium.Cartesian3(x, y, z);//p1的局部坐標
var m = Cesium.Matrix4.fromRotationTranslation(rot_mat3, Cesium.Cartesian3.ZERO);//m為旋轉加平移的4x4變換矩陣,這裏平移為(0,0,0),故填個Cesium.Cartesian3.ZERO
m = Cesium.Matrix4.multiplyByTranslation(m, v);//m = m X v
var cart3 = ellipsoid.cartographicToCartesian(Cesium.Cartographic.fromDegrees(lng, lat, alt)); //得到局部坐標原點的全局坐標
var m1 = Cesium.Transforms.eastNorthUpToFixedFrame(cart3);//m1為局部坐標的z軸垂直於地表,局部坐標的y軸指向正北的4x4變換矩陣
m = Cesium.Matrix4.multiplyTransformation(m, m1);//m = m X m1
var p2 = Cesium.Matrix4.getTranslation(m);//根據最終變換矩陣m得到p2
console.log('x=' + p2.x + ',y=' + p2.y + ',z=' + p2.z );

總結

通過本文,介紹了各個坐標系間的轉換問題,在具體項目中,可結合實際需求,靈活組合解決具體的實際問題。注意,博文是參照網上相關博客及結合自己的實踐總結得來,希望本文對你有所幫助,後續會更新更多內容,感興趣的朋友可以加關注,歡迎留言交流!

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

【其他文章推薦】

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

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

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

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

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

※試算大陸海運運費!

Tsx寫一個通用的button組件

一年又要到年底了,vue3.0都已經出來了,我們也不能一直還停留在過去的js中,是時候學習並且在項目中使用一下Ts了。

  如果說jsx是基於js的話,那麼tsx就是基於typescript的

  廢話也不多說,讓我們開始寫一個Tsx形式的button組件,

  ts真的不僅僅只有我們常常熟知的數據類型,還包括接口,類,枚舉,泛型,等等等,這些都是特別重要的

  項目是基於vue-cli 3.0 下開發的,可以自己配置Ts,不會的話那你真的太難了

  

 

     我們再compenonts中新建一個button文件夾,再建一個unit文件夾,button放button組件的代碼,unit,放一些公共使用模塊

  我們再button文件夾下創建 ,index .tsx放的button源碼,index.less放的是樣式,css也是不可缺少的

       

 

   分析一下button需要的一些東西

  第一個當然是props,還有一個是點擊事件,所以我們第一步就定義一下這兩個類型

type ButtonProps = {
  tag: string,
  size: ButtonSize,
  type: ButtonType,
  text: String
}

type ButtonEvents = {
  onClick?(event: Event) :void
}
type ButtonSize = 'large' | 'normal' | 'small' | 'mini'
type ButtonType = 'default' | 'primary' | 'info' | 'warning' | 'danger'

  因為button是很簡單的組件,內部也沒有一些特別的狀態需要改變,所以我們用函數式組件的方式去寫(之後的render會用到這個方法)

function Button (h: CreateElement, props: ButtonProps, slots: DefaultSlots, ctx: RenderContext<ButtonProps>) {
  const { tag, size, type } = props
  let text
  console.log(slots)
  text = slots.default ? slots.default() : props.text
  function onClick (event: Event) {
    emit(ctx, 'click', event)
  }
  let classes = [size,type]
  return (
    <tag
      onClick = {onClick}
      class = {classes}
    >
      {text}
    </tag>
  )
}

  h 是一個預留參數,這裏並沒有用到 ,CreateElement  這個是vue從2.5之後提供的一個類型,也是為了方便在vue項目上使用ts

  props 就是button組件的傳入的屬性,slots插槽,ctx,代表的是當前的組件,可以理解為當前rendercontext執行環境this

  DefaultSlots是我們自定義的一個插槽類型

export type ScopedSlot<Props = any> = (props?: Props) => VNode[] | VNode | undefined;

export type ScopedSlots = {
  [key: string]: ScopedSlot | undefined;
}

  插槽的內容我們都是需要從ctx中讀取的,默認插槽的key就是defalut,具名插槽就是具體的name

  button放發內部還有一個具體的點擊事件,還有一個emit方法,從名字我們也可以看的出,他是粗發自定義事件的,我們這裏當然不能使用this.emit去促發,

  所以我們需要單獨這個emit方法,我們知道組件內所以的自定義事件都是保存在listeners里的,我們從ctx中拿取到所以的listeners



  import { RenderContext, VNodeData } from ‘vue’ // 從vue中引入一些類型


function
emit (context: RenderContext, eventName: string, ...args: any[]) { const listeners = context.listeners[eventName] if (listeners) { if (Array.isArray(listeners)) { listeners.forEach(listener => { listener(...args) }) } else { listeners(...args) } }

  這樣我們組件內部的事件觸發就完成了

  我們的button肯定是有一些默認的屬性,所以,我們給button加上默認的屬性

Button.props = {
  text: String,
  tag: {
    type: String,
    default: 'button'
  },
  type: {
    type: String,
    default: 'default'
  },
  size: {
    type: String,
    default: 'normal'
  }
}

  我們定義一個通用的functioncomponent 類型

type FunctionComponent<Props=DefaultProps, PropsDefs = PropsDefinition<Props>> = {
  (h: CreateElement, Props:Props, slots: ScopedSlots, context: RenderContext<Props>): VNode |undefined,
  props?: PropsDefs
}

  PropsDefinition<T>  這個是vue內部提供的,對 props的約束定義

  不管怎麼樣我們最終返回的肯定是一個對象,我們把這個類型也定義一下

  ComponentOptions<Vue> 這個也是vue內部提供的

 interface DrmsComponentOptions extends ComponentOptions<Vue> {
  functional?: boolean;
  install?: (Vue: VueConstructor) => void;
}

  最終生成一個組件對象

function transformFunctionComponent (fn:FunctionComponent): DrmsComponentOptions {
  return {
    functional: true, // 函數時組件,這個屬性一定要是ture,要不render方法,第二個context永遠為underfine
    props: fn.props,
    model: fn.model,
    render: (h, context): any => fn(h, context.props, unifySlots(context), context)
  }
}

  unifySlots 是讀取插槽的內容

// 處理插槽的內容
export function unifySlots (context: RenderContext) {
  // use data.scopedSlots in lower Vue version
  const scopedSlots = context.scopedSlots || context.data.scopedSlots || {}
  const slots = context.slots()

  Object.keys(slots).forEach(key => {
    if (!scopedSlots[key]) {
      scopedSlots[key] = () => slots[key]
    }
  })

  return scopedSlots
}

  當然身為一個組件,我們肯定是要提供全局注入接口,並且能夠按需導入

  所以我們給組件加上名稱和install方法,install 是 vue.use() 方法使用的,這樣我們能全部註冊組件

export function CreateComponent (name:string) {
  return function <Props = DefaultProps, Events = {}, Slots = {}> (
    sfc:DrmsComponentOptions | FunctionComponent) {
    if (typeof sfc === 'function') {
      sfc = transformFunctionComponent(sfc)
    }
    sfc.functional = true
    sfc.name = 'drms-' + name
    sfc.install = install
    return sfc 
  }
}

  index.tsx 中的最後一步,導出這個組件

export default CreateComponent('button')<ButtonProps, ButtonEvents>(Button)

  還少一個install的具體實現方法,加上install方法,就能全局的按需導入了

function install (this:ComponentOptions<Vue>, Vue:VueConstructor) {
  const { name } = this
  Vue.component(name as string, this)
}

 

   最終實現的效果圖,事件的話也是完全ok的,這個我也是測過的

 

   代碼參考的是vant的源碼:

  該代碼已經傳到git:     dev分支應該是代碼全的,master可能有些並沒有合併

 

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

【其他文章推薦】

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

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

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

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

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

GM 首款自動駕駛電動車將透過租車平台展開服務

2016 年年初GM 宣佈向線上租車服務平臺Lyft 投資5 億美元,雙方將在自動駕駛汽車的應用上展開合作,近日GM 高管Pam Fletcher 透露,GM 的首款自動駕駛電動車將透過Lyft 的租車服務平臺推出,能夠為用戶提供更好的乘坐體驗。

目前各大傳統汽車廠商紛紛進入無人駕駛領域,GM 也不例外,據GM 自動駕駛技術部門首席工程師Pam Fletcher 透露,雖然GM 還沒有正式宣佈自動駕駛車的發表日期,但這一切會比外界預期的更早到來,GM 正在和線上租車服務Lyft 展開合作,開發一個租車分享平臺,這不是一個停留在概念階段的專案,GM 的團隊已經準備好把這一服務推廣到市場。

GM 的首款自動駕駛汽車將是電動車,目前電動車是GM 重點推進的產品,2016 年年底該公司將推出可遠程行駛的電動車BoltEV,這是一款為城市通勤設計的電動車,同時也考慮了租車服務的需求,Pam Fletcher 認為將自動駕駛技術應用在電動車上非常有意義,能夠給用戶帶來更好的乘坐體驗,電動車行駛時平穩、安靜,乘客可以在車中休息或者處理工作事務。

2016 年3 月GM 收購自動駕駛技術研發公司Cruise Automation,以提升該公司在這一領域的技術實力,2016 年5 月Cruise 帶來的自動駕駛技術已經在Bolt 電動車上進行測試。目前GM、Lyft 共同研發的租車分享系統與Bolt 電動車的自動駕駛技術測試分屬不同的專案,但未來有可能會結合。

據悉GM 和Lyft 有可能在2016 年年底將自動駕駛電動車帶來公路上測試,Bolt 電動車有可能成為主要的測試車款。Bolt 電動車的許多設計看起來都像是為自動駕駛而設計的,未來自動駕駛與線上租車服務結合也並不讓人感到意外。

(本文授權自《》──〈〉)

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

【其他文章推薦】

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

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

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

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

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

mysql 索引筆記

MyISAM引擎的B+Tree的索引

通過上圖可以直接的看出, 在MyISAM對B+樹的運用中明顯的特點如下:

  • 所有的非恭弘=叶 恭弘子節點中存儲的全部是索引信息
  • 在恭弘=叶 恭弘子節點中存儲的 value值其實是 數據庫中某行數據的index

MyISAM引擎 索引文件的查看:

在 /var/lib/mysql目錄中

.myd 即 my data , 數據庫中表的數據文件

.myi 即 my index , 數據庫中 索引文件

.log 即 mysql的日誌文件

InnoDB引擎 索引文件的查看:

同樣在 /var/lib/mysql 目錄下面

InnoDB引擎的B+Tree的索引

InnoDB的實現方式業內也稱其為聚簇索引, 什麼是聚簇索引呢? 就是相鄰的行的簡直被存儲到一起, 對比上面的兩幅圖片就會發現, 在InnDB中, B+樹的恭弘=叶 恭弘子節點中存儲的是數據行中的一行行記錄, 缺點: 因為索引文件被存放在硬盤上, 所以很占硬盤的空間

一般我們會在每一個表中添加一列 取名 id, 設置它為primary key , 即將他設置成主鍵, 如果使用的存儲引擎也是InnoDB的話, 底層就會建立起主鍵索引, 也是聚簇索引, 並且會自動按照id的大小為我們排好序,(因為它的一個有序的樹)

局部性原理

局部性原理是指CPU訪問存儲器時,無論是存取指令還是存取數據,所訪問的存儲單元都趨於聚集在一個較小的連續區域中。 更進一步說, 當我們通過程序向操作系統發送指令讓它讀取我們指定的數據時, 操作系統會一次性讀取一頁(centos 每頁4kb大小,InnoDB存儲引擎中每一頁16kb)的數據, 它遵循局部性理論, 猜測當用戶需要使用某個數據時, 用戶很可能會使用這個數據周圍的數據,故而進行一次

InnoDB的頁格式

什麼是頁呢? 簡單說,就是一條條數據被的存儲在磁盤上, 使用數據時需要先將數據從磁盤上讀取到內存中, InnoDB每次讀出數據時同樣會遵循 局部性原理, 而不是一條條讀取, 於是InnoDB將數據劃分成一個一個的頁, 以頁作為和磁盤之間交互的基本單位

通過如下sql, 可以看到,InnoDB中每一頁的大小是16kb

show global status like 'Innodb_page_size';

名稱 簡述
File Header 文件頭部, 存儲頁的一些通用信息
Page Header 頁面頭部, 存儲數據頁專有的信息
Infinum + supremum 最大記錄和最小記錄, 這是兩個虛擬的行記錄
User Records 用戶記錄, 用來實際存儲行記錄中的內容
Free Space 空閑空間, 頁中尚位使用的空間
Page Directory 頁面目錄, 存儲頁中某些記錄的位置
File Tailer 文件尾部 , 用來校驗頁是否完整

InnoDB的行格式 compact

每一頁中存儲的行數據越多. 整體的性能就會越強

compact的行格式如下圖所示

可以看到在行格式中在存儲真正的數據的前面會存儲一些其他信息, 這些信息是為了描述這條記錄而不得不添加的一些信息, 這些額外的信息就是上圖中的前三行

  • 變長字段的長度列表

在mysql中char是固定長度的類型, 同時mysql還支持諸如像 varchar這樣可變長度的類型, 不止varchar , 想 varbinary text blob這樣的變長數據類型, 因為 變長的數據類型的列存儲的數據的長度是不固定的, 所以說我們在存儲真正的數據時, 也得將這些數據到底佔用了多大的長度也給保存起來

  • NULL標誌位

compact行格式會將值可以為NULL的列統一標記在 NULL標誌位中, 如果數據表中所有的字段都被標記上not null , 那麼就沒有NULL值列表

  • 記錄頭信息

記錄頭信息, 顧名思義就是用來描述記錄頭中的信息, 記錄頭信息由固定的5個字節組成, 一共40位, 不同位代表的意思也不同, 如下錶

名稱 單位 bit 簡介
預留位1 1 未使用
預留位2 1 未使用
delete_mark 1 標記改行記錄是否被刪除了
min_rec_mark 1 標記在 B+樹中每層的非恭弘=叶 恭弘子節點中最小的node
n_owned 4 表示當前記錄擁有的記錄數
heap_no 13 表示當前記錄在堆中的位置
record_type 3 表示當前記錄的類型 , 0表示普通記錄, 1表示B+樹中非恭弘=叶 恭弘子節點記錄, 2表示最小記錄 ,3表示最大記錄
next_record 16 表示下一條記錄的相對位置

行溢出

在mysql中每一行, 能存儲的最大的字節數是65535個字節數, 此時我們使用下面的sql執行時就會出現行溢出現象

CREATE TABLE test ( c VARCHAR(65535) ) CHARSET=ascii ROW_FORMAT=Compact;

給varchar申請最大65535 , 再加上compact行格式中還有前面三個非數據列佔用內存,所以一準溢出, 如果不想溢出, 可以適當的將 65535 – 3

頁溢出

前面說了, InnoDB中數據的讀取按照頁為單位, 每一頁的大小是 16kb, 換算成字節就是16384個字節, 但是每行最多存儲 65535個字節啊, 也就是說一行數據可能需要好幾個頁來存儲

怎麼辦呢?

  • compact行格式會在存儲真實數據的列中多存儲一部分數據, 這部分數據中存儲的就是下一頁的地址
  • dynamic行格式 中直接存儲數據所在的地址, 換句話說就是數據都被存儲在了其他頁上
  • compressed行格式會使用壓縮算法對行格式進行壓縮處理

一般我們都是將表中的id列設置為主鍵, 這就會形成主鍵索引, 於是我們需要注意了:

主鍵的佔用的空間越小,整體的檢索效率就會越高

為什麼這麼說呢? 這就可以結合頁的概念來解析, 在B+樹這種數據結果中, 恭弘=叶 恭弘子節點中用來存儲數據, 存儲數據的格式類似Key-value key就是索引值, value就是數據內容, 如果索引佔用的空間太大的話, 單頁16kb能存儲的索引就越小, 這就導致數據被分散在更多的頁上, 致使查詢的效率降低

建立索引的技巧

為某一列建立索引

給text表中的title列創建索引, 索引名字 my_index
alter table text add index my_index (title);

雖然建立索引能提升查詢的效率, 根據前人的經驗看, 這並不是一定的, 建立索引本身會直接消耗內存空間, 同時索, 插入,刪除, 這種寫操作就會打破B+樹的平衡面臨索引的重建, 一般出現如下兩種情況時,是不推薦建立索引的

  1. 表中的數據本身就很少
  2. 我們計算一下索引的選擇性很低

兼顧 – 索引的選擇性與前綴索引

所謂選擇性,其實就是說不重複出現的索引值(基數,Cardinality) 與 表中的記錄數的比值

即: 選擇性= 基數 / 記錄數

選擇性的取值範圍在(0,1]之間, 選擇性越接近1 , 說明建立索引的必要性就越強, 比如對sex列進行建立索引,這裏面非男即女, 如果對它建立索引的話, 其實是沒意義的, 還不如直接進行全表掃描來的快

如何使用sql計算選擇性呢? 嚴格遵循上面的公式

SELECT count(DISTINCT(title))/count(*) AS Selectivity FROM employees.titles;
count(基數/記錄數)
DISTINCT(title) / /count(*)

更詳細的例子看下面的連接

索引失效問題

注意事項

  • 索引無法存儲null值

  • 如果條件中有or, 即使條件中存在索引也不會使用索引,如果既想使用or,又想使用索引, 就給所有or條件控制的列加上索引

  • 使用like查詢時, 如果以%開頭,肯定是進行全表掃描

  • 使用like查詢時, 如果%在條件後面

    • 對於主鍵索引, 索引失效
    • 對於普通索引, 索引不失效
  • 如果列的類型是字符串類型, 那麼一定要在條件中將數據用引號引起來,不然也會是索引失效

  • 如果mysql認為全表掃描比用索引塊, 同樣不會使用索引

聯合索引

什麼是聯合索引

聯合索引, 也叫複合索引,說白了就是多個字段一起組合成一個索引

像下面這樣使用 id + title 組合在一起構成一個聯合索引

CREATE TABLE `text` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) NOT NULL,
  `content` text NOT NULL,
  PRIMARY KEY (`id`,`title`)
) ENGINE=InnoDB AUTO_INCREMENT=3691 DEFAULT CHARSET=utf8
  • 如果我們像上圖那樣創建了索引,我們只要保證我們的 id+title 兩者結合起來全局唯一就ok
  • 建立聯合索引同樣是需要進行排序的,排序的規則就是按照聯合索引所有列組成的字符串的之間的先後順序進行排序, 如a比b優先

左前原則

使用聯合索引進行查詢時一定要遵循左前綴原則, 什麼是左前綴原則呢? 就是說想讓索引生效的話,一定要添加上第一個索引, 只使用第二個索引進行查詢的話會導致索引失效

比如上面創建的聯合索引, 假如我們的查詢條件是 where id = ‘1’ 或者 where id = ‘1’ and title = ‘唐詩宋詞’ 索引都會不失效

但是如果我們不使用第一個索引id, 像這樣 where title = ‘唐詩’ , 結果就是導致索引失效

聯合索引的分組&排序

還是使用這個例子:

CREATE TABLE `text` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) NOT NULL,
  `content` text NOT NULL,
  PRIMARY KEY (`id`,`title`)
) ENGINE=InnoDB AUTO_INCREMENT=3691 DEFAULT CHARSET=utf8

demo1: 當我們像下面這樣寫sql時, 就會先按照id進行排序, 當id相同時,再按照title進行排序

select * form text order by id, title;

demo2: 當我們像下面這樣寫sql時, 就會先將id相同的劃分為一組, 再將title相同的劃分為一組

select id,title form text group by id, title;

demo3: ASC和DESC混用, 其實大家都知道底層使用B+樹, 本身就是有序的, 要是不加限制的話,默認就是ASC, 反而是混着使用就使得索引失效

select * form text order by id ASC, title DESC;

如何定位慢查詢

相關參數

名稱 簡介
slow_query_log 慢查詢的開啟狀態
slow_query_log_file 慢查詢日誌存儲的位置
long_query_time 查詢超過多少秒才記錄下來

常用sql

# 查看mysql是否開啟了慢查詢
show variables like 'slow_query_log';   
# 將全局變量設置為ON
set global slow_query_log ='on';
# 查看慢查詢日誌存儲的位置
show variables like 'slow_query_log_file';
# 查看規定的超過多少秒才被算作慢查詢記錄下來
show variables like 'long_query_time';
show variables like 'long_query%';
# 超過一秒就記錄 , 每次修改這個配置都重新建立一次鏈接
set global long_query_time=1; 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

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

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

如何教會女友遞歸算法?

一到周末就開始放蕩自我,這不帶着女朋友去萬達電影院看電影(其實是由於整天呆在家敲代碼硬是

被女朋友強行拖拽去看電影,作為一個有理想的程序員,我想各位應該都能體諒我),一到電影院,

女朋友說要買爆米花和可樂,我當時二話沒說,臣本布衣躬耕於南陽,壤中羞澀,所以單買了爆米

花,買完都不帶回頭看老闆的那種,飲料喝多了不好,出門的時候我帶了白開水,還得虧我長得銷

魂,乍一看就能看出是個社會精神小伙,女朋友也沒多說什麼,只是對我微了微笑(我估計是被我的

顏值以及獨到的見解所折服),剛坐下沒多久,女朋友突然問我,咱們現在坐在第幾排啊?電影院里

面太黑了,看不清,沒法數,這個時候,如果是你現在你怎麼辦?別忘了你我是程序員,這個可難不

倒我,遞歸就開始排上用場了。於是我就問前面一排的人他是第幾排,你想只要在他的数字上加一,

就知道自己在哪一排了。但是,前面的人也看不清啊,所以他也問他前面的人。就這樣一排一排往前

問,直到問到第一排的人,說我在第一排,然後再這樣一排一排再把数字傳回來。直到你前面的人告

訴你他在哪一排,於是你就知道答案了。這就是一個非常標準的遞歸求解問題的分解過程,去的過程

叫“遞”,回來的過程叫“歸”。基本上,所有的遞歸問題都可以用遞推公式來表示。我們用遞推公式將

它表示出來就是這樣的

f ( n ) = f (n – 1) + 1 其中,f ( 1 ) = 1

f(n)表示你想知道自己在哪一排,f(n-1)表示前面一排所在的排數,f(1)=1表示第一排的人知道自己在

第一排。有了這個遞推公式,我們就可以很輕鬆地將它改為遞歸代碼,如下:

int f(int n) {
  if (n == 1) return 1;
  return f(n-1) + 1;
}

女朋友不懂遞歸,於是我給她講遞歸需要滿足的三個條件:

1.一個問題的解可以分解為幾個子問題的解

何為子問題?子問題就是數據規模更小的問題。就好比,在電影院,你要知道,“自己在哪一排”的問

題,可以分解為“前一排的人在哪一排”這樣一個子問題。

2.這個問題與分解之後的子問題,除了數據規模不同,求解思路完全一樣

你求解“自己在哪一排”的思路,和前面一排人求解“自己在哪一排”的思路,是一模一樣的。

3.存在遞歸終止條件

把問題分解為子問題,把子問題再分解為子子問題,一層一層分解下去,不能存在無限循環,這就需

要有終止條件。就好比,第一排的人不需要再繼續詢問任何人,就知道自己在哪一排,也就是

f(1)=1,這就是遞歸的終止條件。

如何教女友敲遞歸代碼?

剛剛鋪墊了這麼多,現在我們來看,如何來教女友敲遞歸代碼?個人覺得,寫遞歸代碼最關鍵的是寫

出遞推公式,找到終止條件,剩下將遞推公式轉化為代碼就很簡單了。

你先記住這個理論。我舉一個例子,帶你一步一步實現一個遞歸代碼,幫你理解。

假如這裡有n個台階,每次你可以跨1個台階或者2個台階,請問走這n個台階有多少種走法?如果有7個台階,你可以2,2,2,1這樣子上去,也可以1,2,1,1,2這樣子上去,總之走法有很多,那如何用編程求得總共有多少種走法呢?

我們仔細想下,實際上,可以根據第一步的走法把所有走法分為兩類,第一類是第一步走了1個台

階,另一類是第一步走了2個台階。所以n個台階的走法就等於先走1階后,n-1個台階的走法 加上先

走2階后,n-2個台階的走法。用公式表示就是:

f ( n ) = f (n – 1) + f ( n – 2 )

有了遞推公式,遞歸代碼基本上就完成了一半。我們再來看下終止條件。當有一個台階時,我們不需

要再繼續遞歸,就只有一種走法。所以f(1)=1。這個遞歸終止條件足夠嗎?我們可以用n=2,n=3這樣

比較小的數試驗一下。

n=2時,f(2)=f(1)+f(0)。如果遞歸終止條件只有一個f(1)=1,那f(2)就無法求解了。所以除了f(1)=1這一

個遞歸終止條件外,還要有f(0)=1,表示走0個台階有一種走法,不過這樣子看起來就不符合正常的

邏輯思維了。所以,我們可以把f(2)=2作為一種終止條件,表示走2個台階,有兩種走法,一步走完

或者分兩步來走。

所以,遞歸終止條件就是f(1)=1,f(2)=2。這個時候,你可以再拿n=3,n=4來驗證一下,這個終止條

件是否足夠並且正確。

我們把遞歸終止條件和剛剛得到的遞推公式放到一起就是這樣的:

f(1) = 1;
f(2) = 2;
f(n) = f(n-1)+f(n-2)

有了這個公式,我們轉化成遞歸代碼就簡單多了。最終的遞歸代碼是這樣的:

int f(int n) {
  if (n == 1) return 1;
  if (n == 2) return 2;
  return f(n-1) + f(n-2);
}

我總結一下,寫遞歸代碼的關鍵就是找到如何將大問題分解為小問題的規律,並且基於此寫出遞推公式,然後再推敲終止條件,最後將遞推公式和終止條件翻譯成代碼。

如果以後再遇到類似問題,A可以分解為若干子問題B、C、D情況,你可以假設子問題B、C、D已經

解決,在此基礎上思考如何解決問題A。而且,你只需要思考問題A與子問題B、C、D兩層之間的關

系即可,不需要一層一層往下思考子問題與子子問題,子子問題與子子子問題之間的關係。屏蔽掉遞

歸細節,這樣子理解起來就簡單多了。

因此,編寫遞歸代碼的關鍵是,只要遇到遞歸,我們就把它抽象成一個遞推公式,不用想一層層的調

用關係,不要試圖用人腦去分解遞歸的每個步驟

如何教女友玩轉漢羅塔

好了,講完了遞歸算法,再回到電影院,不說別的,我還真那麼做了,我真問了前面一排的人他是第

幾排如果不清楚並讓他跟我一樣問他的上一排,顯然,沒循環到第三人,我差點被認為是神經病,差

點沒被幾個社會精神小伙打si,座位事情暫時告一段落,話說這電影屬實夠無聊,於是我不知是趁熱

打鐵,還是心血來潮,非要給女朋友玩一個漢羅塔遊戲,我這暴脾氣,剛實踐遞歸算法被懟,是時候

挽回形象了,不秀一把遞歸算法我就不得勁。就是這個遊戲,至於遊戲規則,我覺得你體驗一兩把絕

對比我說的更加記憶深刻,,別看4399覺得有點弱zhi,再怎麼說也承

載着童年

果然,女朋友是個哈皮,剛過第三關就撲街了,這個時候,頭冒五丈光芒的我身披金甲挺身而出(貌

似有一點點小誇張,劇情需要嘛)一聲不吭地敲了幾行靚麗的代碼

public class TestHanoi {

    public static void main(String[] args) {
        hanoi(5,'A','B','C');  //可以理解為5個圈或者第5關
    }
    
    /**
     * @param n     共有N個圈
     * @param A    開始的柱子
     * @param B 中間的柱子
     * @param C 目標的柱子
     * 無論有多少個圈,都認為只有兩個。上面的所有圈和最下面一個圈。
     */
    public static void hanoi(int n,char A,char B,char C) {
        //只有一個圈。
        if(n==1) {
            System.out.println("第1個盤子從"+A+"移到"+C);
        //無論有多少個圈,都認為只有兩個。上面的所有圈和最下面一個圈。
        }else {
            //移動上面所有的圈到中間位置
            hanoi(n-1,A,C,B);
            //移動下面的圈
            System.out.println("第"+n+"個圈從"+A+"移到"+C);
            //把上面的所有圈從中間位置移到目標位置
            hanoi(n-1,B,A,C);
        }
    }

}

只要main方法一致行,對着結果移動即可,就跟開了掛一樣的,其實漢羅塔問題核心關鍵是無論有多少個圈,都認為只有兩個。上面的所有圈和最下面一個圈。

到這裏,教女友敲遞歸算法代碼,你學會了嗎?

哦豁,明天還是一個晴天~老天賜給宜春一個女朋友吧~畢竟我們程序員長得又帥敲代碼又好看,是吧哥幾個~~

如果本文對你有一點點幫助,那麼請點個讚唄,謝謝~

最後,若有不足或者不正之處,歡迎指正批評,感激不盡!如果有疑問歡迎留言,絕對第一時間回復!

歡迎各位關注我的公眾號,一起探討技術,嚮往技術,追求技術,說好了來了就是盆友喔…

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

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

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

堡壘機的核心武器:WebSSH錄像實現

WebSSH終端錄像的實現終於來了

前邊寫了兩篇文章和深入介紹了終端錄製工具Asciinema,我們已經可以實現在終端下對操作過程的錄製,那麼在WebSSH中的操作該如何記錄並提供後續的回放審計呢?

一種方式是文章最後介紹的自動錄製審計日誌的方法,在主機上添加個腳本,每次連接自動進行錄製,但這樣不僅要在每台遠程主機添加腳本,會很繁瑣,而且錄製的腳本文件都是放在遠程主機上的,後續播放也很麻煩

那該如何更好處理呢?下文介紹一種優雅的方式來實現,核心思想是不通過錄製命令進行錄製,而在Webssh交互執行的過程中直接生成可播放的錄像文件

設計思路

通過上邊兩篇文章的閱讀,我們已經知道了Asciinema錄像文件主要由兩部分組成:header頭和IO流數據

header頭位於文件的第一行,定義了這個錄像的版本、寬高、開始時間、環境變量等參數,我們可以在websocket連接創建時將這些參數按照需要的格式寫入到文件

header頭數據如下,只有開頭一行,是一個字典形式

{"version": 2, "width": 213, "height": 55, "timestamp": 1574155029.1815443, "env": {"SHELL": "/bin/bash", "TERM": "xterm-256color"}, "title": "ops-coffee"}

整個錄像文件除了第一行的header頭部分,剩下的就都是輸入輸出的IO流數據,從websocket連接建立開始,隨着操作的進行,IO流數據是不斷增加的,直到整個websocket長連接的結束,那就需要在整個WebSSH交互的過程中不斷的往錄像文件追加輸入輸出的內容

IO流數據如下,每一行一條,列表形式,分別表示操作時間,輸入或輸出(這裏我們為了方便就寫固定字符串輸出),IO數據

[0.2341010570526123, "o", "Last login: Tue Nov 19 17:11:30 2019 from 192.168.105.91\r\r\n"]

似乎很完美,按照上邊的思路錄像文件就應該沒有問題了,但還有一些細節需要處理

首先是需要歷史連接列表,在這個列表裡可以看到什麼時間,哪個用戶連接了哪台主機,當然也需要提供回放功能,新建一張表來記錄這些信息

class Record(models.Model):
    create_time = models.DateTimeField(auto_now_add=True, verbose_name='創建時間')

    host = models.ForeignKey(Host, on_delete=models.CASCADE, verbose_name='主機')
    user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='用戶')

    filename = models.CharField(max_length=128, verbose_name='錄像文件名稱')

    def __str__(self):
        return self.host

其次還需要考慮的一個問題是header和後續IO數據流要寫入同一個文件,這就需要在整個websocket的連接過程中有一個固定的文件名可被讀取,這裏我使用了主機+用戶+當前時間作為文件名,同一用戶在同一時間不能多次連接同一主機,這樣可保證文件名不重複,同時避免操作寫入錯誤的錄像文件,文件名在websocket建立時初始化

def __init__(self, host, user, websocket):
    self.host = host
    self.user = user

    self.time = time.time()
    self.filename = '%s.%s.%d.cast' % (host, user, self.time)

IO流數據會持續不斷的寫入文件,這裏以一個獨立的方法來處理寫入

def record(self, type, data):
    RECORD_DIR = settings.BASE_DIR + '/static/record/'
    if not os.path.isdir(RECORD_DIR):
        os.makedirs(RECORD_DIR)

    if type == 'header':
        Record.objects.create(
            host=Host.objects.get(id=self.host),
            user=self.user,
            filename=self.filename
        )

        with open(RECORD_DIR + self.filename, 'w') as f:
            f.write(json.dumps(data) + '\n')
    else:
        iodata = [time.time() - self.time, 'o', data]
        with open(RECORD_DIR + self.filename, 'a', buffering=1) as f:
            f.write((json.dumps(iodata) + '\n'))

record接收兩個參數type和data,type標識本次寫入的是header頭還是IO流,data則是具體的數據

header只需要執行一次寫入,所以將其放在ssh的connect方法中,只在ssh連接建立時執行一次,在執行header寫入時同時往數據庫插入新的歷史記錄數據

調用record方法寫入header

def connect(self, host, port, username, authtype, password=None, pkey=None,
            term='xterm-256color', cols=80, rows=24):
    ...

    # 構建錄像文件header
    self.record('header', {
        "version": 2,
        "width": cols,
        "height": rows,
        "timestamp": self.time,
        "env": {
            "SHELL": "/bin/bash",
            "TERM": term
        },
        "title": "ops-coffee"
    })

IO流數據則需要與返回給前端的數據保持一致,這樣就能保證前端显示什麼錄像就播放什麼了,所以所有需要返回前端數據的地方都同時寫入錄像文件即可

調用record方法寫入io流數據

def connect(self, host, port, username, authtype, password=None, pkey=None,
            term='xterm-256color', cols=80, rows=24):
    ...

    # 連接建立一次,之後交互數據不會再進入該方法
    for i in range(2):
        recv = self.ssh_channel.recv(65535).decode('utf-8', 'ignore')
        message = json.dumps({'flag': 'success', 'message': recv})
        self.websocket.send(message)

        self.record('iodata', recv)

...

def _ssh_to_ws(self):
    try:
        with self.lock:
            while not self.ssh_channel.exit_status_ready():
                data = self.ssh_channel.recv(1024).decode('utf-8', 'ignore')
                if len(data) != 0:
                    message = {'flag': 'success', 'message': data}
                    self.websocket.send(json.dumps(message))

                    self.record('iodata', data)
                else:
                    break
    except Exception as e:
        message = {'flag': 'error', 'message': str(e)}
        self.websocket.send(json.dumps(message))
        self.record('iodata', str(e))
        
        self.close()

由於命令執行與返回都是多線程的操作,這就會導致在寫入文件時出現文件亂序影響播放的問題,典型的操作有vim、top等,通過加鎖self.lock可以順利解決

最後歷史記錄頁面,當用戶點擊播放按鈕時,調用js彈出播放窗口

<div class="modal fade" id="modalForm">
  <div class="modal-dialog modal-lg">
    <div class="modal-content">
      <div class="modal-body" id="play">
      </div>
    </div>
  </div>
</div>

// 播放錄像
function play(host,user,time,file) {
  $('#play').html(
    '<asciinema-player id="play" title="WebSSH Record" author="ops-coffee.cn" author-url="https://ops-coffee.cn" author-img-url="/static/img/logo.png" src="/static/record/'+file+'" speed="3" '+
    'idle-time-limit="2" poster="data:text/plain,\x1b[1;32m'+time+
    '\x1b[1;0m用戶\x1b[1;32m'+user+
    '\x1b[1;0m連接主機\x1b[1;32m'+host+
    '\x1b[1;0m的錄像記錄"></asciinema-player>'
  )

  $('#modalForm').modal('show');
}

asciinema-player標籤的詳細參數介紹可以看這篇文章

演示與總結

在寫入文件的方案中,考慮了實時寫入和一次性寫入,實時寫入就像上邊這樣,所有的操作都會實時寫入錄像文件,好處是錄像不丟失,且能在操作的過程中進行實時的播放,缺點也很明顯,就是會頻繁的寫文件,造成IO開銷

一次性寫入可以在用戶操作的過程中將錄像數據寫入內存,在websocket關閉時一次性異步寫入到文件中,這種方案在最終寫入文件時可能因為種種原因而失敗,從而導致錄像丟失,還有個缺點是當你WebSSH操作時間過長時,會導致內存的持續增加

兩種方案一種是對磁盤的消耗另一種是對內存的消耗,各有利弊,當然你也可以考慮批量寫入,例如每分鐘寫一次文件,一分鐘之內的保存在內存中,平衡內存和磁盤的消耗,期待你的實現

相關文章推薦閱讀:

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

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

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

電動車電池成長,帶動致茂營收倍翻

受惠於電動車動力電池需求增加,致茂電子的七月合併營收創下歷史新高。今年前七個月的累計營收更已與去年全年相當。

致茂電子表示,因電動車動力電池製造的關鍵技術(turnkey solutions)銷售蓬勃,帶動七月營收大漲,合併營收達新台幣16.2億元,不僅較上月成長71%、更較去年七月成長101%,營收數字創下歷史新高。

此外,母公司的7月單月營收也有128%的月增與172%的年增,達新台幣12.6億元。強勁的需求使致茂今年前七個月的合併營收年增27%來到69.1億新台幣;母公司前七個月的累計營收新台幣45億元,也已相當於去年全年水準。

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

【其他文章推薦】

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

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

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

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

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

計算機圖形學—— 隱藏線和隱藏面的消除(消隱算法)

 

一、概述

由於投影變換失去了深度信息,往往導致圖形的二義性。要消除二義性,就必須在繪製時消除被遮擋的不可見的線或面,習慣上稱作消除隱藏線和隱藏面(或可見線判定、可見面判定),或簡稱為消隱。經過消隱得到的投影圖稱為物體的真實感圖形。

下面這個圖就很好體現了這種二義性。

消隱后的效果圖:

消隱算法的分類

所有隱藏面消隱算法必須確定:
在沿透視投影的投影中心或沿平行投影的投影方向看過去哪些邊或面是可見的

兩種基本算法

1、以構成圖像的每一個像素為處理單元,對場景中的所有表面,確定相對於觀察點是可見的表面,用該表面的顏色填充該像素.
適於面消隱。

算法步驟:

a.在和投影點到像素連線相交的表面中,找到離觀察點最近的表面;
b.用該表面上交點處的顏色填充該像素;

2、以三維場景中的物體對象為處理單元,在所有對象之間進行比較,除去完全不可見的物體和物體上不可見的部分.適於面消隱也適於線消隱。

算法步驟:
a.判定場景中的所有可見表面;
b.用可見表面的顏色填充相應的像素以構成圖形;

提醒注意

1.假定構成物體的面不能相互貫穿,也不能有循環遮擋的情況。
2.假定投影平面是oxy平面,投影方向為z軸的負方向。

如果構成物體的面不滿足該假定,可以把它們剖分成互不貫穿和不循環遮擋的情況。
例如,用圖b中的虛線便可把原來循環遮擋的三個平面,分割成不存在循環遮擋的四個面。  

二、可見面判斷的有效技術

1、邊界盒

指能夠包含該物體的一個幾何形狀(如矩形/圓/長方體等),該形狀有較簡單的邊界。

 

邊界盒技術用於判斷兩條直線是否相交。

進一步簡化判斷

2、後向面消除(Back-face Removal)

思路:把顯然不可見的面去掉,減少消隱過程中的直線求交數目

 

 

如何判斷:根據定義尋找外(或內)法向,若外法向背離觀察者,或內法向指向觀察者,則該面為後向面。

 

 

 

 

 

 

 

 

 

 

注意:如果多邊形是凸的,則可只取一個三角形計算有向面積sp。如果多邊形不是凸的,只取一個三角形計算有向面積sp可能會出現錯誤,即F所在的面為前向面也可能出現sp≥0的情況,因此,需按上式計算多邊形F的有向面積。如果sp ≥0,則F所在的面為後向面。

3、非垂直投影轉化成垂直投影

物體之間的遮擋關係與投影中心和投影方向有着密切的關係,因此,對物體的可見性判定也和投影方式有密切的關係。

垂直投影的優點:進行投影時可以忽略z值,即:實物的(x,y)可直接做為投影后的二維平面上的坐標(x,y)

上述討論說明,垂直投影比非垂直投影容易實現,並且計算量小。因此在進行消隱工作之前,首先應將非垂直投影轉換成垂直投影,從而降低算法的複雜性,提高運算速度。

如何把透視投影變為垂直投影,其本質是把稜台變成長方體。

三、基於窗口的子分算法(Warnack算法)

是一種分而治之(Divide-Conquer)的算法。

 

1、關係判斷

2、可見性判斷

3、分隔結束條件

4、提高效率的有效的處理技術

5、算法描述

用多邊形的邊界對區域作劃分,其目的是盡量減少對區域劃分的次數--利用裁剪算法

 

四、八叉樹算法

為了生成真實感圖形,關鍵問題之一就是對圖像空間的每一個像素進行處理。從場景中所有的在該像素上有投影的表面中確定相對於觀察點是可見表面。為了提高算法效率,自然是希望從可能在像素上有投影的面片中尋找可見表面。八叉樹算法是快速尋找可見面的一種有效方法,是一種搜索算法。

基本思想:將能夠包含整個場景的立方體,即八叉樹的根結點,按照x,y,z三個方向中的剖面分割成八個子立方體,稱為根結點的八個子結點。對每一個子立方體,如果它包含的表面片少於一個給定的值,則該子立方體為八叉樹的終端結點,否則為非終端結點並將其進一步分割成八個子立方體;重複上述過程,直到每個小立方體所包含的表面片少於一個給定的值,分割即告終止。

 

 

那麼對於上述圖所示,視圖平面法向量(1,1,1)那麼此時它的一個排列是0,1,2,4,3,5,6,7,即最遠的八分體是0,與八分體0共享一個面的三個相鄰八分體是1,2和4,與最近八分體7的3個相鄰八分體是3,5和6。

 

五、Z緩衝器算法

1、算法描述

z緩衝器算法是最簡單的隱藏面消除算法之一。
基本思想:對屏幕上每一個像素點,過像素中心做一條投影線,找到此投影線與所有多邊形交點中離觀察者最近的點,此點的屬性(顏色或灰度)值即為這一屏幕像素點的屬性值。

需要兩個緩衝器數組,即:z緩衝器數組和幀緩衝器數組,分別設為 Zdepth[ ][ ] 與  Frame[ ][ ]
z緩衝器是一組存貯單元,其單元個數和屏幕上像素的個數相同,也和幀緩衝器的單元個數相同,它們之間一一對應。
幀緩衝器每個單元存放對應像素的顏色值;z緩衝器每個單元存放對應像素的深度值;

2、算法實現

 

算法的複雜性正比於m*n*N,在屏幕大小即m*n一定的情況下,算法的計算量只和多邊形個數N成正比

3、優缺點

 z-Buffer算法沒有利用圖形的相關性和連續性,這是z-Buffer算法的嚴重缺陷,更為嚴重的是,該算法是像素級上的消隱算法。

 六、掃描線z緩衝器算法

1、算法描述

將z緩衝器的單元數置為和一條掃描線上的像素數目相同。
從最上面的一條掃描線開始工作,向下對每一條掃描線作如下處理:

 

掃描線算法也屬於圖像空間消隱算法。該算法可以看作是多邊形區域填充里介紹過的邊相關掃描線填充算法的延伸。不同的是在消隱算法中處理的是多個面片,而多邊形填充中是對單個多邊形面進行填充。

2、數據結構

對每個多邊形,檢查它在oxy平面上的投影和當前掃描線是否相交?
若不相交,則不考慮該多邊形。
如果相交,則掃描線和多邊形邊界的交點是成對地出現
每對交點中間的像素計算多邊形所在平面對應點的深度(即z值),並和z緩衝器中相應單元存放的深度值作比較
若前者大於後者,則z緩衝器的相應單元內容要被求得的平面深度代替,幀緩衝器相應單元的內容也要換成該平面的屬性。
對所有的多邊形都作上述處理后,幀緩衝器中這一行的值便反應了消隱后的圖形。
對幀緩衝器每一行的單元都填上相應內容后就得到了整個消隱后的圖。

每處理一條掃描線,都要檢查各多邊形是否和該線相交,還要計算多邊形所在平面上很多點的z值,需要花費很大的計算
為了提高算法效率,採用跟多邊形掃描轉換中的掃描線算法類似的數據結構和算法.

多邊形Y表

 

實際上是一個指針數組 ,每個表的深度和显示屏幕行數相同.將所有多邊形存在多邊形Y表中,根據多邊形頂點中Y坐標最大值,插入多邊形Y表中的相應位置,多邊形Y表中保存多邊形的序號和其頂點的最大y坐標.

邊Y表

 要注意:Δx是下一條掃描線與邊交點的x減去當前的掃描線與邊交點的x。

多邊形活化表

邊對活化表

其實這裏最難理解的就是Δyl和Δxr了,這裏的意思就是當前掃描線所處的y值和與該掃描線相交邊的最小y值的差值。

就比如說掃描線y=6,與第一個三角形有兩個交點,左交點(4,6),右交點(7,6)那麼Δyl=6-3  Δyr=6-3

3、重溫算法目標

 對每一條掃描線,檢查對每個多邊形的投影是否相交,如相交則交點成對出現,對每對交點中間的每個像素計算多邊形所在平面對應點的深度(即z值),並和z緩衝器中相應單元存放的深度值作比較,若前者大於後者,則z緩衝器的相應單元內容要被求得的平面深度代替,幀緩衝器相應單元的內容也要換成該平面的屬性。
對所有的多邊形都作上述處理后,幀緩衝器中這一行的值便反應了消隱后的圖形,對幀緩衝器每一行的單元都填上相應內容后也就得到了整個消隱后的圖。

4、算法步驟

 

算法描述如下

七、優先級排序表算法

1、算法思想

優先級排序表算法按多邊形離觀察者的遠近來建立一個多邊形排序表,距觀察者遠的優先級低,放在表頭;近的優先級高,放在表尾
從優先級低的多邊形開始,依次把多邊形的顏色填入幀緩衝存儲器中
表中距觀察者近的元素覆蓋幀緩衝存儲器中原有的內容
當優先級最高的多邊形的圖形送入幀緩衝器后,整幅圖形就形成了
類似於油畫家繪畫過程,因此又稱為油畫家算法。

 

 

 

 

 

 

2、算法的優缺點

算法的優點:
簡單,容易實現,並且可以作為實現更複雜算法的基礎;
缺點:
只能處理不相交的面,而且深度優先級表中面的順序可能出錯.

該算法不能處理某些特殊情況。

 

 

解決辦法:把P沿Q平面一分為二,從多邊形序列中把原多邊形P去掉,把分割P生成的兩個多邊形加入鏈表中。具體實現時,當離視點最遠的多邊形P和其他多邊形交換時,要對P做一標誌,當有標誌的多邊形再換成離視點最遠的多邊形時,則說明出現了上述的現象,可用分割方法進行處理 。

用來解決動態显示問題時,可大大提高效率

八、光線投射算法

1、算法原理

要處理的場景中有無限多條光線,從採樣的角度講我們僅對穿過像素的光線感興趣,因此,可考慮從像素出發,逆向追蹤射入場景的光線路徑

 

 

 

 

 

2、算法實現

由視點出發穿過觀察平面上一像素向場景發射一條射線
求出射線與場景中各物體表面的交點
離視點最近的交點的顏色即為像素要填的顏色。
光線投射算法對於包含曲面,特別是包含球面的場景有很高的效率。

 

 

 

 

 

 

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

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

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

Fork/Join框架詳解

Fork/Join框架詳解

Fork/Join框架是Java 7提供的一個用於并行執行任務的框架,是一個把大任務分割成若干個小任務,最終匯總每個小任務結果后得到大任務結果的框架。Fork/Join框架要完成兩件事情:

  • 任務分割:首先Fork/Join框架需要把大的任務分割成足夠小的子任務,如果子任務比較大的話還要對子任務進行繼續分割

  • 執行任務併合並結果:分割的子任務分別放到雙端隊列里,然後幾個啟動線程分別從雙端隊列里獲取任務執行。子任務執行完的結果都放在另外一個隊列里,啟動一個線程從隊列里取數據,然後合併這些數據

ForkJoinTask

使用Fork/Join框架,首先需要創建一個ForkJoin任務。該類提供了在任務中執行fork和join的機制。通常情況下我們不需要直接集成ForkJoinTask類,只需要繼承它的子類,Fork/Join框架提供了兩個子類:

  • RecursiveAction
    用於沒有返回結果的任務
  • RecursiveTask
    用於有返回結果的任務

ForkJoinPool

ForkJoinTask需要通過ForkJoinPool來執行。

任務分割出的子任務會添加到當前工作線程所維護的雙端隊列中,進入隊列的頭部。當一個工作線程的隊列里暫時沒有任務時,它會隨機從其他工作線程的隊列的尾部獲取一個任務(工作竊取算法);

Fork/Join框架的實現原理

ForkJoinPool由ForkJoinTask數組和ForkJoinWorkerThread數組組成,ForkJoinTask數組負責將存放程序提交給ForkJoinPool,而ForkJoinWorkerThread負責執行這些任務;

ForkJoinTask的fork方法的實現原理

當我們調用ForkJoinTask的fork方法時,程序會把任務放在ForkJoinWorkerThread的pushTask的workQueue中,異步地執行這個任務,然後立即返回結果,代碼如下:

public final ForkJoinTask<V> fork() {
    Thread t;
    if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
        ((ForkJoinWorkerThread)t).workQueue.push(this);
    else
        ForkJoinPool.common.externalPush(this);
    return this;
}

pushTask方法把當前任務存放在ForkJoinTask數組隊列里。然後再調用ForkJoinPool的signalWork()方法喚醒或創建一個工作線程來執行任務。代碼如下:

final void push(ForkJoinTask<?> task) {
    ForkJoinTask<?>[] a; ForkJoinPool p;
    int b = base, s = top, n;
    if ((a = array) != null) {    // ignore if queue removed
        int m = a.length - 1;     // fenced write for task visibility
        U.putOrderedObject(a, ((m & s) << ASHIFT) + ABASE, task);
        U.putOrderedInt(this, QTOP, s + 1);
        if ((n = s - b) <= 1) {
            if ((p = pool) != null)
                p.signalWork(p.workQueues, this);
        }
        else if (n >= m)
            growArray();
    }
}

ForkJoinTask的join方法的實現原理

Join方法的主要作用是阻塞當前線程並等待獲取結果。讓我們一起看看ForkJoinTask的join方法的實現,代碼如下:

public final V join() {
    int s;
    if ((s = doJoin() & DONE_MASK) != NORMAL){
        reportException(s);
    }
    return getRawResult();
}

它首先調用doJoin方法,通過doJoin()方法得到當前任務的狀態來判斷返回什麼結果,任務狀態有4種:已完成(NORMAL)、被取消(CANCELLED)、信號(SIGNAL)和出現異常(EXCEPTIONAL);
如果任務狀態是已完成,則直接返回任務結果;
如果任務狀態是被取消,則直接拋出CancellationException;
如果任務狀態是拋出異常,則直接拋出對應的異常;
doJoin方法的實現,代碼如下:

private int doJoin() {
    int s;
    Thread t;
    ForkJoinWorkerThread wt;
    ForkJoinPool.WorkQueue w;
    return (s = status) < 0 ? s :
            ((t = Thread.currentThread()) instanceof                                ForkJoinWorkerThread) ? (w = (wt =                                      (ForkJoinWorkerThread)t).workQueue).tryUnpush(this) && (s =                 doExec()) < 0 ? s : wt.pool.awaitJoin(w, this, 0L) :                externalAwaitDone();
}

doExec() :

final int doExec() {
    int s; 
    boolean completed;
    if ((s = status) >= 0) {
        try {
            completed = exec();
        } catch (Throwable rex) {
            return setExceptionalCompletion(rex);
        }
        if (completed){
            s = setCompletion(NORMAL);
        }
    }
    return s;
}

在doJoin()方法里,首先通過查看任務的狀態,看任務是否已經執行完成,如果執行完成,則直接返回任務狀態;如果沒有執行完,則從任務數組裡取出任務並執行。如果任務順利執行完成,則設置任務狀態為NORMAL,如果出現異常,則記錄異常,並將任務狀態設置為EXCEPTIONAL

Fork/Join框架的異常處理

ForkJoinTask在執行的時候可能會拋出異常,但是我們沒辦法在主線程里直接捕獲異常,所以ForkJoinTask提供了isCompletedAbnormally()方法來檢查任務是否已經拋出異常或已經被取消了,並且可以通過ForkJoinTask的getException方法獲取異常。代碼如下:

if(task.isCompletedAbnormally())
{
    System.out.println(task.getException());
}

getException方法返回Throwable對象,如果任務被取消了則返回CancellationException。如果任務沒有完成或者沒有拋出異常則返回null:

public final Throwable getException() {
    int s = status & DONE_MASK;
    return ((s >= NORMAL) ? null :
        (s == CANCELLED) ? new CancellationException() :
        getThrowableException());
}

DEMO

需求:求1+2+3+4的結果
分析:Fork/Join框架首先要考慮到的是如何分割任務,如果希望每個子任務最多執行兩個數的相加,那麼我們設置分割的閾值是2,由於是4個数字相加,所以Fork/Join框架會把這個任務fork成兩個子任務,子任務一負責計算1+2,子任務二負責計算3+4,然後再join兩個子任務的結果。因為是有結果的任務,所以必須繼承RecursiveTask,實現代碼如下:

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;

/**
 *
 * @author aikq
 * @date 2018年11月21日 20:37
 */
public class ForkJoinTaskDemo {

    public static void main(String[] args) {
        ForkJoinPool pool = new ForkJoinPool();
        CountTask task = new CountTask(1,4);
        Future<Integer> result = pool.submit(task);
        try {
            System.out.println("計算結果=" + result.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

class CountTask extends RecursiveTask<Integer>{
    private static final long serialVersionUID = -7524245439872879478L;

    private static final int THREAD_HOLD = 2;

    private int start;
    private int end;

    public CountTask(int start,int end){
        this.start = start;
        this.end = end;
    }

    @Override
    protected Integer compute() {
        int sum = 0;
        //如果任務足夠小就計算
        boolean canCompute = (end - start) <= THREAD_HOLD;
        if(canCompute){
            for(int i=start;i<=end;i++){
                sum += i;
            }
        }else{
            int middle = (start + end) / 2;
            CountTask left = new CountTask(start,middle);
            CountTask right = new CountTask(middle+1,end);
            //執行子任務
            left.fork();
            right.fork();
            //獲取子任務結果
            int lResult = left.join();
            int rResult = right.join();
            sum = lResult + rResult;
        }
        return sum;
    }
}

通過這個例子,我們進一步了解ForkJoinTask,ForkJoinTask與一般任務的主要區別在於它需要實現compute方法,在這個方法里,首先需要判斷任務是否足夠小,如果足夠小就直接執行任務。如果不足夠小,就必須分割成兩個子任務,每個子任務在調用fork方法時,又會進入compute方法,看看當前子任務是否需要繼續分割成子任務,如果不需要繼續分割,則執行當前子任務並返回結果。使用join方法會等待子任務執行完並得到其結果

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

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

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

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