男人最愛的這項熱血汽車運動,真的很爽!

除了可以感受到賽車的熱烈氣氛以外,還可以近距離看到不同賽車的結構和技術亮點。當然了,還可以去了解一下賽車手究竟都是怎樣的人,究竟真的是“超人么”。這次的視頻,我們還特別找到了珠海高卡車隊的老闆兼車手王遠先生,去了解一下賽車究竟是怎麼一回事如。

對於喜歡汽車的朋友來說,總會看過一點關於賽車的東西。不過對於大部分對汽車不太熟悉的朋友,賽車貌似離我們非常遙遠,簡直好像是另外一個星球的事情。而賽車往往也是很高深莫測的精密机械,有超凡的性能,連駕駛賽車的車手也好像需要類似飛行員的體質和能力,才能駕馭這種可怕的機器!

真的是這樣么?

為了探尋這個秘密,我們到了珠海國際賽車場ZIC,去觀看泛珠三角賽車節春季賽(簡稱泛珠春季賽)。泛珠春季賽基本集合了大部分常見的賽車類別,包括方程式、prototype、GT、TCR、漂移賽車和改裝房車等等。除了可以感受到賽車的熱烈氣氛以外,還可以近距離看到不同賽車的結構和技術亮點。當然了,還可以去了解一下賽車手究竟都是怎樣的人,究竟真的是“超人么”。

這次的視頻,我們還特別找到了珠海高卡車隊的老闆兼車手王遠先生,去了解一下賽車究竟是怎麼一回事如。如果想體驗賽車,又有什麼門檻。當然了,我們也對他們車隊一台GK5賽車進行剖析,看看它究竟和我們一般街道行駛的飛度又有什麼相同和不同的地方。

如果你對賽車好奇,又想了解一下賽車究竟是怎麼回事,或者已經是一個熱血賽車迷的話,就不要錯過本期的視頻了。

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

Express4.x之中間件與路由詳解及源碼分析,Express4.x之API:express,Express4.x之API:express

  • Application.use()
  • Application.router()
  • express核心源碼模擬

 一、express.use()

1.1app.use([path,] callback [, callback …])

通過語法結構可以看到Application.use()參數分別有以下幾種情形:

app.use(function(){...}); //給全局添加一个中間件
app.use(path,function(){...}); //給指定路由path添加一个中間件
app.use(function(){...}, function(){...}, ...); //給全局添加n个中間件
app.use(path,function(){...},function(){...}, ...); //給指定路由path添加n个中間件

關於path最簡單也是最常用的就是字符串類型(例:‘/abcd’);除了字符串Express還提供了模板和正則格式(例:’/abc?d‘, ‘/ab+cd‘, ‘/ab\*cd‘, ‘/a(bc)?d‘, ‘/\/abc|\/xyz/‘);除了單個的字符串和模板還可以將多個path作為一個數組的元素,然後將這個數組作為use的path,這樣就可以同時給多個路由添加中間件,詳細內容可以參考官方文檔:https://www.expressjs.com.cn/4x/api.html#path-examples。

關於callbakc多個或單个中間件程序這已經再語法結構中直觀的體現出來了,這裏重點來看看回調函數的參數:

app.use(function(req,res,next){...}); //必須提供的參數 
app.use(function(err,req,res,next){...}); //錯誤中間件需要在最前面添加一個錯誤參數

關於中間件的簡單應用:

let express = require('./express');
let app = express();
app.use('/',function(req,res,next){
    console.log("我是一個全局中間件");
    next(); //每个中間件的最末尾必須調用next
});

app.use('/',function(err,req,res,next){
    console.log("我是一個全局錯誤中間,當發生錯誤是調用")    
    console.error(err.stack);
    res.status(500).send('服務出錯誤了!');
    //由於這個錯誤處理直接響應了客戶端,可以不再調用next,當然後面還需要處理一些業務的話也是可以調用next的
});

1.2簡單的模擬Express源碼實現Appliction.use()以及各個請求方法的響應註冊方法(這裏個源碼模擬路由概念還比較模糊,所以使用請求方法的響應註冊API,而沒有使用路由描述):

 1 //文件結構
 2 express
 3     index.js
 4 //源碼模擬實現
 5 let http = require("http");
 6 let url = require('url');
 7 function createApplication(){
 8     //app是一個監聽函數
 9     let app = (req,res) =>{
10         //取出每一個層
11         //1.獲取請求的方法
12         let m = req.method.toLowerCase();
13         let {pathname} = url.parse(req.url,true);
14 
15         //通過next方法進行迭代
16         let index = 0;
17         function next(err){
18             //如果routes迭代完成還沒有找到,說明路徑不存在
19             if(index === app.routes.length) return res.end(`Cannot ${m} ${pathname}`);
20             let {method, path, handler} = app.routes[index++];//每次調用next就應該取下一個layer
21             if(err){
22                 if(handler.length === 4){
23                     handler(err,req,res,next);
24                 }else{
25                     next(err);
26                 }
27             }else{
28                 if(method === 'middle'){ //處理中間件
29                     if(path === '/' || path === pathname || pathname.startsWith(path+'/')){
30                         handler(req,res,next);
31                     }else{
32                         next();//如果這个中間件沒有匹配到,繼續通過next迭代路由容器routes
33                     }
34                 }else{ //處理路由
35                     if( (method === m || method ==='all') && (path === pathname || path === '*')){ //匹配請求方法和請求路徑(接口)
36                         handler(req,res);//匹配成功后執行的Callback
37                     }else{
38                         next();
39                     }
40                 }
41             }
42         }
43         next();
44     }
45     app.routes = [];//路由容器
46     app.use = function(path,handler){
47         if(typeof handler !== 'function'){
48             handler = path;
49             path = '/';
50         }
51         let layer = {
52             method:'middle', //method是middle就表示它是一个中間件
53             path,
54             handler
55         }
56         app.routes.push(layer);
57     }
58     app.all = function(path,handler){
59         let layer = {
60             method:'all',
61             path,
62             handler
63         }
64         app.routes.push(layer);
65     }
66     console.log(http.METHODS);
67     http.METHODS.forEach(method =>{
68         method = method.toLocaleLowerCase();
69         app[method] = function (path,handler){//批量生成各個請求方法的路由註冊方法
70             let layer = {
71                 method,
72                 path,
73                 handler
74             }
75             app.routes.push(layer);
76         }
77     });
78     //內置中間件,給req擴展path、qury屬性
79     app.use(function(req,res,next){
80         let {pathname,query} = url.parse(req.url,true);
81         let hostname = req.headers['host'].split(':')[0];
82         req.path = pathname;
83         req.query = query;
84         req.hostname = hostname;
85         next();
86     });
87     //通過app.listen調用http.createServer()掛在app(),啟動express服務
88     app.listen = function(){
89         let server = http.createServer(app);
90         server.listen(...arguments);
91     }
92     return app;
93 }
94 module.exports = createApplication;

View Code

