1. 排序查询 * 语法:order by 子句 * order by 排序字段1 排序方式1 , 排序字段2 排序方式2... * 排序方式: * ASC:升序,默认的。 * DESC:降序。 * 注意: * 如果有多个排序条件,则当前边的条件值一样时,才会判断第二条件。
2. 聚合函数:将一列数据作为一个整体,进行纵向的计算。 1. count:计算个数 1. 一般选择非空的列:主键 2. count(*) 2. max:计算最大值 3. min:计算最小值 4. sum:计算和 5. avg:计算平均值 * 注意:聚合函数的计算,排除null值。 解决方案: 1. 选择不包含非空的列进行计算 2. IFNULL函数3. 分组查询: 1. 语法:group by 分组字段; 2. 注意: 1. 分组之后查询的字段:分组字段、聚合函数 2. where 和 having 的区别? 1. where 在分组之前进行限定,如果不满足条件,则不参与分组。having在分组之后进行限定,如果不满足结果,则不会被查询出来 2. where 后不可以跟聚合函数,having可以进行聚合函数的判断。 -- 按照性别分组。分别查询男、女同学的平均分 SELECT sex , AVG(math) FROM student GROUP BY sex; -- 按照性别分组。分别查询男、女同学的平均分,人数 SELECT sex , AVG(math),COUNT(id) FROM student GROUP BY sex; -- 按照性别分组。分别查询男、女同学的平均分,人数 要求:分数低于70分的人,不参与分组 SELECT sex , AVG(math),COUNT(id) FROM student WHERE math > 70 GROUP BY sex; -- 按照性别分组。分别查询男、女同学的平均分,人数 要求:分数低于70分的人,不参与分组,分组之后。人数要大于2个人 SELECT sex , AVG(math),COUNT(id) FROM student WHERE math > 70 GROUP BY sex HAVING COUNT(id) > 2; SELECT sex , AVG(math),COUNT(id) 人数 FROM student WHERE math > 70 GROUP BY sex HAVING 人数 > 2;4. 分页查询 1. 语法:limit 开始的索引,每页查询的条数; 2. 公式:开始的索引 = (当前的页码 - 1) * 每页显示的条数 -- 每页显示3条记录 SELECT * FROM student LIMIT 0,3; -- 第1页 SELECT * FROM student LIMIT 3,3; -- 第2页 SELECT * FROM student LIMIT 6,3; -- 第3页 3. limit 是一个MySQL"方言"
* 概念: 对表中的数据进行限定,保证数据的正确性、有效性和完整性。 * 分类: 1. 主键约束:primary key 2. 非空约束:not null 3. 唯一约束:unique 4. 外键约束:foreign key* 非空约束:not null,值不能为null 1. 创建表时添加约束 CREATE TABLE stu( id INT, NAME VARCHAR(20) NOT NULL -- name为非空 ); 2. 创建表完后,添加非空约束 ALTER TABLE stu MODIFY NAME VARCHAR(20) NOT NULL; 3. 删除name的非空约束 ALTER TABLE stu MODIFY NAME VARCHAR(20);* 唯一约束:unique,值不能重复 1. 创建表时,添加唯一约束 CREATE TABLE stu( id INT, phone_number VARCHAR(20) UNIQUE -- 添加了唯一约束 ); * 注意mysql中,唯一约束限定的列的值可以有多个null 2. 删除唯一约束 ALTER TABLE stu DROP INDEX phone_number; 3. 在创建表后,添加唯一约束 ALTER TABLE stu MODIFY phone_number VARCHAR(20) UNIQUE;* 主键约束:primary key。 1. 注意: 1. 含义:非空且唯一 2. 一张表只能有一个字段为主键 3. 主键就是表中记录的唯一标识 2. 在创建表时,添加主键约束 create table stu( id int primary key,-- 给id添加主键约束 name varchar(20) ); 3. 删除主键 -- 错误 alter table stu modify id int ; ALTER TABLE stu DROP PRIMARY KEY; 4. 创建完表后,添加主键 ALTER TABLE stu MODIFY id INT PRIMARY KEY; 5. 自动增长: 1. 概念:如果某一列是数值类型的,使用 auto_increment 可以来完成值得自动增长 2. 在创建表时,添加主键约束,并且完成主键自增长 create table stu( id int primary key auto_increment,-- 给id添加主键约束 name varchar(20) ); 3. 删除自动增长 ALTER TABLE stu MODIFY id INT; 4. 添加自动增长 ALTER TABLE stu MODIFY id INT AUTO_INCREMENT;* 外键约束:foreign key,让表于表产生关系,从而保证数据的正确性。 1. 在创建表时,可以添加外键 * 语法: create table 表名( .... 外键列 constraint 外键名称 foreign key (外键列名称) references 主表名称(主表列名称) ); 2. 删除外键 ALTER TABLE 表名 DROP FOREIGN KEY 外键名称; 3. 创建表之后,添加外键 ALTER TABLE 表名 ADD CONSTRAINT 外键名称 FOREIGN KEY (外键字段名称) REFERENCES 主表名称(主表列名称); 4. 级联操作 1. 添加级联操作 语法:ALTER TABLE 表名 ADD CONSTRAINT 外键名称 FOREIGN KEY (外键字段名称) REFERENCES 主表名称(主表列名称) ON UPDATE CASCADE ON DELETE CASCADE ; 2. 分类: 1. 级联更新:ON UPDATE CASCADE 2. 级联删除:ON DELETE CASCADE
1. 多表之间的关系 1. 分类: 1. 一对一(了解): * 如:人和身份证 * 分析:一个人只有一个身份证,一个身份证只能对应一个人 2. 一对多(多对一): * 如:部门和员工 * 分析:一个部门有多个员工,一个员工只能对应一个部门 3. 多对多: * 如:学生和课程 * 分析:一个学生可以选择很多门课程,一个课程也可以被很多学生选择 2. 实现关系: 1. 一对多(多对一): * 如:部门和员工 * 实现方式:在多的一方建立外键,指向一的一方的主键。 2. 多对多: * 如:学生和课程 * 实现方式:多对多关系实现需要借助第三张中间表。中间表至少包含两个字段,这两个字段作为第三张表的外键,分别指向两张表的主键 3. 一对一(了解): * 如:人和身份证 * 实现方式:一对一关系实现,可以在任意一方添加唯一外键指向另一方的主键。 3. 案例 -- 创建旅游线路分类表 tab_category -- cid 旅游线路分类主键,自动增长 -- cname 旅游线路分类名称非空,唯一,字符串 100 CREATE TABLE tab_category ( cid INT PRIMARY KEY AUTO_INCREMENT, cname VARCHAR(100) NOT NULL UNIQUE ); -- 创建旅游线路表 tab_route /* rid 旅游线路主键,自动增长 rname 旅游线路名称非空,唯一,字符串 100 price 价格 rdate 上架时间,日期类型 cid 外键,所属分类 */ CREATE TABLE tab_route( rid INT PRIMARY KEY AUTO_INCREMENT, rname VARCHAR(100) NOT NULL UNIQUE, price DOUBLE, rdate DATE, cid INT, FOREIGN KEY (cid) REFERENCES tab_category(cid) ); /*创建用户表 tab_user uid 用户主键,自增长 username 用户名长度 100,唯一,非空 password 密码长度 30,非空 name 真实姓名长度 100 birthday 生日 sex 性别,定长字符串 1 telephone 手机号,字符串 11 email 邮箱,字符串长度 100 */ CREATE TABLE tab_user ( uid INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(100) UNIQUE NOT NULL, PASSWORD VARCHAR(30) NOT NULL, NAME VARCHAR(100), birthday DATE, sex CHAR(1) DEFAULT '男', telephone VARCHAR(11), email VARCHAR(100) ); /* 创建收藏表 tab_favorite rid 旅游线路 id,外键 date 收藏时间 uid 用户 id,外键 rid 和 uid 不能重复,设置复合主键,同一个用户不能收藏同一个线路两次 */ CREATE TABLE tab_favorite ( rid INT, -- 线路id DATE DATETIME, uid INT, -- 用户id -- 创建复合主键 PRIMARY KEY(rid,uid), -- 联合主键 FOREIGN KEY (rid) REFERENCES tab_route(rid), FOREIGN KEY(uid) REFERENCES tab_user(uid) );2. 数据库设计的范式 * 概念:设计数据库时,需要遵循的一些规范。要遵循后边的范式要求,必须先遵循前边的所有范式要求 设计关系数据库时,遵从不同的规范要求,设计出合理的关系型数据库,这些不同的规范要求被称为不同的范式,各种范式呈递次规范,越高的范式数据库冗余越小。 目前关系数据库有六种范式:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、巴斯-科德范式(BCNF)、第四范式(4NF)和第五范式(5NF,又称完美范式)。 * 分类: 1. 第一范式(1NF):每一列都是不可分割的原子数据项 2. 第二范式(2NF):在1NF的基础上,非码属性必须完全依赖于码(在1NF基础上消除非主属性对主码的部分函数依赖) * 几个概念: 1. 函数依赖:A-->B,如果通过A属性(属性组)的值,可以确定唯一B属性的值。则称B依赖于A 例如:学号-->姓名。 (学号,课程名称) --> 分数 2. 完全函数依赖:A-->B, 如果A是一个属性组,则B属性值得确定需要依赖于A属性组中所有的属性值。 例如:(学号,课程名称) --> 分数 3. 部分函数依赖:A-->B, 如果A是一个属性组,则B属性值得确定只需要依赖于A属性组中某一些值即可。 例如:(学号,课程名称) -- > 姓名 4. 传递函数依赖:A-->B, B -- >C . 如果通过A属性(属性组)的值,可以确定唯一B属性的值,在通过B属性(属性组)的值可以确定唯一C属性的值,则称 C 传递函数依赖于A 例如:学号-->系名,系名-->系主任 5. 码:如果在一张表中,一个属性或属性组,被其他所有属性所完全依赖,则称这个属性(属性组)为该表的码 例如:该表中码为:(学号,课程名称) * 主属性:码属性组中的所有属性 * 非主属性:除过码属性组的属性 3. 第三范式(3NF):在2NF基础上,任何非主属性不依赖于其它非主属性(在2NF基础上消除传递依赖)
1. 命令行: * 语法: * 备份: mysqldump -u用户名 -p密码 数据库名称 > 保存的路径 * 还原: 1. 登录数据库 2. 创建数据库 3. 使用数据库 4. 执行文件。source 文件路径2. 图形化工具:
]]>1.什么是SQL? Structured Query Language:结构化查询语言 其实就是定义了操作所有关系型数据库的规则。每一种数据库操作的方式存在不一样的地方,称为“方言”。2.SQL通用语法 1. SQL 语句可以单行或多行书写,以分号结尾。 2. 可使用空格和缩进来增强语句的可读性。 3. MySQL 数据库的 SQL 语句不区分大小写,关键字建议使用大写。 4. 3 种注释 * 单行注释: -- 注释内容 或 # 注释内容(mysql 特有) * 多行注释: /* 注释 */3. SQL分类 1) DDL(Data Definition Language)数据定义语言 用来定义数据库对象:数据库,表,列等。关键字:create, drop,alter 等 2) DML(Data Manipulation Language)数据操作语言 用来对数据库中表的数据进行增删改。关键字:insert, delete, update 等 3) DQL(Data Query Language)数据查询语言 用来查询数据库中表的记录(数据)。关键字:select, where 等 4) DCL(Data Control Language)数据控制语言(了解) 用来定义数据库的访问权限和安全级别,及创建用户。关键字:GRANT, REVOKE 等
1. 操作数据库:CRUD 1. C(Create):创建 * 创建数据库: * create database 数据库名称; * 创建数据库,判断不存在,再创建: * create database if not exists 数据库名称; * 创建数据库,并指定字符集 * create database 数据库名称 character set 字符集名; * 练习: 创建db4数据库,判断是否存在,并制定字符集为gbk * create database if not exists db4 character set gbk; 2. R(Retrieve):查询 * 查询所有数据库的名称: * show databases; * 查询某个数据库的字符集:查询某个数据库的创建语句 * show create database 数据库名称; 3. U(Update):修改 * 修改数据库的字符集 * alter database 数据库名称 character set 字符集名称; 4. D(Delete):删除 * 删除数据库 * drop database 数据库名称; * 判断数据库存在,存在再删除 * drop database if exists 数据库名称; 5. 使用数据库 * 查询当前正在使用的数据库名称 * select database(); * 使用数据库 * use 数据库名称;2. 操作表 1. C(Create):创建 1. 语法: create table 表名( 列名1 数据类型1, 列名2 数据类型2, .... 列名n 数据类型n ); * 注意:最后一列,不需要加逗号(,) * 数据库类型: 1. int:整数类型 * age int, 2. double:小数类型 * score double(5,2) 3. date:日期,只包含年月日,yyyy-MM-dd 4. datetime:日期,包含年月日时分秒 yyyy-MM-dd HH:mm:ss 5. timestamp:时间错类型 包含年月日时分秒 yyyy-MM-dd HH:mm:ss * 如果将来不给这个字段赋值,或赋值为null,则默认使用当前的系统时间,来自动赋值 6. varchar:字符串 * name varchar(20):姓名最大20个字符 * zhangsan 8个字符 张三 2个字符 * 创建表 create table student( id int, name varchar(32), age int , score double(4,1), birthday date, insert_time timestamp ); * 复制表: * create table 表名 like 被复制的表名; 2. R(Retrieve):查询 * 查询某个数据库中所有的表名称 * show tables; * 查询表结构 * desc 表名; 3. U(Update):修改 1. 修改表名 alter table 表名 rename to 新的表名; 2. 修改表的字符集 alter table 表名 character set 字符集名称; 3. 添加一列 alter table 表名 add 列名 数据类型; 4. 修改列名称 类型 alter table 表名 change 列名 新列别 新数据类型; alter table 表名 modify 列名 新数据类型; 5. 删除列 alter table 表名 drop 列名; 4. D(Delete):删除 * drop table 表名; * drop table if exists 表名 ;
1. 添加数据: * 语法: * insert into 表名(列名1,列名2,...列名n) values(值1,值2,...值n); * 注意: 1. 列名和值要一一对应。 2. 如果表名后,不定义列名,则默认给所有列添加值 insert into 表名 values(值1,值2,...值n); 3. 除了数字类型,其他类型需要使用引号(单双都可以)引起来2. 删除数据: * 语法: * delete from 表名 [where 条件] * 注意: 1. 如果不加条件,则删除表中所有记录。 2. 如果要删除所有记录 1. delete from 表名; -- 不推荐使用。有多少条记录就会执行多少次删除操作 2. TRUNCATE TABLE 表名; -- 推荐使用,效率更高 先删除表,然后再创建一张一样的表。3. 修改数据: * 语法: * update 表名 set 列名1 = 值1, 列名2 = 值2,... [where 条件]; * 注意: 1. 如果不加任何条件,则会将表中所有记录全部修改。
* select * from 表名;1. 语法: select 字段列表 from 表名列表 where 条件列表 group by 分组字段 having 分组之后的条件 order by 排序 limit 分页限定2. 基础查询 1. 多个字段的查询 select 字段名1,字段名2... from 表名; * 注意: * 如果查询所有字段,则可以使用*来替代字段列表。 2. 去除重复: * distinct 3. 计算列 * 一般可以使用四则运算计算一些列的值。(一般只会进行数值型的计算) * ifnull(表达式1,表达式2):null参与的运算,计算结果都为null * 表达式1:哪个字段需要判断是否为null * 如果该字段为null后的替换值。 4. 起别名: * as:as也可以省略3. 条件查询 1. where子句后跟条件 2. 运算符 * > 、< 、<= 、>= 、= 、<> * BETWEEN...AND * IN( 集合) * LIKE:模糊查询 * 占位符: * _:单个任意字符 * %:多个任意字符 * IS NULL * and 或 && * or 或 || * not 或 ! -- 查询年龄大于20岁 SELECT * FROM student WHERE age > 20; SELECT * FROM student WHERE age >= 20; -- 查询年龄等于20岁 SELECT * FROM student WHERE age = 20; -- 查询年龄不等于20岁 SELECT * FROM student WHERE age != 20; SELECT * FROM student WHERE age <> 20; -- 查询年龄大于等于20 小于等于30 SELECT * FROM student WHERE age >= 20 && age <=30; SELECT * FROM student WHERE age >= 20 AND age <=30; SELECT * FROM student WHERE age BETWEEN 20 AND 30; -- 查询年龄22岁,18岁,25岁的信息 SELECT * FROM student WHERE age = 22 OR age = 18 OR age = 25 SELECT * FROM student WHERE age IN (22,18,25); -- 查询英语成绩为null SELECT * FROM student WHERE english = NULL; -- 不对的。null值不能使用 = (!=) 判断 SELECT * FROM student WHERE english IS NULL; -- 查询英语成绩不为null SELECT * FROM student WHERE english IS NOT NULL; -- 查询姓马的有哪些? like SELECT * FROM student WHERE NAME LIKE '马%'; -- 查询姓名第二个字是化的人 SELECT * FROM student WHERE NAME LIKE "_化%"; -- 查询姓名是3个字的人 SELECT * FROM student WHERE NAME LIKE '___'; -- 查询姓名中包含德的人 SELECT * FROM student WHERE NAME LIKE '%德%';
]]>开发网站的过程中,我们经常遇到某些耗时很长的javascript操作。其中,既有异步的操作(比如ajax读取服务器数据),也有同步的操作(比如遍历一个大型数组),它们都不是立即能得到结果的。
通常的做法是,为它们指定回调函数(callback)。即事先规定,一旦它们运行结束,应该调用哪些函数。
但是,在回调函数方面,jQuery的功能非常弱。为了改变这一点,jQuery开发团队就设计了deferred对象。
简单说,deferred
对象就是jQuery的回调函数解决方案。在英语中,defer的意思是”延迟”,所以deferred
对象的含义就是”延迟”到未来某个点再执行。
它解决了如何处理耗时操作的问题,对那些操作提供了更好的控制,以及统一的编程接口。它的主要功能,可以归结为四点。下面我们通过示例代码,一步步来学习。
首先,回顾一下jQuery的ajax操作的传统写法:
1 | $.ajax({ |
在上面的代码中,$.ajax()接受一个对象参数,这个对象包含两个方法:success方法指定操作成功后的回调函数,error方法指定操作失败后的回调函数。
$.ajax()操作完成后,如果使用的是低于1.5.0版本的jQuery,返回的是XHR对象,你没法进行链式操作;如果高于1.5.0版本,返回的是deferred对象,可以进行链式操作。
现在,新的写法是这样的:
1 | $.ajax("test.html") |
可以看到,done()相当于success方法,fail()相当于error方法。采用链式写法以后,代码的可读性大大提高。
deferred对象的一大好处,就是它允许你自由添加多个回调函数。
还是以上面的代码为例,如果ajax操作成功后,除了原来的回调函数,我还想再运行一个回调函数,怎么办?
很简单,直接把它加在后面就行了。
1 | $.ajax("test.html") |
回调函数可以添加任意多个,它们按照添加顺序执行。
deferred对象的另一大好处,就是它允许你为多个事件指定一个回调函数,这是传统写法做不到的。
请看下面的代码,它用到了一个新的方法$.when():
1 | $.when($.ajax("test1.html"), $.ajax("test2.html")) |
这段代码的意思是,先执行两个操作$.ajax(“test1.html”)和$.ajax(“test2.html”),如果都成功了,就运行done()指定的回调函数;如果有一个失败或都失败了,就执行fail()指定的回调函数。
deferred对象的最大优点,就是它把这一套回调函数接口,从ajax操作扩展到了所有操作。也就是说,任何一个操作—-不管是ajax操作还是本地操作,也不管是异步操作还是同步操作—-都可以使用deferred对象的各种方法,指定回调函数。
我们来看一个具体的例子。假定有一个很耗时的操作wait:
1 | var wait = function(){ |
我们为它指定回调函数,应该怎么做呢?
很自然的,你会想到,可以使用$.when():
1 | $.when(wait()) |
但是,这样写的话,done()方法会立即执行,起不到回调函数的作用。原因在于$.when()的参数只能是deferred对象,所以必须对wait()进行改写:
1 | var dtd = $.deferred(); // 新建一个deferred对象 |
现在,wait()函数返回的是deferred对象,这就可以加上链式操作了。
1 | $.when(wait(dtd)) |
wait()函数运行完,就会自动运行done()方法指定的回调函数。
如果仔细看,你会发现在上面的wait()函数中,还有一个地方我没讲解。那就是dtd.resolve()的作用是什么?
要说清楚这个问题,就要引入一个新概念”执行状态”。jQuery规定,deferred对象有三种执行状态—-未完成,已完成和已失败。如果执行状态是”已完成”(resolved),deferred对象立刻调用done()方法指定的回调函数;如果执行状态是”已失败”,调用fail()方法指定的回调函数;如果执行状态是”未完成”,则继续等待,或者调用progress()方法指定的回调函数(jQuery1.7版本添加)。
前面部分的ajax操作时,deferred对象会根据返回结果,自动改变自身的执行状态;但是,在wait()函数中,这个执行状态必须由程序员手动指定。dtd.resolve()的意思是,将dtd对象的执行状态从”未完成”改为”已完成”,从而触发done()方法。
类似的,还存在一个deferred.reject()方法,作用是将dtd对象的执行状态从”未完成”改为”已失败”,从而触发fail()方法。
1 | var dtd = $.deferred(); // 新建一个deferred对象 |
上面这种写法,还是有问题。那就是dtd是一个全局对象,所以它的执行状态可以从外部改变。
请看下面的代码:
1 | var dtd = $.deferred(); // 新建一个deferred对象 |
我在代码的尾部加了一行dtd.resolve(),这就改变了dtd对象的执行状态,因此导致done()方法立刻执行,跳出”哈哈,成功了!”的提示框,等5秒之后再跳出”执行完毕!”的提示框。
为了避免这种情况,jQuery提供了deferred.promise()方法。它的作用是,在原来的deferred对象上返回另一个deferred对象,后者只开放与改变执行状态无关的方法(比如done()方法和fail()方法),屏蔽与改变执行状态有关的方法(比如resolve()方法和reject()方法),从而使得执行状态不能被改变。
请看下面的代码:
1 | var dtd = $.deferred(); // 新建一个deferred对象 |
在上面的这段代码中,wait()函数返回的是promise对象。然后,我们把回调函数绑定在这个对象上面,而不是原来的deferred对象上面。这样的好处是,无法改变这个对象的执行状态,要想改变执行状态,只能操作原来的deferred对象。
不过,更好的写法是allenm所指出的,将dtd对象变成wait()函数的内部对象。
1 | var wait = function(dtd){ |
另一种防止执行状态被外部改变的方法,是使用deferred对象的建构函数$.deferred()。
这时,wait函数还是保持不变,我们直接把它传入$.deferred():
1 | $.deferred(wait) |
jQuery规定,$.deferred()可以接受一个函数名(注意,是函数名)作为参数,$.deferred()所生成的deferred对象将作为这个函数的默认参数。
除了上面两种方法以外,我们还可以直接在wait对象上部署deferred接口。
1 | var dtd = $.deferred(); // 生成deferred对象 |
这里的关键是dtd.promise(wait)这一行,它的作用就是在wait对象上部署deferred接口。正是因为有了这一行,后面才能直接在wait上面调用done()和fail()。
前面已经讲到了deferred对象的多种方法,下面做一个总结:
(1) $.deferred() 生成一个deferred对象。
(2) deferred.done() 指定操作成功时的回调函数
(3) deferred.fail() 指定操作失败时的回调函数
(4) deferred.promise() 没有参数时,返回一个新的deferred对象,该对象的运行状态无法被改变;接受参数时,作用为在参数对象上部署deferred接口。
(5) deferred.resolve() 手动改变deferred对象的运行状态为”已完成”,从而立即触发done()方法。
(6)deferred.reject() 这个方法与deferred.resolve()正好相反,调用后将deferred对象的运行状态变为”已失败”,从而立即触发fail()方法。
(7) $.when() 为多个操作指定回调函数。
除了这些方法以外,deferred对象还有二个重要方法,上面的教程中没有涉及到。
(8)deferred.then()
有时为了省事,可以把done()和fail()合在一起写,这就是then()方法。
1 | $.when($.ajax( "/main.php" )) |
如果then()有两个参数,那么第一个参数是done()方法的回调函数,第二个参数是fail()方法的回调方法。如果then()只有一个参数,那么等同于done()。
(9)deferred.always()
这个方法也是用来指定回调函数的,它的作用是,不管调用的是deferred.resolve()还是deferred.reject(),最后总是执行。
1 | $.ajax( "test.html" ) |
git init
[project-name] 初始化 在工作路径上创建主分支git config [--global] user.name "[name]"
设置提交代码时的用户信息git config [--global] user.email "[email address]"
设置提交代码时的用户信息git clone
[地址] 克隆远程仓库git clone -b
[分支名] [地址] 克隆分支的代码到本地git add -A
保存所有的修改git add .
保存新的添加和修改,但是不包括删除git add -u
保存修改和删除,但是不包括新建文件。
所以默认使用git add -A就行git commit –m
“本次提交描述” commit可以一次提交缓冲区的所有文件,相当于一个版本。git pull origin
[分支名] 从指定分支获取最新迭代git push origin
[分支名] 推送当前迭代到制动分支
git status
查看状态git push -- force
强制推送
git branch
查看当前分支git branch
[分支名] 新建分支git checkout
[分支名] 切换分支git checkout -b
[分支名] 创建并切换分支git branch -v
查看分支以及提交hash值和commit信息git merge
[分支名] 把该分支的内容合并到现有分支上git branch -d
[分支名] 删除分支git branch -D
[分支名] 强制删除 若没有其他分支合并就删除 d会提示 D不会git branch -m
[旧分支名] [新分支名] 修改分支名git branch -M
[旧分支名] [新分支名] 修改分支名 M强制修改 若与其他分支有冲突也会创建(慎用)git branch -r
// 列出远程分支(远程所有分支名)git branch -a
// 查看远程分支(列出远程分支以及本地分支名)git fetch
更新remote索引git push -u origin
[分支名] 将本地分支推送到origin主机,同时指定origin为默认主机
虽然JAVA8中的stream API与JAVA I/O中的InputStream和OutputStream在名字上比较类似,但是其实是另外一个东西,Stream API是JAVA函数式编程中的一个重要组成部分。
stream是一个可以对单列集合中的元素执行各种计算操作的一个元素序列。
1 | public static void main(String[] args) { |
stream包含中间(intermediate operations)和最终(terminal operation)两种形式的操作。中间操作(intermediate operations)的返回值还是一个stream,因此可以通过链式调用将中间操作(intermediate operations)串联起来。最终操作(terminal operation)只能返回void或者一个非stream的结果。在上述例子中:filter
, map
,sorted
是中间操作,而forEach
是一个最终操作。更多关于stream的中可用的操作可以查看java doc。上面例子中的链式调用也被称为操作管道流。
大多数流操作都接受某种lambda表达式参数,这是一个指定操作确切行为的功能接口。这些操作大多数都必须是无干扰的和无状态的。这意味着什么?
当函数不修改流的基础数据源时,它是无干扰的,例如,在上面的示例中,没有lambda表达式myList通过添加或删除集合中的元素来进行修改。
当操作的执行是确定性的时,函数是无状态的,例如,在上面的示例中,lambda表达式不依赖于外部变量的任何可变变量或状态,这些变量或状态可能在执行期间发生变化。
可以从各种数据源(尤其是集合)创建流。列表和集合支持新方法,stream()
并parallelStream()
可以创建顺序流或并行流。并行流能够在多个线程上运行,并且将在本教程的后续部分中介绍。现在,我们关注顺序流:
1 | Arrays.asList("a1", "a2", "a3") |
stream()
在对象列表上调用该方法将返回常规对象流。但是我们不必创建集合即可使用流,如我们在下一个代码示例中看到的那样:
1 | Stream.of("a1", "a2", "a3") |
仅用于Stream.of()
从一堆对象引用创建流。
除了常规对象流之外,Java 8还附带了特殊的流,用于处理原始数据类型int
,long
以及double
。您可能已经猜到了IntStream
,LongStream
和DoubleStream
。
IntStreams可以使用以下方法替换常规的for循环IntStream.range()
:
1 | IntStream.range(1, 4) |
所有这些原始流都像常规对象流一样工作,但有以下区别:原始流使用专用的lambda表达式,例如IntFunction
代替Function
或IntPredicate
代替Predicate
。基本流支持其他终端聚合操作sum()
和average()
:
1 | Arrays.stream(new int[] {1, 2, 3}) |
有时将常规对象流转换为原始流很有用,反之亦然。为此,对象流支持特殊的映射操作mapToInt()
,mapToLong()
并且mapToDouble
:
1 | Stream.of("a1", "a2", "a3") |
原始流可以通过以下方式转换为对象流mapToObj():
1 | IntStream.range(1, 4) |
这是一个组合的示例:双精度流首先映射到int流,然后映射到字符串对象流:
1 | Stream.of(1.0, 2.0, 3.0) |
既然我们已经学习了如何创建和使用不同类型的流,那么让我们更深入地了解如何在后台处理流操作。
中间操作的一个重要特征是懒惰。查看以下缺少终端操作的示例:
1 | Stream.of("d2", "a2", "b1", "b3", "c") |
执行此代码段时,没有任何内容打印到控制台。这是因为仅当存在终端操作时才执行中间操作。
让我们通过终端操作扩展以上示例forEach:
1 | Stream.of("d2", "a2", "b1", "b3", "c") |
1 | filter: d2 |
结果的顺序可能令人惊讶。天真的方法是在流的所有元素上一个接一个地水平执行操作。但是,每个元素都沿链垂直移动。然后,第一个字符串“ d2”通过,filter
然后forEach
才处理第二个字符串“ a2”。
这种行为可以减少在每个元素上执行的实际操作数,如下例所示:
1 | Stream.of("d2", "a2", "b1", "b3", "c") |
谓词应用于给定输入元素后,该操作anyMatch
将true
立即返回。对于通过“ A2”的第二个元素,这是正确的。由于流链是垂直执行的,map
因此在这种情况下只需执行两次。因此,map
将尽可能少地调用而不是映射流的所有元素。
下一个示例包括两个中间操作map
和filter
和终端操作forEach
。让我们再次检查这些操作是如何执行的:
1 | Stream.of("d2", "a2", "b1", "b3", "c") |
您可能已经猜到了两者,map并且filter基础集合中的每个字符串都被调用了五次,而forEach仅被调用了一次。
如果更改操作顺序(移至filter链的开头),则可以大大减少实际的执行次数:
1 | Stream.of("d2", "a2", "b1", "b3", "c") |
现在,map
仅调用一次,因此操作管道对于大量输入元素的执行速度要快得多。组成复杂的方法链时,请记住这一点。
让我们通过一个额外的操作扩展上述示例sorted
:
1 | Stream.of("d2", "a2", "b1", "b3", "c") |
排序是一种特殊的中间操作。这是所谓的有状态操作,因为为了对元素集合进行排序,您必须在排序期间保持状态。
执行此示例将得到以下控制台输出:
1 | sort: a2; d2 |
首先,对整个输入集合执行排序操作。换句话说,sorted
是水平执行的。因此,在这种情况下sorted
,对输入集合中每个元素的多个组合调用了八次。
我们可以通过重新排序链来再次优化性能:
1 | Stream.of("d2", "a2", "b1", "b3", "c") |
在此示例sorted
中,因为filter
将输入集合简化为一个元素而从未被调用。因此,对于较大的输入集合,性能会大大提高。
Java 8流无法重用。调用任何终端操作后,流就立即关闭:
1 | Stream<String> stream = |
在同一流上调用noneMatchafte
r会anyMatch
导致以下异常:
1 | java.lang.IllegalStateException: stream has already been operated upon or closed |
为了克服此限制,我们必须为要执行的每个终端操作创建一个新的流链,例如,我们可以创建一个流提供程序以构造一个已经设置了所有中间操作的新流:
1 | Supplier<Stream<String>> streamSupplier = |
每次调用都会get()
构造一个新的流,我们可以保存该流以调用所需的终端操作。
流支持许多不同的操作。我们已经了解了最重要的操作,例如filter
或map
。我留给您发现所有其他可用的操作(请参阅Stream Javadoc)。相反,让我们更深入地了解了更复杂的操作collect
,flatMap
和reduce
。
本节中的大多数代码示例都使用以下人员进行演示:
1 | class Person { |
收集是到流中的元素转换为不同的种类的结果,例如一个非常有用的终端操作List
,Set
或Map
。收集接受Collector
由四个不同的操作组成的:供应商,累加器,合并器和装订器。乍一看,这听起来超级复杂,但是好地方是Java 8通过Collectors
该类支持各种内置的收集器。因此,对于最常见的操作,您不必自己实现收集器。
让我们从一个非常常见的用例开始:
1 | List<Person> filtered = |
如您所见,从流的元素构造列表非常简单。需要一个集合而不是列表-只需使用Collectors.toSet()
。
下一个示例按年龄对所有人进行分组:
1 | Map<Integer, List<Person>> personsByAge = persons |
收集器功能极为丰富。您还可以在信息流的元素上创建汇总,例如,确定所有人的平均年龄:
1 | Double averageAge = persons |
如果您对更全面的统计感兴趣,则汇总收集器将返回一个特殊的内置汇总统计对象。因此,我们可以简单地确定人员的最小,最大和算术平均年龄以及总数和计数。
1 | IntSummaryStatistics ageSummary = |
下一个示例将所有人连接成一个字符串:
1 | String phrase = persons |
联接收集器接受定界符以及可选的前缀和后缀。
为了将流元素转换为映射,我们必须指定如何映射键和值。请记住,映射的键必须唯一,否则将IllegalStateException
抛出。您可以选择将合并功能作为附加参数传递来绕过异常:
1 | Map<Integer, String> map = persons |
现在我们知道一些最强大的内置收集器,让我们尝试构建自己的特殊收集器。我们希望将流中的所有人转换为单个字符串,该字符串包含所有用|竖线字符分隔的大写字母名称。为了实现这一点,我们通过创建了一个新的收集器Collector.of()。我们必须通过收集器的四个要素:供应商,累加器,组合器和修整器。
1 | Collector<Person, StringJoiner, String> personNameCollector = |
由于Java中的字符串是不可变的,因此我们需要一个帮助器类,StringJoiner
以便让收集器构造我们的字符串。供应商最初使用适当的定界符构造此类StringJoiner。累加器用于将每个人的大写名称添加到StringJoiner。组合器知道如何将两个StringJoiners合并为一个。在最后一步,修整器从StringJoiner构造所需的String。
我们已经学习了如何通过使用map操作将流的对象转换为另一种对象。映射是有限的,因为每个对象只能精确地映射到另一个对象。但是,如果我们想将一个对象转换成多个其他对象,或者根本不转换呢?这就是flatMap
救援的地方。
FlatMap将流的每个元素转换为其他对象的流。因此,每个对象都将转换为零,一个或多个由流支持的其他对象。然后,将这些流的内容放入flatMap
操作的返回流中。
在看到flatMap
实际效果之前,我们需要一个适当的类型层次结构:
1 | class Foo { |
接下来,我们利用关于流的知识来实例化几个对象:
1 | List<Foo> foos = new ArrayList<>(); |
现在我们有了三个foo的列表,每个foo包含三个小节。
FlatMap接受一个必须返回对象流的函数。因此,为了解析每个foo的bar对象,我们只需传递适当的函数:
1 | foos.stream() |
如您所见,我们已经成功地将三个foo对象的流转换为九个bar对象的流。
最后,以上代码示例可以简化为单个流操作管道:
1 | IntStream.range(1, 4) |
FlatMap也可用于Optional
Java 8中引入的类。Optionals flatMap
操作返回另一种类型的可选对象。因此,它可以用来防止令人讨厌的null
检查。
考虑这样一个高度分层的结构:
1 | class Outer { |
为了解析foo
外部实例的内部字符串,您必须添加多个null检查以防止可能的NullPointerExceptions:
1 | Outer outer = new Outer(); |
通过使用可选flatMap
操作可以获得相同的行为:
1 | Optional.of(new Outer()) |
每次调用时,如果存在或不存在,则flatMap
返回一个Optional
包装所需对象的包装null。
归约运算将流的所有元素组合为单个结果。Java 8支持三种不同的reduce
方法。第一个将元素流简化为该流的一个元素。让我们看看如何使用此方法确定最大的人:
1 | persons |
该reduce
方法接受BinaryOperator
累加器功能。BiFunction
在这种情况下,实际上这是两个操作数共享相同类型的地方Person
。BiFunction
就像,Function
但是接受两个参数。示例函数比较两个人的年龄,以便返回最大年龄的人。
第二种reduce
方法接受身份值和BinaryOperator
累加器。可使用此方法来构造一个新人员,并使用流中所有其他人员的姓名和年龄进行汇总:
1 | Person result = |
第三种reduce
方法接受三个参数:标识值,BiFunction
累加器和type的组合器函数BinaryOperator
。由于身份值类型不限于该Person
类型,因此我们可以利用此归约法确定所有人的年龄总和:
1 | Integer ageSum = persons |
如您所见,结果是76,但是到底发生了什么?让我们通过一些调试输出扩展上面的代码:
1 | Integer ageSum = persons |
如您所见,累加器功能完成了所有工作。首先使用初始标识值0和第一人称Max进行调用。在接下来的三个步骤中sum
,根据最后一个步骤的年龄,人员不断增加,总年龄达到76岁。
等待扫管??组合器永远不会被调用?并行执行相同的流将揭秘:
1 | Integer ageSum = persons |
并行执行此流将导致完全不同的执行行为。现在实际上调用了合并器。由于累加器是并行调用的,因此需要组合器来汇总单独的累加值。
在下一章中,让我们更深入地研究并行流。
可以并行执行流,以提高大量输入元素上的运行时性能。并行流使用ForkJoinPool
可通过静态ForkJoinPool.commonPool()
方法获得的公共变量。基础线程池的大小最多使用五个线程-取决于可用物理CPU内核的数量:
1 | ForkJoinPool commonPool = ForkJoinPool.commonPool(); |
在我的机器上,默认情况下,公共池的并行度为3。可以通过设置以下JVM参数来减小或增大此值:
1 | -Djava.util.concurrent.ForkJoinPool.common.parallelism=5 |
集合支持parallelStream()
创建元素并行流的方法。或者,您可以parallel()
在给定流上调用中间方法,以将顺序流转换为并行对应流。
为了低估并行流的并行执行行为,下一个示例将有关当前线程的信息打印到sout
:
1 | Arrays.asList("a1", "a2", "b1", "c2", "c1") |
通过研究调试输出,我们应该更好地了解哪些线程实际用于执行流操作:
1 | filter: b1 [main] |
如您所见,并行流利用通用中所有可用线程ForkJoinPool
来执行流操作。在连续运行中,输出可能会有所不同,因为实际使用特定线程的行为是不确定的。
让我们通过附加的流操作扩展该示例sort
:
1 | Arrays.asList("a1", "a2", "b1", "c2", "c1") |
起初结果可能看起来很奇怪:
1 | filter: c2 [ForkJoinPool.commonPool-worker-3] |
似乎sort
只在主线程上顺序执行。实际上,sort
在并行流上,在后台使用了新的Java 8方法Arrays.parallelSort()
。如Javadoc中所述,此方法决定数组的长度是排序是顺序执行还是并行执行:
如果指定数组的长度小于最小粒度,则使用适当的Arrays.sort方法对其进行排序。
回到上reduce
一节的示例。我们已经发现,组合器函数仅在并行流中调用,而不在顺序流中调用。让我们看看实际涉及到哪些线程:
1 | List<Person> persons = Arrays.asList( |
控制台输出显示,累加器和合并器函数在所有可用线程上并行执行:
1 | accumulator: sum=0; person=Pamela; [main] |
总之,可以说并行流可以为具有大量输入元素的流带来不错的性能提升。但是,请记住,像一些并行流操作reduce
,并collect
需要额外的计算(组合操作)时,依次执行其中不需要。
此外,我们了解到所有并行流操作共享相同的JVM范围的common ForkJoinPool
。因此,您可能要避免实施缓慢的阻塞流操作,因为这有可能减慢应用程序中严重依赖并行流的其他部分的速度。
https://winterbe.com/posts/2014/07/31/java8-stream-tutorial-examples/#different-kind-of-streams 的翻译版
]]>函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
函数式接口可以被隐式转换为 lambda 表达式。
Lambda 表达式和方法引用(实际上也可认为是Lambda表达式)上。
如定义了一个函数式接口如下:
1 |
|
那么就可以使用Lambda表达式来表示该接口的一个实现(注:JAVA 8 之前一般是用匿名类实现的):
1 | GreetingService greetService1 = message -> System.out.println("Hello " + message); |
与@Override 注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。该注解可用于一个接口的定义上,一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。需要注意的是,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口,使用起来都一样。
(参数列表)->{代码}
Supplier 接口翻译过来就是提供者,目的是生产数据,该接口对应的方法类型为不接受参数,但是提供一个返回值,通俗的理解为这种接口是无私的奉献者,不仅不要参数,还返回一个值,使用get()方法获得这个返回值。
1 | public static void main(String[] args) { |
该接口对应的方法类型为接收一个参数,没有返回值,可以通俗的理解成将这个参数’消费掉了’,一般来说使用Consumer接口往往伴随着一些期望状态的改变或者事件的发生,例如最典型的forEach就是使用的Consumer接口,虽然没有任何的返回值,但是却向控制台输出了语句。
Consumer 使用accept对参数执行行为。
1 | accept(T t) |
T—函数的输入类型,R-函数的输出类型,也就是通过这个函数,可以将一个类型转换为另一个类型,比如下面的例子
1
2
3
4
5
6
7
8
9
10
11
12
13apply(T t)
public static void main(String[] args) {
Function<String, String> function = a -> a + " Jack!";
System.out.println(function.apply("Hello")); // Hello Jack!
}
andThen(Function<? super R,? extends V> after)
public static void main(String[] args) {
Function<String, String> function = a -> a + " Jack!";
Function<String, String> function1 = a -> a + " Bob!";
String greet = function.andThen(function1).apply("Hello");
System.out.println(greet); // Hello Jack! Bob!
}
predicate<T,Boolean> 断言接口,顾名思义,中文中的‘是’与‘不是’是中文语法的谓语,同样的该接口对应的方法为接收一个参数,返回一个Boolean类型值,多用于判断与过滤,当然你可以把他理解成特殊的Funcation<T,R>,但是为了便于区分语义,还是单独的划了一个接口,使用test()方法执行这段行为
1 |
|
Map是双列集合的顶层接口,Map接口下是一个键值对(key-value)的映射接口,Map集合中,不能包含重复的键,每个键只能映射一个值(元素),值可以重复,因此可以根据key快速查找value的值。
1、void clear()
从该地图中删除所有的映射(可选操作)。
2、boolean containsKey(Object key)
如果此映射包含指定键的映射,则返回 true 。
3、boolean containsValue(Object value)
如果此地图将一个或多个键映射到指定的值,则返回 true 。
4、Set<Map.Entry<K,V>> entrySet()
返回此地图中包含的映射的Set视图。
5、Set<K> keySet()
返回此地图中包含的键的Set视图。
6、V put(K key, V value)
将指定的值与该映射中的指定键相关联(可选操作)。
7、V remove(Object key)
如果存在(从可选的操作),从该地图中删除一个键的映射。
8、int size()
返回此地图中键值映射的数量。
基于哈希表的实现的Map接口。 此实现提供了所有可选的地图操作,并允许null的值和null键。 ( HashMap类大致相当于Hashtable ,除了它是不同步的,并允许null)。这个类不能保证地图的顺序; 特别是,它不能保证订单在一段时间内保持不变。
HashMap非线程安全,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致。如果需要满足线程安全,可以用 Collections的synchronizedMap方法使HashMap具有线程安的能力,或者使用ConcurrentHashMap。
HashMap在JDK1.8之前的实现方式 数组+链表,但是在JDK1.8后对HashMap进行了底层优化,改为了由 数组+链表+红黑树实现,主要的目的是提高查找效率。
LinkedHashMap继承于HashMap,哈希表和链表实现的Map接口,具有可预测的迭代次序。 这种实现不同于HashMap,它维持于所有条目的运行双向链表。 此链接列表定义迭代排序,通常是将键插入到地图(插入顺序 )中的顺序 。 请注意,如果将键重新插入到地图中,则插入顺序不受影响。 (A键k被重新插入到地图m如果当m.containsKey(k)将返回true之前立即调用m.put(k, v)被调用。)
该类实现了一个哈希表,它将键映射到值。 任何非null对象都可以用作键值或值。
为了从散列表成功存储和检索对象,用作键的对象必须实现hashCode方法和equals方法。
Hashtable是遗留类,很多映射的常用功能与HashMap类似,不同的是它承自Dictionary类,并且是线程安全的,任一时间只有一个线程能写Hashtable,并发性不如ConcurrentHashMap,因为ConcurrentHashMap引入了分段锁。Hashtable不建议在新代码中使用,不需要线程安全的场合可以用HashMap替换,需要线程安全的场合可以用ConcurrentHashMap替换。
一个红黑树基于NavigableMap实现。 该地图是根据排序natural ordering其密钥,或通过Comparator在地图创建时提供,这取决于所使用的构造方法。
TreeMap实现SortedMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator遍历TreeMap时,得到的记录是排过序的。如果使用排序的映射,建议使用TreeMap。在使用TreeMap时,key必须实现Comparable接口或者在构造TreeMap传入自定义的Comparator,否则会在运行时抛出java.lang.ClassCastException类型的异常。
集合类的特点:提供一种存储空间可变的存储模式存储的数据容量可以随时发生改变。
和数组的区别:数组是存储同种数据类型、长度在定义后便不可变。
集合分为单列集合(Collection)和双列集合(Map)
Collection是单列集合的顶层接口,它表示一组对象,这些对象也称为Collection的元素;JDK不提供此接口的任何直接实现,它提供更具体的子接口(如Set、List)实现。
添加元素boolean add()
、移除boolean remove()
、清空void clear()
、判断集合是否存在某个元素boolean contains()
、判断集合是否为空boolean sEmpty()
、获取集合的长度int size()
。
迭代器的介绍:是集合的专用遍历方式,通过集合的iterator iterator()方法得到。
迭代器的方法:hasNext()方法判断迭代中是否还有元素。如果有则调用next()方法返 下一个元素。值得一提的是增强for循环本质就是一个迭代器。
Collection集合按照不同的特点可分为两类子集合List和Set
List集合的特点:有序(元素的存储顺序一致)、有索引、可重复。
List集合的常用方法:void add()、remove()、set()、get()。都是基于索引进行的增删改查。
列表迭代器:List集合的特有遍历,通过listIterator()方法得到。主要用途是可以沿着任一方向进行遍历,逆向方法hasPrevious(),Previous()。值得注意的是列表迭代器在迭代期间修改列表,返回迭代器的当前位置,所以在进行逆向遍历前先进行正向遍历。
ArrayList:底层是数组,查询快,增删慢,线程不同步,效率高。
LinkedList:底层是链表,增删快,查询慢,线程不同步,效率高。特有方法first()可以获取(删除,得到)列表的一个元素,Last()同理。
Set集合的特点:无序,无索引(所以没有普通for循环),唯一(不可重复)
哈希值的概念:是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值。
如何获取哈希值:通过object类中的hashcode():返回对象的哈希值;
哈希值的特点:1、同一个对象调用hashCode()方法返回的哈希值是相同的;2、不同的对象可以通过重写hashCode()方法得到相同的哈希值。
HashSet:底层是哈希表,无序,无索引(不能使用普通for),hashcode保证唯一性
LinkedHashSet:底层是哈希表和链表,有序,无索引,唯一。
TreeSet:底层是二叉树,有序(指按照一定规则排序),无索引,唯一。
自然排序:实现compareable接口,复写compareTo方法,当主要条件相等时要比较次要元素,保证唯一性的语句是compareTo方法return0
比较器排序:当元素不具备比较性,或者具备的比较性不符合需求,那么就让集合自身具备比较性,定义一个类实现Compare接口,重写compare()方法。
]]>