危及蜜蜂存亡 加國漸禁2款類尼古丁農藥

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

加拿大政府今天(16日)表示,將逐步限用和水生昆蟲及蜜蜂死亡有關連的2款類尼古丁農藥。環保人士認為這是一大勝利,但對販售殺蟲劑的企業而言卻是最新挫敗。

加拿大衛生署(Health Canada)病蟲害管制局(PMRA)表示,未來三到五年之間將逐步禁止在戶外使用賽速安(Thiamethoxam)及可尼丁(Clothianidin)。

加拿大衛生署這項措施將有90天諮詢期,最後在2019年底做出決定。

加拿大衛生署也計畫在今年底之前做出最終決定,是否逐步禁止第3種的類尼古丁農藥,拜耳生產的益達胺(imidacloprid)。

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

【其他文章推薦】

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

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

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

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

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

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

擔心成為新的傾倒場 泰國將禁止電子垃圾進口

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

環境部一名官員今天(16日)說,泰國將在6個月內禁止進口432種廢棄電子用品。中國今年禁止高科技廢棄物進口以來,泰國成為最新呼應的國家。

泰國下達禁令數週前,區域鄰國越南表示,將停止核發新執照給廢棄物進口,並將取締非法運送廢紙、塑膠和金屬。

泰國環境部一名高級官員今天告訴路透社,禁令涵蓋432種電子廢棄物,從電路板到老舊電視機和收音機零件等等,將在6個月內生效。

他並說,禁令是環境部長素拉薩(Surasak Kanchanarat)昨天主持會議時做成決定。

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

【其他文章推薦】

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

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

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

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

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

※產品缺大量曝光嗎?你需要的是一流包裝設計!

一起玩轉微服務(11)——一切從簡單開始

介紹

使用Spring Bboot是快樂並且簡單的,不需要繁瑣的配置就能夠完成一套非常強大的應用。

spring boot 2.3.1

Spring Boot 2.3.1 發佈於:2020/06/12,現在已經提交到 Spring 倉庫和 Maven 中央倉庫了。

這個版本包括 127 個 bug 修復、Spring Boot 文檔改進增強、依賴升級等,另外還新增了一些新特性:

•提供基於新的 Maven 坐標 com.oracle.database 對 Oracle JDBC driver 的依賴管理;

•優化 Spring Cloud 的 CachedRandomPropertySource 不能正確適配的問題;•限制使用定製的 YAML 類型;

•增強對 NoSuchMethodErrors 異常失敗分析,能显示基類從哪被加載的;•提供更佳的錯誤消息,如果 Docker 停止運行了;

•優化 SystemEnvironmentPropertyMapper 類;

•提供更佳的診斷信息,當構建 OCI 鏡像失敗時 Docker 響應的 500 錯誤;

•支持通過 alwaysUseFullPath=true 參數來配置 UrlPathHelper;•支持在 Elasticsearch URIs 中使用用戶信息;

•支持在 Spring WebFlux 框架中使用歡迎頁面;

這個小版本還增加了蠻多東西的,大家也沒有必要跟着版本走,可以根據需要進行升級。疫情也擋不住外國友人更新的熱情。

實現

使用STS,可以去官方網站下載最新版。網站地址 https://Spring.io/tools/sts/ Spring Tool Suite™是基於eclipse開發的專門為Spring開發使用的工具包。

新建工程

選擇Spring Starter Project,

輸入工程名 對應的Name 打包方式 對應的Packaging,可以選擇jar或者war的方式。

輸入組織名 對應的Group 輸入描述 對應的Description

輸入包名 對應的Package 點擊next,然後選擇web和mysql

這裏的版本用的是2.3.1 如果沒有本地maven庫或者私庫會下載很長時間。

添加默認請求

進入 Chapter0301Application 添加

@RestController
@SpringBootApplication
public class Chapter0301Application {
    
    @RequestMapping("/")
    String home() {
         return "歡迎使用Spring Boot!";
    }

    public static void main(String[] args) {
        SpringApplication.run(Chapter0301Application.class, args);
    }

}

使用@RestController 相當於@Controller 和 @RequestBody。是Sspring Bboot 基於Sspring MVC的基礎上進行了改進, 將@Controller 與@ResponseBody 進行了合併形成的一個新的註解。 @EnableAutoConfiguration 作用 從classpath中搜索所有META-INF/spring.factories配置文件然後,將其中org.springframework.boot.autoconfigure.EnableAutoConfiguration key對應的配置項加載到spring容器 只有spring.boot.enableautoconfiguration為true(默認為true)的時候,才啟用自動配置 @EnableAutoConfiguration還可以進行排除,排除方式有2種,一是根據class來排除(exclude),二是根據class name(excludeName)來排除 其內部實現的關鍵點有

1.ImportSelector 該接口的方法的返回值都會被納入到spring容器管理中

2.SpringFactoriesLoader 該類可以從classpath中搜索所有META-INF/spring.factories配置文件,並讀取配置

啟動spring boot

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.1.RELEASE)

