上一篇文章我们讲了怎么用Node.js原生API来写一个web服务器,虽然代码比较丑,但是基本功能还是有的。但是一般我们不会直接用原生API来写,而是借助框架来做,比如本文要讲的Express。通过上一篇文章的铺垫,我们可以猜测,Express其实也没有什么黑魔法,也仅仅是原生API的封装,主要是用来提供更好的扩展性,使用起来更方便,代码更优雅。本文照例会从Express的基本使用入手,然后自己手写一个Express来替代他,也就是源码解析。

本文可运行代码已经上传GitHub,拿下来一边玩代码,一边看文章效果更佳:https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/Node.js/Express

简单示例

使用Express搭建一个最简单的Hello World也是几行代码就可以搞定,下面这个例子来源官方文档:

const express = require('express');
const app = express();
const port = 3000; app.get('/', (req, res) => {
res.send('Hello World!');
}); app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`);
});

可以看到Express的路由可以直接用app.get这种方法来处理,比我们之前在http.createServer里面写一堆if优雅多了。我们用这种方式来改写下上一篇文章的代码:

const path = require("path");
const express = require("express");
const fs = require("fs");
const url = require("url"); const app = express();
const port = 3000; app.get("/", (req, res) => {
res.end("Hello World");
}); app.get("/api/users", (req, res) => {
const resData = [
{
id: 1,
name: "小明",
age: 18,
},
{
id: 2,
name: "小红",
age: 19,
},
];
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify(resData));
}); app.post("/api/users", (req, res) => {
let postData = "";
req.on("data", (chunk) => {
postData = postData + chunk;
}); req.on("end", () => {
// 数据传完后往db.txt插入内容
fs.appendFile(path.join(__dirname, "db.txt"), postData, () => {
res.end(postData); // 数据写完后将数据再次返回
});
});
}); app.listen(port, () => {
console.log(`Server is running on http://localhost:${port}/`);
});

Express还支持中间件,我们写个中间件来打印出每次请求的路径:

app.use((req, res, next) => {
const urlObject = url.parse(req.url);
const { pathname } = urlObject; console.log(`request path: ${pathname}`); next();
});

Express也支持静态资源托管,不过他的API是需要指定一个文件夹来单独存放静态资源的,比如我们新建一个public文件夹来存放静态资源,使用express.static中间件配置一下就行:

app.use(express.static(path.join(__dirname, 'public')));

然后就可以拿到静态资源了:

手写源码

手写源码才是本文的重点,前面的不过是铺垫,本文手写的目标就是自己写一个express来替换前面用到的express api,其实就是源码解析。在开始之前,我们先来看看用到了哪些API:

  1. express(),第一个肯定是express函数,这个运行后会返回一个app的实例,后面用的很多方法都是这个app上的。
  2. app.listen,这个方法类似于原生的server.listen,用来启动服务器。
  3. app.get,这是处理路由的API,类似的还有app.post等。
  4. app.use,这是中间件的调用入口,所有中间件都要通过这个方法来调用。
  5. express.static,这个中间件帮助我们做静态资源托管,其实是另外一个库了,叫serve-static,因为跟Express架构关系不大,本文就先不讲他的源码了。

本文所有手写代码全部参照官方源码写成,方法名和变量名尽量与官方保持一致,大家可以对照着看,写到具体的方法时我也会贴出官方源码的地址。

express()

首先需要写的肯定是express(),这个方法是一切的开始,他会创建并返回一个app,这个app就是我们的web服务器。

// express.js
var mixin = require('merge-descriptors');
var proto = require('./application'); // 创建web服务器的方法
function createApplication() {
// 这个app方法其实就是传给http.createServer的回调函数
var app = function (req, res) { }; mixin(app, proto, false); return app;
} exports = module.exports = createApplication;

上述代码就是我们在运行express()的时候执行的代码,其实就是个空壳,返回的app暂时是个空函数,真正的app并没在这里,而是在proto上,从上述代码可以看出proto其实就是application.js,然后通过下面这行代码将proto上的东西都赋值给了app:

mixin(app, proto, false);

这行代码用到了一个第三方库merge-descriptors,这个库总共没有几行代码,做的事情也很简单,就是将proto上面的属性挨个赋值给app,对merge-descriptors源码感兴趣的可以看这里:https://github.com/component/merge-descriptors/blob/master/index.js。

Express这里之所以使用mixin,而不是普通的面向对象来继承,是因为它除了要mixin proto外,还需要mixin其他库,也就是需要多继承,我这里省略了,但是官方源码是有的。

