jonathan-fei.pro


  • Home

  • Tags

  • Categories

  • Archive

JS 判断数据类型的三个方法

Posted on 2018-08-13 | In JS

JavaScript的数据类型有 null、undefined、Number、String、Object、Boolean、Symbol,其中Symbol类型是在ES6新增的类型。其中Object类型又包括三个子类型,Function、 Array、Object(狭义)。

判断数据类型有三个方法。

typeof

1
2
3
4
5
6
7
8
9
10
11
12
13
var list = [1,2,3];
var obj = {name: 'Han'}
var fun = function () {}

typeof 1 // 'number'
typeof 'Han' // 'string'
typeof true // 'boolean'
typeof undefined // 'undefined'
typeof null // 'object'
typeof list // 'object'
typeof obj // 'object'
typeof fun // 'function'
typeof a // 'undefined'

通过以上例子可以看出 typeof判断数据类型的特点

  1. 无法准备判断数组和对象
  2. typeof null 的返回值为 “object”, 这是因为早期null就是属于”object”
  3. typeof 一个未定义的变量不会报错,而是返回 “undefined”
    typeof在大多数情况是适用的,如果想准备判断数组,可使用Array.isArray()进行判断。

instanceof

instanceof 方法只适用于判断Object类型的对象,基本类型的数据不适用。它的原理是检查右边的构造函数的prototype属性是否在左边实例的原型链上。

1
2
3
4
5
6
7
8
9
10
11
12
13
var list = [1,2,3];
var obj = {name: 'Han'}
var fun = function () {}

list instanceof Array // true
obj instanceof Object // true
fun instanceof Function // true
1 instanceof Number // false
'Han' instanceof String // false
true instanceof Boolean //false

list instanceof Object // true
fun instanceof Object // true

Object.prototype.toString.call()

这个方法可以准确返回判断对象的数据类型。

1
2
3
4
5
6
7
8
9
10
11
12
var list = [1,2,3];
var obj = {name: 'Han'}
var fun = function () {}

Object.prototype.toString.call(null) // "[object Null]"
Object.prototype.toString.call(undefined) // "[object Undefined]"
Object.prototype.toString.call(1) // "[object Number]"
Object.prototype.toString.call('1') // "[object String]"
Object.prototype.toString.call(true) // "[object Boolean]"
Object.prototype.toString.call(list) // "[object Array]"
Object.prototype.toString.call('Han') // "[object String]"
Object.prototype.toString.call(fun) // "[object Function]"

参考链接: https://wangdoc.com/javascript/types/index.html

JS 数组复制的实现方法

Posted on 2018-08-10 | In JS

JavaScript中的值类型分为基础类型和引用类型,基础类型包括Number、String、Boolean等, 引用类型包括数组和对象。基础类型的值是不可改变的,比较的是真正的值,引用类型的值是可以改变的,比较的是各自的引用。下面的几种方法适用于数组元素中不包括引用类型的值。

方法一: for 循环

1
2
3
4
5
6
7
8

function copy(arr) {
var newArr = [];
for (let i = 0; i < arr.length; i++) {
newArr.push(arr[i]);
}
return newArr;
}

方法二: slice 方法或 concat方法

1
2
3
4

var copy = (arr) => {
return arr.slice(); // return arr.concat();
}

方法三: Array.from 方法

1
2
3
var copy = (arr) => {
return Array.from(arr);
}

JS 数组去重

Posted on 2018-07-27 | In JS

实现方法一: 使用for/forEach 遍历

1
2
3
4
5
6
7
8
9
10

function unique(arr) {
var newArr = [];
arr.forEach(function (item) {
if (newArr.indexOf(item) === -1) {
newArr.push(item)
}
})
return newArr
}

使用indexOf方法不识别NaN的情况。

实现方法二: 使用内置库的reduce方法

1
2
3
4
5
6
7
8
9

function unique(arr) {
return arr.reduce(function (prev, next) {
if (prev.includes(next) === false) {
prev.push(next)
}
return prev;
}, [])
}

使用includes方法适用NaN的情况。

实现方法三: filter 和 indexOf

适用于由基础类型值组成的数组。

1
2
3
4
5
6
7
8
9
10
11
function unique(arr) {
return arr.filter(function (elem, index, arr) {
return arr.indexOf(elem) == index;
})
}

function uniqueES6(arr) {
return arr.filter((elem, index, arr) => {
return arr.indexOf(elem) == index;
})
}

实现方法四: Set

Set对象或称为Set构造函数,是ES6提出的,它是一个值的集合,特性为里面的元素是唯一的。
适用于由基础类型值组成的数组。

1
2
3
4
5
6
7
function unique(arr) {
return [...new Set(arr)]
}

var uniqueES6 = (arr) => {
return [...new Set(arr)]
}

MySQL 触发器的使用简介【转发】

Posted on 2018-02-27 | In 数据库

本想总结一下关于MySQL触发器的使用,但是之前某大神给我分享了两篇很不错的触发器学习文章,遗憾的是没有原文的链接地址,于是只能这样恬不知耻的转发到自己的博客,用于今后的学习以及分享出来。总计两篇,拿走不谢~

第一篇 《触发器入门》

MySQL 5.1包含对触发器的支持。触发器是一种与表操作有关的数据库对象,当触发器所在表上出现指定事件时,将调用该对象,即表的操作事件触发表上的触发器的执行。

【创建触发器】

在MySQL中,创建触发器语法如下:
CREATE TRIGGER trigger_name trigger_time trigger_event
ON tbl_name FOR EACH ROW trigger_stmt

其中:
trigger_name:标识触发器名称,用户自行指定;
trigger_time:标识触发时机,取值为 BEFORE 或 AFTER;
trigger_event:标识触发事件,取值为 INSERT、UPDATE 或 DELETE;
tbl_name:标识建立触发器的表名,即在哪张表上建立触发器;
trigger_stmt:触发器程序体,可以是一句SQL语句,或者用 BEGIN 和 END 包含的多条语句。