2020-06-23 13:30:11.611  INFO 9916 --- [           main] com.cloud.sky.Chapter0301Application     : Starting Chapter0301Application on DADI-PC with PID 9916 (D:\java\microservice\chapter0301\target\classes started by Administrator in D:\java\microservice\chapter0301)
2020-06-23 13:30:11.614  INFO 9916 --- [           main] com.cloud.sky.Chapter0301Application     : No active profile set, falling back to default profiles: default
2020-06-23 13:30:12.415  INFO 9916 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2020-06-23 13:30:12.423  INFO 9916 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2020-06-23 13:30:12.424  INFO 9916 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.36]
2020-06-23 13:30:12.512  INFO 9916 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2020-06-23 13:30:12.512  INFO 9916 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 830 ms
2020-06-23 13:30:12.665  INFO 9916 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2020-06-23 13:30:12.809  INFO 9916 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2020-06-23 13:30:12.818  INFO 9916 --- [           main] com.cloud.sky.Chapter0301Application     : Started Chapter0301Application in 1.492 seconds (JVM running for 3.109)
2020-06-23 13:30:20.675  INFO 9916 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2020-06-23 13:30:20.676  INFO 9916 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2020-06-23 13:30:20.680  INFO 9916 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 4 ms

打開瀏覽器訪問 http://localhost:8080/ 可以得到如下頁面

遇到問題

構建的過程中遇到問題

