Unicode编码解明

简介

本文简述了Unicode编码解码相关知识。假设读者已经对此有一定的基本了解

从Base64开始

为了更好说明变长字符编码,我们从Base64编码开始。因为Base64是每个字节进行编码

编码如下字符串:

1
你好, 世界!

得到如下编码:

1
5L2g5aW9LCDkuJbnlYwh

将这段编码放入浏览器控制台中进行不进行任何处理的解密

1
atob('5L2g5aW9LCDkuJbnlYwh');

得到如下结果:

1
你好, 世界!

可以阴影约约看到逗号和感叹号已经被解释出来,而中文没有被正确处理。因为英文的逗号和感叹号在128位ascii表中。而其他的中文字符就无法正确翻译了。

为了能正确翻译中文。我们需要对数据进行一些特殊处理。即实现utf8的编码规范来将二进制转换成一个unicode码,使其能对应上一个具体的字符。(unicode表是一个巨大的map, 每个数字都能对应一个具体的字符, 至于字符的具体渲染由系统提供的字体显示)

atob
atob 是一个很基础的base64转二进制的方法。他只会单独的去处理每个字节而不管其具体的编码实现

UTF8解码

我们先处理第一个字符ä
很明显。这是因为浏览器错误的处理了这个字节的翻译。我们需要将其转换成二进制

1
2
3
4
'ä'.charCodeAt(0); // 228

// 将其转换成二进制
(228).toString(2); // 11100100