由此可见,可以建立6种触发器,即:BEFORE INSERT、BEFORE UPDATE、BEFORE DELETE、AFTER INSERT、AFTER UPDATE、AFTER DELETE。
另外有一个限制是不能同时在一个表上建立2个相同类型的触发器,因此在一个表上最多建立6个触发器。

【trigger_event 详解】

MySQL 除了对 INSERT、UPDATE、DELETE 基本操作进行定义外,还定义了 LOAD DATA 和 REPLACE 语句,这两种语句也能引起上述6中类型的触发器的触发。
LOAD DATA 语句用于将一个文件装入到一个数据表中,相当与一系列的 INSERT 操作。
REPLACE 语句一般来说和 INSERT 语句很像,只是在表中有 primary key 或 unique 索引时,如果插入的数据和原来 primary key 或 unique 索引一致时,会先删除原来的数据,然后增加一条新数据,也就是说,一条 REPLACE 语句有时候等价于一条 INSERT 语句,有时候等价于一条 DELETE 语句加上一条 INSERT 语句。
因此:
INSERT 型触发器:插入某一行时激活触发器,可能通过 INSERT、LOAD DATA、REPLACE 语句触发;
UPDATE 型触发器:更改某一行时激活触发器,可能通过 UPDATE 语句触发;
DELETE 型触发器:删除某一行时激活触发器,可能通过 DELETE、REPLACE 语句触发。

【BEGIN … END 详解】

在MySQL中,BEGIN … END 语句的语法为:
BEGIN
[statement_list]
END
其中,statement_list 代表一个或多个语句的列表,列表内的每条语句都必须用分号(;)来结尾。
而在MySQL中,分号是语句结束的标识符,遇到分号表示该段语句已经结束,MySQL可以开始执行了。因此,解释器遇到 statement_list 中的分号后就开始执行,然后会报出错误,因为没有找到和 BEGIN 匹配的 END。这时就会用到 DELIMITER 命令(DELIMITER 是定界符,分隔符的意思),它是一条命令,不需要语句结束标识,语法为:
DELIMITER new_delemiter
new_delemiter 可以设为1个或多个长度的符号,默认的是分号(;),我们可以把它修改为其他符号,如管道符:
DELIMITER |
在这之后的语句,以分号结束,解释器不会有什么反应,只有遇到了管道符,才认为是语句结束。注意,使用完之后,我们还应该记得把它给修改回来。

【一个完整的创建触发器示例】

假设系统中有两个表:
班级表 class(班级号 classID, 班内学生数 stuCount)
学生表 student(学号 stuID, 所属班级号 classID)
要创建触发器来使班级表中的班内学生数随着学生的添加自动更新,代码如下:
DELIMITER |
create trigger tri_stuInsert after insert
on student for each row
begin
declare c int;
set c = (select stuCount from class where classID=new.classID);
update class set stuCount = c + 1 where classID = new.classID;
end|
DELIMITER ;

【变量详解】
MySQL 中使用 DECLARE 来定义一局部变量,该变量只能在 BEGIN … END 复合语句中使用,并且应该定义在复合语句的开头,

即其它语句之前,语法如下:
DECLARE var_name[,…] type [DEFAULT value]
其中:
var_name 为变量名称,同 SQL 语句一样,变量名不区分大小写;
type 为 MySQL 支持的任何数据类型;
可以同时定义多个同类型的变量,用逗号隔开;
变量初始值为 NULL,如果需要,可以使用 DEFAULT 子句提供默认值,值可以被指定为一个表达式。

对变量赋值采用 SET 语句,语法为:
SET var_name = expr [,var_name = expr] …

【NEW 与 OLD 详解】

上述示例中使用了NEW关键字,和 MS SQL Server 中的 INSERTED 和 DELETED 类似,MySQL 中定义了 NEW 和 OLD,用来表示

触发器的所在表中,触发了触发器的那一行数据。
具体地:
在 INSERT 型触发器中,NEW 用来表示将要(BEFORE)或已经(AFTER)插入的新数据;
在 UPDATE 型触发器中,OLD 用来表示将要或已经被修改的原数据,NEW 用来表示将要或已经修改为的新数据;
在 DELETE 型触发器中,OLD 用来表示将要或已经被删除的原数据;
使用方法: NEW.columnName (columnName 为相应数据表某一列名)
另外,OLD 是只读的,而 NEW 则可以在触发器中使用 SET 赋值,这样不会再次触发触发器,造成循环调用(如每插入一个学生前,都在其学号前加“2013”)。

【查看触发器】

和查看数据库(show databases;)查看表格(show tables;)一样,查看触发器的语法如下:
SHOW TRIGGERS [FROM schema_name];

其中,schema_name 即 Schema 的名称,在 MySQL 中 Schema 和 Database 是一样的,也就是说,可以指定数据库名,这样就不必先“USE database_name;”了。

【删除触发器】

和删除数据库、删除表格一样,删除触发器的语法如下:
DROP TRIGGER [IF EXISTS] [schema_name.]trigger_name

【触发器的执行顺序】

我们建立的数据库一般都是 InnoDB 数据库,其上建立的表是事务性表,也就是事务安全的。这时,若SQL语句或触发器执行失败,MySQL 会回滚事务,有:
①如果 BEFORE 触发器执行失败,SQL 无法正确执行
②SQL 执行失败时,AFTER 型触发器不会触发
③AFTER 类型的触发器执行失败,SQL 会回滚


第二篇 《MySQL-触发器的使用和创建》

【什么是触发器】

