救蜂群的替代殺蟲劑 結果也對蜜蜂有害

摘錄自2018年8月16日中央社報導

研究人員今天(16日)警告,一種用於替代危害蜜蜂的「新類尼古丁」(neonicotinoid)殺蟲藥的新型殺蟲劑,可能與新類尼古丁殺蟲藥一樣,還是對作物授粉的蜜蜂有害。

研究人員在「自然」(Nature)期刊表示,實驗中,大黃蜂的繁殖能力和牠們蜂群成長速度,都會受到新的鵳基亞胺類(sulfoximine)殺蟲劑所影響。

研究主筆、倫敦大學皇家哈洛威學院(Royal Holloway, University of London)研究人員席維特(Harry Siviter)說:「我們研究結果顯示,新型殺蟲劑之一的速殺氟(sulfoxaflor)會對大黃蜂繁殖量產生負面影響。」

與新類尼古丁相似,速殺氟不會直接殺死蜜蜂,但卻顯示會影響蜜蜂的免疫系統或生殖能力。

不過覓食行為和個別蜜蜂採集花粉量在實驗中保持不變。

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

【其他文章推薦】

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

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

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

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

※超省錢租車方案

FB行銷專家,教你從零開始的技巧

買綠電團購力量大 蘋果等四企業簽290MW訂單

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

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

【其他文章推薦】

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

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

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

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

網頁設計最專業,超強功能平台可客製化

ODBC 常見數據源配置整理

目錄

  • 1. 簡介
    • 1.1 ODBC和JDBC
    • 1.2 ODBC配置工具
    • 1.3 ODBC 數據源連接配置
  • 2. MySQL 數據源配置
    • 2.1 配置步驟
    • 2.2 鏈接參數配置
  • 3. SQLServer 數據源配置
    • 3.1 配置步驟
    • 3.2 鏈接參數配置
  • 4. ACCESS 數據源配置
    • 4.1 配置步驟
    • 4.2 鏈接參數配置

1. 簡介

我們用golang做odbc驅動開發的任務並不多,隔段時間可能會來一個。每次開發會忘記如何配置數據源和對應的數據源鏈接參數配置。這裏做一個整理。

1.1 ODBC和JDBC

ODBC(Open Database Connectivity)是一組對數據庫訪問的標準API,其最大的優點是以統一的方式處理所有的數據庫。

JDBC(Java Database Connectivity)是Java與數據庫的接口規範,允許Java程序發送SQL指令並處理結果。比較常見JdbcTemplate

1.2 ODBC配置工具

打開控制面板找到管理工具,當前目錄有兩個ODBC的配置工具。分別是:ODBC Data Sources (32-bit)、ODBC 數據源(64 位)。顧名思義一個是32位,一個是64位。在配置ACCESS數據源時需要選擇32位。

我們也可以直接在系統目錄下找到對應的可執行文件。

1)32位:%windir%\syswow64\odbcad32.exe

2)64位:%windir%\system32\odbcad32.exe

題外話:syswow64 目錄存放的是32位的程序,system32目錄存放的是64位的程序,在註冊dll的時候需要注意下。ITDragon 在接觸驅動開發之前一直都弄反了。這篇文章做了通俗易懂地解釋https://www.cnblogs.com/hbccdf/p/dllchecktoolandsyswow64.html

1.3 ODBC 數據源連接配置

網上收集整理,不保證正確性,僅供參考。

數據庫 連接參數
MySQL driver={mysql};database=數據庫;uid=賬號;pwd=密碼;
MSSQL Server driver={sql server};server=服務器;database=數據庫;uid=ITDragon;pwd=密碼;
Access driver={microsoft access driver (*.mdb)};dbq=mdb文件全路徑;uid=ITDragon;pwd=密碼;
SQLite driver={SQLite3 ODBC Driver};database=db文件全路徑
PostgreSQL driver={PostgreSQL ANSI};server=服務器;uid=賬號;pwd=密碼;database=數據庫;
DBase driver={microsoft dbase driver (*.dbf)};driverid=277;dbq=dbf文件全路徑;
Oracle driver={microsoft odbc for oracle};server=服務器;uid=ITDragon;pwd=密碼;
MS text driver={microsoft text driver (* .txt; *.csv)};dbq=文件全路徑;extensions=asc,csv,tab,txt;PersistSecurityInfo=false;
Visual Foxpro driver={microsoft Visual Foxpro driver};sourcetype=DBC;sourceDB=*.dbc;Exclusive=No;

2. MySQL 數據源配置

2.1 配置步驟

第一步:雙擊ODBC 數據源(64 位),可以選擇用戶DSN(系統只對當前用戶生效),也可以選擇系統DSN(能登錄當前系統的用戶都生效)

第二步:點擊添加,選擇事先安裝好的MySQL ODBC xx Driver 驅動後點擊完成。注意不同版本之間對某些sql語法的支持略有不同(之前吃過這個虧,我的環境有問題,客戶環境沒問題)。

第三步:完善基本鏈接信息後點擊Test,提示鏈接成功後點擊OK完成配置。

2.2 鏈接參數配置

因為ODBC驅動配置已經將數據庫的連接地址、賬號、密碼、數據庫都已經配置完成,連接參數只需要指定驅動名稱即可:DSN=ITDragon_MySQL

Golang偽代碼:

import (
	"database/sql"
	_ "github.com/alexbrainman/odbc"
)

db, err := sql.Open("odbc", "DSN=ITDragon_MySQL")
if err != nil {
    panic(err)
}
defer db.Close()

3. SQLServer 數據源配置

3.1 配置步驟

第一步:雙擊ODBC 數據源(64 位),可以選擇用戶DSN(系統只對當前用戶生效),也可以選擇系統DSN(能登錄當前系統的用戶都生效)

第二步:點擊添加,選擇SQL Server。這一項我ITDragon 並沒有手動安裝,應該是安裝SQL Server數據庫的時候自動安裝上去的。

第三步:鍵盤輸入需要連接的SQLServer服務器,如果是本機,就輸入計算機名。如果是遠程主機就需要輸入IP,Port 。不要點擊下拉框,會卡死。

第四步:選擇SQL Server驗證方式

第五步:選擇默認數據庫,也可以在寫sql語句時將表名的全路徑寫全(我ITDragon 習慣用寫全)

第六步:可以考慮修改SQL Server的系統消息語言,數據的加密,執行字符數據翻譯,修改日誌保存路徑等。也可以默認。

第七步:點擊完成,彈出“按照以下配置創建新的ODBC數據源”,點擊測試數據源,提示測試成功。點擊確定完成創建。

3.2 鏈接參數配置

Golang偽代碼:

import (
	"database/sql"
	_ "github.com/alexbrainman/odbc"
)

db, err := sql.Open("odbc", "driver={sql server};server=DESKTOP-HKC2IA3;DSN=ODBCName;uid=xxx;pwd=xxx;")
if err != nil {
    panic(err)
}
defer db.Close()

4. ACCESS 數據源配置

4.1 配置步驟

第一步:雙擊ODBC Data Sources (32-bit),而不是64位。可以選擇用戶DSN(系統只對當前用戶生效),也可以選擇系統DSN(能登錄當前系統的用戶都生效)

第二步:選擇Microsoft Access Driver (*.mdb)。點擊數據庫下發的選擇按照,選擇mdb文件,點擊確定完成配置。

4.2 鏈接參數配置

我們其實可以不用配置Access的數據源,直接用DBQ指定mdb的文件路徑,再用pwd輸入密碼訪問。

Golang偽代碼:

import (
	"database/sql"
	_ "github.com/alexbrainman/odbc"
)

db, err := sql.Open("odbc", "DRIVER={Microsoft Access Driver (*.mdb)};DBQ=全路徑.MDB;pwd=xx;")
if err != nil {
    panic(err)
}
defer db.Close()

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

【其他文章推薦】

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

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

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

※推薦評價好的iphone維修中心

專家解讀:利用Angular項目與數據庫融合實例

摘要:面對如何在現有的低版本的框架服務上,運行新版本的前端服務問題,華為雲前端推出了一種融合方案,該方案能讓獨立的Angular項目整體運行在低版本的框架服務上,通過各種適配手段,讓Angular項目也能獲取到外層框架服務的資源。

華為雲前端服務前期採用AngularJs作為框架技術棧,技術較為老舊,性能較差,在華為雲快速發展的今天,顯然不能滿足要求。因此我們必須要升級前端技術棧,使用Angular2+來承載我們的前端服務。GeminiDB作為新服務,也是數據庫乃至華為雲未來的重點服務,作為前端部分,必須在技術上使用最前沿的框架,以最大地提高用戶體驗。

但是技術棧的升級不是一蹴而就的,尤其是在華為雲,所有的雲服務必須在框架服務的底座上運行,而框架服務承載了所有的雲服務,如果要進行技術棧升級,必然是一個緩慢的過程。GeminiDB作為華為雲服務里的一員,也不可能脫離框架服務而存在。因此存在一個問題,就是如何在現有的低版本的框架服務上,運行新版本的前端服務。

為了解決以上問題,華為雲前端推出了一種融合方案,該方案能讓獨立的Angular項目整體運行在低版本的框架服務上,通過各種適配手段,讓Angular項目也能獲取到外層框架服務的資源。

底層項目

底層項目使用webpack打包,打包后通過在index.html里引入businessAll.js文件,以該文件為入口啟動整個框架服務。

<script type="text/javascript" src="businessAll.js"></script>

在底層框架服務啟動后,再渲染出具體雲服務內容。

<div class="service-content-view" ui-view ng-animate="{enter:'fade-enter'}"></div>

Angular項目

Angular項目支持獨立運行,有單獨的index.html,也有單獨的main.ts入口。但是如果希望Angular項目運行在底層框架服務上,就必須把Angular項目看作是一個獨立的模塊,把項目整體引入到底層項目中。因此,我們可以預先把Angular項目編譯好,放到底層項目的一個目錄下。在運行底層項目時,在index.html里將Angular項目引進來,獨立運行。

<link rel="stylesheet" type="text/css" href="{底層項目中Angular項目的路徑}/styles.css" />
<script type="text/javascript" src="{底層項目中Angular項目的路徑}/runtime.js"></script>
<script type="text/javascript" src="{底層項目中Angular項目的路徑}/polyfills.js"></script>
<script type="text/javascript" src="{底層項目中Angular項目的路徑}/main.js"></script>

項目融合

底層項目和Angular項目均能獨立,但是要讓兩者融合起來,會遇到以下幾個問題:

1.底層項目中如何渲染出Angular項目。

2.Angular項目依賴底層項目的資源,如何保證Angular項目在底層項目運行起來后再運行。

3.如何解決底層項目和Angular項目的路由衝突問題。

渲染Angular項目

底層項目分為兩部分,一部分是底層框架服務,另一部分是具體雲服務。現在我們要做的是把老的雲服務項目替換成新的Angular項目,因此我們可以直接在渲染老的雲服務的地方替換成新的Angular項目的渲染容器。

<div class="service-content-view" ui-view ng-animate="{enter:'fade-enter'}"></div>
<app-root></app-root>

底層框架服務對頁面渲染上做了一些體驗上的優化,因此必須保留原模板中的ui-view,使底層項目正常運行起來,實際上老的雲服務項目的渲染內容已經轉發到新的Angular項目上面。

Angular項目對底層項目的依賴

底層框架服務給雲服務提供了很多公共變量與服務,這些變量和服務是各個雲服務必須要使用的,否則雲服務將不能正常運作。

啟動順序問題

對於Angular項目來說,要使用底層框架服務提供的內容,首先要求Angular項目在底層項目運行起來之後再運行。這裏採用Augular中的APP_INITIALIZER令牌來解決這個問題。APP_INITIALIZER是一個函數,在程序初始化的時候被調用。這裡在根模塊的providers中以factory的形式來配置。

