-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcontent.json
1 lines (1 loc) · 92.9 KB
/
content.json
1
[{"title":"理解JavaScript原型/原型链","date":"2019-04-10T11:15:25.000Z","path":"20190410/654b96ea.html","text":"对于搞前端的小伙伴来说,不管是新手还是老鸟,我想对于原型应该都被折腾过,总是云里雾里的感觉,而且线上的文章教程一大堆,良莠不齐,因此我不自量力的想死扣一下原型这个东东,尽量能把这个问题讲清楚讲明白。 关于对象当一说到面向对象(Object-Oriented OO)时,你第一反应肯定想到类、对象、接口实现等概念,那我们这里为啥已上来就说对象呢?因为ECMAScript里没有类,另外因为ECMAScript中的函数没有签名,所以也没有接口。 ECMAScript-262中对象定义为:“无序属性的集合,其属性可以是基本值、对象或者函数”。因此从数据结构的角度,可以把对象看成散列表(Hash Table)。 对象分类从对象的创建方式上可以把对象分成:内置对象、宿主对象、自定义对象三大类。关于对象分类详细点这里。 特别需要强调的是,除了number、string、boolean、null、undefined这5中基本类型外,其它统统都是对象(引用类型),包括函数,所有的函数都是对象,反之则不成立。 对象和函数的关系对象的创建前面说过,ECMAScript中没有类,那怎么创建对象呢? 对象字面量12345678910// 方式一: 对象字面量var zhangsan = { type: \"人类\", name: \"张三\", age: 18, greeting: function() { console.log(`hello I'am ${this.name}`); }};zhangsan.greeting(); // \"hello I'am 张三\" 该方式主要有一下几个问题: 当要创建多个变量的时候,不得不写大量重复代码; 每个实例都会持有一个greeting函数,但实际上功能都一样,没有复用,浪费资源; 创建所有“人类”(type=”人类”)的实例,type的值都是一样的,但是每个实例还是持有一个独立的副本; 创建实例无法识别类型(也就是说创建的实例具体是啥类型不知道,只知道它是Object的实例)。 工厂模式1234567891011121314// 方式二: 工厂模式function createPerson (name, age) { var p = new Object(); p.type = \"人类\"; p.name = name; p.age = age; p.greeting = greeting; return p;}var lisi = createPerson (\"李四\", 20);lisi.greeting(); // \"hello I'am 李四\"function greeting () { console.log(`hello I'am ${this.name}`);} 方式二虽然进行了封装,避免了创建时大量重复的代码,也通过把greeting抽离到全局作用域而解决了多个实例持有多个greeting副本的问题,但同时也给全局空间引入了一个只有该类型实例才会引用的函数,污染了全局空间;最后它也米有解决对象识别问题。 123456789101112// 方式三: 构造函数function Person (name, age) { this.type = \"人类\"; this.name = name; this.age = age; this.greeting = greeting;}var wangwu = new Person(\"王五\", 24); // wangwu instanceof Person === truewangwu.greeting(); // \"hello I'am 王五\"function greeting () { console.log(`hello I'am ${this.name}`);} 这个方式近乎完美了,解决了对象识别问题,但是任然没有解决共享函数污染全局空间的问题;为了解决这个问题,下面请出我们的主角prototype(原型)。 原型&原型链终于切入正题了,要解决上面方式三面临的问题,就要有一个属于构造函数专有(不用定义到全局污染全局空间),能够为构造函数创建的所有对象实例所共享的对象。这个对象就是原型(或称为原型对象)。 什么是原型(prototype)默认情况下,任何函数都有一个属性prototype,它是一个指针,指向一个对象(原型对象),原型对象的用途是包含特定类型实例所共享的属性和方法,默认原型对象只有一个constructor属性,我们可以给它定义更多属性和方法。 1234567891011// 方式四: 原型法function Person (name, age) { this.name = name; this.age = age;}Person.prototype.type = \"人类\";Person.prototype.greeting = function () { console.log(`hello I'am ${this.name}`);};var wangwu = new Person(\"王五\", 24); // wangwu instanceof Person === truewangwu.greeting(); // \"hello I'am 王五\" 那上面的实例wangwu是怎么找到原型对象里定义的greeting的呢?原因是所有的对象都有一个内部指针,指向实例构造函数的原型对象,ECMAScript-262第5版中称为[[Prototype]],虽然标准并没有定义怎么访问这个内部指针,但是Firefox、Safari、Chrome在每个对象上都支持一个指向相同、名为__proto__指针属性。 在chrome console里查看wangwu的属性如下图: 原型链查找当对象实例访问某个属性或调用某个方法时,首先在自有属性里找,找到则返回值或发起调用,没有则沿着__proto__的指向往上找,直到最后查到Object.prototype,任然没有查到,即终止并报错。 对象实例、构造函数、构造函数的原型对象这三者的关系如下图: 上图中红色的路径及为查找方向,这条有__proto__指针串起来的链即为原型链(prototype chain)。原型链的本质是一串顺序指向原型对象的指针列表。 原型的动态性因为对象实例的__proto__仅仅是一个指向原型对象的指针,因此对原型对象的修改立即可以在实例上体现出来,哪怕这个实例在修改原型之前创建的: 12345Person.prototype.work = function () { console.log('work function');}// 这里的wangwu是上面创建的实例,给原型增加work方法后,可以立即调用wangwu.work(); // \"work function\" 但是如果重写整个原型对象后,相当于为构造函数指定了新的原型对象,而已创建的实例的__proto__仍然指向旧原型对象,因此访问不到在新原型里定义的方法: 1234567891011Person.prototype = { work: function () { console.log('work function'); }};// 报错wangwu.work(); // \"wangwu.work is not a function\"// 在修改原型对象后创建的实例,因为获取到的__proto__属性是指向新原型的,因此不会报错var sanma = new Person('三毛', 30);// 可以愉快的“工作”sanma.work(); // \"work function\" 覆盖整个原型对象后,相当于上面图中原来的prototype指向被切断了,指向了新的原型。 小结一下默认情况下(因为原型对象实际上是可写的,因此可以被改变): 任何函数都有一个指向其原型对象的指针属性prototype; 任何对象实例都有一个指向其构造函数原型对象的内部指针[[Prototype]](__proto__); 原型对象也是对象,因此也有__proto__(例如上图中指向Object.prototype那个); 对象实例的__proto__指针指向构造函数的原型对象:wangwu.__proto__ === Person.prototype; 原型对象的constructor属性指向构造函数: Person.prototype.constructor === Person; 构造函数和对象实例没有直接联系,仅仅是都有一个指针属性指向同一个原型对象。 对象实例识别(检测)我们知道,对于number、string、boolean、undefined、function这几种类型值,可以通过typeof操作符简单区分,但是对于除function外的引用类型实例和null,typeof都返回”object”,但是再往细了区分,某个对象实例是神类型的实例,typeof就没办法了。 instanceof操作符要识别具体的对象实例类型,就要用到instanceof操作符,格式为instance instanceof Func, instance是待检测实例对象,Func是一个构造函数,有了上面原型链的理解,那instanceof的检测机制就简单多了,只要在instance的原型链上某个__proto__指向了Func的原型对象,就返回true,否则返回false。即: instance.__proto__...__proto__ === Func.prototype 另外也可以用Func.prototype.isPrototypeof(instance)、Object.getPrototypeof(instance) === Func.prototype来判断。 123456console.log(wangwu instanceof Person); // trueconsole.log(wangwu instanceof Object); // trueconsole.log(Person.prototype.isPrototypeof(wangwu)); // trueconsole.log(Object.prototype.isPrototypeof(wangwu)); // trueconsole.log(Object.getPrototypeof(wangwu) === Person.prototype); // trueconsole.log(Object.getPrototypeof(wangwu) === Object.prototype); // false, 因为getPrototypeof函数只返回实例原型,而不会返回原型链上的其它原型 原型继承","tags":[{"name":"原型","slug":"原型","permalink":"https://xuh.io/tags/原型/"},{"name":"原型链","slug":"原型链","permalink":"https://xuh.io/tags/原型链/"},{"name":"prototype","slug":"prototype","permalink":"https://xuh.io/tags/prototype/"}]},{"title":"计算机基础之(五)- 异常控制流","date":"2019-03-11T14:04:35.000Z","path":"20190311/df1509a7.html","text":"异常控制流什么是异常控制流(ECF)计算机从开机到关机,CPU不停的从内存读取指令然后执行,默认情况指令$I_k$和指令$I_{k+1}$在内存中是相邻的,CPU从上一个指令过渡到下一个指令称为控制转移(control transfer),这个控制转移序列即处理器的控制流(control flow)。 到目前为止,在应用程序内,改变控制流的方式有: 跳转和分支 调用和返回 但计算机系统也必须对应用程序外的状态变化做出相应,比如硬件定时器产生信号、网络包到达、磁盘读写就绪等,系统通过控制流的突变来对这些状态做出相应,这种突变的控制流称为异常控制流(Exceptional Control Flow,ECF),ECF存在于计算机系统的各个层次。 理解ECF对程序员的意义有助于理解重要的系统概念ECF是系统实现I/O、进程和虚拟内存的基本机制。 有助于理解应用程序如何与操作系统交互应用程序通过系统调用(system call)向操作系统请求诸如磁盘读写、从网络获取数据、创建/终止进程等服务,系统调用或称为陷阱(trap)即为ECF的一种形式。 有助于理解并发ECF是计算机系统中实现并发的基本机制。 有助于理解软件异常的实现机制C++、Java等有异常处理的编程语言,通过try、catch以及throw语句来提供软件异常机制。底层通过非本地跳转(违反调用/返回栈规则的跳转)来响应错误,在C语言中通过setjmp和longjmp函数实现非本地跳转。 异常(Exception)异常一般概念异常分类异常处理Linux/x86-64系统的异常进程(Process)基本概念逻辑控制流并发流私有地址空间用户模式和内核模式上下文切换进程控制信号(Signal)非本地跳转","tags":[{"name":"异常","slug":"异常","permalink":"https://xuh.io/tags/异常/"},{"name":"中断","slug":"中断","permalink":"https://xuh.io/tags/中断/"},{"name":"ECF","slug":"ECF","permalink":"https://xuh.io/tags/ECF/"}]},{"title":"数据结构之(四)--- 队列","date":"2019-02-11T14:27:28.000Z","path":"20190211/5141ea00.html","text":"队列ADT队列(queue)和栈一样,也是一种表,和栈的主要区别在于插入和删除元素的方式,队列是先进先出(FIFO),从队尾(rear)插入元素,称为入队(enqueue),从队头(front)删除并返回元素,称为出队(dequeue)。 同栈一样,通常也由链表或数组实现队列,所有操作时间复杂度都为O(1)。 队列数组实现ADT规范头文件queue.h: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172#ifndef _queue_hstruct QueueRecord;typedef struct QueueRecord *Queue;/** * 定义队列元素类型 */typedef int VType;/** * 创建一个空队列 * @param maxElements 队列最大容量 * @return */Queue createQueue (int maxElements);/** * 判断队列是否为空 * @param q * @return */int isEmpty (Queue q);/** * 判断队列是否已满 * @param q * @return */int isFull (Queue q);/** * 释放一个队列 * @param q */void disposeQueue (Queue q);/** * 清空队列 * @param q */void deleteAll (Queue q);/** * 入队 * @param x * @param q */void enqueue (VType x, Queue q);/** * 出队 * @param q * @return */VType dequeue (Queue q);/** * 返回队头元素 * @param q * @return */VType front (Queue q);/** * 显示队列元素 * @param q */void show (Queue q);#endif /* _queue_h */ 实现文件queue.c: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170#include <stdio.h>#include <stdlib.h>#include \"queue.h\"#define MinQueueSize (5) // 定义队列最小容量struct QueueRecord { int capacity; // 队列容量 int front; // 队头位置 int rear; // 队尾位置 int size; // 当前队列大小 VType *array;};/** * 显示unix风格的错误 * @param msg */void unix_error (char *msg) { printf(\"%s\\n\", msg); exit(0);}void *Malloc (size_t size) { void *p; if ((p = malloc(size)) == NULL) unix_error(\"malloc error!\"); return p;}/** * 创建一个空队列 * @param maxElements 队列最大容量 * @return */Queue createQueue (int maxElements){ Queue q; if (maxElements < MinQueueSize) unix_error(\"queue size is small\"); q = Malloc(sizeof (struct QueueRecord)); q->capacity = maxElements; q->array = Malloc(sizeof (VType) * maxElements); // 清空队列 deleteAll(q); return q;}/** * 判断队列是否为空 * @param q * @return */int isEmpty (Queue q){ return q->size == 0;}/** * 判断队列是否已满 * @param q * @return */int isFull (Queue q){ return q->size == q->capacity;}/** * 释放一个队列 * @param q */void disposeQueue (Queue q) { if (q != NULL) { free(q->array); free(q); }}/** * 清空队列 * @param q */void deleteAll (Queue q){ q->size = 0; q->front = 1; q->rear = 0;}/** * 入队 * @param x * @param q */void enqueue (VType x, Queue q){ if (isFull(q)) unix_error(\"enqueue error\"); if (++q->size > q->capacity) unix_error(\"queue is overflow\"); int newRear = ++(q->rear) < q->capacity ? q->rear : 0; q->rear = newRear; q->array[newRear] = x;}/** * 出队 * @param q * @return */VType dequeue (Queue q){ if (isEmpty(q)) unix_error(\"dequeue error\"); int newFront; VType result; result = q->array[q->front]; // 新队头 newFront = ++(q->front) < q->capacity ? q->front : 0; q->front = newFront; q->size--; return result;}/** * 返回队头元素 * @param q * @return */VType front (Queue q){ if (isEmpty(q)) unix_error(\"front error\"); return q->array[q->front];}/** * 显示队列元素 * @param q */void show (Queue q){ printf(\"front is %d, rear is %d, size is %d\\n\", q->front, q->rear, q->size); if (isEmpty(q)) return; for (int i = 0, maxIndex = q->capacity - 1; i < q->size; i++) { int tmp = i + q->front; tmp = tmp < maxIndex ? tmp : tmp - q->capacity; printf(\"队列第%d个元素%d\\n\", i + 1, q->array[tmp]); } }","tags":[{"name":"队列","slug":"队列","permalink":"https://xuh.io/tags/队列/"},{"name":"queue","slug":"queue","permalink":"https://xuh.io/tags/queue/"}]},{"title":"数据结构之(三)--- 栈实现","date":"2019-02-11T11:25:40.000Z","path":"20190211/b2596ee6.html","text":"栈ADT概述栈(stack)其实是一种特殊的表,插入后删除都只能从表的指定位置进行,这个位置成为栈顶。栈的插入叫入栈(push),删除叫出栈(pop),因为入栈和出栈都是在栈顶进行,因此最后入栈的元素最先出栈(后进先出:LIFO),因此栈也叫LIFO表,换句话说,在任意时刻,只有栈顶元素是可见的。 因为栈其实是一个表,因此任何实现表的方法都能实现栈,通常有链表和数组两种实现方式,下面分别实现。 栈链表实现首先是栈链表实现的ADT声明头文件stack.h: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465#ifndef _stack_htypedef int VType;struct StackNode;typedef struct StackNode *ptrToNode;typedef ptrToNode Stack;typedef ptrToNode Node;/** * 创建一个空栈 * @return */Stack createStack (void);/** * 检测是否空栈 * @param s * @return */int isEmpty (Stack s);/** * 获取栈大小 * @param s * @return */int size (Stack s);/** * 清空栈 * @param s */void deleteAll (Stack s);/** * 入栈 * @param Stack * @param x */void push (Stack, VType x);/** * 出栈 * @param stack * @return */VType pop (Stack stack);/** * 返回栈顶元素 * @param s * @return */VType top (Stack s);/** * 展示栈数据 * @param list */void show (Stack stack);#endif /* _stack_h */ 然后是显现文件stack.c: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150#include <stdlib.h>#include <stdio.h>#include \"stack.h\"struct StackNode { VType value; struct StackNode *next;};/** * 显示unix风格的错误 * @param msg */void unix_error (char *msg) { printf(\"%s\\n\", msg); exit(0);}void *Malloc (size_t size) { void *p; if ((p = malloc(size)) == NULL) unix_error(\"malloc error!\"); return p;}/** * 创建一个空栈 * @return */Stack createStack (void){ Stack s = (Stack)Malloc(sizeof (struct StackNode)); s->next = NULL; return s;}/** * 检测是否空栈 * @param s * @return */int isEmpty (Stack s) { return s->next == NULL;}/** * 获取栈大小 * @param s * @return */int size (Stack s){ int count = 0; Node p = s; while (p->next != NULL) { count++; p = p->next; } return count;}/** * 清空栈 * @param s */void deleteAll (Stack s){ Node tmp, node = s->next; while (node != NULL) { tmp = node->next; free(node); node = tmp; } s->next = NULL;}/** * 入栈 * @param s * @param x */void push (Stack s, VType x) { Node node; node = (Node)Malloc(sizeof(struct StackNode)); node->value = x; node->next = s->next; s->next = node;}/** * 出栈 * @param s * @return */VType pop (Stack s){ if (isEmpty (s)) unix_error(\"pop error\"); VType result; Node node = s->next; s->next = node->next; result = node->value; free(node); return result;}/** * 返回栈顶元素 * @param s * @return */VType top (Stack s){ if (isEmpty(s)) unix_error(\"empty stack\"); return s->next->value;}/** * 展示栈数据 * @param list */void show (Stack s){ if (isEmpty(s)) { printf(\"当前是空栈\\n\"); return; } int count = 1; Node n = s->next; while (n != NULL) { printf(\"从栈顶第%d个元素:%d\\n\", count++, n->value); n = n->next; }} 栈数组实现栈数组实现和链表实现的区别在于需要提前设置栈的大小,一遍初始化数组,但是它相比于链表实现的优势在于入栈和出栈少了频繁的malloc和free开销。 因为需要提前设置栈大小,因此创建栈实例时需要给出栈容量参数,下面是栈的数组直线的ADT头文件stack2.h: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879#ifndef _stack_hstruct StackRecord;typedef struct StackRecord *Stack;typedef int VType;/** * 创建并初始化一个空栈 * @param maxElements 栈最大容量 * @return */Stack createStack (int maxElements);/** * 获取栈大小 * @param s * @return */int size (Stack s);/** * 检测当期栈是否为空 * @param s * @return */int isEmpty (Stack s);/** * 检测当前栈是否满栈 * @param s * @return */int isFull (Stack s);/** * 清空栈 * @param s [description] */void deleteAll (Stack s);/** * 释放栈 * @param s */void disposeStack (Stack s);/** * 入栈 * @param x 入栈元素 * @param s */void push (VType x, Stack s);/** * 出栈 * @param s * @return */VType pop (Stack s);/** * 返回栈顶元素 * @param s * @return */Vtype top (Stack s);/** * 展示栈数据 * @param list */void show (Stack s);#endif /* _stack_h */ 实现文件stack2.c: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158#include <stdio.h>#include <stdlib.h>#include \"stack2.h\"#define EmptyTOS (-1)#define MinStackSize (5)struct StackRecord { int capacity; // 栈最大容量 int topOfStack; // 栈顶下标 VType *array; // 存储栈元素的数组};/** * 显示unix风格的错误 * @param msg */void unix_error (char *msg) { printf(\"%s\\n\", msg); exit(0);}void *Malloc (size_t size) { void *p; if ((p = malloc(size)) == NULL) unix_error(\"malloc error!\"); return p;}/** * 创建并初始化一个空栈 * @param maxElements 栈最大容量 * @return */Stack createStack (int maxElements){ Stack s; if (maxElements < MinStackSize) unix_error(\"Stack size is too small\"); s = Malloc(sizeof (struct StackRecord)); s->array = Malloc(sizeof (VType) * maxElements); s->capacity = maxElements deleteAll(s); return s;}/** * 获取栈大小 * @param s * @return */int size (Stack s){ return s->topOfStack + 1;}/** * 检测当期栈是否为空 * @param s * @return */int isEmpty (Stack s){ return s->topOfStack == EmptyTOS;}/** * 检测当前栈是否满栈 * @param s * @return */int isFull (Stack s){ return size(s) == s->capacity;}/** * 清空栈 * @param s [description] */void deleteAll (Stack s) { s->topOfStack = EmptyTOS;}/** * 释放栈 * @param s */void disposeStack (Stack s){ if (s != NULL) { free (s->array); free(s); }}/** * 入栈 * @param x 入栈元素 * @param s */void push (VType x, Stack s){ if (isFull(s)) unix_error(\"Full stack\"); s->array[++(s->topOfStack)] = x;}/** * 出栈 * @param s * @return */VType pop (Stack s){ if (isEmpty(s)) unix_error(\"Empty stack\"); return s->array[s->topOfStack--];}/** * 返回栈顶元素 * @param s * @return */VType pop (Stack s){ if (isEmpty(s)) unix_error(\"Empty stack\"); return s->array[s->topOfStack];}/** * 展示栈数据 * @param list */void show (Stack s){ if (isEmpty(s)) { printf(\"当前是空栈\\n\"); return ; } for (int i = s->topOfStack; i >= 0; i--) { printf(\"从栈顶第%d个元素:%d\\n\", (s->topOfStack - i), s->array[i]); }}","tags":[{"name":"栈","slug":"栈","permalink":"https://xuh.io/tags/栈/"},{"name":"stack","slug":"stack","permalink":"https://xuh.io/tags/stack/"}]},{"title":"数据结构之(一)--- 线性表","date":"2019-01-31T17:06:21.000Z","path":"20190201/aaf1bacf.html","text":"抽象数据类型(ADT)抽象数据类型ADT(abstract data type),一个实现包括存储数据元素的存储结构以及基本操作的算法集合,它是数学的抽象,在ADT的定义中,不包括具体的实现细节。 表ADT定义一组数据类型相同的有限序列{$A_1, A_2, A_3 ,…, A_n$},表的大小为n,当表大小为0时成为空表。除第一个元素外,每个元素有且仅有一个前驱,除最后一个元素外,每个元素有且仅有一个后继。 分类根据元素的存储方式可分为顺序表和链表两大类: graph LR B[\"线性表\"] B-->C[线性存储] C-->D[顺序表] B-->E[\"链式存储\"] E-->F[\"单链表\"] E-->G[\"双链表\"] E-->H[\"循环链表\"] E-->I[\"静态链表(借助数组实现)\"] 顺序表逻辑上相邻的两个元素,其物理存储也相邻,一般通过数组实现。 优点 实现简单 查找性能高,时间复杂度为O(1) 缺点 插入、删除需要移动元素,时间复杂度为O(N) 需要预分配存储空间,容易造成资源浪费和发生溢出错误 适用场景 表大小确定 查找操作频繁,插入、删除操作少 链表为了避免插入、删除的线性开销,就要避免插入、删除时的部分或整体移动,因此表元素之间不能连续存储,这种表就是链表,链表分单链表、双链表、循环链表等,下面先说说比较简单和常用的单链表。 在C语言的实现中,通过一个结构体来描述一个元素(节点),包括一个数据域成员和一个指针成员,指针指向元素的后继 。该指针成员称Next指针或“链”,最后一个元素的Next指针指向NULL。 一个含有五个元素的单链表 指向第一个节点A1的指针叫头指针,我们通过头指针来命名链表。 因为元素不是连续存储的,元素之间的逻辑关联通过Next指针引用,因此插入元素时不需要把插入点后的元素整个后移,只需要修改插入点的指针引用关系即可: 上图中,在A2后插入元素X,只需要修改A2的Next指针指向新的节点,然后新的节点指向原A2的后继即可。 同样,对于删除操作,只需要将删除节点的前驱Next指针指向删除节点的后继即可: 如果要删除的是最后一个节点,只需要将前驱Next指针指向NULL即可,但是,这里有一个问题,就是当要删除的节点是第一个节点A1的时候会怎样?头指针的引用将会丢失,也就是表丢失了,同样如果在A1前插入元素也会造成表的丢失。 为了解决首节点删除和前插问题,一般实现中引入一个额外的节点,称为头节点或哑节点,它不包含数据域。头结点在位置0处,下面是一个带头结点的表: 引入头结点后,对A1节点的删除和前插操作和其它节点统一了,下面的实现采用头结点方案。","tags":[{"name":"ADT","slug":"ADT","permalink":"https://xuh.io/tags/ADT/"},{"name":"线性表","slug":"线性表","permalink":"https://xuh.io/tags/线性表/"}]},{"title":"数据结构之(二)--- 单链表实现","date":"2019-01-31T17:04:59.000Z","path":"20190201/5faa822b.html","text":"带头结点单链表ADT定义在头文件里(linkedList.h): 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143#ifndef __LINKED_LIST_H/** * 单链表ADT * * 包含头节点 *//** * 定义列表节点类型 */typedef int T;/** * 链表的节点结构体 * 具体成员定义放在实现文件中 */struct ListNode;// 指向节点的指针typedef struct ListNode *ptrToNode;// 定义节点指针的别名typedef ptrToNode List;typedef ptrToNode Node;/** * 创建一个孤儿节点 * @param x * @return */Node createNode (T x);/** * 初始化一个含头结点的空表 * @return */List createList (void);/** * 获取表大小 * @param list * @return */int size (List list);/** * 检测表是否是空表 * @param list * @return */int isEmpty (List list);/** * 查找x所在的第一个节点,不存在是返回NULL * * @param list 查找列表 * @param x 节点数值 * @return */Node find (List list, T x);/** * 查找第一个包含x的节点的前驱 * @param list * @param x * @return */Node findPrevious (List list, T x);/** * 查找最后的节点 * @param list * @return */Node findLast (List list);/** * 在所给节点前插入元素 * @param list * @param n */void insertBefore (List list, Node n, T x);/** * 在所给节点后插入元素 * @param list * @param n */void insertAfter (List list, Node n, T x);/** * 在表尾附加元素 * @param list * @param x */void append (List list, T x);/** * 删除第一个包含x的节点 * @param list * @param x */void delete (List list, T x);/** * 清空表 * @param list */void deleteAll (List list);/** * 翻转表 * @param list */void reverse (List list);/** * 展示列表数据 * @param list */void show (List list);/** * 显示unix风格的错误 * @param msg */void unix_error (char *msg);#endif /* __LINKED_LIST_H */ 实现文件(linkedList.c): 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244#include <stdlib.h>#include <stdio.h>#include \"linkedList.h\"struct ListNode { T value; struct ListNode *next;};/** * 显示unix风格的错误 * @param msg */void unix_error (char *msg) { printf(\"%s\\n\", msg); exit(0);}void *Malloc (size_t size) { void *p; if ((p = malloc(size)) == NULL) unix_error(\"malloc error!\"); return p;}/** * 创建一个孤儿节点 * @param x * @return */Node createNode (T x) { Node node = (Node)Malloc(sizeof (struct ListNode)); node->value = x; node->next = NULL; return node;}/** * 初始化一个含头结点的空表 * @return */List createList (void) { return (List)createNode(0);}/** * 获取表大小 * @param list * @return */int size (List list) { int count = 0; Node tmp = list; while (tmp->next != NULL) { count++; tmp = tmp->next; } return count;}/** * 检测表是否是空表 * @param list * @return */int isEmpty (List list) { return list->next == NULL;}/** * 查找x所在的第一个节点,不存在是返回NULL * * @param list 查找列表 * @param x 节点数值 * @return */Node find (List list, T x){ Node p = list->next; while (p->next != NULL && p->value != x) p = p->next; return p;}/** * 查找第一个包含x的节点的前驱 * @param list * @param x * @return */Node findPrevious (List list, T x){ Node p = list; while (p->next != NULL && p->next->value != x) p = p->next; return p;}/** * 查找最后的节点 * @param list * @return */Node findLast (List list){ Node p = list; while (p->next != NULL) p = p->next; return p;}/** * 在所给节点前插入元素 * @param list * @param n */void insertBefore (List list, Node n, T x){ Node prev = findPrevious(list, n->value); Node new_node = createNode(x); new_node->next = n; prev->next = new_node;}/** * 在所给节点后插入元素 * @param list * @param n */void insertAfter (List list, Node n, T x){ Node new_node = createNode(x); new_node->next = n->next; n->next = new_node;}/** * 在表尾附加元素 * @param list * @param x */void append (List list, T x){ Node last_node = findLast(list); Node new_node = createNode(x); last_node->next = new_node;}/** * 删除第一个包含x的节点 * @param list * @param x */void delete (List list, T x){ Node tmp; Node prev = findPrevious(list, x); if (prev != NULL && prev->next != NULL) { tmp = prev->next; prev->next = prev->next->next; free(tmp); }}/** * 清空表 * @param list */void deleteAll (List list){ Node tmp; Node p = list->next; while (p != NULL) { tmp = p->next; free(p); p = tmp; } list->next = NULL;}/** * 翻转表 * @param list */void reverse (List list){ Node prev = NULL; Node cur = list->next; while (cur != NULL) { Node tmp = cur->next; // 当前节点是尾节点 if (tmp == NULL) { list->next = cur; } cur->next = prev; prev = cur; cur = tmp; } }/** * 展示列表数据 * @param list */void show (List list){ Node p = list->next; if (p == NULL) { printf(\"当前列表为空\\n\"); return ; } printf(\"头结点\"); while (p != NULL) { printf(\" -> %d \", p->value); p = p->next; } printf(\"\\n\");}","tags":[{"name":"单链表","slug":"单链表","permalink":"https://xuh.io/tags/单链表/"}]},{"title":"结构和联合","date":"2019-01-29T10:55:23.000Z","path":"20190129/667c01ef.html","text":"C语言中有两种聚合数据类型,数组和结构,数组是相同元素的集合,而结构可以存储不同类型的元素,这些元素称为结构的成员(member),每个成员都有自己的名字和类型。 另外,结构变量是一种标量,这和数组有很大的区别,在绝大多数表达式中,数组名的值为指向数组第一个元素的指针(例外: sizeof 数组名返回整个数组占用字节大小,&array返回指向整个数组的指针,而不是指向第一个元素的指针的指针)。 结构声明1struct tag {member-list} variable-list; tag、member-list、variable-list都是可选的,但是除了tag,其它可选部分至少出现2个。 下面是几种常见的基本声明: 123456789101112131415161718192021222324252627// 合法,不完整声明struct Node;// 合法struct Point { int x; int y;};// 合法struct { int a; int b;} x;// 合法struct { int a; int b;} y,*z;// 非法struct self_ref { int a; struct self_ref b;};// 合法struct self_ref2 { int a; struct self_ref2 b;}; 不完整声明 声明语句只有tag 一般用在结构体相互引用(依赖)的的情形。 声明Point结构时并不分配内存 这是因为这里只是声明一种类型,而没有声明变量,因此不占用内存。 结构变量x、y是两种完全不同的类型 尽管他们成员列表相同,但是他们是两种不同的结构类型,因此z=&x是非法的。 当结构自引用时,必须用指针 self_ref的声明是非法的,因为成员b构成无线循环引用,而self_ref2是合法的,因为self_ref2中b是指针,编译器在结构本身长度确定之前就可以确定指针的长度。 最后,声明语句最后一定要有;结尾,尤其是没有声明变量时,结尾的花括号很容易误以为是语句块而少了分号。 如果上面的x、和y、z要共享相同的成员列表声明变成同一种类型,可以把标签tag和成员列表一起声明: 12345678struct Point { int a; int b;};struct Point x; // struct关键词不能省略struct Point y, *z;z = &x; 这个时候x和y类型就相同了,但是这个声明也有一个问题,太啰嗦了,在定义变量时,模式struct Point 变量名中struct关键词不能省略,这问题可以通过typedef关键词解决。 123456789typedef struct Point { // 标签Point可省略 int a; int b;} myPoint;myPoint x;myPoint y, *z;z = &x; 现在相当于给类型struct Point定义了一个别名myPoint。 最后,关于自引用结构的定义,注意下面的错误: 1234typedef struct { int a; self_ref3 *b;} self_ref3; 这个声明的问题在于类型名self_ref3在末尾才完成定义,因此成员列表的声明部分就没办法使用它,可以改成下面的格式: 1234typedef struct self_ref3_tag { int a; struct self_ref3_tag *b;} self_ref3; 初始化结构体初始化和数组初始化类似,一对花括号包含的以逗号分隔的值序列,根据成员定义顺序一一赋值,如果值少于成员,则剩余成员以缺省值初始化: 123456789101112struct INT_EX { int a; short b[5]; struct { int a; char b; } c;} = { 10, {1,2,3}, {5,'x'}}; 成员访问 结构体变量通过.操作符访问 12345678910struct { int a; char b[20];} x = { 10, \"hello world!\"};printf(\"%d\\n\", x.a); // 输出10printf(\"%s\\n\", x.b); // 输出hello world! 结构体指针通过->访问(指针间接访问) 123456789101112struct { int a; char b[20];} x = { 10, \"hello world!\" }, *y;y = &x;printf(\"%d\\n\", y->a); // 输出10printf(\"%s\\n\", y->b); // 输出hello world!// 下面的代码和上面效果一样printf(\"%d\\n\", (*y).a); // 输出10printf(\"%s\\n\", (*y).b); // 输出hello world! 存储分配结构体成员在内存中按照定义顺序存储,但是成员之间可能存在“空隙”,这是为了提升CPU寻址效率而采用对齐算法进行分配导致的,因为“空隙”的存在,通常结构占用字节数大于成员占用字节之和,C语言结构成员存储对齐规则如下: 每个成员的其实地址必须是自身类型大小的整数倍 结构体大小必须是占用字节数最大的成员的整数倍 根据对齐规则,分析下面的结构提占用字节数,为分析方便,下面用一个星号*表示一个实际占用的字节,一个横线-表示填充字节。 12345678910111213141516171819202122232425262728293031323334353637struct str1 { char a; int b; char c;}/*** str1内存分配如下:* a b c* * - - - * * * * * - - -* a占用一字节,因此从内存0开始,而b占4字节,因此其地址开始位置必须为4的倍数* 因此a后面填充3个字节,最后c占一个字节,因此紧跟b后,但是因为结构体占用字节数* 必须为最大占用字节成员的倍数,即必须是b所占字节的整数倍,因此最后填充3个字节刚好12字节*/struct str2 { int b; char a; char c;}/*** str2内存分配如下:* b a c * * * * * * * - - * 成员和str1一样,只是调整了顺序,但是str2占用字节数为8字节,因此成员的顺序会影响内存分配。*/struct str3 { char a; int b; float c; double d;}/*** str3内存分配如下:* a b c d* * - - - * * * * * * * * - - - - * * * * * * * * * 共占用24字节*/","tags":[{"name":"struct","slug":"struct","permalink":"https://xuh.io/tags/struct/"},{"name":"union","slug":"union","permalink":"https://xuh.io/tags/union/"},{"name":"结构体","slug":"结构体","permalink":"https://xuh.io/tags/结构体/"},{"name":"联合","slug":"联合","permalink":"https://xuh.io/tags/联合/"}]},{"title":"豆豆诞生记","date":"2018-05-01T03:32:10.000Z","path":"20180501/41c54498.html","text":"一、诞生说来话长,先从一份产品说明书讲起。 名称: 徐骁文 别名: 豆豆 出厂日期: 2016年8月8日 AM 8:20 出厂质量: 3.17KG 出 厂 地: 深圳市妇幼保健院 2016年8月8日早上八点,深圳市妇幼保健院五楼的产房里,伴随着一阵阵清脆响亮的哇哇啼哭声,我正式向这个世界宣布我的降临,笛卡尔说“我思故我在”,我说“我哭故我在”。 从一片混沌来到这个清澈的世界,一丝不挂,带着发育尚不完全的视觉、听觉和嗅觉,放佛这个世界没有光、没有声音,我大口呼吸着,大声嚎啕着,似乎我就是这个世界,整个世界都回荡着我的哭声。 突然身边飘来一阵阵我身处那混沌中早已熟悉的味道,一个很有力的环抱把我搂住,我感觉到了亲切和温暖,我又哭了,是因为兴奋、好奇、幸福还是因为饥饿,好像都是又好像都不是,反正现在哭是我唯一的表达方式,而你却笑了,梨花带雨,气若游丝。 我们静静的依偎着,像刚刚一起经历了一场战火的兄弟,这就是我在学会说话之前就会叫的那个人-妈妈,我想我这一辈子最终要的一件事情就是爱你孝敬你,因为你是在我生命起点就陪着我的那个人。 不知过了多久,护士抱着我走出产房,眼前影影绰绰,产房外,一个皮肤黝黑的男人,来回踱着急促的脚步,时而焦急向产房张望,脸上夹杂着疲倦不安和期待的复杂表情,在我感觉到他存在的那瞬间,他几个箭步冲到了我眼前,然后定住,眼神木讷,一脸茫然不知所措,其实我也一样,素昧平生,我还有点紧张呢,你谁呀?要干嘛?! 我们静静的对视了几秒,还是他先回过神儿来,伸手来抱我,被护士阻止了,灰溜溜的,又手忙脚乱的掏出手机对着我一阵狂闪,借着过道微弱的亮光,我发现这个坚毅的面庞下尽然满是温情和慈爱,这样的眼神,这样的注视,在我以后的生命里也经常出现,温情、慈爱、坚毅和期许。“爸爸”,接下来的六百多天里天天教我学叫的称呼,尽管我一直都叫成“妈妈”。 三天后,爸爸去医院前台结账,在递交的出生证明申请表姓名一栏填上“徐骁文”这三个字,这就算给我注册商标了。办完出院手续,全家人簇拥着我上了车,这是我生命旅程的起点站,开往未知的幸福未来。 二、睁眼看世界尿不湿的袋子瘪了一个又一个,嫩绿色的尿布慢慢退了色,妈妈在台历上画了一个又一个的圈,记录着有关我的点滴,每一次吃奶间隔,每一次尿尿间隔,第一次翻身,第一次爬行,第一次微笑,第一次梦中喃喃自语,第一次开口叫妈妈,第一次蹒跚学步。台历翻了一页又一页,我的头发剃了又长,长了又剃,爸爸、妈妈、爷爷、奶奶、姥姥、姥爷的手机了相册里塞满了转存、转存了又塞满,日子就这么一天一天的在我的哭闹声和家人的欢笑声中溜走,我在一天天的长大。 第一次发现我跟这个世界的边界,曾经我以为我就是这个世界,现在我知道,玩具箱里是我的世界,捧在手心里的手机是我的世界,端着碗里的美食是我的世界,装满牛奶的奶瓶是我的世界,我的世界属于我,别人休想染指,曾经的我,爸妈逢人边夸,这孩子懂事知道分享,现在都说我不懂分享了,其实不是的,是我慢慢在成长,第一次知道“我”的概念,我知道了什么好吃,什么好玩,我也学会了感恩和馈赠,当爸妈下班回家我第一时间冲上去的熊抱,半夜不睡在你们脸庞留下的幼小的唇印,这些都是证据。 未完待续… 三、小小音乐家未完待续… 四、像少年啊飞驰未完待续…","tags":[{"name":"豆豆","slug":"豆豆","permalink":"https://xuh.io/tags/豆豆/"}]},{"title":"前端程序员笔试集锦","date":"2018-01-20T14:33:19.000Z","path":"20180120/dad26ace.html","text":"一、JavaScript基础选择题判断题填空题问答题 JavaScript是一门什么样的语言,它有哪些特点? JavaScript的数据类型都有什么? ECMAScript和JavaScript是什么关系? - 二、JavaScript进阶 怎么判断一个变量是数组? 三、JavaScript高级","tags":[{"name":"前端笔试","slug":"前端笔试","permalink":"https://xuh.io/tags/前端笔试/"},{"name":"JavaScript基础","slug":"JavaScript基础","permalink":"https://xuh.io/tags/JavaScript基础/"}]},{"title":"车加家错峰停车接口说明文档","date":"2018-01-10T09:59:54.000Z","path":"20180110/740cbc05.html","text":"一、系统用例 二、同利云车场接口2.1 接口清单 接口名称 接口备注 租赁车位资源查询接口 车加家APP会定期主动发起查询,补全/修正/校对已开通租赁停车场信息。 租赁白名单接收接口 用户在车加家APP生成租赁订单后,会通过该接口推送给同利云车场系统。 2.2 接口详情2.2.1 租赁车场资源查询接口(api/leasePark)车加家通过该接口查询可租赁停车场价格,时段等信息。 请求参数 参数名 类型 必须 说明 page Int N 页码,指定需要返回第 { page } 页的数据,默认 1 limit Int N 返回数据数量,指定一次返回的数据条数,默认1000 返回data参数 名称 类型 含义 park_sn String 停车场编号 park_name String 停车场名称 deadline String 有效截止期限,格式:YYYY-mm-dd start_time String 租赁时段起始时间,格式:HH:ii end_time String 租赁时段结束时间,格式:HH:ii is_sat Int 是否支持周六全天停车。可选值:1:支持0:不支持 is_sun Int 是否支持周日全天停车。可选值:1:支持0:不支持 is_holiday Int 是否支持法定假日全天停车。可选值:1:支持0:不支持 price Int 每30分钟价格。单位:分 start_price Int 起步价。单位:分 holiday_price Int 节假日全天价格。单位:分 longitude String 停车场经度 latitude String 停车场纬度 错误码 NULL 2.2.2 租赁订单同步接口(api/SynLeaseOrder)车加家通过该接口同步租赁白名单到同利停车系统。 请求参数 参数名 类型 必须 说明 park_sn String Y 停车场编号 car_plate_no String N 车牌号 dates Object[] Y 租赁时间 dates参数 参数名 类型 必须 说明 date String Y 租赁日期 start_time String Y 租赁时段起始时间,格式:HH:ii end_time String Y 租赁时段结束时间,格式:HH:ii 请求参数示例(不含公共参数) 1234567891011121314151617{ 'park_sn': '2017-01-01 12:00:00', 'car_plate_no': '苏 E88888', 'dates': [{ 'date': '2018-01-01', 'start_time': '19:00', 'end_time': '23:30', },{ 'date': '2018-01-02', 'start_time': '19:00', 'end_time': '06:30', },{ 'date': '2018-01-03', 'start_time': '19:00', 'end_time': '07:30', },....]} 返回data参数 NULL 错误码 NULL 三、车加家接口3.1 接口清单 接口名称 接口备注 租赁车场资源接收接口 同利云车场在可租赁车场/时段/价格等变更时会通过该接口推送给车加家APP。 租赁订单查询接口 同利云车场可以设置定时任务查询该接口,以补全/修正/校验租赁白名单记录。 3.2 接口详情3.2.1 租赁车场资源接收(lease/park)车加家通过该接口接收同利推送的可租赁停车场价格,时段等信息并保存。 请求参数 参数名 类型 必须 说明 park_sn String Y 停车场编号 park_name String Y 停车场名称 deadline String Y 有效截止期限,格式:YYYY-mm-dd start_time String Y 租赁时段起始时间,格式:HH:ii end_time String Y 租赁时段结束时间,格式:HH:ii is_sat Int Y 是否支持周六全天停车。可选值:1:支持0:不支持 is_sun Int Y 是否支持周日全天停车。可选值:1:支持0:不支持 is_holiday Int Y 是否支持法定假日全天停车。可选值:1:支持0:不支持 price Int Y 单价(每30分钟价格)。单位:分 start_price Int Y 起步价。单位:分 holiday_price Int Y 节假日(含周六日)全天价格。单位:分 longitude String N 经度 latitude String N 纬度 返回data参数 NULL 错误码 NULL 3.2.2 租赁订单查询接口(lease/order)同利停车系统可以根据停车场编号调用该接口,查询该停车场租赁订单列表信息。 请求参数 参数名 类型 必须 说明 park_sn String Y 停车场编号 car_plate_no String N 查询指定车牌号,有值时只返回指定车牌号的租赁订单 page Int N 页码,指定需要返回第 { page } 页的数据,默认 1 limit Int N 返回数据数量,指定一次返回的数据条数,默认1000 返回data参数 名称 类型 含义 car_plate_no String 车牌号 date String 租赁日期 start_time String 租赁时段起始时间,格式:HH:ii end_time String 租赁时段结束时间,格式:HH:ii data参数示例 12345678910111213141516[{ 'car_plate_no': '苏 E88888', 'date': '2018-01-01', 'start_time': '19:00', 'end_time': '23:30'},{ 'car_plate_no': '苏 E88888', 'date': '2018-01-01', 'start_time': '19:00', 'end_time': '23:30'},{ 'car_plate_no': '苏 E88888', 'date': '2018-01-01', 'start_time': '19:00', 'end_time': '23:30'},....] 错误码 NULL","tags":[{"name":"错峰停车","slug":"错峰停车","permalink":"https://xuh.io/tags/错峰停车/"}]},{"title":"车加家停车支付方式说明","date":"2018-01-07T09:57:35.000Z","path":"20180107/f3b169ff.html","text":"概述目的为了规范和统一车加家停车费支付体系语境,方便各产品团队就停车费支付功能的产内内涵和可能拓展外延达成共识,特编写本文档。 阅读对象文档阅读对象为车加家、同利的产品、开发、运营相关人员。 名词解释 名词 缩写 说明 悦生活内嵌H5 PAPPCCB 指嵌入建行APP悦生活频道提供无感支付缴费服务的H5应用。 用户 本文档用户特指车加家APP的车主会员。 客户端 本文档客户端指包含车加家APP(iOS/android)、车加家公众号应用、其它一切以扫码器或浏览器访问的车加家WEB应用。 微信APP支付 WXNativePay 在车加家的APP(iOS/android)客户端内,呼起微信客户端完成支付然后再返回到车加家APP的支付方式。 微信公众号支付 WXJSPay 在微信客户端里打开车加家的公众号web页面发起的支付方式 微信H5支付 WXH5Pay 在微信客户端里打开车加家的公众号web页面发起的支付方式 支付方式概览这里为了区分方便,根据支付动作是用户主动发起还是被动扣缴把支付方式分为主动支付和被动支付两大类,被动支付包含出口(收费岗亭)付、离场付;主动付包括预付和场内付。 详细说明主动支付即用户在驱车离场前,通过车加家 场内付场内付主要是指通过扫描场内二维码或者打开车加家公众号、APP等输入车牌号查询费用并缴费,动作发生在车辆即将出错前。 场内付根据收款主体不同,区分为不同的电子缴费柜台,每个柜台下都支持微信和支付宝支付。 注意一点,微信线上支付包含三种方式,APP支付、公众号支付、H5支付,本质上三者没有区别,区别仅仅是用户使用什么方式访问客户端,如果是直接打开APP,则用APP支付,如果是打开车加家公众号,则用公众号支付,其它任何方式打开的页面,只要开启了微信支付且用户选择了微信支付,使用的就是微信的H5方式支付。 车加家柜台支付收款主体为车加家,然后由车加家向停车场结算,这是电子支付的默认柜台,默认所有停车场都自持该中支付。 建行聚合电子支付柜台支付建行聚合电子支付柜台主要应用于建行拓展的停车场内,其收款主体是停车场物业,建行只是作为聚合通道。 停车场独立柜台支付停车场独立柜台支付主要应用于非建行合作的停车场,物业方希望作为收款主体,或者对停车服务本身有定制需求的停车场,车加家作为微信的支付服务商和支付宝的ISV服务商,负责为其定制支付界面,接入微信、支付宝等支付方式。 场内付流程 预付即支付动作发生在车辆入场以前的停车费缴费形式,目前仅包含月卡支付和错峰停车支付。 月卡支付月卡分传统月卡和电子月卡,传统月卡指车主自行到物业处办理并交纳月费的月卡,其收费主体为物业本身,支付现金流和信息流都不会经过车加家停车系统,缴费后的名单直接在场内生效。 电子月卡指通过物业办理且通过车加家APP/公众号缴纳月费的月卡,电子月卡的详细流程及说明这里不赘述,详见《月卡充值模块文档》。 错峰预定支付错峰预定方式相当于车加家的时段月卡,用户根据要求提前支付停车场指定时间段内的停车费,后面在缴费时段可以自由出入而不需另行缴费,该缴费方式详见错峰停车功能模块。 被动支付被动支付指非用户主动发起缴费,而是停车场现场收取或车辆出场后车加家通过各种方式托收停车费的方式。 出口付用户把车辆开到出口处时仍然没有缴费,而在离场前不缴纳不会自动开闸放行的情形。 同利当面付岗亭收费人员通过同利手持设备扫描用户的支付宝或微信付款吗完成扣缴。 建行聚合被扫付对于建行拓展的停车场并在出口岗亭配备建行扫码设备,通过该扫码设备扫用户的支付宝、微信、建行龙支付付款码等方式完成停车费扣缴。 现金支付这是兼容传统方式,不赘述。 离场付离场付是指用户已开通了建行无感支付或其它(支付宝、微信)免密支付功能且名单已下发成功,车辆在离场时自动放行,然后通过后台程序自动托收对应金额的停车费并发送通知。 未来还会支持车加家自己的无感支付,用户通过充值成为VIP用户,VIP用户账户不低于指定数量的金额,这部分VIP用户可以绑定车辆并提前下发到场内,同样可以享有无感支付的停车体验。","tags":[{"name":"支付方式","slug":"支付方式","permalink":"https://xuh.io/tags/支付方式/"}]},{"title":"使用centos7 firewall-cmd做端口转发","date":"2017-12-27T07:27:41.000Z","path":"20171227/a395cd40.html","text":"可以做端口转发的工具有很多,centos7以前的防火墙软件iptables就是其中之一,但是因为使用复杂,centos7以后被firewall代替了,下面就简单看看怎么用它来做端口转发。 开通伪IP1234$ firewall-cmd --query-masquerade# 如果没有开通,则开通$ firewall-cmd --add-masquerade --permanent$ firewall-cmd --reload 设置端口转发12$ firewall-cmd --add-forward-port=port=<开放的本地端口>:proto=tcp:toaddr=<目的主机IP>:toport=<目的主机端口> --permanent$ systecmctl restart firewalld.service OK,就是这么简单","tags":[{"name":"centos","slug":"centos","permalink":"https://xuh.io/tags/centos/"},{"name":"端口转发","slug":"端口转发","permalink":"https://xuh.io/tags/端口转发/"},{"name":"firewall","slug":"firewall","permalink":"https://xuh.io/tags/firewall/"}]},{"title":"Nginx日期切割","date":"2017-11-30T07:21:57.000Z","path":"20171130/6528fea8.html","text":"编写切割日志脚本123456789101112131415161718192021#!/bin/bashLOG_PATH=/server/log/nginxBACKUP_PATH=${LOG_PATH}/backupCURRENT_FILE=""# 备份目录不存在则创建[[ ! -x "${BACKUP_PATH}" ]] && { mkdir -p $BACKUP_PATH ; }# 遍历日志文件并备份for file in `ls ${LOG_PATH}`;do CURRENT_FILE="${LOG_PATH}/${file}" if [[ -f "${CURRENT_FILE}" && "${file: -4}" = ".log" ]];then echo ${file%.*} mv "${CURRENT_FILE}" "${BACKUP_PATH}/${file%.*}-$(date +%Y%m%d).log" fidonekill -USR1 $(cat /server/tengine/logs/nginx.pid) 比如保存在根目录下/rotateLog,设置脚本执行权限 1$ chmod +x /rotateLog 设置crontab任务123$ crontab -e59 23 * * * /rotateLog","tags":[{"name":"tengine","slug":"tengine","permalink":"https://xuh.io/tags/tengine/"},{"name":"nginx lua扩展","slug":"nginx-lua扩展","permalink":"https://xuh.io/tags/nginx-lua扩展/"}]},{"title":"CentOS7.2编译安装tengine","date":"2017-11-30T05:54:33.000Z","path":"20171130/f110b008.html","text":"Tengine介绍Tengine是有淘宝网发起的web服务器项目。它在Nginx的基础上,针对大访问量网站的需求,添加了很多高级功能和特性。它的最终目标是打造一个高效、稳定、安全、已用的web平台。 特性Tengine在Nginx的基础上作了很多改进,但是促使我弃Nginx转投它的原因是动态模块加载支持和多个请求报文重组(多个请求变成一个请求,比如多个css、js文件的访问)。 下面是官方列举的一些特性: 继承Nginx-1.8.1的所有特性,兼容Nginx的配置(最新v2.2.1); 动态模块加载(DSO)支持。加入一个模块不再需要重新编译整个Tengine; 支持HTTP/2协议,HTTP/2模块替代SPDY模块; 流式上传到HTTP后端服务器或FastCGI服务器,大量减少机器的I/O压力; 更加强大的负载均衡能力,包括一致性hash模块、会话保持模块,还可以对后端的服务器进行主动健康检查,根据服务器状态自动上线下线,以及动态解析upstream中出现的域名; 输入过滤器机制支持。通过使用这种机制Web应用防火墙的编写更为方便; 支持设置proxy、memcached、fastcgi、scgi、uwsgi在后端失败时的重试次数 动态脚本语言Lua支持。扩展功能非常高效简单; 支持按指定关键字(域名,url等)收集Tengine运行状态; 组合多个CSS、JavaScript文件的访问请求变成一个请求; 自动去除空白字符和注释从而减小页面的体积 自动根据CPU数目设置进程个数和绑定CPU亲缘性; 监控系统的负载和资源占用从而对系统进行保护; 显示对运维人员更友好的出错信息,便于定位出错机器; 更强大的防攻击(访问速度限制)模块; 更方便的命令行参数,如列出编译的模块列表、支持的指令等; 可以根据访问文件类型设置过期时间; 安装准备编译环境12$ yum update$ yum install gcc gcc-c++ autoconf automake 安装所需组件安装PCRE库PCRE(Perl Compatible Regular Expressions)是一个Perl库,包括 perl 兼容的正则表达式库。nginx rewrite模块处理正则正是依赖于PCRE库。 123456$ cd /usr/local/src$ wget ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre-8.40.tar.gz$ tar xf pcre-8.40.tar.gz$ cd pcre-8.40$ ./configure --prefix=/usr/local/pcre$ make && make install 安装OpenSSLOpenSSL是一个功能强大的安全套接字层密码库,囊括主要的密码算法、常用的秘钥和证书封装管理功能及SSL协议,并提供丰富的应用程序供测试或其它目的使用。Nginx处理https请求的http_ssl_module模块依赖OpenSSL库。 12345678$ mv `which openssl` `which openssl`bak # 可选,备份本机已安装的旧openssl$ cd /usr/local/src$ wget http://www.openssl.org/source/openssl-1.0.2.tar.gz$ tar xf openssl-1.0.2.tar.gz$ cd openssl-1.0.2$ ./config --prefix=/usr/local/openssl$ make && make install$ ln -s /usr/local/openssl/bin/openssl /usr/bin/openssl 安装ZlibZlib是提供资料压缩的函数库,当nginx启动GZIP压缩时会依赖该库。 12345$ cd /usr/local/src$ tar xf zlib-1.2.11.tar.gz$ cd zlib-1.2.11$ ./configure --prefix=/usr/local/zlib$ make && make install 安装jemallocjemalloc是一个更好的内存管理工具,使用jemalloc可以更好的优化Tengine的内存管理。 123456$ cd /usr/local/src$ wget https://github.com/jemalloc/jemalloc/releases/download/5.0.1/jemalloc-5.0.1.tar.bz2$ tar xf jemalloc-5.0.1.tar.bz2$ cd jemalloc-5.0.1$ ./configure --prefix=/usr/local/jemalloc$ make && make install 安装LuaJITLuaJIT(LuaJIT is a Just-In-Time Compilerfor the Lua programming languag),它是Lua脚本的解释器,nginx可以通过lua扩展其功能,开启lua支持就需要LuaJIT。 123456789$ cd /usr/local/src$ wget http://luajit.org/download/LuaJIT-2.0.5.tar.gz$ tar xf LuaJIT-2.0.5.tar.gz$ make PREFIX=/usr/local/luajit$ make install PREFIX=/usr/local/luajit$ # 添加环境变量,告诉Nginx在哪里找LuaJIT执行lua脚本 $ echo "export LUAJIT_LIB=/usr/local/luajit/lib" >> /etc/profile$ echo "export LUAJIT_INC=/usr/local/luajit/include/luajit-2.0" >> /etc/profile$ source /etc/profile 详情可以参照这里。 下载lua模块12345$ cd /usr/local/src$ wget https://github.com/simpl/ngx_devel_kit/archive/v0.3.0.tar.gz$ wget https://github.com/openresty/lua-nginx-module/archive/v0.10.11.tar.gz$ tar xf v0.3.0.tar.gz$ tar xf v0.10.11.tar.gz 这里只需要下载解压到/usr/local/src目录下解压即可。 增加运行tengine的用户和组1$ useradd -s /bin/bash -m -U -c "web user" www 安装TengineTengine和Nginx大多编译和配置选项均相同,Tengine特有的编译选项参见这里。 123456789101112131415161718192021222324252627$ cd /usr/local/src$ curl -O http://tengine.taobao.org/download/tengine-2.2.1.tar.gz$ tar xf tengine-2.2.1.tar.gz$ cd tengine-2.2.1$ ./configure \\--prefix=/server/tengine \\--sbin-path=/server/tengine \\--conf-path=/server/tengine/conf/nginx.conf \\--user=www \\--group=www \\--with-ipv6 \\--dso-path=/server/tengine/dso \\--with-http_concat_module \\--with-http_stub_status_module \\--with-http_ssl_module \\--with-http_gzip_static_module \\--with-http_realip_module \\--with-pcre=/usr/local/src/pcre-8.40 \\--with-zlib=/usr/local/src/zlib-1.2.11 \\--with-openssl=/usr/local/src/openssl-1.0.2 \\--with-jemalloc=/usr/local/src/jemalloc-5.0.1 \\--with-ld-opt="-Wl,-rpath,/usr/local/luajit/lib" \\--add-module=/usr/local/src/lua-nginx-module-0.10.11 \\--add-module=/usr/local/src/ngx_devel_kit-0.3.0$ make && make install$ ln -s /server/tengine/nginx /usr/bin/nginx # 可选 这里有几点需要注意: 运行Tengine的用户和组须先添加,这里省略了; pcre、zlib、openssl、jemalloc相关的编译选项需要指向的是安装源文件路径,不是安装该库是指定的prefix,这个一定要注意; ngx_devel_kit、lua-nginx-module两个模块编译选项只要需要指向解压路径,Tengine编译时会进入相应目录完成编译。 需要增加–with-ipv6 选项,不然启动会报错。 了解更多的编译选项参见这里、这里、还有这里。 配置Tengine服务,设置开机启动创建nginx.service1234567891011121314151617$ vim /lib/systemd/system/nginx.service[Unit]Description=The nginx HTTP and reverse proxy serverAfter=syslog.target network.target remote-fs.target nss-lookup.target[Service]Type=forkingPIDFile=/server/tengine/logs/nginx.pidExecStartPre=/server/tengine/nginx -tExecStart=/server/tengine/nginx -c /server/tengine/conf/nginx.confExecReload=/bin/kill -s HUP $MAINPIDExecStop=/bin/kill -s QUIT $MAINPIDPrivateTmp=true[Install]WantedBy=multi-user.target 设置开机启动12$ chmod 745 /lib/systemd/system/nginx.service$ systemctl enable nginx.service # 设置开机启动 开启服务1$ systemctl start nginx.service 访问http://127.0.0.1看到欢迎页则安装过程完成。 安装PHP5.6123456$ yum -y install epel-release$ rpm -Uvh https://mirror.webtatic.com/yum/el7/webtatic-release.rpm$ yum -y install mod_ssl mcrypt libjpeg*$ yum -y install php56w php56w-devel php56w-mysqlnd php56w-gd php56w-ldap php56w-pear php56w-pdo php56w-xml php56w-xmlrpc php56w-mbstring php56w-bcmath php56w-devel php56w-fpm php56w-intl php56w-mcrypt php56w-pecl-igbinary php56w-pecl-memcached php56w-pecl-mongodb php56w-pecl-redis php56w-pecl-xdebug php56w-opcache$ systemctl enable php-fpm$ systemctl start php-fpm 安装MySQL1https://www.cnblogs.com/yizitrd/p/5363059.html","tags":[{"name":"tengine","slug":"tengine","permalink":"https://xuh.io/tags/tengine/"},{"name":"nginx lua扩展","slug":"nginx-lua扩展","permalink":"https://xuh.io/tags/nginx-lua扩展/"}]},{"title":"MongoDB用户管理","date":"2017-07-19T03:58:17.000Z","path":"20170719/ff4ef26d.html","text":"MongoDB为了使用方便,默认启动是不带用户认证的,也就是说所有人都可以连接并进行读写操作,这在开发阶段当然很方便,但是生产环境可就不能这么裸奔了。 首先来看看MongoDB的启动命令: $ mongod [--auth] --dbpath=<数据存放目录,默认/data/db> --fork --logpath=<日志存放目录> 当带上参数–auth即在安全模式下启动,要在安全模式下启动,必须先完成用户的添加和授权。 添加管理员首次在非认证模式下登录,然后添加管理员账户,在默认的admin数据库里,有一个名为userAdminAnyDatabase的内置角色,顾名思义,该角色的用户即为超级管理员,不过该角色只有管理用户和角色的功能,没有数据库读写权限。 123456789$ mongo#...省略连接输出..> use admin> db.createUser({ user: "userAdmin", pwd: "123456" roles:[{role:"userAdminAnyDatabase",db:"admin"}]}) 重启MongoDB并在安全模式下登录12$ ps -ef | grep mongod | grep -v grep | cut -c 10-16 | xargs kill -9$ mongod --auth --dbpath=<数据存放目录,默认/data/db> --fork --logpath=<日志存放目录> 以管理员身份登录可以先连接到test数据库然后用db.auth进行认证,也可以直接在连接时候认证: $ mongo -u "userAdmin" -p "123456" --authenticationDatabase "admin", 从命令参数可以看出来,连接时认证真不好记那些参数,因此一般我都是先连接后认证的方式: 12345678$ mongo$ use adminMongoDB shell version: 3.2.10connecting to: test> use adminswitched to db admin> db.auth("root","ZzJK7Eg-akh9")1 登录后可以查看下用户表:> db.system.users.find() 添加其它用户和添加管理员方式一致,只是授权的数据库和赋予的角色有区别,一般常用的角色有read、readWrite、dbOwner等。 要了解其它内置角色参考这里https://docs.mongodb.com/manual/reference/built-in-roles/,怎样自定义角色参考这里:https://docs.mongodb.com/manual/core/security-user-defined-roles/#user-defined-roles","tags":[{"name":"用户管理","slug":"用户管理","permalink":"https://xuh.io/tags/用户管理/"},{"name":"auth","slug":"auth","permalink":"https://xuh.io/tags/auth/"}]},{"title":"持续集成介绍","date":"2017-07-07T11:28:51.000Z","path":"20170707/53ff9fdd.html","text":"一、什么是持续集成这个概念源于极限编程(XP),是XP的12种实践之一,通俗地说,持续集成就是频繁地小步幅地向主干分支合并代码。 1.1 特征 自动化 单元测试 容器/虚拟化 版本控制 灵魂的反馈机制 1.2 优点 快速失败 持续集成最大的有点事快速失败,因为是快速的小步幅的合并代码,问题域会更集中,单元测试失败就直接拒绝,避免因提交内容太多造成的集成困难和携带错误发布而导致的发布回滚。 防止分支大幅偏离主干 如果不是经常集成,主干又在不断更新,会导致以后集成的难度变大,甚至难以集成。 1.3 持续交付(Continuous Delivery)和持续(Continuous Deployment)持续交付 持续部署 持续集成是持续交付的前提,持续交付是持续部署的前提。 三者在流程上的关系 注意,从上面我们可以看出来,在整个开发流程中,最重要的环节是持续集成,另外为了表述简便,在以后的文档中不会再可以提持续交付和持续部署,而是不严格的用持续集成代替整个流程或者用应为CI/CD指代整个流程。 1.4 持续集成构建方案(工具)从上面的整个持续集成的流程来看,都是自动化的,人工去完成这个过程显然不现实,因此就有各种各样的继续集成的构建工具,常见的以下几种 Jenkins Gitlab Travis Strider Codeship 二、Gitlab中CI/CD相关概念一些基本概念gitlab中通过根目录下的.gitlab-ci.yml配置持续集成任务。为了理解.gitlab-ci.yml,这里简单说明下gitlab中的几个概念 Pipline Pipline是管道的意思,一次构建任务就是一个Pipline,一个Pipline可以包括多个Stages。 Stages Stages是阶段的意思,就是表示个持续发布流程中的一个环节,比如构建、测试、发布等,Stages是包含多个Jobs(任务)的集合,不包含任何Jobs的Stages会被gitlab-runner自动忽略。 Gitlab默认包含build, test, deploy三个Stages。 Stages按顺序串行执行,任何一个Stages失败,Pipline就失败。 Jobs Jobs在这里是任务的意思。 Stages中的Jobs并行执行,任何一个Jobs失败则Stages失败。 三者的关系 Gitlab中用gitlab-runner来执行pipline。 更多请参见官方文档 https://docs.gitlab.com/ce/ci/#getting-started gitlab持续集成服务器拓扑","tags":[{"name":"CI/CD","slug":"CI-CD","permalink":"https://xuh.io/tags/CI-CD/"},{"name":"持续集成","slug":"持续集成","permalink":"https://xuh.io/tags/持续集成/"}]},{"title":"gitlab系列教程之1-安装","date":"2017-07-07T11:11:16.000Z","path":"20170707/14c433bb.html","text":"安装gitlabe安装docker安装环境为centos_7.0 3.10.0-327.10.1.el7.x86_64,参考链接https://docs.docker.com/engine/installation/linux/centos/#install-docker。安装方式有两种,一种是通过yum安装,另外一种是下载RPM包手动安装,这里采用第一种方式。 这里以安装docker-ce stable为例,docker-ee安装类似,可以参照上面的链接。 设置yum repository安装yum-utils,它提供yum-config-manager这个工具包: 1$ sudo yum install -y yum-utils 添加docker资源库: 123$ sudo yum-config-manager \\ --add-repo \\ https://download.docker.com/linux/centos/docker-ce.repo 如果需要安装edage版,则执行下面的命令启用其repository: 1$ sudo yum-config-manager --enable docker-ce-edage 安装更新yum的安装包索引: 1$ sudo yum makecache fast 安装最新版的docker: 1$ sudo yum install docker-ce 输入docker version输出类似下面的内容则表示安装成功。 启动docker服务 1$ sudo systemctl start docker.service 安装docker-compose因为一个完整的gitlab应用包含gitlab容器、redis、postgresql等服务,每个服务都启动一个docker实例,那么gitlab的管理就会很麻烦,docker-compose就是这么一个管理多容器应用的神器,它基于一个yml配置文件搞定依赖服务之前的管理。 由于docker-compose依赖Python-pip,因此先安装它: 1$ sudo yum install -y python-pip 对安装的pip进行升级: 1$ sudo pip install --upgrade pip 利用pip安装docker-compose 1$ sudo pip install docker-compose 安装后查看版本如下: 安装gitlabgitlab依赖redis、postgresql、其中redis提供缓存服务,postgresql负责持久化数据存储(当然也可以是MySQL),因此需要开启三个容器,大致步骤如下。 启动postgresql容器 12345$ docker run --name gitlab-postgresql -d \\ --env 'DB_NAME=gitlabhq_production' \\ --env 'DB_USER=gitlab' --env 'DB_PASS=password' \\ --volume /srv/docker/gitlab/postgresql:/var/lib/postgresql \\ sameersbn/postgresql:9.4-12 启动redis容器 123docker run --name gitlab-redis -d \\ --volume /srv/docker/gitlab/redis:/var/lib/redis \\ sameersbn/redis:latest 然后再启动gitlab容器,然后通过–link连接redis和postgresql容器 1234567docker run --name gitlab -d \\ --link gitlab-postgresql:postgresql --link gitlab-redis:redisio \\ --publish 10022:22 --publish 10080:80 \\ --env 'GITLAB_PORT=10080' --env 'GITLAB_SSH_PORT=10022' \\ --env 'GITLAB_SECRETS_DB_KEY_BASE=long-and-random-alpha-numeric-string' \\ --volume /srv/docker/gitlab/gitlab:/home/git/data \\ sameersbn/gitlab:8.4.4 这样相当复杂,因此我们可以把这些启动配置写到一个yml文件里面去,让docker-compose帮我们来管理这些容器,而这些容器之间的compose配置,已经有大牛贡献出来了(点击这里查看)。因此我们这里把它的配置文件下载下来 1$ wget https://raw.githubusercontent.com/sameersbn/docker-gitlab/master/docker-compose.yml 然后修改里面的配置项(见下节),然后一条命令即可启动gitlab服务 1$ docker-compose up 不过出现如下错误: 1docker: Error response from daemon: mkdir /data/docker/mnt/overlay/e37098a0043c2bd200b919c4cd466a1cfe98a03865b08be82efa215e32e92196-init/merged/dev/shm: invalid argument. 查了很久,找到两篇帖子(看这里,还有这里),说啥的都有,不过隐约觉得应该是内核版本问题(最新内核版本),具体内核升级参照这里。 果然,升级内核后不再报错了。 配置gitlab这里只列出有配置改动的部分: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990version: '2'services: redis: restart: always image: sameersbn/redis:latest command: - --loglevel warning volumes: - /server/docker/gitlab/redis:/var/lib/redis:Z postgresql: restart: always image: sameersbn/postgresql:9.6-2 volumes: - /server/docker/gitlab/postgresql:/var/lib/postgresql:Z environment: # postgsql的账户设置 - DB_USER=gitlab - DB_PASS=8uf0s3cxdf - DB_NAME=gitlabhq_production - DB_EXTENSION=pg_trgm gitlab: restart: always image: sameersbn/gitlab:9.0.5 depends_on: - redis - postgresql ports: # 把容器内nginx的80端口隐射到宿主机的10080端口上 - "10080:80" # 把容器内ssh的22号端口映射到宿主机的10022端口上 - "10022:22" volumes: # 通过数据卷把gitlab的数据挂载到/server/docker/gitlab/gitlab目录下,这样容器重启后数据就不会丢失了 - /server/docker/gitlab/gitlab:/home/git/data:Z environment: - DEBUG=false - DB_ADAPTER=postgresql - DB_HOST=postgresql - DB_PORT=5432 - DB_USER=gitlab - DB_PASS=8uf0s3cxdf - DB_NAME=gitlabhq_production - REDIS_HOST=redis - REDIS_PORT=6379 # 修改时区 - TZ=Asia/Shanghai - GITLAB_TIMEZONE=Beijing - GITLAB_HTTPS=false - SSL_SELF_SIGNED=false # 发布gitlab应用的主机名称 - GITLAB_HOST=gitlab.cn-etc.com - GITLAB_PORT=10080 - GITLAB_SSH_PORT=10022 - GITLAB_RELATIVE_URL_ROOT= - GITLAB_SECRETS_DB_KEY_BASE=long-and-random-alphanumeric-string - GITLAB_SECRETS_SECRET_KEY_BASE=long-and-random-alphanumeric-string - GITLAB_SECRETS_OTP_KEY_BASE=long-and-random-alphanumeric-string - GITLAB_ROOT_PASSWORD=cnetc123 - [email protected] - GITLAB_NOTIFY_ON_BROKEN_BUILDS=true - GITLAB_NOTIFY_PUSHER=false - [email protected] - [email protected] - [email protected] # 发送邮件时的显示名称 - GITLAB_EMAIL_DISPLAY_NAME=Gitlab系统 - GITLAB_BACKUP_SCHEDULE=daily - GITLAB_BACKUP_TIME=01:00 # SMTP配置 - SMTP_ENABLED=true - SMTP_DOMAIN=smtp.exmail.qq.com - SMTP_HOST=smtp.exmail.qq.com - SMTP_PORT=465 - [email protected] - SMTP_PASS=Gitlab123 - SMTP_STARTTLS=true - SMTP_AUTHENTICATION=login 重新启动 1$ docker-compose up 至此,gitlab安装完成,登录http://gitlab.cn-etc.com:10080 去注册用户新建group、project开干。 持续集成(CI/CD)继续集成介绍为了更好的理解gitlab持续集成的配置和管理,有必要详细理顺与持续集成相关概念,这里单独另开了一篇来说明这些概念,点击这里查看。 安装gitlab-runnerrunner就是一个用来跑集成任务的特殊进程,可以和gitlab在同一台服务器,也可以安装在其它服务器上。 添加gitlab-runner资源库 1$ curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-ci-multi-runner/script.rpm.sh | sudo bash 然后安装 1$ sudo yum install -y gitlab-ci-multi-runner","tags":[{"name":"gitlab","slug":"gitlab","permalink":"https://xuh.io/tags/gitlab/"},{"name":"安装","slug":"安装","permalink":"https://xuh.io/tags/安装/"},{"name":"docker","slug":"docker","permalink":"https://xuh.io/tags/docker/"}]},{"title":"算法分析","date":"2017-06-10T15:07:26.000Z","path":"20170610/d68e5e2f.html","text":"算法(algorithm)是为求解一个问题需要遵循的、被清楚指定的简单指令集合。衡量算法合理性的两个指标是运行时间和占用资源(比如内存)。 分析估计算法性能一般来说是一个理论问题,因此需要有一套正式的系统理论,这个理论基础就是数学。 数学基础定义算法分析用到以下4中定义。 1、如果存在正常数c和$ n_0$使得当$ N \\geq n_0 时T(N) \\leq cf(N),则记为T(N)=O(f(N)) 。$ 2、如果存在正常数c和$ n_0 $使得当$ N \\geq n_0 时T(N) \\geq cg(N),则记为T(N) = Ω(g(N))。$ 3、$T(N)= \\Theta (h(N))当且仅当T(N)=O(h(N))且T(N)=Ω(h(N))。$ 4、$ 如果T(N)=O(p(N))且T(N) \\neq \\Theta (p(N)),则T(N) = o(p(N))。$ 通常在一些点上一个函数的值大于另外一个函数的值,比较这些点的大小往往是没有意义的,在算法分析中我们通常是要比较两个函数的增长趋势,即相对增长率。 从不等式的角度来看,定义1的含义为$T(N)$的相对增长率小于或等于$f(N)$,及$T(N)以不大于f(N)的趋势增长,因此f(N)是T(N)的一个$上界。 定义2含义为$T(N)以大于或等于g(N)的增长率增长,g(N)为T(N)的一个$下界。 定义3表示两个函数的增脏率相同。 定义4与定义1的区别是定义1包含增长率相同的可能,定义1的表方法称着大O记法,定义4的表示方法称为小o记法。 几个重要的法则法则1$ 如果T_1(N)=O(f(N))且T_2(N)=O(g(N)),那么$ $ T_1(N) + T_2(N)=max(O(f(N),O(g(N)))$ $ T_1(N) T_2(N)=O(f(N) g(N))$ 法则2$如果T(N)是一个k次多项式,则T(N)=\\Theta(N^k)$ 法则3$对任意常数k,log^k N = O(N)$ 注意 在大O表示发中,忽略常数和低阶项,比如$T(N) = O(2N^2)$和$T(N)=O(N^2 + N)$都应该表示为$T(N)=O(N^2)$ 可以通过计算极限$\\lim_{n->\\infty}{f(N)/g(N)}$来确定两个函数$f(N)和g(N)$的增长率。如果极限为0,则$f(N)=o(g(N))$;如果为不等于0的常数则$f(N)=\\Theta(g(N))$;如果极限是$\\infty$,则$g(N)=o(f(N))$;如果极限摆动,则二者无关(算法分析中不会出现这种情况)。","tags":[{"name":"算法分析","slug":"算法分析","permalink":"https://xuh.io/tags/算法分析/"}]},{"title":"数学证明方法与递归","date":"2017-06-09T10:34:34.000Z","path":"20170609/d00315b3.html","text":"证明数据结构分析中的结论经常用到两种方法,归纳法和反证法,这两种方法比较直观简单,只有在不得已的情况下才使用高大上的高等数学的证明方法。证明一个定理不成立最好的方法是反证法。 归纳法 归纳法证明的两个关键步骤: 基准情形,归纳假设 证明基准情形归纳法证明第一步是证明基准情形,即对确定的某个小范围(通常是退化的)值的正确性,这一步通常是最简单直接的。 归纳假设接下来是进行归纳假设,一般来说,这意味着假设定理对直到某个有限的数k的所有情况都成立,然后以此作为假设前提,证明定理对下一个值(通常是k+1)也成立,至此证明完成。 下面通过归纳法证明下面的定理。 斐波那契数列:$ F_0 = 1,F_1 = 1,F_2 = 2,F_3 = 3,F_4 = 5,…,F_i = F_{i-1} + F_{i-2} $, 对于$ i \\geq 1 满足 {F_i < ({ 5 \\over 3})^i} $ 证明: 首先,证明基准情形,对于$ F_1 = 1 < {5 \\over 3},F_2 = 2 < {25 \\over 9 }成立。$ 现假设对于$ i = 1,2,…,k $欲证定理成立,在此基础上如果我们能证明$ F_{k+1} < ({5 \\over 3})^{k+1} $成立,则定理成立。根据定义我们有: $ F_{k+1} = F_k + F_{k-1} $ $ < ({5 \\over 3})^k + ({5 \\over 3})^{k-1} $ $ < ({3 \\over 5})({5 \\over 3})^{k+1} + ({3\\over5})^2({5 \\over 3})^{k+1} $ $ < ({24 \\over 25})({5 \\over 3})^{k+1} $ $ < ({5 \\over 3})^{k+1} $ 以此定理成立。 如果 $ N \\geq 1,则\\sum_{i=1}^N i^2 = { N(N+1)(2N+1) \\over 6 } $ 证明 当N=1时定理成立,现假设命题对 $ 1\\leq k \\leq N $ 成立,现证明对于N+1也成立 $ \\sum_{i=1}^{N+1} i^2 = \\sum_{i=1}^N i^2 + (N+1)^2 $ $ ={N(N+1)(2N+1) \\over 6} + (N+1)^2 $ $ =(N+1)[{N(2N+1) \\over 6} + (N+1)] $ $ =(N+1){(2N^2 + 7N + 6) \\over 6} $ $ ={(N+1)[(N+1)+2][2(N+1)+1] \\over 6} $ 于是N+1时也成立,命题得证。 反证法 费马猜想(费马素数),对于$ n \\in N(自然数),F_n = 2^{2^n} + 1 $是素数 这是费马老先生当时随便胡写在书页留白处的,并且说自己已经有证明方法了,可以这里写不下,我擦,据说好几十年没人证明出来,也是当时的计算能力有限,实际上当n=0,1,2,3,4时,$ F_n $分别是 3,5,17,257,65537都是素数,但是n=4时已经非常大了,但是当n=5时,$ F_n=614×6700417 $ ,这就是反证法 证明存在无穷多个素数 证明: 为了证明命题成立,我们先假设不成立,于是存在某个最大的素数$ P_k ,令P_1,P_2,..,P_k $是依序排列的所有素数,$ 令N = P_1P_2P_3…P_k + 1,显然N是比P_k $大的数, 根据假设N不是素数,可是$ P_1,P2,…,P_k $都不能整除N,因为整除的结果总有余数1,这就产生了 矛盾,因此假设不成立,因此原命题成立。","tags":[{"name":"证明方法","slug":"证明方法","permalink":"https://xuh.io/tags/证明方法/"},{"name":"递归","slug":"递归","permalink":"https://xuh.io/tags/递归/"}]},{"title":"数学基础知识","date":"2017-06-07T14:54:13.000Z","path":"20170607/bacfe825.html","text":"算法分析的基础是数学,但是只从走出校门,就很少再接触这些东西,基本上都还给老师了,最近重读算法分析这本书,好多公式又冒出来了,为了以后再接触方便,这里特意记录下来。 指数$$ X^AX^B=X^{A+B} $$ $$ { X^A \\over X^B } = X^{A-B} $$ $$ (X^A)^B = X^{AB} $$ $$ X^N + X^N = 2X^N \\neq X^{2N} $$ $$ 2^N + 2^N = 2^{N+1} $$ 对数在计算机科学中,除非又特殊说明,所有对数都是以2为底。 对数定义 $ X^A = B ,当且仅当 \\log_XB = A $ 根据定义可以推理出几个定理。 定理1$ \\log_AB = { \\log_CB \\over \\log_CA}; C > 0 $ 定理2$ logAB = logA + logB $ 一些有用的公式$$ log{A \\over B} = logA - logB $$ $$ log(A^B) = BlogA $$ $$ logX < X (对所有X>0成立) $$ $$ log1 = 0,log2 = 1,log1024=10, log1048576=20 $$ 级数$$ \\sum_{i=0}^N 2^i = 2^{N+1} - 1 $$ $$ \\sum_{i=0}^N A^i = { A^{N+1} - 1 \\over A - 1} $$ $$ \\sum_{i=0}^N A^i \\leq {1 \\over 1 - A}; (0 < A < 1) $$ $$ \\sum_{i=0}^\\infty = {1 \\over 1 - A} ;(0 < A < 1) $$ $$ \\sum_{i=1}^N = { N(N + 1) \\over 2 } \\approx {N^2 \\over 2} $$ $$ \\sum_{i=1}^N i^2 = {N(N + 1)(2N + 1) \\over 6 } \\approx {N^3 \\over 3} $$ $$ \\sum_{i=1}^N i^k \\approx { N^{k+1} \\over | k + 1 |};k \\neq -1 $$ 欧拉常数上面最后一个公式,当k=-1时不成立,此时我们需要另外的一个公式,这个公式在计算机科学中使用要远比在其它学科中使用得多。 $$ \\sum_{i=1}^N { 1 \\over i } = H_N \\approx \\log_e N,H_N称为调和数 $$ 式中误差γ趋近于0.57721566,这个值称为欧拉常数 两个代数运算公式$$ \\sum_{i=1}^N f(N) = Nf(N) $$ $ \\sum_{i=n_0}^N f(i) = A $ $ \\sum_{i=1}^N f(i) = B $ $ \\sum_{i=1}^{n_0 - 1}f(i) = C,则 A = B - C $ 模运算 如果A - B能够被N整除,那么A与B模N同余(congruent),记为: $ A \\equiv B(mod N) $ 推论 若 $ A \\equiv B(mod N) ,则 A + C \\equiv B + C(mod N) $ 若 $ A \\equiv B(mod N) ,则 AD \\equiv BD(mod N) $","tags":[{"name":"数学基础","slug":"数学基础","permalink":"https://xuh.io/tags/数学基础/"}]},{"title":"UITabBar的基本用法","date":"2017-06-06T08:20:39.000Z","path":"20170606/550b57b7.html","text":"","tags":[]},{"title":"集合类型之0-概述","date":"2017-06-05T11:51:02.000Z","path":"20170605/87569439.html","text":"集合类型所有编程语言中,元素集合都是最重要的数据类型,Swift提供三种集合类型存储集合数据:Arrays(数组)、Sets(集合)、Dictionaries(字典)。 Arrays: 有序数据集 Sets: 无序不重复数据集 Dictionaries: 无序键值对 注意: 1、三种中集合都被实现为泛型集合 2、三种集合都是值类型 3、集合的可变性有声明关键字let、var决定 4、在不需要改变集合的时候,尽量以let声明成常量","tags":[{"name":"集合","slug":"集合","permalink":"https://xuh.io/tags/集合/"}]},{"title":"集合类型之3-字典","date":"2017-06-05T11:50:46.000Z","path":"20170605/bcf0b2d0.html","text":"","tags":[]},{"title":"集合类型之2-集合","date":"2017-06-05T11:50:38.000Z","path":"20170605/d030d951.html","text":"","tags":[]},{"title":"集合类型之1-数组","date":"2017-06-05T11:50:04.000Z","path":"20170605/b9db1057.html","text":"","tags":[]},{"title":"为编译安装的nginx和php-fpm定制服务启动脚本","date":"2015-11-29T05:26:38.000Z","path":"20151129/8d0d65c4.html","text":"源码编译安装的nginx和php-fpm没有centos自带的服务那样可以通过start|stop|restart等管理服务,只能自己定制了。 nginx启动脚本/etc/init.d/nginx123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106#!/bin/bash## Startup script for Nginx - this script starts and stops the nginx daemon## chkconfig: - 85 15# description: Nginx is an HTTP(S) server, HTTP(S) reverse proxy and IMAP/POP3 proxy server# processname: nginx# Source function library.. /etc/rc.d/init.d/functions# Source networking configuration.. /etc/sysconfig/network# Check that networking is up.[ \"$NETWORKING\" = \"no\" ] && exit 0nginx=\"/alidata/server/nginx/sbin/nginx\"prog=$(basename $nginx)NGINX_CONF_FILE=\"/alidata/server/nginx/conf/nginx.conf\"[ -f /etc/sysconfig/nginx ] && . /etc/sysconfig/nginxlockfile=/var/lock/subsys/nginxstart() { [ -x $nginx ] || exit 5 [ -f $NGINX_CONF_FILE ] || exit 6 echo -n $\"Starting $prog: \" daemon $nginx -c $NGINX_CONF_FILE retval=$? echo [ $retval -eq 0 ] && touch $lockfile return $retval}stop() { echo -n $\"Stopping $prog: \" killproc $prog -QUIT retval=$? echo [ $retval -eq 0 ] && rm -f $lockfile return $retval}restart() { configtest || return $? stop sleep 1 start}reload() { configtest || return $? echo -n $\"Reloading $prog: \" killproc $nginx -HUP RETVAL=$? echo}force_reload() { restart}configtest() { $nginx -t -c $NGINX_CONF_FILE}rh_status() { status $prog}rh_status_q() { rh_status >/dev/null 2>&1}case \"$1\" in start) rh_status_q && exit 0 $1 ;; stop) rh_status_q || exit 0 $1 ;; restart|configtest) $1 ;; reload) rh_status_q || exit 7 $1 ;; force-reload) force_reload ;; status) rh_status ;; condrestart|try-restart) rh_status_q || exit 0 ;; *) echo $\"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload|configtest}\" exit 2esac 保存后赋予执行权限,加入开机启动 12sudo chmod +x /etc/init.d/nginxsudo /sbin/chkconfig nginx on PHP-FPM启动脚本/etc/init.d/php-fpm12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667 #!/bin/bash## Startup script for the PHP-FPM server.## chkconfig: 345 85 15# description: PHP is an HTML-embedded scripting language# processname: php-fpm# config: /usr/local/php/etc/php.ini# Source function library.. /etc/rc.d/init.d/functionsPHP_PATH=/alidata/serverDESC=\"php-fpm daemon\"NAME=php-fpm# php-fpm路径DAEMON=$PHP_PATH/php/sbin/$NAME# 配置文件路径CONFIGFILE=$PHP_PATH/php/etc/php-fpm.conf# PID文件路径(在php-fpm.conf设置)PIDFILE=$PHP_PATH/php/var/run/$NAME.pidSCRIPTNAME=/etc/init.d/$NAME# Gracefully exit if the package has been removed.test -x $DAEMON || exit 0rh_start() { $DAEMON -y $CONFIGFILE || echo -n \" already running\"}rh_stop() { kill -QUIT `cat $PIDFILE` || echo -n \" not running\"}rh_reload() { kill -HUP `cat $PIDFILE` || echo -n \" can't reload\"}case \"$1\" in start) echo -n \"Starting $DESC: $NAME\" rh_start echo \".\" ;; stop) echo -n \"Stopping $DESC: $NAME\" rh_stop echo \".\" ;; reload) echo -n \"Reloading $DESC configuration...\" rh_reload echo \"reloaded.\" ;; restart) echo -n \"Restarting $DESC: $NAME\" rh_stop sleep 1 rh_start echo \".\" ;; *) echo \"Usage: $SCRIPTNAME {start|stop|restart|reload}\" >&2 exit 3 ;;esacexit 0 同样赋予执行权限和设置开机启动: 12sudo chmod +x /etc/init.d/php-fpmsudo /sbin/chkconfig php-fpm on","tags":[{"name":"启动脚本","slug":"启动脚本","permalink":"https://xuh.io/tags/启动脚本/"},{"name":"nginx","slug":"nginx","permalink":"https://xuh.io/tags/nginx/"},{"name":"php-fpm","slug":"php-fpm","permalink":"https://xuh.io/tags/php-fpm/"}]},{"title":"centos6.5安装Memcached和php memcached扩展","date":"2015-11-29T05:12:15.000Z","path":"20151129/61bbb9b8.html","text":"安装memcached服务$ yum -y install memcached 把memcached加入开机启动 $ chkconfig memcached on 这个比较简单,yum同时安装依赖的libevent,安装后只要执行memcached -h有输出即安装成功,memcached的默认启动参数可以在/etc/sysconfig/memcached 修改。 安装memcached扩展依赖的libmemcached12345$ wget https://launchpad.net/libmemcached/1.0/1.0.18/+download/libmemcached-1.0.18.tar.gz$ tar zxvf libmemcached-1.0.18.tar.gz $ cd libmemcached-1.0.18$ ./configure -prefix=/usr/local/libmemcached -with-memcached$ make && make install 安装php-devel如果在你php的bin目录下有phpize这个东东这步可以省略,phpize主要用来编译php外挂扩展 $ yum -y install php-devel 安装igbinary扩展123456$ wget http://pecl.php.net/get/igbinary-1.2.1.tgz$ tar zxvf igbinary-1.2.1.tgz$ cd igbinary-1.2.1$ /alidata/server/php/bin/phpize$ ./configure --with-php-config=/alidata/server/php/bin/php-config$ make && make install 然后在在php.ini中增加extension=igbinary.so 安装memcached扩展123456$ wget http://pecl.php.net/get/memcached-2.2.0.tgz$ tar zxvf memcached-2.2.0.tgz$ cd memcached-2.2.0$ /alidata/server/php/bin/phpize$ ./configure -enable-memcached -enable-memcached-igbinary -enable-memcached-json -with-php-config=/alidata/server/php/bin/php-config -with-zlib-dir -with-libmemcached-dir=/usr/local/libmemcached -prefix=/usr/local/phpmemcached --disable-memcached-sasl$ make && make install 最后编辑php.ini,加入memcached扩展 extension=memcached.so 正常情况安装就成功了。","tags":[{"name":"php扩展","slug":"php扩展","permalink":"https://xuh.io/tags/php扩展/"},{"name":"memcached","slug":"memcached","permalink":"https://xuh.io/tags/memcached/"}]},{"title":"查询linux版本方法","date":"2015-11-29T03:54:33.000Z","path":"20151129/1addf0dc.html","text":"1、查看/etc/redhat-release $ cat /etc/redhat-release 2、查看rpm包版本 $ rpm -q centos-release 如果是redhat则执行rpm -q redhat-release 3、所有版本通用的lsb_release 1234567#lsb_release -aLSB Version: :base-4.0-amd64:base-4.0-noarch:core-4.0-amd64:core-4.0-noarchDistributor ID: CentOSDescription: CentOS release 6.5 (Final)Release: 6.5Codename: Final 4、使用uname-a","tags":[{"name":"linux","slug":"linux","permalink":"https://xuh.io/tags/linux/"},{"name":"发型版本","slug":"发型版本","permalink":"https://xuh.io/tags/发型版本/"}]},{"title":"一个 git hook自动部署脚本","date":"2015-11-28T03:21:44.000Z","path":"20151128/56cf4d4.html","text":"http://www.ihorve.com/?p123456789101112131415161718192021222324252627282930313233343536373839#!/bin/sh## git autodeploy script when it matches string \"[deploy]\"## Usage:# 1. put this into the post-receive hook file itself below# 2. `chmod +x post-receive`# 3. Done!# Check the remote git repository whether it is bareIS_BARE=$(git rev-parse --is-bare-repository)if [ -z \"$IS_BARE\" ]; then echo >&2 \"fatal:post-receive:IS_NOT_BARE\" exit 1fi# Get the latest commit subjectSUBJECT=$(git log -1 --pretty=format:\"%s\")# Deploy the HEAD source to publishIS_PULL=$(echo \"$SUBJECT\" | grep \"\\[deploy\\]\")if [ -z \"$IS_PULL\" ];then echo >&2 \"tips:post-receive:IS_NOT_PULL\" exit 1fi# Check the deploy dir whether is existsDEPLOY_DIR=/home/wwwif [ ! -d $DEPLOY_DIR ]; then echo >&2 \"fatal:post-receive:DEPLOY_DIR_NOT_EXISTS:\\\"$DEPLOY_DIR\\\"\" exit 1fi# Check the deploy dir whether it is git repository#IS_GIT=$(git rev-parse --git-dir 2>/dev/null)#if [ -z \"$IS_GIT\" ] ;then# echo >&2 \"fatal:post-receive:IS_NOT_GIT\"# exit 1#fi# Goto the deploy dir and pull the latest sourcescd $DEPLOY_DIR# env -i git reset --hardenv -i git pull","tags":[{"name":"git","slug":"git","permalink":"https://xuh.io/tags/git/"}]},{"title":"让nginx支持path_info","date":"2015-11-28T03:21:44.000Z","path":"20151128/8e316de2.html","text":"nginx默认是不支持path_info模式的,也就是说不支持index.php/*这样的url,因此像Thinkphp中URL model为2的那种路径方式nginx不支持。 只要修改虚拟主机的下面三个地方即可: 12345678910location ~ .php { #删除.php后的$ fastcgi_pass soopj_phpfcgi; fastcgi_index default.php; fastcgi_split_path_info ^((?U).+.php)(/?.+)$; #增加这句 fastcgi_param PATH_INFO $fastcgi_path_info; #增加这句 fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; }","tags":[{"name":"nginx","slug":"nginx","permalink":"https://xuh.io/tags/nginx/"}]},{"title":"CentOS安装samba服务","date":"2015-11-28T03:21:44.000Z","path":"20151128/294fe70.html","text":"检查是否有安装samba服务: $ rpm -qa | grep samba 如果没安装则yum安装 $ yum -y install samba samba-client samba-common 默认的安装目录是/etc/samba,配置文件为smb.conf,先备份下smb文件 $ cd /etc/samba $ cp smb.conf smb.conf.bak 然后编辑smb.conf加入以下内容: 123456[remote_dev] path = /alidata/nginx_www/wx.soopj.com public =no writable = yes write list = @www valid users = @www @www是客户端登录所需要的用户,设置nginx的用户密码: $ smbpasswd -a www 为了避免在启动Samba时出现以下警告信息:rlimit_max: increasing rlimit_max (1024) tominimum Windows limit (16384), 配置内核参数 12345$ ulimit -n 16384$ vi /etc/security/limits.conf#在最后加入以下内容* - nofile 16384 然后运行testparm检测配置文件。 然后启动服务,关闭防火墙: 123$ service smb start$ service nmb start$ service iptables stop centos7后启动服务由systemctl管理,防火墙换成firewalld,因此命令稍有不同。然后客户端即可测试。","tags":[{"name":"samba","slug":"samba","permalink":"https://xuh.io/tags/samba/"}]}]