触发器(TRIGGER)是MySQL的数据库对象之一,从5.0.2版本开始支持。该对象与编程语言中的函数非常类似,都需要声明、执行等。但是触发器的执行不是由程序调用,也不是由手工启动,而是由事件来触发、激活从而实现执行。有点类似DOM中的事件。
那么为什么要使用数据库对象触发器呢?在具体开发项目时,经常会遇到如下实例:

<1> 在学生表中拥有字段学生姓名,字段学生总数,每当添加一条学生信息时,学生的总数就必须同时更改。

<2> 在学生表中还会有学生姓名的缩写,学生住址等字段,添加学生信息时,往往需要检查电话、邮箱等格式是否正确。
上面的例子使用触发器完成时具有这样的特点,需要在表发生改变时,自动进行一些处理。MySQL在触发DELETE/UPDATE/INSERT语句时就会自动执行所设置的操作,其他SQL语句则不会激活触发器。

【创建触发器】

使用帮助命令查看具体的语法:
CREATE
[DEFINER = { user | CURRENT_USER }]
TRIGGER trigger_name
trigger_time trigger_event
ON tbl_name FOR EACH ROW
trigger_body
语法中
trigger_name:触发器的名称,不能与已经存在的触发器重复;
trigger_time:{ BEFORE | AFTER },表示在事件之前或之后触发;
trigger_event::{ INSERT |UPDATE | DELETE },触发该触发器的具体事件;
tbl_name:该触发器作用在tbl_name上;

【创建简单触发器】

示例1,创建简单触发器

<1> 准备学生表和学生数目统计表

1
2
3
4
5
6
7
8
9
CREATE TABLE student_info (
stu_no INT(11) NOT NULL AUTO_INCREMENT,
stu_name VARCHAR(255) DEFAULT NULL,
PRIMARY KEY (stu_no)
);
CREATE TABLE student_count (
student_count INT(11) DEFAULT 0
);
INSERT INTO student_count VALUES(0);

<2> 创建简单触发器,在向学生表INSERT数据时,学生数增加,DELETE学生时,学生数减少

1
2
3
4
5
6
7
8
CREATE TRIGGER trigger_student_count_insert
AFTER INSERT
ON student_info FOR EACH ROW
UPDATE student_count SET student_count=student_count+1;
CREATE TRIGGER trigger_student_count_delete
AFTER DELETE
ON student_info FOR EACH ROW
UPDATE student_count SET student_count=student_count-1;

<3> INSERT、DELETE数据,查看触发器是否正常工作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
mysql> INSERT INTO student_info VALUES(NULL,'张明'),(NULL,'李明'),(NULL,'王明');
Query OK, 3 rows affected (0.02 sec)
Records: 3 Duplicates: 0 Warnings: 0

mysql> SELECT * FROM student_info;
+--------+----------+
| stu_no | stu_name |
+--------+----------+
| 1 | 张明 |
| 2 | 李明 |
| 3 | 王明 |
+--------+----------+
3 rows in set (0.00 sec)

mysql> SELECT * FROM student_count;
+---------------+
| student_count |
+---------------+
| 3 |
+---------------+
1 row in set (0.00 sec)

mysql> DELETE FROM student_info WHERE stu_name IN('张明','李明');
Query OK, 2 rows affected (0.00 sec)

mysql> SELECT * FROM student_info;
+--------+----------+
| stu_no | stu_name |
+--------+----------+
| 3 | 王明 |
+--------+----------+
1 row in set (0.00 sec)

mysql> SELECT * FROM student_count;
+---------------+
| student_count |
+---------------+
| 1 |
+---------------+
1 row in set (0.00 sec)

可以看到无论是INSERT还是DELETE学生,学生数目都是跟随着变化的。

【创建包含多条执行语句的触发器】

在trigger_body中可以执行多条SQL语句,此时的trigger_body需要使用BEGIN和END作为开始和结束的标志:

1
2
3
4
5
6
7
8
CREATE
[DEFINER = { user | CURRENT_USER }]
TRIGGER trigger_name
trigger_time trigger_event
ON tbl_name FOR EACH ROW
BEGIN
trigger_statement
END;

示例2,创建包含多条执行语句的触发器
依然沿用上面的例子中的表,对student_count表做如下变更:增加student_class字段表示具体年级的学生数,其中0表示全年级,1代表1年级……;同样学生表中也增加该字段。清空两个表中的所有数据。

<1> 删除上例中的两个触发器,初始化student_count表中数据,插入三条数据(0,0),(1,0),(2,0)表示全年级、一年级、二年级的初始人数都是0;

<2> 创建触发器,在INSERT时首先增加学生总人数,然后判断新增的学生是几年级的,再增加对应年级的学生总数:

1
2
3
4
5
6
7
8
9
10
DELIMITER $$
CREATE TRIGGER trigger_student_count_insert
AFTER INSERT
ON student_info FOR EACH ROW
BEGIN
UPDATE student_count SET student_count=student_count+1 WHERE student_class=0;
UPDATE student_count SET student_count=student_count+1 WHERE student_class= NEW.student_class;
END
$$
DELIMITER ;

<3> 创建触发器,在DELETE时首先减少学生总人数,然后判断删除的学生是几年级的,再减少对应年级的学生总数:

1
2
3
4
5
6
7
8
9
10
DELIMITER $$
CREATE TRIGGER trigger_student_count_delete
AFTER DELETE
ON student_info FOR EACH ROW
BEGIN
UPDATE student_count SET student_count=student_count-1 WHERE student_class=0;
UPDATE student_count SET student_count=student_count-1 WHERE student_class= OLD.student_class;
END
$$
DELIMITER ;

<4> 向学生表中分别插入多条不同年级的学生信息,查看触发器是否起作用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
mysql> INSERT INTO student_info VALUES(NULL,'AAA',1),(NULL,'BBB',1),(NULL,'CCC',2),(NULL,'DDD',2),(NULL,'ABB',1),(NULL,'ACC',1);
Query OK, 6 rows affected (0.02 sec)
Records: 6 Duplicates: 0 Warnings: 0