[INFO] Scanning for projects...
[ERROR] [ERROR] Some problems were encountered while processing the POMs:
[FATAL] Non-parseable POM D:\java\apache-maven-3.1.1\repo\org\jetbrains\kotlin\kotlin-bom\1.3.72\kotlin-bom-1.3.72.pom: entity reference names can not start with character ')' (position: START_TAG seen ...ost,s="";function qs(n){var u=D.URL;var t=u.match(eval(\'/(\\?|#|&)... @1:243)  @ D:\java\apache-maven-3.1.1\repo\org\jetbrains\kotlin\kotlin-bom\1.3.72\kotlin-bom-1.3.72.pom, line 1, column 243
 @ 
[ERROR] The build could not read 1 project -> [Help 1]
[ERROR]   
[ERROR]   The project com.cloudskyme:chapter0301:0.0.1 (D:\java\microservice\chapter0301\pom.xml) has 1 error
[ERROR]     Non-parseable POM D:\java\apache-maven-3.1.1\repo\org\jetbrains\kotlin\kotlin-bom\1.3.72\kotlin-bom-1.3.72.pom: entity reference names can not start with character ')' (position: START_TAG seen ...ost,s="";function qs(n){var u=D.URL;var t=u.match(eval(\'/(\\?|#|&)... @1:243)  @ D:\java\apache-maven-3.1.1\repo\org\jetbrains\kotlin\kotlin-bom\1.3.72\kotlin-bom-1.3.72.pom, line 1, column 243 -> [Help 2]
[ERROR] 
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR] 
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/ProjectBuildingException
[ERROR] [Help 2] http://cwiki.apache.org/confluence/display/MAVEN/ModelParseException

1. 解決

修改maven默認源配置
我使用的是阿里的maven倉庫,國外的東西沒個代理還真麻煩。

<repositories>
        <repository>  
            <id>alimaven</id>
            <name>aliyun maven</name>
            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
        </repository> 
        <repository>
            <id>sonatype-nexus-snapshots</id>
            <url>https://oss.sonatype.org/content/repositories/snapshots</url>
            <releases>
                <enabled>false</enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
    </repositories>

然後執行 mvn help:system
成功可以看到如下界面:

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

【其他文章推薦】

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

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

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

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

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

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

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維修中心

Jmeter系列(27)- 詳解正則提取器

如果你想從頭學習Jmeter,可以看看這個系列的文章哦

https://www.cnblogs.com/poloyy/category/1746599.html

 

有了 JSON 提取器為啥還要用正則提取器?

  • JSON 提取器只針對接口返回的響應內容
  • 如果想提取的是響應頭、請求頭的值,而非響應內容的值呢?
  • 這個時候正則提取器的作用就出來了,它可以提取請求任一部分的值

 

需知

  • 正則表達式很多內容,在這篇文章中不會展開詳細說的哦,主要還是說提取器的使用
  • 想詳細學習正則表達式可以看這篇文章:待補充

 

正則提取器

我們通過實際栗子去講述理論知識點

 

正則提取器界面介紹

 

字段含義

字段 含義
Apply to 應用範圍,選默認的 main sample only 就行了
Field to check

可提取的字段

Names of created variables
  • 接收提取值的變量名
  • 必傳
Regular Expression

正則表達式

Template 從找到的匹配項中創建字符串的模板
Match No.(0 for Random)
  • 取第幾個值
  • 0:隨機,默認
  • -1:所有
  • 1:第一個值
  • 非必傳
Default Value
  • 缺省值,匹配不到值的時候取該值
  • 非必傳
Use empty default value

勾選后,提取不到值時,則返回空字符串

 

Template

  • 如果一條正則表達式有多個提取結果,則提取結果是數組形式
  • 模板 $1$、$2$…..表示把解析到的第幾個值賦給變量,從 1 開始匹配
  • $0$ 表示整個表達式匹配的內容(後續具體看栗子)
  • 若只有一個結果,只能是$1$

 

Field to check

屬性 含義
Body 響應體,不包括響應頭;最常用
Body (unescaped) 響應體,替換了所有HTML轉義符;不建議使用
Body as a Document 從不同類型的文件中提取文本;影響性能
Request Headers 請求頭
Response Headers 響應頭
URL URL
Response Code 響應碼
Response Message 響應信息

 

Body

 

Request Headers

 

Response Headers

 

URL

 

Response Code、Message

 

入門栗子

栗子的前提

這個栗子,我都會以這個地址的接口來完成 JSON 提取器的實戰慄子,大家可以註冊個賬號玩一玩哦

http://api.yesapi.cn/docs.php?keyword=%E4%BC%9A%E5%91%98&channel=api

 

測試計劃樹結構

下面多個栗子都以這個測試計劃為基礎哦

 

提取某個特定的值的栗子

登錄接口響應

登錄是執行其他接口的前置接口,所以要獲取用戶登錄后的 token、uuid

 

提取 token

 

提取 uuid

 

其他接口調用 token、uuid

 

知識點

  • 提其他接口可以通過  ${var}  這種格式,來獲取提取到的值
  •  ( ) 裏面寫匹配規則,用於解析正則表達式
  •  .*? 表示匹配任意長度的任意字符,這也是最常用的正則表達式
  • 一般 (.+?) 和 (.*?) 能夠滿足我們 80%的使用場景

 

一般正則表達式都可以寫成下面兩種

  •  左邊界(.+?)右邊界 
  •  左邊界(.*?)右邊界 

 

舉更多栗子前的一些話

  • 上面講的是使用正則提取器時的一個流程,也是實際工作中最簡單的栗子
  • 在實際項目中,我們可能會出現一條正則表達式有多個提取結果的情況

 

JSON 字符串

下面的栗子都以這個 JSON 字符串為基礎,从里面提取結果

這 JSON 字符串也是某個接口的響應內容,貨真價實,感興趣也可以自己玩一玩:http://api.yesapi.cn/docs-api-App.User.GetList.html

{
    "ret": 200,
    "msg": "V2.5.1 YesApi App.User.GetList",
    "data": {
        "total": 4,
        "err_msg": "",
        "err_code": 0,
        "users": [
            {
                "role": "user",
                "status_desc": "正常",
                "reg_time": "2020-06-22 20:45:05",
                "role_desc": "普通會員",
                "ext_info": {
                    "yesapi_nickname": "",
                    "yesapi_points": 0
                },
                "uuid": "0564CE592B4CE914365D8922F6FC4CEC",
                "username": "luojunjiess286",
                "status": 0
            },
            {
                "role": "user",
                "status_desc": "正常",
                "reg_time": "2020-06-22 14:27:17",
                "role_desc": "普通會員",
                "ext_info": {
                    "yesapi_nickname": "",
                    "yesapi_points": 0
                },
                "uuid": "0164DC0680F84DCE40D3DD4A36640ECA",
                "username": "luojunjiessa",
                "status": 0
            },
            {
                "role": "admin",
                "status_desc": "正常",
                "reg_time": "2020-03-23 22:48:32",
                "role_desc": "管理員",
                "ext_info": {
                    "yesapi_nickname": "",
                    "yesapi_points": 0
                    "yesapi_reg_source": ""
                },
                "uuid": "079BF6BB82AFCFC7084F96AECAF0519F",
                "username": "luojunjiess",
                "status": 0
            }
        ]
    }
}

 

一條正則表達式只有一個提取結果的栗子

什麼叫只有一個提取結果

就是正則表達式里只有一個 ( ) ,且  Match No. 不是 -1

 

未填寫模板

提取器

 

測試結果

uuid1=
uuid1_g=1
uuid1_g0="uuid":"0564CE592B4CE914365D8922F6FC4CEC"
uuid1_g1=0564CE592B4CE914365D8922F6FC4CEC

 

知識點

如果正則匹配到值,但是沒有填模板,則返回

 

$0$

提取器

 

測試結果

uuid2="uuid":"0564CE592B4CE914365D8922F6FC4CEC"
uuid2_g=1
uuid2_g0="uuid":"0564CE592B4CE914365D8922F6FC4CEC"
uuid2_g1=0564CE592B4CE914365D8922F6FC4CEC

 

知識點

  •  $0$ 模板其實返回的就是 uuid2_g0 的值
  • 返回了整個正則表達式,不只是 ( ) 內匹配到的值

 

$1$

提取器

 

測試結果

uuid3=0564CE592B4CE914365D8922F6FC4CEC
uuid3_g=1
uuid3_g0="uuid":"0564CE592B4CE914365D8922F6FC4CEC"
uuid3_g1=0564CE592B4CE914365D8922F6FC4CEC

 

知識點

  •  $1$  模板其實返回的就是 uuid2_g1 的值
  • 僅返回 ( ) 內匹配到的值

 

$2$

提取器

 

測試結果

uuid4=null
uuid4_g=1
uuid4_g0="uuid":"0564CE592B4CE914365D8922F6FC4CEC"
uuid4_g1=0564CE592B4CE914365D8922F6FC4CEC

 

知識點

 $2$ 模板並不存在,其實就是 uuid4_g2 變量不存在,即使勾了使用空默認值,也返回 null,

 

總結

  • 其實 uuid 在 JSON 字符串中有三個可匹配到的值,如果不填寫匹配数字 Match No. ,則會隨機取一個 uuid 並返回
  • 像上述的幾個栗子,都填了 1 ,所以都返回了第一個匹配到的 uuid

 

一條正則表達式有多個提取結果的栗子

什麼叫有多個提取結果

有兩種情況

  • 一條表達式有多個 ( ) 
  • 一個 ( ) 匹配到多個值,且 Match No 填了 -1

 

一個 ( ) 匹配到多個值 

提取器

 

測試結果

手動分成四部分

uuid1_1=0564CE592B4CE914365D8922F6FC4CEC
uuid1_1_g=1
uuid1_1_g0="uuid":"0564CE592B4CE914365D8922F6FC4CEC"
uuid1_1_g1=0564CE592B4CE914365D8922F6FC4CEC

uuid1_2=0164DC0680F84DCE40D3DD4A36640ECA
uuid1_2_g=1
uuid1_2_g0="uuid":"0164DC0680F84DCE40D3DD4A36640ECA"
uuid1_2_g1=0164DC0680F84DCE40D3DD4A36640ECA

uuid1_3=079BF6BB82AFCFC7084F96AECAF0519F
uuid1_3_g=1
uuid1_3_g0="uuid":"079BF6BB82AFCFC7084F96AECAF0519F"
uuid1_3_g1=079BF6BB82AFCFC7084F96AECAF0519F

uuid1_matchNr=3

 

知識點

  • 一個 ( ) 匹配到多個值的場景 ,一般會結合 ForEach控制器,可以循環將提取到的值賦予到 HTTP 請求中
  • 可以看看下圖的小栗子,這裏不展開講,後面會再詳細講解

 

結構樹 + ForEach 控制器

 

查看結果樹

 

一條表達式有多個( ),且模板為空

提取器

 

測試結果

info1=
info1_g=2
info1_g0="uuid":"0564CE592B4CE914365D8922F6FC4CEC","username":"luojunjiess286"
info1_g1=0564CE592B4CE914365D8922F6FC4CEC
info1_g2=luojunjiess286

 

一條表達式有多個( ),且只有一個模板

提取器

 

測試結果

info2=0564CE592B4CE914365D8922F6FC4CEC
info2_g=2
info2_g0="uuid":"0564CE592B4CE914365D8922F6FC4CEC","username":"luojunjiess286"
info2_g1=0564CE592B4CE914365D8922F6FC4CEC
info2_g2=luojunjiess286

 

知識點

  • info2 拿的就是 info2_g1 的值
  •  $1$ 獲取的是第一個 ( ) 匹配到的值, $2$ 獲取的是第二個 ( ) 匹配到的值,以此類推     

 

一條表達式有多個( ),且有兩個模板

提取器

 

測試結果

info3=0564CE592B4CE914365D8922F6FC4CECluojunjiess286
info3_g=2
info3_g0="uuid":"0564CE592B4CE914365D8922F6FC4CEC","username":"luojunjiess286"
info3_g1=0564CE592B4CE914365D8922F6FC4CEC
info3_g2=luojunjiess286

info4=0564CE592B4CE914365D8922F6FC4CEC,luojunjiess286
info4_g=2
info4_g0="uuid":"0564CE592B4CE914365D8922F6FC4CEC","username":"luojunjiess286"
info4_g1=0564CE592B4CE914365D8922F6FC4CEC
info4_g2=luojunjiess286

 

正則表達式中有多個 ( ) 時的總結

  • 如果其中一個 ( ) 匹配不到元素,那也無法獲取到值
  • 引用名稱、匹配数字、缺省值三個字段也只需要填一個值即可,不需要跟 ( ) 的數量一致
  • 多個模板( $1$$2$ )的時候,可以用空格、, 、. 、  連接模板,最終會显示在變量上,如:info4

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

【其他文章推薦】

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

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

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

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

※超省錢租車方案

chromedp入門

chromedp入門

chromedp是什麼?

chromedp是go寫的,支持Chrome DevTools Protocol 的一個驅動瀏覽器的庫。並且它不需要依賴其他的外界服務(比如 Selenium 和 PhantomJs)。

Chrome DevTools Protocol (CDP)

Chrome DevTools Protocol (CDP) 的主頁在:https://chromedevtools.github.io/devtools-protocol/。 它提供一系列的接口來查看,檢查,調整並且檢查 Chromium 的性能。Chrome 的開發者工具就是使用這一系列的接口,並且 Chrome 開發者工具來維護這些接口。

所謂 CDP 的協議,本質上是什麼呢?本質上是基於 websocket 的一種協議。比如

在我們打開 webtool 調試工具的時候,其實調試工具也是一個web頁面,兩個web頁面通過websocket建立了一個聯繫。
所以我們如果寫了一個客戶端程序,也和目標頁面創建一個基於 CDP 的 websocket連接,我們也可以通過這個協議來對頁面進行操作。

如何打開 Protocol Monitor

在chrome的開發者工具中

打開實驗選項 Protocol Monitor

重啟chrome,在console的更多裏面就可以打開對應的 Monitor

CDP 協議內容

我們從 Protocol Monitor 面板中可以看到,其中有幾個字樣,Method,Request,Response。
這裏的 Method 就是對應官網 https://chromedevtools.github.io/devtools-protocol/ 左側每個Domain的 Event。

這裏的每個Method方法可能是調試頁面給目標頁面發送的,但是更多是目標頁面給調試頁面發送的消息。所以我們需要讀懂每個Method的內容。不過很可惜,我個人感覺官網的每個Method文檔的描述寫的實在是太簡單了,也沒有看到更詳細的描述,只能通過名字和事件來猜測每個Method意思了。

chromedp 使用

chromedp的使用最快的方法就是看 https://github.com/chromedp/examples 這個項目

基本我們可以熟悉最常用的幾個方法了:

  • chromedp.NewContext() 初始化chromedp的上下文,後續這個頁面都使用這個上下文進行操作
  • chromedp.Run() 運行一個chrome的一系列操作
  • chromedp.Navigate() 將瀏覽器導航到某個頁面
  • chromedp.WaitVisible() 等候某個元素可見,再繼續執行。
  • chromedp.Click() 模擬鼠標點擊某個元素
  • chromedp.Value() 獲取某個元素的value值
  • chromedp.ActionFunc() 再當前頁面執行某些自定義函數
  • chromedp.Text() 讀取某個元素的text值
  • chromedp.Evaluate() 執行某個js,相當於控制台輸入js
  • network.SetExtraHTTPHeaders() 截取請求,額外增加header頭
  • chromedp.SendKeys() 模擬鍵盤操作,輸入字符
  • chromedp.Nodes() 根據xpath獲取某些元素,並存儲進入數組
  • chromedp.NewRemoteAllocator
  • chromedp.OuterHTML() 獲取元素的outer html
  • chromedp.Screenshot() 根據某個元素截圖
  • page.CaptureScreenshot() 截取整個頁面的元素
  • chromedp.Submit() 提交某個表單
  • chromedp.WaitNotPresent() 等候某個元素不存在,比如“正在搜索。。。”
  • chromedp.Tasks{} 一系列Action組成的任務

實踐

我們嘗試打開 https://www.cnblogs.com/ 的首頁,然後獲取所有文章的標題和鏈接:

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/chromedp/cdproto/cdp"
	"github.com/chromedp/chromedp"
)

func main() {

	ctx, cancel := chromedp.NewContext(
		context.Background(),
		chromedp.WithLogf(log.Printf),
	)
	defer cancel()

	var nodes []*cdp.Node
	err := chromedp.Run(ctx,
		chromedp.Navigate("https://www.cnblogs.com/"),
		chromedp.WaitVisible(`#footer`, chromedp.ByID),
		chromedp.Nodes(`.//a[@class="titlelnk"]`, &nodes),
	)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("get nodes:", len(nodes))
	// print titles
	for _, node := range nodes {
		fmt.Println(node.Children[0].NodeValue, ":", node.AttributeValue("href"))
	}
}

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

【其他文章推薦】

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

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

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

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

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

向強大的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;
}

效果圖:

 

 

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

【其他文章推薦】

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

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

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

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

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

跳躍表確定不了解下

redis源碼分析系列文章

[Redis源碼系列]在Liunx安裝和常見API 

為什麼要從Redis源碼分析 

String底層實現——動態字符串SDS 

Redis的雙向鏈表一文全知道

面試官:說說Redis的Hash底層 我:……(來自閱文的面試題)

前言

hello,大家好,周五見了。前面幾周我們一起看了Redis底層數據結構,如動態字符串SDS,雙向鏈表Adlist,字典Dict,如果有對Redis常見的類型或底層數據結構不明白的請看上面傳送門。

今天我們來看下ZSET的底層架構,如果不知道ZSET是什麼的,可以看上面傳送門第一篇。簡單來說,ZSET是Redis提供的根據數據和分數來判斷其排名的數據結構。最常見的就是微信運動的排名,每個用戶對應自己的步數,每天晚上可以給出用戶的排名。

有小夥伴可能會想,如果是實現排名的話,各種排序方法都可以實現的,沒必要引入Redis的ZSET結構啊?

當然,如果是採用排序方法的話,是可以實現相同功能的,但是代碼裏面需要硬編碼,會添加工作量,還會提供代碼的Bug哦,哈哈哈。而且Redis的底層是C實現的,直接操作內存,速度也會比Java方法實現提升。

綜上,使用Redis的ZSET結構,好處多多。那話不多說,開始把。在正式開始之前,我們需要引入下跳躍表的概念,其是ZSET結構的底層實現。以下可能有點枯燥,我盡量說的簡單點哈。

什麼是跳躍表?

對於數據量大的鏈表結構,插入和刪除比較快,但是查詢速度卻很慢。那是因為無法直接獲取某個節點,需要從頭節點開始,藉助某個節點的next指針來獲取下一節點。即使數據是有序排放的,想要查詢某個數據,只能從頭到尾遍歷變量,查詢效率會很低,時間複雜度為O(n)。

如果我們需要快速查詢鏈表有啥辦法呢?有同學說用數組存放,但是如果不改數據結構呢?

我們可以先想想在有序數組結構中有二分法,每次將範圍都縮小一半,這樣查詢速度提升了很多,那麼在鏈表中能不能也使用這種思想。

這就到了今天講的主角——跳躍表。(一點也生硬的引出概念)

步驟一  新建有序單項鏈表

先看下圖有序單向鏈表,存放了1,2,3,4,5,6,7這7個元素。

步驟二 抽取二級索引節點

我們可以在鏈表中抽取部分節點,下圖抽取了1,3,5,7四個節點,也就是每兩個節點提取了一個節點到上級,抽取出來的叫做索引。

注意不是每次都能抽取到這麼完美,這其實就跟拋硬幣一樣,每個硬幣的正反兩面的概率是一樣的,都是1/2。當數據量小的時候,正反的概率可能差別較大。但是隨着數據量的加大,正反的概率越來越接近於1/2。類比過來是一個意思,每個節點的機會都是一樣的,要麼停留原級,要麼提取到上級,概率都是1/2。但是隨着節點數量的增加,抽取的節點越來越接近與1/2。

步驟三 抽取三級索引節點

我們可以在鏈表中抽取部分節點,下圖抽取了1,5兩個節點,也就是每兩個節點提取了一個節點到上級,抽取出來的叫做索引。

步驟四 類二分法查詢

我們假設要查找值為6的節點,先從三級索引開始,找到值為1的節點,發現比5小,根據值為1節點的next指針,找到值為5的節點,5後面沒有其他的三級索引啦。

於是順着往下找,到了二級索引,根據值為5的節點的next指針找到值為7的節點,發現比6小,說明要找到的節點6在此範圍內。

再接着到了一級索引位置,根據值為5的節點next指針指向值為6的節點,發現是想要查詢的數據,所以查詢過程結束。

根據上面的查詢過程(下圖的藍色連線),我們發現其採用的核心思想是二分法,不斷縮小查詢範圍,如果在上層索引找到區間,則順延深入到下一層找到真正的數據。

總結

從上面的整個過程中可以看出,數據量小的時候,這種拿空間換時間,消耗內存方法的並不是最優解。所以Redis的zset結構在數據量小的時候採用壓縮表,數據量大的時候採用跳躍表。

像這種鏈表加多級索引的結構,就是跳躍表。這名字起的形象,過程是跳躍着來查詢的。

Redis中跳躍表圖解

下圖簡單來說是對跳躍表的改進和再封裝,首先引入了表頭的概念,這與雙向鏈表,字典結構一樣,都是對數據的封裝,因為他們都是採用的指針,而指針必然導致在計算長度,獲取最後節點的數據問題上會產生查詢太慢的性能問題,所以封裝表頭是為了在這些問題上提升速度,浪費的只是添加,刪除等操作的時間,與此對比,是可以忽略的。

其次是引入管理所有節點的層數數組,我們可以看到有32層,即32個數組,這和後面的數據節點結構是一樣的。引入它是為了便於直接根據此數組的層數定位到每個元素。

再其次是數據節點的每個level都有層級和span(也就是下圖箭頭指針上的数字,其是為了方便統計兩個節點相距多少長度)。

最後就是數據節點的後退指針backward,引入目的是Level數組只有前指針,即只能指向下一個節點地址,而後退指針是為了能往回找節點。

上圖主要分為3大塊:(這邊大致看下就行,下面將對各模塊進行代碼詳細解釋)

表頭

主要包括四個屬性,分別是頭指針header,尾指針tail,節點長度length,所有節點的最大level。

header:指向跳躍表的表頭節點,通過這個指針地址可以直接找到表頭,時間複雜度為O(1)。

tail:指向跳躍表的表尾節點,通過這個指針可以直接找到表尾,時間複雜度為O(1)。

length:記錄跳躍表的長度,即不包含表頭節點,整個跳躍表中有多少個元素。

level:記錄當前跳躍表內,所有節點層數最大的level(排除表頭節點)。

管理所有節點層數level的數組

其對象值為空,level數組為32層,目的是為了管理真正的數據節點。關於具體的level有哪些屬性放在數據節點來說。

數據節點

主要包括四個屬性對象值obj,分數score,後退指針backward和level數組。每個數據的Level數組有多少層,是隨機產生的,這跟上面說過的跳躍表是一樣的。

成員對象obj:真正的實際數據,每個節點的數據都是唯一的,但是節點的分數可能相同。兩個相同分數的節點是按照成員對象在字典中的大小進行排序的,成員對象較小的節點會排在前面,成員對象較大的節點會排在後面。

分數score:各個節點中的数字是節點所保存的分數,在跳躍表中,節點按各自所保存的分數從小到大排列。

後退指針backward:用於從表尾向表頭遍歷,每個節點只有一個後退指針,即每次只能後退一步。

層級level:節點中用1,2,3等字樣標記節點的各個層,L1代表第一層,L2代表第二層,L3代表第三層,並以此類推。

跳躍表的定義

表頭結構zskiplist

 

typedef struct zskiplist {
    //表頭的頭指針header和尾指針tail
    struct zskiplistNode *header, *tail;
    //一共有多少個節點length
    unsigned long length;
    // 所有節點最大的層級level
   int level;
} zskiplist;

具體數據節點zskiplistNode

 

//跳錶的具體節點 
typedef struct zskiplistNode {
    sds ele; //具體的數據,對應張三
    double score;//分數,對應70
    struct zskiplistNode *backward;//後退指針backward
     //層級數組    struct zskiplistLevel {
        struct zskiplistNode *forward;//前進指針forward
        unsigned int span;//跨度span
    } level[];
} zskiplistNode; 

 

跳躍表的實現(源碼分析)

redis關於跳躍表的API都定義在t_zset.c文件中。

千萬不要看到源碼分析就跑開了,一定要看哦。

創建跳躍表

創建空的跳躍表,其實就是創建表頭和管理所有的節點的level數組。首先,定義一些變量,嘗試分配內存空間。其次是初始化表頭的level和length,分別賦值1和0。接着創建管理所有節點的Level的數組,是調用zslCreateNode函數,輸入參數為數組大小宏常量ZSKIPLIST_MAXLEVEL(32),分數為0,對象值為NULL。(此為跳躍表得以實現重點)。再接着就是為此數組每個元素的前指針forword和跨度span初始化。最後初始化尾指針並返回值。

可以參照下面的圖解和源碼:

 

//創建一個空表頭的跳躍表
zskiplist *zslCreate(void) {
    int j;
    zskiplist *zsl;
    //嘗試分配內存空間
    zsl = zmalloc(sizeof(*zsl));
    //初始化level和length
    zsl->level = 1;
    zsl->length = 0;
    //調用下面的方法zslCreateNode,傳入的參數有數組長度ZSKIPLIST_MAXLEVEL 32
    //分數0,對象值NuLL
    //這一步就是創建管理所有節點的數組
    //並且設置表頭的頭頭指針為此對象的地址
    zsl->header = zslCreateNode(ZSKIPLIST_MAXLEVEL,0,NULL);
    //為這32個數組賦值前指針forward和跨度span
    for (j = 0; j < ZSKIPLIST_MAXLEVEL; j++) {
        zsl->header->level[j].forward = NULL;
        zsl->header->level[j].span = 0;
    }
    //設置尾指針
    zsl->header->backward = NULL;
    zsl->tail = NULL;
    //返回對象
    return zsl;
}
zskiplistNode *zslCreateNode(int level, double score, sds ele) {
    zskiplistNode *zn =
        zmalloc(sizeof(*zn)+level*sizeof(struct zskiplistLevel));
    zn->score = score;
    zn->ele = ele;
    return zn;
}

 

插入節點

比如有下圖6個元素,需要插入值為趙六,分數為101的元素,我們大致想一想,大致的步驟包括找到要插入的位置新建一個數據節點,然後調整與之相關的頭尾指針的level數組。那就看看redis咋做的,和我們想的一樣不一樣呢?

噔噔噔噔,答案揭曉。當然了大框架是相同的。

正文開始了:(先來圖片)

1.遍歷管理所有節點的level數組,從最大的level開始,即3,挨個對比值,如果有分數比他大的值或者分數相同,但是數據的值比他大,記錄到數組裡面,同時記錄跨度。

這樣說太抽象了。拿上圖舉個例子,從表頭的level即3開始,首先到張三的L3,發現分數70,比目標分數101小跳過,根據其前指針找到趙六的L3,發現分數102,比目標分數101大,將趙六L3記錄在待更新數組update中,同時記錄跨度span為4。接着到下一層,張三的L2層,發現分數70比目標分數101小跳過,根據前指針找到王五的L2,發現分數90,比目標分數101小跳過,根據前指針找到趙六的L2,發現分數102比目標分數101大,將趙六的L2記錄到待更新數組update中,同時記錄跨度span為2。最後到下一層,張三的L1層,邏輯和剛才一樣的,也是記錄趙六的L1層和跨度span為1。

2.為新節點隨機生成層級數level(通過位運算),如果生成的level大於目前level最大值3,則將將大於部分挨個遍歷,並將跨度等信息記錄到上面update表中。

比如,新節點生成的level為5,目前level最大值為3,說明這個節點只會有一個,並且跨越了之前的所有節點,那麼我們將從第四層和第五層都遍歷下,記錄到待更新數組update中。

3.準備工作都做好了,找到了該節點將插入到哪一位置,處於哪一層,每層對應的跨度是多少,下面就要新增數據節點了。把上兩步的信息都添加到新節點上,並且調整位置前後指針即可。

4.最後就是一些收尾工作,比如修改表頭的層級level,節點大小length和尾指針tail等屬性。

綜上,整個流程就已經結束了。可能看着有點複雜,可以對照下面代碼來。

 

//插入節點,輸入參數為
//zsl:表頭
//score:插入元素的分數score
//ele:插入元素的具體數據ele
zskiplistNode *zslInsert(zskiplist *zsl, double score, sds ele) {
    //使用update數組記錄每層待插入元素的前一個元素
    zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
    //記錄前置節點與第一個節點之間的跨度,即元素在列表中的排名-1
    unsigned int rank[ZSKIPLIST_MAXLEVEL];
    int i, level;

    serverAssert(!isnan(score));
    x = zsl->header;
    //從最大的level開始遍歷,從頂到底,找到每一層待插入的位置
    for (i = zsl->level-1; i >= 0; i--) {
        /* store rank that is crossed to reach the insert position */
        rank[i] = i == (zsl->level-1) ? 0 : rank[i+1];
    //直接找到第一個分數比該元素大的位置
    //或者分數與該元素相同但是對象的ASSICC碼比該元素大的位置
        while (x->level[i].forward &&
                (x->level[i].forward->score < score ||
                    (x->level[i].forward->score == score &&
                    sdscmp(x->level[i].forward->ele,ele) < 0)))
        {
            //將已走過元素的跨越元素進行計數,得到元素在列表中排名,或者是已搜尋的路徑長度
            rank[i] += x->level[i].span;
            x = x->level[i].forward;
        }
    //記錄待插入位置
        update[i] = x;
    }
     //隨機產生一個層數,在1到32之間,層數越高,生成的概率越低
    level = zslRandomLevel();
    //如果產生的層數大於現有的最高層數,則超出層數都需要初始化
    if (level > zsl->level) {
        //開始循環
        for (i = zsl->level; i < level; i++) {
            rank[i] = 0;
            //該元素作為這些層的第一個節點,前節點就是header
            update[i] = zsl->header;
            //初始化后這些層每層有兩個元素,走一步就是跨越所有元素
            update[i]->level[i].span = zsl->length;
        }
        zsl->level = level;
    }
    //創建節點
    x = zslCreateNode(level,score,ele);
    for (i = 0; i < level; i++) {
        //將新節點插入到各層鏈表中
        x->level[i].forward = update[i]->level[i].forward;
        update[i]->level[i].forward = x;

        // rank[0]是第0層的前置節點P1(也就是底層插入節點前面那個節點)與第一個節點的跨度
        // rank[i]是第i層的前置節點P2(這一層里在插入節點前面那個節點)與第一個節點的跨度
        // 插入節點X與後置節點Y的跨度f(X,Y)可由以下公式計算
        // 關鍵在於f(P1,0)-f(P2,0)+1等於新節點與P2的跨度,這是因為跨度呈扇形形向下延伸到最底層
        // 記錄節點各層跨越元素情況span, 由層與層之間的跨越元素總和rank相減而得
        x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]);
               // 插入位置前一個節點的span在原基礎上加1即可(新節點在rank[0]的后一個位置)

 update[i]->level[i].span = (rank[0] - rank[i]) + 1;
    }

    /* increment span for untouched levels */
    for (i = level; i < zsl->level; i++) {
        update[i]->level[i].span++;
    }
    // 第0層是雙向鏈表, 便於redis常支持逆序類查找
    x->backward = (update[0] == zsl->header) ? NULL : update[0];
    if (x->level[0].forward)
        x->level[0].forward->backward = x;
    else
        zsl->tail = x;
    zsl->length++;
    return x;
}

 

int zslRandomLevel(void) {
    int level = 1;
    while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))
        level += 1;
    return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;
}

 