測試模擬實現的Express:

 1 let express = require('./express');
 2 
 3 let app = express();
 4 
 5 app.use('/',function(req,res,next){
 6     console.log("我是一個全局中間件");
 7     next();
 8 });
 9 app.use('/user',function(req,res,next){
10     console.log("我是user接口的中間件");
11     next();
12 });
13 app.get('/name',function(req,res){
14     console.log(req.path,req.query,req.hostname);
15     res.end('zfpx');
16 });
17 app.post('/name',function(req,res){
18     res.end('post name');
19 });
20 
21 
22 app.all("*",function(req,res){
23     res.end('all');
24 });
25 
26 app.use(function(err,req,res,next){
27     console.log(err);
28     next();
29 });
30 
31 app.listen(12306);

View Code

在windows系統下測試請求:

 

 

關於源碼的構建詳細內容可以參考這個視頻教程:app.use()模擬構建視頻教程,前面就已經說明過這個模式實現僅僅是從表面的業務邏輯,雖然有一點底層的雛形,但與源碼還是相差甚遠,這一部分也僅僅只是想幫助理解Express採用最簡單的方式表現出來。

1.3如果你看過上面的源碼或自己也實現過,就會發現Express關於中間件的添加方式除了app.use()還有app.all()及app.METHOD()。在模擬源碼中我並未就use和all的差異做處理,都是採用了請求路徑絕對等於path,這種方式是all的特性,use的path實際表示為請求路徑的開頭:

app.use(path,callback):path表示請求路徑的開頭部分。

app.all(path,callback):paht表示完全等於請求路徑。

app.METHOD(path,callback):並不是真的有METHOD這個方法,而是指HTTP請求方法,實際上表示的是app.get()、app.post()、app.put()等方法,而有時候我們會將這些方法說成用來註冊路由,這是因為路由註冊的確使用這些方法,但同時這些方法也是可以用作中間的添加,這在前一篇博客中的功能解析中就有說明(Express4.x之API:express),詳細見過後面的路由解析就會更加明了。

 二、express.router()

2.1在實例化一個Application時會實例化一個express.router()實例並被添加到app._router屬性上,實際上這個app使用的use、all、METHOD時都是在底層調用了該Router實例上對應的方法,比如看下面這些示例:

 1 let express = require("express");
 2 let app = express();
 3 
 4 app._router.use(function(req,res,next){
 5     console.log("--app.router--");
 6     next();
 7 });
 8 
 9 app._router.post("/csJSON",function(req,res,next){
10     res.writeHead(200);
11     res.write(JSON.stringify(req.body));
12     res.end();
13 });
14 
15 app.listen(12306);

上面示例中的app._router.use、app._router.post分別同等與app.use、app.post,這裏到這裏也就說明了上一篇博客中的路由與Application的關係Express4.x之API:express。

2.2Express中的Router除了為Express.Application提供路由功能以外,Express也將它作為一個獨立的路由工具分離了出來,也就是說Router自身可以獨立作為一個應用,如果我們在實際應用中有相關業務有類似Express.Application的路由需求,可以直接實例化一個Router使用,應用的方式如下:

let express = require('/express');
let router = express.Router();
//這部分可以詳細參考官方文檔有詳細的介紹

2.3由於這篇博客主要是分析Express的中間件及路由的底層邏輯,所以就不在這裏詳細介紹某個模塊的應用,如果有時間我再寫一篇關於Router模塊的應用,這裏我直接上一份模擬Express路由的代碼以供參考:

文件結構:

express //根路徑
    index.js //express主入口文件
    application.js //express應用構造模塊
    router //路由路徑
        index.js //路由主入口文件
        layer.js //構造層的模塊
        route.js //子路由模塊

Express路由系統的邏輯結構圖:

模擬代碼(express核心源碼模擬):

1 //express主入口文件
2 let Application = require('./application.js');
3 
4 function createApplication(){
5     return new Application();
6 }
7 
8 module.exports = createApplication;

index.js //express主入口文件

 1 //用來創建應用app
 2 let http = require('http');
 3 let url = require('url');
 4 
 5 //導入路由系統模塊
 6 let Router = require('./router');
 7 
 8 const methods = http.METHODS;
 9 
10 //Application ---- express的應用系統
11 function Application(){
12     //創建一個內置的路由系統
13     this._router = new Router();
14 }
15 
16 //app.get ---- 實現路由註冊業務
17 // Application.prototype.get = function(path,...handlers){
18 //     this._router.get(path,'use',handlers);
19 // }
20 
21 methods.forEach(method => {
22     method = method.toLocaleLowerCase();
23     Application.prototype[method] = function(path,...handlers){
24         this._router[method](path,handlers);
25     }
26 });
27 
28 //app.use ---- 實現中間件註冊業務
29 //這裏模擬處理三種參數模式:
30 // -- 1個回調函數:callback
31 // -- 多個回調函數:[callback,] callback [,callback...]
32 // -- 指定路由的中間件:[path,] callback [,callback...]
33 // -- 注意源碼中可以處理這三種參數形式還可以處理上面數據的數組形式,以及其他Application(直接將其他app上的中間件添加到當前應用上)
34 Application.prototype.use = function(fn){
35     let path = '/';
36     let fns = [];
37     let arg = [].slice.call(arguments);
38     if(typeof fn !== 'function' && arg.length >= 2){
39         if(typeof arg[0] !== 'string'){
40             fns = arg;
41         }else{
42             path = arg[0];
43             fns = arg.slice(1);
44         }
45     }else{
46         fns = arg;
47     }
48     this._router.use(path,'use',fns);
49 }
50 
51 Application.prototype.all = function(fn){
52     let path = '/';
53     let fns = [];
54     let arg = [].slice.call(arguments);
55     if(typeof fn !== 'function' && arg.length >= 2){
56         if(typeof arg[0] !== 'string'){
57             fns = arg;
58         }else{
59             path = arg[0];
60             fns = arg.slice(1);
61         }
62       }else{
63         fns = arg;
64     }
65     this._router.use(path,'all',fns);
66 }
67 
68 //將http的listen方法封裝到Application的原型上
69 Application.prototype.listen = function(){
70     let server = http.createServer((req,res)=>{
71         //done 用於當路由無任何可匹配項時調用的處理函數
72         function done(){
73             res.end(`Cannot ${req.url} ${req.method}`);
74         }
75         this._router.handle(req,res,done); //調用路由系統的handle方法處理請求
76     });
77     server.listen(...arguments);
78 };
79 
80 module.exports = Application;

application.js //express應用構造模塊

 1 //express路由系統
 2 const Layer = require('./layer.js');
 3 const Route = require('./route.js');
 4 
 5 const http = require('http');
 6 const methods = http.METHODS;
 7 
 8 const url = require('url');
 9 