express.js对应的源码看这里:https://github.com/expressjs/express/blob/master/lib/express.js

app.listen

上面说了,express.js只是一个空壳,真正的app在application.js里面,所以app.listen也是在这里。

// application.js

var app = exports = module.exports = {};

app.listen = function listen() {
var server = http.createServer(this);
return server.listen.apply(server, arguments);
};

上面代码就是调用原生http模块创建了一个服务器,但是传的参数是this,这里的this是什么呢?回想一下我们使用express的时候是这样用的:

const app = express();

app.listen(3000);

所以listen方法的实际调用者是express()的返回值,也就是上面express.js里面createApplication的返回值,也就是这个函数:

var app = function (req, res) {
};

所以这里的this也是这个函数,所以我在express.js里面就加了注释,这个函数是http.createServer的回调函数。现在这个函数是空的,实际上他应该是整个web服务器的处理入口,所以我们给他加上处理的逻辑,在里面再加一行代码:

var app = function(req, res) {
app.handle(req, res); // 这是真正的服务器处理入口
};

app.handle

app.handle也是挂载在app下面的,所以他实际也在application.js这个文件里面,下面我们来看看他干了什么:

app.handle = function handle(req, res) {
var router = this._router; // 最终的处理方法
var done = finalhandler(req, res); // 如果没有定义router
// 直接结束返回
if (!router) {
done();
return;
} // 有router,就用router来处理
router.handle(req, res, done);
}

上面代码可以看出,实际处理路由的是router,这是Router的一个实例,并且挂载在this上的,我们这里还没有给他赋值,如果没有赋值的话,会直接运行finalhandler并且结束处理。finalhandler也是一个第三方库,GitHub链接在这里:https://github.com/pillarjs/finalhandler。这个库的功能也不复杂,就是帮你处理一些收尾的工作,比如所有路由都没匹配上,你可能需要返回404并记录下error log,这个库就可以帮你做。

app.get

上面说了,在具体处理网络请求时,实际上是用app._router来处理的,那么app._router是在哪里赋值的呢?事实上app._router的赋值有多个地方,一个地方就是HTTP动词处理方法上,比如我们用到的app.get或者app.post。无论是app.get还是app.post都是调用的router方法来处理,所以可以统一用一个循环来写这一类的方法。

// HTTP动词的方法
var methods = ['get', 'post'];
methods.forEach(function (method) {
app[method] = function (path) {
this.lazyrouter(); var route = this._router.route(path);
route[method].apply(route, Array.prototype.slice.call(arguments, 1));
return this;
}
});

上面代码HTTP动词都放到了一个数组里面,官方源码中这个数组也是一个第三方库维护的,名字就叫methods,GitHub地址在这里:https://github.com/jshttp/methods。我这个例子因为只需要两个动词,就简化了,直接用数组了。这段代码其实给app创建了跟每个动词同名的函数,所有动词的处理函数都是一样的,都是去调router里面的对应方法来处理。这种将不同部分抽取出来,从而复用共同部分的代码,有点像我之前另一篇文章写过的设计模式----享元模式。

我们注意到上面代码除了调用router来处理路由外,还有一行代码:

this.lazyrouter();

lazyrouter方法其实就是我们给this._router赋值的地方,代码也比较简单,就是检测下有没有_router,如果没有就给他赋个值,赋的值就是Router的一个实例:

app.lazyrouter = function lazyrouter() {
if (!this._router) {
this._router = new Router();
}
}

app.listen,app.handle和methods处理方法都在application.js里面,application.js源码在这里:https://github.com/expressjs/express/blob/master/lib/application.js

Router

写到这里我们发现我们已经使用了Router的多个API,比如:

  1. router.handle
  2. router.route
  3. route[method]

所以我们来看下Router这个类,下面的代码是从源码中简化出来的:

// router/index.js
var setPrototypeOf = require('setprototypeof'); var proto = module.exports = function () {
function router(req, res, next) {
router.handle(req, res, next);
} setPrototypeOf(router, proto); return router;
}

这段代码对我来说是比较奇怪的,我们在执行new Router()的时候其实执行的是new proto(),new proto()并不是我奇怪的地方,奇怪的是他设置原型的方式。我之前在讲JS的面向对象的文章提到过如果你要给一个类加上类方法可以这样写:

function Class() {}

Class.prototype.method1 = function() {}