我们查看一下[utf8的编码规范](https://zh.wikipedia.org/wiki/UTF-8)。首字符前n个1表示由n个字节组成.以0表示收尾与分割。因此我们提取出其描述的二进制位为1110表示这个字由3个字节组成。那么我们继续提取接下来的2个字符

1
2
'½'.charCodeAt(0).toString(2); // 10111101
' '.charCodeAt(0).toString(2); // 10100000

其中最前的二位10是描述位,是无效的。我们将这三个字节的有效的二进制位提取出来可得: 0100 111101 100000。将其转换成16进制

1
(0b0100111101100000).toString(16); // 4f60

简单验证一下结果。直接在控制台输入:

1
'\u4f60' // 你

我们成功提取出了第一个中文字符。那么剩下的中文字符也很简单了

而编码就是其逆过程

UTF8编码

1
2
3
4
5
6
7
8
// 将 你好, 世界! 编码成16进制字符
const str = '你好, 世界!'

str
.split("")
.map(char => char.charCodeAt(0).toString(16))
.map(hex => "\\u" + hex.padStart(4, "0"))
.join(""); // \u4f60\u597d\u002c\u0020\u4e16\u754c\u0021

直接将输出的字符串复制到控制台可以看到自动转换出的中文

\u表示的是只后面跟的是一个unicode。长度为4字节

Git骚操作之从一个分支中批量将离散的commit 迁移到另一个分支

背景

因为某些原因。分支A与分支B在某个点分叉了。且分叉出来的分支B拥有很多乱七八糟的commit。现在希望将分支B中的代码迁移到分支A中。但因为分支B中有很多其他的commit。因此希望把分支B舍弃,只保留想要的一些commit。

本例是指仅作者为我自己的commit

方案

使用git log将自己的commit选出来。然后切换到分支A上。将选出来的commit cherry-pick。

首先根据某些条件选出想要迁移的commit。最终输出成空格分割的hash号

bash语句如下:

1
2
3
# 本例假设起始commit为4524cb34ea4
# 当前所在分支: 分支B
git log --author moonrailgun --oneline 4524cb34ea4^1...HEAD | awk '{print $1}' | sed '1!G;h;$!d' | xargs echo

语句解释:

  • git log --author moonrailgun --oneline 4524cb34ea4^1...HEAD 获取范围4524cb34ea4(包含该commit)~当前commit的commit中作者是(包含)moonrailgun的列并用单行显示
  • awk '{print $1}' 获取每行中以空格分割的第一列
  • sed '1!G;h;$!d' 将输入按行倒序输出(因为git log输出的最后一行在最上面)
  • xargs echo 将输入的行变成一行

由此可以得到一串用空格分割的hash字符串

然后git checkout A切换到分支A。执行git cherry-pick <此处输入刚刚得到的字符串>
如有冲突,解决冲突后git cherry-pick --continue即可

结论

  • 不会丢失代码
  • 若有冲突能马上解决
  • 保留commit细节
  • 解放生产力

MySQL 查询优化笔记

概述

正确的查询方式与正确的索引可以极大增加数据的查询效率,能极大提升服务器响应时间。对于较大的并发服务来说一点点提升也有比较大的收益。

sql调优

一条sql语句的执行过程:

mysql分为server层和存储引擎层两个部分;
Server 层包括连接器、查询缓存、分析器、优化器、执行器等,涵盖 MySQL 的大多数核心服务功能,以及所有的内置函数(如日期、时间、数学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。
而存储引擎层负责数据的存储和提取。其架构模式是插件式的,支持 InnoDB、MyISAM、Memory 等多个存储引擎。
现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始成为了默认存储引擎。

使用show create table 语句查看一张表的DDL

show create table user

使用desc或explain调优sql语句

e.g. : desc select * from user

即在要执行的语句前可以加上desc或explain查看该语句的查询操作。(两者操作是等价的)

该命令会列出以下项:

  • id
  • select_type
  • table
  • partitions
  • type
  • possible_key
  • key
  • key_len
  • ref
  • rows
  • filterd
  • Extra

其中我们主要需要关心的有三列: key, rowsfiltered

其中:
key主要是表示当前查询用到的键
rows表示查询后返回的行数
filtered表示当前查询过滤了多少。 100表示完全没有进行过滤,是最佳的情况。因为where操作是很消耗性能的

简单的,如select count(1) from employees where gender = 'M'
我们可以通过简单的增加一个索引来实现优化: alter table employees add index(gender)
select count(1) 表示仅返回数据量而不关心数据值 减少其他变量影响结果

这样当我们使用desc语句时可以看到key变为了gender, rows数量为返回的数量,filtered变为了100。可以注意到的是,Extra的值从Using where变为Using index。说明我们这条语句使用了索引

而对于多条件的查询。我们可以通过联合索引来进行优化
如以下查询:
select count(1) from employees where gender = 'F' and birth_date > '1964-01-01'
则可以通过增加联合索引来进行优化
alter table employees add key (gender, birth_date)
注意: 联合索引的顺序很重要,如果顺序不对则无法进行优化

如果我们想在select时统计别的数据,如以下查询:
select count(distinct birth_date) from employees where gender = 'F'
我们也可以使用联合索引进行调优
alter table employees add key (birth_date, gender)
同样的。需要注意顺序, 不过要注意比如是(birth_date, gender), 虽然都是filtered: 100但是执行效率仍有区别
区别如下:

  • (birth_date, gender): key_len为4, 即用到了两个键(gender是tiny,长度1,birth_date是日期,长度4)。rows为4788,Extra为Using where; Using index for group-by
  • (gender, birth_date): key_len为1, 只用到了gender键。rows为149734,Extra为Using index

而对于一些无法优化的。如双向like操作,也可以通过联合索引来应用一部分索引增加部分速度(虽然只会应用一部分)
select count(1) from employees where gender = 'F' and first_name like '%a%' and last_name like '%b%';
可以增加联合索引实现索引下推功能:
alter table employees drop key gender, add key (gender, first_name, last_name);

优化总结

  • 每个索引都是一个BTree(MySQL一般是B+Tree)
  • 什么时候加索引:搜索条件固定,数据分布不均(即需要全表搜索)
  • 双向like是没法优化的
  • 联合索引受顺序影响。而且如果最左索引是范围的话无法使用后面的
  • 使用索引下推,可以减少从主键树上取数据的时间
  • 大多数复杂情况or查询是无法优化的, 但是一些简单查询可以优化
  • 尽量使用join查询而不是子查询,因为join查询能被优化而子查询不行

参考文档

Linux 小资源服务器使用经验总结

善用交换内存

有些时候。作为个人用的服务器我们往往不会去购买一些性能很好的服务器。但是在某些情况下我们却要临时去使用一个比较高的内存去执行某个程序,但是我们当前服务器的资源却无法执行。因为linux为了防止自身系统崩溃,引入了一个OOM Killer机制。即当一个进程占用过多内存时,系统会直接kill掉这个进程并抛出Out of memory错误。
我们可以通过grep "Out of memory" /var/log/messages来查看相关日志。
这个时候我们就可以使用交换内存来用磁盘空间换内存了。

第一步: 创建一个空白文件

使用命令dd if=/dev/zero of=/opt/swapfile bs=1M count=1024来创建一个1GB的空白文件到/opt/swapfile。同样的可以按照这个方法创建其他大小的空白文件。我一般会创建和内存一样大小的交换空间

第二步: 创建交换空间

使用命令mkswap /opt/swapfile来将这个空白文件变成一个swap文件。只有该文件才能将对应的磁盘空间作为一个临时内存。

第三部: 应用交换文件

使用命令swapon /opt/swapfile将该交换空间应用到系统中。此时执行free -h可以看到swap一行多出了1GB空间

交换空间应该设定多大?

实际内存 推荐交换空间 推荐交换空间(开启休眠模式)
⩽ 2GB 2倍 3倍
2GB - 8GB 1倍 2倍
8GB - 64GB 至少4GB 1.5倍
> 64GB 至少4GB 不推荐

参考文章:

其他

  • swapoff -a 移除所有的swap内存空间

玩PUBG时运行VMware被禁止进入游戏的解决方案

起因

因为需要安装一些mysql之类的东西而不想在本机(主要用于娱乐)上安装这些东西。因此想到了在VMware上安装一个linux环境。然后当我完成了VMware的安装时。悲剧发生了。。我无法进行游戏

反外挂系统给我提出的警告是。
发现不允许应用程序VMware

尝试

我看到上面的信息感觉很诧异。。因为BE在我打完了一局正在打第二局的时候把我踢出去了。看来BE的扫描是有个延时特性的。可能是为了防止电脑卡。那么这样的话。我关闭VMware不就好了?

结果是失败。。

一开始我还以为是BE的问题。然后经过一系列问题排查发现。是VMware会在后台不断得启动一系列VMware开头的后台进程。对用户来说没有什么影响,但是BE会检测到并禁止你进入游戏。而且就算在任务管理器中退出了进程他也会自动重启

解决方案

在window服务中将VMware的服务设为手动即可

修改完毕后需要重启电脑后生效

后续

作为代价。当使用VMware时会出现网络异常。只需要在用的时候手动找到服务将其打开即可(就是麻烦了一点)

git使用方法简明教程 - 团队篇

简述

首先,如果你没有看过前文。我推荐你先看一下我的上一篇文章git使用方法简明教程 - 个人篇,这样你能对git这个程序员必用的软件能够有一定的了解。

工作流

团队使用代码进行协作编程与个人使用不太一样。因为它一般来说是在多个分支上进行操作。这里涉及到一个分支(branch的概念,我稍后再讲)

一般来说一个团队的代码仓库至少会有2个以上的分支。有一个生产环境的master (或者release) 分支,一个用于内部内部开发的dev分支。
有两种模式,一种是小团队的快速开发。多个人在同一个分支开发。通过不断地进行push和pull进行代码间的同步。另一种是以开发分支为基础。每个人分出一个自己的分支,完成开发后合并到主开发分支。这种情况可以根据实际需求选择一个人一个个人分支或者一个人开发一个任务就分出一个分支两种情况。

分支

分支,英文名branch。可以通过git branch命令查看本地的分支。
使用git checkout [分支名]的方式来切换不同分支。
我们在使用git log命令查看提交简述的时候也能看到分支所在位置。

分支分为本地分支和远程分支。远程分支是远程代码仓库在本地的映射。我们可以通过git fetch命令进行同步本地的远程分支。通过git branch -r命令可以查看所有的远程分支。
我们正常操作都是操作的本地分支。

通过git checkout -b [新分支名]就可以从当前位置创建一个新的分支。并且切换到新的分支上来。
在完成代码编辑的时候我们可以通过git merge命令进行分支的合并操作。
举例:
我有两个分支masterdev.分别是主分支和开发分支。我在开发分支上完成了一些代码的提交后。想要把dev分支的变更合并到主分支

1
2
$ git checkout master
$ git merge dev

如果有代码冲突的情况我们需要解决冲突,然后使用git commit命令提交合并
如果在代码冲突的过程中想要中断代码合并的过程,使用git merge --abort命令

代码冲突

代码冲突是多人协作时常见的问题,因为你没法保证在你进行编辑的时候其他的不会对代码进行修改。在大多数情况下git会自动处理代码变更。但如果两个开发者共同修改了同一处代码时。git无法自动处理。那么就需要开发者手动解决冲突。
当git出现无法处理的冲突时,它会告知你冲突的文件,并修改文件共同修改处的代码变更为:

1
2
3
4
5
<<<<<<<head
这里是当前代码
=======
这里是远程代码
>>>>>>>xxxxxxxxxx(发生冲突的hash值)

当出现这种情况时,作为开发者你需要做的是仔细比较两边的代码,并把多余的东西删除。然后把所有的冲突全部解决完毕后,提交代码。

rebase

NOTE:rebase是一种破坏性的行为, 请尽量不要在公共分支上进行操作

当我们在自己的分支提交了一系列代码后,需要把完成的功能提交到公共开发分支,那么我们要做的第一步是先把公共分支的代码同步到我们自己的分支。简单的,我们当然可以使用git merge命令。但是,这样会留下一个git生成的merge commit点,而这个commit是无意义的。对于code review(代码审查)来说是一个比较影响的东西。当然了,merge commit 点的好处是所有的操作都是线性的。在进行合并个人分支到公共分支的过程中最好使用merge,而把公共分支同步到个人分支的时候,我建议使用git rebase, 他能让你看上去是从当前公共分支创建出来的个人分支进行往前编辑
rebase的原理是把往前一段距离的代码提交重新打开,然后重新提交一遍。因为是一种破坏性的行为,因此无法与远程服务器同步,需要使用git push -f命令强制覆盖远程分支。

举例:
我有一个个人开发的分支moonrailgun, 然后有一个开发分支dev。在经过一段时间的开发后,两个分支各向前跑了几个commit。现在我希望能够把我的moonrailgun合并到dev分支。为了保证冲突能现在我的个人分支解决,我需要先把dev的变更同步到moonrailgun分支

1
2
# 当前在moonrailgun分支
$ git rebase dev

如果出现冲突。需要解决冲突,然后git add以后使用git rebase --continue命令继续操作。
如果想要中断rebase的过程,使用git rebase --abort

然后你就能很方便的把moonrailgun分支合并到dev分支上了,并且代码的历史线会很干净。

P.S.: 如果团队在分支上进行代码操作,那么常常会出现代码不同步的问题。那么为了保证代码分支线的干净。最好不要使用git pull命令(因为这会留下一个合并的commit点)。推荐的操作如下:

1
2
3
4
$ git fetch
$ git log origin/master
# 查看最新的远程commit的hash值。复制下来
$ git rebase <hash>

这样可以完美的处理单分支多用户进行代码提交。

Vue学习笔记

概述

本章内容主要记录一些在Vue开发过程中比较实用的一些工具,写法之类的东西

本章内容可能会不断、不定期的进行更新。

工具

好用的UI框架: elementUI

写法

根据字符串动态生成不同组件:is属性
通过is属性我们可以做一些比较特殊的定制化操作。比如我们可能定义一系列的控件,然后根据后台返回的表单对象自由组合成对应的表单。

状态管理

除了常见的Vuex以外。Vue+RxJS+VueRx 这套技术栈也是一个不错的选择

如何实现方法既能实现函数式编程又能获取结果进行比较——实现形如add(1)(2)(3)...(n)的方法

面试的时候面试官出了一道题目很有意思。问的是这样的。假如我有一个方法add。如何实现形如add(1)(2)调用返回3
这道题答案很简单,如下:

1
2
3
4
5
function add (x) {
return function (y) {
return x + y;
}
}

但我当时第一时间的反应是这样的: 如何实现形如add(1)(2)(3)...(n)这样的链式调用。
首先这个问题有两个难点:

  • 1.如何解决累加结果的存储问题
  • 2.如何解决他返回的方法可以作为结果来使用

首先这第一个问题是好解决的。只需要在内部存储计数器即可。而第二个问题是无法完全解决的。因为如果要实现链式调用那么返回必须是函数。而函数的typeof值是function, 永远不能作为一个number使用。但是如果放宽限制。我们可以用过重写方法的toString函数来实现对一些隐式调用转换为字符串的匹配。
代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
var sum = 0;
function add(x) {
sum += x;
return add;
}
add.toString = function() {
return sum;
}

var result = add(1)(2)(3);
console.log(typeof result); // 返回function
console.log(result + ''); // 返回6
console.log(result == 6); // 返回true

当然,这里有个问题。就是封装性不好。会污染全局变量。那么我们把add方法做一下闭包处理进行优化一下:

1
2
3
4
5
6
7
8
9
10
11
function add(x) {
var sum = x;
function tmp(y) {
sum += y;
return tmp;
}
tmp.toString = function() {
return sum;
}
return tmp;
}

Firefox、safari的session无法匹配的原因

场景

在测试服务器上出现一个这样奇怪的场景:在Firefox和Safari浏览器上出现session无法正确被识别,而Chrome浏览器能够正常被服务器识别session。

原因

服务端时间与客户端时间不匹配,客户端时间在服务端时间后并差距大于session过期时间(如session过期时间为30分钟, 那么客户端时间往后调30分钟以上或者服务端时间往前调30分钟以上都会出现这个问题)

分析

首先我们看一下以下两张图:

[图1]


[图2]

其中图1是我在火狐浏览器发起的一次http请求的请求头,图2是我在chrome浏览器中发起的http请求的请求头
可以见当cookie过期的时候火狐浏览器发送的请求头中会自动过滤掉已经过期的cookie,而Chrome浏览器则不会。而我们知道服务端的session是根据浏览器发送的cookie进行匹配的。因此火狐浏览器当客户端时间与服务端时间差距过大的时候会出现session无法正常匹配的情况

同理,Safari也会过滤掉失效的cookie。

解决方案

暂时没有比较好的解决方案,只能确保服务端的时间是正常的即可。

nginx使用笔记

简介

主要记录一些使用nginx的一些特殊的技巧。常用的不会记录在内

技巧

使用rewrite重定向时保留referer网站来源

首先我们要知道referer是浏览器主动发起时带入的。跟服务端无关。而浏览器在进行rewrite重定向时并不会带入referer,而目前没有成熟的解决方案。这里记录一个骚操作: 通过cookie来进行同域之间的referer传递

比如我有一个页面www.moonrailgun.com,我想检测当用户是手机端访问时跳转到m.moonrailgun.com
那么要这么做:

1
2
3
4
5
6
7
8
#在www.moonrailgun.com把referer数据写入cookie:
add_header Set-Cookie "referer=$http_referer;Domain=moonrailgun.com";

#在m.moonrailgun.com把referer数据根据cookie信息重新构造出来:
if ($http_cookie ~* "referer=(.+?)(?=;|$)") {
set $referer_cookie $1;
}
proxy_set_header Referer $referer_cookie;