import { BrowserModule } from "@angular/platform-browser";
import { NgModule } from "@angular/core";

import { AppInitService } from './services/app-init.service';
import { AppComponent } from "./app.component";

@NgModule({
 declarations: [AppComponent],
 imports: [BrowserModule],
 providers: [
     AppInitService,
    {
         provide: APP_INITIALIZER,
         useFactory: initializeApp,
         deps: [AppInitService],
         multi: true
    }
],
 bootstrap: [AppComponent]
})
export class AppModule {}

export function initializeApp(appInitService: AppInitService) {
   return (): Promise<any> => {
       return appInitService.Init();
  };
}

在appInitService里,先獲取到底層框架的資源,再進行Angular項目的初始化。

import { Injectable } from '@angular/core';

@Injectable()
export class AppInitService {
   constructor() {}

   Init() {
       return new Promise<void>((resolve, reject) => {
           // 獲取到底層框架服務的資源
           resolve();
      });
  }
}

資源依賴問題

底層項目使用的是AngularJs,Angular項目獲取底層框架服務提供的資源不能通過Angular的方式引入,因此需要藉助AngularJS的注入器獲取在底層框架中註冊的服務組件:

static get(inject: string): any {
return (window as any).angular.element('html').injector().get(inject);}
如,要獲取 $rootScope:
 rootScope = (window as any).angular.element('html').injector().get(‘$rootScope’);

路由衝突問題

Angular項目本身有自己的路由,但是Angular項目是運行在底層框架之上的,Angular項目的路由將會被底層框架所攔截。因此,我們也需要在底層框架的項目中配置相同的路由,以免Angular項目中的有效路由被底層框架識導向為404。

Angular項目路由:

{
   path: '',
   redirectTo: 'ng2app1',
   pathMatch: 'full'
},
{
   path: 'ng2app1',
   loadChildren: './ng2app1/ng2app1.module#Ng2app1Module',
},
{
   path: 'ng2app2',
   loadChildren: './ng2app2/ng2app2.module#Ng2app2Module',
}
 
底層框架路由:
var configArr = [
  {
       name: 'ng2app1',
       url: '/ng2app1'
  },
  {
       name: 'ng2app2',
       url: '/ng2app2'
  }
];

另外,由於底層項目使用的是hash路由,Angular項目中也要做相應的配置,默認是使用的是PathLocationStrategy,需要切換到hash模式。

import { LocationStrategy, HashLocationStrategy } from '@angular/common';

...
providers: [
  {
       provide: LocationStrategy,
       useClass: HashLocationStrategy
  }
]

總結

以上方案是在底層框架升級周期長的前提下的一個臨時方案,實際上還是存在着不少的問題。比如底層框架對於老的雲服務容器是有統一管理的,老的雲服務容器會針對不同的場景能夠自適應,而融合方案中的Angular項目則不能;每次啟動整個項目時,必須要預先編譯好裏面的Angular項目,再去啟動外層的底層框架,開發效率比較低。因此,後續GeminiDB服務應該在底層框架升級后,儘快適應到新的底層框架體系中,提高服務的可用性和穩定性。

 

點擊關注,第一時間了解華為雲新鮮技術~

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

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

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

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

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

※回頭車貨運收費標準

向強大的SVG邁進

作者:凹凸曼 – 暖暖

SVG 即 Scalable Vector Graphics 可縮放矢量圖形,使用XML格式定義圖形。

一、SVG印象

SVG 的應用十分廣泛,得益於 SVG 強大的各種特性。

1.1、 矢量

可利用 SVG 矢量的特點,描出深圳地鐵的輪廓:

1.2、iconfont

SVG 可依據一定的規則,轉成 iconfont 使用:

1.3、 foreignObject

利用 SVG 的 foreignObject 標籤實現截圖功能,原理:foreignObject 內部嵌入 HTML 元素:

<svg xmlns="http://www.w3.org/2000/svg">
	<foreignObject width="120" height="60">
		<p style="font-size:20px;margin:0;">凹凸實驗室 歡迎您</p>
	</foreignObject>
</svg>

截圖實現流程:

  1. 首先聲明一個基礎的 svg 模版,這個模版需要一些基礎的描述信息,最重要的,它要有 <foreignObject></foreignObject> 這對標籤;
  2. 將要渲染的 DOM 模版模版嵌入 foreignObject 即可;
  3. 利用 Blob 構建 svg 對象;
  4. 利用 URL.createObjectURL(svg) 取出 URL。

1.4、SVG SMIL

由於微信編輯器不允許嵌入 <style><script><a> 標籤,利用SVG SMIL 可進行微信公眾號極具創意的圖文排版設計,包括動畫與交互。
但是也要注意,標籤里不允許有id,否則會被過濾或替換掉。

點擊 “凹凸實驗室” 后,圍繞 “凹凸實驗室” 中心旋轉 360度,點擊0.5秒后 出現 https://aotu.io/ ,動畫只運行一次。

下圖為 GIF循環演示:

代碼如下:

<svg width="360" height="300" xmlns="http://www.w3.org/2000/svg">
    <g>
        <!-- 點擊后 運行transform旋轉動畫,restart="never"表示只運行一次 -->
        <animateTransform attributeName="transform" type="rotate" begin="click" dur="0.5s" from="0 100 80" to="360 100 80"  fill="freeze" restart="never" />
        <g>
            <text font-family="microsoft yahei" font-size="20" x="50" y="80">
                凹凸實驗室
            </text>
        </g>
        <g style="opacity: 0;">
            <!-- 同一個初始位置以及大致的寬高,觸發點擊事件 -->
            <text font-family="microsoft yahei" font-size="20" x="50" y="80">https://aotu.io/</text>
            <!-- 點擊后 運行transform移動動畫,改變文本的位置 -->
            <animateTransform attributeName="transform" type="translate" begin="click" dur="0.1s" to="0 40"  fill="freeze" restart="never" />
            <!-- 點擊0.5秒后 運行opacity显示動畫 -->
            <animate attributeName="opacity" begin="click+0.5s" from="0" to="1" dur="0.5s" fill="freeze" restart="never" />
        </g>
    </g>
</svg>

以上是鄙人對SVG的大致印象,最近的需求開發再次刷新了我的認知,那就是 SVG實現非比例縮放 以及 小程序不支持SVG標籤的處理,下面容我來講述一番。

二、SVG 實現非比例縮放

我們熟知的 iconfont,可通過改變字體大小縮放,但是這是 比例縮放,那如何實現 SVG 的非比例縮放呢?
如下圖所示,如何將 一隻兔子 非比例縮放?

划重點:實現非比例縮放主要涉及三個知識點:viewport、viewBox和preserveAspectRatio,viewport 與viewBox 結合可實現縮放的功能,viewBox 與 preserveAspectRatio 結合可實現非比例的功能。

2.1、viewport

viewport 表示SVG可見區域的大小。
viewport 就像是我們的显示器屏幕大小,超出區域則隱藏,原點位於左上角,x 軸水平向右,y 軸垂直向下。

通過類似CSS的屬性 widthheight 指定視圖大小:

<svg width="400" height="200"></svg>

2.2、viewBox

viewBox值有4個数字:x, y, width, height 。
其中 x:左上角橫坐標,y:左上角縱坐標,width:寬度,height:高度。
原點默認位於左上角,x 軸水平向右,y 軸垂直向下。

<svg width="400" height="200" viewBox="0 0 200 100"></svg>

显示器屏幕的畫面,可以特寫,可以全景,這就是 viewBox
viewBox 可以想象成截屏工具選中的那個框框,和 viewport 作用的結果就是 把框框中的截屏內容再次在 显示器 中全屏显示。

(圖片來源:SVG 研究之路 (23) – 理解 viewport 與 viewbox)

2.3、preserveAspectRatio

上圖的紅色框框和藍色框框,恰好和显示器的比例相同,如果是下圖的綠色框框,怎樣在显示器屏幕中显示呢?

2.3.1、 定義

preserveAspectRatio 作用的對象是 viewBox,使用方法如下:

preserveAspectRatio="[defer] <align> [<meetOrSlice>]"
// 例如 preserveAspectRatio="xMidYMid meet"

其中 defer 此時不是重點,暫且忽略,主要了解 alignmeetOrSlice 的 用法:

  • align:由兩個名詞組成,分別代表 viewbox 與 viewport 的 x 方向、y方向的對齊方式。
含義
xMin viewport 和 viewBox 左邊對齊
xMid viewport 和 viewBox x軸中心對齊
xMax viewport 和 viewBox 右邊對齊
YMin viewport 和 viewBox 上邊緣對齊。注意Y是大寫。
YMid viewport 和 viewBox y軸中心點對齊。注意Y是大寫。
YMax viewport 和 viewBox 下邊緣對齊。注意Y是大寫。
  • meetOrSlice:表示如何維持高寬的比例,有三個值 meet、slice、none。
    • meet – 默認值,保持縱橫比縮放 viewBox 適應 viewport,可能會有餘留的空白。
    • slice – 保持縱橫比同時比例小的方向放大填滿 viewport,超出的部分被剪裁掉。
    • none – 扭曲縱橫比以充分適應 viewport。
2.3.2、 例子

例子1:preserveAspectRatio="xMidYMid meet" 表示 綠色框框 與 显示器的 x 方向、y方向的 中心點 對齊;

例子2:preserveAspectRatio="xMidYMin slice" 表示 綠色框框 與 显示器的 x 方向 中心點 對齊,Y 方向 上邊緣對齊,保持比例放大填滿 显示屏 后超出部分隱藏;

例子3:preserveAspectRatio="xMidYMid slice" 表示 綠色框框 與 显示器的 x 方向、y方向的 中心點 對齊,保持比例放大填滿显示屏 后超出部分隱藏;

例子4:preserveAspectRatio="none" 不管三七二十一,隨意縮放綠色框框,填滿 显示屏即可;這就是非比例縮放的答案了。

三、小程序不支持svg標籤怎麼辦

微信小程序官方不支持 SVG 標籤的,但是決定曲線救國,相當於自己實現了一個SVG標籤:使用小程序內置的 Canvas 渲染器, 在 Cax 中實現 SVG 標準的子集,使用 JSX 或者 HTM(Hyperscript Tagged Markup) 描述 SVG 結構行為表現。