var instance = new Class();

这样instance.__proto__就会指向Class.prototype,你就可使用instance.method1了。

Express.js的上述代码其实也是实现了类似的效果,setprototypeof又是一个第三方库,作用类似Object.setPrototypeOf(obj, prototype),就是给一个对象设置原型,setprototypeof存在的意义就是兼容老标准的JS,也就是加了一些polyfill,他的代码在这里。所以:

setPrototypeOf(router, proto);

这行代码的意思就是让router.__proto__指向proto,router是你在new proto()时的返回对象,执行了上面这行代码,这个router就可以拿到proto上的全部方法了。像router.handle这种方法就可以挂载到proto上了,成为proto.handle。

绕了一大圈,其实就是JS面向对象的使用,给router添加类方法,但是为什么使用这么绕的方式,而不是像我上面那个Class那样用呢?这我就不是很清楚了,可能有什么历史原因吧。

路由架构

Router的基本结构知道了,要理解Router的具体代码,我们还需要对Express的路由架构有一个整体的认识。就以我们这两个示例API来说:

get /api/users

post /api/users

我们发现他们的path是一样的,都是/api/users,但是他们的请求方法,也就是method不一样。Express里面将path这一层提取出来作为了一个类,叫做Layer。但是对于一个Layer,我们只知道他的path,不知道method的话,是不能确定一个路由的,所以Layer上还添加了一个属性route,这个route上也存了一个数组,数组的每个项存了对应的method和回调函数handle。整个结构你可以理解成这个样子:

const router = {
stack: [
// 里面很多layer
{
path: '/api/users'
route: {
stack: [
// 里面存了多个method和回调函数
{
method: 'get',
handle: function1
},
{
method: 'post',
handle: function2
}
]
}
}
]
}

知道了这个结构我们可以猜到,整个流程可以分成两部分:注册路由和匹配路由。当我们写app.get和app.post这些方法时,其实就是在router上添加layer和route。当一个网络请求过来时,其实就是遍历layer和route,找到对应的handle拿出来执行。

注意route数组里面的结构,每个项按理来说应该使用一种新的数据结构来存储,比如routeItem之类的。但是Express并没有这样做,而是将它和layer合在一起了,给layer添加了method和handle属性。这在初次看源码的时候可能造成困惑,因为layer同时存在于router的stack上和route的stack上,肩负了两种职责。

router.route

这个方法是我们前面注册路由的时候调用的一个方法,回顾下前面的注册路由的方法,比如app.get:

app.get = function (path) {
this.lazyrouter(); var route = this._router.route(path);
route.get.apply(route, Array.prototype.slice.call(arguments, 1));
return this;
}

结合上面讲的路由架构,我们在注册路由的时候,应该给router添加对应的layer和route,router.route的代码就不难写出了:

proto.route = function route(path) {
var route = new Route();
var layer = new Layer(path, route.dispatch.bind(route)); // 参数是path和回调函数 layer.route = route; this.stack.push(layer); return route;
}

Layer和Route构造函数

上面代码新建了Route和Layer实例,这两个类的构造函数其实也挺简单的。只是参数的申明和初始化:

// layer.js
module.exports = Layer; function Layer(path, fn) {
this.path = path; this.handle = fn;
this.method = '';
}
// route.js
module.exports = Route; function Route() {
this.stack = [];
this.methods = {}; // 一个加快查找的hash表
}

route.get

前面我们看到了app.get其实通过下面这行代码,最终调用的是route.get:

route.get.apply(route, Array.prototype.slice.call(arguments, 1));

也知道了route.get这种动词处理函数,其实就是往route.stack上添加layer,那我们的route.get也可以写出来了:

var methods = ["get", "post"];
methods.forEach(function (method) {
Route.prototype[method] = function () {
// 支持传入多个回调函数
var handles = flatten(slice.call(arguments)); // 为每个回调新建一个layer,并加到stack上
for (var i = 0; i < handles.length; i++) {
var handle = handles[i]; // 每个handle都应该是个函数
if (typeof handle !== "function") {
var type = toString.call(handle);
var msg =
"Route." +
method +
"() requires a callback function but got a " +
type;
throw new Error(msg);
} // 注意这里的层级是layer.route.layer
// 前面第一个layer已经做个path的比较了,所以这里是第二个layer,path可以直接设置为/
var layer = new Layer("/", handle);
layer.method = method;
this.methods[method] = true; // 将methods对应的method设置为true,用于后面的快速查找
this.stack.push(layer);
}
};
});