mysql> SELECT * FROM student_info;
+--------+----------+---------------+
| stu_no | stu_name | student_class |
+--------+----------+---------------+
| 4 | AAA | 1 |
| 5 | BBB | 1 |
| 6 | CCC | 2 |
| 7 | DDD | 2 |
| 8 | ABB | 1 |
| 9 | ACC | 1 |
+--------+----------+---------------+
6 rows in set (0.00 sec)

mysql> SELECT * FROM student_count;
+---------------+---------------+
| student_count | student_class |
+---------------+---------------+
| 6 | 0 |
| 4 | 1 |
| 2 | 2 |
+---------------+---------------+
3 rows in set (0.00 sec)

可以看到,总共插入了6条数据,学生总数是6,1年级4个,2年级2个,trigger正确执行。

<5> 从学生表中分别删除多条不同年级的学生信息,查看触发器是否起作用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
mysql> DELETE FROM student_info WHERE stu_name LIKE 'A%';
Query OK, 3 rows affected (0.02 sec)

mysql> SELECT * FROM student_info;
+--------+----------+---------------+
| stu_no | stu_name | student_class |
+--------+----------+---------------+
| 5 | BBB | 1 |
| 6 | CCC | 2 |
| 7 | DDD | 2 |
+--------+----------+---------------+
3 rows in set (0.00 sec)

mysql> SELECT * FROM student_count;
+---------------+---------------+
| student_count | student_class |
+---------------+---------------+
| 3 | 0 |
| 1 | 1 |
| 2 | 2 |
+---------------+---------------+
3 rows in set (0.00 sec)

从学生表中将姓名以A开头的学生信息删除,学生信息删除的同时,数量表也跟随变化。
在上面的示例中,使用了三个新的关键字:DELIMITER、NEW、OLD,这三个关键字在官网上“触发器语法”一节中都有介绍,整理如下:

【DELIMITER】

使用BEGIN…END结构,可以定义一个执行多句SQL的触发器。在BEGIN语句块中,还可以使用其它的语法,例如条件语句和循环语句。在MySQL中,分号”;”标志着SQL语句的结束,但是在触发器要执行的SQL语句中使用到了”;”作为要执行SQL语句的结束标记,所以你需要重新定义结束标识符。
重新定义结束标识符使用DELIMITER关键字,后面跟空格和重新定义的结束标识符。
注意:该语句与其他语句不同的是不需要在语句末尾添加结束标志符,如DELIMITER
的作用是将现有的结束标识符重新定义为
,但是,此时由于习惯或是疏忽在末尾添加了”;”也就是” DELIMITER
;”那么该语句的作用就变成了将符号”
;”作为新的结束标志符。

【NEW和OLD】

NEW在触发器为INSERT事件类型时有效,表示当前正在插入的数据;同理,OLD在触发器类型为DELETE事件类型时有效,表示当前正在删除的数据。
如上面的示例中,可以在触发器中使用NEW.student_class取得正在插入的学生信息中年级值,使用OLD.student_class取得正在删除的学生信息中的年级值。

【触发器的使用限制】

官网“触发器语法和示例” http://dev.mysql.com/doc/refman/5.6/en/trigger-syntax.html

<1> 触发器只能创建在永久表上,不能对临时表创建触发器;

<2> 触发器不能使用CALL语句调用具有返回值或使用了动态SQL的存储过程(存储过程可以使用OUT或INOUT参数返回给触发器返回值)。

<3> 触发器中不能使用开启或结束事务的语句段,比如,开始事务(START TRANSACTION)、提交事务(COMMIT)或是回滚事务(ROLLBACK),但是回滚到一个保存点(SAVEPOINT是允许的,因为回滚到保存点不会结束事务);

<4> 外键不会激活触发器;

<5> 当使用基于行的复制时,从表上的触发器不会因操作主表中的数据而激活。当使用基于语句的复制时,从表上的触发器会被激活。参考 Section 17.4.1.34,“Replication and Triggers”;

<6> 触发器中不允许返回值,因此触发器中不能有返回语句,如果要立即停止一个触发器,应该使用LEAVE语句;

【触发器中的异常机制】

MySQL的触发器是按照BEFORE触发器、行操作、AFTER触发器的顺序执行的,其中任何一步发生错误都不会继续执行剩下的操作。如果是对事务表进行的操作,那么会整个作为一个事务被回滚,但是如果是对非事务表进行的操作,那么已经更新的记录将无法回滚,这也是设计触发器的时候需要注意的问题。
【查看触发器】

可以通过执行SHOW TRIGGERS命令查看触发器,但是因为不能查询指定的触发器,所以每次都返回所有的触发器的信息,使用不方便。但是可以使用查询系统表information_schema.triggers的方式指定查询条件,查看指定的触发器信息。如:

1
2
3
mysql> USE information_schema;
Database changed
mysql> SELECT * FROM triggers WHERE trigger_name='trigger_student_count_insert';

【删除触发器】

DROP TRIGGER trigger_name;

Ruby string#unpack

Posted on 2018-02-26 | In Ruby

在学习字符串压缩之前,需要了解一些基础知识。

1. 比特(bit)运算

(1)比特(bit),台湾称为“位”,是计算机中最小的数据单位,每一个bit的值是0或者1。
(2)字节(Byte):8个二进制位构成一个字节,字节是存储空间的基本计量单位。1个字节可以存储一个英文字母或者半个汉字,也就是说一个汉字需要两个字节存储。
(3)换算

1
2
3
4
5
6
7
8
# 二进制换算
1Byte = 8bits
1Byte = 2nibbles
1nibble = 4bits