10 
11 //路由對象構造函數
12 function Router(){
13     this.stack = [];
14 }
15 
16 //router.route ---- 用於創建子路由對象route與主路由上層(layer)的關係
17 //並將主路由上的層緩存到路由對象的stack容器中,該層建立路徑與子路由處理請求的關係
18 Router.prototype.route = function(path){
19     let route = new Route();
20     let layer = new Layer(path,route.dispatch.bind(route));
21     this.stack.push(layer);
22     return route;
23 }
24 
25 //router.get ---- 實現路由註冊
26 //實際上這個方法調用router.route方法分別創建一個主路由系統層、一個子路由系統,並建立兩者之間的關係,詳細見Router.prototype.route
27 //然後獲取子路由系統對象,並將回調函數和請求方法註冊在這個子路由系統上
28 
29 
30 // Router.prototype.get = function(path,handlers){
31 //     let route = this.route(path);
32 //     route.get(handlers);
33 // }
34 
35 methods.forEach(method =>{ 
36     method = method.toLocaleLowerCase();
37     //注意下面這個方法會出現內存泄漏問題,有待改進
38     Router.prototype[method] = function(path, handlers){
39         let route = this.route(path);
40         route[method](handlers);
41     }
42 });
43 
44 //router.use ---- 實現中間件註冊(按照路由開頭的路徑匹配,即相對路由匹配)
45 Router.prototype.use = function(path,routerType,fns){
46     let router = this;
47     fns.forEach(function(fn){
48         let layer = new Layer(path,fn);
49         layer.middle = true; //標記這個層為相對路由中間件
50         layer.routerType = routerType;
51         router.stack.push(layer);
52     });
53 }
54 
55 //調用路由處理請求
56 Router.prototype.handle = function(req,res,out){
57     let {pathname} = url.parse(req.url);
58     let index = 0;
59     let next = () => {
60         if(index >= this.stack.length) return out();
61         let layer = this.stack[index++];
62         if(layer.middle && (layer.path === '/' || pathname === layer.path || pathname.startsWith(layer.path + '/'))){
63             //處理中間件
64             if(layer.routerType === 'use'){
65                 layer.handle_request(req,res,next);
66             }else if(layer.routerType === 'all' && layer.path === pathname){
67                 layer.handle_request(req,res,next);
68             }else{
69                 next();
70             }
71         }else if(layer.match(pathname)){
72             //處理響應--更準確的說是處理具體請求方法上的中間件或響應
73             layer.handle_request(req,res,next);
74         }else{
75             next();
76         }
77     }
78     next();
79 }
80 
81 module.exports = Router;

index.js //路由主入口文件

 1 //Layer的構造函數
 2 function Layer(path,handler){
 3     this.path = path; //當前層的路徑
 4     this.handler = handler;  //當前層的回調函數
 5 }
 6 
 7 //判斷請求方法與當前層的方法是否一致
 8 Layer.prototype.match = function(pathname){
 9     return this.path === pathname;
10 }
11 
12 //調用當前層的回調函數handler
13 Layer.prototype.handle_request = function(req,res,next){
14     this.handler(req,res,next);
15 }
16 
17 module.exports = Layer;

layer.js //構造層的模塊

 1 //Layer的構造函數
 2 function Layer(path,handler){
 3     this.path = path; //當前層的路徑
 4     this.handler = handler;  //當前層的回調函數
 5 }
 6 
 7 //判斷請求方法與當前層的方法是否一致
 8 Layer.prototype.match = function(pathname){
 9     return this.path === pathname;
10 }
11 
12 //調用當前層的回調函數handler
13 Layer.prototype.handle_request = function(req,res,next){
14     this.handler(req,res,next);
15 }
16 
17 module.exports = Layer;

route.js //子路由模塊

測試代碼:

 1 let express = require('./express');
 2 let app = express();
 3 
 4 
 5 app.use('/name',function(req,res,next){
 6     console.log('use1');
 7     next();
 8 });
 9 app.use(function(req,res,next){
10     console.log('use2-1');
11     next();
12 },function(req,res,next){
13     console.log('use2-2');
14     next();
15 });
16 
17 app.all('/name',function(req,res,next){
18     console.log('all-1');
19     next();
20 });
21 app.all('/name/app',function(req,res,next){
22     console.log('all-2');
23     next();
24 });
25 
26 app.get('/name/app',function(req,res){
27     res.end(req.url);
28 });
29 // console.log(app._router.stack);
30 app.listen(12306);

View Code

測試結果:

 

 

 

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

Dart Memo for Android Developers

Dart Memo for Android Developers

Dart語言一些語法特點和編程規範.

本文適合: 日常使用Kotlin, 突然想寫個Flutter程序的Android程序員.

Dart語言

完整的請看A tour of the Dart language

  • 創建對象可以不用new. -> 並且規範不讓用new, lint會報錯.
  • 聲明變量可以用var, 也可以用具體類型如String. 不變量用final, 常量用const.
  • 沒有訪問修飾符, 用_來表示私有: 文件級別.
  • 字符串可以用單引號'.
  • 語句結尾要用;.
  • 創建數組可以用: var list = [1, 2, 3];.
  • assert()常用來斷定開發時不可能會出現的情況.
  • 空測試操作符: ??.
  • 過濾操作符: where.
  • 兩個點..表示鏈式調用.
  • dynamic說明類型未指定.
  • 除了throw異常, 還可以throw別的東西, 比如字符串.

函數

  • 函數返回值在函數最開頭, 可以不標. -> 但是規範會建議標註返回值.
bool isNoble(int atomicNumber) {
  return _nobleGases[atomicNumber] != null;
}
  • =>箭頭符號, 用來簡化一句話的方法.
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;

構造函數

  • 構造函數{}表示帶名字, 參數可選, 若要必選加上@required.
const Scrollbar({Key key, @required Widget child})
  • 構造函數名可以是ClassName或者ClassName.identifier.
  • 空構造函數體可以省略, 用;結尾就行:
class Point {
  double x, y;
  Point(this.x, this.y);
}

這裡會初始化相應的變量, 也不用聲明具體的參數類型.

  • factory構造, 可以用來返回緩存實例, 或者返回類型的子類:
factory Logger(String name) {
    return _cache.putIfAbsent(name, () => Logger._internal(name));
}

異步代碼

Future<String> lookUpVersion() async => '1.0.0';

Future checkVersion() async {
  var version = await lookUpVersion();
  // Do something with version
}

編程規範類

完整的規範在這裏: Effective Dart.

有一些Good和Bad的舉例, 這裏僅列出比較常用的幾項.

  • 文件名要蛇形命名: lowercase_with_underscores. 類名: UpperCamelCase.
  • 對自己程序的文件, 兩種import都可以(package開頭或者相對路徑), 但是要保持一致.
  • Flutter程序嵌套比較多, 要用結尾的,來幫助格式化.

本文緣由

年初的時候學了一陣子Flutter, 寫了各種大小demo. 結果隔了兩個月之後, 突然心血來潮想寫個小東西, 打開Android Studio, 首先發現創建Flutter程序的按鈕都不見了. (估計是Android Studio4.0升級之後Flutter的插件沒跟上).

接着用命令行創建了工程, 打開之後稍微整理了一下心情, 然後就….懵逼了.

突然不知道如何下手.
宏觀的東西還記得, 要用什麼package, 基本常用的幾個Widget都是啥, 但是微觀的, 忘了函數和數組都是咋定義的了.
這種懵逼的狀態令我很憤怒, 果然是上年紀了嗎, 無縫切換個語言都不行.

於是就想着還是寫個備忘錄吧.