这样,其实整个router的结构就构建出来了,后面就看看怎么用这个结构来处理请求了,也就是router.handle方法。

router.handle

前面说了app.handle实际上是调用的router.handle,也知道了router的结构是在stack上添加了layer和router,所以router.handle需要做的就是从router.stack上找出对应的layer和router并执行回调函数:

// 真正处理路由的函数
proto.handle = function handle(req, res, done) {
var self = this;
var idx = 0;
var stack = self.stack; // next方法来查找对应的layer和回调函数
next();
function next() {
// 使用第三方库parseUrl获取path,如果没有path,直接返回
var path = parseUrl(req).pathname;
if (path == null) {
return done();
} var layer;
var match;
var route; while (match !== true && idx < stack.length) {
layer = stack[idx++]; // 注意这里先执行 layer = stack[idx]; 再执行idx++;
match = layer.match(path); // 调用layer.match来检测当前路径是否匹配
route = layer.route; // 没匹配上,跳出当次循环
if (match !== true) {
continue;
} // layer匹配上了,但是没有route,也跳出当次循环
if (!route) {
continue;
} // 匹配上了,看看route上有没有对应的method
var method = req.method;
var has_method = route._handles_method(method);
// 如果没有对应的method,其实也是没匹配上,跳出当次循环
if (!has_method) {
match = false;
continue;
}
} // 循环完了还没有匹配的,就done了,其实就是404
if (match !== true) {
return done();
} // 如果匹配上了,就执行对应的回调函数
return layer.handle_request(req, res, next);
}
};

上面代码还用到了几个Layer和Route的实例方法:

layer.match(path): 检测当前layer的path是否匹配。

route._handles_method(method):检测当前route的method是否匹配。

layer.handle_request(req, res, next):使用layer的回调函数来处理请求。

这几个方法看起来并不复杂,我们后面一个一个来实现。

到这里其实还有个疑问。从他整个的匹配流程来看,他寻找的其实是router.stack.layer这一层,但是最终应该执行的回调却是在router.stack.layer.route.stack.layer.handle。这是怎么通过router.stack.layer找到最终的router.stack.layer.route.stack.layer.handle来执行的呢?

这要回到我们前面的router.route方法:

proto.route = function route(path) {
var route = new Route();
var layer = new Layer(path, route.dispatch.bind(route)); layer.route = route; this.stack.push(layer); return route;
}

这里我们new Layer的时候给的回调其实是route.dispatch.bind(route),这个方法会再去route.stack上找到正确的layer来执行。所以router.handle真正的流程其实是:

  1. 找到path匹配的layer
  2. 拿出layer上的route,看看有没有匹配的method
  3. layer和method都有匹配的,再调用route.dispatch去找出真正的回调函数来执行。

所以又多了一个需要实现的函数,route.dispatch。

layer.match

layer.match是用来检测当前path是否匹配的函数,用到了一个第三方库path-to-regexp,这个库可以将path转为正则表达式,方便后面的匹配,这个库在之前写过的react-router源码中也出现过。

var pathRegexp = require("path-to-regexp");

module.exports = Layer;

function Layer(path, fn) {
this.path = path; this.handle = fn;
this.method = ""; // 添加一个匹配正则
this.regexp = pathRegexp(path);
// 快速匹配/
this.regexp.fast_slash = path === "/";
}

然后就可以添加match实例方法了:

Layer.prototype.match = function match(path) {
var match; if (path != null) {
if (this.regexp.fast_slash) {
return true;
} match = this.regexp.exec(path);
} // 没匹配上,返回false
if (!match) {
return false;
} // 不然返回true
return true;
};

layer.handle_request

layer.handle_request是用来调用具体的回调函数的方法,其实就是拿出layer.handle来执行:

Layer.prototype.handle_request = function handle(req, res, next) {
var fn = this.handle; fn(req, res, next);
};

route._handles_method

route._handles_method就是检测当前route是否包含需要的method,因为之前添加了一个methods对象,可以用它来进行快速查找:

Route.prototype._handles_method = function _handles_method(method) {
var name = method.toLowerCase(); return Boolean(this.methods[name]);
};

route.dispatch

route.dispatch其实是router.stack.layer的回调函数,作用是找到对应的router.stack.layer.route.stack.layer.handle并执行。