#十进制换算
1Byte(B)= 8bits(b)
1KB = 1000bytes

(4)基本术语

  • most significant bit ————- 最高有效位
  • least significant bit ————- 最低有效位
  • ASCII ———————————– 美国信息交换标准代码:在计算机中,所有的数据在存储和运算时都以二进制表示,比如汉字、字符、英文等。
  • hex :十六进制; dec: 十进制; oct:八进制; bin:二进制
  • unpack: 解压; pack:压缩。

2. String unpack

String unpack and Array pack.

1
2
3
4
5
> 'jonathan'.unpack('C*').map{|i| i.to_s 2}
=> ["1101010", "1101111", "1101110", "1100001", "1110100", "1101000", "1100001", "1101110"]

> 'jonathan'.unpack('B*')
=> ["0110101001101111011011100110000101110100011010000110000101101110"]

上面这个例子的意思是将'jonathan' 这个字符串的每个字符解压成二进制位。

1
2
> 'jonathan'.unpack('C*')
=> [106, 111, 110, 97, 116, 104, 97, 110]

上面例子是将'jonathan'这个字符串解压为十进制位。

1
2
3
4
5
> 'jonathan'.unpack('H*')
=> ["6a6f6e617468616e"]

> 'jonathan'.unpack('C*').map{|i| i.to_s 16}
=> ["6a", "6f", "6e", "61", "74", "68", "61", "6e"]

上面这个例子是将'jonathan' 这个字符串解压成16进制位。
更过例子:

1
2
3
4
5
6
7
8
"abc \0\0abc \0\0".unpack('A6Z6')  #=> ["abc", "abc "]
"abc \0\0".unpack('a3a3') #=> ["abc", " \000\000"]
"abc \0abc \0".unpack('Z*Z*') #=> ["abc ", "abc "]
"aa".unpack('b8B8') #=> ["10000110", "01100001"]
"aaa".unpack('h2H2c') #=> ["16", "61", 97]
"\xfe\xff\xfe\xff".unpack('sS') #=> [-2, 65534]
"now=20is".unpack('M*') #=> ["now is"]
"whole".unpack('xax2aX2aX1aX2a') #=> ["h", "e", "l", "l", "o"]

3. 参考资料

ASCII百度百科
Ruby pack unpack
Ruby中操作字符串的基本方法
Packing & Unpacking: A Guide to Reading Binary Data in Ruby

重温注册与登录--学习笔记

Posted on 2018-02-25 | In Rails

最近把Ruby on Rails 教程重温了一遍,学习到了一些之前没注意到的知识。因此以笔记的形式记录下来,目的是为了梳理实作用户系统的步骤(大标题)与方法,以及一些豆知识,具体细节可以参考Ruby on Rails 教程。

一. 注册

豆知识:查看含有user的路径的命令 — rake routes | grep user

1. 建立模型,存储用户信息

(1)模型user是单数,表示单个用户;表名users为复数,代表数据库存储了很多的用户信息。
(2)rails c --sandbox rails的“沙盒”模式,退出后会撤销所做的所有操作。
(3)创建与保存 user = User.new user.save 两步合成一步相当于user = User.create(..)
(4)更新: user = User.first user.name = "jonathan" user.save 合成一步 相当于 user.update(:name => "jonathan")
关于更新用户属性值的写法有以下几种:

1
2
3
第一种写法. user.update(:name => "joanthan")
第二种写法. user.update(name: "joanthan")
第三种写法. user.update_attribute(:name, "joanthan")

update 与 update_attribute 的区别:
update会把user模型中的update_at栏位更新为当前时间;
update_attribute 只更新要求更新的字段值。

2. 数据验证

建立好user的模型,我们需要添加必要的数据验证。有存在性、长度(姓名长度等)、格式(邮箱格式)、唯一性。我们需要用到Active Record中的validate方法,以及正则表达式等来完成这些设置。

对于唯一性来讲,Active Record的设置后的数据唯一性无法保证数据库层的唯一性,比如用户用同一个账号同时注册的情况,解决方法是: 在数据层为需要进行唯一性设置的加上索引,并进行唯一性约束。

3.安全密码

分为两步:
(1)设置密码,并进行二次确认;
(2)验证身份:获取用户提交的密码,哈希加密,然后与数据库存储的密码进行比较。
(3)使用SSL进行加密通信防止数据被恶意用户拦截,确保传输层安全。

实现过程:使用has_secure_password方法
实现条件:user模型中须有 password_digest字段
具体细节:在user模型中调用has_secure_password方法时,会添加如下功能。
A. password_digest存储密码的哈希值;
B. 获得一对虚拟属性,password与password_confirmation;虚拟属性的意思为在model中有这两个属性,但是数据库中没有对应的列。
C. 获得authenticate 方法,如果输入的密码值哈希后与数据库存储的密码哈希相同,返回对应的对象,否则返回true。

豆知识:
(1)使用bcrypt计算密码哈希值
(2)!!会把结果转化成相应的boolean。例如!!user.authenticate("123456")
(3)健壮参数:user_params
params.require(:user).permit(:name, :email)

二.登录

1. 会话

(1)session是两台电脑之间的半永久性连接,使得用户在切换网页时能够记住用户状态;
(2)Users资源使用数据库存储数据,session使用cookie;
(3)Rails中提供session来实现临时会话;
(4)会话不是模型,不能创建类型@user的实例变量。form_for(@user)会让表单向/users发起post请求;这里我们可以使用form_for(:session, url: login_path)

2. 验证用户身份

豆知识:
params[:session][:email] 是一个嵌套散列。

1
2
3
4
5
6
user = User.find_by(email: params[:session][:email].downcase)
user && user.authenticate(params[:session][:password])
意思为当且仅当通过邮箱查到一个用户且提交的密码和数据库的密码匹配