獲取節點排名

擔心大家忘了這張圖,再粘貼一遍。如下圖,這部分邏輯比較簡單,就不寫了,具體參考代碼分析。

 

//得到節點的排名
//輸入參數為表頭結構zsl,分數score,真正的數據ele
unsigned long zslGetRank(zskiplist *zsl, double score, sds ele) {
    zskiplistNode *x;
    unsigned long rank = 0;
    int i;
    //先獲取表頭的頭指針,即找到管理所有節點的level數組
    x = zsl->header;
     //從表頭的level,即最大值開始循環遍歷
    for (i = zsl->level-1; i >= 0; i--) {
        //如果找到分數小於目標分數的,排名加上其跨度
        //或者分數相同,但是具體數據小於目標數據的,排名也加上跨度
        while (x->level[i].forward &&
            (x->level[i].forward->score < score ||
                (x->level[i].forward->score == score &&
                sdscmp(x->level[i].forward->ele,ele) <= 0))) {
            rank += x->level[i].span;
            x = x->level[i].forward;
        }

        //確保在第i層找到分值相同,且對象相同時才會返回排位值
        if (x->ele && sdscmp(x->ele,ele) == 0) {
            return rank;
        }
    }
    return 0;
}

 

結語

該篇主要講了Redis的ZSET數據類型的底層實現跳躍表,先從跳躍表是什麼,引出跳躍表的概念和數據結構,剖析了其主要組成部分,進而通過多幅過程圖解釋了Redis是如何設計跳躍表的,最後結合源碼對跳躍表進行描述,如創建過程,添加節點過程,獲取某個節點排名過程,中間穿插例子和過程圖。

如果覺得寫得還行,麻煩給個贊,您的認可才是我寫作的動力!

如果覺得有說的不對的地方,歡迎評論指出。

好了,拜拜咯。

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

【其他文章推薦】

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

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

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

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

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

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

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

目錄

  • 一、背景
  • 二、概念
    • 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維修中心