Route.prototype.dispatch = function dispatch(req, res, done) {
var idx = 0;
var stack = this.stack; // 注意这个stack是route.stack // 如果stack为空,直接done
// 这里的done其实是router.stack.layer的next
// 也就是执行下一个router.stack.layer
if (stack.length === 0) {
return done();
} var method = req.method.toLowerCase(); // 这个next方法其实是在router.stack.layer.route.stack上寻找method匹配的layer
// 找到了就执行layer的回调函数
next();
function next() {
var layer = stack[idx++];
if (!layer) {
return done();
} if (layer.method && layer.method !== method) {
return next();
} layer.handle_request(req, res, next);
}
};

到这里其实Express整体的路由结构,注册和执行流程都完成了,贴下对应的官方源码:

Router类:https://github.com/expressjs/express/blob/master/lib/router/index.js

Layer类:https://github.com/expressjs/express/blob/master/lib/router/layer.js

Route类:https://github.com/expressjs/express/blob/master/lib/router/route.js

中间件

其实我们前面已经隐含了中间件,从前面的结构可以看出,一个网络请求过来,会到router的第一个layer,然后调用next到到第二个layer,匹配上layer的path就执行回调,然后一直这样把所有的layer都走完。所以中间件是啥?中间件就是一个layer,他的path默认是/,也就是对所有请求都生效。按照这个思路,代码就简单了:

// application.js

// app.use就是调用router.use
app.use = function use(fn) {
var path = "/"; this.lazyrouter();
var router = this._router;
router.use(path, fn);
};

然后在router.use里面再加一层layer就行了:

proto.use = function use(path, fn) {
var layer = new Layer(path, fn); this.stack.push(layer);
};

总结

  1. Express也是用原生APIhttp.createServer来实现的。
  2. Express的主要工作是将http.createServer的回调函数拆出来了,构建了一个路由结构Router。
  3. 这个路由结构由很多层layer组成。
  4. 一个中间件就是一个layer。
  5. 路由也是一个layer,layer上有一个path属性来表示他可以处理的API路径。
  6. path可能有不同的method,每个method对应layer.route上的一个layer。
  7. layer.route上的layer虽然名字和router上的layer一样,但是功能侧重点并不一样,这也是源码中让人困惑的一个点。
  8. layer.route上的layer的主要参数是method和handle,如果method匹配了,就执行对应的handle。
  9. 整个路由匹配过程其实就是遍历router.layer的一个过程。
  10. 每个请求来了都会遍历一遍所有的layer,匹配上就执行回调,一个请求可能会匹配上多个layer。
  11. 总体来看,Express代码给人的感觉并不是很完美,特别是Layer类肩负两种职责,跟软件工程强调的单一职责原则不符,这也导致Router,Layer,Route三个类的调用关系有点混乱。而且对于继承和原型的使用都是很老的方式。可能也是这种不完美催生了Koa的诞生,下一篇文章我们就来看看Koa的源码吧。
  12. Express其实还对原生的req和res进行了扩展,让他们变得更好用,但是这个其实只相当于一个语法糖,对整体架构没有太大影响,所以本文就没涉及了。

本文可运行代码已经上传GitHub,拿下来一边玩代码,一边看文章效果更佳:https://github.com/dennis-jiang/Front-End-Knowledges/tree/master/Examples/Node.js/Express

参考资料

Express官方文档:http://expressjs.com/

Express官方源码:https://github.com/expressjs/express/tree/master/lib

文章的最后,感谢你花费宝贵的时间阅读本文,如果本文给了你一点点帮助或者启发,请不要吝啬你的赞和GitHub小星星,你的支持是作者持续创作的动力。

作者博文GitHub项目地址: https://github.com/dennis-jiang/Front-End-Knowledges

作者掘金文章汇总:https://juejin.im/post/5e3ffc85518825494e2772fd

我也搞了个公众号[进击的大前端],不打广告,不写水文,只发高质量原创,欢迎关注~