有时习惯写成
user.present? user.authenticate(params[:session][:password])

3. 实现登录

(1)临时会话会让用户登录,关闭浏览器后会关闭会话;
(2)include SessionHelper引入模块;
(3)session[:user_id] = user.id ,将session视为一个散列,user_id为键,user.id为值;
(4)redirect_to user 返回的是 user_url(user)。

三. 高级登录

使用cookie有会话劫持的风险,有四种途径盗取cookie信息
(1)使用包嗅探接活不安全网络中传输的cookie 。 => 使用SSL解决

(2)获取包含记忆令牌的数据库。=> 数据库中不直接存储记忆令牌,而是哈希。

(3)使用跨站脚本攻击。=> Rails会自动转移视图模板的内容。

(4)获取已登录用户的访问权。=> 尽量降低其影响,通过加密存储。

SecureRandom.urlsafe_base64 会返回长度为22的随机字符串,每一个字符串有64种可能。

1. 持久会话

实现步骤:
(1)生成随机字符串,用户记忆令牌(remember_token);
(2)把令牌存入浏览器的cookie,并设置过期时间;
(3)在数据库存储令牌摘要(remember_digest);
(4)在浏览器的cookie存储加密后的用户ID;
(5)如果cookie中有用户ID,那么就用这个ID查找数据库的用户,并检查cookie中的记忆令牌和数据中的哈希摘要是否匹配。

创建有效令牌和摘要:
(1)使用 User.new_token 创建一个新记忆令牌;
(2)使用 User.digest生成摘要;
(3)更新数据库中的摘要。

豆知识: self.remember_token = User.new_token,会把值赋给用户的remember_token属性;如果没有self,remember_token只是一个局部变量。

相关代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 返回指定字符串的哈希摘要
def digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::Password.create(string, cost: cost)
end


#返回一个随机令牌(用于remember_token)
def new_token
SecureRandom.urlsafe_base64
end
end

#为了持久保持会话,在数据库中记住用户
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end

2. 登录时记住登录状态

(1)cookies方法和session方法一样,也将它视为一个散列;

1
2
cookies[:remember_token] = { value: remember_token, expires: 20.years.from_now.utc}
=> cookies.permanent[:remember_token] = remember_token

(2)持久存储用户ID

1
2
3
cookies[:user_id] = user.id #已纯文本的格式存入cookie,这样不安全
cookies.signed[:user_id] = user.id # 对cookie签名,存取浏览器前安全加密cookie
cookies.permanent.signed[:user_id] = user.id # 持久保存用户ID

(3)存储cookie后,用下列代码搜索用户

1
user = User.find_by(id: cookies.signed[:user_id])

A. cookies.signed[:user_id] 会自动解密用户ID;
B. 使用bcrypt确定cookies[:remember_token] 与 remember_digest是否匹配 .

1
Bcrypt::Password.new(remember_digest).is_password?(remember_token)

(4)知识补充:三元运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
例子1:
if boolean?
do_one_thing
else
do_other_thing
end

=> boolean? ? do_one_thing : do_other_thing

例子2:
if boolean?
var = foo
else
var = bar
end

=> var = boolean? ? foo : bar

例子3:
def foo
do_stuff
boolean? ? "bar" : "baz"
end
Ruby 函数的默认返回值是定义体中的最有一个表打式,所以foo方法的返回值会根据boolean?的结果而不同,不是“bar” 就是“baz”

我们写一个简单的脚本来做一个小试验:

1
2
3
4
5
6
7
print "请输入x的值:"
x = gets.to_i

print "请输入y的值:"
y = gets.to_i

puts "x > y ? 1 : 2"

四. 权限系统

1. 必须先登录

2. 只能编辑自己的资料

3. 友好转向: 登录以后重定向到登录前的页面。

4. 用户管理: 加入新字段(如admin)

简述Ransack

Posted on 2018-02-23 | In Rails

一. Ransack 是什么?

Ransack是rails中用来做搜索的一个gem。

二. Ransack 怎么用?

1
2
3
4
5
#controller document
class TblAccountsController < ApplicationController
@q = TblAccount.ransack([:q])
@player = TblAccount.result(distinct: true)
end
1
2
3
4
5
# views document
<% search_form_for @q do |f| %>
<%= f.search_field :account_cont %>
<%= submit_tag "搜索", class: "btn" %>
<% end %>

这里只是简单的用法介绍,另外还有高级用法,这里不再赘述,具体用法可以参见https://github.com/activerecord-hackery/ransack/blob/master/README.md

三. Ransack 原理

Ransack用来做模糊(LIKE)查询。当我们使用Ransack进行查询时,它会通过Active Record 执行sql语句,去搜索并且返回与输入字符相匹配的结果,查询日志代码如下:

1
SELECT  `tbl_account`.* FROM `tbl_account` WHERE (`tbl_account`.`account` LIKE '%玩家%' LIKE '%joe%')

这样会查询中带有“joe”关键字的所有用户,我们可以直接将rails查询语句转化为sql语句,来弄清ransack查询的真相。

1
2
3
4
5
6
 >> TblAccount.ransack(account_cont: "jonathan").result.to_sql # account为tbl_account这张表中的一个字段。
=> SELECT `tbl_account`.* FROM `tbl_account` WHERE (`tbl_account`.`account` LIKE 'jonathan')

相当于
>> TblAccount.where("account like ?", "%jonathan%")
=> SELECT `tbl_account`.* FROM `tbl_account` WHERE (account like 'jonathan')

PS: 关于ransack中的cont以及其他搜索条件介绍,请参考这篇文章

精英的学习之法

Posted on 2018-02-22

本文转载自得到APP万维刚《精英日课2》中的一篇名为“春节特刊-寻常问题的精英解法(3)”,里面谈到了“学习”这个让我们再熟悉不过的词,讲的很好,因此分享出来。