References

  • A tour of the Dart language
  • Effective Dart

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

保護全球最美星空 智利環團提告商業大樓「污染天空」

摘錄自2020年9月28日奇摩新聞報導

智利北部的阿他加馬沙漠曾獲選BBC全球十大最美暗夜星空,入夜後整片的星空美不勝收,吸引了各種追星者和天文學家,因此聚集大量觀星者的巨型望遠鏡,幾乎半數的世界天文觀測站都在這。但現在都市的擴張和發展伴隨的光污染使星星黯淡許多,甚至使一些關鍵地區的天空退化超過10%。

智利環保機構表示,將提告用「人造冷光」污染天空的公司,當地政府也打算修法,若業者減少光污染將有特別優惠,希望利用合法的力量和新的保護措施讓天空保持黑暗。但目前收到起訴和修正的公司都還未回覆,其他公司也都還在審理中。

污染治理
國際新聞
智利
光害
星空

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

【其他文章推薦】

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

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

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

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

※回頭車貨運收費標準

分析ThreadLocal的弱引用與內存泄漏問題-Java8,利用線性探測法解決hash衝突

目錄

一.介紹

二.問題提出

  2.1內存原理圖

  2.2幾個問題

三.回答問題

  3.1為什麼會出現內存泄漏

  3.2若Entry使用弱引用

  3.3弱引用配合自動回收

四.總結  

 

 

 

一.介紹

  之前使用ThreadLocal的時候,就聽過ThreadLocal怎麼怎麼的可能會出現內存泄漏,具體原因也沒去深究,就是一種不清不楚的狀態。最近在看JDK的源碼,其中就包含ThreadLocal,在對ThreadLocal的使用場景介紹以及源碼的分析后,對於ThreadLocal中可能存在的內存泄漏問題也搞清楚了,所以這裏專門寫一篇博客分析一下。

  在分析內存泄漏之前,先了解2個概念,就是內存泄漏和內存溢出:

  內存溢出(memory overflow):是指不能申請到足夠的內存進行使用,就會發生內存溢出,比如出現的OOM(Out Of Memory)

  內存泄漏(memory lack):內存泄露是指在程序中已經動態分配的堆內存由於某種原因未釋放或者無法釋放(已經沒有用處了,但是沒有釋放),造成系統內存的浪費,這種現象叫“內存泄露”。

  當內存泄露到達一定規模后,造成系統能申請的內存較少,甚至無法申請內存,最終導致內存溢出,所以內存泄露是導致內存溢出的一個原因。

 

二.問題提出

2.1內存原理圖

  下圖是程序運行中的內存分布圖,簡要介紹一下這種圖:當前線程有一個threadLocals屬性(ThreadLocalMap屬性),該map的底層是數組,每個數組元素時Entry類型,Entry類型的key是ThreadLocal類型(也就是創建的ThreadLocal對象),而value是則是ThreadLocal.set()方法設置的value。

  

  需要注意的是ThreadLocalMap的Entry,繼承自弱引用,定義如下,關於Java的引用介紹,可以參考:Java-強引用、軟引用、弱引用、虛引用

/**
 * ThreadLocalMap中存放的元素類型,繼承了弱引用類
 */
static class Entry extends WeakReference<ThreadLocal<?>> {
    // key對應的value,注意key是ThreadLocal類型
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

 

2.2問題提出

  在看了上面ThreadLocal和ThreadLocalMap相關的內存分佈以及關聯后,提出這樣幾個問題:

  1.ThreadLocal為什麼會出現內存溢出?

  2.Entry的key為什麼要用弱引用?

  3.使用弱引用是否就能解決內存溢出?

  為了回答上面這3個問題,我寫了一段代碼,後面根據這段代碼進行分析:

public void step1() {
    // some action
    
    step2();
    step3();
    
    // other action
}

// 在stepX中都會創建threadLocal對象
public void step2() {
    ThreadLocal<String> tl = new ThreadLocal<>();
    tl.set("this is value");
}
public void step3() {
    ThreadLocal<Integer> tl = new ThreadLocal<>();
    tl.set(99);
}

  在step1中會調用step2和step3,step2和step3都會創建ThreadLocal對象,當step2和step3執行完畢后,其中的棧內存中ThreadLocal引用就會被清除。

 

三.回答問題

 

  

  現在針對這個圖,一步一步的分析問題,中途會得出一些臨時的結論,但是最終的結論才是正確的

 

3.1為什麼會出現內存泄露

  現在有2點假設,本小節的分析都是基於這兩個假設之上的:

  1.Entry的key使用強引用,key對ThreadLocal對象使用強引用,也就是上面圖中連線5是強引用(key強引用ThreadLocal對象);

  2.ThreadLocalMap中不會對過期的Entry進行清理。

  上面代碼中,如果ThreadLocalMap的key使用強引用,那麼即使棧內存的ThreadLocal引用被清除,但是堆中的ThreadLocal對象卻並不會被清除,這是因為ThreadLocalMap中Entry的key對ThreadLocal對象是強引用。

  如果當前線程不結束,那麼堆中的ThreadLocal對象將會一直存在,對應的內存就不會被回收,與之關聯的Entry也不會被回收(Entry對應的value也不會被回收),當這種情況出現數量比較多的時候,未釋放的內存就會上升,就可能出現內存泄漏的問題。

  上面的結論是暫時的,有前提假設!!!最終結論還需要看後面分析。

 

3.2若Entry使用弱引用

  

  仍舊有1個假設,就是ThreadLocalMap中不會對過期的Entry進行清理,陳舊的Entry是指Entry的key為null。

  按照源碼,Entry繼承弱引用,其Key對ThreadLocal是弱引用,也就是上圖中連線5是弱引用,連線6仍為強引用。

  同樣以上面代碼為例,step2和step3創建了ThreadLocal對象,step2和step3執行完后,棧中的ThreadLocal引用被清除了;由於堆內存中ThreadLocalMap的Entry key弱引用ThreadLocal對象,根據垃圾收集器對弱引用對象的處理:

當垃圾收集器工作時,無論當前內存是否足夠,都會回收掉只被弱引用關聯的對象。

  此時堆中ThreadLocal對象會被gc回收(因為現在沒有對ThreadLocal的強引用,只有一個弱引用ThreadLocal對象),Entry的key為null,但是value不為null,且value也是強引用(連線6),所以Entry仍舊不能回收,只能釋放ThreadLocal的內存,仍舊可能導致內存泄漏

  在沒有自動清理陳舊Entry的前提下,即使Entry使用弱引用,仍可能出現內存泄漏。

 

3.3弱引用配合自動回收

  通過3.2的分析,其實只要陳舊的Entry能自動被回收,就能解決內存泄漏的問題,其實JDK就是這麼做的。

  如果看過源碼,就知道,ThreadLocalMap底層使用數組來保存元素,使用“線性探測法”來解決hash衝突,關於線性探測法的介紹可以查看:利用線性探測法解決hash衝突

  在每次調用ThreadLocal類的get、set、remove這些方法的時候,內部其實都是對ThreadLocalMap進行操作,對應ThreadLocalMap的get、set、remove操作。