手写Express.js源码的更多相关文章

  1. 手写Koa.js源码

    用Node.js写一个web服务器,我前面已经写过两篇文章了: 第一篇是不使用任何框架也能搭建一个web服务器,主要是熟悉Node.js原生API的使用:使用Node.js原生API写一个web服务器 ...

  2. 手写@koa/router源码

    上一篇文章我们讲了Koa的基本架构,可以看到Koa的基本架构只有中间件内核,并没有其他功能,路由功能也没有.要实现路由功能我们必须引入第三方中间件,本文要讲的路由中间件是@koa/router,这个中 ...

  3. Underscore.js 源码学习笔记(上)

    版本 Underscore.js 1.9.1 一共 1693 行.注释我就删了,太长了… 整体是一个 (function() {...}());  这样的东西,我们应该知道这是一个 IIFE(立即执行 ...

  4. MVVM大比拼之avalon.js源码精析

    简介 avalon是国内 司徒正美 写的MVVM框架,相比同类框架它的特点是: 使用 observe 模式,性能高. 将原始对象用object.defineProperty重写,不需要用户像用knoc ...

  5. MVVM大比拼之knockout.js源码精析

    简介 本文主要对源码和内部机制做较深如的分析,基础部分请参阅官网文档. knockout.js (以下简称 ko )是最早将 MVVM 引入到前端的重要功臣之一.目前版本已更新到 3 .相比同类主要有 ...

  6. 深入理解unslider.js源码

    最近用到了一个挺好用的幻灯片插件,叫做unslider.js,就想看看怎么实现幻灯片功能,就看看源码,顺便自己也学习学习.看完之后收获很多,这里和大家分享一下. unslider.js 源码和使用教程 ...

  7. basket.js 源码分析

    basket.js 源码分析 一.前言 basket.js 可以用来加载js脚本并且保存到 LocalStorage 上,使我们可以更加精准地控制缓存,即使是在 http 缓存过期之后也可以使用.因此 ...

  8. underscore.js 源码

    underscore.js 源码 underscore]JavaScript 中如何判断两个元素是否 "相同" Why underscore 最近开始看 underscore.js ...

  9. 国籍控件(js源码)

    国籍控件(js源码) 一直苦于没有好的国籍控件可以用,于是抽空写了一个国籍控件,现分享给大家. 主要功能和界面介绍 国籍控件主要支持中文.英文过滤以及键盘上下事件. 源码介绍 国籍控件核心是两个文件, ...

随机推荐

  1. git push 提交时出错 the remote end hung up unexpectedly

    错误原因 与远程服务的连接中断,但是检查发现origin还在,可能是文件太大,缓存不够,增加缓存大小 解决方案 专案目录 >.git >config 在末尾增加如下代码 [http] po ...

  2. Tomcat学习小记(二)

    1.Tomcat源码入口 生命周期统一管理接口:LifeCycle 实现LifeCycle接口的类:(idea快捷键:Ctrl+h) 多个组件共同实现LifeCycle接口 Tomcat启动入口分析: ...

  3. linux_基础调优

    1. 配置授时服务,使用阿里云的授时服务 echo -e "# update time\n*/5 * * * * /usr/sbin/ntpdate time1.aliyun.com &am ...

  4. Leetcode-哈希表

    136. 只出现一次的数字 https://leetcode-cn.com/problems/single-number/ 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次.找 ...

  5. Django-Scrapy生成后端json接口

    Django-Scrapy生成后端json接口: 网上的关于django-scrapy的介绍比较少,该博客只在本人查资料的过程中学习的,如果不对之处,希望指出改正: 以后的博客可能不会再出关于djan ...

  6. Java学习day06

    [方法]   [可以在不是main的方法中调用其他方法] [方法调用时的参数问题] [方法调用]    [上面定义了两个class,实际上不推荐] [递归调用]    [方法的返回值]   [retu ...

  7. CAS 原子操作

    理会CAS和CAS: 有时候面试官面试问你的时候,会问,谈谈你对CAS的理解,这时应该有很多人,就会比较懵,当然,我也会比较懵,当然我和很多人的懵不同,很多人可能,并不知道CAS是一个什么东西,而在我 ...

  8. 在sqlserver中创建表

    1:在sql语句中,临时表有两类,分别是局部(local)和全局(global)临时表,局部临时表只在其会话(事务)中可见,全局临时表可以被会话(事务)中的任何程序或者 模块访问 2:创建局部临时表 ...

  9. JDK1.8前_HashMap的扩容机制原理

    最近在研究hashmap的扩容机制,作为一个小白,相信我的理解,对于一些同样是刚刚接触hashmap的白白是有很很大的帮助,毕竟你去看一些已经对数据结构了解透彻的大神谈hashmap的原理等,人家说的 ...

  10. 达梦产品技术支持-DM8-数据库安装

    (该文档只适合个人环境搭建,未涉及到数据库的各种参数配置,未涉及到数据库规划,若需要企业环境搭建请咨询专业人员) 基于Windows的安装 windows下安装是图形化界面,与linux下的图形化界面 ...