声明:支持正版阅读,万大大的文章写的很好,大家感兴趣的话可以下载得到app订阅《精英日课》专栏,相信会让你收获蛮多。

“学习”是一个我们常常提到的词,有人认为它很简单,有人觉得它很难,有人认为它等同于读书,有人感觉它无处不在;有人在大学毕业就停止了,有人决心要坚持一辈子。

查理·芒格说:我们只有学习了学习的方法之后才能成为高手。今天和明天,咱们就来总结回顾一下:高手该怎么学习。

1.为什么要学习

人身上的可遗传信息,把所有DNA信息加起来,大概只有1.6GB。而成年人大脑中可以存储的信息量,则是100TB。所以最理想的办法是只遗传最基本的本能,把绝大多数技能都留到后天慢慢学习。
这 100TB 的脑容量就可以让你装下很多很多东西了,学习的潜能是巨大的。

正因为人是“生命2.0”,可以在一定程度上自己设计软件系统,人才是万物灵长。

所以我们千万不要像那些1.0的生物一样整天感慨遗传基因好不好,给你那么大的大脑是让你学习用的。

2.只在学习区学习

人脑学习新技能,是发生在神经元这个层面的。因为练习一个动作而经常被一起触发的神经元,最后就会长在一起,整个网络结构长好了,就相当于一个技能长在了你的大脑之中。

心理学家把人对外界的感知分为三个区域,最里面第一层是“舒适区”,是我们熟悉的事物;最外面一层是“恐慌区”,是我们完全陌生的事物。“学习区”,是在舒适区和恐慌区之间,既有熟悉,又有新意。
刻意练习的一个关键,就是要脱离舒适区,只在压力状态下做事。成长来自打击,技艺来自斗争。尝试、失败,再尝试、再失败,直到掌握为止,我们要的就是这样“有成效的失败”。

而且,我们不但应该在学习区练习,而且应该在学习区研究、工作和娱乐。关键的一点,就是你要敢于从舒适区往外走一步,但是还不能走得太远。面对这个世界你不能一味迎合,你要敢于任性地加入一点新东西,但是为了你自己的安全起见,也不要太新!

不但学习新技能应该这样,平时工作也应该这样。心理学家契克森米哈赖说,想要达到心流状态,需要这个工作的难度和你的技能正好配得上。技能低工作难,你就会焦虑;技能高工作简单,你就会无聊。心流是工作难度稍微比你的技能高一点点,你历经一番忘我的挣扎把它完成。这样你不但做事做得有意思、感到时间过得特别快,而且还能不断提高水平。在这样的状态下学习和工作,需要高度集中注意力。

3.想记住,先忘记

你可能会说,我是在学习区,学了,背了,但是啥也没记住,这不是白学了?

其实,人的记忆有两个强度:存储强度(storage strength),和提取强度(retrieval strength)。

存储强度不会随时间减弱!我们每时每刻都在接收大量的信息,而其中的绝大部分都被大脑自动忽略了 —— 这些被忽略的不算。那些剩下来的,你主动希望记住的东西 —— 比如说一个人名,一个电话号码,一个英语单词 —— 一旦进入记忆,就永远在那里了。下次再见到它,它在你大脑里的存储强度会增强,但是哪怕你再也不见它了,它的存储强度也不会减弱。存储强度只增不减。

那么为什么我们会忘记一些东西呢?那是提取强度出了问题。如果没有复习,提取强度就随着时间慢慢减弱。

这其实很容易理解。比如现在让你回忆二十年前同学的音容笑貌,你肯定想不起来什么,但是如果你跟她突然见面,俩人一聊天,当初种种就呼啦一下全回来了。记忆一直都存在那里,只是不好提取了。
心理学家说,提取强度是越用越高。每一次提取记忆,提取强度都会增加;而因为这个记忆在你脑子里又过了一遍,所以存储强度也增加了。

如此说来,考试就是最好的复习。拿本单词书从头到尾反复念 —— 这种效率很低,因为你没有提取动作!复习的时候你应该先考自己这个单词什么意思,实在想不起来了再去看答案。
而最重要的是这个:提取的时候越困难,这个提取动作对两个强度的增加值就越大。

既然如此,最有效率的学习方法就不是天天复习,而是故意把它放在那里等几天,等到提取强度慢慢变弱了,我们已经有点“忘记”了,再搞一次测试式的复习。如此一来你不但用最少的时间学习,而且还能通过遗忘过滤掉一些不必要的信息。

以背单词为例,这就要求我们
• 第一次复习是在一天之后;
• 第二次就要拉长,比如说再等一周之后;
• 然后是一个月之后;
• 然后是几个月甚至更长时间。

而有实验证明,哪怕第一次复习是在两个月之后,你感觉什么都不记得了,其实还是能找到一点印象,这种学法还是有效的!

所以要想记住,最好先忘了。

从这个角度讲,慢慢学才是好办法。这周学了一点就放下,下周接着学得先提取一下前面的记忆,这样多次提取,记忆就加深了很多。交叉着在同一时期内学习几门课程,比学完一门再学另一门的效果好得多。

4.慢慢来,比较快

现在心理学认为人有两种“工作记忆”。“短期工作记忆”相当于计算机内存,是完成一项具体工作的时候大脑随时使用的记忆。“长期工作记忆”相当于硬盘,是我们平时的知识储备。

人的短期工作记忆能力非常有限,现在的公式是一般人只能同时考虑四个东西。这四个东西最好都是跟当前要解决的问题有关的,这就是为什么要专注。

所谓学习,就是把进入到大脑的短期工作记忆的内容强化吸收,写进长期工作记忆之中的过程。所谓创新,就是把长期工作记忆中的相关内容调出来,放在短期工作记忆里跟新信息形成配合的过程。