但是今天我想講講其他的。
我們知道,小程序雖然不支持 SVG 標籤,但是支持 svg 轉成 base64 後作為 background-imageurl,如 background-image: url("data:image/svg+xml.......)

但是我這邊還有個需求,隨時更改 SVG 每個路徑的顏色,即 顏色可配置:

來迴轉 Base64 肯定是比較麻煩的,有沒有更好的方式呢?
直接貼答案:對於SVG圖形,還有更好的實現方式,就是直接使用SVG XML格式代碼,無需進行base64轉換。

3.1、URL 編碼

直接使用 SVG XML 格式代碼,首先要了解 Data URI的格式。
划重點:base64非必選項,不指定的時候,後面的 <data> 將使用 URL編碼。

3.1.1、入門

百分號編碼(Percent-encoding), 也稱作URL編碼(URL encoding),是特定上下文的統一資源定位符 (URL)的編碼機制。

原理:ASCII 字符 = % + 兩位 ASCII 碼(十六進制)。
例如,字符 a 對應的 ASCII 碼為 0x61,那麼 URL 編碼后得到 %61 。

3.1.2、URL 編碼壓縮

前言:

Data URI 的格式中的 <data> 完全使用URL 編碼也是可以的,如 encodeURIComponent('<svg version="1.1" viewBox= …</svg>')
但是和轉義前原始SVG相比,可讀性差了很多,而且佔用體積也變大了。
如果深入了解URL 編碼的話,<data> 沒必要全部編碼的。

正文:

RFC3986文檔規定,URL中只允許包含 未保留字符 以及 所有保留字符。

  • 未保留字符:包含英文字母(a-zA-Z)、数字(0-9)、-_.~ 4個特殊字符。對於未保留字符,不需要百分號編碼。
  • 保留字符:具有特殊含義的字符 :/?#[]@ (分隔Url的協議、主機、路徑等組件) 和 !$&'()*+,;= (用於在每個組件中起到分隔作用的,如&符號用於分隔查詢多個鍵值對)。
  • 受限字符或不安全字符:直接放在Url中的時候,可能會引起解析程序的歧義,因此這部分需要百分號編碼,如%空格雙引號"尖號 <>等等。

綜上所述,只需要對 受限字符或不安全字符 進行編碼即可。

  • JS 處理比較簡單,利用 replace 將 需要編碼的字符 替換掉 即可,基本替換 以下的符號 就夠用了:
svgToUrl (svgData) {
    encoded = encoded
      .replace(/<!--(.*)-->/g, '') // 親測必須去掉註釋
      .replace(/[\r\n]/g, ' ') // 親測最好去掉換行
      .replace(/"/g, `'`) // 單引號是保留字符,雙引號改成單引號減少編碼
      .replace(/%/g, '%25')
      .replace(/&/g, '%26')
      .replace(/#/g, '%23')
      .replace(/{/g, '%7B')
      .replace(/}/g, '%7D')
      .replace(/</g, '%3C')
      .replace(/>/g, '%3E')
    return `data:image/svg+xml,${encoded}`
  }
  • 如果使用在 CSS 中,可利用 SASS版本3.3以上 的 三個API 對 SVG字符串做替換處理。

    1. str_insert(string, insert, index): 從 $string$index 插入字符 $insert
    2. str_index(string, substring): 返回 $substring$string 中第一個位置;
    3. str_slice(string, start_at, end_at = nil): 返回從字符 $string 中第 $start_at 開始到 $end_at 結束的一個新字符串。

    前人已有總結,可前往 https://github.com/leeenx/sass-svg/blob/master/sass-encodeuri.scss 查看完整代碼。

3.2、SVG 壓縮

一般從 Sketch 導出 SVG ,冗餘代碼比較多,有條件的話建議使用 SVGO 壓縮SVG的原本體積,比如清除換行、重複空格;刪除文檔聲明;刪除註釋;刪除desc描述等等。

四、總結

SVG強大的地方在於,出其不意,炫酷,與眾不同。

無論是微信公眾號花式排版,foreignObject 標籤實現截圖,實現非比例縮放,或者 背景圖直接使用 SVG XML 格式代碼,還是上文沒有提及的路徑動畫、描邊動畫、圖形裁剪、濾鏡等等,都可以玩出新的花樣。

SVG 一個屬性可成就一篇文章,學習 SVG 可以說是在挑戰自己,歡迎加入 SVG 的學習隊列。

五、參考內容 · 推薦閱讀

三看 SVG Web 動效
URL編碼的奧秘
學習了,CSS中內聯SVG圖片有比Base64更好的形式
超級強大的SVG SMIL animation動畫詳解
詳細教你微信公眾號正文頁SVG交互開發
SVG 簡介與截圖等應用

歡迎關注凹凸實驗室博客:aotu.io

或者關注凹凸實驗室公眾號(AOTULabs),不定時推送文章:

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

【其他文章推薦】

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

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

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

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

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

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

Day10-微信小程序實戰-交友小程序-添加好友功能之創建並更新message信息

1、首先要在 添加好友 這個按鈕上添加一個事件,也就是在detail.wxml的添加好友這個按鈕的哪裡,添加一個點擊事件 handleAddFriend

  並且添加好友還要考慮,現在是已登陸狀態還是未登陸狀態的,只有是登陸狀態的時候,才可以發起添加好友的請求的

所以就要先判斷一下它是否已經登陸了

因為只要是登陸之後,就會把用戶的id寫入到全局的userinfo下面的

  handleAddFriend(){
      if( app.userInfo._id){

      }
      else{
        wx.showToast({
          title: '請先登陸',
          duration : 2000,
          // 然後不要讓它显示圖標
          icon : 'none',
          success: ()=>{
            // 如果成功的話就直接跳轉到我的頁面去
            // 但是注意了這裏不能用 navigator to,因為它主要是跳轉
            // 普通的頁面,而這裏“我的頁面”其實是同tabbar來進行配置的
            
          }
        })
      }
  }

這個時候就可以查找一下 小程序文檔 中關於“路由”的介紹了

 

 

 

可以看到要用wx.switchtab來進行操作了

 然後因為我們設置了那個提示“請先登陸”是維持兩秒鐘,所以我們也要設置這個跳轉到我的頁面中的時間也是兩秒鐘

handleAddFriend(){
      if( app.userInfo._id){

      }
      else{
        wx.showToast({
          title: '請先登陸',
          duration : 2000,
          // 然後不要讓它显示圖標
          icon : 'none',
          success: ()=>{
            // 如果成功的話就直接跳轉到我的頁面去
            // 但是注意了這裏不能用 navigator to,因為它主要是跳轉
            // 普通的頁面,而這裏“我的頁面”其實是同tabbar來進行配置的
         setTimeout(()=>{
           wx.switchTab({
             url: '/pages/user/user',
           })
         } , 2000);
          }
        })
      }
  }

上面的加入 沒登陸的情況也寫好了,下面就是對已經登陸了之後的設計了

就要在數據可以中建立一個message集合,主要是用來存儲好友消息,或者是系統的消息給這個用戶的一個信息集合的

這個集合裏面的每一個信息,包含了userID也就是這個好友請求或者是信息是發送給哪一個人的

然後還有一個其他想要加他好友的用戶id list數組,因為每個人都可以給這個人發起好友請求的,這就是對於數據庫1的建立了

所以在已經登陸之後,先查看一下有沒有這個發起好友的信息了,如果還有的話,就在數據庫中創立這個字段了

這個數據庫裏面的userid字段存的其實就是我們要加的這個人的id標識了,然後這個人的id我們可以從這個人的詳情頁面(detail)下的data中來獲得的

通過where就可以定位到在數據庫中這個用戶對應的字段了,然後用get就可以開始對這個字段裏面的東西進行查詢了

如果這個信息已經存在了就做更新操作,如果不存在的話就做創建操作即可了

 if( app.userInfo._id){
        db.collection('mesasge').where({
          userId : this.data.detail._id
        }).get().then((res)=>{
 
            if( res.data.length){//更新

            }
            else{  //tianjia1
              db.collection('message').add({
                data : {
                  userId : this.data.detail._id,
                  list : [ app.userInfo._id]
                }
              })
            }
        });
      }

之後就可以查看數據可以中的message 集合

 

 

 

 這樣的話,說明就調用成功了

二、更新message 信息

因為如果已經申請過了的話,就不能再往list裏面添加自己的id了,所以就要檢測一下現在是否在list中,

可以直接調用數組的include方法來進行查詢,如果找到了的話,就提示“已申請過了”如果沒找到的話就要往這個數組裡面進行數據更新了

查看了微信開放文檔之後會發現,如果是單個數據進行更新的話可以直接用doc好到之後進行更新就好了,但是如果對大量的數據進行批量的更新的話

因為在客戶端的更新能力還是有限的,所以就要到服務端上來完成了,也就是用雲函數來完成了(其實這個方法我們已經寫好了,也就是雲函數update了

else{
                wx.cloud.callFunction({
                  // name也就是我們要修改的數據庫的名字,data就是在雲函數中
                  // 想要的參數了
                  name : 'updata',
                  data : {
                    collection : 'message',
                    where :{
                        userId : this.data.detail._id
                    },
                    data : {
                      
                    }
                  }
                })
              }

注意了,在調用雲函數的時候,前面的data和後面的data是不一樣的,錢買你的是給雲函數的參數,但是後面的是我們要修改的數據了

在要修改list的數據到時候,就涉及了要對數組進行添加,也就是push操作了,其實在數據可以中也內置了一些的方法,commend.push等等

 

注意:在detail.js文件中,如果找到了這個數據流,但是沒有申請的話,就是進行更新,在更新的時候用到了update雲更新函數,

給這個雲函數傳入的data中

  data : `{list : _.unshift(' ${app.userInfo._id} ')}`

注意:最外面那層 並不是 單引號,而是 鍵盤 Esc下面的那個標點

 

三、添加好友功能之監聽message消息

在數據庫加入了一個 帶有list和userid的數據,所以在userid這個人登陸小程序之後,就應該可以看到有沒有人給他發送消息了

並且還是要實時的更新,就是這個人在登陸狀態的話,也可以直接收到了,也就是要實現實時的監聽數據庫中list的實時變化了

 

在開發者文檔中:雲開發-》實時數據的推送:

https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/database/realtime.html

(它的意思就是我們可以監聽到數據庫發送的變化

 

 可以直接查看demo

const db = wx.cloud.database()
const watcher = db.collection('todos')
  // 按 progress 降序
  .orderBy('progress', 'desc')
  // 取按 orderBy 排序之後的前 10 個
  .limit(10)
  .where({
    team: 'our dev team'
  })
  .watch({
    onChange: function(snapshot) {
      console.log('docs\'s changed events', snapshot.docChanges)
      console.log('query result snapshot after the event', snapshot.docs)
      console.log('is init data', snapshot.type === 'init')
    },
    onError: function(err) {
      console.error('the watch closed because of error', err)
    }
  })
// ...
// 等到需要關閉監聽的時候調用 close() 方法
watcher.close()

我們可以在user頁面中進行檢測即可,也就是在登陸之後進行檢測了

我們創建了一個方法 getMessage()。只要用戶登陸了之後就可以進行觸發了,在onReady裏面的登陸成功代碼之後即可了

也就是在數據庫定位到uuseid是這個用戶的數據之後,得到了之後就可以用watch方法來進行監聽了

 getMessage(){
    db.collection('message').where({
      userId : app.userInfo._id
    }).watch({
      onChange: function (snapshot) {
     console.log(snapshot);
      }
    });
  }

這個onChange就是進行監聽的函數了,我們在遇到陌生的一定要傳入參數的函數的時候,最好是把這個參數用console.log打印出來看看我們的想要的數據在哪個位置裏面的

注意了:如果是按照上面這樣的話,是會報錯的,以為缺少了錯誤返回的  onError函數的,示例的demo裏面的格式是怎麼樣的,最好就用怎麼樣的

不然可能都是會報錯的

 

但是會發現,我們沒拿到有用的數據

 

 就可以用多賬號來調試一下了

(這裏用的多賬號最好還是用真實的賬號把,因為虛擬的出現的問題挺大的)

(然後還要設置給message權限是第一個,允許全部人看的那種,才可以看到在別的賬號上的加好友信息的

在別的賬號上面的話就可以看到打印的信息了,可以看到我們得到的消息其實是挺亂的,所以最好用判斷來搞一下

 

 測試之後會發現,得到的 snapshot 數據中有一個 docChanges 數組的,只要有申請,就會有显示了

所以我們可以通過對數組的長度進行一個判斷

然後再對這個list進行判斷,通過長度來進行判斷,如果有長度的話說明就有消息了

有消息的話就要給用戶一個提示,就是在下面的tabbar中的消息圖標右上角添加一個紅色的小點

===其實這個功能在微信小程序中其實就已經幫我們設計好了

微信文檔-》API-》界面-》tabbar-》wx.showTabBarRedDot

https://developers.weixin.qq.com/miniprogram/dev/api/ui/tab-bar/wx.showTabBarRedDot.html

它需要定義一個index屬性,來指定放紅點的是tabbar中的哪一項的(它是從0開始的,所以我們設置為2即可了)

也就是說這個用戶拿到了這個list之後,通過這個list的長度來判斷有沒有消息,然後設置紅點提示,並且還要把這個得到的list用到消息頁面中去的

所以就涉及到了,怎麼把這個得到的list共享到消息中去,這個和之前的userInfo是類似的,點開全局的app.js

 this.userMessage = []

這裏創立的是一個數組來的,不是對象了

然後在user.js裏面,判斷這個得到的list的長度,設置tabbar上面的小紅心,然後把得到的list賦值給全局的userMessage

但是如果檢測到這個list是空的話,就要把在tabbar上面的小紅心取消掉了

**然後還要讓我們全局的userMessage等於一個空的數組即可了

這樣,這個監聽的函數就完成了:

 getMessage(){
    db.collection('message').where({
      userId : app.userInfo._id
    }).watch({
      onChange: function (snapshot) {
    //  console.log(snapshot)
        if( snapshot.docChanges.length){
          //這裏就可以直接拿到message裏面所對應的消息列表了
          let list = snapshow.docChanges[0].doc.list;
          if( list.length ){
            wx.showTabBarRedDot({
              index: 2,
            });
            app.userMessage = list;
          }
          else{
            wx.hideTabBarRedDot({
              index: 2,
            })
            app.userMessage = [];
          }
        }
      },
      onError: function (err) {
        console.error('the watch closed because of error', err)
      }
    });
  }

 

 然後因為watch是實時監聽的,我們在數據庫裏面把給的信息刪掉的話

 

 這個紅點也就會消失了

 這就是因為正在實時的監聽着

四、下面搞的就是如何把共享的userMessage在消息頁面中渲染出來

===消息頁面和removeList組件布局

首先 切換編譯模式到消息頁面中,先做布局

<view class="message">
  <view>
    <text>暫無消息:</text>
  </view> 
  <view>
    <text>消息列表:</text>
    <view>第一條消息</view>
    <view>第二條消息</view>
  </view>

</view>

  之後就是先判斷有沒有消息。所以就要在js看i嗎添加一個東西,這裏添加的是一個userMessage數組,就是用來接收我們的那個全局的list的,

如果這個數組是空的話,說明就是沒有消息了,反之,所以就可以通過這個來進行判斷了

 

**之後就要測試一下message這個頁面裏面文件的生命周期了

這就是為了測試,在tabbar中的生命周期是怎麼樣的

在message.js裏面

  onReady: function () {
    console.log(1)
  },

  /**
   * 生命周期函數--監聽頁面显示
   */
  onShow: function () {
    console.log(2)
  },

 

 

在點擊了下main的tabbar的圖標之後,

打印的結果:

 

 但是當我們幾點了個人頁面之後,再點擊消息頁面的時候,只打印了2

也就是說在tabbar裏面的onreay並不會再次的觸發(但是普通頁面的onreay是會被再次觸發的),但是他也是會觸發onshow的

所以基於這個就可以在onshow中添加東西,來監聽現在的消息變化情況了

因為如果想要進入消息頁面的話,就得先登陸,所以這力又要一個判斷了,如果沒登陸得話,就讓他跳轉到登陸頁面去的

(這個功能就和我們的detail裏面很像的,就可以直接COPY過來了)

const app = getApp()
Page({

  /**
   * 頁面的初始數據
   */
  data: {
    userMessage : [],
    logged : false
  },

  /**
   * 生命周期函數--監聽頁面加載
   */
  onLoad: function (options) {

  },

  /**
   * 生命周期函數--監聽頁面初次渲染完成
   */
  onReady: function () {
    // console.log(1)
  },

  /**
   * 生命周期函數--監聽頁面显示
   */
  onShow: function () {
    // console.log(2)
    if( app.userInfo._id ){
      this.setData({
        logged : true,
        userMessage : app.userMessage
      });
    }else{
      wx.showToast({
        title: '請先登陸',
        duration: 2000,
        // 然後不要讓它显示圖標
        icon: 'none',
        success: () => {
          // 如果成功的話就直接跳轉到我的頁面去
          // 但是注意了這裏不能用 navigator to,因為它主要是跳轉
          // 普通的頁面,而這裏“我的頁面”其實是同tabbar來進行配置的
          setTimeout(() => {
            wx.switchTab({
              url: '/pages/user/user',
            })
          }, 2000);
        }
      })
    }
  },

  /**
   * 生命周期函數--監聽頁面隱藏
   */
  onHide: function () {

  },

  /**
   * 生命周期函數--監聽頁面卸載
   */
  onUnload: function () {

  },

  /**
   * 頁面相關事件處理函數--監聽用戶下拉動作
   */
  onPullDownRefresh: function () {

  },

  /**
   * 頁面上拉觸底事件的處理函數
   */
  onReachBottom: function () {

  },

  /**
   * 用戶點擊右上角分享
   */
  onShareAppMessage: function () {

  }
})

message.js

雖然我們吧list引入進來了,下面就是吧這個list显示出來了

 

 我們用虛擬賬號,給我的主號提交了兩個申請來進行測試了

 之後在message.wxml中對我們的信息進行打印:

<!--miniprogram/pages/message/message.wxml-->
<view class="message" wx:if="{{ logged }}">
  <view wx:if="{{ !userMessage.length }}">
    <text class="message-text">暫無消息:</text>
  </view> 
  <view wx:else>
    <text class="message-text">消息列表:</text>
    <view wx:for="{{ userMessage }}" wx:key="{{index}}">{{item}}</view>
  </view>
</view>

 

 我們還要進行優化,就是可以得到一個列表,有頭像有昵稱,等信息的,別還要有刪除的功能的

 

為了練習一下組件的功能,雖然這個刪除的功能可以直接在這個文件裏面寫,但是我們還是吧這個刪除變成一個組件的形式l

 

 新建一個removeList的刪除組件,然後就是找到message的JSON文件引入這個組件

這個組件其實是可以拖拽的?

在文檔-》組件-》視圖容器 movable-area和movable-view相互配合的

demo:

<movable-area>
        <movable-view x="{{x}}" y="{{y}}" direction="all">text</movable-view>
 </movable-area>

我們設置的結構和樣式:

<!--components/removeList/removeList.wxml-->
<movable-area class="area">
     <movable-view class="view">小喵喵</movable-view>
 </movable-area>
/* components/removeList/removeList.wxss */
.area{width:800rpx; height: 150rpx; margin: 20rpx ; 
position: relative;background: blue;}
.view{width:630rpx; height:100%;  background: red;position: absolute;left:150rpx;top:0;
line-height: 150rpx;text-indent: 10rpx;}

效果;

但是這個時候還是不能進行拖拽的,其中的direction定義的就是拖拽的方向

 添加了這個屬性之後:

<!--components/removeList/removeList.wxml-->
<movable-area class="area">
     <movable-view direction="horizontal" class="view">小喵喵</movable-view>
 </movable-area>

就可以進行拖拽了

 

 注意;下面設置的z-Index是在設置級別,z-index大的會覆蓋在小的上面的

<!--components/removeList/removeList.wxml-->
<movable-area class="area">
     <movable-view direction="horizontal" class="view">小喵喵</movable-view>
     <image src="" />
     <view class="delete">刪除</view>
 </movable-area>
/* components/removeList/removeList.wxss */
.area{width:800rpx; height: 150rpx; margin: 20rpx ; 
position: relative;border-bottom: 1px #cdcdcd dashed}
.view{width:630rpx; height:100%; position: absolute;left:150rpx;top:0;
line-height: 150rpx;text-indent: 10rpx;z-index: 2;}
image{
  width: 100rpx;
  height: 100rpx;
  border-radius: 50%;
  position: absolute;
  left: 0;
  top: 0;
  z-index: 1;
}
.delete{
  width: 200rpx;
  height: 150rpx;
  background: red;
  color: white;
  position: absolute;
  right: 0;
  top: 0;
  z-index: 1;
}

但是得到的效果是:

 

 會發現不管怎麼拖拉,都擋不住後面的刪除–原因就是class==view這塊沒加背景色,雖然層級夠了

給他在wxss裏面添加一個 background:white即可了

刪除那個文字要居中的話,

.delete{
  width: 170rpx;
  height: 100rpx;
  background: red;
  color: white;
  position: absolute;
  right: 0;
  top: 0;
  z-index: 1;
  line-height: 100rpx;
}

效果圖:

 

 

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

【其他文章推薦】

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

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

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

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

01MySQL內核分析-The Skeleton of the Server Code

摘要

這個官方文檔一段對MySQL內核分析的一個嚮導。是對MySQL一條insert語句寫入到MySQL數據庫的分析。
但是,對於MySQL 5.7版本來說,基本上都是寫入到innodb引擎。但也還是有借鑒意義,大的框架沒有太大變化。
後面的文檔,會通過mysqld –debug 和gdb等工具,通過分析mysqld.trace來分析insert語句在MySQL 5.7中怎麼寫入數據庫。

官方文檔給出的一段結構,如下:

/sql/mysqld.cc
/sql/sql_parse.cc
/sql/sql_prepare.cc
/sql/sql_insert.cc
/sql/ha_myisam.cc
/myisam/mi_write.c

上述梳理一個過程,是說從客戶段執行一條簡單的insert語句,然後到達MySQL服務器端,並通過MyISAM存儲層。寫入到MyISAM文件的過程。

由於,我們現在的主流都是InnoDB存儲引擎,所以我們分析的寫入到存儲層應該是InnoDB的源代碼。但是上述的一個框架也有借鑒意義。雖然,走的是InnoDB存儲引擎插入數據,但是也還是需要通過SQL層的ha_*這樣的接口進行接入。

正題開始!!!!!!!!!!!!!!!!!!!!!!!

第一步,進入MySQL大門的地方。夢開始的地方。眾所周知,C語言都是需要main方法作為主入口。而MySQL的主入口如下:

代碼位置 /sql/mysqld.cc

  int main(int argc, char **argv)
  {
    _cust_check_startup();
    (void) thr_setconcurrency(concurrency);
    init_ssl();
    server_init();                             // 'bind' + 'listen'
    init_server_components();
    start_signal_handler();
    acl_init((THD *)0, opt_noacl);
    init_slave();
    create_shutdown_thread();
    create_maintenance_thread();
    handle_connections_sockets(0);             // !  這裏也代表着我們進入下一個門的地方
    DBUG_PRINT("quit",("Exiting main thread"));
    exit(0);
  }

這裏可以看到很多的init_*或者server_init()。通過名字我們可以猜測出,這裏做了很多初始化的工作。例如:啟動過程中一些初始化的檢查和MySQL配置變量的加載和一些組件的初始化等。

這裏重要的函數是handle_connections_sockets

繼續跟蹤 /sql/mysqld.cc

 handle_connections_sockets (arg __attribute__((unused))
  {
     if (ip_sock != INVALID_SOCKET)
     {
       FD_SET(ip_sock,&clientFDs);
       DBUG_PRINT("general",("Waiting for connections."));
       while (!abort_loop)
       {
         new_sock = accept(sock, my_reinterpret_cast(struct sockaddr*)
           (&cAddr),             &length);
         thd= new THD;
         if (sock == unix_sock)
         thd->host=(char*) localhost;
         create_new_thread(thd);            // !
         }

從簡易的思維,忽視其他的判斷語句。可以看到這裏做的是典型的client/server架構。服務器有一個主線程,它總是偵聽來自新客戶機的請求。一旦它接收到這樣的請求,它將分配資源。特別是,主線程將生成一個新線程來處理連接。然後主服務器將循環並偵聽新連接——但我們將保留它並跟蹤新線程。

這裏創建新線程的方法是:create_new_thread(thd);

繼續跟蹤 /sql/mysqld.cc

  create_new_thread(THD *thd)
  {
    pthread_mutex_lock(&LOCK_thread_count);
    pthread_create(&thd->real_id,&connection_attrib,
        handle_one_connection,                        // !
        (void*) thd));
    pthread_mutex_unlock(&LOCK_thread_count);
  }

可以看到這裏獲得一個新線程加入一個互斥鎖,避免衝突。

繼續跟蹤 /sql/mysqld.cc

handle_one_connection(THD *thd)
  {
    init_sql_alloc(&thd->mem_root, MEM_ROOT_BLOCK_SIZE, MEM_ROOT_PREALLOC);
    while (!net->error && net->vio != 0 && !thd->killed)
    {
      if (do_command(thd))            // !
        break;
    }
    close_connection(net);
    end_thread(thd,1);
    packet=(char*) net->read_pos;

從這裏開始,我們即將脫離mysqld.cc文件,因為我們獲得了thread,且分配一小段內存資源,給與我們來處理我們的SQL語句了。

我們會走向何方呢,可以開始觀察do_command(thd)方法。

繼續跟蹤/sql/sql_parse.cc

bool do_command(THD *thd)
{
  net_new_transaction(net);
  packet_length=my_net_read(net);
  packet=(char*) net->read_pos;
  command = (enum enum_server_command) (uchar) packet[0];
  dispatch_command(command,thd, packet+1, (uint) packet_length);
// !
}

其中從這裏可以看到,do_command(THD *thd)把它串聯起來的是一個叫作THD的東西,也就是thread。所以後面的工作和行為,基本都是通過thread進行牽線搭橋的。

my_net_read函數位於另一個名為net_servlet .cc的文件中。該函數從客戶端獲取一個包,解壓縮它,並去除頭部。

一旦完成,我們就得到了一個名為packet的多字節變量,它包含客戶端發送的內容。第一個字節很重要,因為它包含標識消息類型的代碼。

說明了packet第一個字節很重要。debug也有證據進行一個佐證。

packet_header: Memory: 0x7f7fc000a4b0  Bytes: (4)
21 00 00 00

然後把packet第一個字節和餘下的部分傳遞給dispatch_command

繼續跟蹤/sql/sql_parse.cc

bool dispatch_command(enum enum_server_command command, THD *thd,
       char* packet, uint packet_length)
{
  switch (command) {
    case COM_INIT_DB:          ...
    case COM_REGISTER_SLAVE:   ...
    case COM_TABLE_DUMP:       ...
    case COM_CHANGE_USER:      ...
    case COM_EXECUTE:
         mysql_stmt_execute(thd,packet);
    case COM_LONG_DATA:        ...
    case COM_PREPARE:
         mysql_stmt_prepare(thd, packet, packet_length);   // !
    /* and so on for 18 other cases */
    default:
     send_error(thd, ER_UNKNOWN_COM_ERROR);
     break;
    }

這裏sql_parser .cc中有一個非常大的switch語句

switch語句中代碼有:code for prepare, close statement, query, quit, create database, drop database, dump binary log, refresh, statistics, get process info, kill process, sleep, connect, and several minor commands

除了COM_EXECUTE和COM_PREPARE兩種情況外,我們刪除了所有情況下的代碼細節。

可以看到

  • COM_EXECUTE 會調用mysql_stmt_execute(thd,packet);

  • COM_PREPARE 會調用mysql_stmt_prepare(thd, packet, packet_length);

這裏就像一个中轉站一般,看我們去向什麼地方。這裏去的門是:COM_PREPARE:mysql_stmt_prepare

跟蹤 /sql/sql_prepare.cc

下面是一段prepare的註釋

"Prepare:
Parse the query
Allocate a new statement, keep it in 'thd->prepared statements' pool
Return to client the total number of parameters and result-set
metadata information (if any)"

繼續回到主線COM_EXECUTE

跟蹤/sql/sql_parse.cc

  bool dispatch_command(enum enum_server_command command, THD *thd,
       char* packet, uint packet_length)
  {
  switch (command) {
    case COM_INIT_DB:          ...
    case COM_REGISTER_SLAVE:   ...
    case COM_TABLE_DUMP:       ...
    case COM_CHANGE_USER:      ...
    case COM_EXECUTE:
         mysql_stmt_execute(thd,packet);                   // !
    case COM_LONG_DATA:        ...
    case COM_PREPARE:
         mysql_stmt_prepare(thd, packet, packet_length);
    /* and so on for 18 other cases */
    default:
     send_error(thd, ER_UNKNOWN_COM_ERROR);
     break;
    }

現在“COM_EXECUTE 中的mysql_stmt_execute`是我們關注的重點,我們來看看

跟蹤/sql/sql_prepare.cc代碼

  void mysql_stmt_execute(THD *thd, char *packet)
  {
    if (!(stmt=find_prepared_statement(thd, stmt_id, "execute")))
    {
      send_error(thd);
      DBUG_VOID_RETURN;
    }
    init_stmt_execute(stmt);
    mysql_execute_command(thd);           // !
  }

這裏做一個判斷,看是否是execute,然後初始化語句,並開始執行mysql_execute_command(thd);可以看到,是通過thread來調用動作。

跟蹤/sql/sql_parse.cc代碼

  void mysql_execute_command(THD *thd)
       switch (lex->sql_command) {
       case SQLCOM_SELECT: ...
       case SQLCOM_SHOW_ERRORS: ...
       case SQLCOM_CREATE_TABLE: ...
       case SQLCOM_UPDATE: ...
       case SQLCOM_INSERT: ...                   // !
       case SQLCOM_DELETE: ...
       case SQLCOM_DROP_TABLE: ...
       }

lex 解析sql語句。然後進入SQLCOM_INSERT。

跟蹤/sql/sql_parse.cc代碼

case SQLCOM_INSERT:
{
  my_bool update=(lex->value_list.elements ? UPDATE_ACL : 0);
  ulong privilege= (lex->duplicates == DUP_REPLACE ?
                    INSERT_ACL | DELETE_ACL : INSERT_ACL | update);
  if (check_access(thd,privilege,tables->db,&tables->grant.privilege))
    goto error;
  if (grant_option && check_grant(thd,privilege,tables))
    goto error;
  if (select_lex->item_list.elements != lex->value_list.elements)
  {
    send_error(thd,ER_WRONG_VALUE_COUNT);
    DBUG_VOID_RETURN;
  }
  res = mysql_insert(thd,tables,lex->field_list,lex->many_values,
                     select_lex->item_list, lex->value_list,
                     (update ? DUP_UPDATE : lex->duplicates));
// !
  if (thd->net.report_error)
    res= -1;
  break;
}

對於插入數據,我們要做的第一件事情是:檢查用戶是否具有對錶進行插入的適當特權,服務器通過調用check_access和check_grant函數在這裏進行檢查。

有了權限才可以做【插入】動作。

我們可以導航 /sql 目錄,如下:

Program Name          SQL statement type
------------          ------------------
sql_delete.cc         DELETE
sql_do.cc             DO
sql_handler.cc        HANDLER
sql_help.cc           HELP
sql_insert.cc         INSERT            // !
sql_load.cc           LOAD
sql_rename.cc         RENAME
sql_select.cc         SELECT
sql_show.cc           SHOW
sql_update.cc         UPDATE

sql_insert.cc是具體執行插入的操作。

上面的mysql_insert() 的方法具體實現,在sql_insert.cc文件中。

跟蹤 /sql/sql_insert.cc代碼

 int mysql_insert(THD *thd,TABLE_LIST *table_list, List<Item> &fields,
        List<List_item> &values_list,enum_duplicates duplic)
  {
    table = open_ltable(thd,table_list,lock_type);
    if (check_insert_fields(thd,table,fields,*values,1) ||
      setup_tables(table_list) ||
      setup_fields(thd,table_list,*values,0,0,0))
      goto abort;
    fill_record(table->field,*values);
    error=write_record(table,&info);                 // !
    query_cache_invalidate3(thd, table_list, 1);
    if (transactional_table)
      error=ha_autocommit_or_rollback(thd,error);
    query_cache_invalidate3(thd, table_list, 1);
    mysql_unlock_tables(thd, thd->lock);
    }

這裏就要開始,打開一張表。然後各種檢查,看插入表的字段是否有問題。不行就abort。

然後,開始填充記錄數據。最終調用write_record 寫記錄的方法。

由於write_record 會對應不同的存儲引擎,所以這裡有分支的。我這裏講解兩種

繼續跟蹤/sql/sql_insert.cc

  int write_record(TABLE *table,COPY_INFO *info)
  {
    table->file->write_row(table->record[0];           // !
  }

終於,要寫文件了。調用那個存儲引擎呢?看handler.h

  /* The handler for a table type.
     Will be included in the TABLE structure */

  handler(TABLE *table_arg) :
table(table_arg),active_index(MAX_REF_PARTS),
    ref(0),ref_length(sizeof(my_off_t)),
block_size(0),records(0),deleted(0),
    data_file_length(0), max_data_file_length(0),
index_file_length(0),
    delete_length(0), auto_increment_value(0), raid_type(0),
    key_used_on_scan(MAX_KEY),
    create_time(0), check_time(0), update_time(0), mean_rec_length(0),
    ft_handler(0)
    {}
  ...
  virtual int write_row(byte * buf)=0;

寫入之MyISAM的代碼路徑

官方文檔默認調用的是 ha_myisam::write_row

代碼 /sql/ha_myisam.cc

如下:

int ha_myisam::write_row(byte * buf)
{
  statistic_increment(ha_write_count,&LOCK_status);
   /* If we have a timestamp column, update it to the current time */
   if (table->time_stamp)
    update_timestamp(buf+table->time_stamp-1);
   /*
  If we have an auto_increment column and we are writing a changed row
    or a new row, then update the auto_increment value in the record.
  */
  if (table->next_number_field && buf == table->record[0])
    update_auto_increment();
  return mi_write(file,buf);     // !
}

這些以字母ha開頭的程序是處理程序的接口,而這個程序是myisam處理程序的接口。我們這裏就開始調用MyISAM了。

可以看到這裏調用了mi_write(file,buf);

跟蹤/myisam/mi_write.c

int mi_write(MI_INFO *info, byte *record)
{
  _mi_readinfo(info,F_WRLCK,1);
  _mi_mark_file_changed(info);
  /* Calculate and check all unique constraints */
  for (i=0 ; i < share->state.header.uniques ; i++)
  {
    mi_check_unique(info,share->uniqueinfo+i,record,
      mi_unique_hash(share->uniqueinfo+i,record),
      HA_OFFSET_ERROR);
  }

  ... to be continued in next snippet

這裡有很多唯一性的校驗,繼續看下面

 ... continued from previous snippet

  /* Write all keys to indextree */
  for (i=0 ; i < share->base.keys ; i++)
  {
    share->keyinfo[i].ck_insert(info,i,buff,
      _mi_make_key(info,i,buff,record,filepos)
  }
  (*share->write_record)(info,record);
  if (share->base.auto_key)
    update_auto_increment(info,record);
}

這裏就是我們寫入到文件的地方。至此,MySQL的插入操作結束。

路徑為:

main in /sql/mysqld.cc
handle_connections_sockets in /sql/mysqld.cc
create_new_thread in /sql/mysqld.cc
handle_one_connection in /sql/sql_parse.cc
do_command in /sql/sql_parse.cc
dispatch_command in /sql/sql_parse.cc
mysql_stmt_execute in /sql/sql_prepare.cc
mysql_execute_command in /sql/sql_parse.cc
mysql_insert in /sql/mysql_insert.cc
write_record in /sql/mysql_insert.cc
ha_myisam::write_row in /sql/ha_myisam.cc
mi_write in /myisam/mi_write.c

1.進入主函數入口

2.建立socket connection的請求

3.創建一個新的線程

4.處理線程,分配內存資源

5.do_command,是獲取packet第一字節,看做什麼操作,並接受餘下字節。

6.dispatch_command,分發操作,這裏分發的是insert。

7.mysql_stmt_execute,檢查是否為execute,初始化,準備做execute動作。

8.mysql_execute_command ,lex解析SQL語句,進入到SQLCOM_INSERT

9.mysql_insert ,開始做插入操作。調用write_record

10.write_record,準備寫入,看調用哪個存儲引擎,寫入前期準備工作

11.ha_myisam::write_row,ha_myisam進行插入寫入。

12.mi_write,最後做寫入操作。

文獻參考:https://dev.mysql.com/doc/internals/en/guided-tour-skeleton.html

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

【其他文章推薦】

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

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

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

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

※超省錢租車方案

.Net Core Configuration源碼探究

前言

    上篇文章我們演示了為Configuration添加Etcd數據源,並且了解到為Configuration擴展自定義數據源還是非常簡單的,核心就是把數據源的數據按照一定的規則讀取到指定的字典里,這些都得益於微軟設計的合理性和便捷性。本篇文章我們將一起探究Configuration源碼,去了解Configuration到底是如何工作的。

ConfigurationBuilder

    相信使用了.Net Core或者看過.Net Core源碼的同學都非常清楚,.Net Core使用了大量的Builder模式許多核心操作都是是用來了Builder模式,微軟在.Net Core使用了許多在傳統.Net框架上並未使用的設計模式,這也使得.Net Core使用更方便,代碼更合理。Configuration作為.Net Core的核心功能當然也不例外。
    其實並沒有Configuration這個類,這隻是我們對配置模塊的代名詞。其核心是IConfiguration接口,IConfiguration又是由IConfigurationBuilder構建出來的,我們找到IConfigurationBuilder源碼大致定義如下

public interface IConfigurationBuilder
{
    IDictionary<string, object> Properties { get; }

    IList<IConfigurationSource> Sources { get; }

    IConfigurationBuilder Add(IConfigurationSource source);

    IConfigurationRoot Build();
}

Add方法我們上篇文章曾使用過,就是為ConfigurationBuilder添加ConfigurationSource數據源,添加的數據源被存放在Sources這個屬性里。當我們要使用IConfiguration的時候通過Build的方法得到IConfiguration實例,IConfigurationRoot接口是繼承自IConfiguration接口的,待會我們會探究這個接口。
我們找到IConfigurationBuilder的默認實現類ConfigurationBuilder大致代碼實現如下

public class ConfigurationBuilder : IConfigurationBuilder
{
    /// <summary>
    /// 添加的數據源被存放到了這裏
    /// </summary>
    public IList<IConfigurationSource> Sources { get; } = new List<IConfigurationSource>();

    public IDictionary<string, object> Properties { get; } = new Dictionary<string, object>();

    /// <summary>
    /// 添加IConfigurationSource數據源
    /// </summary>
    /// <returns></returns>
    public IConfigurationBuilder Add(IConfigurationSource source)
    {
        if (source == null)
        {
            throw new ArgumentNullException(nameof(source));
        }
        Sources.Add(source);
        return this;
    }

    public IConfigurationRoot Build()
    {
        //獲取所有添加的IConfigurationSource里的IConfigurationProvider
        var providers = new List<IConfigurationProvider>();
        foreach (var source in Sources)
        {
            var provider = source.Build(this);
            providers.Add(provider);
        }
        //用providers去實例化ConfigurationRoot
        return new ConfigurationRoot(providers);
    }
}

這個類的定義非常的簡單,相信大家都能看明白。其實整個IConfigurationBuilder的工作流程都非常簡單就是將IConfigurationSource添加到Sources中,然後通過Sources里的Provider去構建IConfigurationRoot。

Configuration

通過上面我們了解到通過ConfigurationBuilder構建出來的並非是直接實現IConfiguration的實現類而是另一個接口IConfigurationRoot

ConfigurationRoot

通過源代碼我們可以知道IConfigurationRoot是繼承自IConfiguration,具體定義關係如下

public interface IConfigurationRoot : IConfiguration
{
    /// <summary>
    /// 強制刷新數據
    /// </summary>
    /// <returns></returns>
    void Reload();

    IEnumerable<IConfigurationProvider> Providers { get; }
}

public interface IConfiguration
{
    string this[string key] { get; set; }

    /// <summary>
    /// 獲取指定名稱子數據節點
    /// </summary>
    /// <returns></returns>
    IConfigurationSection GetSection(string key);

    /// <summary>
    /// 獲取所有子數據節點
    /// </summary>
    /// <returns></returns>
    IEnumerable<IConfigurationSection> GetChildren();
    
    /// <summary>
    /// 獲取IChangeToken用於當數據源有數據變化時,通知外部使用者
    /// </summary>
    /// <returns></returns>
    IChangeToken GetReloadToken();
}

接下來我們查看IConfigurationRoot實現類ConfigurationRoot的大致實現,代碼有刪減

public class ConfigurationRoot : IConfigurationRoot, IDisposable
{
    private readonly IList<IIConfigurationProvider> _providers;
    private readonly IList<IDisposable> _changeTokenRegistrations;
    private ConfigurationReloadToken _changeToken = new ConfigurationReloadToken();

    public ConfigurationRoot(IList<IConfigurationProvider> providers)
    {
        _providers = providers;
        _changeTokenRegistrations = new List<IDisposable>(providers.Count);
        //通過便利的方式調用ConfigurationProvider的Load方法,將數據加載到每個ConfigurationProvider的字典里
        foreach (var p in providers)
        {
            p.Load();
            //監聽每個ConfigurationProvider的ReloadToken實現如果數據源發生變化去刷新Token通知外部發生變化
            _changeTokenRegistrations.Add(ChangeToken.OnChange(() => p.GetReloadToken(), () => RaiseChanged()));
        }
    }

    //// <summary>
    /// 讀取或設置配置相關信息
    /// </summary>
    public string this[string key]
    {
        get
        {
            //通過這個我們可以了解到讀取的順序取決於註冊Source的順序,採用的是後來者居上的方式
            //后註冊的會先被讀取到,如果讀取到直接return
            for (var i = _providers.Count - 1; i >= 0; i--)
            {
                var provider = _providers[i];
                if (provider.TryGet(key, out var value))
                {
                    return value;
                }
            }
            return null;
        }
        set
        {
            //這裏的設置只是把值放到內存中去,並不會持久化到相關數據源
            foreach (var provider in _providers)
            {
                provider.Set(key, value);
            }
        }
    }

    public IEnumerable<IConfigurationSection> GetChildren() => this.GetChildrenImplementation(null);

    public IChangeToken GetReloadToken() => _changeToken;

    public IConfigurationSection GetSection(string key)
        => new ConfigurationSection(this, key);

    //// <summary>
    /// 手動調用該方法也可以實現強制刷新的效果
    /// </summary>
    public void Reload()
    {
        foreach (var provider in _providers)
        {
            provider.Load();
        }
        RaiseChanged();
    }

    //// <summary>
    /// 強烈推薦不熟悉Interlocked的同學研究一下Interlocked具體用法
    /// </summary>
    private void RaiseChanged()
    {
        var previousToken = Interlocked.Exchange(ref _changeToken, new ConfigurationReloadToken());
        previousToken.OnReload();
    }
}

上面展示了ConfigurationRoot的核心實現其實主要就是兩點

  • 讀取的方式其實是循環匹配註冊進來的每個provider里的數據,是後來者居上的模式,同名key后註冊進來的會先被讀取到,然後直接返回
  • 構造ConfigurationRoot的時候才把數據加載到內存中,而且為註冊進來的每個provider設置監聽回調

ConfigurationSection

其實通過上面的代碼我們會產生一個疑問,獲取子節點數據返回的是另一個接口類型IConfigurationSection,我們來看下具體的定義

public interface IConfigurationSection : IConfiguration
{
    string Key { get; }

    string Path { get; }

    string Value { get; set; }
}

這個接口也是繼承了IConfiguration,這就奇怪了分明只有一套配置IConfiguration,為什麼還要區分IConfigurationRoot和IConfigurationSection呢?其實不難理解因為Configuration可以同時承載許多不同的配置源,而IConfigurationRoot正是表示承載所有配置信息的根節點,而配置又是可以表示層級化的一種結構,在根配置里獲取下來的子節點是可以表示承載一套相關配置的另一套系統,所以單獨使用IConfigurationSection去表示,會顯得結構更清晰,比如我們有如下的json數據格式

{
  "OrderId":"202005202220",
  "Address":"銀河系太陽系火星",
  "Total":666.66,
  "Products":[
    {
      "Id":1,
      "Name":"果子狸",
      "Price":66.6,
      "Detail":{
          "Color":"棕色",
          "Weight":"1000g"
      }
    },
    {
      "Id":2,
      "Name":"蝙蝠",
      "Price":55.5,
      "Detail":{
          "Color":"黑色",
          "Weight":"200g"
      }
    }
  ]
}

我們知道json是一個結構化的存儲結構,其存儲元素分為三種一是簡單類型,二是對象類型,三是集合類型。但是字典是KV結構,並不存在結構化關係,在.Net Corez中配置系統是這麼解決的,比如以上信息存儲到字典中的結構就是這種

Key Value
OrderId 202005202220
Address 銀河系太陽系火星
Products:0:Id 1
Products:0:Name 果子狸
Products:0:Detail:Color 棕色
Products:1:Id 2
Products:1:Name 蝙蝠
Products:1:Detail:Weight 200g

如果我想獲取Products節點下的第一條商品數據直接

IConfigurationSection productSection = configuration.GetSection("Products:0")

類比到這裏的話根配置IConfigurationRoot里存儲了訂單的所有數據,獲取下來的子節點IConfigurationSection表示了訂單里第一個商品的信息,而這個商品也是一個完整的描述商品信息的數據系統,所以這樣可以更清晰的區分Configuration的結構,我們來看一下ConfigurationSection的大致實現

public class ConfigurationSection : IConfigurationSection
{
    private readonly IConfigurationRoot _root;
    private readonly string _path;
    private string _key;

    public ConfigurationSection(IConfigurationRoot root, string path)
    {
        _root = root;
        _path = path;
    }

    public string Path => _path;

    public string Key
    {
        get
        {
            return _key;
        }
    }

    public string Value
    {
        get
        {
            return _root[Path];
        }
        set
        {
            _root[Path] = value;
        }
    }

    public string this[string key]
    {
        get
        {
            //獲取當前Section下的數據其實就是組合了Path和Key
            return _root[ConfigurationPath.Combine(Path, key)];
        }
        set
        {
            _root[ConfigurationPath.Combine(Path, key)] = value;
        }
    }
    
    //獲取當前節點下的某個子節點也是組合當前的Path和子節點的標識Key
    public IConfigurationSection GetSection(string key) => _root.GetSection(ConfigurationPath.Combine(Path, key));
    //獲取當前節點下的所有子節點其實就是在字典里獲取包含當前Path字符串的所有Key
    public IEnumerable<IConfigurationSection> GetChildren() => _root.GetChildrenImplementation(Path);
    public IChangeToken GetReloadToken() => _root.GetReloadToken();
}

這裏我們可以看到既然有Key可以獲取字典里對應的Value了,為何還需要Path?通過ConfigurationRoot里的代碼我們可以知道Path的初始值其實就是獲取ConfigurationSection的Key,說白了其實就是如何獲取到當前IConfigurationSection的路徑。比如

//當前productSection的Path是 Products:0
IConfigurationSection productSection = configuration.GetSection("Products:0");
//當前productDetailSection的Path是 Products:0:Detail
IConfigurationSection productDetailSection = productSection.GetSection("Detail");
//獲取到pColor的全路徑就是 Products:0:Detail:Color
string pColor = productDetailSection["Color"];

而獲取Section所有子節點
GetChildrenImplementation來自於IConfigurationRoot的擴展方法

internal static class InternalConfigurationRootExtensions
{
    //// <summary>
    /// 其實就是在數據源字典里獲取Key包含給定Path的所有值
    /// </summary>
    internal static IEnumerable<IConfigurationSection> GetChildrenImplementation(this IConfigurationRoot root, string path)
    {
        return root.Providers
            .Aggregate(Enumerable.Empty<string>(),
                (seed, source) => source.GetChildKeys(seed, path))
            .Distinct(StringComparer.OrdinalIgnoreCase)
            .Select(key => root.GetSection(path == null ? key : ConfigurationPath.Combine(path, key)));
    }
}

相信講到這裏,大家對ConfigurationSection或者是對Configuration整體的思路有一定的了解,細節上的設計確實不少。但是整體實現思路還是比較清晰的。關於Configuration還有一個比較重要的擴展方法就是將配置綁定到具體POCO的擴展方法,該方法承載在ConfigurationBinder擴展類了,由於實現比較複雜,也不是本篇文章的重點,有興趣的同學可以自行查閱,這裏就不做探究了。

總結

    通過以上部分的講解,其實我們可以大概的將Configuration配置相關總結為兩大核心抽象接口IConfigurationBuilder,IConfiguration,整體結構關係可大致表示成如下關係

    配置相關的整體實現思路就是IConfigurationSource作為一種特定類型的數據源,它提供了提供當前數據源的提供者ConfigurationProvider,Provider負責將數據源的數據按照一定的規則放入到字典里。IConfigurationSource添加到IConfigurationBuilder的容器中,後者使用Provide構建出整個程序的根配置容器IConfigurationRoot。通過獲取IConfigurationRoot子節點得到IConfigurationSection負責維護子節點容器相關。這二者都繼承自IConfiguration,然後通過他們就可以獲取到整個配置體系的數據數據操作了。

    以上講解都是本人通過實踐和閱讀源碼得出的結論,可能會存在一定的偏差或理解上的誤區,但是我還是想把我的理解分享給大家,希望大家能多多包涵。如果有大家有不同的見解或者更深的理解,可以在評論區多多留言。

歡迎掃碼關注我的公眾號 本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

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

用一個圖書庫實例搞懂二分搜索樹的底層原理

目錄

  • 一、背景
  • 二、概念
    • 1、定義
    • 2、 動畫示例
  • 三、圖書庫實例
    • 3.1、項目需求
    • 3.2、代碼結構
    • 3.3、圖書類
    • 3.4、二分搜索樹的底層實現
    • 3.5、圖書庫的構建
  • 四、深入理解

一、背景

二叉樹是一種常用的數據結構,更是實現眾多算法的一把利器。本文將通過建立一個圖書庫的實例對二叉樹中的常用類型:二分搜索樹(Binary Search Tree)進行底層原理的深入理解。

二、概念

1、定義

1 二分搜索樹是一顆二叉樹
2 二分搜索樹每個節點的左子樹的值都小於該節點的值,每個節點右子樹的值都大於該節點的值
3 任意一個節點的每棵子樹都滿足二分搜索樹的定義

2、 動畫示例

三、圖書庫實例

3.1、項目需求
  • 創建一個圖書類:圖書類中需包含ISBN號,書名,作者,定價,出版社、出版日期等
  • 用二分搜索樹的數據結構創建一個圖書庫,每種圖書需有當前數量
  • 圖書庫需實現添加圖書,遍歷整個圖書庫,及可根據ISBN號進行快速查找
3.2、代碼結構

3.3、圖書類
  • 在圖書類的定義中,重寫compareTo方法:通過比較ISBN(國際標準書號)的大小表示圖書在二叉樹的結點順序。
/**
 - 用二分搜索樹實現圖書庫--圖書類
 -  - @author zhuhuix
 - @date 2020-06-23
 */
public class Books implements Serializable, Comparable {
    // ISBN
    private Long bookId;
    // 作者
    private String author;
    // 分類
    private String category;
    // 書名
    private String bookName;
    // 定價
    private BigDecimal bookPrice;
    // 出版社
    private String bookPublisher;
    // 出版時間
    private LocalDate bookDate;
    // 當前數量
    private Integer bookCount;

    public Books(Long bookId, String bookName, String category, String author, BigDecimal bookPrice, String bookPublisher, LocalDate bookDate, Integer bookCount) {
        this.bookId = bookId;
        this.author = author;
        this.category = category;
        this.bookName = bookName;
        this.bookPrice = bookPrice;
        this.bookPublisher = bookPublisher;
        this.bookDate = bookDate;
        this.bookCount = bookCount;
    }

    public Books(Long bookId){
        this.bookId= bookId;
    }

    // 通過ISBN號進行比較大小
    @Override
    public int compareTo(Object o) {
        if (o instanceof Books) {
            return this.getBookId().compareTo(((Books) o).getBookId());
        } else {
            return -1;
        }
    }

    public Long getBookId() {
        return bookId;
    }


    public Integer getBookCount() {
        return bookCount;
    }

    public void setBookCount(Integer bookCount) {
        this.bookCount += bookCount;
    }

    @Override
    public String toString() {
        return "{" +
                "ISBN=" + bookId +
                ", 書名='" + bookName + '\'' +
                ", 作者='" + author + '\'' +
                ", 分類='" + category + '\'' +
                ", 價格=" + bookPrice +
                ", 出版社='" + bookPublisher + '\'' +
                ", 出版時間=" + bookDate +
                ", 當前數量=" + bookCount +
                '}';
    }
}
3.4、二分搜索樹的底層實現
  • 底層創建內部結點類(class Node):元素,左子樹,右子樹
  • add方法:使用遞歸方法增加結點:
    如果圖書種類不存在,則創建新結點。
    如果圖書種類存在,則對數量進行累加。
  • traverse方法:使用遞歸方法對所有結點進行遍歷
  • search方法:根據ISBN碼查找結點
/**
 * 用二分搜索樹實現圖書庫--二分搜索樹
 *
 * @author zhuhuix
 * @date 2020-06-23
 */
public class BinarySearchTree {

    // 結點
    private Node root;
    // 書的種類
    private int bookSize;
    // 書的總數量
    private int bookCount;

    public BinarySearchTree() {
        this.root = null;
        this.bookSize = 0;
        this.bookCount = 0;
    }

    // 增加元素
    public void add(Books data) {
        this.root = addNode(this.root, data);
    }

    // 用遞歸方法實現結點的添加
    private Node addNode(Node node, Books data) {
        // 遞歸退出條件 書不存在拉加結點,並將結點數量加1
        if (node == null) {
            this.bookSize++;
            this.bookCount += data.getBookCount();
            return new Node(data);
        }

        if (node.data.compareTo(data) < 0) {
            node.right = addNode(node.right, data);
        } else if (node.data.compareTo(data) > 0) {
            node.left = addNode(node.left, data);
        } else if (node.data.compareTo(data) == 0) {
            // 如果結點已存在,則將書的數量累加
            this.bookCount += data.getBookCount();
            node.getData().setBookCount(data.getBookCount());
        }
        return node;
    }

    // 用遞歸方法實現結點前序遍歷
    public void traverse(Node node) {
        if (node == null) {
            return;
        }
        System.out.println(node.getData().toString());
        traverse(node.left);
        traverse(node.right);
    }

    // 用遞歸方法實現通過isbn查找圖書
    public Books search(Long isbn) {
        Node node = nodeSearch(this.root, new Books(isbn));
        if (node != null) {
            return node.getData();
        } else {
            return null;
        }
    }

    private Node nodeSearch(Node node, Books books) {
        if (node == null) {
            return null;
        }
        if (books.compareTo(node.getData()) == 0) {
            return node;
        } else if (books.compareTo(node.getData()) < 0) {
            return nodeSearch(node.left, books);
        } else {
            return nodeSearch(node.right, books);
        }
    }

    public Node getRoot() {
        return root;
    }

    // 返回書的種類數
    public int getBookSize() {
        return bookSize;
    }

    // 返回書的總數量
    public int getBookCount() {
        return bookCount;
    }

    // 私有內部類-樹結點
    private class Node {
        Books data;
        Node left, right;

        Node(Books data) {
            this.data = data;
            this.left = null;
            this.right = null;
        }

        Books getData() {
            return data;
        }
    }
}

3.5、圖書庫的構建
  1. 構建一棵二分搜索樹;
  2. 將京東十大暢銷圖書加入二分搜索樹;
  3. 統計圖書種類及數量,並遍歷輸出;
  4. 加入3種已經進入圖書庫的圖書;
  5. 再次統計圖書種類及數量,並遍歷輸出;
  6. 根據某個ISBN號查找圖書。
/**
 * 用二分搜索樹實現圖書庫
 *
 * @author zhuhuix
 * @date 2020-06-23
 */
public class BookStore {
    public static void main(String[] args) {

        // 構建一棵二分搜索樹
        BinarySearchTree bst = new BinarySearchTree();

        // 將十大暢銷圖書加入二分搜索樹
        bst.add(new Books(9787115428028L,"Python編程 從入門到實踐",
                "編程語言與程序設計","埃里克·馬瑟斯",
                BigDecimal.valueOf(61.40),"人民郵電出版社",
                LocalDate.of(2017,07,01),1));

        bst.add(new Books(9787115525963L,"說服力 工作型PPT該這樣做",
                "辦公軟件","秦陽",
                BigDecimal.valueOf(66.30),"人民郵電出版社",
                LocalDate.of(2020,05,01),1));

        bst.add(new Books(9787569222258L,"零基礎學Python(全彩版)",
                "編程語言與程序設計","明日科技",
                BigDecimal.valueOf(67.00),"吉林大學出版社",
                LocalDate.of(2018,04,01),1));

        bst.add(new Books(9787121388361L,"PS之光:一看就懂的Photoshop攻略(全彩)",
                "圖形圖像/多媒體","馮注龍",
                BigDecimal.valueOf(60.70),"电子工業出版社",
                LocalDate.of(2020,06,01),1));

        bst.add(new Books(9787302423287L,"機器學習",
                "人工智能","周志華",
                BigDecimal.valueOf(64.80),"清華大學出版社",
                LocalDate.of(2016,01,01),1));

        bst.add(new Books(9787111641247L,"深入理解Java虛擬機:JVM高級特性與最佳實踐(第3版)",
                "編程語言與程序設計","周志明",
                BigDecimal.valueOf(106.40),"机械工業出版社",
                LocalDate.of(2019,12,01),1));

        bst.add(new Books(9787115472588L,"鳥哥的Linux私房菜 基礎學習篇 第四版",
                "操作系統","鳥哥",
                BigDecimal.valueOf(93.00),"人民郵電出版社",
                LocalDate.of(2018,10,01),1));

        bst.add(new Books(9787115293800L,"算法(第4版)",
                "編程語言與程序設計","Robert Sedgewick,Kevin Wayne",
                BigDecimal.valueOf(66.30),"人民郵電出版社",
                LocalDate.of(2012,10,01),1));

        bst.add(new Books(9787115537973L,"數學之美 第三版",
                "計算機理論、基礎知識","吳軍",
                BigDecimal.valueOf(54.40),"人民郵電出版社",
                LocalDate.of(2020,05,01),1));

        bst.add(new Books(9787302255659L,"大話數據結構",
                "編程語言與程序設計","程傑",
                BigDecimal.valueOf(47.20),"清華大學出版社",
                LocalDate.of(2011,06,01),1));

        // 遍歷圖書庫
        System.out.println("圖書庫新建:");
        System.out.println("書的種類數:"+bst.getBookSize());
        System.out.println("書的總數量:"+bst.getBookCount());
        bst.traverse(bst.getRoot());

        // 再次增加相同的書
        bst.add(new Books(9787302255659L,"大話數據結構",
                "編程語言與程序設計","程傑",
                BigDecimal.valueOf(47.20),"清華大學出版社",
                LocalDate.of(2011,06,01),1));

        bst.add(new Books(9787115472588L,"鳥哥的Linux私房菜 基礎學習篇 第四版",
                "操作系統","鳥哥",
                BigDecimal.valueOf(93.00),"人民郵電出版社",
                LocalDate.of(2018,10,01),1));

        bst.add(new Books(9787115293800L,"算法(第4版)",
                "編程語言與程序設計","Robert Sedgewick,Kevin Wayne",
                BigDecimal.valueOf(66.30),"人民郵電出版社",
                LocalDate.of(2012,10,01),1));

        // 再次遍歷圖書庫
        System.out.println("圖書庫同種圖書加入:");
        System.out.println("書的種類數:"+bst.getBookSize());
        System.out.println("書的總數量:"+bst.getBookCount());
        bst.traverse(bst.getRoot());

        // 根據ISBN號查找圖書
        Books books =bst.search(9787115472588L);
        if (books!=null) {
            System.out.println("已找到該圖書:");
            System.out.println(books.toString());
        }
    }
}

程序輸出如下:

圖書庫新建:
書的種類數:10
書的總數量:10
{ISBN=9787115428028, 書名='Python編程 從入門到實踐', 作者='埃里克·馬瑟斯', 分類='編程語言與程序設計', 價格=61.4, 出版社='人民郵電出版社', 出版時間=2017-07-01, 當前數量=1}
{ISBN=9787111641247, 書名='深入理解Java虛擬機:JVM高級特性與最佳實踐(第3版)', 作者='周志明', 分類='編程語言與程序設計', 價格=106.4, 出版社='机械工業出版社', 出版時間=2019-12-01, 當前數量=1}
{ISBN=9787115293800, 書名='算法(第4版)', 作者='Robert Sedgewick,Kevin Wayne', 分類='編程語言與程序設計', 價格=66.3, 出版社='人民郵電出版社', 出版時間=2012-10-01, 當前數量=1}
{ISBN=9787115525963, 書名='說服力 工作型PPT該這樣做', 作者='秦陽', 分類='辦公軟件', 價格=66.3, 出版社='人民郵電出版社', 出版時間=2020-05-01, 當前數量=1}
{ISBN=9787115472588, 書名='鳥哥的Linux私房菜 基礎學習篇 第四版', 作者='鳥哥', 分類='操作系統', 價格=93.0, 出版社='人民郵電出版社', 出版時間=2018-10-01, 當前數量=1}
{ISBN=9787569222258, 書名='零基礎學Python(全彩版)', 作者='明日科技', 分類='編程語言與程序設計', 價格=67.0, 出版社='吉林大學出版社', 出版時間=2018-04-01, 當前數量=1}
{ISBN=9787121388361, 書名='PS之光:一看就懂的Photoshop攻略(全彩)', 作者='馮注龍', 分類='圖形圖像/多媒體', 價格=60.7, 出版社='电子工業出版社', 出版時間=2020-06-01, 當前數量=1}
{ISBN=9787115537973, 書名='數學之美 第三版', 作者='吳軍', 分類='計算機理論、基礎知識', 價格=54.4, 出版社='人民郵電出版社', 出版時間=2020-05-01, 當前數量=1}
{ISBN=9787302423287, 書名='機器學習', 作者='周志華', 分類='人工智能', 價格=64.8, 出版社='清華大學出版社', 出版時間=2016-01-01, 當前數量=1}
{ISBN=9787302255659, 書名='大話數據結構', 作者='程傑', 分類='編程語言與程序設計', 價格=47.2, 出版社='清華大學出版社', 出版時間=2011-06-01, 當前數量=1}
圖書庫同種圖書加入:
書的種類數:10
書的總數量:13
{ISBN=9787115428028, 書名='Python編程 從入門到實踐', 作者='埃里克·馬瑟斯', 分類='編程語言與程序設計', 價格=61.4, 出版社='人民郵電出版社', 出版時間=2017-07-01, 當前數量=1}
{ISBN=9787111641247, 書名='深入理解Java虛擬機:JVM高級特性與最佳實踐(第3版)', 作者='周志明', 分類='編程語言與程序設計', 價格=106.4, 出版社='机械工業出版社', 出版時間=2019-12-01, 當前數量=1}
{ISBN=9787115293800, 書名='算法(第4版)', 作者='Robert Sedgewick,Kevin Wayne', 分類='編程語言與程序設計', 價格=66.3, 出版社='人民郵電出版社', 出版時間=2012-10-01, 當前數量=2}
{ISBN=9787115525963, 書名='說服力 工作型PPT該這樣做', 作者='秦陽', 分類='辦公軟件', 價格=66.3, 出版社='人民郵電出版社', 出版時間=2020-05-01, 當前數量=1}
{ISBN=9787115472588, 書名='鳥哥的Linux私房菜 基礎學習篇 第四版', 作者='鳥哥', 分類='操作系統', 價格=93.0, 出版社='人民郵電出版社', 出版時間=2018-10-01, 當前數量=2}
{ISBN=9787569222258, 書名='零基礎學Python(全彩版)', 作者='明日科技', 分類='編程語言與程序設計', 價格=67.0, 出版社='吉林大學出版社', 出版時間=2018-04-01, 當前數量=1}
{ISBN=9787121388361, 書名='PS之光:一看就懂的Photoshop攻略(全彩)', 作者='馮注龍', 分類='圖形圖像/多媒體', 價格=60.7, 出版社='电子工業出版社', 出版時間=2020-06-01, 當前數量=1}
{ISBN=9787115537973, 書名='數學之美 第三版', 作者='吳軍', 分類='計算機理論、基礎知識', 價格=54.4, 出版社='人民郵電出版社', 出版時間=2020-05-01, 當前數量=1}
{ISBN=9787302423287, 書名='機器學習', 作者='周志華', 分類='人工智能', 價格=64.8, 出版社='清華大學出版社', 出版時間=2016-01-01, 當前數量=1}
{ISBN=9787302255659, 書名='大話數據結構', 作者='程傑', 分類='編程語言與程序設計', 價格=47.2, 出版社='清華大學出版社', 出版時間=2011-06-01, 當前數量=2}
已找到該圖書:
{ISBN=9787115472588, 書名='鳥哥的Linux私房菜 基礎學習篇 第四版', 作者='鳥哥', 分類='操作系統', 價格=93.0, 出版社='人民郵電出版社', 出版時間=2018-10-01, 當前數量=2}

四、深入理解

  1. 二分搜索樹的底層是一個鏈點,可以實現高效地插入,刪除以及動態維護。
  2. 二分搜索樹的結點是有序的,可以很快地求出最大,最小之類的關係值。
  3. 也正是因為二分搜索樹的結點是有序的,在極端情況下,二分搜索樹會褪化成一個鏈表

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

【其他文章推薦】

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

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

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

※推薦評價好的iphone維修中心

CCNA-Part1:網絡基礎概念

由於身處一家網絡公司,日常項目中涉及到的網絡概念較多,恰逢之後公司組織相關培訓。藉此機會,打算寫下一系列文章用於之后梳理並回顧。文章主要涉及 NA,NP 中所覆蓋的知識。由於網絡分為較多方向,如路由交換,無線,安全等。在今年,大綱正好有所改變,其中無線和路由交換放在一起合稱為企業架構。所以本系列文章以企業架構為主。

在網絡界,Cisco 證書一直被普遍認可,其中分為三個等級 NA,NP,IE. 對於開發人員來說,掌握 NA 水平一般即可,本系列文章會 NA 開始,到 NP 結束。NA 內容較為寬泛,其中涉及知識面較寬,但不深入,用於入門。NP 在 NA 基礎上,更加深入,涉及到更多的協議與概念。

話不多說,在閱讀本文後,應該了解以下內容:

  1. 了解網絡的組成?
  2. 衡量網絡的指標?
  3. 不同應用程序對網絡的要求?

什麼是網絡?

網絡的組成

先看一下網絡的組成組件:

主要分為 4 大類:

  • 終端設備:產生和接受數據的設備,如 PC ,服務器,電話
  • 中間設備:常見叫法為2,3層設備,如 2 層設備交換機,3層設備路由器
  • 媒介:數據傳輸時的媒介,如無線,線纜,光纖。
  • 服務:如應用服務器,taobao,QQ 等

由此可見,網絡就是由上述設備組成,在其中進行數據產生,轉發的過程。在討論網絡時,一般討論的都是企業網絡,下面是常見的一張企業網絡的架構圖。

從圖中我們可以看到,網絡的大體架構由,終端設備,接入層設備(交換機,用於將設備接入),轉發層設備(路由器)和數據中心組成。舉例子來說,如果左下角的同學 A 想要和右上角的同學 B 同學通信,則需要經歷如下過程:

  1. 通過接入層設備接入企業內網
  2. 通過核心層設備,將數據轉發至 ISP(服務提供商,如聯通電信等)
  3. ISP 將數據轉發至同學 B 所在核心層設備。
  4. B 所在核心層設備轉發至接入層設備。
  5. 接入層設備轉發給用戶B。

接口類型

設備連入的端口,稱為接口,下面是常見的接口類型。

接口名稱 速度
Ethernet 10M
Fast Ethernet 100M
GigabitEthernet 1000M
10GigabitEthernet 10000M
40GigabitEthernet 40000M
100GigabitEthernet 1000000M
Serial – 串行口

衡量網絡的主要指標

在討論或者設計一個網絡架構是,往往會在如下的方面進行討論:

帶寬

表示數據的發送速率,單位為比特每秒(b/s),意思為一秒鐘發送的比特數,因此帶寬又稱為比特率。(以太網: 10Mbps, 快速以太網:100Mbps)

可用性與可靠性:

拓展性:

在設計網絡架構時,要考慮到可拓展性,公司的人數會隨着時間增加。

安全性:

數據傳輸和存儲的安全。

服務質量 (Qos):

對流量進行分類整理,拿家庭具體,分類出訪問 QQ 的流量,訪問 P2P 的流量,然後對其進行限制設置上限,防止一個服務佔用過大的帶寬,造成其他服務無法正常訪問。

開銷(Cost):

設備及搭建網絡的費用。

虛擬化:

將一個物理設備虛擬化成多個虛擬設備,例如交換機,路由器等。

拓撲:

物理拓撲:實際設備之間的物理連接的布局,稱為物理拓撲。

物理之間拓撲的比較:

拓撲類型 優點
總線拓撲 1. 所用電纜少
2. 結構簡單
3. 易於擴充
4. 布線方便
1. 傳輸距離有限,通信範圍受到限制
2.故障診斷和隔離較困難
3.所有數據經過總線傳送,不具有實時功能
4. 單點故障:所有 PC 不得不共享線纜,一個節點出錯,將影響整個網絡
環形拓撲 1. 增加或減少工作站時,僅需要簡單的連接操作
2. 電纜長度短
3. 傳輸延遲確定
1. 傳輸距離有限,通信範圍受到限制
2. 故障診斷和隔離困難
3. 節點過多時影響傳輸效率
4. 任意節點出現故障,整個網絡將癱瘓
星型拓撲-局域網較為常見 1. 集中控制,控制簡單
2. 故障診斷和隔離容易
3. 網絡延遲短
1. 中央節點的負擔較重,形成瓶頸
2. 各節點的分佈處理能力較低
3. 網絡共享能里較差
部分網狀拓撲-廣域網常見 1. 系統可靠性高,比較容易擴展 1. 結構複雜,每一結點都與多點進行連結
2. 因此必須採用路由算法和流量控制方法。

邏輯拓撲:以數據轉發的過程為側重,描述節點之間數據的轉發過程。

應用程序流量分類

在網絡中提供服務的應用種類較多,對應對網絡的要求也一般不同,可大致分為如下幾類:

總結

本篇內容中,多為網絡的基礎概念,方便大家入門,只需理解有個印象就好。接下來的內容,才是真正學習的網絡的開始。

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

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

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

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

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

※回頭車貨運收費標準