  重點來了!重點來了!重點來了!

  ThreadLocalMap的每次get、set、remove,都會清理過期的Entry,下面以get操作解釋,其他操作也是一個意思,大致如下:

  1.ThreadLocalMap底層用數組保存元素,當get一個Entry時,根據key的hash值(非hashCode)計算出該Entry應該出在什麼位置;

  2.計算出的位置可能會有衝突,比如預期位置是position=5,但是position=5的位置已經有其他Entry了;

  3.出現衝突后,會使用線性探測法,找position=6位置上的Entry是否匹配(匹配是指hash相同),如果匹配,則返回position=6的Entry。

  4.在這個過程中,如果position=5位置上的Entry已經是陳舊的Entry(Entry的key為null),此時position=5的key就應該被清理;

  5.光清理position=5的Entry還不夠,為了保證線性探測法的規則,需要判斷數組中的其他元素是否需要調整位置(如果需要,則調整位置),在這個過程中,也會進行清理陳舊Entry的操作。

  上面這5個步驟就保證了每次get都會清理數組中(map)的陳舊Entry,清理一個陳舊的Entry,就是下面這三行代碼:

Entry.value = null; // 將Entry的value設為null
table[index] = null;// 將數組中該Entry的位置設置null
size--;	// map的size減一

  對於ThreadLocal的set、remove也類似這個原理。

  有了自動回收陳舊Entry的操作,需要注意的是,在這個時候,key使用弱引用就是至關重要的一點!!!

  因為key使用弱引用后,當弱引用的ThreadLocal對象被會回收后,該key的引用為null,則該Entry在下一次get、set、remove的時候就才會被清理,從未避免內存泄漏的問題。

  

四.總結

  在上面的分析中,看到ThreadLocal基本不會出現內存泄漏的問題了,因為ThreadLocalMap中會在get、set、remove的時候清理陳舊的Entry,與Entry的key使用弱引用密不可分。

  當然我們也可以在代碼中手動調用ThreadLocal的remove方法進行清除map中key為該threadLocal對象的Entry,同時清理過期的Entry。

  

 

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

又要到處浪又要省油,有這種好事?這5款車就行

以不到30萬的價格買到一輛混動的大7座SUV,還要什麼入門BBA。再來看看同樣換新顏、尺寸大一號的全新秦pro,新車搭載一套由BYD476ZQA型1。5T發動機、電動機和鋰電池組構成的插電式混動系統后,工信部申報綜合油耗僅為1升/百公里。

前幾天正巧試駕了朋友剛提不久的雷克薩斯CT200h,除了非常不錯的操控感和車身動態表現之外,讓我印象最深刻的就是油耗,在一頓猛踩和駕駛模式來回切換之後,平均油耗也不過7L/百公里,我相信還有其他車主做出了更漂亮的數據。

雖然當初一早就布局混動車型市場的CT200h並沒有因此而大賣,但發展到今天,隨着混動技術的不斷成熟和國內市場政策的風雲變幻,已經有越來越多的消費者被混動車型的諸多優勢所打動,比如省油、安靜、駕駛體驗等等。所以今天就給大家盤點一下,北京車展之後都有哪些混動車型是值得消費者密切關注的。

1、卡羅拉/雷凌pHEV插電混動版

卡羅拉和雷凌可以說是豐田的兩個“開掛”車型,從燃油版到HEV油電混動版(雙擎)從來都不愁賣,而此次北京車展上豐田終於兌現了兩年前的承諾,在國內正式發布了卡羅拉/雷凌pHEV插電混動版車型。

這兩款新車可以說是含着金鑰匙出生的“富二代”,畢竟豐田多年以來經營的品牌形象、技術實力和市場口碑為這二者打下了堅實的銷量基礎,所以目測上市后月銷1萬輛是基本操作,想要入手的朋友們可要盯緊了。

在外形上兩款新車與普通版的差別並不大,僅針對細節進行了微調,比如車頭和車尾部分都加入了藍色元素。此外,由於插電混動版搭載了更大容量的電池組,所以內部空間與普通版車型稍有差異。

值得一提的是,這兩款車屬於中國特供車,意味着自帶銷量BUG,並且其搭載了1.8L自然吸氣發動機+電動機組,同時還配備容量為9-13kWh的鋰離子電池組,純電動條件下的續航里程可超過100km。

這是神馬概念?起碼就目前我國市場上銷售的插電混動車型,大部分純電動續航里程都只在50-80km以內,並且考慮到緊湊級車推出pHEV版本的多是自主品牌車型,而它們的老對手軒逸、朗逸等還沒加入這個市場,所以此次亮相的卡羅拉和雷凌pHEV版本只要定價合適,銷量應該不成問題,別忘了它們還可獲得一部分政策補貼哦(雙擎版本沒有)。

2、領克01 pHEV插電混動版

說領克01 pHEV版之前必先提一提燃油版領克01,這款車型自去年11月上市以來便憑藉優秀的做工和沃爾沃技術的加持迅速圈粉,獲得市場的好評如潮。本以為領克會按部就班、穩紮穩打地推出新能源產品,不過眼看WEY氣勢洶洶地推出了p8之後,領克怕也是坐不太住了,於是便適時地推出了領克01 pHEV版。

在外觀和內飾方面,該車和之前的燃油版車型基本相同,只是在前進氣格柵以及前翼子板處增加了藍色元素進行點綴,表明其新能源型的身份。而尾部右下側還貼有“01 pHEV”的字樣。

動力方面,領克01 pHEV則搭載了1.5T發動機和電動機所組成的插電式混合動力系統,發動機最大凈功率約180馬力,動力電池為鎳鈷錳三元鋰離子電池,官方宣稱該車的百公里油耗僅為1.8L,同時其純電動模式下最大行駛里程為51km。如果想要入手朋友,其量產車型最快會在今年年內上市,所以準備好鈔票就是了。

3、比亞迪唐DM/秦pro

我們都知道,自從前奧迪設計師艾格入主比亞迪之後,旗下打頭陣車型瞬間秒變高富帥,就連之前被人嫌棄的“BYD”都多了一個洋氣的註釋–Build Your Dream!如果你還沒什麼畫面感的話,在此貢獻兩張圖讓大家感受一下:

了解更多點擊視頻:

全新一代唐从里裡外外都整了個遍,多了幾分國際范。首先在外觀上採用了家族最新的Dragon Face設計語言,整體大氣而又十分吸睛;

內飾同樣話題性十足,其中最搶眼的部分當屬中控可90°旋轉的超級大屏,同時全液晶儀錶和藍色氛圍燈增加了車內的科技感。

動力方面,其將搭載2.0T發動機與電動機組成的插電式混動系統,系統綜合最大功率超過500馬力,而高配車型百公里加速僅需4.5秒,咳咳划重點了–奔馳GLC43 AMG的成績為4.9秒。

值得一提的是,新一代唐標配7座布局,其長寬高分別為4870/1940/1720mm,軸距為2820mm,相比上一代車型在軸距和寬度上有所增加,所以能帶來更寬敞的乘坐體驗。另外在此次北京車展,新一代唐公布預售價–補貼后25萬起步。港真!以不到30萬的價格買到一輛混動的大7座SUV,還要什麼入門BBA!

再來看看同樣換新顏、尺寸大一號的全新秦pro,新車搭載一套由BYD476ZQA型1.5T發動機、電動機和鋰電池組構成的插電式混動系統后,工信部申報綜合油耗僅為1升/百公里!並且根據車尾標判斷,新車的官方百公里加速時間或為5.9秒,看來是充分體現了什麼叫“雨露均沾”了。

4、廣汽謳歌CDX混動版

每每提到謳歌這個品牌,總有兩道跨不過的坎,一個是跟長安LOGO抹不去的姻緣,另一個就是跟隔壁家的雷克薩斯比境遇,很顯然以上兩項都是招黑的。不過也必須承認,痛定思痛的謳歌如今似乎也有點重新步入正軌的意思了。

在此次北京車展,廣汽謳歌就帶來了CDX的混動版本,並且從售價上看還是蠻有誠意的。新車共推出了三款車型,售價區間為29.98-35.28萬元,同時新車外觀顏色增加磨砂皓灰和混動版專屬的藍紫色車漆,內飾則增加了紅色內飾可選,而頂配版車型還將增配Acura Watch。

畢竟謳歌也是“本田大法”的受益者,所以混動版車型相比同級別對手的較大優勢就體現在動力系統上。新車將搭載本田的i-MMD混合動力系統,其由一台2.0L的阿特金森循環自然吸氣發動機、由驅動電機和發電機組成的“電動CVT”直驅傳動機構、動力控制單元及鋰電池組等組成。

這套系統可以根據負載和使用條件的不同,在純電動、混合動力和發動機直驅三種模式之間無縫切換。參數方面,2.0L發動機最大功率146馬力,混動系統綜合最大功率215馬力,而官方公布的綜合油耗為5.0L/百公里。所以三十萬買一台省油的豪華SUV,好像很不錯的樣子!

5、雷克薩斯ES混動版

此次北京車展雷克薩斯的展台只放了兩台車,沒錯!只有兩台!在大多數人看來這就是典型的“寒酸”,而在雷粉眼裡這就叫“傲嬌”,因為雷克薩斯選擇把ES混動版的全球首發放在了中國,雖說一直不願意國產,雖說看展的觀眾不一定get到這樣做的用心良苦,但足以說明其對中國市場的看重。

雷克薩斯全新一代ES基於TNGA平台打造,除搭載全新2.0L發動機外,還配有2.5L多級混合動力系統,也就是全新凱美瑞上用的那一套,沒錯,就是傳說中41%熱效率的那一款名機。同時它還搭載了結構更緊湊的全新一代E-CVT电子無極變速系統,以及布局更為合理的鎳氫電池組。另外,最高純電巡航時速由前一代的75km/h提升到120km/h。

如想了解更多,點擊下方視頻:

結語

從當初雷克薩斯CT200h的“天生異質”,到如今各家廠商混合動力車型的日漸成熟,可以說能活下來的、能在市場上銷售的技術都是好技術。不管是自主品牌的比亞迪、長城、吉利,還是合資品牌的兩田,甚至是進口品牌,都在加緊布局新能源戰線,所以這對我們消費者來說無疑是個大利好。至於混動車型值不值得買這樣俗套的話題,我想就不用多糾結了,畢竟買車從來都是按需購買的。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

隨機抽樣一致性(RANSAC)算法詳解

隨機抽樣一致性(RANSAC)算法能夠有效的剔除特徵匹配中的錯誤匹配點。

實際上,RANSAC能夠有效擬合存在噪聲模型下的擬合函數。實際上,RANSAC算法的核心在於將點劃分為“內點”和“外點”。在一組包含“外點”的數據集中,採用不斷迭代的方法,尋找最優參數模型,不符合最優模型的點,被定義為“外點”。這就是RANSAC的核心思想。

RANSAC原理

OpenCV中濾除誤匹配對採用RANSAC算法尋找一個最佳單應性矩陣H,矩陣大小為3×3。RANSAC目的是找到最優的參數矩陣使得滿足該矩陣的數據點個數最多,通常令h33=1來歸一化矩陣。由於單應性矩陣有8個未知參數,至少需要8個線性方程求解,對應到點位置信息上,一組點對可以列出兩個方程,則至少包含4組匹配點對

 

 