集中思维,就是在此时此刻的短期工作记忆里强化这个新信息。而发散思维,就是短期工作记忆和长期工作记忆之间的通道。

人脑不像计算机的存储器能瞬间记录信息,大脑是肉长的。这就好像砌墙,你有了砖头和水泥,总要再给点时间让水泥风干了,墙才能结实。

这就是为什么我们第一次学习新技能的时候总觉得很别扭,过段时间,哪怕是睡一觉或者隔一天不练,再拿起来感觉反而好多了。

这一小段“不练”的间隔期对大脑非常重要。就好比练举重,如果你每时每刻都举重,你的肌肉没有办法生长,总要停一段时间长肉。

我们学习各种技能,有时候会遇到一个短暂的“平台期”。比如我记得当年学开车,一开始进步神速,过了一段时间,就感觉有几天虽然一直在练,但是水平不但没提高反而还下降了,怎么开都别扭 —— 这就是平台期。这个平台期其实是大脑内部正在忙着建立新连接的时期,不是没有进步,后台正在重组!

据此我们知道,学习知识并不是越快越好。复杂的技能需要时间间隔。

5.忘记的功夫

修炼这个“忘记”的功夫,咱们得学学庄子。

我们看《庄子》书中人物一旦要去做个什么大事,总要花上好几天时间去做“忘记”的功夫。这种功夫大约分三步:
• 正常状态,能听到外界的声音,考虑外界对自己的评价
• 不听外界的声音了,只关注自己的内心冷认知对对错的判断
• 忘记自己,不听冷认知的判断了,让热认知接手

这一套功夫,特别适合运动员和演员发挥。比如游泳,庄子就特别提到,如果这个人能忘记自己,忘记恐惧,他就能游得特别好。如果一个人游泳的时候总想着“自己”,可能连动作都不会做了。
庄子这简直就是完美的无为状态。

6.无为的状态

那么一位有学问的现代人,应该怎么达到无为的境界呢?

孔子、老子、孟子和庄子,给我们提供了四种路径方法,每个人应该根据自己的性格喜好选择不同的方法,而且在人生的不同阶段,也可以使用不同的方法。

在学习任何技能的初期,我们应该用孔子的办法,勤学苦练,搞沉浸式的教育体验,争取习惯成自然。

具体做事的时候,尤其是要做那些能够影响别人的大事,我们应该参考老子的思想,不轻易干扰复杂系统。

培养艺术品位,提升道德水准,我们可以用孟子的办法,找到身上的闪光点作为种子,慢慢发展壮大。

如果面对一项压力巨大而又特别重要的工作,我们就应该学习庄子,忘记自我,让热认知引导我们发挥水平。

这些手段的确是互相矛盾的,但世界本来就充满矛盾。无为,本来就是一个悖论:你越想得到无为,你就越得不到无为。

这四派武功,你只有实践了,才知道那到底是什么境界。正所谓运用之妙,存乎一心。

如何取出字符串中的汉字?

Posted on 2018-02-09 | In Rails

最近项目中需要实现取出用户的位置信息的小功能,但是数据表中location这个表示位置的字段的记录是这样的:

1
"{\"y\":120, \"x\":30, \"loc\":\"浙江省杭州市\"}"

翻译成编程的意思为:取出字符串的汉字。实现过程如下:

1. 查找匹配中文字符的正则表达式

(1) /[\u4e00-\u9fa5]/; 取出字符串中的所有汉字,并把每个汉字当做一个元素,并已数组的形式返回,即 [“浙”, “江” , “省”, “杭”, “州”, “ 市” ] 。
(2)/\p{Han}+/u;匹配字符串中的所有汉字,并把这一串汉字当做一个元素,以数组的形式返回, 即 [ “浙江省杭州市” ]。

2. 使用gsub方法

Ruby中的gsub方法,它的作用是用指定的字符串置换原字符串中的某部分字符。代码片段:

1
2
3
4
5
6
7
8
9
10
11
if @player.present?
@gamer = @player.first
json.code 0
json.msg "已找到该玩家"
json.playerMsg do
json.location @gamer.location.gsub(/\p{Han}+/u).first
end
else
json.code 1
json.msg "玩家不存在"
end

其中 `@gamer.location.gsub(/\p{Han}+/u)` 返回的是数组 [ “浙江省杭州市” ], 如果只想得到里面的字符串,在其后加上first即可。这个用法与使用where条件查询颇为相似。(where查询后的结果返回的也是数组)

3. 踩过的坑

在实现从字符串中取出汉字这个小功能中,一共产生两个思路:一是查找获取数组中元素的方法;二是如何将数组转化为字符串。但是都失败了,下面就来总结下踩过的坑。
示例代码:

1
2
$ irb
$ > str = ["123"] #=> ["123"]

(1)Ruby中 获取数组的第一个元素

1
$ > str[0] #=> "123"

PS: 在Rails中这样用会报错。undefined method '[]'

(2)把数组中的元素变成一个字符串

1
$ > str.join #=> "123"

PS: 在Rails中这样也会报错。undefined method 'join'

总结: Ruby的编译环境(irb), 与Rails环境不可一概而论。

计算机学习资源小汇总

Posted on 2018-02-09 | In 网络基础

这篇文章统计了一些在日常学习中学到的知识点。不间断更新~

进程与线程的简单解释

互联网协议入门(一)

互联网协议入门(二)

性能优化案例分析之一:软删除是慢查询的罪魁祸首?

性能优化案例分析之二:时间区域查询的性能优化

SQL在线练习

123
jonathan-fei

jonathan-fei

Stay hungry. Stay foolish.

26 posts
5 categories
46 tags
GitHub E-Mail Twitter Weibo
© 2018 jonathan-fei
Powered by Hexo
|
Theme — NexT.Pisces v5.1.3