 RANSAC算法從匹配數據集中隨機抽出4個樣本並保證這4個樣本之間不共線,計算出單應性矩陣,然後利用這個模型測試所有數據,並計算滿足這個模型數據點的個數與投影誤差(即代價函數),若此模型為最優模型,則對應的代價函數最小。

損失函數:

 

 

 也就是通過隨機抽樣求解得到一個矩陣,然後驗證其他的點是否符合模型,然後符合的點成為“內點”,不符合的點成為“外點”。下次依然從“新的內點集合”中抽取點構造新的矩陣,重新計算誤差。最後誤差最小,點數最多就是最終的模型。

RANSAC算法步驟:

RANSAC算法步驟: 

          1. 隨機從數據集中隨機抽出4個樣本數據 (此4個樣本之間不能共線),計算出變換矩陣H,記為模型M;

          2. 計算數據集中所有數據與模型M的投影誤差,若誤差小於閾值,加入內點集 I ;

          3. 如果當前內點集 I 元素個數大於最優內點集 I_best , 則更新 I_best = I,同時更新迭代次數k ;

          4. 如果迭代次數大於k,則退出 ; 否則迭代次數加1,並重複上述步驟;

  注:迭代次數k在不大於最大迭代次數的情況下,是在不斷更新而不是固定的;

 

 

 其中,p為置信度,一般取0.995;w為”內點”的比例 ; m為計算模型所需要的最少樣本數=4;
關於RANSAC算法的思想,可以用下圖表示

 

 也就是RANSAC算法的本質是:在存在噪聲的數據中,我們求解一個模型,使得非噪聲數據可以用該模型表示,而噪聲數據被排除在外。

分享三個講解RANSAC算法的網址:

https://www.csdn.net/gather_2d/MtjaMg3sNDAwNS1ibG9n.html

https://www.cnblogs.com/xrwang/archive/2011/03/09/ransac-1.html

https://blog.csdn.net/yanghan742915081/article/details/83005442

 

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

微服務技術棧:常見註冊中心組件,對比分析

本文源碼:GitHub·點這裏 || GitEE·點這裏

一、註冊中心簡介

1、基礎概念

在分佈式架構的系統中註冊中心這個概念就已經被提出了,最經典的就是Zookeeper中間件。

微服務架構中,註冊中心是最核心的基礎服務之一,註冊中心可以看做是微服務架構中的通信中心,當一個服務去請求另一個服務時,通過註冊中心可以獲取該服務的狀態,地址等核心信息。

服務註冊主要關係到三大角色:服務提供者、服務消費者、註冊中心。

2、流程和原理

基礎流程

  • 服務啟動時,將自身的網絡地址等信息註冊到註冊中心,註冊中心記錄服務註冊數據。
  • 服務消費者從註冊中心獲取服務提供者的地址,並通過地址和基於特定的方式調用服務提供者的接口。
  • 各個服務與註冊中心使用一定機制通信。如果註冊中心與服務長時間無法通信,就會註銷該實例,這也稱為服務下線,當服務重新連接之後,會基於一定的策略在線上線。
  • 服務地址相關信息發生變化時,會重新註冊到註冊中心。這樣,服務消費者就無需手工維護提供者的相關配置。

核心功能

通過上面的基本流程,不難發現一個註冊中心需要具備哪些核心功能:

  • 服務發現

服務發現是指服務在啟動后,註冊到註冊中心,服務方提供自身的元數據,比如IP地址、端口、運行狀況指標的Uri 、主頁地址等信息。

  • 服務記錄

記錄註冊中心的服務的信息,例如服務名稱、IP地址、端口等。服務消費方基於查詢獲取可用的服務實例列表。

  • 動態管理服務

註冊中心基於特定的機制定時測試已註冊的服務,例如:默認的情況下會每隔30秒發送一次心跳來進行服務續約。通過服務續約來告知Server該Client仍然可用。正常情況下,如果Server在90 秒內沒有收到Client 的心跳,Server會將Client 實例從註冊列表中刪除。

二、基礎組件對比

1、Zookeeper組件

1.1基礎描述

ZooKeeper是非常經典的服務註冊中心中間件,在國內環境下,由於受到Dubbo框架的影響,大部分情況下認為Zookeeper是RPC服務框架下註冊中心最好選擇,隨着Dubbo框架的不斷開發優化,和各種註冊中心組件的誕生,即使是RPC框架,現在的註冊中心也逐步放棄了ZooKeeper。在常用的開發集群環境中,ZooKeeper依然起到十分重要的作用,Java體系中,大部分的集群環境都是依賴ZooKeeper管理服務的各個節點。

1.2組件特點

從Zookeeper的數據結構特點看,並不是基於服務註冊而設計的,ZooKeeper提供的命名空間與文件系統的名稱空間非常相似,在數據結構上高度抽象為K-V格式,十分通用,說到這裏不得不提一下Redis,也可以作為註冊中心使用,只是用的不多。

ZooKeeper組件支持節點短暫存在,只要創建znode的會話處於活動狀態,這些znode就會存在,會話結束時,將刪除znode。Dubbo框架正是基於這個特點,服務啟動往Zookeeper註冊的就是臨時節點,需要定時發心跳到Zookeeper來續約節點,並允許服務下線時,將Zookeeper上相應的節點刪除,同時Zookeeper使用ZAB協議雖然保證了數據的強一致性。

2、Eureka組件

2.1基礎描述

SpringCloud框架生態中最原生的深度結合組件,Eureka是Netflix開發的服務發現框架,基於REST的服務,主要用於服務註冊,管理,負載均衡和服務故障轉移。但是官方聲明在Eureka2.0版本停止維護,不建議使用。

2.2組件特點

Eureka包含兩個組件:EurekaServer和EurekaClient。

EurekaServer提供服務註冊服務,各個節點啟動后,會在EurekaServer中進行註冊,這樣EurekaServer中的服務註冊表中將會存儲所有可用服務節點的信息,服務節點的信息可以在界面中直觀的看到。Eureka允許在註冊服務的時候,自定義實現檢查自身狀態的是否健康的方法,這在服務實例能夠保持心跳上報的場景下,是一種比較好的體驗。

EurekaClient是一個java客戶端,用於簡化與EurekaServer的交互,客戶端同時也就是一個內置的、使用輪詢(round-robin)負載算法的負載均衡器。

3、Consul組件

3.1基礎描述

Consul是用於服務發現和配置的工具。Consul是分佈式的,高度可用的,並且具有極高的可伸縮性,而且開發使用都很簡便。它提供了一個功能齊全的控制面板,主要特點是:服務發現、健康檢查、鍵值存儲、安全服務通信、多數據中心、ServiceMesh。Consul在設計上把很多分佈式服務治理上要用到的功能都包含在內了。

3.2組件特點

Consul提供多個數據中心的支持,基於Fabio做負載均衡,每個數據中心內,都有客戶端和服務端的混合構成。預計有三到五台服務端。可以在失敗和性能的可用性之間取得良好的平衡。數據中心中的所有節點都參与八卦協議。這意味着有一個八卦池,其中包含給定數據中心的所有節點。這有幾個目的:首先,不需要為客戶端配置服務器的地址;發現是自動完成的。其次,檢測節點故障的工作不是放在服務器上,而是分佈式的。這使得故障檢測比天真的心跳方案更具可擴展性。第三,它被用作消息傳遞層,用於在諸如領導者選舉等重要事件發生時進行通知。

4、Nacos組件

4.1基礎描述

Nacos致力於發現、配置和管理微服務。Nacos提供了一組簡單易用的特性集,幫助您實現動態服務發現、服務配置管理、服務及流量管理。Nacos更敏捷和容易地構建、交付和管理微服務平台。 Nacos 是構建以“服務”為中心的現代應用架構(例如微服務範式、雲原生範式)的服務基礎設施。Nacos支持作為RPC註冊中心,例如:支持Dubbo框架;也具備微服務註冊中心的能力,例如:SpringCloud框架。

4.2組件特點

Nacos在經過多年生產經驗后提煉出的數據模型,則是一種服務-集群-實例的三層模型。如上文所說,這樣基本可以滿足服務在所有場景下的數據存儲和管理,數據模型雖然相對複雜,但是並不強制使用數據結構的風格,大多數應用場景下,和Eureka數據模型是類似的。

Nacos提供數據邏輯隔離模型,用戶賬號可以新建多個命名空間,每個命名空間對應一個客戶端實例,這個命名空間對應的註冊中心物理集群是可以根據規則進行路由的,這樣可以讓註冊中心內部的升級和遷移對用戶是無感知的。

三、組件選擇

如下註冊中心對比圖。

綜合上述幾種註冊中心對比,再從現在SpringCloud框架流行趨勢看,個人推薦後續微服務架構體系選擇Nacos組件,大致原因如下,社區活躍,經過大規模業務驗證,不但可以作為微服務註冊中心,也支持作RPC框架Dubbo的註冊中心,且有完善的中文文檔,總結下來就一句話:通用中間件,省時;文檔詳細,省心。

四、源代碼地址

GitHub·地址
https://github.com/cicadasmile/husky-spring-cloud
GitEE·地址
https://gitee.com/cicadasmile/husky-spring-cloud

推薦文章:微服務基礎系列

序號 文章標題
01 微服務基礎:Eureka組件,管理服務註冊發現
02 微服務基礎:Ribbon和Feign組件,實現請求負載均衡
03 微服務基礎:Hystrix組件,實現服務熔斷
04 微服務基礎:Turbine組件,實現微服務集群監控
05 微服務基礎:Zuul組件,實現路由網關控制
06 微服務基礎:Config組件,實現配置統一管理
07 微服務基礎:Zipkin組件,實現請求鏈路追蹤
08 微服務基礎:與Dubbo框架、Boot框架對比分析
09 微服務基礎:Nacos組件,服務和配置管理
10 微服務基礎:Sentinel組件,服務限流和降級
11 微服務應用:分庫分表模式下,數據庫擴容方案
12 微服務應用:Shard-Jdbc分庫分表,擴容方案實現

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

同級最便宜,這款20多萬的超帥氣SUV車主卻說……

0L混動最大馬力(pS):182/發動機:146 電動機:184最大扭矩(Nm):240/發動機:175 電動機:315變速箱:8DCT/E-CVT百公里加速(s):8。35/8。6百公里油耗(L):6。8/5車主百公里油耗(L):8。71/暫

謳歌CDX肩負着在國內為謳歌品牌打開市場的重任,它具備極其前衛的設計風格,而且看到實車時你就會感受到其設計的精緻感!

寬敞的空間,前衛設計讓它具有不錯的競爭力。而且它搭載一款1.5T高功率發動機,匹配一副8擋雙離合變速箱,動力系統匹配是高效的,它換擋平順,降擋的速度也相當快,動力表現可圈可點!下面我們就來看看這款豪華SUV車型的各方面表現怎麼樣吧!

長寬高:4496*1840*1615mm

軸距:2660mm

定位:緊湊型SUV

設計激進的謳歌CDX可謂是同級車型中的一股清流,前臉造型極其激進,鑽石形中網造型顯得相當有個性。同時車身線條緊湊,但車內卻能營造出足夠的乘坐空間。

內飾方面它在細節處富有創意,造型類似“如意”的旋鈕擋把設計科技感強,而內飾的用料也是到位的。值得一提的是這套內飾的按鍵布局簡約、合理,方向盤上的按鍵也具備不錯手感,在行車過程中操作便利!

發動機:1.5T/2.0L混動

最大馬力(pS):182/發動機:146 電動機:184

最大扭矩(Nm):240/發動機:175 電動機:315

變速箱:8DCT/E-CVT

百公里加速(s):8.35/8.6

百公里油耗(L):6.8/5

車主百公里油耗(L):8.71/暫本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

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

UniRx精講(二):獨立的 Update &UniRx 的基本語法格式

獨立的 Update

在 UniRx 簡介的時候,筆者講了一種比較麻煩的情況:就是在 MonoBehaviour 的 Update 中摻雜了大量互相無關的邏輯,導致代碼非常不容易閱讀。

這種情況我們平時在項目開發中非常常見,代碼如下:

private void Update()
{
	if (A)
	{
		...
	}

	if (B)
	{
		...
		if (D)
		{
			...
		}
		else {}
	}

	switch (C)
	{
		...
	}

	if (Input.GetMouseButtonUp(0))
	{
		...
	}
}

Update 方法中代碼冗長,而且干擾視線,非常影響閱讀。

而使用 UniRx 則可以改善這個問題。

void Start()
{
	// A 邏輯,實現了 xx
	Observable.EveryUpdate()
			.Subscribe(_ => 
			{
				if (A)
				{
					...
				}
			}).AddTo(this);
	

	// B 邏輯,實現了 xx
	Observable.EveryUpdate()
			.Subscribe(_ =>
			{
				if (B)
				{
					...
					if (D)
					{
						...
					}
				else {}
				}
			}).AddTo(this);

	// C 邏輯,實現了 xx
	Observable.EveryUpdate()
			.Subscribe(_ =>
			{
				switch (C)
				{
					...
				}
			}).AddTo(this);

	// 鼠標點擊檢測邏輯
	Observable.EveryUpdate()
			.Subscribe(_ => {
			{
				if (Input.GetMouseButtonUp(0))
				{
					...
				}
			}).AddTo(this);
}

雖然在代碼長度上沒有任何改善,但是最起碼,這些 Update 邏輯互相之間獨立了。
狀態跳轉、延時等等這些經常在 Update 里實現的邏輯,都可以使用以上這種方式獨立。

使用 UniRx 可以對我們工程中的代碼進行了改善,而筆者接觸 UniRx 之後,就再也沒有使用過 Update 方法了。

不過以上的這種 UniRx 使用方式,是比較初級的,而這種使用方式,隨着對 UniRx 的深入學習,也會漸漸淘汰,因為等我們入門之後,會學習更好的實現方式。

今天的內容就這些。

知識地圖

UniRx 的基本語法格式

在之前的兩篇文章中,我們學習了 UniRx 的 Timer 和 Update 這兩個 API,但是對代碼的工作原理還沒有進行過介紹。在這篇文章中,我們就來試着理解一下 UniRx 的代碼工作原理及 UniRx 的基本語法格式。

先搬出來第一篇文章中 Delay 的實現代碼:

/****************************************************************************
 * http://liangxiegame.com liangxie
 ****************************************************************************/
 
using System;
using UniRx;
using UnityEngine;

namespace UniRxLesson
{
	public class DelayExample : MonoBehaviour
	{
		private void Start()
		{
			Observable.Timer(TimeSpan.FromSeconds(2.0f)).Subscribe(_ =>
			{
				Debug.Log("延時兩秒"); 
				
			}).AddTo(this);
		}
	}
}

代碼中的 Observable.XXX().Subscribe() 是非常經典的 UniRx 格式。只要理解了這種格式就可以看懂大部分的 UniRx 的用法了。

首先解決代碼中的詞彙問題:

  • Observable:可觀察的,是形容詞,它形容後邊的詞(Timer)是可觀察的,我們可以直接把 Observable 後邊的詞理解成發布者。
  • Timer:定時器,名詞,被 Observable 修飾,所以是發布者,是事件的發送方。
  • Subscribe:訂閱,是動詞,它訂閱誰呢?當然是前邊的 Timer,這裏可以理解成訂閱者,也就是事件的接收方。
  • AddTo:添加到,這個我們暫時不用理解得太深刻,只需要知道它是與 MonoBehaviour 進行生命周期綁定即可。

以上的代碼,連起來則是:可被觀察(監聽)的.Timer().訂閱()
理順了之後應該是:訂閱可被觀察的定時器。

其概念關係很容易理解。

  • Timer 是可觀察的。
  • 可觀察的才能被訂閱。
Observable.XXX().Subscribe();

這行代碼我們可以理解為:可被觀察(監聽)的 XX,註冊。

以上筆者從發布者和訂閱者這個角度進行了簡單的介紹,以便大家理解。
但是 UniRx 的側重點,不是發布者和訂閱者這兩個概念如何使用,而是事件從發布者到訂閱者之間的過程如何處理。
所以這兩個點不重要,重要的是兩點之間的線,也就是事件的傳遞過程。

這裏先不說得太深入,在入門之後,會用很大的篇幅去深入介紹這些概念的。

今天的 UniRx 的基本語法格式的介紹就到這裏,我們下一篇再見,拜拜~

知識地圖

更多內容
QFramework 地址:https://github.com/liangxiegame/QFramework
QQ 交流群:623597263
涼鞋的主頁:https://liangxiegame.com/zhuanlan
關注公眾號:liangxiegame 獲取第一時間更新通知及更多的免費內容。

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案