commit dc01f929d3ab8f8feff2bc3ab2e074415635f72b Author: 罗祥 <1366971433@qq.com> Date: Mon Jun 10 16:28:43 2019 +0800 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4745931 --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +*# +*.iml +*.ipr +*.iws +*.sw? +*~ +.#* +.*.md.html +.DS_Store +.classpath +.factorypath +.gradle +.idea +.metadata +.project +.recommenders +.settings +.springBeans +/build +MANIFEST.MF +_site/ +activemq-data +bin +build +build.log +dependency-reduced-pom.xml +dump.rdb +interpolated*.xml +lib/ +manifest.yml +overridedb.* +settings.xml +target +classes +out +logs +transaction-logs +.flattened-pom.xml +secrets.yml +.gradletasknamecache +.sts4-cache \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..79a256a --- /dev/null +++ b/README.md @@ -0,0 +1,163 @@ +# :memo:全栈工程师笔记 + + + +| ☕️ | 💻 | 💾 | 📟 | :globe_with_meridians: | 🌳 | 🚀 | 📊 | :cd: | :books: | +| :----: | :----: | :----: | :----: | :----: | :----: | :----: | :----: | :----: | :----: | +| Java | 前端 | 数据库 | 操作系统 | 网络通信 | Spring | 分布式 | 算法和数据结构 | 大数据 | 读书笔记 | + + + +# :coffee: Java + +1. Java设计模式 + +2. Java数据结构 + +3. 深入理解Java虚拟机 + +4. 并发编程框架disruptor + +5. Java网络编程 +6. tomcat 调优 + + + +## 💻 前端 + +TODO + + + +## 💾 数据库 + +#### 1. Oracle + + + +#### 2. MySQL + ++ MySQL 主从复制及读写分离 ++ MySQL+keepalived 高可用实践方案 ++ MySQL 分库分表 ++ 数据库中间件 Mycat + +#### 3. Redis + ++ redis 简介及基本数据结构 ++ redis 管道模式详解 ++ redis AOF 和 RDB 持久化策略原理 ++ redis 哨兵模式 ++ reids 集群模式 ++ 缓存击穿、缓存雪崩的解决方案 ++ redis 管理客户端cachecloud + +#### 4.MongoDB + ++ MongoDB 简介及基本原理 ++ MongoDB数据类型分析 ++ MongoDB 聚合、索引及基本执行命令 ++ MongoDB数据分片、转存及恢复策略 + + + +## 📟 操作系统 + +linux 核心概念、常用命令 + + + +## 🌐 网络通信 + +1. IO 基本概念、NIO、AIO、BIO 深入分析 +2. 高性能NIO框架Netty + + + +## 🌳 Spring + +#### 1.spring 基础 + ++ AOP ++ IOC ++ Spring事务机制、事务的传播与监控 ++ ...... + +#### 2.spring Boot + ++ spring 自动装配原理 ++ 理解SpringApplication ++ Web MVC REST ++ WebFlux 核心 ++ ...... + + + +## 🚀 分布式 + +#### 1. Zookeeper + ++ Zookeeper 简介及原理介绍 + ++ Zookeeper 集群搭建 ++ Zookeeper 分布式锁实现方案 ++ Zookeeper 集群升级、迁移 ++ 深入分析 Zookeeper Zab协议及选举机制 + +#### 2. Dubbo + ++ Dubbo 管理中心及监控平台安装部署 ++ Dubbo 负载均衡和服务降级 + +#### 3. Spring Cloud + +- Eureka 服务的注册和发现 +- Eureka 高可用集群搭建 +- Ribbon 客户端负载均衡 RestTemplate 服务远程调用 +- OpenFeign 声明式服务调用、服务容错处理 +- Hystix 服务容错保护、hystrix dashboard 断路器监控、Turbine 断路器聚合监控 +- Zuul 网关服务 +- Sleuth + Zipkin 服务链路追踪 +- Config 分布式配置中心 、集成Bus消息总线实现配置热更新 + +#### 4. 消息中间件:Kafka + ++ Kafka 简介及消息处理过程分析 + ++ 基于Zookeeper搭建Kafka高可用集群 ++ Kafka 副本机制以及选举原理剖析 + +#### 5. 消息中间件:RabbitMQ + ++ RabbitMQ 简介及消息处理过程分析 ++ RabbitMQ 消息确认机制 ++ RabbitMQ 如何保证消息的可靠性投递和防止重复消费 + +#### 6. Nginx + ++ Nginx反向代理及负载均衡服务配置实战 ++ 利用keeplived+Nginx实现Nginx高可用方案 ++ Nginx动静分离实战 + +#### 7. Docker + ++ Docker 简介及基本概念 ++ Docker常用命令 ++ kubernetes 简介及集群搭建 + +#### 8.分布式解决方案 + ++ 全局id生成方案 ++ 分布式session解决方案 ++ 分布式事务解决方案实战 ++ 分布式锁解决方案 + + + +## 📊 算法和数据结构 + +#### 1. 数据结构 + +数组、栈、队列、链表、二分搜索树、集合、映射、优先队列、堆、线段树、Trie、并查集、AVL、红黑树、哈希表 + +#### 2. 算法 diff --git a/notes/Java单例设计模式详解.md b/notes/Java单例设计模式详解.md new file mode 100644 index 0000000..ac6e770 --- /dev/null +++ b/notes/Java单例设计模式详解.md @@ -0,0 +1,193 @@ +# Java 单例设计模式详解 + +参考自慕课网课程:[Java设计模式精讲](https://coding.imooc.com/class/270.html):star::star::star::star::star: + +#### 1、单例模式的饿汉模式(线程安全) + +缺点:在类初始化时候就会初始化对应的单例类,如果单例类没有被用到且单例类初始化复杂,就不应用这种模式 + +```java +public class HungrySingleton { + + private static final HungrySingleton hungrySingleton; + + private HungrySingleton(){ + // 如果不加上这个判断,就会被反射攻击 + if (hungrySingleton!=null){ + throw new RuntimeException("单例模式禁止反射调用!"); + } + } + + static { + hungrySingleton=new HungrySingleton(); + } + + public static HungrySingleton getInstance(){ + return hungrySingleton; + } +} +``` + +```java +// 反射攻击 +public class ReflectAttack { + + public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { + // 反射攻击 + Class hungrySingletonClass = HungrySingleton.class; + Constructor constructor = hungrySingletonClass.getDeclaredConstructor(); + // 获取可操作权限 + constructor.setAccessible(true); + HungrySingleton hungrySingleton = constructor.newInstance(); + HungrySingleton instance = HungrySingleton.getInstance(); + System.out.println(hungrySingleton==instance); + } +} +``` + + + +#### 2.单例模式的懒汉模式(线程不安全) + +```java +public class LazySingleton { + + private static LazySingleton lazySingleton=null; + + private LazySingleton(){ + if (lazySingleton!=null){ + throw new RuntimeException("单例模式禁止反射调用!"); + } + } + + // 在多线程下是不安全的 + public static LazySingleton getInstance(){ + if (lazySingleton!=null){ + lazySingleton=new LazySingleton(); + } + return lazySingleton; + } +} +``` + + + +#### 3.单例模式的懒汉模式(采用双重检查锁,线程安全) + +```java +public class LazySingleton { + + // 必须要声明为 volatile 防止指令重排序 + private static volatile LazySingleton lazySingleton = null; + + private LazySingleton() { + if (lazySingleton != null) { + throw new RuntimeException("单例模式禁止反射调用!"); + } + } + + public static LazySingleton getInstance() { + if (lazySingleton == null) { + synchronized (LazySingleton.class) { + if (lazySingleton == null) { + /* + new对象过程: + 1.分配内存给这个对象 + 2.初始化对象 + 3.设置lazyDoubleCheckSingleton 指向刚分配的内存地址 + 上述三步会发生指令重排序 + */ + lazySingleton = new LazySingleton(); + } + } + } + return lazySingleton; + } +} +``` + + + +#### 4.使用枚举实现单例(推荐 JVM保证下的单例) + +**能够防止反射攻击和序列化对单例的破坏** + +```java +// 枚举单例模式 +public enum EnumInstance { + + INSTANCE; + + public static EnumInstance getInstance() { + return INSTANCE; + } +} +``` + +```java +// 测试 +public class Test { + + public static void main(String[] args) { + EnumInstance instance0 = EnumInstance.INSTANCE; + EnumInstance instance1 = EnumInstance.getInstance(); + EnumInstance instance2 = EnumInstance.getInstance(); + System.out.println(instance0 == instance1); // true + System.out.println(instance0 == instance2); // true + System.out.println(instance1 == instance2); // true + } +} + +``` + +```java +public enum EnumInstance { + + INSTANCE; + + private String data; + + // 可以增加对应的方法 + public void setData(String data) { + this.data = data; + } + + public String getData() { + return data; + } + + public static EnumInstance getInstance() { + return INSTANCE; + } +} +``` + + + +#### 5.容器式单例(不常用) + +```java +public class ContainerSingleton { + + private ContainerSingleton(){ + } + + private static Map singletonMap = new HashMap(); + + public static void putInstance(String key,Object instance){ + if(StringUtils.isNotBlank(key) && instance != null){ + if(!singletonMap.containsKey(key)){ + singletonMap.put(key,instance); + } + } + } + + public static Object getInstance(String key){ + return singletonMap.get(key); + } + + +} + +``` + diff --git a/notes/Oracle和MySQL异同对比.md b/notes/Oracle和MySQL异同对比.md new file mode 100644 index 0000000..106acc0 --- /dev/null +++ b/notes/Oracle和MySQL异同对比.md @@ -0,0 +1,1507 @@ +# oracle和mysql 知识点总结和异同对比 + +## 目录
+一、数据库管理
+    1.1 用户管理
+        1.1.1 mysql用户、权限管理
+        1.1.2 oracle 用户、角色、权限管理
+二、DQL 语句
+    2.1 基础查询
+        1.常量查询的区别:
+        2.字符串拼接
+        3.判断字段是否为空
+        4.查询非空字段
+    2.2 常见函数
+        1.字符函数
+        2.数学函数
+        3.日期函数
+        1. oracle 日期函数:
+            1.1 oracel常用的时间格式掩码
+            1.2 Oracle 获取当前年、月、日
+            1.3 计算两个时间差
+            1.4 日期和字符串转换
+            1.5 日期的加减计算
+        2.mysql日期函数
+            2.1 mysql 常用日期格式掩码
+            2.2 Oracle 获取当前年、月、日
+            2.3 计算两个时间之差
+            2.4 日期和字符串转换
+            2.5 日期的加减计算
+        4、流程控制函数
+            4.1 oracle decode函数
+            4.2 mysql流程控制函数
+        5.分页查询
+三、DML语句
+    1. 插入语句
+            1.1 mysql 多行插入
+            1.2 oracle 多行插入
+        2.多表更新
+            2.1 mysql 多表更新
+            2.2 oracle 多表更新
+        3. 级联删除
+            3.1 mysql 级联删除
+            3.2 oracle 级联删除
+四、DDL语句
+    4.1 数据库操作
+        4.1 mysql数据库的创建、修改和删除
+        4.2 oracle 数据库的创建和删除
+    4.2 表的管理
+        4.2.1 创建表
+            1. oracle 字段类型
+            2. mysql 字段类型
+        4.2.2 修改表
+        4.2.3 删除表
+        4.2.4 复制表
+    4.3 约束
+        4.3.1 常见的约束
+        4.3.2 创建表时添加约束
+        4.3.3 修改表时添加或删除约束
+        4.3.4 自增长列(mysql)
+        4.3.5 序列(oracle)
+        4.3.6 级联操作
+五、TCL语言
+    5.1 事务
+        5.1.1 事务隔离级别
+六、其他扩展
+    6.1 视图
+    6.2 触发器
+    6.3 存储过程
+        6.3.1 创建存储过程
+        6.3.2变量
+        6.3.3 存储过程的查询
+        6.3.4 存储过程的修改
+        6.3.5 存储过程的删除
+        6.3.6 条件语句
+            1.分支结构
+            2.循环结构
+    6.4 游标
+        6.4.1 游标简介
+        6.4.2 游标的特性
+        6.4.3 游标的操作
+ +## 正文
+ + +## 一、数据库管理 + +### 1.1 用户管理 + +#### 1.1.1 mysql用户、权限管理 + +**1.创建、修改、删除用户** + +```mysql +-- 创建用户 +CREATE USER '用户名' IDENTIFIED BY '密码'; + +-- 重命名 +RENAME USER '用户名' TO '新用户名'; + +-- 修改用户口令 + -- 自己的口令 + SET PASSWORD=Password('新密码') + -- 其他用户的口令 + SET PASSWORD FOR 用户名 = Password('新密码') +-- 删除用户 +DROP USER '用户名'; +``` + +**2.用户权限管理** + +```mysql +-- 查看用户权限 +SHOW GRANTS FOR '用户名'; + +-- 赋予权限 +GRANT privileges ON [权限范围] TO '用户名'; + +-- 撤回权限 +REVOKE privileges ON [权限范围] TO '用户名'; +``` + +**注:修改完权限以后 一定要刷新服务,或者重启服务,刷新服务用:FLUSH PRIVILEGES** + + + +**3.权限级别范围** + +MySQL grant 权限,分别可以作用在多个层次上。 +```mysql +-- 1. grant 作用在整个 MySQL 服务器上: +grant select on . to dba@localhost; -- dba 可以查询 MySQL 中所有数据库中的表。 +grant all on . to dba@localhost; -- dba 可以管理 MySQL 中的所有数据库 +-- 2. grant 作用在单个数据库上: +grant select on testdb.* to dba@localhost; -- dba 可以查询 testdb 中的表。 +-- 3. grant 作用在单个数据表上: +grant select, insert, update, delete on testdb.orders to dba@localhost; +-- 4. grant 作用在表中的列上: +grant select(id, se, rank) on testdb.apache_log to dba@localhost; +-- 5. grant 作用在存储过程、函数上: +grant execute on procedure testdb.pr_add to ’dba’@’localhost’ +grant execute on function testdb.fn_add to ’dba’@’localhost’ + +``` + +**4.可以赋予的权限(privileges)列表:** + +- ALTER: 修改表和索引。 +- CREATE: 创建数据库和表。 +- DELETE: 删除表中已有的记录。 +- DROP: 抛弃(删除)数据库和表。 +- INDEX: 创建或抛弃索引。 +- INSERT: 向表中插入新行。 +- REFERENCE: 未用。 +- SELECT: 检索表中的记录。 +- UPDATE: 修改现存表记录。 +- FILE: 读或写服务器上的文件。 +- PROCESS: 查看服务器中执行的线程信息或杀死线程。 +- RELOAD: 重载授权表或清空日志、主机缓存或表缓存。 +- SHUTDOWN: 关闭服务器。 +- **ALL: 所有权限,ALL PRIVILEGES同义词**。 +- USAGE: 特殊的 "无权限" 权限。 + + + +#### 1.1.2 oracle 用户、角色、权限管理 + +**1、创建用户** + +oracle内部有两个建好的用户:system和sys。用户可直接登录到system用户以创建其他用户,因为system具有创建别 的用户的 权限。 在安装oracle时,用户或系统管理员首先可以为自己建立一个用户。 + +```mysql +语法[创建用户]: create user 用户名 identified by 口令[即密码]; +例子: create user test identified by test; + +语法[更改用户]: alter user 用户名 identified by 口令[改变的口令]; +例子: alter user test identified by 123456; +``` + +**2、删除用户** + +```mysql +语法:drop user 用户名; +例子:drop user test; +``` + +若用户拥有对象,则不能直接删除,否则将返回一个错误值。指定关键字cascade,可删除用户所有的对象,然后再删除用户。 + +```sql +语法: drop user 用户名 cascade; +例子: drop user test cascade; +``` + +**3、授权角色** + +oracle为兼容以前版本,提供三种标准角色(role)connect/resource和dba. + +(1)connect role(连接角色) + +- 临时用户,特指不需要建表的用户,通常只赋予他们connect role. +- connect是使用oracle简单权限,这种权限只对其他用户的表有访问权限,包括select/insert/update和delete等。 +- 拥有connect role 的用户还能够创建表、视图、序列(sequence)、簇(cluster)、同义词(synonym)、回话(session)和其他 数据的链(link) + +(2) resource role(资源角色) + +- 更可靠和正式的数据库用户可以授予resource role。 +- resource提供给用户另外的权限以创建他们自己的表、序列、过程(procedure)、触发器(trigger)、索引(index)和簇(cluster)。 + +(3) dba role(数据库管理员角色) + +- dba role拥有所有的系统权限 +- 包括无限制的空间限额和给其他用户授予各种权限的能力。system由dba用户拥有 + +```sql +-- 授权命令 +语法: grant connect, resource to 用户名; +例子: grant connect, resource to test; + +-- 撤销权限 +语法: revoke connect, resource from 用户名; +列子: revoke connect, resource from test; +``` + + + +**4. 创建/授权/删除角色** + +除了前面讲到的三种系统角色----connect、resource和dba,用户还可以在oracle创建自己的role。用户创建的role可以由表或系统权限或两者的组合构成。为了创建role,用户必须具有create role系统权限。 + +(1)创建角色 + +```mysql +语法: create role 角色名; +例子: create role testRole; +``` + +(2)授权角色 + +```mysql +语法: grant select on class to 角色名; +列子: grant select on class to testRole; +``` + +注:现在,拥有testRole角色的所有用户都具有对class表的select查询权限 + +(3)删除角色 + +```mysql +语法: drop role 角色名; +例子: drop role testRole; +``` + +注:与testRole角色相关的权限将从数据库全部删除 + + + + +## 二、DQL 语句 + +### 2.1 基础查询 + +#### 1.常量查询的区别: + +```sql +-- mysql +select 常量值; + +--oracel +select 常量值 from dual; +``` + +#### 2.字符串拼接 + +```sql +-- mysql oracle +select concat(字符1,字符2,字符3,...); +``` + +#### 3.判断字段是否为空 + +功能:判断某字段或表达式是否为null,如果为null 返回指定的值,否则返回原本的值 + +```sql +-- mysql +select ifnull(字段名,默认值) from 表名; + +--oracle +select nvl(字段名,默认值) from 表名; +``` + +#### 4.查询非空字段 + +```sql +-- mysql oracle +select 字段名 from 表名 where 字段名 IS NOT NULL; +``` + + + +### 2.2 常见函数 + +#### 1.字符函数 + +```sql +-- mysql oracle +concat:连接 +substr:截取子串 +upper:变大写 +lower:变小写 +replace:替换 +length:获取字节长度 +trim:去前后空格 +lpad:左填充 +rpad:右填充 +instr:获取子串第一次出现的索引 +``` + +#### 2.数学函数 + +```mysql +-- mysql -oracle +ceil:向上取整 +round:四舍五入 +mod:取模 +floor:向下取整 + +-- mysql +truncate:截断 +rand:获取随机数,返回0-1之间的小数 +-- oracle +trunc:截断 + SELECT DBMS_RANDOM.value(0,1) FROM DUAL; 获取随机数,返回0-1之间的小数 +``` + +#### 3.日期函数 + +#### 1. oracle 日期函数: + +##### 1.1 Oracle 常用的时间格式掩码 + +| 掩码元素 | 含义 | +| -------- | ---------------------------------------------------- | +| YYYY | 四位数年份 (如:2005) year | +| YY | 二位数年份(如 05) | +| Q | 季度(1-4) | +| MM | 月份(01-12) month | +| WW | 年的星期数(1-53),其中第一星期为年的第一天至第七天 | +| W | 月的星期数(1-5),其中第一星期为月的第一天至第七天 | +| DDD | 年的日(1-366) | +| DD | 月的日(1-31) | +| D | 周的日(1-7),其中周日为1,周六为7 day | +| HH24 | 24小时制(0-23) hour | +| MI | 分钟(0-59) minute | +| SS | 秒(0-59) second | +| SSSSS | 自午夜之后的秒(0-86399) | + + + +##### 1.2 Oracle 获取当前年、月、日 + +EXTRACT ({ YEAR | MONTH | DAY | HOUR | MINUTE | SECOND } | { TIMEZONE_HOUR | TIMEZONE_MINUTE } | { TIMEZONE_REGION | TIMEZONE_ABBR } FROM { date_value | interval_value } ) + +```sql +select extract(year from sysdate) from dual; --当前年 + +select extract(month from sysdate) from dual; --本年到当月的月数 + +select extract(day from sysdate) from dual; --本月到当日的天数 + +``` + + + +##### 1.3 计算两个时间差 + +```sql +--天: +ROUND(TO_NUMBER(END_DATE - START_DATE)) +--小时: +ROUND(TO_NUMBER(END_DATE - START_DATE) * 24) +--分钟: +ROUND(TO_NUMBER(END_DATE - START_DATE) * 24 * 60) +--秒: +ROUND(TO_NUMBER(END_DATE - START_DATE) * 24 * 60 * 60) +--毫秒: +ROUND(TO_NUMBER(END_DATE - START_DATE) * 24 * 60 * 60 * 60) +``` + + + +##### 1.4 日期和字符串转换 + +```sql +-- 日期到字符串 +select sysdate,to_char(sysdate,'yyyy-mm-dd hh24:mi:ss') from dual +select sysdate,to_char(sysdate,'yyyy-mm-dd hh:mi:ss') from dual + +-- 字符串到日期 +select to_date('2018-10-10 10:13:56','yyyy-mm-dd hh24:mi:ss') from dual + +``` + + + +##### 1.5 日期的加减计算 + +```sql +--当前时间减去7分钟的时间 +select sysdate,sysdate - interval '7' MINUTE from dual; + +--当前时间减去7小时的时间 +select sysdate - interval '7' hour from dual; + +--当前时间减去7天的时间 +select sysdate - interval '7' day from dual; + +--当前时间减去7月的时间 +select sysdate,sysdate - interval '7' month from dual; + +--当前时间减去7年的时间 +select sysdate,sysdate - interval '7' year from dual; + +--时间间隔乘以一个数字 +select sysdate,sysdate - 8*interval '7' hour from dual; +``` + + + +#### 2.mysql日期函数 + +##### 2.1 mysql 常用日期格式掩码 + +| | 值 | 含义 | +| -------- | ----------------------------------------------------- | ------------------------------------------------- | +| 秒 | %S、%s | 两位数字形式的秒( 00,01, ..., 59) | +| 分 | %I、%i | 两位数字形式的分( 00,01, ..., 59) | +| 小时 | %H | 24小时制,两位数形式小时(00,01, ...,23) | +| %h | 12小时制,两位数形式小时(00,01, ...,12) | | +| %k | 24小时制,数形式小时(0,1, ...,23) | | +| %l | 12小时制,数形式小时(0,1, ...,12) | | +| %T | 24小时制,时间形式(HH:mm:ss) | | +| %r | 12小时制,时间形式(hh:mm:ss AM 或 PM) | | +| %p | AM上午或PM下午 | | +| 周 | %W | 一周中每一天的名称(Sunday,Monday, ...,Saturday) | +| %a | 一周中每一天名称的缩写(Sun,Mon, ...,Sat) | | +| %w | 以数字形式标识周(0=Sunday,1=Monday, ...,6=Saturday) | | +| %U | 数字表示周数,星期天为周中第一天 | | +| %u | 数字表示周数,星期一为周中第一天 | | +| 天 | %d | 两位数字表示月中天数(01,02, ...,31) | +| %e | 数字表示月中天数(1,2, ...,31) | | +| %D | 英文后缀表示月中天数(1st,2nd,3rd ...) | | +| %j | 以三位数字表示年中天数(001,002, ...,366) | | +| 月 | %M | 英文月名(January,February, ...,December) | +| %b | 英文缩写月名(Jan,Feb, ...,Dec) | | +| %m | 两位数字表示月份(01,02, ...,12) | | +| %c | 数字表示月份(1,2, ...,12) | | +| 年 | %Y | 四位数字表示的年份(2015,2016...) | +| %y | 两位数字表示的年份(15,16...) | | +| 文字输出 | %文字 | 直接输出文字内容 | + + + +##### 2.2 mysql 获取当前年、月、日 + +- now:返回当前日期+时间 +- year:返回年 +- month:返回月 +- day:返回日 +- curdate:返回当前日期 +- curtime:返回当前时间 +- hour:小时 +- minute:分钟 +- second:秒 + + + +##### 2.3 计算两个时间之差 + +**2.3.1、利用TO_DAYS函数** + +```mysql +select to_days(now()) - to_days('20120512') +``` + +**2.3.2、利用DATEDIFF函数** + +```mysql +select datediff(now(),'20120512') +``` + +**2.3. 3、利用TIMESTAMPDIFF函数** + +计算两日期时间之间相差的天数,秒数,分钟数,周数,小时数,这里主要分享的是通过MySql内置的函数 TimeStampDiff() 实现。函数 TimeStampDiff() 是MySQL本身提供的可以计算两个时间间隔的函数,语法为: + +```mysql +TIMESTAMPDIFF(unit,datetime_expr1,datetime_expr2) +``` + +其中unit单位有如下几种: + +- FRAC_SECOND 表示间隔是毫秒 +- SECOND 秒 +- MINUTE 分钟 +- HOUR 小时 +- DAY 天 +- WEEK 星期 +- MONTH 月 +- QUARTER 季度 +- YEAR 年 + +```sql +--计算两日期之间相差多少周 +select timestampdiff(week,'2011-09-30','2015-05-04'); +--计算两日期之间相差多少天 +select timestampdiff(day,'2011-09-30','2015-05-04'); +``` + + + +##### 2.4 日期和字符串转换 + +```sql +-- 字符串转日期 +select str_to_date('2018-12-15 16:44:41','%Y-%m-%d %H:%i:%s'); +-- 日期转字符串 +select date_format(now(),'%Y-%m-%d %H:%i:%s'); +``` + + + +##### 2.5 日期的加减计算 + +MySQL 为日期增加一个时间间隔:date_add() + + MySQL 为日期减去一个时间间隔:date_sub() + +```sql +select date_add(now(), interval 1 day); -- 加1天 +select date_add(now(), interval 1 hour); --加1小时 +select date_add(now(), interval 1 minute); -- 加1分钟 +select date_add(now(), interval 1 second); --加1秒 +select date_add(now(), interval 1 microsecond);--加1毫秒 +select date_add(now(), interval 1 week);--加1周 +select date_add(now(), interval 1 month);--加1月 +select date_add(now(), interval 1 quarter);--加1季 +select date_add(now(), interval 1 year);--加1年 +``` + + + +#### 4、流程控制函数 + +##### 4.1 oracle decode函数 + +**decode(条件,值1,返回值1,值2,返回值2,...值n,返回值n,缺省值)** + +该函数的含义如下: + +```mysql +IF 条件=值1 THEN +    RETURN(翻译值1) +ELSIF 条件=值2 THEN +    RETURN(翻译值2) +    ...... +ELSIF 条件=值n THEN +    RETURN(翻译值n) +ELSE +    RETURN(缺省值) +END IF +``` + +**decode(字段或字段的运算,值1,值2,值3)** + +这个函数运行的结果是,当字段或字段的运算的值等于值1时,该函数返回值2,否则返回值3,值1,值2,值3也可以是表达式 + +```sql +-- 比较大小,取较小值 +select decode(sign(变量1-变量2),-1,变量1,变量2) from dual; +-- sign()函数根据某个值是0、正数还是负数,分别返回0、1、-1 +``` + + + +##### 4.2 mysql流程控制函数 + +1.if(条件表达式,表达式1,表达式2):如果条件表达式成立,返回表达式1,否则返回表达式2 + +2.case情况一 + +```sql +case 变量或表达式或字段 +when 常量1 then 值1 +when 常量2 then 值2 +... +else 值n +end +``` + +```sql +--运用 +select case 字段名 +when 条件值1 then 值1 +when 条件值2 then 值1 +else 其他值 +end from 表名 +``` + +3.case情况二 + +``` +case +when 条件1 then 值1 +when 条件2 then 值2 +... +else 值n +end +``` + +#### 5.分页查询 + +```sql +-- mysql +select 查询列 from 表 limit [offset , ] size; +-- offset代表的是起始的条目索引,默认从0开始 + +--oracle +SELECT * FROM +( +SELECT A.*, ROWNUM RN +FROM (SELECT * FROM TABLE_NAME) A +WHERE ROWNUM <= 40 +) +WHERE RN >= 21 + +``` + +## 三、DML语句 + +### 1. 插入语句 + +##### 1.1 mysql 多行插入 + +```mysql +-- 方式一 +insert into 表名【(字段名,..)】 values(值,..),(值,...),...; + +-- 方式二 +insert into 表名(字段名,...) values(值,...); +insert into 表名(字段名,...) values(值,...); +insert into 表名(字段名,...) values(值,...); +... +``` + +##### 1.2 oracle 多行插入 + +```mysql +INSERT ALL INTO 表 VALUES(各个值) INTO 表 VALUES (其它值) INTO 表 VALUES(其它值) .... +``` + + + +#### 2.多表更新 + +##### 2.1 mysql 多表更新 + +```mysql +update 表1 别名 +left|right|inner join 表2 别名 +on 连接条件 +set 字段=值,字段=值 +【where 筛选条件】; +``` + +##### 2.2 oracle 多表更新 + +不支持类似mysql 的语法 + + + +#### 3. 级联删除 + +##### 3.1 mysql 级联删除 + +```mysql +delete 别名1,别名2 from 表1 别名 +inner|left|right join 表2 别名 +on 连接条件 +【where 筛选条件】 +``` + +##### 3.2 oracle 级联删除 + +不支持上面的语法,可以通过以下方式实现 + +- 创建约束时设定级联删除 +- 使用触发器(创建时没有级联删除) + + + +## 四、DDL语句 + +### 4.1 数据库操作 + +#### 4.1 mysql数据库的创建、修改和删除 + +1、创建库 + +```sql +create database 【if not exists】 库名【 character set 字符集名】; +``` + +2、修改库 + +```sql +alter database 库名 character set 字符集名; +``` + +3、删除库 + +```sql +drop database 【if exists】 库名; +``` + + + +#### 4.2 oracle 数据库的创建和删除 + +**1.oracle创建数据库:** + +1. 创建两个数据库的文件 +2. 创建用户,与上面创建的文件形成映射关系 +3. 给用户添加权限 + +(1)、创建两个数据库的文件(databaseName.dbf 和databaseName_temp.dbf 两个文件) + +```shell +CREATE TABLESPACE monitor LOGGING DATAFILE 'E:\app\owner\oradata\orcl\databaseName.dbf' +SIZE 100M AUTOEXTEND ON NEXT 32M MAXSIZE 500M EXTENT MANAGEMENT LOCAL; + +create temporary tablespace monitor_temp tempfile 'E:\app\owner\oradata\orcl\databaseName_temp.dbf' +size 100m autoextend on next 32m maxsize 500m extent management local; +``` + +(2)、创建用户,与上面创建的文件形成映射关系 + +```sql +CREATE USER youUsername IDENTIFIED BY youPassword DEFAULT databaseName monitor TEMPORARY TABLESPACE databaseName_temp; +``` + +(3)、添加权限 + +```sql +grant connect,resource,dba to databaseName; +grant create session to databaseName; +``` + +**2、删除数据库** + +```sql +DROP TABLESPACE databaseName INCLUDING CONTENTS AND DATAFILES; +``` + +**3、删除用户** + +```sql +drop user youUsername cascade; +``` + +**4. 关于权限的说明** + +**CONNECT角色:** 是授予最终用户的典型权利,最基本的权力,能够连接到ORACLE数据库中,并在对其他用户的表有访问权限时,做SELECT、UPDATE、INSERT等操作。 + +- ALTER SESSION --修改会话 +- CREATE CLUSTER --建立聚簇 +- CREATE DATABASE LINK --建立数据库链接 +- CREATE SEQUENCE --建立序列 +- CREATE SESSION --建立会话 +- CREATE SYNONYM --建立同义词 +- CREATE VIEW --建立视图 + +**RESOURCE角色:** 是授予开发人员的,能在自己的方案中创建表、序列、视图等。 + +- CREATE CLUSTER --建立聚簇 +- CREATE PROCEDURE --建立过程 +- CREATE SEQUENCE --建立序列 +- CREATE TABLE --建表 +- CREATE TRIGGER --建立触发器 +- CREATE TYPE --建立类型 + +**DBA角色**,是授予系统管理员的,拥有所有的系统权限 + + + +### 4.2 表的管理 + +#### 4.2.1 创建表 + +```sql +-- mysql +create table 【if not exists】 表名( + 字段名 字段类型 【约束】, + 字段名 字段类型 【约束】, + ... + 字段名 字段类型 【约束】 + +) + +-- oracle +CREATE TABLE [schema.]表名( + 字段名 字段类型 【约束】 + 字段名 字段类型 【约束】, + ... + 字段名 字段类型 【约束】 +); + +``` + +##### 1. oracle 字段类型 + +| 数据类型 | 描述 | +| -------------- | -------------------------------------- | +| VARCHAR2(size) | 可变长字符数据 | +| CHAR(size) | 定长字符数据 | +| NUMBER(p,s) | 可变长数值数据,P为整数位,S为小数位 | +| DATE | 日期型数据 | +| LONG | 可变长字符数据,最大可达到2G | +| CLOB | 字符数据,最大可达到4G | +| ROWID | 行地址 | +| RAW (LONG RAW) | 原始的二进制数据 | +| BLOB | 二进制数据,最大可达到4G | +| BFILE | 存储外部文件的二进制数据,最大可达到4G | + +##### 2. mysql 字段类型 + +(1)数值类型: + +| 类型 | 大小 | 范围(有符号) | 范围(无符号) | 用途 | +| ------------ | ---------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | --------------- | +| TINYINT | 1 字节 | (-128,127) | (0,255) | 小整数值 | +| SMALLINT | 2 字节 | (-32 768,32 767) | (0,65 535) | 大整数值 | +| MEDIUMINT | 3 字节 | (-8 388 608,8 388 607) | (0,16 777 215) | 大整数值 | +| INT或INTEGER | 4 字节 | (-2 147 483 648,2 147 483 647) | (0,4 294 967 295) | 大整数值 | +| BIGINT | 8 字节 | (-9 233 372 036 854 775 808,9 223 372 036 854 775 807) | (0,18 446 744 073 709 551 615) | 极大整数值 | +| FLOAT | 4 字节 | (-3.402 823 466 E+38,-1.175 494 351 E-38),0,(1.175 494 351 E-38,3.402 823 466 351 E+38) | 0,(1.175 494 351 E-38,3.402 823 466 E+38) | 单精度 浮点数值 | +| DOUBLE | 8 字节 | (-1.797 693 134 862 315 7 E+308,-2.225 073 858 507 201 4 E-308),0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308) | 0,(2.225 073 858 507 201 4 E-308,1.797 693 134 862 315 7 E+308) | 双精度 浮点数值 | +| DECIMAL | 对DECIMAL(M,D) ,如果M>D,为M+2否则为D+2 | 依赖于M和D的值 | 依赖于M和D的值 | 小数值 | + +(2)日期和时间类型 + +| 类型 | 大小 (字节) | 范围 | 格式 | 用途 | +| --------- | ----------- | ------------------------------------------------------------ | ------------------- | ------------------------ | +| DATE | 3 | 1000-01-01/9999-12-31 | YYYY-MM-DD | 日期值 | +| TIME | 3 | '-838:59:59'/'838:59:59' | HH:MM:SS | 时间值或持续时间 | +| YEAR | 1 | 1901/2155 | YYYY | 年份值 | +| DATETIME | 8 | 1000-01-01 00:00:00/9999-12-31 23:59:59 | YYYY-MM-DD HH:MM:SS | 混合日期和时间值 | +| TIMESTAMP | 4 | 1970-01-01 00:00:00/2038 结束时间是第 **2147483647** 秒,北京时间 **2038-1-19 11:14:07**,格林尼治时间 2038年1月19日 凌晨 03:14:07 | YYYYMMDD HHMMSS | 混合日期和时间值,时间戳 | + + (3)字符串类型 + +| 类型 | 大小 | 用途 | +| ---------- | ------------------- | ------------------------------- | +| CHAR | 0-255字节 | 定长字符串 | +| VARCHAR | 0-65535 字节 | 变长字符串 | +| TINYBLOB | 0-255字节 | 不超过 255 个字符的二进制字符串 | +| TINYTEXT | 0-255字节 | 短文本字符串 | +| BLOB | 0-65 535字节 | 二进制形式的长文本数据 | +| TEXT | 0-65 535字节 | 长文本数据 | +| MEDIUMBLOB | 0-16 777 215字节 | 二进制形式的中等长度文本数据 | +| MEDIUMTEXT | 0-16 777 215字节 | 中等长度文本数据 | +| LONGBLOB | 0-4 294 967 295字节 | 二进制形式的极大文本数据 | +| LONGTEXT | 0-4 294 967 295字节 | 极大文本数据 | + +#### 4.2.2 修改表 + +1.添加列 + +```sql +-- mysql +alter table 表名 add column 列名 类型 【default 值】 【first|after 字段名】; + +-- oracle +alter table 表名 add 列名 类型 【default 值】【not null】; +``` + +2.修改列的类型或约束 + +```sql +-- mysql +alter table 表名 modify column 列名 新类型 【新约束】; + +-- oracle +alter table 表名 modify (列名 新类型 【新约束】); +``` + +3.修改列名 + +```mysql +-- mysql +alter table 表名 change column 旧列名 新列名 类型; + +-- oracle +alter table 表名 rename column 旧列名 to 新列名; +``` + +4 .删除列 + +```sql +-- mysql oracle +alter table 表名 drop column 列名; +``` + +5.修改表名 + +``` +-- mysql oracle +alter table 表名 rename to 新表名; +``` + +#### 4.2.3 删除表 + +```mysql +-- mysql +drop table【if exists】 表名; + +-- oracle +drop table 表名; +``` + +#### 4.2.4 复制表 + +1、复制表的结构 + +```sql +-- mysql +create table 表名 like 旧表; + +-- oracle +create table 表名 as select * from 旧表 where 1=2; +``` + +2、复制表的结构+数据 + +```mysql +-- mysql oracle +create table table_name_new as select * from table_name_old【where 筛选】; +``` + +3 、只复制数据 + +```sql +-- mysql oracle + +-- 两个表结构一样 +insert into 表1 select * from 表2; + +-- 两个表的结构不一样,只复制部分列 +insert into 表1(column1,column2,column3) select column1x,column2x,column3x from 表2; + +``` + + + +### 4.3 约束 + +#### 4.3.1 常见的约束 + +- NOT NULL:非空,该字段的值必填 +- UNIQUE:唯一,该字段的值不可重复 +- DEFAULT:默认,该字段的值不用手动插入有默认值 +- CHECK:检查,mysql不支持 +- PRIMARY KEY:主键,该字段的值不可重复并且非空 unique+not null +- FOREIGN KEY:外键,该字段的值引用了另外的表的字段 + +注:主键和唯一约束区别 + +- 一个表至多有一个主键,但可以有多个唯一 +- 主键不允许为空,唯一可以为空 + + + +#### 4.3.2 创建表时添加约束 + +```sql +create table 表名( + 字段名 字段类型 not null,#非空 + 字段名 字段类型 primary key,#主键 + 字段名 字段类型 unique,#唯一 + 字段名 字段类型 default 值,#默认 + constraint 约束名 foreign key(字段名) references 主表(被引用列) +) + +``` + +#### 4.3.3 修改表时添加或删除约束 + +```sql +--1、非空约束 + -- mysql + -- 添加非空 + alter table 表名 modify column 字段名 字段类型 not null; + -- 删除非空 + alter table 表名 modify column 字段名 字段类型 ; + -- oracle + -- 添加非空 + alter table 表名 modify 字段名 not null; + -- 删除非空 + alter table 表名 modify 字段名 ; + + +-- 2、默认约束 + -- mysql + -- 添加默认 + alter table 表名 modify column 字段名 字段类型 default 默认值 + -- 删除默认 + alter table 表名 modify column 字段名 字段类型 ; + -- oracle + -- 添加默认 + alter table 表名 modify 字段名 default 默认值; + -- 删除默认 + alter table 表名 modify 字段名 ; + + +-- 3、主键约束 (oracle mysql) + -- 添加主键 + alter table 表名 add【 constraint 约束名】 primary key(字段名); + -- 删除主键 + alter table 表名 drop primary key; + +-- 4、唯一约束 (oracle mysql) + -- 添加唯一 + alter table 表名 add【 constraint 约束名】 unique(字段名); + -- 删除唯一 + alter table 表名 drop index 索引名; + +-- 5、外键约束 (oracle mysql) + --添加外键 + alter table 表名 add【 constraint 约束名】 foreign key(字段名) references 主表(被引用列); + --删除外键 + alter table 表名 drop foreign key 约束名; + +``` + +#### 4.3.4 自增长列(mysql) + +- 不用手动插入值,可以自动提供序列值,默认从1开始,步长为1 +- 一个表至多有一个自增长列 +- 自增长列只能支持数值型 +- 自增长列必须为一个key + +```sql +-- 创建表时设置自增长列 +create table 表( + 字段名 字段类型 约束 auto_increment +) + +-- 修改表时设置自增长列 +alter table 表 modify column 字段名 字段类型 约束 auto_increment + +-- 删除自增长列 +alter table 表 modify column 字段名 字段类型 约束 +``` + +#### 4.3.5 序列(oracle) + +oracle 没有自增长列,可以使用序列实现 + +**1.创建序列** + +```sql + CREATE SEQUENCE sequence //创建序列名称 + [INCREMENT BY n] //递增的序列值是n 如果n是正数就递增,如果是负数就递减 默认是1 + [START WITH n] //开始的值,递增默认是minvalue 递减是maxvalue + [{MAXVALUE n | NOMAXVALUE}] //最大值 + [{MINVALUE n | NOMINVALUE}] //最小值 + [{CYCLE | NOCYCLE}] //循环/不循环 + [{CACHE n | NOCACHE}];//分配并存入到内存中 +``` + +使用CACHE选项时,该序列会根据序列规则预生成一组序列号。保留在内存中,当使用下一个序列号时,可以更快的响应。当内存中的序列号用完时,系统再生成一组新的序列号,并保存在缓存中,这样可以提高生成序列号的效率。Oracle默认会生产20个序列号。 + +**2.修改序列** + +```sql + alter SEQUENCE sequence //创建序列名称 + [INCREMENT BY n] //递增的序列值是n 如果n是正数就递增,如果是负数就递减 默认是1 + [START WITH n] //开始的值,递增默认是minvalue 递减是maxvalue + [{MAXVALUE n | NOMAXVALUE}] //最大值 + [{MINVALUE n | NOMINVALUE}] //最小值 + [{CYCLE | NOCYCLE}] //循环/不循环 + [{CACHE n | NOCACHE}];//分配并存入到内存中 +``` + +**3.删除序列** + +``` +DROP SEQUENCE sequence +``` + +**4.使用序列** + +```sql +insert into 表名(字段,..) values(sequence.nextval,...); +``` + + + +#### 4.3.6 级联操作 + +```sql +-- oracle mysql +-- 级联删除 +ALTER TABLE 表名 ADD CONSTRAINT 约束名 FOREIGN KEY(外键列列名) REFERENCES 关联表(列名) ON DELETE CASCADE; +-- 级联置空 +ALTER TABLE 表名 ADD CONSTRAINT 约束名 FOREIGN KEY(外键列列名) REFERENCES 关联表(列名) ON DELETE SET NULL; +``` + + + +## 五、TCL语言 + +### 5.1 事务 + +#### 5.1.1 事务隔离级别 + +| 隔离级别 | 脏读 | 不可重复读 | 幻读 | +| --------------------------- | ---- | ---------- | ---- | +| Read uncommitted(读未提交) | 是 | 是 | 是 | +| Read committed(读已提交) | 否 | 是 | 是 | +| Repeatable read(可重复读) | 否 | 否 | 是 | +| Serializable(串行读) | 否 | 否 | 否 | + +**1.设置事务隔离级别** + +```sql +-- mysql oracle +SET [SESSION|GLOBAL] TRANSACTION ISOLATION LEVEL [READ UNCOMMITTED|READ COMMITTED|REPEATABLE READ|SERIALIZABLE] +``` + +**2.查看事务隔离级别** + +(1 )**mysql**默认的事务处理级别是 REPEATABLE-READ,也就是可重复读 + +```sql +-- mysql +-- 1.查看当前会话隔离级别 +select @@tx_isolation; + +-- 2.查看系统当前隔离级别 +select @@global.tx_isolation; +``` + +(2) **oracle**数据库支持READ COMMITTED 和 SERIALIZABLE这两种事务隔离级别。默认系统事务隔离级别是READ COMMITTED,也就是读已提交。 + +```sql +--查看系统默认事务隔离级别,也是当前会话隔离级别 +--1.首先创建一个事务 +declare + trans_id Varchar2(100); + begin + trans_id := dbms_transaction.local_transaction_id( TRUE ); + end; + +--2.查看事务隔离级别 +SELECT s.sid, s.serial#, + +  CASE BITAND(t.flag, POWER(2, 28)) +    WHEN 0 THEN 'READ COMMITTED' +    ELSE 'SERIALIZABLE' +  END AS isolation_level +FROM v$transaction t +JOIN v$session s ON t.addr = s.taddr AND s.sid = sys_context('USERENV', 'SID'); + + +``` + +**3.基本事务操作** + +- 开启事务:start transaction + +- 使用回滚点名:savepoint 回滚点 +- 提交事务:commit; +- 回滚事务:rollback; +- 回滚到指定的地方:rollback to 回滚点名; + + + +## 六、其他扩展 + +### 6.1 视图 + +**1. 创建视图** + +```sql +CREATE [OR REPLACE] [{FORCE|NOFORCE}] VIEW view_name +AS +SELECT查询 +[WITH READ ONLY CONSTRAINT] +``` + +- **OR REPLACE**:如果视图已经存在,则替换旧视图。 +- **FORCE**:即使基表不存在,也可以创建该视图,但是该视图不能正常使用,当基表创建成功后,视图才能正常使用。 +- **NOFORCE**:如果基表不存在,无法创建视图,该项是默认选项。 +- **WITH READ ONLY**:默认可以通过视图对基表执行增删改操作,WITH READ ONLY说明视图是只读视图。 + + + +**2. 删除视图** + +```sql +drop view 视图; +``` + + + +**3.查看所有视图** + +```sql +-- mysql +show table status where comment='view'; + +-- oracle +select * from dba_tab_privs --查看所有视图 +[all_tab_cols / all_tab_columns]--查看所有用户下的表及视图结构 +[user_tab_cols / user_tab_columns] --查看当前用户下的表及视图结构 +[user_col_comments] --查看当前用户下表及视图中字段名称及字段注释 +[all_col_comments] --查看所以用户下表及视图中字段名称及字段注释 +``` + + + +**4.查看单个视图** + +```sql +-- mysql +show create view hy_account_hospital_view; + +-- oracle +select text from user_views where view_name='view_name'; +``` + + + +### 6.2 触发器 + +**1.基本语法** + +```sql +-- 创建触发器 +CREATE TRIGGER 触发器名 BEFORE|AFTER 触发事件 +ON 表名 FOR EACH ROW +[BEGIN] + 执行语句列表 + 执行语句列表 + ... +[END] + +-- 删除触发器 +DROP TRIGGER [IF EXISTS] [schema_name.]触发器名 + +-- 查看触发器 +SHOW TRIGGERS [FROM schema_name]; +``` + +**MySQL支持创建以下六种触发器:** + +- + BEFORE INSERT, +- BEFORE DELETE, +- BEFORE UPDATE +- AFTER INSERT, +- AFTER DELETE, +- AFTER UPDATE + +**在触发器内部语句中,可以引用NEW和OLD两张虚拟表:** + +- 在 INSERT 型触发器中,NEW 用来表示将要(BEFORE)或已经(AFTER)插入的新数据; +- 在 UPDATE 型触发器中,OLD 用来表示将要或已经被修改的原数据,NEW 用来表示将要或已经修改为的新数据; +- 在 DELETE 型触发器中,OLD 用来表示将要或已经被删除的原数据; +- 使用方法: [NEW|OLD].columnName + +注:oracle使用 :NEW 和 : OLD来引用 这两张虚拟表,mysql则直接使用NEW和OLD + + + +### 6.3 存储过程 + +#### 6.3.1 创建存储过程 + +```sql +DELIMITER // +CREATE PROCEDURE 过程名([[IN|OUT|INOUT] 参数名 数据类型],...) + BEGIN + 过程体 + END +DELIMITER ; + +--调用 +call([参数名]) +``` + +- **IN**参数的值必须在调用存储过程时指定,在存储过程中修改该参数的值不能被返回,为默认值 +- **OUT**:该值可在存储过程内部被改变,并可返回 +- **INOUT**:调用时指定,并且可被改变和返回 + +#### 6.3.2变量 + +声明:DECLARE 变量名1[,变量名2...] 数据类型 [默认值]; + +赋值:SET 变量名 = 变量值 [,变量名= 变量值 ...] + +用户变量一般以@开头 + + + +#### 6.3.3 存储过程的查询 + +```sql +--查询存储过程 +SELECT name FROM mysql.proc WHERE db='数据库名'; +SELECT routine_name FROM information_schema.routines WHERE routine_schema='数据库名'; +SHOW PROCEDURE STATUS WHERE db='数据库名'; +``` + +```sql +-- 查看存储过程详细信息 +SHOW CREATE PROCEDURE 数据库.存储过程名; + +``` + +#### 6.3.4 存储过程的修改 + +ALTER PROCEDURE 更改用CREATE PROCEDURE 建立的预先指定的存储过程,其不会影响相关存储过程或存储功能。 + +```sql +ALTER {PROCEDURE | FUNCTION} sp_name [characteristic ...] +characteristic: +{ CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA } +| SQL SECURITY { DEFINER | INVOKER } +| COMMENT 'string' +``` + +- sp_name参数表示存储过程或函数的名称; +- characteristic参数指定存储函数的特性。 +- CONTAINS SQL表示子程序包含SQL语句,但不包含读或写数据的语句; +- NO SQL表示子程序中不包含SQL语句; +- READS SQL DATA表示子程序中包含读数据的语句; +- MODIFIES SQL DATA表示子程序中包含写数据的语句。 +- SQL SECURITY { DEFINER | INVOKER }指明谁有权限来执行,DEFINER表示只有定义者自己才能够执行;INVOKER表示调用者可以执行。 +- COMMENT 'string'是注释信息。 + +```sql +-- 将读写权限改为MODIFIES SQL DATA,并指明调用者可以执行。 +ALTER PROCEDURE num_from_employee + MODIFIES SQL DATA + SQL SECURITY INVOKER ; +-- 将读写权限改为READS SQL DATA,并加上注释信息'FIND NAME'。 +ALTER PROCEDURE name_from_employee + READS SQL DATA + COMMENT 'FIND NAME' ; + +``` + +#### 6.3.5 存储过程的删除 + +```sql +DROP PROCEDURE [过程1[,过程2…]] +``` + +#### 6.3.6 条件语句 + +##### 1.分支结构 + +**(1)case结构** + +```mysql +case 表达式或字段 +when 值1 then 语句1; +when 值2 then 语句2; +.. +else 语句n; +end [case]; + +语法2: +case +when 条件1 then 语句1; +when 条件2 then 语句2; +.. +else 语句n; +end [case]; +``` + +**(2)if 结构** + +```mysql +if 条件1 then 语句1; +elseif 条件2 then 语句2; +... +else 语句n; +end if; +``` + + + +##### 2.循环结构 + +(1)while + +```sql +【名称:】while 循环条件 do + 循环体 +end while 【名称】; +``` + + +(2)loop + +```sql +【名称:】loop + 循环体 +end loop 【名称】; +``` + +(3)repeat + +```sql +【名称:】repeat + 循环体 +until 结束条件 +end repeat 【名称】; +``` + +结束循环: + +- leave:类似于break,用于跳出所在的循环 +- iterate:类似于continue,用于结束本次循环,继续下一次 + +对比: + +- 这三种循环都可以省略名称,但如果循环中添加了循环控制语句(leave或iterate)则必须添加名称 + +- loop 一般用于实现简单的死循环 + +- while 先判断后执行 + +- repeat 先执行后判断,无条件至少执行一次 + + + + +### 6.4 游标 + +#### 6.4.1 游标简介 + +游标的设计是一种数据缓冲区的思想,用来存放SQL语句执行的结果。游标是一种能从包括多条数据记录的结果集中每次提取一条记录的机制。 +尽管游标能遍历结果中的所有行,但一次只指向一行。 +游标的作用就是用于对查询数据库所返回的记录进行遍历,以便进行相应的操作。 + +#### 6.4.2 游标的特性 + +游标具有三个属性: + +1. 不敏感(Asensitive):数据库可以选择不复制结果集 +2. 只读(Read only) +3. 不滚动(Nonscrollable):游标只能向一个方向前进,并且不可以跳过任何一行数据。 + +#### 6.4.3 游标的操作 + +**1、游标的定义** + +``` +DECLARE cursor_name CURSOR FOR select_statement +``` + +**2、打开游标** + +``` +OPEN cursor_name; +``` + +**3、取游标中的数据** + +``` +FETCH cursor_name INTO var_name [, var_name]... +``` + +**4、关闭游标** + +``` +CLOSE cursor_name; +``` + +**5、释放游标** + +``` +DEALLOCATE cursor_name; +``` + + + +## 参考资料 + ++ [Oracle创建用户、角色、授权、建表](https://www.cnblogs.com/roger112/p/7685307.html) ++ [MYSQL存储过程](https://www.cnblogs.com/mark-chan/p/5384139.html) ++ [MySQL数据库高级(九)——游标](http://blog.51cto.com/9291927/2097626) + + + + diff --git a/notes/《Java8实战》读书笔记.md b/notes/《Java8实战》读书笔记.md new file mode 100644 index 0000000..ada21d5 --- /dev/null +++ b/notes/《Java8实战》读书笔记.md @@ -0,0 +1,612 @@ +# 《Java 8 实战》读书笔记 + ## 目录
+第二部分 函数式数据处理
+    第4章 引入流
+        1.流的基本使用
+        2.数值流
+        3.构建流
+    第5章 使用流
+        1.中间操作和基本操作
+        2.flatMap的使用
+        3.归约
+    第6章 用流收集数据
+        1.预定义收集器
+第三部分 高效的Java 8编程
+    第12章 新的日期和时间API
+        12.1 LocalDate、LocalTime、Instant、Duration、period
+            1.使用 LocalDate 和 LocalTime
+            2.使用LocalDateTime
+            3.时间间隔 Duration 或 Period
+        12.2 操纵、解析和格式化日期
+            1.操纵日期加减
+            2.使用TemporalAdjusters
+            3.日期格式解析
+## 正文
+ + + + + + +## 第二部分 函数式数据处理 + +### 第4章 引入流 + +#### 1.流的基本使用 + +```java +// 基础数据类 +public class Data { + + public enum Type {MEAT, FISH, OTHER} + + public static List menu = Arrays.asList( + new Dish("pork", false, 800, Type.MEAT), + new Dish("beef", false, 700, Type.MEAT), + new Dish("chicken", false, 400, Type.MEAT), + new Dish("french fries", true, 530, Type.OTHER), + new Dish("rice", true, 350, Type.OTHER), + new Dish("season fruit", true, 120, Type.OTHER), + new Dish("pizza", true, 550, Type.OTHER), + new Dish("prawns", false, 300, Type.FISH), + new Dish("salmon", false, 450, Type.FISH)); + + static class Dish { + private final String name; + private final boolean vegetarian; + private final int calories; + private final Data.Type type; + + public Dish(String name, boolean vegetarian, int calories, Data.Type type) { + this.name = name; + this.vegetarian = vegetarian; + this.calories = calories; + this.type = type; + } + + public String getName() { + return name; + } + + public boolean isVegetarian() { + return vegetarian; + } + + public int getCalories() { + return calories; + } + + public Data.Type getType() { + return type; + } + + @Override + public String toString() { + return name; + } + + } +} + +``` + +```java +// 流的使用 +public class Stream { + + public static void main(String[] args) { + List collect = Data.menu.stream().filter(d -> d.getCalories() > 300).map(Data.Dish::getName).limit(3).collect(Collectors.toList()); + collect.forEach(System.out::println); + } +} +``` + +#### 2.数值流 + +**映射到数值流(mapToInt)** + +```java +public class Stream { + + public static void main(String[] args) { + + //得到的是 Stream + java.util.stream.Stream integerStream = Data.menu.stream().map(Data.Dish::getCalories); + Integer reduce = integerStream.reduce(0, Integer::sum); + System.out.println(reduce); //4200 + + //得到的是 IntStream + IntStream intStream = Data.menu.stream().mapToInt(Data.Dish::getCalories); + int sum = intStream.sum(); + System.out.println(sum); //4200 + } +} +``` + +**数值流到映射(boxed)** + +```java +public class Stream { + + public static void main(String[] args) { + + //得到的是 Stream + java.util.stream.Stream integerStream = Data.menu.stream().map(Data.Dish::getCalories); + Integer reduce = integerStream.reduce(0, Integer::sum); + System.out.println(reduce); //4200 + + //得到的是 IntStream + IntStream intStream = Data.menu.stream().mapToInt(Data.Dish::getCalories); + java.util.stream.Stream boxed = intStream.boxed(); + Integer reduceSum = boxed.reduce(0, Integer::sum); + System.out.println(reduceSum); //4200 + } +} +``` + +```java +// 收集器对流的消耗 +public class Stream { + + public static void main(String[] args) { + + //得到的是 Stream + java.util.stream.Stream integerStream = Data.menu.stream().map(Data.Dish::getCalories); + Integer reduce = integerStream.reduce(0, Integer::sum); + System.out.println(reduce); //4200 + + //得到的是 IntStream + IntStream intStream = Data.menu.stream().mapToInt(Data.Dish::getCalories); + int sum = intStream.sum(); + // 下面这行报错:IllegalStateException: stream has already been operated upon or closed + // 因为sum() 是一个终止流方法,会消耗掉流,所以调用 intStream.boxed() 方法时候会报错 + java.util.stream.Stream boxed = intStream.boxed(); + Integer reduceSum = boxed.reduce(0, Integer::sum); + System.out.println(sum); //4200 + System.out.println(reduceSum); //4200 + } +``` + +#### 3.构建流 + +**1.由值创建流** + +```java +public class StreamCreate { + + public static void main(String[] args) { + + // 由值创建流 + Stream stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action"); + stream.map(String::toUpperCase).forEach(System.out::println); + + // 创建空流 + Stream empty = Stream.empty(); + } +} +``` + +**2.由数组创建流** + +```java +public class StreamCreate { + + public static void main(String[] args) { + + int[] numbers = {2, 3, 5, 7, 11, 13}; + int sum = Arrays.stream(numbers).sum(); + System.out.println(sum); //41 + } +} +``` + +**3.由文件创建流** + +```java +public class StreamCreate { + + public static void main(String[] args) { + + try (Stream lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())) { + Set collect = lines.flatMap(line -> Arrays.stream(line.split(""))) + .distinct() + .collect(Collectors.toSet()); + collect.forEach(System.out::print); + } catch (IOException e) { + e.printStackTrace(); + } + } +} +``` + +**4.由函数生成流** + +Stream API提供了两个静态方法来从函数生成流: **Stream.iterate**和**Stream.generate**。这两个操作可以创建所谓的无限流:不像从固定集合创建的流那样有固定大小的流。由iterate和generate产生的流会用给定的函数按需创建值,因此可以无穷无尽地计算下去!一般来说,应该使用limit(n)来对这种流加以限制,以避免打印无穷多个值。 + +- **iterate**方法接受一个初始值(在这里是0),还有一个依次应用在每个产生的新值上的Lambda(UnaryOperator\类型)。 +- **generate**方法也可让你按需生成一个无限流。但generate不是依次对每个新生成的值应用函数的。它接受一个Supplier\类型的Lambda提供新的值。 + +```java +public class StreamCreate { + + public static void main(String[] args) { + + // 迭代 + Stream.iterate(0, n -> n + 2) + .limit(10) + .forEach(System.out::println); + + // 生成 + Stream.generate(Math::random) + .limit(5) + .forEach(System.out::println); + } +} +``` + +### 第5章 使用流 + +你可以把Java 8的流看作花哨又懒惰的数据集迭代器。它们支持两种类型的操作:中间操作和终端操作。中间操作可以链接起来,**将一个流转换为另一个流**。这些操作不会消耗流,其目的是建立一个流水线。与此相反,**终端操作会消耗流**,以产生一个最终结果,例如返回流中的最大元素。 + +#### 1.中间操作和基本操作 + +**表5-1 中间操作和终端操作** + +| 操作 | 类型 | 返回类型 | 使用的类型/函数式接口 | 函数描述符 | +| --------- | ----------------- | ------------ | ---------------------- | --------------- | +| filter | 中间 | Stream | Predicate\ | T -> boolean | +| distinct | 中间(有状态-无界) | Stream | | | +| skin | 中间(有状态-有界) | Stream | long | | +| limit | 中间(有状态-有界) | Stream | long | | +| map | 中间 | Stream | Function | T -> R | +| flatMap | 中间 | Stream | Function> | T -> Stream\ | +| sorted | 中间(有状态-无界) | Stream | Comparator\ | (T , T) -> int | +| anyMatch | 终端 | boolean | Predicate\ | T -> boolean | +| noneMatch | 终端 | boolean | Predicate\ | T -> boolean | +| allMatch | 终端 | boolean | Predicate\ | T -> boolean | +| findAny | 终端 | Optional\ | | | +| findFirst | 终端 | Optional\ | | | +| forEach | 终端 | void | Cosumer\ | T -> void | +| collect | 终端 | R | collector | | +| reduce | 终端 | Optional\ | BinaryOperator\ | (T , T) -> T | +| count | 终端 | long | | | + +#### 2.flatMap的使用 + +给 定 单 词 列 表["Hello","World"],你想要返回列表["H","e","l", "o","W","r","d"]。 + +**Arrays.stream()的方法可以接受一个数组并产生一个流 。** + +```java +public class Stream { + + public static void main(String[] args) { + String[] strings = {"Hello", "World"}; + + // 如下图 5.5 + List list01 = Arrays.stream(strings) + .map(s -> s.split("")) + .distinct().collect(Collectors.toList()); + + // 使用flatMap方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。 如下图5.6 + List list02 = Arrays.stream(strings) + .map(s -> s.split("")) + .flatMap(Arrays::stream) + .distinct().collect(Collectors.toList()); + + //每个单词转换成一个字母数组,然后把每个数组变成了一个独立的流。 + List> list03 = Arrays.stream(strings) + .map(s -> s.split("")) + .map(Arrays::stream) + .distinct().collect(Collectors.toList()); + + list01.forEach(System.out::println); + list02.forEach(System.out::println); + list03.forEach(System.out::println); + } +} +``` + +

+ +

+ +#### 3.归约 + +```java +public class Stream { + + public static void main(String[] args) { + List integers = Arrays.asList(1, 2, 3, 4, 5); + Integer reduce = integers.stream().reduce(0, (a, b) -> a + b); + System.out.println(reduce); + } +} +``` + +### 第6章 用流收集数据 + +#### 1.预定义收集器 + +预定义收集器主要指 Collectors类提供的工厂方法(例如groupingBy)创建的收集器。它们主要提供了三大功能: + +- 将流元素归约和汇总为一个值 +- 元素分组 +- 元素分区 + +**表6.1 Collectors 类的静态工厂方法** + +| 工厂方法 | 返回类型 | 用于 | +| ----------------- | --------------------- | ------------------------------------------------------------ | +| toList | List\ | 把流中所有项目收集到一个List | +| toSet | Set\ | 把流中所有项目收集到一个Set,删除重复项 | +| toCollection | Collection\ | 把流中所有项目收集到给定的供应源创建的集合 | +| counting | Long | 计算流中元素的个数 | +| summingInt | Integer | 对流中项目的一个整数属性求和 | +| averagingInt | Double | 计算流中项目Integer属性的平均值 | +| summarizingInt | IntSummaryStatistics | 收集关于流中项目Integer属性的统计值,例如最大、最小、总和与平均值 | +| joining | String | 连接对流中每个项目调用toString方法所生成的字符串 | +| maxBy | Optional\ | 查找最大元素的Optional,或如果流为空则为Optional.empty() | +| minBy | Optional\ | 查找最小元素的Optional,或如果流为空则为Optional.empty() | +| reducing | 规约操作产生的类型 | 从一个作为累加器的初始值开启,利用BinaryOperator与流中的元素逐个结合,从而将流归约为单个值 | +| collectingAndThen | 转换返回返回的类型 | 包裹另一个收集器,对其结果应用转换函数 | +| groupingBy | Map> | 根据项目的一个属性的值对流中项目作分组,并将其属性值作为结果Map的键 | +| partitionBy | Map> | 根据对流中每个项目应用谓词的结果来对项目进行区分 | + +```java +import java.util.*; +import java.util.stream.Collectors; +import static java.util.stream.Collectors.*; + +public class Stream { + + public static void main(String[] args) { + + // toList + List dishList = Data.menu.stream().collect(toList()); + + // toSet + Set dishSet = Data.menu.stream().collect(Collectors.toSet()); + + // toCollection + ArrayList arrayList = Data.menu.stream() + .collect(Collectors.toCollection(ArrayList::new)); + + // counting + long count = Data.menu.stream().count(); + // summingInt 可以用中间操作等价 + Integer sum01 = Data.menu.stream() + .collect(Collectors.summingInt(Data.Dish::getCalories)); + Integer sum02 = Data.menu.stream() + .mapToInt(Data.Dish::getCalories).sum(); + + // averagingInt + Double average = Data.menu.stream() + .collect(Collectors.averagingInt(Data.Dish::getCalories)); + + // summarizingInt + IntSummaryStatistics statistics = Data.menu.stream() + .collect(Collectors.summarizingInt(Data.Dish::getCalories)); + statistics.getAverage(); + statistics.getMax(); + + // joining + String joining = Data.menu.stream().map(Data.Dish::getName) + .collect(Collectors.joining(",")); + + // maxBy + Optional max = Data.menu.stream() + .max(Comparator.comparingInt(Data.Dish::getCalories)); + + // minBy + Optional min = Data.menu.stream() + .min(Comparator.comparingInt(Data.Dish::getCalories)); + + // reducing + Integer reduce01 = Data.menu.stream().map(Data.Dish::getCalories) + .reduce(0, Integer::sum); + Integer reduce02 = Data.menu.stream().map(Data.Dish::getCalories) + .reduce(0, Integer::sum); + + // collectingAndThen + Integer size = Data.menu.stream().collect(collectingAndThen(toList(), List::size)); + + // groupingBy + Map> typeListMap = Data.menu.stream() + .collect(groupingBy(Data.Dish::getType)); + + // partitionBy + Map> booleanListMap = Data.menu.stream() + .collect(partitioningBy(Data.Dish::isVegetarian)); + } +} +``` + +## 第三部分 高效的Java 8编程 + +### 第12章 新的日期和时间API + +#### 12.1 LocalDate、LocalTime、Instant、Duration、period + +##### 1.使用 LocalDate 和 LocalTime + +```java +public class NewDateApi { + + public static void main(String[] args) { + + // 创建日期 + LocalDate date = LocalDate.of(2018, 10, 8); + int year = date.getYear(); // 2018 + Month month = date.getMonth(); // OCTOBER + int value = month.getValue(); // 10 + int dayOfMonth = date.getDayOfMonth(); // 8 + int i = date.lengthOfMonth(); // 31 + + // 获取当前日期 + LocalDate now = LocalDate.now(); + + //创建时间 + LocalTime time = LocalTime.of(12, 13, 32); + int hour = time.getHour(); + int minute = time.getMinute(); + int second = time.getSecond(); + + // 通过解析创建日期和时间 + // the text to parse such as "2007-12-03", not null + LocalDate localDate = LocalDate.parse("2019-03-12"); + // the text to parse such as "10:15:30", not null + LocalTime localTime = LocalTime.parse("12:21:45"); + } +} +``` + +##### 2.使用LocalDateTime + +```java +public class NewDateApi { + + public static void main(String[] args) { + + LocalDate date = LocalDate.of(2014, Month.MARCH, 18); + LocalTime time = LocalTime.of(13, 45, 20); + + LocalDateTime dt1 = LocalDateTime.of(2014, Month.MARCH, 18, 13, 45, 20); + + LocalDateTime dt2 = LocalDateTime.of(date, time); + LocalDateTime dt3 = date.atTime(13, 45, 20); + LocalDateTime dt4 = date.atTime(time); + LocalDateTime dt5 = time.atDate(date); + + LocalDate localDate = dt1.toLocalDate(); + LocalTime localTime = dt1.toLocalTime(); + + boolean equal = dt1.isEqual(dt2); // true + boolean equals = date.equals(localDate); // true + + } +} +``` + +##### 3.时间间隔 Duration 或 Period + +```java +// 计算时间间隔 +public class NewDateApi { + + public static void main(String[] args) { + + LocalDate date01 = LocalDate.of(2014, Month.MARCH, 18); + LocalDate date02 = LocalDate.of(2012, Month.MARCH, 23); + LocalTime time01 = LocalTime.of(13, 45, 20); + LocalTime time02 = LocalTime.of(13, 12, 35); + + // 计算日期间隔 + Period between01 = Period.between(date01, date02); + + // 计算时间间隔 + Duration between02 = Duration.between(time01, time02); + + // 间隔时间可能为正值 也可能为负值 可以用 isNegative 判断 + System.out.println(between01.getDays()); + System.out.println(between01.getYears()); + System.out.println(between02.getSeconds()); + + } +} +``` + +```java +// 创建时间间隔 +public class NewDateApi { + + public static void main(String[] args) { + + // 创建 Duration 和 Period 对象 + Duration duration = Duration.ofMinutes(3); + Duration duration1 = Duration.of(3, ChronoUnit.MINUTES); + + Period tenDays = Period.ofDays(10); + Period threeWeeks = Period.ofWeeks(3); + Period twoYearsSixMonthsOneDay = Period.of(2, 6, 1); + + } +} +``` + +#### 12.2 操纵、解析和格式化日期 + +##### 1.操纵日期加减 + +```java +public class NewDateApi { + + public static void main(String[] args) { + + // 以直接修改方式操作 LocalDate + LocalDate date1 = LocalDate.of(2014, 3, 18); + LocalDate date2 = date1.withYear(2011); + LocalDate date3 = date2.withDayOfMonth(25); + LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR, 9); + + // 以相对的方式操作 LocalDate + LocalDate date5 = date1.plusWeeks(1); + LocalDate date6 = date1.minusYears(3); + LocalDate date7 = date1.plus(6, ChronoUnit.MONTHS); + + + // 操作 LocalDate + LocalTime time = LocalTime.of(12, 3, 18); + LocalTime time1 = time.withHour(3); + LocalTime time2 = time.plus(3, ChronoUnit.HOURS); + System.out.println(time2.getHour()); + + } +} +``` + +##### 2.使用TemporalAdjusters + +```java +import java.time.*; +import static java.time.temporal.TemporalAdjusters.lastDayOfMonth; +import static java.time.temporal.TemporalAdjusters.nextOrSame; + +public class NewDateApi { + + public static void main(String[] args) { + + LocalDate date1 = LocalDate.of(2014, 3, 18); + LocalDate date2 = date1.with(nextOrSame(DayOfWeek.SUNDAY)); + LocalDate date3 = date2.with(lastDayOfMonth()); + + } +} +``` + +

+ + + +##### 3.日期格式解析 + +```java +public class NewDateApi { + + public static void main(String[] args) { + + // 使用内置格式解析 + LocalDate date = LocalDate.of(2014, 3, 18); + String s1 = date.format(DateTimeFormatter.ISO_LOCAL_DATE); //2014-03-18 + + + // 使用自定义格式解析 + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy"); + LocalDate date1 = LocalDate.of(2014, 3, 18); + String formattedDate = date1.format(formatter); + LocalDate date2 = LocalDate.parse(formattedDate, formatter); + + } +} +``` + diff --git a/notes/《Linux就该这么学》读书笔记.md b/notes/《Linux就该这么学》读书笔记.md new file mode 100644 index 0000000..cd6937d --- /dev/null +++ b/notes/《Linux就该这么学》读书笔记.md @@ -0,0 +1,1672 @@ + + +# 《linux就该这么学》读书笔记 + +## 目录
+第一章 部署虚拟环境安装Linux系统
+    1.6 Yum 软件仓库
+    1.7 systemd 初始化进程
+第二章 新手必须掌握的Linux命令
+    2.2 执行查看帮助命令
+    2.3 常用系统工作命令
+        1. echo命令
+        2. date命令
+        3. reboot 命令
+        4. poweroff命令
+        5. wget命令
+        6. ps 命令
+        7. top 命令
+        8. pidof 命令
+        9. kill命令
+        10. killall 命令
+    2.4 系统状态检测命令
+        1. ifconfig 命令
+        2. uname 命令
+        3. uptime 命令
+        4. free 命令
+        5. who 命令
+        6. last 命令
+        7. history 命令
+        8. sosreport 命令
+        1. pwd 命令
+        2. cd 命令
+        3. ls命令
+    2.6 文本文件编辑命令
+        1. cat 命令
+        2. more 命令
+        3.head 命令
+        4. tail 命令
+        5. tr 命令
+        6. wc 命令
+        7. stat 命令
+        8. cut命令
+        9. diff 命令
+        1. touch 命令
+        2. mkdir 命令
+        3. cp 命令
+        4. mv 命令
+        5. rm 命令
+        6. dd 命令
+        7. file命令
+    2.8 打包压缩与搜索命令
+        1. tar 命令
+        2. grep 命令
+        3. find命令
+第三章 管道符、重定向与环境变量
+    3.1 输入输出重定向
+    3.2 管道运算符
+    3.3 命令通配符
+    3.4 常用的转义字符
+    3.5 重要的环境变量
+第四章 Vim 编辑器与 shell 脚本命令
+    4.1 vim编辑器
+    4.2 编写的脚本
+        4.2.1 编写简单的脚本
+        4.2.2 接受用户参数
+4.3 流程控制语句
+4.4 计划任务服务程序
+第五章 用户身份与文件权限
+    5.1 用户身份与能力
+        1. useradd 命令
+        2. groupadd 命令
+        3. usermod 命令
+        4. passwd 命令
+        5. userdel 命令
+    5.2 文件权限与归属
+    5.3 文件的特殊权限
+        5.3.1 SUID
+        5.3.2 SGID
+            1.chmod命令
+            2.chown命令
+        5.3.3 SBIT
+    5.4 文件的隐藏属性
+        1. chattr命令
+        2. lsattr 命令
+    5.5 文件访问控制列表
+        5.5.1 setfacl 命令
+        5.5.2 getfacl 命令
+    5.6 su命令与sudo服务
+第六章 存储结构与磁盘划分
+    6.1 一切从“/”开始
+    6.2 物理设备的命名规则
+    6.4 挂载硬件设备
+        1.挂载
+        2.永久挂载
+        3.撤消挂载
+    6.8 软硬链接连接方式
+        1. ln 命令
+第八章 Iptables与Firewalld防火墙
+    8.2 iptables
+        8.2.1 策略与规则链
+        8.2.2 基本的命令参数
+    8.3 Firewalld
+        8.3.1 终端管理工具
+## 正文
+ + +## 第一章 部署虚拟环境安装Linux系统 + +### 1.6 Yum 软件仓库 + +**表:常见的yum命令** + +| 命令 | 列出所有仓库 | +| ------------------------- | ---------------------------- | +| yum repolist all | 列出所有仓库 | +| yum list all | 列出仓库中所有软件包 | +| yum info 软件包名称 | 查看软件包信息 | +| yum install 软件包名称 | 安装软件包 | +| yum reinstall 软件包名称 | 重新安装软件包 | +| yum update 软件包名称 | 升级软件包 | +| yum remove 软件包名称 | 移除软件包 | +| yum clean all | 清除所有仓库缓存 | +| yum check-update | 检查可更新的软件包 | +| yum grouplist | 查看系统中已经安装的软件包组 | +| yum groupinstall 软件包组 | 安装指定的软件包组 | +| yum groupremove 软件包组 | 移除指定的软件包组 | +| yum groupinfo 软件包组 | 查询指定的软件包组信息 | + + + +### 1.7 systemd 初始化进程 + +**表:systemctl 管理服务的启动、重启、停止、重载、查看状态等常用命令** + +| System V init 命令(RHE6系统) | systemctl命令(RHEL 7 系统) | 作用 | +| ------------------------------ | ----------------------------- | ------------------------------ | +| service foo start | systemctl start foo.service | 启动服务 | +| service foo restart | systemctl restart foo.service | 重启服务 | +| service foo stop | systemctl stop foo.service | 停止服务 | +| service foo reload | systemctl reload foo.service | 重新加载配置文件(不终止服务) | +| service foo status | systemctl status foo.service | 查看服务状态 | + + + +**表:systemctl 设置服务开机启动、不启动、查看各级别下服务启动状态等常用命令** + +| System V init 命令(RHE6系统) | systemctl命令(RHEL 7 系统) | 作用 | +| ------------------------------ | ---------------------------------------- | ---------------------------------- | +| chkconfig foo in | systemctl enable foo.service | 开机自动启动 | +| chkconfig foo off | systemctl disable foo.service | 开机不自动启动 | +| chkconfig foo | systemctl is-enable foo.service | 查看特定服务是否为开启自动启动 | +| chkconfig --list | systemctl list-unit-files --type=service | 查看各个级别下服务的启动与禁用情况 | + + + +```shell +[root@localhost ~]# chkconfig docker +注意:正在将请求转发到“systemctl is-enabled docker.service”。 +enabled +``` + + + +## 第二章 新手必须掌握的Linux命令 + +### 2.2 执行查看帮助命令 + +**表:man命令中常用按键以及用途** + +| 按键 | 用处 | +| --------- | ---------------------------------- | +| 空格键 | 向下翻一页 | +| PaGe down | 向下翻一页 | +| PaGe up | 向上翻一页 | +| home | 直接前往首页 | +| end | 直接前往尾页 | +| / | 从上至下搜索某个关键词,如“/linux” | +| ? | 从下至上搜索某个关键词,如“?linux” | +| n | 定位到下一个搜索到的关键词 | +| N | 定位到上一个搜索到的关键词 | +| q | 退出帮助文档 | + +### 2.3 常用系统工作命令 + +#### 1. echo命令 + +echo 命令用于在终端输出字符串或变量提取后的值,格式为“echo [字符串 | $变量]”。 + +```shell +[root@localhost ~]# echo hello +hello +[root@localhost ~]# echo $SHELL +/bin/bash +``` + +#### 2. date命令 + +date命令用于显示及设置系统的时间或日期。 + +**表: date命令中的参数以及作用** + +| 参数 | 作用 | +| ---- | -------------- | +| %t | 跳格[Tab键] | +| %H | 小时(00~23) | +| %I | 小时(00~12) | +| %M | 分钟(00~59) | +| %S | 秒(00~59) | +| %j | 今年中的第几天 | + +按照默认格式查看当前系统时间的date命令如下所示: + +```shell +[root@linuxprobe ~]# date +Mon Aug 24 16:11:23 CST 2017 +``` + +按照“年-月-日 小时:分钟:秒”的格式查看当前系统时间的date命令如下所示: + +```shell +[root@linuxprobe ~]# date "+%Y-%m-%d %H:%M:%S" +2017-08-24 16:29:12 +``` + +将系统的当前时间设置为2017年9月1日8点30分的date命令如下所示: + +```shell +[root@linuxprobe ~]# date -s "20170901 8:30:00" +Fri Sep 1 08:30:00 CST 2017 +``` + +再次使用date命令并按照默认的格式查看当前的系统时间,如下所示: + +```shell +[root@linuxprobe ~]# date +Fri Sep 1 08:30:01 CST 2017 +``` + +date命令中的参数%j可用来查看今天是当年中的第几天。 + +```shell +[root@linuxprobe ~]# date "+%j" +244 +``` + +#### 3. reboot 命令 + +reboot 命令用于重启系统,其格式为 reboot。 + +#### 4. poweroff命令 + +poweroff 命令用于关闭系统,其格式为 poweroff。 + +#### 5. wget命令 + +wget 命令用于在终端中下载网络文件,格式为“wget [参数] 下载地址”。 + +**表:wget 命令的参数以及作用** + +| 参数 | 作用 | +| ---- | ------------------------------------ | +| -b | 后台下载模式 | +| -P | 下载到指定目录 | +| -t | 最大尝试次数 | +| -c | 断点续传 | +| -p | 下载页面内所有资源,包括图片、视频等 | +| -r | 递归下载 | + +wget 命令递归下载 www.linuxprobe.com 网站内的所有页面数据以及文件,下载完后会自动保存到当前路径下一个名为 www.linuxprobe.com 的目录中。 + +```shell +[root@localhost ~]# wget -r -p http://www.linuxprobe.com +``` + +#### 6. ps 命令 + +ps 命令用于查看系统中的进程状态,格式为“ps [参数]”。 + +**表:ps 命令的参数以及作用** + +| 参数 | 作用 | +| ---- | ---------------------------------- | +| -a | 显示所有进程(包括其他用户的进程) | +| -u | 用户以及其他详细信息 | +| -x | 显示没有控制终端的进程 | + +在Linux系统中,有5种常见的进程状态,分别为运行、中断、不可中断、僵死与停止,其各自含义如下所示。 + +> **R(运行)**:进程正在运行或在运行队列中等待。 +> +> **S(中断)**:进程处于休眠中,当某个条件形成后或者接收到信号时,则脱离该 状态。 +> +> **D(不可中断)**:进程不响应系统异步信号,即便用kill命令也不能将其中断。 +> +> **Z(僵死)**:进程已经终止,但进程描述符依然存在, 直到父进程调用wait4()系统函数后将进程释放。 +> +> **T(停止)**:进程收到停止信号后停止运行。 + +| USER | PID | %CPU | %MEM | VSZ | RSS | TTY | STAT | START | TIME | COMMAND | +| ------------ | -------- | ------------ | ---------- | ------------------------ | -------------------------- | -------- | -------- | ------------ | ----------------- | -------------- | +| 进程的所有者 | 进程ID号 | 运算器占用率 | 内存占用率 | 虚拟内存使用量(单位是KB) | 占用的固定内存量(单位是KB) | 所在终端 | 进程状态 | 被启动的时间 | 实际使用CPU的时间 | 命令名称与参数 | + +```shell +USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND +root 1 0.0 0.6 128148 6736 ? Ss 09:41 0:03 /usr/lib/systemd +root 2 0.0 0.0 0 0 ? S 09:41 0:00 [kthreadd] +root 3 0.0 0.0 0 0 ? S 09:41 0:00 [ksoftirqd/0] +root 5 0.0 0.0 0 0 ? S< 09:41 0:00 [kworker/0:0H] +root 7 0.0 0.0 0 0 ? S 09:41 0:00 [migration/0] +root 8 0.0 0.0 0 0 ? S 09:41 0:00 [rcu_bh] +root 9 0.0 0.0 0 0 ? R 09:41 0:01 [rcu_sched] +root 10 0.0 0.0 0 0 ? S< 09:41 0:00 [lru-add-drain] +root 11 0.0 0.0 0 0 ? S 09:41 0:00 [watchdog/0] +root 13 0.0 0.0 0 0 ? S 09:41 0:00 [kdevtmpfs] +``` + +#### 7. top 命令 + +top 命令用于动态地监视进程活动与系统负载等信息,其格式为 top。 + +```shell +top - 10:59:52 up 1:18, 2 users, load average: 0.00, 0.01, 0.05 +Tasks: 90 total, 2 running, 88 sleeping, 0 stopped, 0 zombie +%Cpu(s): 0.7 us, 0.3 sy, 0.0 ni, 99.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st +KiB Mem : 1015508 total, 586988 free, 116684 used, 311836 buff/cache +KiB Swap: 1048572 total, 1048572 free, 0 used. 730224 avail Mem + + PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND + 1890 root 20 0 161840 2164 1552 R 0.7 0.2 0:00.18 top + 1000 root 20 0 573816 19080 6060 S 0.3 1.9 0:01.53 tuned + 1812 root 20 0 0 0 0 S 0.3 0.0 0:01.92 kworker/0:0 + 1 root 20 0 128148 6736 4216 S 0.0 0.7 0:03.38 systemd + 2 root 20 0 0 0 0 S 0.0 0.0 0:00.00 kthreadd + 3 root 20 0 0 0 0 S 0.0 0.0 0:00.17 ksoftirqd/0 + 5 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 kworker/0:0H + 7 root rt 0 0 0 0 S 0.0 0.0 0:00.00 migration/0 + 8 root 20 0 0 0 0 S 0.0 0.0 0:00.00 rcu_bh + 9 root 20 0 0 0 0 R 0.0 0.0 0:01.09 rcu_sched + 10 root 0 -20 0 0 0 S 0.0 0.0 0:00.00 lru-add-dr +``` + +top命令执行结果的前5行为系统整体的统计信息,其所代表的含义如下。 + +> 第1行:系统时间、运行时间、登录终端数、系统负载(三个数值分别为1分钟、5分钟、15分钟内的平均值,数值越小意味着负载越低)。 +> +> 第2行:进程总数、运行中的进程数、睡眠中的进程数、停止的进程数、僵死的进程数。 +> +> 第3行:用户占用资源百分比、系统内核占用资源百分比、改变过优先级的进程资源百分比、空闲的资源百分比等。其中数据均为CPU数据并以百分比格式显示,例如“97.1 id”意味着有97.1%的CPU处理器资源处于空闲。 +> +> 第4行:物理内存总量、内存使用量、内存空闲量、作为内核缓存的内存量。 +> +> 第5行:虚拟内存总量、虚拟内存使用量、虚拟内存空闲量、已被提前加载的内存量。 + +#### 8. pidof 命令 + +pidof 命令用于查询某个指定服务进程的 PID 值,格式为“pidof \[参数][服务名称]”。 + +#### 9. kill命令 + +kill 命令用于终止某个指定 PID 的服务进程,格式为“kill \[参数][进程 PID]”。 + +#### 10. killall 命令 + +killall 命令用于终止某个指定名称的服务所对应的全部进程,格式为:“killall \[参数][服务名称]”。 + + + +### 2.4 系统状态检测命令 + +#### 1. ifconfig 命令 + +ifconfig命令用于获取网卡配置与网络状态等信息,格式为“ifconfig \[网络设备][参数]”。 + +#### 2. uname 命令 + +uname命令用于查看系统内核与系统版本等信息,格式为“uname [-a]”。 + +在使用uname命令时,一般会固定搭配上-a参数来完整地查看当前系统的内核名称、主机名、内核发行版本、节点名、系统时间、硬件名称、硬件平台、处理器类型以及操作系统名称等信息。 + +```shell +[root@linuxprobe ~]# uname -a +Linux linuxprobe.com 3.10.0-123.el7.x86_64 #1 SMP Mon May 5 11:16:57 EDT 2017 x86_64 x86_64 x86_64 GNU/Linux +``` + +顺带一提,如果要查看当前系统版本的详细信息,则需要查看redhat-release文件,其命令以及相应的结果如下: + +```shell +[root@linuxprobe ~]# cat /etc/redhat-release +Red Hat Enterprise Linux Server release 7.0 (Maipo) +``` + +#### 3. uptime 命令 + +uptime用于查看系统的负载信息。 + +显示**当前系统时间**、**系统已运行时间**、**启用终端数量**以及**平均负载值**等信息。平均负载值指的是系统在最近1分钟、5分钟、15分钟内的压力情况(下面加粗的信息部分);负载值越低越好,尽量不要长期超过1,在生产环境中不要超过5。 + +```shell +[root@linuxprobe ~]# uptime +22:49:55 up 10 min, 2 users, load average: 0.01, 0.19, 0.18 +``` + +#### 4. free 命令 + +free 用于显示当前系统中内存的使用量信息,格式为“free [-h]”。 + +**swap**全称为swap place,即交换区,当内存不够的时候,被踢出的进程被暂时存储到交换区。当需要这条被踢出的进程的时候,就从交换区重新加载到内存,否则它不会主动交换到真实内存中。 + +**表:执行free -h命令后的输出信息** + +| | 内存总量 | 已用量 | 可用量 | 进程共享的内存量 | 磁盘缓存的内存量 | 缓存的内存量 | +| ------------------ | -------- | ------ | ------ | ---------------- | ---------------- | ------------ | +| | total | used | free | shared | buffers | cached | +| Mem: | 1.8G | 1.3G | 542M | 9.8M | 1.6M | 413M | +| -/+ buffers/cache: | | 869M | 957M | | | | +| Swap: | 2.0G | 0B | 2.0G | | | | + +#### 5. who 命令 + +who用于查看当前登入主机的用户终端信息,格式为“who [参数]”。 + +``` +[root@linuxprobe ~]# who +``` + +**表: 执行who命令的结果** + +| 登陆的用户名 | 终端设备 | 登陆到系统的时间 | +| ------------ | -------- | --------------------- | +| root | :0 | 2017-08-24 17:52 (:0) | +| root | pts/0 | 2017-08-24 17:52 (:0) | + +#### 6. last 命令 + +last 命令用于查看所有系统的登录记录,格式为“last [参数]”。 + +#### 7. history 命令 + +history命令用于显示历史执行过的命令,格式为“history [-c]”。 + +执行history命令能显示出当前用户在本地计算机中执行过的最近1000条命令记录。如果觉得1000不够用,还可以自义/etc/profile文件中的HISTSIZE变量值。在使用history命令时,如果使用-c参数则会清空所有的命令历史记录。还可以使用“!编码数字”的方式来重复执行某一次的命令。 + +#### 8. sosreport 命令 + +sosreport命令用于收集系统配置及架构信息并输出诊断文档,格式为sosreport。 + + + +### 2.5 工作目录切换命令 + +#### 1. pwd 命令 + +#### 2. cd 命令 + +“cd -”命令返回到上一次所处的目录 + +#### 3. ls命令 + + + +### 2.6 文本文件编辑命令 + +#### 1. cat 命令 + +cat 命令用于查看纯文本文件(内容较少的),格式为“cat \[选项][文件]”。 + +- -n 带行号显示。 + +#### 2. more 命令 + +more 命令用于查看纯文本文件(内容较多的),格式为“more [选项]文件”。 可以使用空格键或回车 +键向下翻页。 + +#### 3.head 命令 + +head 命令用于查看纯文本文档的前 N 行,格式为“head \[选项][文件]”。 + +```shell +head -n 20 initial-setup-ks.cfg +``` + +#### 4. tail 命令 + +tail 命令用于查看纯文本文档的后 N 行或持续刷新内容,格式为“tail \[选项][文件]”。 + +```shell +head -n 20 initial-setup-ks.cfg +``` + +tail 命令最强悍的功能是可以持续刷新一个文件的内容,当想要实时查看最新日志文件时,这特别有用,此时的命令格式为“tail -f 文件名”。 + +#### 5. tr 命令 + +tr 命令用于替换文本文件中的字符,格式为“tr \[原始字符][目标字符]”。 + +```shell +cat anaconda-ks.cfg | tr [a-z] [A-Z] +``` + +#### 6. wc 命令 + +wc 命令用于统计指定文本的行数、字数、字节数,格式为“wc [参数] 文本”。 + +| 参数 | 作用 | +| ---- | ------------ | +| -l | 只显示行数 | +| -w | 只显示单词数 | +| -c | 只显示字节数 | + +#### 7. stat 命令 + +stat 命令用于查看文件的具体存储信息和时间等信息,格式为“stat 文件名称”。 + +```shell +stat anaconda-ks.cfg +``` + +#### 8. cut命令 + +cut 命令用于按“列”提取文本字符,格式为“cut [参数] 文本”。 + +- -f 参数来设置需要看的列数, +- -d 参数来设置**间隔符号** + +```shell +cut -d: -f1 /etc/passwd +``` + +#### 9. diff 命令 + +diff 命令用于比较多个文本文件的差异,格式为“diff [参数] 文件”。 + +- 使用-c 参数来详细比较出多个文件的差异之处 +- 使用--brief 参数来确认两个文件是否不同 + +```shell +diff --brief diff_A.txt diff_B.txt +diff -c diff_A.txt diff_B.txt +``` + +### 2.7 文件目录管理命令 + +#### 1. touch 命令 + +touch 命令用于创建空白文件或**设置文件的时间**,格式为“touch \[选项][文件]”。 + + **表: touch命令的参数及其作用** + +| 参数 | 作用 | +| ---- | ------------------------- | +| -a | 仅修改“读取时间”(atime) | +| -m | 仅修改“修改时间”(mtime) | +| -d | 同时修改atime与mtime | + +```shell +touch -d "2017-05-04 15:44" anaconda-ks.cfg +``` + +#### 2. mkdir 命令 + +mkdir 命令用于创建空白的目录,格式为“mkdir [选项] 目录”。 + +- -p 参数来递归创建出具有嵌套叠层关系的文件目录 + +```shell +mkdir -p a/b/c/d/e +``` + +#### 3. cp 命令 + +cp 命令用于复制文件或目录,格式为“cp [选项] 源文件 目标文件”。 + +**表: cp命令的参数及其作用** + +| 参数 | 作用 | +| ---- | -------------------------------------------- | +| -p | 保留原始文件的属性 | +| -d | 若对象为“链接文件”,则保留该“链接文件”的属性 | +| -r | 递归持续复制(用于目录) | +| -i | 若目标文件存在则询问是否覆盖 | +| -a | 相当于-pdr(p、d、r为上述参数) | + +#### 4. mv 命令 + +mv 命令用于剪切文件或将文件重命名,格式为“mv [选项] 源文件 [目标路径|目标文件名]”。 如果在同一个目录中对一个文件进行剪切操作,其实也就是对其进行重命名: + +```shell +mv x.log linux.log +``` + +#### 5. rm 命令 + +rm 命令用于删除文件或目录,格式为“rm [选项] 文件”。 + +- -f 参数来强制删除 +- -r 参数递归删除文件夹 + +#### 6. dd 命令 + +dd 命令用于按照指定大小和个数的数据块来复制文件或转换文件,格式为“dd [参数]”。 + +**表: dd命令的参数及其作用** + +| 参数 | 作用 | +| ----- | -------------------- | +| if | 输入的文件名称 | +| of | 输出的文件名称 | +| bs | 设置每个“块”的大小 | +| count | 设置要复制“块”的个数 | + +用 dd 命令从/dev/zero 设备文件中取出一个大小为 560MB 的数据块,然后保存成名为 560_file 的文件 + +```shell +dd if=/dev/zero of=560_file count=1 bs=560M +``` + +在 Linux 系统中可以直接使用 dd 命令来压制出光盘镜像文件,将它变成一个可立即使用的 iso 镜像 + +```shell +dd if=/dev/cdrom of=RHEL-server-7.0-x86_64-LinuxProbe.Com.iso +``` + +#### 7. file命令 + +file 命令用于查看文件的类型,格式为“file 文件名”。 + +```shell +file anaconda-ks.cfg +``` + + + +### 2.8 打包压缩与搜索命令 + +#### 1. tar 命令 + +tar 命令用于对文件进行打包压缩或解压,格式为“tar \[选项][文件]”。 + +**表:tar命令的参数及其作用** + +| 参数 | 作用 | +| ---- | ---------------------- | +| -c | 创建压缩文件 | +| -x | 解开压缩文件 | +| -t | 查看压缩包内有哪些文件 | +| -z | 用Gzip压缩或解压 | +| -j | 用bzip2压缩或解压 | +| -v | 显示压缩或解压的过程 | +| -f | 目标文件名 | +| -p | 保留原始的权限与属性 | +| -P | 使用绝对路径来压缩 | +| -C | 指定解压到的目录 | + +常用打包命令 : “**tar -czvf 压缩包名称.tar.gz 要打包的目录**”; + +常用解压命令 :“**tar -xzvf 压缩包名称.tar.gz**”。 + + + +#### 2. grep 命令 + +grep 命令用于在文本中执行关键词搜索,并显示匹配的结果,格式为“grep \[选项][文件]”。 + +**表: grep命令的参数及其作用** + +| 参数 | 作用 | +| ---- | ---------------------------------------------- | +| -b | 将可执行文件(binary)当作文本文件(text)来搜索 | +| -c | 仅显示找到的行数 | +| -i | 忽略大小写 | +| -n | 显示行号 | +| -v | 反向选择——仅列出没有“关键词”的行。 | + +```shell +[root@localhost dic]# cat index.html |grep html -n +1: +3: +75: +``` + +#### 3. find命令 + +find 命令用于按照指定条件来查找文件,格式为“find [查找路径] 寻找条件 操作”。 + +**表:find命令中的参数以及作用** + +| 参数 | 作用 | +| ------------------ | ------------------------------------------------------------ | +| -name | 匹配名称 | +| -perm | 匹配权限(mode为完全匹配,-mode为包含即可) | +| -user | 匹配所有者 | +| -group | 匹配所有组 | +| -mtime -n +n | 匹配修改内容的时间(-n指n天以内,+n指n天以前) | +| -atime -n +n | 匹配访问文件的时间(-n指n天以内,+n指n天以前) | +| -ctime -n +n | 匹配修改文件权限的时间(-n指n天以内,+n指n天以前) | +| -nouser | 匹配无所有者的文件 | +| -nogroup | 匹配无所有组的文件 | +| -newer f1 !f2 | 匹配比文件f1新但比f2旧的文件 | +| --type b/d/c/p/l/f | 匹配文件类型(后面的字幕字母依次表示块设备、目录、字符设备、管道、链接文件、文本文件) | +| -size | 匹配文件的大小(+50KB为查找超过50KB的文件,而-50KB为查找小于50KB的文件) | +| -prune | 忽略某个目录 | +| -exec …… {}\; | 后面可跟用于进一步处理搜索结果的命令(下文会有演示) | + +获取到指定目录中所有以 host 开头的文件列表,可以执行如下命令: + +```shell +find /etc -name "host*" -print +``` + +如果要在整个系统中搜索权限中包括 SUID 权限的所有文件,只需使用-4000 即可: + +```shell +find / -perm -4000 -print +``` + + + +## 第三章 管道符、重定向与环境变量 + +### 3.1 输入输出重定向 + +> 标准输入重定向(STDIN,文件描述符为0):默认从键盘输入,也可从其他文件或命令中输入。 +> +> 标准输出重定向(STDOUT,文件描述符为1):默认输出到屏幕。 +> +> 错误输出重定向(STDERR,文件描述符为2):默认输出到屏幕。 + +**对于输入重定向来讲,用到的符号及其作用如表3-1所示。** + +表3-1 输入重定向中用到的符号及其作用 + +| 符号 | 作用 | +| -------------------- | -------------------------------------------- | +| 命令 < 文件 | 将文件作为命令的标准输入 | +| 命令 << 分界符 | 从标准输入中读入,直到遇见分界符才停止 | +| 命令 < 文件1 > 文件2 | 将文件1作为命令的标准输入并将标准输出到文件2 | + +**对于输出重定向来讲,用到的符号及其作用如表3-2所示。** + +表3-2 输出重定向中用到的符号及其作用 + +| 符号 | 作用 | +| ------------------------------------- | ------------------------------------------------------------ | +| 命令 > 文件 | 将标准输出重定向到一个文件中(清空原有文件的数据) | +| 命令 2> 文件 | 将错误输出重定向到一个文件中(清空原有文件的数据) | +| 命令 >> 文件 | 将标准输出重定向到一个文件中(追加到原有内容的后面) | +| 命令 2>> 文件 | 将错误输出重定向到一个文件中(追加到原有内容的后面) | +| 命令 >> 文件 2>&1 或 命令 &>> 文件 | 将标准输出与错误输出共同写入到文件中(追加到原有内容的后面) | + +```shell +man bash > readme.txt +``` + +### 3.2 管道运算符 + +管道命令符的作用也可以用一句话来概括“**把前一个命令原本要输出到屏幕的标准正常数据当作是后一个命令的标准输入**”。 + +### 3.3 命令通配符 + +- 星号(*)代表匹配零个或多个字符; +- 问号(?)代表匹配单个字符; +- 中括号内加上数字[0-9]代表匹配 0~9 之间的单个数字的字符; +- 而中括号内加上字母[abc]则是代表匹配 a、 b、 c 三个字符中的任意一个字符 + +```shell + ls -l /dev/sda* + ls -l /dev/sda? + ls -l /dev/sda[0-9] + ls -l /dev/sda[135] +``` + +### 3.4 常用的转义字符 + +> 反斜杠(\):使反斜杠后面的一个变量变为单纯的字符串。 +> +> 单引号(''):转义其中所有的变量为单纯的字符串。 +> +> 双引号(""):保留其中的变量属性,不进行转义处理。 +> +> 反引号(``):把其中的命令执行后返回结果。 + +```shell +[root@linuxprobe ~]# PRICE=5 +[root@linuxprobe ~]# echo "Price is $PRICE" +Price is 5 + +[root@linuxprobe ~]# echo "Price is \$$PRICE" +Price is $5 + +[root@linuxprobe ~]# echo `uname -a` +Linux linuxprobe.com 3.10.0-123.el7.x86_64 #1 SMP Mon May 5 11:16:57 EDT 2017 +x86_64 x86_64 x86_64 GNU/Linux +``` + +### 3.5 重要的环境变量 + +表3-3 Linux系统中最重要的10个环境变量 + +| 变量名称 | 作用 | +| ------------ | -------------------------------- | +| HOME | 用户的主目录(即家目录) | +| SHELL | 用户在使用的Shell解释器名称 | +| HISTSIZE | 输出的历史命令记录条数 | +| HISTFILESIZE | 保存的历史命令记录条数 | +| MAIL | 邮件保存路径 | +| LANG | 系统语言、语系名称 | +| RANDOM | 生成一个随机数字 | +| PS1 | Bash解释器的提示符 | +| PATH | 定义解释器搜索用户执行命令的路径 | +| EDITOR | 用户默认的文本编辑器 | + +```shell + echo $HOME +``` + +设置一个名称为 WORKDIR 的变量 : + +```shell +[root@linuxprobe ~]# mkdir /home/workdir +[root@linuxprobe ~]# WORKDIR=/home/workdir +[root@linuxprobe ~]# cd $WORKDIR +[root@linuxprobe workdir]# pwd +/home/workdir +``` + +但是,这样的变量不具有全局性,作用范围也有限,默认情况下不能被其他用户使用。如果工作需要,可以使用 **export 命令**将其提升为全局变量 。 + + + +## 第四章 Vim 编辑器与 shell 脚本命令 + +### 4.1 vim编辑器 + +> 命令模式:控制光标移动,可对文本进行复制、粘贴、删除和查找等工作。 +> +> 输入模式:正常的文本录入。 +> +> 末行模式:保存或退出文档,以及设置编辑环境。 + +表4-1 Vim中常用的命令 + +| 命令 | 作用 | +| ---- | -------------------------------------------------- | +| dd | 删除(剪切)光标所在整行 | +| 5dd | 删除(剪切)从光标处开始的5行 | +| yy | 复制光标所在整行 | +| 5yy | 复制从光标处开始的5行 | +| n | 显示搜索命令定位到的下一个字符串 | +| N | 显示搜索命令定位到的上一个字符串 | +| u | 撤销上一步的操作 | +| p | 将之前删除(dd)或复制(yy)过的数据粘贴到光标后面 | + +表4-2 末行模式中可用的命令 + +| 命令 | 作用 | +| ------------- | ------------------------------------ | +| :w | 保存 | +| :q | 退出 | +| :q! | 强制退出(放弃对文档的修改内容) | +| :wq! | 强制保存退出 | +| :set nu | 显示行号 | +| :set nonu | 不显示行号 | +| :命令 | 执行该命令 | +| :整数 | 跳转到该行 | +| :s/one/two | 将当前光标所在行的第一个one替换成two | +| :s/one/two/g | 将当前光标所在行的所有one替换成two | +| :%s/one/two/g | 将全文中的所有one替换成two | +| ?字符串 | 在文本中从下至上搜索该字符串 | +| /字符串 | 在文本中从上至下搜索该字符串 | + +使用 a、 i、 o 三个键从命令模式切换到输入模式。其中, a 键与 i 键分别是在光标后面一位和光标当前位置切换到输入模式,而 **o 键则是在光标的下面再创建一个空行**,此时可敲击 a 键进入到编辑器的输入模式。 + +### 4.2 编写的脚本 + +#### 4.2.1 编写简单的脚本 + +```shell +[root@linuxprobe ~]# vim example.sh +#!/bin/bash +pwd +ls -al +``` + +#### 4.2.2 接受用户参数 + +- $0 对应的是当前 Shell 脚本程序的名称, +- $# 对应的是总共有几个参数, +- $* 对应的是所有位置的参数值, +- $? 对应的是显示上一次命令的执行返回值, +- 而$1、$2、 $3……则分别对应着第 N 个位置的参数值 + +```shell +[root@linuxprobe ~]# vim example.sh +#!/bin/bash +echo "当前脚本名称为$0" +echo "总共有$#个参数,分别是$*。" +echo "第1个参数为$1,第5个为$5。" +[root@linuxprobe ~]# sh example.sh one two three four five six +当前脚本名称为example.sh +总共有6个参数,分别是one two three four five six。 +第1个参数为one,第5个为five。 +``` + +**按照测试对象来划分,条件测试语句可以分为4种**: + +> 文件测试语句; +> +> 逻辑测试语句; +> +> 整数值比较语句; +> +> 字符串比较语句。 + +表4-3 文件测试所用的参数 + +| 操作符 | 作用 | +| ------ | -------------------------- | +| -d | 测试文件是否为目录类型 | +| -e | 测试文件是否存在 | +| -f | 判断是否为一般文件 | +| -r | 测试当前用户是否有权限读取 | +| -w | 测试当前用户是否有权限写入 | +| -x | 测试当前用户是否有权限执行 | + +表4-4 可用的整数比较运算符 + +| 操作符 | 作用 | +| ------ | -------------- | +| -eq | 是否等于 | +| -ne | 是否不等于 | +| -gt | 是否大于 | +| -lt | 是否小于 | +| -le | 是否等于或小于 | +| -ge | 是否大于或等于 | + +表4-5 常见的字符串比较运算符 + +| 操作符 | 作用 | +| ------ | ---------------------- | +| = | 比较字符串内容是否相同 | +| != | 比较字符串内容是否不同 | +| -z | 判断字符串内容是否为空 | + +下面使用文件测试语句来判断/etc/fstab是否为一个目录类型的文件,然后通过Shell解释器的内设$?变量显示上一条命令执行后的返回值。如果返回值为0,则目录存在;如果返回值为非零的值,则意味着目录不存在: + +```shell +[root@linuxprobe ~]# [ -d /etc/fstab ] +[root@linuxprobe ~]# echo $? +1 +``` + +## 4.3 流程控制语句 + +**结构**: + +```shell +# if单分支结构 +if 条件测试操作 + then 命令序列 +fi + + +# if双分支结构 +if 条件测试操作 + then 命令序列1 + else 命令序列2 +fi + + +# if多分支结构 +if 条件测试操作1 ; + then 命令序列1 +elif 条件测试操作2 ; + then 命令序列2 +else + 命令序列3 +fi + + +# for 条件循环语句 +for 变量名 in 取值列表 +do + 命令序列 +done + + +# while 条件循环语句 +while 条件测试操作 +do + 命令序列 +done + + +# case 条件测试语句 +case 变量值 in +模式1) + 命令序列1 + ;; +模式2) + 命令序列2 + ;; + ..... +*) + 默认命令序列 +esac +``` + +**示例**: + +```shell +# if单分支结构 +#!/bin/bash +DIR="/media/cdrom" +if [ ! -e $DIR ] +then +mkdir -p $DIR +fi + + +# if双分支结构 +#!/bin/bash +ping -c 3 -i 0.2 -W 3 $1 &> /dev/null +if [ $? -eq 0 ] +then +echo "Host $1 is On-line." +else +echo "Host $1 is Off-line." +fi + + +# if多分支结构 +#!/bin/bash +read -p "Enter your score(0-100):" GRADE +if [ $GRADE -ge 85 ] && [ $GRADE -le 100 ] ; then +echo "$GRADE is Excellent" +elif [ $GRADE -ge 70 ] && [ $GRADE -le 84 ] ; then +echo "$GRADE is Pass" +else +echo "$GRADE is Fail" +fi + + +# for 条件循环语句 +#!/bin/bash +read -p "Enter The Users Password : " PASSWD +for UNAME in `cat users.txt` +do +id $UNAME &> /dev/null +if [ $? -eq 0 ] +then +echo "Already exists" +else +useradd $UNAME &> /dev/null +echo "$PASSWD" | passwd --stdin $UNAME &> /dev/null +if [ $? -eq 0 ] +then +echo "$UNAME , Create success" +else +echo "$UNAME , Create failure" +fi +fi +done + + +# while 条件循环语句 +#!/bin/bash +PRICE=$(expr $RANDOM % 1000) +TIMES=0 +echo "商品实际价格为0-999之间,猜猜看是多少?" +while true +do +read -p "请输入您猜测的价格数目:" INT +let TIMES++ +if [ $INT -eq $PRICE ] ; then +echo "恭喜您答对了,实际价格是 $PRICE" +echo "您总共猜测了 $TIMES 次" +exit 0 +elif [ $INT -gt $PRICE ] ; then +echo "太高了!" +else +echo "太低了!" +fi +done + + +# case 条件测试语句 +#!/bin/bash +read -p "请输入一个字符,并按Enter键确认:" KEY +case "$KEY" in +[a-z]|[A-Z]) +echo "您输入的是 字母。" +;; +[0-9]) +echo "您输入的是 数字。" +;; +*) +echo "您输入的是 空格、功能键或其他控制字符。" +esac +``` + + + +## 4.4 计划任务服务程序 + +> 一次性计划任务:今晚11点30分开启网站服务。 +> +> 长期性计划任务:每周一的凌晨3点25分把/home/wwwroot目录打包备份为backup.tar.gz。 + +**一次性计划任务:** + +- 设置一次性计划任务 “at 时间” +- 查看已设置好但还未执行的一次性计划任务,可以使用“at -l”命令 +- 删除用“atrm 任务序号” + +```shell +[root@linuxprobe ~]# at 23:30 +at > systemctl restart httpd +at > 此处请同时按下Ctrl+d来结束编写计划任务 +job 3 at Mon Apr 27 23:30:00 2015 +[root@linuxprobe ~]# at -l +3 Mon Apr 27 23:30:00 2016 a root +[root@linuxprobe ~]# echo "systemctl restart httpd" | at 23:30 +job 4 at Mon Apr 27 23:30:00 2015 +[root@linuxprobe ~]# at -l +3 Mon Apr 27 23:30:00 2016 a root +4 Mon Apr 27 23:30:00 2016 a root +[root@linuxprobe ~]# atrm 3 +[root@linuxprobe ~]# at -l +4 Mon Apr 27 23:30:00 2016 a root +``` + +**周期性任务**: + +- 创建、编辑计划任务的命令为“crontab -e” +- 查看当前计划任务的命令为“crontab -l” +- 删除某条计划任务的命令为“crontab -r” +- 另外,如果您是以管理员的身份登录的系统,还可以在 crontab 命令中加上-u 参数来编辑他人的计划任务 + +表4-6 使用crond设置任务的参数字段说明 + +| 字段 | 说明 | +| ---- | ---------------------------------------- | +| 分钟 | 取值为0~59的整数 | +| 小时 | 取值为0~23的任意整数 | +| 日期 | 取值为1~31的任意整数 | +| 月份 | 取值为1~12的任意整数 | +| 星期 | 取值为0~7的任意整数,其中0与7均为星期日 | +| 命令 | 要执行的命令或程序脚本 | + +**在crond服务的计划任务参数中,所有命令一定要用绝对路径的方式来写,如果不知道绝对路径,请用whereis命令进行查询** + +```shell +[root@linuxprobe ~]# crontab -e +no crontab for root - using an empty one +crontab: installing new crontab +[root@linuxprobe ~]# crontab -l +#假设在每周一、三、五的凌晨3点25分,都需要使用tar命令把某个网站的数据目录进行打包处理,使其作为一个备份文件。 +25 3 * * 1,3,5 /usr/bin/tar -czvf backup.tar.gz /home/wwwroot + + +[root@linuxprobe ~]# whereis rm +rm: /usr/bin/rm /usr/share/man/man1/rm.1.gz /usr/share/man/man1p/rm.1p.gz +[root@linuxprobe ~]# crontab -e +crontab: installing new crontab +[root@linuxprobe ~]# crontab -l +#每周一至周五的凌晨1点钟自动清空/tmp目录内的所有文件 +25 3 * * 1,3,5 /usr/bin/tar -czvf backup.tar.gz /home/wwwroot +0 1 * * 1-5 /usr/bin/rm -rf /tmp/* +``` + +**计划任务中的“分”字段必须有数值,绝对不能为空或是*号,而“日”和“星期”字段不能同时使用,否则就会发生冲突。** + + + +## 第五章 用户身份与文件权限 + +### 5.1 用户身份与能力 + +> 管理员UID为0:系统的管理员用户。 +> +> 系统用户UID为1~999: Linux系统为了避免因某个服务程序出现漏洞而被黑客提权至整台服务器,默认服务程序会有独立的系统用户负责运行,进而有效控制被破坏范围。 +> +> 普通用户UID从1000开始:是由管理员创建的用于日常工作的用户。 + +#### 1. useradd 命令 + +useradd命令用于创建新的用户,格式为“useradd [选项] 用户名”。 + +可以使用useradd命令创建用户账户。使用该命令创建用户账户时,默认的用户家目录会被存放在/home目录中,默认的Shell解释器为/bin/bash,而且默认会创建一个与该用户同名的基本用户组。这些默认设置可以根据表5-1中的useradd命令参数自行修改。 + +表5-1 useradd命令中的用户参数以及作用 + +| 参数 | 作用 | +| ---- | ---------------------------------------- | +| -d | 指定用户的家目录(默认为/home/username) | +| -e | 账户的到期时间,格式为YYYY-MM-DD. | +| -u | 指定该用户的默认UID | +| -g | 指定一个初始的用户基本组(必须已存在) | +| -G | 指定一个或多个扩展用户组 | +| -N | 不创建与用户同名的基本用户组 | +| -s | 指定该用户的默认Shell解释器 | + +```shell +[root@linuxprobe ~]# useradd -d /home/linux -u 8888 -s /sbin/nologin linuxprobe +[root@linuxprobe ~]# id linuxprobe +uid=8888(linuxprobe) gid=8888(linuxprobe) groups=8888(linuxprobe) +``` + +#### 2. groupadd 命令 + +groupadd 命令用于创建用户组,格式为“groupadd [选项] 群组名”。 + +#### 3. usermod 命令 + +usermod命令用于修改用户的属性,格式为“usermod [选项] 用户名”。 + +前文曾反复强调,Linux系统中的一切都是文件,因此在系统中创建用户也就是修改配置文件的过程。用户的信息保存在/etc/passwd文件中,可以直接用文本编辑器来修改其中的用户参数项目,也可以用usermod命令修改已经创建的用户信息。 + +表5-2 usermod命令中的参数及作用 + +| 参数 | 作用 | +| ----- | ------------------------------------------------------------ | +| -c | 填写用户账户的备注信息 | +| -d -m | 参数-m与参数-d连用,可重新指定用户的家目录并自动把旧的数据转移过去 | +| -e | 账户的到期时间,格式为YYYY-MM-DD | +| -g | 变更所属用户组 | +| -G | 变更扩展用户组 | +| -L | 锁定用户禁止其登录系统 | +| -U | 解锁用户,允许其登录系统 | +| -s | 变更默认终端 | +| -u | 修改用户的UID | + +```shell +[root@linuxprobe ~]# usermod -u 8888 linuxprobe +[root@linuxprobe ~]# id linuxprobe +uid=8888(linuxprobe) gid=1000(linuxprobe) groups=1000(linuxprobe),0(root) +``` + +#### 4. passwd 命令 + +passwd命令用于修改用户密码、过期时间、认证信息等,格式为“passwd \[选项][用户名]”。 + +表5-3 passwd命令中的参数以及作用 + +| 参数 | 作用 | +| ------- | ------------------------------------------------------------ | +| -l | 锁定用户,禁止其登录 | +| -u | 解除锁定,允许用户登录 | +| --stdin | 允许通过标准输入修改用户密码,如echo "NewPassWord" \| passwd --stdin Username | +| -d | 使该用户可用空密码登录系统 | +| -e | 强制用户在下次登录时修改密码 | +| -S | 显示用户的密码是否被锁定,以及密码所采用的加密算法名称 | + +```shell +[root@linuxprobe ~]# passwd -l linuxprobe +Locking password for user linuxprobe. +passwd: Success +[root@linuxprobe ~]# passwd -S linuxprobe +linuxprobe LK 2017-12-26 0 99999 7 -1 (Password locked.) +[root@linuxprobe ~]# passwd -u linuxprobe +Unlocking password for user linuxprobe. +passwd: Success +[root@linuxprobe ~]# passwd -S linuxprobe +linuxprobe PS 2017-12-26 0 99999 7 -1 (Password set, SHA512 crypt.) +``` + +#### 5. userdel 命令 + +userdel命令用于删除用户,格式为“userdel [选项] 用户名”。 + +表5-4 userdel命令的参数以及作用 + +| 参数 | 作用 | +| ---- | ------------------------ | +| -f | 强制删除用户 | +| -r | 同时删除用户及用户家目录 | + +### 5.2 文件权限与归属 + +> -:普通文件。 +> +> d:目录文件。 +> +> l:链接文件。 +> +> b:块设备文件。 +> +> c:字符设备文件。 +> +> p:管道文件。 + +

+ +

+ +### 5.3 文件的特殊权限 + +#### 5.3.1 SUID + +SUID 是一种对二进制程序进行设置的特殊权限,可以让二进制程序的执行者临时拥有属主的权限(仅对拥有执行权限的二进制程序有效) + +#### 5.3.2 SGID + +SGID主要实现如下两种功能: + +> 让执行者临时拥有属组的权限(对拥有执行权限的二进制程序进行设置); +> +> 在某个目录中创建的文件自动继承该目录的用户组(只可以对目录进行设置)。 + +##### 1.chmod命令 + +chmod是一个非常实用的命令,能够用来设置文件或目录的**权限**,格式为“chmod [参数] 权限 文件或目录名称”。 + +##### 2.chown命令 + +chown设置文件或目录的**所有者和所属组**,其格式为“chown [参数] 所有者:所属组 文件或目录名称”。 + +```shell +[root@linuxprobe ~]# chmod 760 test +[root@linuxprobe ~]# ls -l test +-rwxrw----. 1 linuxprobe root 15 Feb 11 11:50 test +[root@linuxprobe ~]# chown root:bin test +[root@linuxprobe ~]# ls -l test +-rwxrw----. 1 root bin 15 Feb 11 11:50 test +``` + +#### 5.3.3 SBIT + +SBIT特殊权限位可确保用户只能删除自己的文件,而不能删除其他用户的文件。换句话说,当对某个目录设置了SBIT粘滞位权限后,那么该目录中的文件就只能被其所有者执行删除操作了。 + +### 5.4 文件的隐藏属性 + +#### 1. chattr命令 + +chattr命令用于设置文件的隐藏权限,格式为“chattr [参数] 文件”。如果想要把某个隐藏功能添加到文件上,则需要在命令后面追加“+参数”,如果想要把某个隐藏功能移出文件,则需要追加“-参数”。 + +表5-6 chattr命令中用于隐藏权限的参数及其作用 + +| 参数 | 作用 | +| ---- | ------------------------------------------------------------ | +| i | 无法对文件进行修改;若对目录设置了该参数,则仅能修改其中的子文件内容而不能新建或删除文件 | +| a | 仅允许补充(追加)内容,无法覆盖/删除内容(Append Only) | +| S | 文件内容在变更后立即同步到硬盘(sync) | +| s | 彻底从硬盘中删除,不可恢复(用0填充原文件所在硬盘区域) | +| A | 不再修改这个文件或目录的最后访问时间(atime) | +| b | 不再修改文件或目录的存取时间 | +| D | 检查压缩文件中的错误 | +| d | 使用dump命令备份时忽略本文件/目录 | +| c | 默认将文件或目录进行压缩 | +| u | 当删除该文件后依然保留其在硬盘中的数据,方便日后恢复 | +| t | 让文件系统支持尾部合并(tail-merging) | +| x | 可以直接访问压缩文件中的内容 | + +```shell +[root@linuxprobe ~]# echo "for Test" > linuxprobe +[root@linuxprobe ~]# chattr +a linuxprobe +[root@linuxprobe ~]# rm linuxprobe +rm: remove regular file ‘linuxprobe’? y +rm: cannot remove ‘linuxprobe’: Operation not permitted +``` + +#### 2. lsattr 命令 + +lsattr命令用于显示文件的隐藏权限,格式为“lsattr [参数] 文件”。 + +```shell +[root@linuxprobe ~]# lsattr linuxprobe +-----a---------- linuxprobe +[root@linuxprobe ~]# chattr -a linuxprobe +[root@linuxprobe ~]# lsattr linuxprobe +---------------- linuxprobe +[root@linuxprobe ~]# rm linuxprobe +rm: remove regular file ‘linuxprobe’? y +``` + +### 5.5 文件访问控制列表 + +#### 5.5.1 setfacl 命令 + +setfacl 命令用于管理文件的 ACL 规则,格式为“setfacl [参数] 文件名称”。文件的 ACL提供的是在所有者、所属组、其他人的读/写/执行权限之外的特殊权限控制,使用 setfacl 命令可以**针对单一用户或用户组、单一文件或目录来进行读/写/执行权限的控制**。其中: + +- 针对目录文件需要使用-R 递归参数; +- 针对普通文件则使用-m 参数; +- 如果想要删除某个文件的 ACL,则可以使用-b 参数。 + +下面来设置用户在/root 目录上的权限: + +```shell +[root@linuxprobe ~]# setfacl -Rm u:linuxprobe:rwx /root +``` + +#### 5.5.2 getfacl 命令 + +getfacl命令用于显示文件上设置的ACL信息,格式为“getfacl 文件名称”。 + +### 5.6 su命令与sudo服务 + +su 命令与用户名之间有一个减号(-),这意味着完全切换到新的用户,**即把环境变量信息也变更为新用户的相应信息**,而不是保留原始的信息。强烈建议在切换用户身份时添加这个减号(-)。 + +sudo 命令用于给普通用户提供额外的权限来完成原本 root 管理员才能完成的任务,格式为“sudo [参数] 命令名称”。 + +表5-7 sudo服务中的可用参数以及作用 + +| 参数 | 作用 | +| ---------------- | ------------------------------------------------------ | +| -h | 列出帮助信息 | +| -l | 列出当前用户可执行的命令 | +| -u 用户名或UID值 | 以指定的用户身份执行命令 | +| -k | 清空密码的有效时间,下次执行sudo时需要再次进行密码验证 | +| -b | 在后台执行指定的命令 | +| -p | 更改询问密码的提示语 | + +## 第六章 存储结构与磁盘划分 + +### 6.1 一切从“/”开始 + +表6-1 Linux系统中常见的目录名称以及相应内容 + +| 目录名称 | 应放置文件的内容 | +| ----------- | --------------------------------------------------------- | +| /boot | 开机所需文件—内核、开机菜单以及所需配置文件等 | +| /dev | 以文件形式存放任何设备与接口 | +| /etc | 配置文件 | +| /home | 用户主目录 | +| /bin | 存放单用户模式下还可以操作的命令 | +| /lib | 开机时用到的函数库,以及/bin与/sbin下面的命令要调用的函数 | +| /sbin | 开机过程中需要的命令 | +| /media | 用于挂载设备文件的目录 | +| /opt | 放置第三方的软件 | +| /root | 系统管理员的家目录 | +| /srv | 一些网络服务的数据文件目录 | +| /tmp | 任何人均可使用的“共享”临时目录 | +| /proc | 虚拟文件系统,例如系统内核、进程、外部设备及网络状态等 | +| /usr/local | 用户自行安装的软件 | +| /usr/sbin | Linux系统开机时不会使用到的软件/命令/脚本 | +| /usr/share | 帮助与说明文件,也可放置共享文件 | +| /var | 主要存放经常变化的文件,如日志 | +| /lost+found | 当文件系统发生错误时,将一些丢失的文件片段存放在这里 | + +### 6.2 物理设备的命名规则 + +表6-2 常见的硬件设备及其文件名称 + +| 硬件设备 | 文件名称 | +| ------------- | ------------------ | +| IDE设备 | /dev/hd[a-d] | +| SCSI/SATA/U盘 | /dev/sd[a-p] | +| 软驱 | /dev/fd[0-1] | +| 打印机 | /dev/lp[0-15] | +| 光驱 | /dev/cdrom | +| 鼠标 | /dev/mouse | +| 磁带机 | /dev/st0或/dev/ht0 | + +由于现在的IDE设备已经很少见了,所以一般的硬盘设备都会是以“/dev/sd”开头的。而一台主机上可以有多块硬盘,因此系统采用a~p来代表16块不同的硬盘(默认从a开始分配),而且硬盘的分区编号也很有讲究: + +> 主分区或扩展分区的编号从1开始,到4结束; +> +> 逻辑分区从编号5开始。 + +

+ +### 6.4 挂载硬件设备 + +当用户需要使用硬盘设备或分区中的数据时,需要先将其与一个已存在的目录文件进行关联,而这个关联动作就是“挂载”。 + +#### 1.挂载 + +mount命令用于挂载文件系统,格式为“mount 文件系统 挂载目录”。 + +表6-3 mount命令中的参数以及作用 + +| 参数 | 作用 | +| ---- | ------------------------------------------------------------ | +| -a | 挂载所有在/etc/fstab中定义的文件系统,会在执行后自动检查/etc/fstab文件中有无疏漏被挂载的设备文件,如果有,则进行自动挂载操作。 | +| -t | 指定文件系统的类型。 对于比较新的Linux系统来讲,一般不需要使用-t参数来指定文件系统的类型 | + +```shell +[root@linuxprobe ~]# mount /dev/sdb2 /backup +``` + +#### 2.永久挂载 + +如果想让硬件设备和目录永久地进行自动关联,就必须把挂载信息按照指定的填写格式“设备文件 挂载目录 格式类型 权限选项 是否备份 是否自检”(各字段的意义见表6-4)写入到**/etc/fstab**文件中。 + +表6-4 用于挂载信息的指定填写格式中,各字段所表示的意义 + +| 字段 | 意义 | +| -------- | ------------------------------------------------------------ | +| 设备文件 | 一般为设备的路径+设备名称,也可以写唯一识别码(UUID,Universally Unique Identifier) | +| 挂载目录 | 指定要挂载到的目录,需在挂载前创建好 | +| 格式类型 | 指定文件系统的格式,比如Ext3、Ext4、XFS、SWAP、iso9660(此为光盘设备)等 | +| 权限选项 | 若设置为defaults,则默认权限为:rw, suid, dev, exec, auto, nouser, async | +| 是否备份 | 若为1则开机后使用dump进行磁盘备份,为0则不备份 | +| 是否自检 | 若为1则开机后自动进行磁盘自检,为0则不自检 | + +如果想将文件系统为ext4的硬件设备/dev/sdb2在开机后自动挂载到/backup目录上,并保持默认权限且无需开机自检,就需要在/etc/fstab文件中写入下面的信息,这样在系统重启后也会成功挂载。 + +```shell +[root@linuxprobe ~]# vim /etc/fstab +# +# /etc/fstab +# Created by anaconda on Wed May 4 19:26:23 2017 +# +# Accessible filesystems, by reference, are maintained under '/dev/disk' +# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info +# +/dev/mapper/rhel-root / xfs defaults 1 1 +UUID=812b1f7c-8b5b-43da-8c06-b9999e0fe48b /boot xfs defaults 1 2 +/dev/mapper/rhel-swap swap swap defaults 0 0 +/dev/cdrom /media/cdrom iso9660 defaults 0 0 +# 永久挂载 +/dev/sdb2 /backup ext4 defaults 0 0 +``` + +#### 3.撤消挂载 + +umount命令用于撤销已经挂载的设备文件,格式为“umount [挂载点/设备文件]”。 + +```shell +[root@linuxprobe ~]# umount /dev/sdb2 +``` + +### 6.8 软硬链接连接方式 + +**硬链接(hard link)**:可以将它理解为一个“指向原始文件inode的指针”,系统不为它分配独立的inode和文件。所以,硬链接文件与原始文件其实是同一个文件,只是名字不同。我们每添加一个硬链接,该文件的inode连接数就会增加1;而且只有当该文件的inode连接数为0时,才算彻底将它删除。换言之,由于硬链接实际上是指向原文件inode的指针,因此即便原始文件被删除,依然可以通过硬链接文件来访问。需要注意的是,由于技术的局限性,我们不能跨分区对目录文件进行链接。 + +**软链接(也称为符号链接[symbolic link])**:仅仅包含所链接文件的路径名,因此能链接目录文件,也可以跨越文件系统进行链接。但是,当原始文件被删除后,链接文件也将失效,从这一点上来说与Windows系统中的“快捷方式”具有一样的性质。 + +#### 1. ln 命令 + +ln命令用于创建链接文件,格式为“ln [选项] 目标”。 + +表6-6 ln命令中可用的参数以及作用 + +| 参数 | 作用 | +| ---- | -------------------------------------------------- | +| -s | 创建“符号链接”(如果不带-s参数,则默认创建硬链接) | +| -f | 强制创建文件或目录的链接 | +| -i | 覆盖前先询问 | +| -v | 显示创建链接的过程 | + +```shell +# 软连接 +[root@linuxprobe ~]# echo "Welcome to linuxprobe.com" > readme.txt +[root@linuxprobe ~]# ln -s readme.txt readit.txt +[root@linuxprobe ~]# cat readme.txt +Welcome to linuxprobe.com +[root@linuxprobe ~]# cat readit.txt +Welcome to linuxprobe.com +[root@linuxprobe ~]# ls -l readme.txt +-rw-r--r-- 1 root root 26 Jan 11 00:08 readme.txt +[root@linuxprobe ~]# rm -f readme.txt +[root@linuxprobe ~]# cat readit.txt +cat: readit.txt: No such file or directory + +# 硬连接 删除原文件后 链接文件任然有效 +[root@linuxprobe ~]# echo "Welcome to linuxprobe.com" > readme.txt +[root@linuxprobe ~]# ln readme.txt readit.txt +[root@linuxprobe ~]# cat readme.txt +Welcome to linuxprobe.com +[root@linuxprobe ~]# cat readit.txt +Welcome to linuxprobe.com +[root@linuxprobe ~]# ls -l readme.txt +-rw-r--r-- 2 root root 26 Jan 11 00:13 readme.txt +[root@linuxprobe ~]# rm -f readme.txt +[root@linuxprobe ~]# cat readit.txt +Welcome to linuxprobe.com +``` + +## 第八章 Iptables与Firewalld防火墙 + +### 8.2 iptables + +#### 8.2.1 策略与规则链 + +iptables服务把用于处理或过滤流量的策略条目称之为规则,多条规则可以组成一个规则链,而规则链则依据数据包处理位置的不同进行分类,具体如下: + +> 在进行路由选择前处理数据包(PREROUTING); +> +> 处理流入的数据包(INPUT); +> +> 处理流出的数据包(OUTPUT); +> +> 处理转发的数据包(FORWARD); +> +> 在进行路由选择后处理数据包(POSTROUTING)。 + +#### 8.2.2 基本的命令参数 + +表8-1 iptables中常用的参数以及作用 + +| 参数 | 作用 | +| ----------- | -------------------------------------------- | +| -P | 设置默认策略 | +| -F | 清空规则链 | +| -L | 查看规则链 | +| -A | 在规则链的末尾加入新规则 | +| -I num | 在规则链的头部加入新规则 | +| -D num | 删除某一条规则 | +| -s | 匹配来源地址IP/MASK,加叹号“!”表示除这个IP外 | +| -d | 匹配目标地址 | +| -i 网卡名称 | 匹配从这块网卡流入的数据 | +| -o 网卡名称 | 匹配从这块网卡流出的数据 | +| -p | 匹配协议,如TCP、UDP、ICMP | +| --dport num | 匹配目标端口号 | +| --sport num | 匹配来源端口号 | + +```shell +# 在iptables命令后添加-L参数查看已有的防火墙规则链: +iptables -L +# 在iptables命令后添加-F参数清空已有的防火墙规则链: +iptables -F +# 把INPUT规则链的默认策略设置为拒绝: +iptables -P INPUT DROP +# 向INPUT链中添加允许ICMP流量进入的策略规则: +iptables -I INPUT -p icmp -j ACCEPT +# 删除INPUT规则链中刚刚加入的那条策略(允许ICMP流量),并把默认策略设置为允许: +iptables -D INPUT 1 +iptables -P INPUT ACCEPT +# 将INPUT规则链设置为只允许指定网段的主机访问本机的22端口,拒绝来自其他所有主机的流量: +iptables -I INPUT -s 192.168.10.0/24 -p tcp --dport 22 -j ACCEPT +iptables -A INPUT -p tcp --dport 22 -j REJECT +# 向INPUT规则链中添加拒绝所有人访问本机12345端口的策略规则: +iptables -I INPUT -p tcp --dport 12345 -j REJECT +iptables -I INPUT -p udp --dport 12345 -j REJECT +# 向INPUT规则链中添加拒绝192.168.10.5主机访问本机80端口(Web服务)的策略规则: +ptables -I INPUT -p tcp -s 192.168.10.5 --dport 80 -j REJECT +# 向INPUT规则链中添加拒绝所有主机访问本机1000~1024端口的策略规则: +iptables -A INPUT -p tcp --dport 1000:1024 -j REJECT +iptables -A INPUT -p udp --dport 1000:1024 -j REJECT +``` + +### 8.3 Firewalld + +表8-2 firewalld中常用的区域名称及策略规则 + +| 区域 | 默认规则策略 | +| -------- | ------------------------------------------------------------ | +| trusted | 允许所有的数据包 | +| home | 拒绝流入的流量,除非与流出的流量相关;而如果流量与ssh、mdns、ipp-client、amba-client与dhcpv6-client服务相关,则允许流量 | +| internal | 等同于home区域 | +| work | 拒绝流入的流量,除非与流出的流量相关;而如果流量与ssh、ipp-client与dhcpv6-client服务相关,则允许流量 | +| public | 拒绝流入的流量,除非与流出的流量相关;而如果流量与ssh、dhcpv6-client服务相关,则允许流量 | +| external | 拒绝流入的流量,除非与流出的流量相关;而如果流量与ssh服务相关,则允许流量 | +| dmz | 拒绝流入的流量,除非与流出的流量相关;而如果流量与ssh服务相关,则允许流量 | +| block | 拒绝流入的流量,除非与流出的流量相关 | +| drop | 拒绝流入的流量,除非与流出的流量相关 | + +#### 8.3.1 终端管理工具 + +表8-3 firewall-cmd命令中使用的参数以及作用 + +| 参数 | 作用 | +| ----------------------------- | ---------------------------------------------------- | +| --get-default-zone | 查询默认的区域名称 | +| --set-default-zone=<区域名称> | 设置默认的区域,使其永久生效 | +| --get-zones | 显示可用的区域 | +| --get-services | 显示预先定义的服务 | +| --get-active-zones | 显示当前正在使用的区域与网卡名称 | +| --add-source= | 将源自此IP或子网的流量导向指定的区域 | +| --remove-source= | 不再将源自此IP或子网的流量导向某个指定区域 | +| --add-interface=<网卡名称> | 将源自该网卡的所有流量都导向某个指定区域 | +| --change-interface=<网卡名称> | 将某个网卡与区域进行关联 | +| --list-all | 显示当前区域的网卡配置参数、资源、端口以及服务等信息 | +| --list-all-zones | 显示所有区域的网卡配置参数、资源、端口以及服务等信息 | +| --add-service=<服务名> | 设置默认区域允许该服务的流量 | +| --add-port=<端口号/协议> | 设置默认区域允许该端口的流量 | +| --remove-service=<服务名> | 设置默认区域不再允许该服务的流量 | +| --remove-port=<端口号/协议> | 设置默认区域不再允许该端口的流量 | +| --reload | 让“永久生效”的配置规则立即生效,并覆盖当前的配置规则 | +| --panic-on | 开启应急状况模式 | +| --panic-off | 关闭应急状况模式 | diff --git a/notes/《RabbitMQ实战指南》读书笔记.md b/notes/《RabbitMQ实战指南》读书笔记.md new file mode 100644 index 0000000..3e32776 --- /dev/null +++ b/notes/《RabbitMQ实战指南》读书笔记.md @@ -0,0 +1,1096 @@ +# 《RabbitMQ实战指南》读书笔记 + +## 目录
+第一章 RabbitMQ的安装以及简单使用
+第二章 RabbitMQ入门
+    2.1 相关概念介绍
+        2.1.1 生产者和消费者
+        2.1.2 队列
+        2.1.3 交换器、路由键、绑定
+        2.1.4 交换器类型
+        2.1.5 RabbitMQ运转流程
+第三章 客户端开发向导
+    3.1 连接RabbitMQ
+    3.2 使用交换器和队列
+        3.2.1 exchangDeclare 方法详解
+        3.2.2 queneDeclare方法详解
+        3.2.3 queueBind方法详解
+        3.2.4 exchangeBind方法详解
+    3.3 发送消息(basicPublish)
+    3.4 消费消息
+        3.4.1 推模式
+        3.4.2 拉模式
+    3.5 消费端的确认与拒绝
+    3.6 关闭连接
+第四章 RabbitMQ进阶
+    4.1 消息何去何从
+        4.1.1 mandatory 参数
+        4.1.2 immedidate 参数
+        4.1.3 备份交换器
+    4.2 过期时间(TTL)
+        4.2.1设置消息的TTL
+        4.2.2 设置队列的TTL
+    4.3 死信队列DLX
+    4.4 延迟队列
+    4.5 优先级队列
+    4.6 RPC 实现
+    4.7 持久化
+    4.8 生产者确认
+        4.8.1 事务机制(不推荐)
+        4.8.2 发送方确认机制(推荐)
+    4.9 消费端要点介绍
+        4.9.1 消息分发
+第五章 RabbitMQ 管理
+    5.1 虚拟主机与权限
+    5.2 用户管理
+    5.3 web端管理
+    5.4 应用与集群管理
+        5.4.1 应用管理
+        5.4.2 集群管理
+    5.5 服务端状态
+        1. 队列状态
+        2. 交换机状态
+        3. 绑定状态
+        4. TCP|IP 连接状态
+        5. 信道状态
+        6.消费者状态
+        7.Brokder的状态
+        8.其他状态
+## 正文
+ + + + + +## 第一章 RabbitMQ的安装以及简单使用 + +**linux下安装步骤(docker):** + +1.docker pull rabbitmq:management + +2.docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 -v `pwd`/data:/var/lib/rabbitmq --hostname myRabbit -e RABBITMQ_DEFAULT_VHOST=my_vhost -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin 镜像ID + +说明: + +- d 后台运行容器; +- name 指定容器名; +- p 指定服务运行的端口(5672:应用访问端口;15672:控制台Web端口号); +- v 映射目录或文件; +- hostname 主机名(RabbitMQ的一个重要注意事项是它根据所谓的 “节点名称” 存储数据,默认为主机名); +- e 指定环境变量;(RABBITMQ_DEFAULT_VHOST:默认虚拟机名;RABBITMQ_DEFAULT_USER:默认的用户名;RABBITMQ_DEFAULT_PASS:默认用户名的密码) + +3.访问 http://宿主机地址:15672 + +**简单使用**: + +```xml +# 引入依赖 + + + com.rabbitmq + amqp-client + 5.2.0 + + + + junit + junit + 4.12 + +``` + +```java +public class Rabbit { + + private static final String EXCHANGE_NAME="exchange-heibai"; + private static final String ROUTING_KEY="routingkey-heibai"; + private static final String QUEUE_NAME="queue-heibai"; + private static final String USER_NAME="heibai"; + private static final String PASSWORD="heibai"; + private static final String IP_ADDRESS="127.0.0.1"; + private static final int PORT=5672; //默认端口号 + + + @Test + public void producer() throws IOException, TimeoutException { + // 创建连接工厂 + ConnectionFactory factory = new ConnectionFactory(); + factory.setHost(IP_ADDRESS); + factory.setPort(PORT); + factory.setUsername(USER_NAME); + factory.setPassword(PASSWORD); + // 获取新的连接 + Connection connection = factory.newConnection(); + // 创建信道 + Channel channel = connection.createChannel(); + // 创建一个 type="direct"、持久化的、非自动删除的交换器 + channel.exchangeDeclare(EXCHANGE_NAME,"direct",true,false,null); + // 创建一个持久化、非排他的、非自动删除的交换器 + channel.queueDeclare(QUEUE_NAME,true,false,false,null); + // 将交换器与队列通过路由键绑定 + channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,ROUTING_KEY); + String message="hello world"; + // 发送一条持久化的消息 + channel.basicPublish(EXCHANGE_NAME,ROUTING_KEY,MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes()); + // 关闭信道 + channel.close(); + // 关闭资源 + connection.close(); + } + + @Test + public void consumer() throws IOException, TimeoutException, InterruptedException { + Address[] addresses = {new Address(IP_ADDRESS, PORT)}; + ConnectionFactory factory=new ConnectionFactory(); + factory.setUsername(USER_NAME); + factory.setPassword(PASSWORD); + Connection connection = factory.newConnection(addresses); + final Channel channel = connection.createChannel(); + // 设置客户端最多接收未被ack的消息的个数 + channel.basicQos(64); + DefaultConsumer consumer = new DefaultConsumer(channel) { + // 客户端接收后如何处理消息 + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + System.out.println("rev message:"+new String(body)); + // 确认收到消息 + channel.basicAck(envelope.getDeliveryTag(),false); + } + }; + channel.basicConsume(QUEUE_NAME,consumer); + // 等到回调函数完成后关闭连接 + TimeUnit.SECONDS.sleep(2); + channel.close(); + connection.close(); + } + +} +``` + +## 第二章 RabbitMQ入门 + +### 2.1 相关概念介绍 + +#### 2.1.1 生产者和消费者 + +

+ +

+ +#### 2.1.2 队列 + +**Queue(队列)** 是 RabbitMQ的内部对象,用于**存储消息**。多个消费者可以订阅同一个消息,这时候队列中的消息会被平均分摊(Round-Robin,即轮训)给多个消费者进行处理,而不是每个消费者都收到所有消费并处理。 + +#### 2.1.3 交换器、路由键、绑定 + +**Exchange(交换器)**:生产者将消息发送到Exchange(交换器),由交换器将消息路由到一个或者多个队列中。 + +**Routingkey (路由键)** : 生产者将消息发给交换器的时候,一般会指定一个RountingKey,用来指定这个消息的路由规则。而这个RountingKey需要与交换器类型和绑定键(BindingKey)联合使用才能最终生效。 + +**Binding(绑定)**:RabbitMQ中通过绑定将交换器与队列关联起来,在绑定的时候一般会指定一个绑定键(BindingKey),这样RabbitMQ就知道如何正确地将消息路由到队列。 + +#### 2.1.4 交换器类型 + +**1. fanout**: 把所有发送到该交换器的消息路由到所有与该交换器绑定的队列中。 + +**2. direct**: 把消息路由到那些BindingKey和RountingKey 完全匹配的队列中。 + +

+ +**官方原文:** + +In this setup, we can see the direct exchange X with two queues bound to it. The first queue is bound with binding key orange, and the second has two bindings, one with binding key black and the other one with green. + +In such a setup a message published to the exchange with a routing key orange will be routed to queue Q1. Messages with a routing key of black or green will go to Q2. All other messages will be discarded. + + + +**3. topic**:将消息路由到BindingKey和RountingKey 相匹配的队列中,匹配规则约定: + +- RountingKey 和 BindingKey 均为一个点“.”分隔得字符串,被点号分隔得每一段独立的字符串称为一个单词。 +- BindingKey 中可以存在两种特殊的字符串“#”和“*”,其中“\*”用于匹配一个单词,“\#”用于匹配零个或者多个单词。 + - \* (star) can substitute for exactly one word. + - \# (hash) can substitute for zero or more words. + +

+ +官网原文对这一模式的说明比较清晰,摘抄如下: + +We created three bindings: Q1 is bound with binding key "*.orange.*" and Q2 with "*.*.rabbit" and "lazy.#". + +These bindings can be summarised as: + +- Q1 is interested in all the orange animals. +- Q2 wants to hear everything about rabbits, and everything about lazy animals. + +A message with a routing key set to "quick.orange.rabbit" will be delivered to both queues. Message "lazy.orange.elephant" also will go to both of them. On the other hand "quick.orange.fox" will only go to the first queue, and "lazy.brown.fox" only to the second. "lazy.pink.rabbit" will be delivered to the second queue only once, even though it matches two bindings. "quick.brown.fox" doesn't match any binding so it will be discarded. + +What happens if we break our contract and send a message with one or four words, like "orange" or "quick.orange.male.rabbit"? Well, these messages won't match any bindings and will be lost. + +On the other hand "lazy.orange.male.rabbit", even though it has four words, will match the last binding and will be delivered to the second queue. + +**4. headers** + +headers类型的交换器不依赖路由键的匹配规则来路由消息,而是根据发送的消息内容中的headers属性进行匹配。(不常用) + +#### 2.1.5 RabbitMQ运转流程 + +**生产者发送消息过程**: + +1. 生产者连接到RabbitMQ Broker, 建立一个连接(Connection), 开启一个信道(Channel); +2. 生产者声明一个交换器,并设置相关属性; +3. 生产者声明一个队列并设置相关属性; +4. 生产者通过路由键将交换器与队列绑定起来; +5. 生产者发送消息至RabbitMQ Broker ,其中包含路由键、交换器等信息; +6. 相应的交换器根据接收到的路由键查找相应的匹配队列; +7. 如果找到,则将从生产者发送过来的消息存入相应的队列中; +8. 如果没有找到,则根据生产者配置的属性选择丢弃还是回退给生产者; +9. 关闭信道; +10. 关闭连接。 + +**消费者接收消息的过程**: + +1. 消费者连接到RabbitMQ Broker, 建立一个连接(Connection), 开启一个信道(Channel); +2. 消费者向RabbitMQ Broker请求消费相应队列中的消息,可能会设置相应的回调函数; +3. 等待RabbitMQ Broker回应并投递相应对列中的消息,消费者接收消息; +4. 消费者确认(ack)接收到的消息; +5. RabbitMQ从队列中删除相应已经被确认的消息; +6. 关闭信道; +7. 关闭连接。 + +## 第三章 客户端开发向导 + +### 3.1 连接RabbitMQ + +```java +// 1. 给定参数连接 +ConnectionFactory factory = new ConnectionFactory(); +factory.setHost(IP_ADDRESS); +factory.setPort(PORT); +factory.setVirtualHost("/"); +factory.setUsername(USER_NAME); +factory.setPassword(PASSWORD); +Connection connection = factory.newConnection(); + +// 2.url 连接 +ConnectionFactory factory new ConnectionFactory(); +factory.setUri( "amqp://userName:password@ipAddress:portNumber/virtualHost"); +Connection conn = factory.newConnection(); +``` + +**注**:Connection 可以 用来创建多个 Channel 实例,但是 Channel 实例不能在线程问共享,应用程序应该为每一个线程开辟一个 Channel 。 + +### 3.2 使用交换器和队列 + +```java +// 创建一个 type="direct"、持久化的、非自动删除的交换器 +channel.exchangeDeclare(EXCHANGE_NAME,"direct",true,false,null); +// 创建一个持久化、非排他的、非自动删除的交换器 +channel.queueDeclare(QUEUE_NAME,true,false,false,null); +// 将交换器与队列通过路由键绑定 +channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,ROUTING_KEY); +``` + +#### 3.2.1 exchangDeclare 方法详解 + +**1.声明交换器** + +```java +/** +* Declare an exchange. + +* @param exchange the name of the exchange + 交换器的名称 + +* @param type the exchange type + 交换器的类型,fanout,direct,topic 等 + +* @param durable true if we are declaring a durable exchange (the exchange will survive a server restart) + 设置是否持久化,持久化可以将交换器存盘,在服务器重启的时候不会丢失相关的信息 + +* @param autoDelete true if the server should delete the exchange when it is no longer in use + 是否自动删除,自删除的前提是至少有一个队列或者交换器与这交换器绑定,之后所有与这个交换器绑定的队列或者交换器都与此解绑后删除,一般都设置为fase + +* @param internal true if the exchange is internal, i.e. can't be directly + internal是否内置,如果设置 为true,则表示是内置的交换器,客户端程序无法直接发送消息到这个交换器中,只能通过交换器路由到交换器的方式 + +* @param arguments other properties (construction arguments) for the exchange + arguments:其它一些结构化参数比如:alternate-exchange + +* @return a declaration-confirm method to indicate the exchange was successfully declared +* @throws java.io.IOException if an error is encountered + */ +Exchange.DeclareOk exchangeDeclare(String exchange, + String type, + boolean durable, + boolean autoDelete, + boolean internal, + Map arguments) throws IOException; +``` + +**2.检测交换器是否存在** + +```java +/** +* Declare an exchange passively; that is, check if the named exchange exists. +* @param name check the existence of an exchange named this +* @throws IOException the server will raise a 404 channel exception if the named exchange does not exist. +它主要用来检测相应的交换器是否存在。如果存在则正常返回:如果不存在则抛出异常 : 404 channel exception。 +*/ +Exchange.DeclareOk exchangeDeclarePassive(String name) throws IOException; +``` + +**3. 删除交换器** + +```java +/** +* Delete an exchange +* @param exchange the name of the exchange +* @param ifUnused true to indicate that the exchange is only to be deleted if it is unuse + ifUnused 用来设置是否在交换器没有被使用的情况下删除 。 如果 isUnused 设置为 true,则只有在此交换器没有被使用的情况下才会被删除;如果设置 false,则无论如何这个交换器都要被删除。 +* @return a deletion-confirm method to indicate the exchange was successfully deleted +* @throws java.io.IOException if an error is encountered +*/ +Exchange.DeleteOk exchangeDelete(String exchange, boolean ifUnused) throws IOException; +``` + +#### 3.2.2 queneDeclare方法详解 + +queneDeclare只有两个重载方法: + +不带任何参数的 queueDeclare 方法默认创建一个由 RabbitMQ 命名的(类似这种amq.gen-LhQzlgv3GhDOv8PIDabOXA 名称,这种队列也称之为匿名队列〉、排他的、自动删除的、非持久化的队列。 + +```java +/** +* Declare a queue +* @param queue the name of the queue + 队列的名称。 +* @param durable true if we are declaring a durable queue (the queue will survive a server restart) + 是否持久化 +* @param exclusive true if we are declaring an exclusive queue (restricted to this connection) +* @param autoDelete true if we are declaring an autodelete queue (server will delete it when no longer in use) + 是否自动删除 自动删除的前提是:至少有一个消费者连接到这个队列,之后所有与这个队列连接的消费者都断开时,才会自动删除。 +* @param arguments other properties (construction arguments) for the queue +* @return a declaration-confirm method to indicate the queue was successfully declared +* @throws java.io.IOException if an error is encountered +*/ +Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, + Map arguments) throws IOException; +``` + +exclusive : 设置是否排他。为 true 则设置队列为排他的。如果一个队列被声明为排他队列,该队列仅对首次声明它的连接可见,并在连接断开时自动删除。这里需要注意三点: + +- 排他队列是基于连接( Connection) 可见的,同一个连接的不同信道 (Channel)是可以同时访问同一连接创建的排他队列; +- "首次"是指如果一个连接己经声明了 一个排他队列,其他连接是不允许建立同名的排他队列的,这个与普通队列不同; +- 即使该队列是持久化的,一旦连接关闭或者客户端退出,该排他队列都会被自动删除,这种队列适用于一个客户端同时发送和读取消息的应用场景。 + +注: + +生产者和消费者都能够使用 queueDeclare 来声明一个队列,但是如果消费者在同一个信道上订阅了另一个队列,就无法再声明队列了。必须先取消订阅,然后将信道直为"传输" 模式,之后才能声明队列。 + +**清空队列内容**: + +```java +Queue.PurgeOk queuePurge(String queue) throws IOException; +``` + +#### 3.2.3 queueBind方法详解 + +**用于交换器与队列的绑定** + +```java +# 绑定 +Queue.BindOk queueBind(String queue, String exchange, String routingKey, Map arguments) throws IOException; + +# 解绑 +Queue.UnbindOk queueUnbind(String queue, String exchange, String routingKey, Map arguments) throws IOException; +``` + +#### 3.2.4 exchangeBind方法详解 + +**用于交换器与交换器的绑定** + +```java +Exchange.BindOk exchangeBind(String destination, String source, String routingKey, Map arguments) throws IOException; +``` + +生产者发送消息至交换器 source 中,交换器 source 根据路由键找到与其匹配的另一个交换器 destination , 井把消息转发到 destination 中 , 进而存储在 destination 绑定 的队列 queue 中 。 + +### 3.3 发送消息(basicPublish) + +补充:Java 8 对接口做了进一步的增强。 + +- **a.** 在接口中可以添加使用 default 关键字修饰的非抽象方法。即:默认方法(或扩展方法) +- **b.** 接口里可以声明静态方法,并且可以实现。 + +```java +void basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate, BasicProperties props, byte[] body) throws IOException; +``` + +- exchange: 交换器的名称,指明消息需要发送到哪个交换器中 。 **如果设置为空字符串,则消息会被发送到 RabbitMQ 默认的交换器中**; +- props : 消息的基本属性集; +- byte [] body : 消息体 ( payload ) ,真正需要发送的消息 。 + +```java +// 发送一条持久化的消息 +channel.basicPublish(EXCHANGE_NAME,ROUTING_KEY,MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes()); + +// 发送一条特殊配置的消息 +channel.basicPublish(EXCHANGE_NAME,ROUTING_KEY, + new AMQP.BasicProperties.Builder() + .contentType("text/plain") + .deliveryMode(2) + .priority(1) + .headers(new HashMap()) + .expiration("6000") + .build(), + message.getBytes()); +``` + +### 3.4 消费消息 + +#### 3.4.1 推模式 + +在推模式中,可以通过持续订阅的方式来消费消息。接收消息一般通过实现**Consumer**接口或者继承**DefaultConsumer**类来实现。当调用与Consumer相关的API方法时,不同的订阅采用不同的消费者标签(consumerTag)来区分彼此,在同一个Channel中的消费者也需要通过唯一的消费者标签以作区分。 + +```java +channel.basicConsume(QUEUE_NAME,false,"myConsumerTag",new DefaultConsumer(channel) { + // 客户端接收后处理消息 + @Override + public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { + System.out.println("rev message:"+new String(body)); + String routingKey = envelope.getRoutingKey(); + String contentType = properties.getContentType(); + long deliveryTag = envelope.getDeliveryTag(); + // 确认收到消息 + channel.basicAck(envelope.getDeliveryTag(),false); + // 或者拒绝消息 一次只能拒绝一条 + channel.basicReject(deliveryTag,true); + // 一次拒绝多条 + channel.basicNack(deliveryTag,true,true); + } + }); + +``` + +```java +String basicConsume(String queue, boolean autoAck, String consumerTag, boolean noLocal, boolean exclusive, Map arguments, Consumer callback) throws IOException; +``` + +- queue : 队列的名称: +- autoAck : 设置是否自动确认。建议设成 false ,即不自动确认: +- consumerTag: 消费者标签,用来区分多个消费者: +- noLocal : 设置为 true 则表示不能将同一个 Connection中生产者发送的消息传送给这个 Connection 中的消费者: +- exclusive : 设置是否排他 : +- arguments : 设置消费者的其他参数: +- callback : 设置消费者的回调函数。用来处理 RabbitMQ 推送过来的消息,比如DefaultConsumer , 使用时需要客户端重写 (override) 其中的方法。 + +**注**: + +每个 Channel 都拥有自己独立的线程。最常用的做法是一个 Channel 对应一个消费者,也就是意味着消费者彼此之间没有任何关联。当然也可以在一个 Channel 中维持多个消费者,但是要注意一个问题,如果 Channel 中的一个消费者一直在运行,那么其他消费者的 callback会被"耽搁"。 + +#### 3.4.2 拉模式 + +通过channel.basicGet方法可以单挑获取信息。 + +```java +GetResponse response = channel.basicGet(QUEUE_NAME, false); +System.out.println(new String(response.getBody())); +channel.basicAck(response.getEnvelope().getDeliveryTag(),false); +``` + +**注**: + +Basic . Consume 将信道 (Channel) 直为接收模式,直到取消队列的订阅为止。在接收模式期间, RabbitMQ 会不断地推送消息给消费者,当然推送消息的个数还是会受到 Basic.Qos的限制.如果只想从队列获得单条消息而不是持续订阅,建议还是使用Basic.Get 进行消费. + +### 3.5 消费端的确认与拒绝 + +**1.确认** + +为了保证消息从队列可靠地达到消费者, RabbitMQ 提供了消息确认机制( messageacknowledgement) 。 消费者在订阅队列时,可以指定 aut oAck 参数: + +**当 autoAck 等于 false时, RabbitMQ 会等待消费者显式地回复确认信号后才从内存(或者磁盘)中移去消息(实质上是先打上删除标记,之后再删除) 。** + +**当 autoAck 等于 true 时, RabbitMQ 会自动把发送出去的消息置为确认,然后从内存(或者磁盘)中删除,而不管消费者是否真正地消费到了这些消息 。** + +采用消息确认机制后,只要设置 autoAck 参数为 false,消费者就有足够的时间处理消息(任务) ,不用担心处理消息过程中消费者进程挂掉后消息丢失的问题 , 因为 RabbitMQ 会一直等待持有消息直到消费者显式调用 Basic.Ack 命令为止 。 +当 autoAck 参数置为 false ,对于 RabbitMQ 服务端而言 ,队列中的消息分成了两个部分 :一部分是等待投递给消费者的消息:一部分是己经投递给消费者,但是还没有收到消费者确认信号的消息。 如果 RabbitMQ 一直没有收到消费者的确认信号,并且消费此消息的消费者己经断开连接,则 RabbitMQ 会安排该消息重新进入队列,等待投递给下一个消费者,当然也有可能还是原来的那个消费者。 + +RabbitMQ 不会为未确认的消息设置过期时间,它判断此消息是否需要重新投递给消费者的唯一依据是消费该消息的消费者连接是否己经断开,这么设计的原因是 RabbitMQ 允许消费者消费一条消息的时间可以很久很久。 + +**2.拒绝** + +```java +// 一次拒绝一条 +void basicReject(long deliveryTag, boolean requeue) throws IOException; + +// 一次拒绝多条 + void basicNack(long deliveryTag, boolean multiple, boolean requeue) + throws IOException; +``` + +其中 deliveryTag 可以看作消息的编号 。如果 requeue 参数设置为 true ,则 RabbitMQ 会重新将这条消息存入队列,以便可以发送给下一个订阅的消费者;如果 requeue 参数设置为 false,则 RabbitMQ立即会把消息从队列中移除,而不会把它发送给新的消费者。 + +multiple 参数设置为 false 则表示拒绝编号为 deliveryTag的这 一条消息,这时候 basicNack 和basicReject 方法一样; multiple 参数设置为 true 则表示拒绝 deliveryTag 编号之前所有未被当前消费者确认的消息。 + +**3.重入队列** + +```java +Basic.RecoverOk basicRecover() throws IOException; +Basic.RecoverOk basicRecover(boolean requeue) throws IOException; +``` + +这个 channel.basicRecover 方法用来请求 RabbitMQ 重新发送还未被确认的消息 。 如果 requeue 参数设置为 true ,则未被确认的消息会被重新加入到队列中,这样对于同一条消息来说,可能会被分配给与之前不同的消费者。如果 requeue 参数设置为 false,那么同一条消息会被分配给与之前相同的消费者。默认情况下,如果不设置 requeue 这个参数,相当channel.basicRecover(true) ,即 requeue 默认为 true。 + +### 3.6 关闭连接 + +在应用程序使用完之后,需要关闭连接,释放资源: +channel.close(); +conn.close() ; + +## 第四章 RabbitMQ进阶 + +### 4.1 消息何去何从 + +mandatory 和 immediate 是 channel.basicPublish 方法中的两个参数,它们都有**当消息传递过程中不可达目的地时将消息返回给生产者的功能**。 RabbitMQ 提供的备份交换器(Altemate Exchange) 用以将未能被交换器路由的消息(没有绑定队列或者没有匹配的绑定〉存储起来,而不用返回给客户端。 + +#### 4.1.1 mandatory 参数 + +当 mandatory 参数设为 true 时,**交换器无法根据自身的类型和路由键找到一个符合条件的队列**,那么 RabbitMQ 会调用 Basic.Return 命令将消息返回给生产者 。 + +当 mandatory 参数设置为 false 时,出现上述情形,则消息直接被丢弃 。 + +那么生产者如何获取到没有被正确路由到合适队列的消息呢? **可以通过调用channel . addReturnListener 来添加 ReturnListener 监昕器实现**。 + +```java +channel.addReturnListener(new ReturnListener() { + public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException { + System.out.println("Basic.Return返回的结果是:"+message); + } + }); +``` + +#### 4.1.2 immedidate 参数 + +当 immediate 参数设为 true 时,如果交换器在**将消息路由到队列时发现队列上并不存在任何消费者**,那么这条消息将不会存入队列中。当与路由键匹配的所有队列都没有消费者时 ,该消息会通过 Basic . Return 返回至生产者。 + +**RabbitMQ 3 .0 版本因为性能开始去掉了对 imrnediate 参数的支持** + +#### 4.1.3 备份交换器 + +备份交换器,可以将未被路由的消息存储在 RabbitMQ 中,再在需要的时候去处理这些消息。 + +可以通过在声明交换器(调用 channel.exchangeDeclare 方法)的时候添加**alternate-exchange** 参数来实现 + +```java +// 声明备份交换器 最好将type类型设置为 fanout +channel.exchangeDeclare( "myAe " , "fanout" , true, false , null) ; + +// 声明正常交换器 并设置备份交换器为myAe +Map args = new HashMap(); +args.put("a1ternate-exchange" , "myAe"); +channel.exchangeDeclare( "normalExchange" , "direct" , true , false , args); + +// 声明队列并和交换器绑定 +channel.queueDeclare( "norma1Queue " , true , false , false , null); +channel.queueBind( " norma1Queue " , " normalExchange" , " norma1Key"); +channel.queueDeclare( "unroutedQueue " , true , false , false , null); +channel.queueBind( "unroutedQueue ", "myAe ", ""); +``` + +**特殊情况**: + +- 如果设置的备份交换器不存在,客户端和 RabbitMQ 服务端都不会有异常出现,此时消息会丢失。 +- 如果备份交换器没有绑定任何队列,客户端和 RabbitMQ 服务端都不会有异常出现,此时消息会丢失。 +- 如果备份交换器没有任何匹配的队列,客户端和 RabbitMQ 服务端都不会有异常出现,此时消息会丢失。 +- 如果备份交换器和 mandatory 参数一起使用,那么 mandatory 参数无效。 + +### 4.2 过期时间(TTL) + +#### 4.2.1设置消息的TTL + +- 第一种方法是通过队列属性设置,队列中所有消息都有相同的过期时间。 +- 第二种方法是对消息本身进行单独设置,每条消息的 TTL 可以不同。 +- 两种方法一起使用,则消息的 TTL 以两者之间较小的那个数值为准。 +- 消息在队列中的生存时司 一旦超过设置 的 TTL 值时,就会变成"死信" (Dead Message) ,消费者将无法再收到该消息(这点不是绝对的 ,可以参考 4.3 节) 。 + +**1.通过队列属性设置消息 TTL 的方法是在 channel.queueDeclare 方法中加入 x-message-ttl 参数实现的,这个参数的单位是毫秒。** + +```java +Map argss = new HashMap(); +argss.put("x-message-ttl " , 6000); +channel.queueDeclare(queueName , durable , exclusive , autoDelete , argss) ; +``` + +**2.针对每条消息设置 TTL 的方法是在 channel.basicPublish 方法中加入 expiration 的属性参数,单位为毫秒。** + +采用这种方法设置,一旦消息过期,就会从队列中抹去 。 + +注:如果不设置 TTL.则表示此消息不会过期 ;如果将 TTL 设置为 0,则表示除非此时可以直接将消息投递到消费者,否则该消息会被立即丢弃。 + +```java +AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder(); +builder.deliveryMode(2); // 持久化消息 +builder.expiration( " 60000 " );// 设置 TTL=60000ms +AMQP.BasicProperties properties = builder . build() ; +channel.basicPublish(exchangeName, routingKey, mandatory, properties,"ttlTestMessage".getBytes()); +``` + +#### 4.2.2 设置队列的TTL + +通过 channel . queueDeclare 方法中的 x - expires 参数可以控制队列被自动删除前处于未使用状态的时间。未使用的意思是队列上没有任何的消费者,队列也没有被重新声明,并且在过期时间段 内也未调用过 Basic.Get 命令。 RabbitMQ 会确保在过期时间到达后将队列删除,但是不保障删除的动作有多及时 。在RabbitMQ 重启后 , 持久化的队列的过期时间会被重新计算。 + +**用于表示过期时间的 x-expires 参数以毫秒为单位 , 井且服从和 x-message-ttl 一样的约束条件,不过不能设置为 0。比如该参数设置为 1000 ,则表示该队列如果在 1 秒钟之内未使用则会被删除。** + +```java +Map args =new HashMap() ; +args.put( "x-expires" , 1800000); +channel.queueDeclare("myqueue " , false , false , false , args) ; +``` + +### 4.3 死信队列DLX + +当有队列中存在死信时,RabbitMQ 就会自动地将这个消息重新发布到设置的 DLX 上去 。 + +**消息变成死信一般是由于以下几种情况**: + +- 消息被拒绝 (Basic.Reject/Basic.Nack) ,井且设置 requeue 参数为 false; +- 消息过期; +- 队列达到最大长度。 + +**通过在 channel.queueDeclare 方法中设置 x-dead-letter-exchange 参数来为这个队列添加 DLX** + +```java + //创建 DLX: dlx_exchange + channel.exchangeDeclare("dlx_exchange " , "direct "); + Map args = new HashMap(); + args.put("x-dead-letter-exchange" , " dlx_exchange "); + //也可以为这个 DLX 指定路由键,如果没有特殊指定,则使用原队列的路由键: + args.put("x-dead-letter-routing-key" , "dlx-routing-key"); + //为队列 myqueue 添加 DLX + channel.queueDeclare("myqueue" , false , false , false , args); +``` + +```java + // 声明用于DXL队列的交换器 + channel.exchangeDeclare("exchange.dlx", "direct", true); + // 声明正常交换器 + channel.exchangeDeclare("exchange.normal ", "fanout ", true); + // 为正常队列绑定DXL交换器 + Map args = new HashMap(); + args.put("x-message - ttl ", 10000); + args.put("x-dead-letter-exchange ", "exchange.dlx"); //DXL队列与DXL交换器的原路由键 + args.put("x-dead-letter-routing-key", " routingkey"); + channel.queueDeclare(" queue.norma1 ", true, false, false, args); + // 正常队列与正常交换器绑定 + channel.queueBind("queue.normal", "exchange.normal", ""); + // 声明DXL队列 + channel.queueDeclare(" queue.d1x ", true, false, false, null); + // DXL队列与信DXL交换器绑定 + channel.queueBind("queue.dlx ", "exchange.dlx ", "routingkey"); + // 发送一条信息 10s超时后成为死信 + channel.basicPublish("exchange.normal", " rk", + MessageProperties.PERSISTENT_TEXT_PLAIN, "message".getBytes()); +``` + +### 4.4 延迟队列 + +利用死信队列来实现: + +

+ +### 4.5 优先级队列 + +**设置队列优先级**: + +```java +Map args = new HashMap(); +args.put("x-max-priority ", 10); +channel.queueDeclare(" queue . priority", true, false, false, args); +``` + +**设置消息优先级**: + +```java +AMQP.BasicProperties.Builder builder = new AMQP.BasicProperties.Builder(); +builder.priority(5); +AMQP.BasicProperties properties = builder.build(); +channel.basicPublish("exchange_priority", " rk_priority ", properties, "message".getBytes()); +``` + +### 4.6 RPC 实现 + +Java版本可参考博客:https://blog.csdn.net/u013256816/article/details/55218595 + +python版本可参考官网:http://www.rabbitmq.com/tutorials/tutorial-six-python.html + +### 4.7 持久化 + +**交换器的持久化**:通过在声明队列是将 durable 参数置为 true 实现的; + +**队列的持久化**:是通过在声明队列时将 durable 参数置为 true 实现的; + +**消息的持久化** :通过将消息的投递模式(BasicProperties中的 deliveryMode 属性) 设置为 2 即可实现 + +### 4.8 生产者确认 + +当消息的生产者将消息发送出去之后,消息到底有没有正确地到达服务器呢? RabbitMQ 针对这个问题,提供了两种解决方式: + +- 通过事务机制实现: +- 通过发送方确认 C publisher confirm ) 机制实现。 + +#### 4.8.1 事务机制(不推荐) + +RabbitMQ 客户端中与事务机制相关的方法有 三个: + +- **channel.txSelect** 用于将当前的信道设置成事务模式; +- **channel.txCommit** 用于提交事务 ; +- **channel.txRollback** 用于事务回滚。 + +```java +// 單條消息 +try { + channel.txSelect(); + channel.basicPublish(exchange, routekey, MessageProperties.PERSISTENT_TEXT_PLAIN, msg.getBytes()); + int result = 1 / 0; + channel.txCommit(); +} catch (Exception e) { + e.printStackTrace(); + channel.txRollback(); +} + +//多條消息 +channel.txSelect(); +for (int i = 0; i < NUM; i++) { + try { + channel.basicPublish(exchange, routekey, MessageProperties.PERSISTENT_TEXT_PLAIN, msg.getBytes()); + channel.txCommit(); + } catch (Exception e) { + e.printStackTrace(); + channel.txRollback(); + } +} +``` + +#### 4.8.2 发送方确认机制(推荐) + +生产者将信道设置成 confirm(确认)模式,一旦信道进入 confirm模式,所有在该信道上面发布的消息都会被指派一个唯一的ID(从 1开始),一旦消息被投递到所有匹配的队列之后,RabbitMQ 就会发送一个确认(Basic.Ack) 给生产者(包含消息的唯一 ID) ,这就使得生产者知晓消息已经正确到达了目的地了。 + +```java +try { + // 将信到置为 publisher confirm 模式 + channel.confirmSelect(); hannel.basicPublish(EXCHANGE_NAME,ROUTING_KEY,MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes()); + // 这样意味着还是同步阻塞的 性能并不比事务方式好(不推荐) + if (!channel.waitForConfirms()){ + System.out.println("send message failed"); + // do something else ... + } + } catch (Exception e) { + e.printStackTrace(); +} +``` + +**异步confirm模式**(推荐):的编程实现最复杂,Channel对象提供的ConfirmListener()回调方法只包含deliveryTag(当前Chanel发出的消息序号),我们需要自己为每一个Channel维护一个unconfirm的消息序号集合,每publish一条数据,集合中元素加1,每回调一次handleAck方法,unconfirm集合删掉相应的一条(multiple=false)或多条(multiple=true)记录。从程序运行效率上看,这个unconfirm集合最好采用有序集合SortedSet存储结构。 + +**注:不论是handleAck还是handleNack都证明消息被收到了,并没有丢失。** + +```java +SortedSet confirmSet = Collections.synchronizedSortedSet(new TreeSet()); + channel.confirmSelect(); + channel.addConfirmListener(new ConfirmListener() { + + public void handleAck(long deliveryTag, boolean multiple) throws IOException { + if (multiple) { + confirmSet.headSet(deliveryTag + 1).clear(); + } else { + confirmSet.remove(deliveryTag); + } + } + + public void handleNack(long deliveryTag, boolean multiple) throws IOException { + System.out.println("Nack, SeqNo: " + deliveryTag + ", multiple: " + multiple); + if (multiple) { + confirmSet.headSet(deliveryTag + 1).clear(); + } else { + confirmSet.remove(deliveryTag); + } + // 添加消息重发逻辑 + } + }); + while (true) { + long nextSeqNo = channel.getNextPublishSeqNo(); + channel.basicPublish(ConfirmConfig.exchangeName, ConfirmConfig.routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, ConfirmConfig.msg_10B.getBytes()); + // 将需要发送消息的id放入Set中 + confirmSet.add(nextSeqNo); + } +``` + +### 4.9 消费端要点介绍 + +#### 4.9.1 消息分发 + +**channel.basicQos 方法允许限制信道上的消费者所能保持的最大未确认消息的数量。** + + + +## 第五章 RabbitMQ 管理 + +rabbitmqctl 标准语法([] 表示可选参数,{} 表示必选参数): + +rabbitmqctl [-n node] \[-t timeout] [-q] \{command} [command options...] + +- **[-n node]**:指定节点; +- **[-t timeout]**:操作超时时间,单位秒; +- **[-q]** :quiet 屏蔽一些消息的输出,默认不开启。 + +### 5.1 虚拟主机与权限 + +**虚拟主机:** + +| 作用 | 命令 | 示例 | +| ------------ | ------------------------------------------------------------ | ------------------------------------ | +| 新建虚拟主机 | rabbitmqctl add_vhost {vhost} | rabbitmqctl add_vhost vhost1 | +| 罗列虚拟主机 | rabbitmqctl list_vhosts [vhostinfoitem...]
name:表示vhost 名称
tracing:表示是否启用了RabbitMQ的trace功能 | abbitmqctl list_vhosts name tracing | +| 删除虚拟主机 | rabbitmqctl delete_vhost {vhost} | rabbitmqctl delete_vhost vhost1 | + +**权限管理**: + +当创建一个用户时,用户通常会被指派给至少一个vhost,并且只能访问被指派的vhost内的队列、交换器和绑定关系等。因此,RabbitMQ中的授予权限是指在vhost级别对用户而言的权限授予。 + +| 作用 | 命令 | 示例 | +| ------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | +| 权限授予 | rabbitmqctl set_permissions [-p vhost] {user} {config} {write} {read}
vhost : 授予用户访问权限的 vhost 名称,可以设置为默认值,即 vhost 为 “/”
user: 可以访问指定 vhost 的用户名。
conf: 一个用于匹配用户在哪些资源上拥有可配置权限的正则表达式。
write: 一个用于匹配用户在哪些资源上拥有可写权限的正则表达式。
read : 一个用于匹配用户在哪些资源上拥有可读权限的正则表达式 。
可配置指的是队列和交换器的创建及删除之类的操作;可写指的是发布消息;可读指与消息有关的操作,包括读取消息及清空整个队列等。 | 赋予所有权限:
rabbitmqctl set_permissions -p vhost1 root ".\*" ".\*" ".\*"
在以“queue”开头的资源上具备可配置权限:
rabbitmqctl set_permissions -p vhost1 root "^queue.\*" ".\*" ".\*" | +| 清除权限 | rabbitmqctl clear_permissions [-p vhost] {username} | rabbitmqctl clear_permissions -p vhost1 root | +| 显示虚拟机上的权限 | rabbitmqctl list_permissions [-p vhost] | rabbitmqctl list_permissions -p vhost1 | +| 显示用户权限 | rabbitmqctl list_user_permissions {username} | rabbitmqctl list_user_permissions root | + +### 5.2 用户管理 + +| 作用 | 命令 | +| ------------------ | ------------------------------------------------------------ | +| 创建用户 | rabbitmqctl add_user {username} {password} | +| 更改密码 | rabbitmqctl change_password {username} {newpassword} | +| 验证密码是否正确 | rabbitmqctl authenticate_user {username} {password} | +| 删除用户 | rabbitmqctl delete_user {username} | +| 罗列当前的所有用户 | rabbitmqctl list_users | +| 设置用户角色 | rabbitmqctl set_user_tags {username} {tag ...}
设置0个、1个或者多个角色,设置之后任何之前现有的身份都会被删除 | + +用户的角色分为 5 种类型: + +- **none**: 无任何角色。新创建的用户的角色默认为 none。 +- **management**: 可以访问 Web 管理页面。 +- **policymaker**: 包含 management 的所有权限,并且可以 管理策略 ( Policy) 和参数(Parameter)。 +- **monitoring**: 包含 management 的所有权限,并且可以看到所有连接、信道及节点相关的信息。 +- **administartor**: 包含 monitoring 的所有权限,井且可以管理用户、 虚拟主机、权限、策略、参数等,是最高权限。 + +### 5.3 web端管理 + +在使用 Web 管理界面之前需要先启用 RabbitMQ management 插件 。 RabbitMQ 提供了很多的插件,默认存放在 $RABBITMQ_HOME /plugins 目录下 ,插件扩展名为“.ez”。 + +| 作用 | 命令 | +| -------- | -------------------------------------- | +| 启动插件 | rabbitmq-plugins enable [plugin-name] | +| 关闭插件 | rabbitmq-plugins disable [plugin-name] | +| 查看插件 | rabbitmq-plugins list | + +### 5.4 应用与集群管理 + +#### 5.4.1 应用管理 + +| 命令 | 作用 | +| -------------------------------- | ------------------------------------------------------------ | +| rabbitmqctl stop [pid_file] | 用于停止运行 RabbitMQ 的 Erlang 虚拟机和 RabbitMQ 服务应用。
如果指定了 pid_file ,还需要等待指定进程的结束。其中 pid file 是通过调用 rabbitmq-server 命令启动RabbitMQ 服务时创建的,默认情况下存放于Mnesia 目录中,可以通过 RABBITMQ_PID_FILE这个环境变量来改变存放路径。注意,如果使用 rabbitmq-server -detach 这个带有-detach 后缀的命令来启动 RabbitMQ 服务则不会生成 pid file 文件。 | +| rabbitmqctl shutdown | 用于停止运行 RabbitMQ 的 Erlang 虚拟机和 RabbitMQ 服务应用。执行这个命令会阻塞直到 Erlang 虚拟机进程退出。 | +| rabbitmqctl stop_app | 停止 RabbitMQ 服务应用,但是 Erlang 虚拟机还是处于运行状态。 | +| rabbitmqctl start_app | 启动RabbitMQ 服务应用 | +| rabbitmqctl wait [pid_file] | 等待 RabbitMQ 应用的启动。它会等到 pid_file 的创建,然后等待 pid_file 中所代表的进程启动。当指定的进程没有启动 RabbitMQ 应用而关闭时将会返回失败。 | +| rabbitmqctl reset | RabbitMQ 节点重置还原到最初状态。包括从原来所在的集群中删除此节点,从管理数据库中删除所有的配置数据,如己配置的用户、 vhost 等,以及删除所有的持久化消息。执行rabbitmqctl reset 命令前必须停止RabbitMQ应用(比如先执行 rabbitmqctlstop_app) 。 | +| rabbitmqctl force_reset | 强制将 RabbitMQ 节点重置还原到最初状态。不同于 rabbitmqctl reset 命令,rabbitmqctl force reset 命令不论当前管理数据库的状态和集群配置是什么,都会无条件地重置节点。它只能在数据库或集群配置己损坏的情况下使用。与 rabbitmqctl reset命令一样,执行 rabbitmqctl force reset 命令前必须先停止 RabbitMQ 应用。 | +| rabbitmqctl rotate_logs {suffix} | 指示 RabbitMQ 节点轮换日志文件。 RabbitMQ 节点会将原来的日志文件中的内容追加到"原始名称+后缀"的日志文件中,然后再将新的日志内容记录到新创建的日志中(与原日志文件同名)。当目标文件不存在时,会新创建。如果不指定后缀 suffix. 则日志文件只是重新打开而不会进行轮换。 | + +#### 5.4.2 集群管理 + +| 命令 | 作用 | +| ------------------------------------------------ | ------------------------------------------------------------ | +| rabbitmqctl join_cluster {cluster_node} [-- ram] | 将节点加入指定集群中。
在这个命令执行前需要停止RabbitMQ应用并重置节点。 | +| rabbitmqctl cluster_status | 显示集群状态 | +| rabbitmqctl change_cluster_node_type {disc\|ram} | 修改集群节点的类型。
在这个命令执行前需要停止RabbitMQ应用。 | +| rabbitmqctl forget_cluster_node [--offine] | 将节点重集群中删除,允许离线执行 | +| rabbitmqctl update_cluster_nodes {clusternode} | 在集群中的节点应用启动前咨询clusternode节点的最新信息,并更新相应的集群信息。 | +| rabbitmqctl force_boot | 确保节点可以启动,即使它不是最后一个关闭的节点。 | +| rabbitmqctl sync_queue [- p vhost] {queue} | 指示未同步队列 queue 的 slave 镜像可以同步 master 镜像行的内容。同步期间此队列会被阻塞(所有此队列的生产消费者都会被阻塞),直到同步完成。此条命令执行成功的前提是队列queue 配置了镜像。注意 , 未同步队列中的消息被耗尽后 , 最终也会变成同步,此命令主要用于未耗尽的队列。 | +| rabbitmqctl cancel_sync_queue [-p vhost] {queue} | 取消队列queue同步镜像的操作。 | +| rabbitmqctl set_cluster_name {name} | 设置集群名称。集群名称默认是集群中第一个节点的名称。 | + +### 5.5 服务端状态 + +#### 1. 队列状态 + +**命令: rabbitmqctl list_queues [-p vhost]\[queueinfoitem...]** + +此命令返回队列的详细信息,如果无 [-p vhost] 参数,将显示默认的 vhost 为 " / " 中的队列详情 。 queueinfoitem 参数用于指示哪些队列的信息项会包含在结果集中,**结果集的列顺序将匹配参数的顺序** 。 queueinfoitem 可以是下面列表中的任何值 。 + +- **name**: 队列名称 。 +- **durable**: 队列是否持久化 。 +- **auto_delete**: 队列是否自动删除 。 +- **arguments** : 队列的参数。 +- **policy** : 应用到队列上的策略名称 。 +- **pid**: 队列关联的 Erlang 进程的 ID 。 +- **owner_pid**: 处理排他队列连接的 Erlang 进程 D 。 如果此队列是非排他的,此值将为空。 +- **exclusive**: 队列是否是排他的 。 +- **exclusive_consumer_pid**: 订阅到此排他队列的消费者相关的信道关联的 Erlang进程ID。如果此队列是非排他的,此值将为空 。 +- **exclusive_consumer_tag** : 订阅到此排他队列的消费者的 consumerTag 。 如果此队列是非排他的,此值将为空。 +- **messages_ready**: 准备发送给客户端的消息个数 。 +- **messages_unacknowledged**: 发送给客户端但尚未应答的消息个数 。 +- **messages**: 准备发送给客户端和未应答消息的总和 。 +- **messages_ready_ram**: 驻留在内存中 messages_ready 的消息个数 。 +- **messages_unacknowledged_ram**: 驻留在内存中 messages_unacknowledged的消息个数 。 +- **messages_ram** : 驻留在内存中的消息总数 。 +- **messages_persistent** : 队列中持久化消息的个数 。 对于非持久化队列来说总是 0 。 +- **messages_bytes**: 队列中所有消息的大小总和 。 这里不包括消息属性或者任何其他开销。 +- **messages_bytes_ready**: 准备发送给客户端的消息的大小总和。 +- **messages_bytes_unacknowledged**: 发送给客户端但尚未应答的消息的大小总和。 +- **messages_bytes_ram**: 驻留在内存中的 messages_bytes 。 +- **messages_bytes_persistent**: 队列中持久化的 messages_bytes 。 +- **disk_reads**: 从队列启动开始,己从磁盘中读取该队列的消息总次数。 +- **disk_writes**: 从队列启动开始,己向磁盘队列写消息的总次数。 +- **consumer**: 消费者数目。 +- **consumer_utilisation**: 队列中的消息能够立刻投递给消费者的比率,介于 0 和1之间 。这个受网络拥塞或者 Basic.Qos 的影响而小于 1 。 +- **memory**: 与队列相关的 Erlang 进程所消耗的内存字节数,包括栈、堆及内部结构 。 +- **slave_pids**: 如果队列是镜像的 ,列出所有 slave 镜像的 pid 。 +- **synchronised_slave_pids**: 如果队列是镜像的,列出所有己经同步的 slave 镜像的 pid 。 +- **state** : 队列状 态。正常情况下是running : 如果队列正常同步数据可能会有"{syncing, MsgCount}" 的状态;如果队列所在的节点掉线了,则队列显示状态为down (此时大多数的 queueinfoitems 也将不可用〉。 + +**如果没有指定 queueinfoitems ,那么此命令将显示队列的名称和消息的个数。** + + + +#### 2. 交换机状态 + +**命令:rabbitmqctl list_exchanges [-p vhost]\[exchangeinfoitem...]** + +- **name**: 交换器的名称。 +- **type**: 交换器的类型。 +- **durable** : 设置是否持久化。 durable 设置为 true 表示持久化,反之是非持久化。持久化可以将交换器信息存盘 ,而在服务器重启的时候不会丢失相关信息。 +- **auto_delete** : 设直是否自动删除。 +- **internal** : 是否是内置的。 +- **arguments** : 其他一些结构化参数,比如 alternate-exchange 。 +- **policy** : 应用到交换器上的策略名称。 + +**如果没有指定 exchangeinfoitem, 那么此命令将显示交换器的名称和类型。** + + + +#### 3. 绑定状态 + +**命令:rabbitmqctl list_bindings [-p vhost]\[bingdinfoitem...]** + +- **source_name**: 绑定中消息来源的名称。 +- source_kind: 绑定中消息来源的类别。 +- **destination_name**: 绑定中消息目的地的名称。 +- **destination_kind**: 绑定中消息目的地的种类。 +- **routing_key**: 绑定的路由键。 +- **arguments**: 绑定的参数。 + +**如果没有指定 bindinginfoitem,那么将显示所有的条目。** + + + +#### 4. TCP|IP 连接状态 + +**命令: rabbitmqctl list_connections [-p vhost]\[connectioninfoitem...]** + +- **pid**: 与连接相关的 Erlang 进程 ID 。 +- **name**: 连接的名称。 +- **port**: 服务器端口。 +- **host**: 返回反向 DNS 获取的服务器主机名称,或者 IP 地址,或者未启用。 +- **peer_port**: 服务器对端端口。当一个客户端与服务器连接时,这个客户端的端口就是 peer_port 。 +- **peer_host**: 返回反向 DNS 获取的对端主机名称,或者IP地址,或者未启用。 +- **ssl**: 是否启用 SSL 。 +- **ssl_protocol**: SSL 协议,如 tlsv1 。 +- **ssl_key_exchange**: SSL 密钥交换算法,如 rsa 。 +- **ssl_cipher**: SSL 加密算法,如 aes_256_cbc 。 +- **ssl_hash**: SSL 哈希算法,如 sha。 +- **peer_cert_subject**: 对端的 SSL 安全证书的主题,基于RFC4514 的形式。 +- **peer_cert_issuer**: 对端 SSL 安全证书的发行者, 基于RFC4514 的形式 。 +- **peer_cert_validity**: 对端 SSL 安全证书的有效期。 +- **state**: 连接状态,包括 starting 、tuning 、opening、 running 、flow 、blokcing 、blocked 、closing 和closed这几种。 +- **channels**: 该连接中的信道个数。 +- **protocol**: 使用的 AMQP 协议的版本,当前是 {0,9 , 1} 或者 {0, 8 ,0} 。注意,如果客户端请求的是 AMQP 0-9 的连接, RabbitMQ 也会将其视为 0-9-l 。 +- **auth_mechanism**: 使用的 SASL 认证机制,如 PLAIN 、 AMQPLAIN 、 EXTERNAL 、RABBIT-CR-DEMO 等 。 +- **user**: 与连接相关的用户名。 +- **vhost**: 与连接相关的 vhost 的名称。 +- **timeout**: 连接超时/协商的心跳间隔,单位为秒。 +- **frame** max: 最大传输帧的大小,单位为 B 。 +- **channel_max**: 此连接上信道的最大数量。如果值 0 ,则表示无上限,但客户端一般会将 0 转变为 65535 +- **client_properties**: 在建立连接期间由客户端发送的信息属性。 +- **recv_oct**: 收到的字节数。 +- **recv_cnt**: 收到的数据包个数。 +- **send_oct**: 发送的字节数。 +- **end_cnt** : 发送的数据包个数。 +- **send_pend**: 发送队列大小。 +- **connected_at** : 连接建立的时间戳。 + + + +#### 5. 信道状态 + +**命令: rabbitmqctl list_channels [-p vhost]\[channelinfoitem...]** + +- **pid**: 与连接相关的 Erlang 进程 ID 。 +- **connection** : 信道所属连接的 Erlang 进程 ID 。 +- **name** : 信道的名称。 +- **number**: 信道的序号。 +- **user**: 与信道相关的用户名称。 +- **vhost** : 与信道相关的 vhost。 +- **transactional**: 信道是否处于事务模式。 +- **confirm** : 信道是否处于 publisher confirm 模式。 +- **consumer_count** : 信道中的消费者的个数。 +- **messages_unacknowledged**: 己投递但是还未被 ack 的消息个数。 +- **messages_uncommitted** : 己接收但是还未提交事务的消息个数 。 +- **acks_uncommitted** : 己 ack 收到但是还未提交事务的消息个数 。 +- **messages_unconfirmed** : 己发送但是还未确认的消息个数 。 如果信道不处于publisher confmn 模式下 ,则此值为 0 。 +- **perfetch_count** : 新消费者的 Qos 个数限制 。 0 表示无上限 。 +- **global_prefetch_count**: 整个信道的 Qos 个数限制 。 0 表示无上限 。 + + + +#### 6.消费者状态 + +**命令: rabbitmqctl list_consumers [-p vhost]** + + + +#### 7.Brokder的状态 + +**命令: rabbitmqctl status** + +显示 Broker 的状态, 比如当前 Erlang 节点上运行的应用程序、 RabbitMQ/Erlang 的版本信息、 os 的名称 、内存及文件描述符等统计信息。 + + + +#### 8.其他状态 + +**rabbitmqctl node_health_check** +对 RabbitMQ 节点进行健康检查 , 确定应用是否正常运行。 + +**rabbitmqctl environment** +显示每个运行程序环境中每个变量的名称和值。 + +**rabbitmqctl report** +为所有服务器状态生成一个服务器状态报告,井将输出重定向到一个文件。相关示例如下 : + +```shell +[root@nodel -]# rabbitmqctl report > report.txt +``` + +**rabbitmqctl eval {expr}** +执行任意 Erlang 表达式。相关示例如下(示例命令用于返回 rabbitmqctl 连接的节点名称) : + +```shell +[root@nodel - ]# rabbitmqctl eval 'node().' +rabbit@nodel +``` + + + diff --git a/notes/《Redis开发与运维》读书笔记.md b/notes/《Redis开发与运维》读书笔记.md new file mode 100644 index 0000000..514938f --- /dev/null +++ b/notes/《Redis开发与运维》读书笔记.md @@ -0,0 +1,726 @@ +# 《Redis开发与运维》读书笔记 + +## 目录
+第二章 API的理解与使用
+    2.1 预备
+        2.1.1 全局命令
+        2.1.2 数据结构和内部编码
+        2.1.3 单线程架构
+    2.2 字符串
+    2.3 哈希
+    2.4 列表
+    2.5 集合
+    2.6 有序集合
+    2.7 键管理
+        2.7.1 单个键管理
+            1.键重命名
+            2. 随机返回键
+            3.键过期
+        2.7.2 键遍历
+            1. 全量键遍历
+            2. 渐进式遍历
+        2.7.3 数据库管理
+            1.切换数据库
+            2.flushdb/flushall
+第三章 小功能 大用处
+    3.1 慢查询分析
+        3.1.1 慢查询的两个配置参数
+    3.2 redis shell
+        3.2.1 redis-cli
+        3.2.2 redis-server
+        3.2.3 redis-benchmark
+    3.3 Pipeline
+    3.4 事务与Lua
+    3.5 Bitmaps
+    3.6 HyperLogLog
+    3.7 发布订阅
+    3.8 GEO
+第四章 客户端
+    4.4 客户端管理
+        4.4.1 客户端API
+        4.4.2 客户端相关配置
+        4.4.3 客户端统计片段
+第五章 持久化
+    5.1 RDB
+    5.2 AOF
+第六章 复制
+    6.1 配置
+        6.1.1 建立复制
+        6.1.2 断开复制
+        6.1.3 传输延迟
+第十章 集群
+    10.1 数据分区
+    10.2 搭建集群
+    10.4 集群扩容
+        10.4.2 扩容集群
+        10.4.3 收缩集群
+附:[CacheCloud GitHub](https://github.com/sohutv/cachecloud)
+## 正文
+ + +## 第二章 API的理解与使用 + +### 2.1 预备 + +#### 2.1.1 全局命令 + +1. 查看所有键: **keys \*** + +2. 查看键总数:**dbsize** + +3. 检查键是否存在:**exists key** + +4. 删除键:**del key [key ...]** 支持删除多个键 + +5. 键过期:**expire key seconds** + + ttl命令会返回键的剩余过期时间, 它有3种返回值: + + - 大于等于0的整数: 键剩余的过期时间。 + - -1: 键没设置过期时间。 + - -2: 键不存在 + +6. 键的数据结构 **type key** + +#### 2.1.2 数据结构和内部编码 + +type命令实际返回的就是当前键的数据结构类型, 它们分别是:**string**(字符串) 、 **hash**(哈希) 、 **list**(列表) 、 **set**(集合) 、 **zset**(有序集合) + +#### 2.1.3 单线程架构 + +1. 纯内存访问, Redis将所有数据放在内存中, 内存的响应时长大约为100纳秒, 这是Redis达到每秒万级别访问的重要基础。 +2. 非阻塞I/O, Redis使用epoll作为I/O多路复用技术的实现, 再加上Redis自身的事件处理模型将epoll中的连接、 读写、 关闭都转换为事件, 不在网络I/O上浪费过多的时间, 如图2-6所示。 +3. 单线程避免了线程切换和竞态产生的消耗。 + +### 2.2 字符串 + +| 作用 | 格式 | 参数或示例 | +| ---------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | +| 设置值 | set key value \[ex seconds]\[px milliseconds][nx\|xx] setnx setex | ex seconds: 为键设置秒级过期时间。
px milliseconds: 为键设置毫秒级过期时间。
nx: 键必须不存在, 才可以设置成功, 用于添加。
xx: 与nx相反, 键必须存在, 才可以设置成功, 用于更新。 | +| 获取值 | get key | r如果获取的键不存在 ,则返回nil(空) | +| 批量设置 | mset key value [key value ...] | mset a 1 b 2 c 3 d 4 | +| 批量获取值 | mget key [key ...] | mget a b c d | +| 计数 | incr key decr key incrby key increment(指定数值自增)
decrby key decrement(指定数值自减)
incrbyfloat key increment (浮点数自增) | 值不是整数, 返回错误。 值是整数, 返回自增或自减后的结果。
键不存在,创建键,并按照值为0自增或自减, 返回结果为1。 | +| 追加值 | append key value | 向字符串的默认追加值 | +| 字符串长度 | strlen key | 获取字符串长度,中文占用三个字节 | +| 设置并返回原值 | getset key value | | +| 设置指定位置的租字符串 | setrange key offeset value | | +| 获取部分字符串 | getrange key start end | | + +### 2.3 哈希 + +| 作用 | 格式 | 参数或示例 | +| ------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | +| 设置值 | hset key field value | hset user:1 name tom
hset user:1 age 12 | +| 获取值 | hget key field | hget user:1 name | +| 删除field | hdel key field [field ...] | | +| 计算field个数 | hlen key | | +| 批量设置或获取field-value | hmget key field [field]
hmset key field value [field value...] | hmset user:1 name mike age 12 city tianjin
hmget user:1 name city | +| 判断field是否存在 | hexists key field | | +| 获取所有field | hkeys key | | +| 获取所有value | hvals key | | +| 获取所有的filed-value | hgetall key | 如果哈希元素个数比较多, 会存在阻塞Redis的可能。
获取全部 可以使用hscan命令, 该命令会渐进式遍历哈希类型 | +| 计数 | hincrby key field
hincrbyfloat key field | | + +### 2.4 列表 + +| 作用 | 格式 | 参数或示例 | +| -------- | ------------------------------------------------------------ | ------------------------------------------------------------ | +| 增 | 左侧插入:lpush key value [value ...]
右侧插入:rpush key value [value ...]
某个指定元素前后插入:linsert key before\|after pivot value | | +| 查 | 获取指定范围内的元素列表:lrange key start end
获取列表指定索引下标的元素:lindex key index
获取列表指定长度:llen key | lrange listkey 0 -1 | +| 删 | 从列表左侧弹出元素:lpop key
从列表右侧弹出元素:rpop key
删除指定元素:lrem key count value
截取列表:ltrim key start end | count>0, 从左到右, 删除最多count个元素。
count<0, 从右到左, 删除最多count绝对值个元素。
count=0, 删除所有 | +| 改 | 修改指定索引下标的元素:lset key index newValue | | +| 阻塞操作 | blpop key [key ...] timeout
brpop key [key ...] timeout | key[key...]: 多个列表的键。 timeout: 阻塞时间\|等待时间(单位: 秒) | + + + +### 2.5 集合 + +集合(set) 类型也是用来保存多个的字符串元素, 但和列表类型不一样的是, **集合中不允许有重复元素**, 并且集合中的元素是无序的, **不能通过索引下标获取元素**。 + +**集合内操作**: + +| 作用 | 格式 | 参数或示例 | +| -------------------- | ------------------------------ | ----------------------------------------- | +| 添加元素 | sadd key element [element ...] | 返回结果为添加成功的元素个数 | +| 删除元素 | srem key element [element ...] | 返回结果为成功删除的元素个数 | +| 计算元素个数 | scard key | | +| 判断元素是否在集合中 | sismember key element | | +| 随机返回 | srandmember key [count] | 随机从集合返回指定个数元素,count 默认为1 | +| 从集合随机弹出元素 | spop key | srandmember 不会从集合中删除元素,spop 会 | +| 获取集合中所有元素 | smembers key | 可用sscan 代替 | + +**集合间操作**: + +| 作用 | 格式 | +| ---------------------------- | ------------------------------------------------------------ | +| 求多个集合的交集 | sinter key [key ...] | +| 求多个集合的并集 | suinon key [key ...] | +| 求多个集合的差集 | sdiff key [key ...] | +| 将交集、并集、差集的结果保存 | sinterstore destination key [key ...]
suionstore destination key [key ...]
sdiffstore destination key [key ...] | + +### 2.6 有序集合 + +有序集合中的元素可以排序。 但是它和列表使用索引下标作为排序依据不同的是, 它给每个元素设置一个分数(score) 作为排序的依据。 + +**集合内操作**: + +| 作用 | 格式 | 参数或示例 | +| ------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | +| 添加成员 | zadd key score member [score member ...] | nx: member必须不存在, 才可设置成功, 用于添加。
xx: member必须存在, 才可以设置成功, 用于更新。
ch: 返回此次操作后, 有序集合元素和分数发生变化的个数
incr: 对score做增加, 相当于后面介绍的zincrby。 | +| 计算成员个数 | zcard key | | +| 计算某个成员的分数 | zscore key member | | +| 计算某个成员的排名 | zrank key member zrevrank key member | zrank是从分数从低到高返回排名, zrevrank反之。 | +| 删除成员 | zrem key member [member ...] | | +| 增加成员分数 | zincrby key increment member | zincrby user:ranking 9 tom | +| 返回指定排名范围的成员 | zrange key start end [withscores] zrange key start end [withscores] | zrange是从低到高返回, zrevrange反之。 | +| 返回指定分数范围内的成员 | zrangebyscore key min max \[withscores][limit offset count]
zrevrangebyscore key max min \[withscores][limit offset count] | 其中zrangebyscore按照分数从低到高返回, zrevrangebyscore反之。 [limit offset count]选项可以限制输出的起始位置和个数: 同时min和max还支持开区间(小括号) 和闭区间(中括号) , -inf和+inf分别代表无限小和无限大 | +| 删除指定排名内的升序元素 | zremrangerank key start end | | +| 删除指定分数范围的成员 | zremrangebyscore key min max | | + +**集合间操作**: + +| 作用 | 格式 | +| ---- | ------------------------------------------------------------ | +| 交集 | zinterstore destination numkeys key \[key ...] [weights weight [weight ...]] \[aggregate sum\|min\|max] | +| 并集 | zunionstore destination numkeys key \[key ...] [weights weight [weight ...]] \[aggregate sum\|min\|max] | + +- destination: 交集计算结果保存到这个键。 +- numkeys: 需要做交集计算键的个数。 +- key[key...]: 需要做交集计算的键。 +- weights weight[weight...]: 每个键的权重, 在做交集计算时, 每个键中的每个member会将自己分数乘以这个权重, 每个键的权重默认是1。 +- aggregate sum|min|max: 计算成员交集后, 分值可以按照sum(和) 、min(最小值) 、 max(最大值) 做汇总, 默认值是sum。 + +### 2.7 键管理 + +#### 2.7.1 单个键管理 + +##### 1.键重命名 + +**rename key newkey** + + 为了防止被强行rename, Redis提供了renamenx命令, 确保只有newKey不存在时候才被覆盖。 + +##### 2. 随机返回键 + + **random key** + +##### 3.键过期 + +- expire key seconds: 键在seconds秒后过期。 +- expireat key timestamp: 键在秒级时间戳timestamp后过期。 +- pexpire key milliseconds: 键在milliseconds毫秒后过期。 +- pexpireat key milliseconds-timestamp键在毫秒级时间戳timestamp后过期 + +注意: + +1. 如果expire key的键不存在, 返回结果为0 +2. 如果设置过期时间为负值, 键会立即被删除, 犹如使用del命令一样 +3. persist key t命令可以将键的过期时间清除 +4. 对于字符串类型键, 执行set命令会去掉过期时间, 这个问题很容易在开发中被忽视 +5. Redis不支持二级数据结构(例如哈希、 列表) 内部元素的过期功能, 例如不能对列表类型的一个元素做过期时间设置 +6. setex命令作为set+expire的组合, 不但是原子执行, 同时减少了一次网络通讯的时间 + +#### 2.7.2 键遍历 + +##### 1. 全量键遍历 + +**keys pattern** + +##### 2. 渐进式遍历 + +scan cursor \[match pattern] \[count number] + +- cursor是必需参数, 实际上cursor是一个游标, 第一次遍历从0开始, 每次scan遍历完都会返回当前游标的值, 直到游标值为0, 表示遍历结束。 +- match pattern是可选参数, 它的作用的是做模式的匹配, 这点和keys的模式匹配很像。 +- count number是可选参数, 它的作用是表明每次要遍历的键个数, 默认值是10, 此参数可以适当增大。 + +#### 2.7.3 数据库管理 + +##### 1.切换数据库 + +**select dbIndex** + +##### 2.flushdb/flushall + +flushdb/flushall命令用于清除数据库, 两者的区别的是flushdb只清除当前数据库, flushall会清除所有数据库。 + + + +## 第三章 小功能 大用处 + +### 3.1 慢查询分析 + +#### 3.1.1 慢查询的两个配置参数 + +```shell +# 设置慢查询阈值 耗时高于阈值的操作将被记录 +config set slowlog-log-slower-than 20000 +# 设置慢查询维护日志的记录长度 +config set slowlog-max-len 1000 +# 将配置持久化到本地配置文件 +config rewrite + +# 获取慢查询日志 +slowlog get [n] +# 获取慢查询日志当前的长度 +slowlog len +# 慢查询日志重置 +slowlog reset +``` + + + +### 3.2 redis shell + +#### 3.2.1 redis-cli + +```shell +# 1. -r (repeat)选项代表将执行多次命令 +redis-cli -r 3 ping + +# 2. -i (interval) 选项代表每隔几秒执行一次命令 +redis-cli -r 5 -i 1 ping + +# 3. -x 选项代表从标准输入(stdin)读取数据作为redis-cli 的最后一个参数 +echo "world" | redis-cli -x set hello + +# 4. --salve 选项是把当前客户端模拟成当前Redis节点的从节点,可以用来获取当前Redis节点的更新操作 +redis-cli --salve + +# 5. --stat 选项可以实时获取Redis的重要统计信息 +redis-cli --stat + +# --no-raw 选项要求命令的返回结果必须是原始的格式 +# --raw 返回格式化之后的结果 +$ redis-cli set hello "你好" +$ redis-cli get hello + "\xe4\xbd\xa0\xe5\xa5\xbd" +$redis-cli --no-raw get hello + "\xe4\xbd\xa0\xe5\xa5\xbd" +$redis-cli --raw get hello + 你好 +``` + +#### 3.2.2 redis-server + +```shell +# --test-memory 选项用来检测当前操作系统能否稳定地分配指定容量给Redis +redis-server --test-memory 1024 +``` + +#### 3.2.3 redis-benchmark + +```shell +# 1. -c (client) 选项代表客户端的并发数量(默认是50) +# 2. -n (num)选项代表客户端请求总量(默认是100000) +redis-benchmark -c 100 -n 20000 代表100个客户端同时请求Redis,一共执行20000次 + +# 3.-q 选项仅仅显示 redis-benchmark 的 requests per second 信息 +redis-benchmark -c 100 -n 20000 -g + +# 4.-r 执行 redis-benchmark 的时候插入更多随机的键 +redis-benchmark -c 100 -n 20000 -r 10000 +# -r 选项会在key,count键上加一个12位的后缀,-r 10000 代表只对后四位做随机处理(-r 不是随机数的个数) + +# 5.-P选项代表每个请求pipeline的数据量(默认为1) + +# 6.-k +# -k 选项代表客户端是否使用keepalive,1为使用,0为不使用,默认值为 1。 + +# 7.-t 选项可以对指定命令进行基准测试 +redis-benchmark -t get,set -q + +# 8.--csv 选项会将结果按照csv格式输出,便于后续处理 +``` + + + +### 3.3 Pipeline + +原生批量命令与Pipeline对比 : + +- 原生批量命令是原子的, Pipeline是非原子的。 +- 原生批量命令是一个命令对应多个key, Pipeline支持多个命令。 +- 原生批量命令是Redis服务端支持实现的, 而Pipeline需要服务端和客户端的共同实现。 + + + +### 3.4 事务与Lua + +1. **multi**命令代表事务开始, **exec**命令代表事务结束 ,如果要停止事务的执行, 可以使用**discard**命令代替exec命令即可。 +2. **Redis并不支持回滚功能** 。 +3. 有些应用场景需要在事务之前, 确保事务中的key没有被其他客户端修改过, 才执行事务, 否则不执行(类似乐观锁) , Redis提供了**watch**命令来解决这类问题 。 + + + +### 3.5 Bitmaps + +```shell +# 1.设置值 +setbit key offset value + +# 2.获取值 +getbit key offset + +# 3.获取BitMaps指定范围值为1的个数 +bitcount [start][end] + +# 4.Bitmaps 间的运算 +bitop op destkey key[key...] +# op 可以为 and (交集)、or(并集)、not(非)、xor(异或)操作 + +# 计算Bitmaps中第一个值为targetBit的偏移量 +bitpos key tartgetBit [start] [end] +``` + +### 3.6 HyperLogLog + +通过HyperLogLog可以利用极小的内存空间完成独立总数的统计 。 + +```shell +# 1.添加 +pfadd key element [element …] + +# 2.计算独立用户数 +pfcount key [key …] + +# 3.合并 +pfmerge destkey sourcekey [sourcekey ...] +``` + +### 3.7 发布订阅 + +```shell +# 1. 发布消息 +publish channel message + +# 2.订阅消息 +subscribe channel [channel ...] + +# 3.取消订阅 +unsubscribe [channel [channel ...]] + +# 4.按照模式订阅和取消订阅 +psubscribe pattern [pattern...] +punsubscribe [pattern [pattern ...]] + +# 查看活跃的频道(指当前频道至少有一个订阅者) +pubsub channels [pattern] + +# 查看频道订阅数 +pubsub numsub [channel ...] + +# 查看模式订阅数 +pubsub numpat +``` + +### 3.8 GEO + +``` +# 1.增加地理位置信息 +geoadd key longitude latitude member [longitude latitude member ...] +geoadd cities:locations 116.28 39.55 beijing + +# 2.获取地理位置信息 +geopos key member [member ...] +geopos cities:locations tianjin + +# 删除地理位置信息 +zrem key member +``` + + + +## 第四章 客户端 + +### 4.4 客户端管理 + +#### 4.4.1 客户端API + +**1.client list** + +```shell +127.0.0.1:6379> client list +id=1610 addr=10.0.2.2:58879 fd=9 name= age=2169 idle=1590 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=scan +id=1612 addr=10.0.2.2:59560 fd=10 name=heibairuiwen age=1642 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=32742 obl=0 oll=0 omem=0 events=r cmd=client +``` + +| 参数 | 含义 | +| --------- | ------------------------------------------------------------ | +| id | 客户端连接id | +| addr | 客户端连接IP和端口 | +| fd | socket 的文件描述符 | +| name | 客户端连接名
client setName 设置当前客户端名字;
client getName 获取当前客户端名字 | +| age | 客户端连接存活时间 | +| idle | 客户端连接空闲时间 | +| flags | 客户端类型类型标识 | +| db | 当前客户端正在使用的数据库索引下标 | +| sub/psub | 当前客户端订阅的频道或者模式 | +| multi | 当前事务中已执行命令个数 | +| qbuf | 输入缓冲区总容量
输入缓冲区会根据内容动态调整,但大小不能超过1G | +| qbuf-free | 输入缓冲区剩余容量 | +| obl | 输出固定缓冲区的长度 | +| oll | 输出动态缓冲区列表长度 | +| omem | 固定缓冲区和动态缓冲区使用的容量。输出缓冲区的容量控制:
client-output-buffer-limit \ \ \ \
\: 客户端类型分为三种。normal: 普通客户端;slave: slave客户端, 用于复制;pubsub: 发布订阅客户端。
\: 如果客户端使用的输出缓冲区大于\, 客户端会被立即关闭。
\和\: 如果客户端使用的输出缓冲区超过了\并且持续了\秒, 客户端会被立即关闭。
示例:client-output-buffer-limit normal 20mb 10mb 120 | +| events | 文件描述符事作件(r/w): r 和 w 分别代表客户端套接字可读和可写 | +| cmd | 当前客户端最后一次执行的命令,不包含参数 | +**2.客户端的限制maxclients和timeout** + +Redis提供了maxclients参数来限制最大客户端连接数, 一旦连接数超过maxclients, 新的连接将被拒绝。 maxclients默认值是10000, 可以通过info clients来查询当前Redis的连接数。 + +可以通过config set maxclients对最大客户端连接数进行动态设置。 + +**3.client kill** + +client kill ip:port 此命令用于杀掉指定IP地址和端口的客户端。 + +**4.client pause** + +client pause timeout(毫秒) client pause命令用于阻塞客户端timeout毫秒数, 在此期间客户端连接将被阻塞。 + +**5.monitor** + +monitor命令用于监控Redis正在执行的命令。monitor命令能够监听其他客户端正在执行的命令, 并记录了详细的时间戳。 + +#### 4.4.2 客户端相关配置 + +1. **timeout**: 检测客户端空闲连接的超时时间, 一旦idle时间达到了timeout, 客户端将会被关闭, 如果设置为0就不进行检测。 +2. **tcp-keepalive**: 检测TCP连接活性的周期, 默认值为0, 也就是不进行检测, 如果需要设置, 建议为60, 那么Redis会每隔60秒对它创建的TCP连接进行活性检测, 防止大量死连接占用系统资源。 +3. **tcp-backlog**: TCP三次握手后, 会将接受的连接放入队列中, tcpbacklog就是队列的大小, 它在Redis中的默认值是511。 + +#### 4.4.3 客户端统计片段 + +**info clients** + +1. **connected_clients**: 代表当前Redis节点的客户端连接数, 需要重点监控, 一旦超过maxclients, 新的客户端连接将被拒绝。 +2. **client_longest_output_list**: 当前所有输出缓冲区中队列对象个数的最大值。 +3. **client_biggest_input_buf**: 当前所有输入缓冲区中占用的最大容量。 +4. **blocked_clients**: 正在执行阻塞命令(例如blpop、 brpop、brpoplpush) 的客户端个数。 + + + +## 第五章 持久化 + +### 5.1 RDB + +RDB 持久化是把当前进程数据生成快照保存到硬盘的过程。触发RBD持久化的过程分为手动触发和自动触发。 + +**手动触发**: + +```shell +# 手动触发(阻塞) +save +# 手动触发(非阻塞) +bgsave +``` + +**自动触发**: + +1. 使用save相关配置, 如“save m n”。 表示m秒内数据集存在n次修改时, 自动触发bgsave。 +2. 如果从节点执行全量复制操作, 主节点自动执行bgsave生成RDB文件并发送给从节点。 +3. 执行debug reload命令重新加载Redis时, 也会自动触发save操作。 +4. 默认情况下执行shutdown命令时, 如果没有开启AOF持久化功能则自动执行bgsave。 + +**保存**: + + RDB文件保存在dir配置指定的目录下, 文件名通过**dbfilename**配置指定。 可以通过执行**config set dir{newDir}**和**config setdbfilename{newFileName}**运行期动态执行, 当下次运行时RDB文件会保存到新目录。 + +**缺点**: + +无法做到实时持久化/秒级持久化,重量级操作,频繁执行成本高 + +### 5.2 AOF + +AOF 持久化以独立日志的方式记录每次写命令,重启时在重新执行AOF文件中的命令达到恢复数据的目的。 + +1. 开启AOF功能需要设置配置: **appendonly yes**, 默认不开启。 +2. AOF文件名通过**appendfilename**配置设置, 默认文件名是appendonly.aof。 +3. 保存路径同RDB持久化方式一致, 通过dir配置指定。 + +**1. AOF缓冲区同步文件策略**: + +| 可配置值 | 说明 | +| -------------- | ------------------------------------------------------------ | +| always | 命令写入aof_buf后调用系统fsyn操作同步到AOF文件,fsync 完成后线程返回 | +| everysec(常用) | 每秒执行一次 | +| no | 同步由操作系统控制 | + +**2. AOF重写过程**: + +**手动触发**:直接调用**bgrewriteaof**命令 + +**自动触发**:根据**auto-aof-rewrite-min-size**和**auto-aof-rewrite-percentage**参数确定自动触发时机。 + +- auto-aof-rewrite-min-size: 表示运行AOF重写时文件最小体积, 默认为64MB。 +- auto-aof-rewrite-percentage: 代表当前AOF文件空间(aof_current_size) 和上一次重写后AOF文件空间(aof_base_size) 的比值。 + +**3. 错误修复** + +对于错误格式的AOF文件, 先进行备份, 然后采用**redis-check-aof--fix**命令进行修复, 修复后使用diff-u对比数据的差异, 找出丢失的数据, 有些可以人工修改补全。 + + + +## 第六章 复制 + +### 6.1 配置 + +#### 6.1.1 建立复制 + +三种方式: + +1. 在配置文件中加入slaveof {masterHost} {masterPort}随Redis启动生效。 +2. 在redis-server启动命令后加入--slaveof {masterHost} {masterPort}生效。 +3. 直接使用命令: slaveof {masterHost} {masterPort}生效。 + +```shell +127.0.0.1:6380>slaveof 127.0.0.1 6379 +``` + +#### 6.1.2 断开复制 + +在从节点执行**slaveof no one**来断开与主节点复制关系。 从节点断开复制后**并不会抛弃原有数据**, 只是无法再获取主节点上的数 +据变化。 + +通过slaveof命令还可以实现切主操作, 所谓切主是指把当前从节点对主节点的复制切换到另一个主节点。 执行slaveof{newMasterIp} {newMasterPort}命令即可。**会清除原有数据**。 + +注:**默认情况下, 从节点使用slave-read-only=yes配置为只读模式**。 + +#### 6.1.3 传输延迟 + +Redis为我们提供了**repl-disable-tcp-nodelay**参数用于控制是否关闭TCP_NODELAY, 默认关闭, 说明如下: + +- 当关闭时, 主节点产生的命令数据无论大小都会及时地发送给从节点, 这样主从之间延迟会变小, 但增加了网络带宽的消耗。 适用于主从之间的网络环境良好的场景, 如同机架或同机房部署。 +- 当开启时, 主节点会合并较小的TCP数据包从而节省带宽。 默认发送时间间隔取决于Linux的内核, 一般默认为40毫秒。 这种配置节省了带宽但增大主从之间的延迟。 适用于主从网络环境复杂或带宽紧张的场景, 如跨机房部署。 + + + +## 第十章 集群 + +### 10.1 数据分区 + +Redis Cluser采用虚拟槽分区, 所有的键根据哈希函数映射到0~16383整数槽内, 计算公式: slot=CRC16(key) &16383。 每一个节点负责维护一部分槽以及槽所映射的键值数据。 + +### 10.2 搭建集群 + +**用redis-trib.rb搭建集群** : + +**1.Ruby环境准备** + +```shell +# 安装Ruby: +wget https:// cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.1.tar.gz +tar xvf ruby-2.3.1.tar.gz +cd ruby-2.3.1 +./configure -prefix=/usr/local/ruby +make +make install +cd /usr/local/ruby +sudo cp bin/ruby /usr/local/bin +sudo cp bin/gem /usr/local/bin + +# 安装rubygem redis依赖: +wget http:// rubygems.org/downloads/redis-3.3.0.gem +gem install -l redis-3.3.0.gem +gem list --check redis gem + +# 安装redis-trib.rb: +sudo cp /{redis_home}/src/redis-trib.rb /usr/local/bin + +# 执行确认 +# redis-trib.rb +``` + +**2.节点配置** + +```shell +#节点端口 +port 6379 +# 开启集群模式 +cluster-enabled yes +# 节点超时时间, 单位毫秒 +cluster-node-timeout 15000 +# 集群内部配置文件 +cluster-config-file "nodes-6379.conf" +``` + +```shell +redis-server conf/redis-6481.conf +redis-server conf/redis-6482.conf +redis-server conf/redis-6483.conf +redis-server conf/redis-6484.conf +redis-server conf/redis-6485.conf +redis-server conf/redis-6486.conf +``` + +**3.创建集群** + +```shell +redis-trib.rb create --replicas 1 127.0.0.1:6481 127.0.0.1:6482 127.0.0.1:6483 +127.0.0.1:6484 127.0.0.1:6485 127.0.0.1:6486 +``` + +--replicas参数指定集群中每个主节点配备几个从节点, 这里设置为1。 + +**4.集群完整性检查** + +集群完整性指所有的槽都分配到存活的主节点上, 只要16384个槽中有一个没有分配给节点则表示集群不完整。 可以使用redis-trib.rb check命令检测之前创建的两个集群是否成功, check命令只需要给出集群中任意一个节点地址就可以完成整个集群的检查工作 . + +```shell +redis-trib.rb check 127.0.0.1:6379 +redis-trib.rb check 127.0.0.1:6481 +``` + +### 10.4 集群扩容 + +#### 10.4.2 扩容集群 + +**步骤:** + +```shell +# 准备新的节点 +redis-server conf/redis-6385.conf +redis-server conf/redis-6386.conf + +# 加入集群 +redis-trib.rb add-node 127.0.0.1:6385 127.0.0.1:6379 +redis-trib.rb add-node 127.0.0.1:6386 127.0.0.1:6379 + +# 槽重新分片 +redis-trib.rb reshard 127.0.0.1:6379 + +#要把节点6386作为6385的从节点, 从而保证整个集群的高可用。 +127.0.0.1:6386>cluster replicate 1a205dd8b2819a00dd1e8b6be40a8e2abe77b756 + +#查看情况验证 +cluster nodes +``` + +槽重新分片: + +```shell +redis-trib.rb reshard host:port --from --to --slots --yes --timeout + --pipeline +``` + +参数说明: + +- host: port: 必传参数, 集群内任意节点地址, 用来获取整个集群信息。 +- --from: 制定源节点的id, 如果有多个源节点, 使用逗号分隔, 如果是all源节点变为集群内所有主节点, 在迁移过程中提示用户输入。 +- --to: 需要迁移的目标节点的id, 目标节点只能填写一个, 在迁移过程中提示用户输入。 +- --slots: 需要迁移槽的总数量, 在迁移过程中提示用户输入。 +- --yes: 当打印出reshard执行计划时, 是否需要用户输入yes确认后再执行reshard。 +- --timeout: 控制每次migrate操作的超时时间, 默认为60000毫秒。 + +#### 10.4.3 收缩集群 + +下线节点: + +```shell +redistrib.rb del-node{host: port}{downNodeId} +``` + + + +## 附:[CacheCloud GitHub](https://github.com/sohutv/cachecloud) + diff --git a/notes/《javascript高级程序设计》读书笔记.md b/notes/《javascript高级程序设计》读书笔记.md new file mode 100644 index 0000000..f77bbaa --- /dev/null +++ b/notes/《javascript高级程序设计》读书笔记.md @@ -0,0 +1,3663 @@ +# 《JavaScript 高级程序设计》读书笔记 + +## 目录
+第三章 基本语法
+第四章 变量、作用域和内存问题
+第五章 应用类型
+    1. Array 类型
+    2. RegExp 类型
+    3. Function 类型
+    4. String 类型
+第六章 面向对象的程序设计
+    6.1 理解对象
+         1. 属性类型
+        2. 读取属性
+    6.2 创建对象
+        1. 构造函数模式
+        2. 原型模式
+        3.组合使用构造函数和原型模式(主要使用方式)
+    6.3 继承
+        1. 原型链
+        2. 组合继承(主要使用方式)
+        3. 原型链继承
+第七章 函数表达式
+第八章 BOM
+    1.window 对象
+    2.location 对象
+第十章 DOM
+    1. Node 类型
+    2.Document类型
+    3.Element类型
+    4.Text类型
+    5.动态创建表格
+第十一章 DOM扩展
+第十二章 DOM2和DOM3
+第十三 事件
+    13.1 DOM事件流
+    13.2 事件处理程序
+        1. DOM0 级事件处理程序
+        2. DOM2 级事件处理程序
+        3.兼容IE
+    13.3 事件处理程序
+    13.4 事件类型
+        1.UI 事件
+        2.焦点事件
+        3.鼠标和滚轮事件
+        4.键盘与文本事件
+        5.变动事件
+        6.HTML5事件
+    第十四章 表单脚本
+    14.1 表单基础知识
+        1.提交表单
+        2.重置表单
+        3.表单字段
+            3.1 共有的表单字段属性
+            3.2 共有的表单字段方法
+            3.3 共有的表单字段事件
+    14.2 文本框脚本
+        1. 选择文本
+        2. 过滤输入(**keypress**)
+        3.自动切换焦点
+        4. HTML5 验证约束API
+    14.3 选择框脚本
+        1.选择选项
+        2.增加选项
+        3.移除选项
+        4.移动和重排选项
+    14.4 表单序列化
+    第十六章 HTML5 脚本编程
+    1.媒体元素
+    第十七章 错误处理
+第二十章 JSON
+    20.1 语法
+    20.2 json序列化与反序列化
+        1.序列化
+        2.序列化选项
+            2.1 过滤结果
+            2.2 toJSON()方法
+            2.3 解析选项
+第二十二章 高级技巧
+    22.1 高级函数
+        1.安全类型检测
+        2.作用域安全的构造函数
+        3.函数绑定
+    22.2 防篡改对象
+        1.不可扩展对象
+        3.冻结的对象
+    22.3 高级定时器
+        1.函数节流
+第二十三章 离线应用与客户端存储
+    23.1 离线检测
+    23.2 数据存储
+        1.Cookie
+        2.web存储机制
+            2.1 Storage 类型
+            2.2 sessionStorage 对象
+            2.3 localStorage 对象
+## 正文
+ + + + +## 第三章 基本语法 + +1. 由于保存浮点数值需要的内存空间是保存整数值的两倍,因此 ECMAScript 会不失时机地将浮点数值转换为整数值。显然,如果小数点后面没有跟任何数字,那么这个数值就可以作为整数值来保存。同样地,如果浮点数值本身表示的就是一个整数(如 1.0),那么该值也会被转换为整数,如下面的例子所示: + + ```javascript + var floatNum1 = 1.; // 小数点后面没有数字——解析为 1 + var floatNum2 = 10.0; // 整数——解析为 10 + ``` + +2. 为了消除在使用parseInt() 函数时可能导致的上述困惑,可以为这个函数提供第二个参数:转换时使用的基数(即多少进制)。如果知道要解析的值是十六进制格式的字符串,那么指定基数 16 作为第二个参数,可以保证得到正确的结果,例如:var num = parseInt("0xAF", 16); //175实际上,如果指定了 16 作为第二个参数,字符串可以不带前面的"0x",如下所示: + + ```javascript + var num1 = parseInt("AF", 16); //175 + var num2 = parseInt("AF"); //NaN + ``` + +3. 多数情况下,调用 toString()方法不必传递参数。但是,在调用数值的 toString()方法时,可以传递一个参数:输出数值的基数。默认情况下, toString()方法以十进制格式返回数值的字符串表示。而通过传递基数,toString()可以输出以二进制、八进制、十六进制,乃至其他任意有效进制格式表示的字符串值。下面给出几个例子: + + ```javascript + var num = 10; + alert(num.toString()); // "10" + alert(num.toString(2)); // "1010" + alert(num.toString(8)); // "12" + alert(num.toString(10)); // "10" + alert(num.toString(16)); // "a" + ``` + + +4. 在不知道要转换的值是不是 null 或 undefined 的情况下,还可以使用转型函数 String(),这个函数能够将任何类型的值转换为字符串。 String()函数遵循下列转换规则: + + + 如果值有 toString()方法,则调用该方法(没有参数)并返回相应的结果; + + - 如果值是 null,则返回"null"; + - 如果值是 undefined,则返回"undefined"。 + 下面再看几个例子: + + ```javascript + var value1 = 10; + var value2 = true; + var value3 = null; + var value4; + alert(String(value1)); // "10" + alert(String(value2)); // "true" + alert(String(value3)); // "null" + alert(String(value4)); // "undefined" + ``` + +5. Object 的每个实例都具有下列属性和方法。 + + + **constructor**:保存着用于创建当前对象的函数。对于前面的例子而言,构造函数(constructor)就是 Object()。 + + + **hasOwnProperty(propertyName)**:用于检查给定的属性在当前对象实例中(而不是在实例的原型中)是否存在。其中,作为参数的属性名(propertyName)必须以字符串形式指定(例如: o.hasOwnProperty("name"))。 + + + **isPrototypeOf(object)**:用于检查传入的对象是否是传入对象的原型(第 5 章将讨论原型)。 + + + **propertyIsEnumerable(propertyName)**:用于检查给定的属性是否能够使用 for-in 语句(本章后面将会讨论)来枚举。与 hasOwnProperty()方法一样,作为参数的属性名必须以字符 + 串形式指定。 + + + **toLocaleString()**:返回对象的字符串表示,该字符串与执行环境的地区对应。 + + + **toString()**:返回对象的字符串表示。 + + + **valueOf()**:返回对象的字符串、数值或布尔值表示。通常与 toString()方法的返回值相同。 + +

+ + +## 第四章 变量、作用域和内存问题 + +1. **ECMAScript 中所有函数的参数都是按值传递的。** + + ```javascript + function setName(obj) { + obj.name = "Nicholas"; + } + var person = new Object(); + setName(person); + alert(person.name); //"Nicholas" + ``` + + ```javascript + function setName(obj) { + obj.name = "Nicholas"; + obj = new Object(); + obj.name = "Greg"; + } + var person = new Object(); + setName(person); + alert(person.name); //"Nicholas" + ``` + + + +## 第五章 应用类型 + +### 1. Array 类型 + +1. 数组的 length 属性很有特点——它不是只读的。因此,通过设置这个属性,可以从数组的末尾移除项或向数组中添加新项。请看下面的例子: + + ```javascript + var colors = ["red", "blue", "green"]; // 创建一个包含 3 个字符串的数组 + colors.length = 2; + alert(colors[2]); //undefined + ``` + + 这个例子中的数组 colors 一开始有 3 个值。将其 length 属性设置为 2 会移除最后一项(位置为2 的那一项),结果再访问 colors[2]就会显示 undefined 了。如果将其 length 属性设置为大于数组项数的值,则新增的每一项都会取得 undefined 值,如下所示: + + ```javascript + var colors = ["red", "blue", "green"]; // 创建一个包含 3 个字符串的数组 + colors.length = 4; + alert(colors[3]); //undefined + ``` + +2. 数组常用方法 + + ```javascript + var colors = ["red", "blue", "green"]; + + alert(colors.toString()); // red,blue,green + alert(colors.valueOf()); // red,blue,green + alert(colors); // red,blue,green + + alert(colors.join(",")); //red,green,blue + alert(colors.join("||")); //red||green||blue + + + var colors = new Array(); // 创建一个数组 + var count = colors.push("red", "green"); // 推入两项 + alert(count); //2 + count = colors.push("black"); // 推入另一项 + alert(count); //3 + var item = colors.pop(); // 取得最后一项 + alert(item); //"black" + alert(colors.length); //2 + + var item = colors.shift(); //取得第一项 + alert(item); //"red" + alert(colors.length); //2 + + + var colors = new Array(); //创建一个数组 + var count = colors.unshift("red", "green"); //推入两项 + alert(count); //2 + count = colors.unshift("black"); //推入另一项 + alert(count); //3 + var item = colors.pop(); //取得最后一项 + alert(item); //"green" + alert(colors.length); //2 + ``` + + + +3. 数组重排序 + + 在默认情况下, `sort()`方法按升序排列数组项——即最小的值位于最前面,最大的值排在最后面。为了实现排序, sort()方法会调用每个数组项的 toString()转型方法,然后比较得到的字符串,以确定如何排序。即使数组中的每一项都是数值, sort()方法比较的也是字符串,如下所示。 + + ```javascript + var values = [1, 2, 3, 4, 5]; + values.reverse(); + alert(values); //5,4,3,2,1 + + var values = [0, 1, 5, 10, 15]; + values.sort(); + alert(values); //0,1,10,15,5 + + //传入比较函数 + function compare(value1, value2) { + if (value1 < value2) { + return -1; + } else if (value1 > value2) { + return 1; + } else { + return 0; + } + } + + var values = [0, 1, 5, 10, 15]; + values.sort(compare); + alert(values); // 15,10,5,1,0 + ``` + + + +4. 数组操作方法 + + `splice()`的主要用途是向数组的中部插入项,但使用这种方法的方式则有如下 3 种。 + + - **删除**:可以删除任意数量的项,只需指定 2 个参数:要删除的第一项的位置和要删除的项数。例如, splice(0,2)会删除数组中的前两项。 + - **插入**:可以向指定位置插入任意数量的项,只需提供 3 个参数:起始位置、 0(要删除的项数)和要插入的项。如果要插入多个项,可以再传入第四、第五,以至任意多个项。例如,splice(2,0,"red","green")会从当前数组的位置 2 开始插入字符串"red"和"green"。 + - **替换**:可以向指定位置插入任意数量的项,且同时删除任意数量的项,只需指定 3 个参数:起始位置、要删除的项数和要插入的任意数量的项。插入的项数不必与删除的项数相等。例如,splice (2,1,"red","green")会删除当前数组位置 2 的项,然后再从位置 2 开始插入字符串"red"和"green"。 + + `splice()`方法始终都会返回一个数组,该数组中包含从原始数组中删除的项(如果没有删除任何项,则返回一个空数组)。下面的代码展示了上述 3 种使用 splice()方法的方式。 + + ```javascript + //基于当前数据创建新的数组 + var colors = ["red", "green", "blue"]; + var colors2 = colors.concat("yellow", ["black", "brown"]); + alert(colors); //red,green,blue + alert(colors2); //red,green,blue,yellow,black,brown + + //slice + var colors = ["red", "green", "blue", "yellow", "purple"]; + var colors2 = colors.slice(1); + var colors3 = colors.slice(1,4); + alert(colors2); //green,blue,yellow,purple + alert(colors3); //green,blue,yellow + + + var colors = ["red", "green", "blue"]; + var removed = colors.splice(0,1); // 删除第一项 + alert(colors); // green,blue + alert(removed); // red,返回的数组中只包含一项 + removed = colors.splice(1, 0, "yellow", "orange"); // 从位置 1 开始插入两项 + alert(colors); // green,yellow,orange,blue + alert(removed); // 返回的是一个空数组 + removed = colors.splice(1, 1, "red", "purple"); // 插入两项,删除一项 + alert(colors); // green,red,purple,orange,blue + alert(removed); // yellow,返回的数组中只包含一项 + ``` + + + +5. 数组迭代方法 + + ECMAScript 5 为数组定义了 5 个迭代方法。每个方法都接收两个参数:要在每一项上运行的函数和(可选的)运行该函数的作用域对象——影响 this 的值。传入这些方法中的函数会接收三个参数:数组项的值、该项在数组中的位置和数组对象本身。根据使用的方法不同,这个函数执行后的返回值可能会也可能不会影响方法的返回值。以下是这 5 个迭代方法的作用。 + + + **every()**:对数组中的每一项运行给定函数,如果该函数对每一项都返回 true,则返回 true。 + + **filter()**:对数组中的每一项运行给定函数,返回该函数会返回 true 的项组成的数组。 + + **forEach()**:对数组中的每一项运行给定函数。这个方法没有返回值。 + + **map()**:对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。 + + **some()**:对数组中的每一项运行给定函数,如果该函数对任一项返回 true,则返回 true。 + 以上方法都不会修改数组中的包含的值。 + + ```javascript + var numbers = [1,2,3,4,5,4,3,2,1]; + + var everyResult = numbers.every(function(item, index, array){ + return (item > 2); + }); + alert(everyResult); //false + + var someResult = numbers.some(function(item, index, array){ + return (item > 2); + }); + alert(someResult); //true + + var filterResult = numbers.filter(function(item, index, array){ + return (item > 2); + }); + alert(filterResult); //[3,4,5,4,3] + + var mapResult = numbers.map(function(item, index, array){ + return item * 2; + }); + alert(mapResult); //[2,4,6,8,10,8,6,4,2] + + numbers.forEach(function(item, index, array){ + //执行某些操作 + }); + ``` + + + +6. 归并方法 + + ECMAScript 5 还新增了两个归并数组的方法: `reduce()`和 `reduceRight()`。这两个方法都会迭代数组的所有项,然后构建一个最终返回的值。其中, reduce()方法从数组的第一项开始,逐个遍历到最后。而 reduceRight()则从数组的最后一项开始,向前遍历到第一项。这两个方法都接收两个参数:一个在每一项上调用的函数和(可选的)作为归并基础的初始值。传给 reduce()和 reduceRight()的函数接收 4 个参数:前一个值、当前值、项的索引和数组对象。这个函数返回的任何值都会作为第一个参数自动传给下一项。第一次迭代发生在数组的第二项上,因此第一个参数是数组的第一项,第二个参数就是数组的第二项。 + + ```javascript + var values = [1,2,3,4,5]; + var sum = values.reduce(function(prev, cur, index, array){ + return prev + cur; + }); + alert(sum); //15 + + var sum = values.reduceRight(function(prev, cur, index, array){ + return prev + cur; + }); + alert(sum); //15 + ``` + + + +### 2. RegExp 类型 + +``` +var expression = / pattern / flags ; +``` + +1 . 其中的模式(pattern)部分可以是任何简单或复杂的正则表达式,可以包含字符类、限定符、分组、向前查找以及反向引用。每个正则表达式都可带有一或多个标志(flags),用以标明正则表达式的行为。 +正则表达式的匹配模式支持下列 3 个标志。 + ++ **g**:表示全局(global)模式,即模式将被应用于所有字符串,而非在发现第一个匹配项时立即停止; ++ **i**:表示不区分大小写(case-insensitive)模式,即在确定匹配项时忽略模式与字符串的大小写; ++ **m**:表示多行(multiline)模式,即在到达一行文本末尾时还会继续查找下一行中是否存在与模式匹配的项。 + +模式中使用的所有元字符都必须转义。正则表达式中的元字符包括: ( [ { \ ^ $ | ) ? * + .]} + +```javascript +/* +* 匹配字符串中所有"at"的实例 +*/ +var pattern1 = /at/g; +/* +* 匹配第一个"bat"或"cat",不区分大小写 +*/ +var pattern2 = /[bc]at/i; +/* +* 匹配所有以"at"结尾的 3 个字符的组合,不区分大小写 +*/ +var pattern3 = /.at/gi; +/* +* 匹配第一个"bat"或"cat",不区分大小写 +*/ +var pattern1 = /[bc]at/i; +/* +* 匹配第一个" [bc]at",不区分大小写 +*/ +var pattern2 = /\[bc\]at/i; +/* +* 匹配所有以"at"结尾的 3 个字符的组合,不区分大小写 +*/ +var pattern3 = /.at/gi; +/* +* 匹配所有".at",不区分大小写 +*/ +var pattern4 = /\.at/gi; +/* +* 匹配第一个"bat"或"cat",不区分大小写 +*/ +var pattern1 = /[bc]at/i; +/* +* 与 pattern1 相同,只不过是使用构造函数创建的 +*/ +var pattern2 = new RegExp("[bc]at", "i"); +``` + + +2. RegExp 对象的主要方法是 `exec()`,该方法是专门为捕获组而设计的。 exec()接受一个参数,即要应用模式的字符串,然后返回包含第一个匹配项信息的数组;或者在没有匹配项的情况下返回 null。返回的数组虽然是 Array 的实例,但包含两个额外的属性: index 和 input。其中, index 表示匹配项在字符串中的位置,而 input 表示应用正则表达式的字符串。在数组中,第一项是与整个模式匹配的字符串,其他项是与模式中的捕获组匹配的字符串(如果模式中没有捕获组,则该数组只包含一项)。 + + ```javascript + var text = "mom and dad and baby"; + var pattern = /mom( and dad( and baby)?)?/gi; + var matches = pattern.exec(text); + alert(matches.index); // 0 + alert(matches.input); // "mom and dad and baby" + alert(matches[0]); // "mom and dad and baby" + alert(matches[1]); // " and dad and baby" + alert(matches[2]); // " and baby" + ``` + + 对于 exec()方法而言,即使在模式中设置了全局标志(g),它每次也只会返回一个匹配项。在不设置全局标志的情况下,在同一个字符串上多次调用 exec()将始终返回第一个匹配项的信息。而在设置全局标志的情况下,每次调用 exec()则都会在字符串中继续查找新匹配项,如下面的例子所示。 + + ```javascript + var text = "cat, bat, sat, fat"; + + var pattern1 = /.at/; + var matches = pattern1.exec(text); + alert(matches.index); //0 + alert(matches[0]); //cat + alert(pattern1.lastIndex); //0 + matches = pattern1.exec(text); + alert(matches.index); //0 + alert(matches[0]); //cat + alert(pattern1.lastIndex); //0 + + var pattern2 = /.at/g; + var matches = pattern2.exec(text); + alert(matches.index); //0 + alert(matches[0]); //cat + alert(pattern2.lastIndex); //3 + matches = pattern2.exec(text); + alert(matches.index); //5 + alert(matches[0]); //bat + alert(pattern2.lastIndex); //8 + + + var text = "000-00-0000"; + var pattern = /\d{3}-\d{2}-\d{4}/; + if (pattern.test(text)){ + alert("The pattern was matched."); + } + ``` + + + +3. RegExp构造函数属性 + + | 长属性名 | 短属性名 | 说 明 | + | :----------: | :------: | :----------------------------------------------------------: | + | input | $_ | 最近一次要匹配的字符串。 Opera未实现此属性 | + | lastMatch | $& | 最近一次的匹配项。 Opera未实现此属性 | + | lastParen | $+ | 最近一次匹配的捕获组。 Opera未实现此属性 | + | leftContext | $` | input字符串中lastMatch之前的文本 | + | multiline | $* | 布尔值,表示是否所有表达式都使用多行模式。 IE和Opera未实现此属性 | + | rightContext | $' | Input字符串中lastMatch之后的文本 | + + ```javascript + var text = "this has been a short summer"; + var pattern = /(.)hort/g; + /* + * 注意: Opera 不支持 input、 lastMatch、 lastParen 和 multiline 属性 + * Internet Explorer 不支持 multiline 属性 + */ + if (pattern.test(text)){ + alert(RegExp.input); // this has been a short summer + alert(RegExp.leftContext); // this has been a + alert(RegExp.rightContext); // summer + alert(RegExp.lastMatch); // short + alert(RegExp.lastParen); // s + alert(RegExp.multiline); // false + } + + if (pattern.test(text)){ + alert(RegExp.$_); // this has been a short summer + alert(RegExp["$`"]); // this has been a + alert(RegExp["$'"]); // summer + alert(RegExp["$&"]); // short + alert(RegExp["$+"]); // s + alert(RegExp["$*"]); // false + } + + //RegExp.$1、 RegExp.$2…RegExp.$9,分别用于存储第一、第二……第九个匹配的捕获组。 + var text = "this has been a short summer"; + var pattern = /(..)or(.)/g; + if (pattern.test(text)){ + alert(RegExp.$1); //sh + alert(RegExp.$2); //t + } + ``` + + +### 3. Function 类型 + +1. 每个函数都包含两个非继承而来的方法: `apply()`和 `call()`。这两个方法的用途都是在特定的作用域中调用函数,实际上等于设置函数体内 this 对象的值。首先, apply()方法接收两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是 Array 的实例,也可以是arguments 对象。例如: + + ```javascript + function sum(num1, num2){ + return num1 + num2; + } + + function callSum1(num1, num2){ + return sum.apply(this, arguments); // 传入 arguments 对象 + } + + function callSum2(num1, num2){ + return sum.apply(this, [num1, num2]); // 传入数组 + } + + alert(callSum1(10,10)); //20 + alert(callSum2(10,10)); //20 + + /*call()方法与 apply()方法的作用相同,它们的区别仅在于接收参数的方式不同。对于 call() 方法而言,第一个参数是 this 值没有变化,变化的是其余参数都直接传递给函数。换句话说,在使用call()方法时,传递给函数的参数必须逐个列举出来,如下面的例子所示。 + */ + function sum(num1, num2){ + return num1 + num2; + } + + function callSum(num1, num2){ + return sum.call(this, num1, num2); + } + alert(callSum(10,10)); //20 + + + /*传递参数并非 apply()和 call()真正的用武之地;它们真正强大的地方是能够扩充函数赖以运行的作用域。下面来看一个例子。*/ + window.color = "red"; + var o = { color: "blue" }; + function sayColor(){ + alert(this.color); + } + sayColor(); //red + sayColor.call(this); //red + sayColor.call(window); //red + sayColor.call(o); //blue + ``` + +2. ECMAScript 5 还定义了一个方法: `bind()`。这个方法会创建一个函数的实例,其 this 值会被绑定到传给 bind()函数的值。例如: + + ```javascript + window.color = "red"; + var o = { color: "blue" }; + function sayColor(){ + alert(this.color); + } + var objectSayColor = sayColor.bind(o); + objectSayColor(); //blue + ``` + + +### 4. String 类型 + +1. ECMAScript还提供了三个基于子字符串创建新字符串的方法: `slice()`、 `substr()`和 `substring()`。这三个方法都会返回被操作字符串的一个子字符串,而且也都接受一或两个参数。第一个参数指定子字符串的开始位置,第二个参数(在指定的情况下)表示子字符串到哪里结束。具体来说, slice()和substring()的第二个参数指定的是子字符串最后一个字符后面的位置。而 substr()的第二个参数指定的则是返回的字符个数。如果没有给这些方法传递第二个参数,则将字符串的长度作为结束位置。与concat()方法一样, slice()、 substr()和 substring()也不会修改字符串本身的值——它们只是返回一个基本类型的字符串值,对原始字符串没有任何影响。请看下面的例子。 + + ```javascript + var stringValue = "hello world"; + alert(stringValue.slice(3)); //"lo world" + alert(stringValue.substring(3)); //"lo world" + alert(stringValue.substr(3)); //"lo world" + alert(stringValue.slice(3, 7)); //"lo w" + alert(stringValue.substring(3,7)); //"lo w" + alert(stringValue.substr(3, 7)); //"lo worl" + ``` + + +2. 字符串的模式匹配 + + ```javascript + //match + var text = "cat, bat, sat, fat"; + var pattern = /.at/; + //与 pattern.exec(text)相同 + var matches = text.match(pattern); + alert(matches.index); //0 + alert(matches[0]); //"cat" + alert(pattern.lastIndex); //0 + + //search + var text = "cat, bat, sat, fat"; + var pos = text.search(/at/); + alert(pos); //1 + + + //replace + ar text = "cat, bat, sat, fat"; + var result = text.replace("at", "ond"); + alert(result); //"cond, bat, sat, fat" + result = text.replace(/at/g, "ond"); + alert(result); //"cond, bond, sond, fond" + + var text = "cat, bat, sat, fat"; + result = text.replace(/(.at)/g, "word ($1)"); + alert(result); //word (cat), word (bat), word (sat), word (fat) + + function htmlEscape(text){ + return text.replace(/[<>"&]/g, function(match, pos, originalText){ + switch(match){ + case "<": + return "<"; + case ">": + return ">"; + case "&": + return "&"; + case "\"": + return """; + } + }); + } + alert(htmlEscape("

Hello world!

")); + //<p class="greeting">Hello world!</p> + + + /*split(),这个方法可以基于指定的分隔符将一个字符串分割成多个子字符串,并将结果放在一个数组中。分隔符可以是字符串,也可以是一个 RegExp 对象(这个方法不会将字符串看成正则表达式)。 split()方法可以接受可选的第二个参数,用于指定数组的大小,以便确保返回的数组不会超过既定大小。请看下面的例子 + */ + var colorText = "red,blue,green,yellow"; + var colors1 = colorText.split(","); //["red", "blue", "green", "yellow"] + var colors2 = colorText.split(",", 2); //["red", "blue"] + var colors3 = colorText.split(/[^\,]+/); //["", ",", ",", ",", ""] + ``` + +## 第六章 面向对象的程序设计 + +### 6.1 理解对象 + +#### 1. 属性类型 + +1. ECMAScript 中有两种属性:数据属性和访问器属性。 + 1. **数据属性** + 数据属性包含一个数据值的位置。在这个位置可以读取和写入值。数据属性有 4 个描述其行为的特性。 + + + **[[Configurable]]**:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为 true。 + + + **[[Enumerable]]**:表示能否通过 for-in 循环返回属性。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为 true。 + + + **[[Writable]]**:表示能否修改属性的值。像前面例子中那样直接在对象上定义的属性,它们的这个特性默认值为 true。 + + + **[[Value]]**:包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。这个特性的默认值为 undefined。 + + ```javascript + var person = {}; + Object.defineProperty(person, "name", { + writable: false, + value: "Nicholas" + }); + alert(person.name); //"Nicholas" + person.name = "Greg"; + alert(person.name); //"Nicholas" + + var person = {}; + Object.defineProperty(person, "name", { + configurable: false, + value: "Nicholas" + }); + alert(person.name); //"Nicholas" + delete person.name; + alert(person.name); //"Nicholas" + + //一旦把属性定义为不可配置的,就不能再把它变回可配置了 + var person = {}; + Object.defineProperty(person, "name", { + configurable: false, + value: "Nicholas" + }); + //抛出错误 + Object.defineProperty(person, "name", { + configurable: true, + value: "Nicholas" + }); + ``` + + 2. **访问器属性** + 访问器属性不包含数据值;它们包含一对儿 getter 和 setter 函数(不过,这两个函数都不是必需的) + + + **[[Configurable]]**:表示能否通过 delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为数据属性。对于直接在对象上定义的属性,这个特性的默认值为true。 + + + **[[Enumerable]]**:表示能否通过 for-in 循环返回属性。对于直接在对象上定义的属性,这个特性的默认值为 true。 + + + **[[Get]]**:在读取属性时调用的函数。默认值为 undefined。 + + + **[[Set]]**:在写入属性时调用的函数。默认值为 undefined。 + + **访问器属性不能直接定义,必须使用 Object.defineProperty()来定义。** + + ```javascript + var book = { + _year: 2004, + edition: 1 + }; + Object.defineProperty(book, "year", { + get: function(){ + return this._year; + }, + set: function(newValue){ + if (newValue > 2004) { + this._year = newValue; + this.edition += newValue - 2004; + } + } + }); + book.year = 2005; + alert(book.edition); //2 + + + // 定义多个属性 + var book = {}; + Object.defineProperties(book, { + _year: { + value: 2004 + }, + edition: { + value: 1 + }, + year: { + get: function(){ + return this._year; + }, + set: function(newValue){ + if (newValue > 2004) { + this._year = newValue; + this.edition += newValue - 2004; + } + } + } + }); + ``` + + +#### 2. 读取属性 + +1. 使用 ECMAScript 5 的 `Object.getOwnPropertyDescriptor()`方法,可以取得给定属性的描述符。这个方法接收两个参数:属性所在的对象和要读取其描述符的属性名称。 + +```javascript +var book = {}; +Object.defineProperties(book, { + _year: { + value: 2004 + }, + edition: { + value: 1 + }, + year: { + get: function(){ + return this._year; + }, + set: function(newValue){ + if (newValue > 2004) { + this._year = newValue; + this.edition += newValue - 2004; + } + } + } +}); + + +var descriptor = Object.getOwnPropertyDescriptor(book, "_year"); +alert(descriptor.value); //2004 +alert(descriptor.configurable); //false + +alert(typeof descriptor.get); //"undefined" +var descriptor = Object.getOwnPropertyDescriptor(book, "year"); +alert(descriptor.value); //undefined +alert(descriptor.enumerable); //false +alert(typeof descriptor.get); //"function" +``` + + + +### 6.2 创建对象 + +#### 1. 构造函数模式 + +```javascript +function Person(name, age, job){ + this.name = name; + this.age = age; + this.job = job; + this.sayName = function(){ + alert(this.name); + }; +} +var person1 = new Person("Nicholas", 29, "Software Engineer"); +var person2 = new Person("Greg", 27, "Doctor"); + +alert(person1.constructor == Person); //true +alert(person2.constructor == Person); //true +alert(person1 instanceof Object); //true +alert(person1 instanceof Person); //true +alert(person2 instanceof Object); //true +alert(person2 instanceof Person); //true + + +//构造函数模式的缺陷 +alert(person1.sayName == person2.sayName); //false + +//解决办法 +function Person(name, age, job){ + this.name = name; + this.age = age; + this.job = job; + this.sayName = sayName; +} +function sayName(){ + alert(this.name); +} +var person1 = new Person("Nicholas", 29, "Software Engineer"); +var person2 = new Person("Greg", 27, "Doctor"); +``` + + + +#### 2. 原型模式 + +```javascript +function Person(){ +} +Person.prototype.name = "Nicholas"; +Person.prototype.age = 29; +Person.prototype.job = "Software Engineer"; +Person.prototype.sayName = function(){ +alert(this.name); +}; +var person1 = new Person(); +person1.sayName(); //"Nicholas" +var person2 = new Person(); +person2.sayName(); //"Nicholas" +alert(person1.sayName == person2.sayName); //true +alert(Person.prototype.constructor=== Person); //true +alert(Person.prototype == person1.__proto__); //true +``` + +

+ +

+ +```javascript +//覆盖原型中的属性 +function Person(){ +} +Person.prototype.name = "Nicholas"; +Person.prototype.age = 29; +Person.prototype.job = "Software Engineer"; +Person.prototype.sayName = function(){ + alert(this.name); +}; + +var person1 = new Person(); +var person2 = new Person(); +person1.name = "Greg"; +alert(person1.name); //"Greg"—— 来自实例 +alert(person2.name); //"Nicholas"—— 来自原型 +``` + +```javascript +//属性恢复 +function Person(){ +} +Person.prototype.name = "Nicholas"; +Person.prototype.age = 29; +Person.prototype.job = "Software Engineer"; +Person.prototype.sayName = function(){ + alert(this.name); +}; + +var person1 = new Person(); +var person2 = new Person(); +person1.name = "Greg"; +alert(person1.name); //"Greg"—— 来自实例 +alert(person2.name); //"Nicholas"—— 来自原型 +delete person1.name; +alert(person1.name); //"Nicholas"—— 来自原型 +``` + +```javascript +//使用 hasOwnProperty()方法可以检测一个属性是存在于实例中,还是存在于原型中。 +function Person(){ +} +Person.prototype.name = "Nicholas"; +Person.prototype.age = 29; +Person.prototype.job = "Software Engineer"; +Person.prototype.sayName = function(){ + alert(this.name); +}; + +var person1 = new Person(); +var person2 = new Person(); +alert(person1.hasOwnProperty("name")); //false +person1.name = "Greg"; +alert(person1.name); //"Greg"—— 来自实例 +alert(person1.hasOwnProperty("name")); //true +alert(person2.name); //"Nicholas"—— 来自原型 +alert(person2.hasOwnProperty("name")); //false +delete person1.name; +alert(person1.name); //"Nicholas"—— 来自原型 +alert(person1.hasOwnProperty("name")); //false +``` + +```javascript +// in 操作符只要通过对象能够访问到属性就返回 true +function Person(){ +} +Person.prototype.name = "Nicholas"; +Person.prototype.age = 29; +Person.prototype.job = "Software Engineer"; +Person.prototype.sayName = function(){ +alert(this.name); +}; + +var person1 = new Person(); +var person2 = new Person(); +alert(person1.hasOwnProperty("name")); //false +alert("name" in person1); //true +person1.name = "Greg"; +alert(person1.name); //"Greg" —— 来自实例 +alert(person1.hasOwnProperty("name")); //true +alert("name" in person1); //true +alert(person2.name); //"Nicholas" —— 来自原型 +alert(person2.hasOwnProperty("name")); //false +alert("name" in person2); //true +delete person1.name; +alert(person1.name); //"Nicholas" —— 来自原型 +alert(person1.hasOwnProperty("name")); //false +alert("name" in person1); //true +``` + +在使用 for-in 循环时,返回的是所有能够通过对象访问的、可枚举的(enumerated)属性,其中既包括存在于实例中的属性,也包括存在于原型中的属性。屏蔽了原型中不可枚举属性(即将[[Enumerable]]标记为 false 的属性)的实例属性也会在 for-in 循环中返回,因为根据规定,所有开发人员定义的属性都是可枚举的。 + +```javascript +//获取所有可枚举的属性 +function Person(){ +} +Person.prototype.name = "Nicholas"; +Person.prototype.age = 29; +Person.prototype.job = "Software Engineer"; +Person.prototype.sayName = function(){ + alert(this.name); +}; +var keys = Object.keys(Person.prototype); +alert(keys); //"name,age,job,sayName" +var p1 = new Person(); +p1.name = "Rob"; +p1.age = 31; +var p1keys = Object.keys(p1); +alert(p1keys); //"name,age" + +// 获取所有属性 不论是否可以枚举 +var keys = Object.getOwnPropertyNames(Person.prototype); +alert(keys); //"constructor,name,age,job,sayName" +``` + +```javascript +// 更简单的原型语法 +function Person(){ +} +Person.prototype = { + name : "Nicholas", + age : 29, + job: "Software Engineer", + sayName : function () { + alert(this.name); + } +}; +var friend = new Person(); +alert(friend instanceof Object); //true +alert(friend instanceof Person); //true +alert(friend.constructor == Person); //false +alert(friend.constructor == Object); //true + + +// 弊端 +function Person(){ +} +//这种赋值方式 相当于把Person.prototype指向一个用字面量创建的对象,相当于下面的创建结果 +Person.prototype = { + name : "Nicholas", + age : 29, + job: "Software Engineer", + sayName : function () { + alert(this.name); + } +}; +console.log(Person.prototype.constructor); //[Function: Object] +var obj = { + name : "Nicholas", + age : 29, + job: "Software Engineer", + sayName : function () { + alert(this.name); + } +}; +console.log(obj.constructor); //[Function: Object] + + +//更好的创建方式 以这种方式重设 constructor 属性会导致它的[[Enumerable]]特性被设置为 true。默认情况下,原生的 constructor 属性是不可枚举的, +function Person(){ +} +Person.prototype = { + constructor : Person, + name : "Nicholas", + age : 29, + job: "Software Engineer", + sayName : function () { + alert(this.name); + } +}; + + +//最好的创建方式 +function Person(){ +} +Person.prototype = { + name : "Nicholas", + age : 29, + job : "Software Engineer", + sayName : function () { + alert(this.name); + } +}; +//重设构造函数,只适用于 ECMAScript 5 兼容的浏览器 +Object.defineProperty(Person.prototype, "constructor", { + enumerable: false, + value: Person +}); +``` + +```javascript +// 扩展原生对象的原型方式 +alert(typeof Array.prototype.sort); //"function" +alert(typeof String.prototype.substring); //"function" + +String.prototype.startsWith = function (text) { + return this.indexOf(text) == 0; +}; +var msg = "Hello world!"; +alert(msg.startsWith("Hello")); //true +``` + +```javascript +// 原生模式的弊端 +function Person(){ +} +Person.prototype = { + constructor: Person, + name : "Nicholas", + age : 29, + job : "Software Engineer", + friends : ["Shelby", "Court"], + sayName : function () { + alert(this.name); + } +}; +var person1 = new Person(); +var person2 = new Person(); +person1.friends.push("Van"); +alert(person1.friends); //"Shelby,Court,Van" +alert(person2.friends); //"Shelby,Court,Van" +alert(person1.friends === person2.friends); //true +``` + + + +#### 3.组合使用构造函数和原型模式(主要使用方式) + +```javascript +function Person(name, age, job){ + this.name = name; + this.age = age; + this.job = job; + this.friends = ["Shelby", "Court"]; +} +Person.prototype = { +constructor : Person, + sayName : function(){ + alert(this.name); + } +} + + +var person1 = new Person("Nicholas", 29, "Software Engineer"); +var person2 = new Person("Greg", 27, "Doctor"); +person1.friends.push("Van"); +alert(person1.friends); //"Shelby,Count,Van" +alert(person2.friends); //"Shelby,Count" +alert(person1.friends === person2.friends); //false +alert(person1.sayName === person2.sayName); //true +``` + + + +### 6.3 继承 + +#### 1. 原型链 + +```javascript +function Parent(){ + this.property = "parent"; + this.value="common value"; +} +Parent.prototype.getParentValue= function(){ + return this.property; +}; +Parent.prototype.getValue= function(){ + return this.value; +}; +function Son(){ + this.subproperty = "son"; +} + +Son.prototype = new Parent(); +Son.prototype.getSonValue = function (){ + return this.subproperty; +}; +var instance = new Son(); +console.log(instance.getParentValue()); //parent +console.log(instance.getSonValue()); //son +console.log(instance.getValue()); //common value + +//重写原型中的方法 +Son.prototype.getValue=function(){ + return this.value+" modified by son" //common value modified by son +}; + +console.log(instance.getValue()); + +// 确定原型和实例的关系 +console.log(instance instanceof Object); //true +console.log(instance instanceof Parent); //true +console.log(instance instanceof Son); //true + +// 确定原型和实例的关系 +console.log(Object.prototype.isPrototypeOf(instance)); //true +console.log(Parent.prototype.isPrototypeOf(instance)); //true +console.log(Son.prototype.isPrototypeOf(instance)); //true +``` + + + +#### 2. 组合继承(主要使用方式) + +```javascript +function Parent(name) { + this.name = name; + this.colors = ["red", "blue", "green"]; +} + +Parent.prototype.sayName = function () { + console.log(this.name); +}; + +function Son(name, age) { +//继承属性 + Parent.call(this, name); + this.age = age; +} + +//继承方法 +Son.prototype = new Parent(); +Son.prototype.constructor = Son; +Son.prototype.sayAge = function () { + console.log(this.age); +}; +var instance1 = new Son("Nicholas", 29); +instance1.colors.push("black"); +console.log(instance1.colors); //"red,blue,green,black" +instance1.sayName(); //"Nicholas"; +instance1.sayAge(); //29 + +var instance2 = new Son("Greg", 27); +console.log(instance2.colors); //"red,blue,green" +instance2.sayName(); //"Greg"; +instance2.sayAge(); //27 + +``` + + + +#### 3. 原型链继承 + +```javascript +var person = { + name: "Nicholas", + friends: ["Shelby", "Court", "Van"] +}; +var anotherPerson = Object.create(person); +anotherPerson.name = "Greg"; +anotherPerson.friends.push("Rob"); +var yetAnotherPerson = Object.create(person); +yetAnotherPerson.name = "Linda"; +yetAnotherPerson.friends.push("Barbie"); + +console.log(person.name); // Nicholas +console.log(yetAnotherPerson.name); // Linda +console.log(anotherPerson.name); // Greg +//包含引用类型值的属性始终都会共享相应的值 +console.log(person.friends); // [ 'Shelby', 'Court', 'Van', 'Rob', 'Barbie' ] +console.log(yetAnotherPerson.friends); // [ 'Shelby', 'Court', 'Van', 'Rob', 'Barbie' ] +console.log(anotherPerson.friends); // [ 'Shelby', 'Court', 'Van', 'Rob', 'Barbie' ] + + +var anotherPerson2 = Object.create(person, { + name: { + value: "Greg2" + } +}); +console.log(anotherPerson2.name); //"Greg2" +``` + + + +## 第七章 函数表达式 + +1. 递归的隐藏问题 + + ```javascript + function factorial(num){ + if (num <= 1){ + return 1; + } else { + return num * factorial(num-1); + } + } + var anotherFactorial = factorial; + factorial=function(){ + return 0 + }; + console.log(anotherFactorial(4)); //0 + factorial = null; + console.log(anotherFactorial(4)); //出错 + + //稳妥实现方式1 + function factorial(num){ + if (num <= 1){ + return 1; + } else { + return num * arguments.callee(num-1); + } + } + + // 稳妥实现方式2 + var factorial = (function f(num){ + if (num <= 1){ + return 1; + } else { + return num * f(num-1); + } + }); + ``` + + +2. #### *闭包是指有权访问另一个函数作用域中的变量的函数。* + + ```javascript + //闭包 + function f(param) { + // 内部函数持有了外部函数的变量 + return function () { + return param+10 + } + } + let f1 = f(2); + console.log(f1()); //12 + ``` + + ```javascript + //回调函数和this关键字 + function f(param, fun) { + for (let i = 0; i < 1000; i++) { + param++; + } + fun(param) + } + + var a = {}; + a.say = f1; + + function f1() { + this.name = 1000; + var that = this; + f(222, function (param) { + console.log("this " + this.name); // undefined + console.log("global " + global.name); // undefined + console.log("that name " + that.name); // 1000 + console.log("结果 " + param) // 1222 + }); + } + + a.say(); + console.log(a.name); //1000 + + ``` + + + +## 第八章 BOM + +### 1.window 对象 + +```javascript +//保证兼容性下确定窗口位置 +var leftPos = (typeof window.screenLeft == "number") ? +window.screenLeft : window.screenX; +var topPos = (typeof window.screenTop == "number") ? +window.screenTop : window.screenY; + +//窗口大小 +var pageWidth = window.innerWidth, +pageHeight = window.innerHeight; +if (typeof pageWidth != "number"){ + if (document.compatMode == "CSS1Compat"){ + pageWidth = document.documentElement.clientWidth; + pageHeight = document.documentElement.clientHeight; + } else { + pageWidth = document.body.clientWidth; + pageHeight = document.body.clientHeight; + } +} + +// 打开新窗口 +window.open("http://www.wrox.com/","wroxWindow","height=400,width=400,top=10,left=10,resizable=yes"; + +// 判断弹出窗口是否被屏蔽 +var blocked = false; +try { + var wroxWin = window.open("http://www.wrox.com", "_blank"); + if (wroxWin == null){ + blocked = true; + } + } catch (ex){ + blocked = true; + } + if (blocked){ + alert("The popup was blocked!"); +} + +``` + +```javascript +// 间歇调用和超时调用 + +//超时调用 +setTimeout("alert('Hello world!') ", 1000); //不建议传递字符串! +//推荐的调用方式 +setTimeout(function() { + alert("Hello world!"); +}, 1000); + +//设置超时调用 +var timeoutId = setTimeout(function() { +alert("Hello world!"); +}, 1000); +//注意:把它取消 +clearTimeout(timeoutId); + +// 间歇调用 +setInterval ("alert('Hello world!') ", 10000); //不建议传递字符串! +//推荐的调用方式 +setInterval (function() { +alert("Hello world!"); +}, 10000); +``` + + + +### 2.location 对象 + +| 属性名 | 例子 | 说 | +| :------: | :------------------: | :----------------------------------------------------------: | +| hash | "#contents" | 返回URL中的hash(#号后跟零或多个字符),如果URL | +| host | "www.wrox.com:80" | 返回服务器名称和端口号(如果有) | +| hostname | "www.wrox.com" | 返回不带端口号的服务器名称 | +| href | "http:/www.wrox.com" | 返回当前加载页面的完整URL。而location对象的toString()方法也返回这个值 | +| pathname | "/WileyCDA/" | 返回URL中的目录和(或)文件名 | +| port | "8080" | 返回URL中指定的端口号。如果URL中不包含端口号,则这个属性返回空字符串 | +| protocol | "http:" | 返回页面使用的协议。通常是http:或https: | +| search | "?q=javascript" | 返回URL的查询字符串。这个字符串以问号开头 | + +```javascript +//位置操作 +window.location = "http://www.wrox.com"; +location.href = "http://www.wrox.com"; +location.replace("http://www.wrox.com/"); +location.assign("http://www.wrox.com"); + +location.reload(); //重新加载(有可能从缓存中加载) +location.reload(true); //重新加载(从服务器重新加载) +``` + + + +## 第十章 DOM + +### 1. Node 类型 + +

+ +
+ +```JavaScript +//新增节点 +var returnedNode = someNode.appendChild(newNode); + +//someNode 有多个子节点 +var returnedNode = someNode.appendChild(someNode.firstChild); +alert(returnedNode == someNode.firstChild); //false +alert(returnedNode == someNode.lastChild); //true + +//插入后成为最后一个子节点 +returnedNode = someNode.insertBefore(newNode, null); +alert(newNode == someNode.lastChild); //true +//插入后成为第一个子节点 +var returnedNode = someNode.insertBefore(newNode, someNode.firstChild); +alert(returnedNode == newNode); //true +alert(newNode == someNode.firstChild); //true +//插入到最后一个子节点前面 +returnedNode = someNode.insertBefore(newNode, someNode.lastChild); +alert(newNode == someNode.childNodes[someNode.childNodes.length-2]); //true + +//替换第一个子节点 +var returnedNode = someNode.replaceChild(newNode, someNode.firstChild); +//替换最后一个子节点 +returnedNode = someNode.replaceChild(newNode, someNode.lastChild); + +//移除第一个子节点 +var formerFirstChild = someNode.removeChild(someNode.firstChild); +//移除最后一个子节点 +var formerLastChild = someNode.removeChild(someNode.lastChild); + +// 深拷贝 +var deepList = myList.cloneNode(true); +alert(deepList.childNodes.length); //3( IE < 9)或 7(其他浏览器) +// 浅拷贝 +var shallowList = myList.cloneNode(false); +alert(shallowList.childNodes.length); +``` + + + +### 2.Document类型 + +2.1 JavaScript 通过 Document 类型表示文档。在浏览器中, document 对象是 HTMLDocument(继承自 Document 类型)的一个实例,表示整个 HTML 页面。而且, document 对象是 window 对象的一个属性,因此可以将其作为全局对象来访问。 + +```javascript +var html = document.documentElement; //取得对的引用 +alert(html === document.childNodes[0]); //true +alert(html === document.firstChild); //true + +var body = document.body; //取得对的引用 + +//取得文档标题 +var originalTitle = document.title; +//设置文档标题 +document.title = "New page title"; +//取得完整的 URL +var url = document.URL; +//取得域名 +var domain = document.domain; +//取得来源页面的 URL +var referrer = document.referrer; + +//如果 URL 中包含一个子域名,例如 p2p.wrox.com,那么就只能将 domain 设置为"wrox.com"(URL 中包含"www",如 www.wrox.com 时,也是如此)。不能将这个属性设置为 URL 中不包含的域, +document.domain = "wrox.com"; // 成功 +document.domain = "nczonline.net"; // 出错 + +//假设页面来自于 p2p.wrox.com 域 允许由紧绷到松散 不允许松散到紧绷 +document.domain = "wrox.com"; //松散的(成功) +document.domain = "p2p.wrox.com"; //紧绷的(出错!)! +``` + +2.2 查找元素 + +```javascript +//getElementById +var div = document.getElementById("mydiv"); + +//getElementsByTagName +var images = document.getElementsByTagName("img"); +alert(images.length); //输出图像的数量 +alert(images[0].src); //输出第一个图像元素的 src 特性 +alert(images.item(0).src); //输出第一个图像元素的 src 特性 + +//对 HTMLCollection 而言,我们可以向方括号中传入数值或字符串形式的索引值。在后台,对数值索引就会调用 item(),而对字符串索引就会调用 namedItem()。 + +var myImage = images.namedItem("myImage"); +var myImage = images["myImage"]; + + +
+ Which color do you prefer? +
    +
  • +
  • +
  • +
  • +
  • +
  • +
+
+//getElementsByName +var radios = document.getElementsByName("color"); +``` + + + +### 3.Element类型 + +```javascript +
+var div = document.getElementById("myDiv"); +alert(div.tagName); //"DIV" +alert(div.tagName == div.nodeName); //true + +if (element.tagName == "div"){ //不能这样比较,很容易出错! +//在此执行某些操作 +} +if (element.tagName.toLowerCase() == "div"){ //这样最好(适用于任何文档) +//在此执行某些操作 +} + +
+var div = document.getElementById("myDiv"); +// 取值 +alert(div.id); //"myDiv"" +alert(div.className); //"bd" +alert(div.title); //"Body text" +alert(div.lang); //"en" +alert(div.dir); //"ltr" +//赋值 +div.id = "someOtherId"; +div.className = "ft"; +div.title = "Some other text"; +div.lang = "fr"; +div.dir ="rtl"; +//取值 +alert(div.getAttribute("id")); //"myDiv" +alert(div.getAttribute("class")); //"bd" +alert(div.getAttribute("title")); //"Body text" +alert(div.getAttribute("lang")); //"en" +alert(div.getAttribute("dir")); //"ltr" +//赋值 +div.setAttribute("id", "someOtherId"); +div.setAttribute("class", "ft"); +div.setAttribute("title", "Some other text"); +div.setAttribute("lang","fr"); +div.setAttribute("dir", "rtl"); +//删除属性 +div.removeAttribute("class"); + +// 创建元素方式1 +var div = document.createElement("div"); +div.id = "myNewDiv"; +div.className = "box"; +document.body.appendChild(div); + +//创建元素方式2 +var div = document.createElement("
"); + +//查找子元素 +var ul = document.getElementById("myList"); +//选择所有后代元素中标签为li,不论是否是直接子元素还是间接子元素 +var items = ul.getElementsByTagName("li"); +``` + + + +### 4.Text类型 + +文本节点由 Text 类型表示,包含的是可以照字面解释的纯文本内容。 + +- appendData(text):将 text 添加到节点的末尾。 +- deleteData(offset, count):从 offset 指定的位置开始删除 count 个字符。 +- insertData(offset, text):在 offset 指定的位置插入 text。 +- replaceData(offset, count, text):用 text 替换从 offset 指定的位置开始到 offset+count 为止处的文本。 +- splitText(offset):从 offset 指定的位置将当前文本节点分成两个文本节点。 +- substringData(offset, count):提取从 offset 指定的位置开始到 offset+count 为止处的字符串。 + +```javascript +//创建文本节点 +var textNode = document.createTextNode("Hello world!"); + +//创建文本节点 +var element = document.createElement("div"); +element.className = "message"; +var textNode = document.createTextNode("Hello world!"); +element.appendChild(textNode); +var anotherTextNode = document.createTextNode("Yippee!"); +element.appendChild(anotherTextNode); +document.body.appendChild(element); + +//规范化文本节点 DOM 文档中存在相邻的同胞文本节点很容易导致混乱,因为分不清哪个文本节点表示哪个字符串 +var element = document.createElement("div"); +element.className = "message"; +var textNode = document.createTextNode("Hello world!"); +element.appendChild(textNode); +var anotherTextNode = document.createTextNode("Yippee!"); +element.appendChild(anotherTextNode); +document.body.appendChild(element); +alert(element.childNodes.length); //2 +element.normalize(); +alert(element.childNodes.length); //1 +alert(element.firstChild.nodeValue); // "Hello world!Yippee!" + +//分割文本节点 +var element = document.createElement("div"); +element.className = "message"; +var textNode = document.createTextNode("Hello world!"); +element.appendChild(textNode); +document.body.appendChild(element); +var newNode = element.firstChild.splitText(5); +alert(element.firstChild.nodeValue); //"Hello" +alert(newNode.nodeValue); //" world!" +alert(element.childNodes.length); //2 +``` + + + +### 5.动态创建表格 + +**为\元素添加的属性和方法如下:** + +- caption:保存着对\元素的 HTMLCollection。 +- tFoot:保存着对\元素(如果有)的指针。 +- tHead:保存着对\元素(如果有)的指针。 +- rows:是一个表格中所有行的 HTMLCollection。 +- createTHead():创建\元素,将其放到表格中,返回引用。 +- createTFoot():创建\元素,将其放到表格中,返回引用。 +- createCaption():创建\元素。 +- deleteTFoot():删除\元素。 +- deleteCaption():删除\元素添加的属性和方法如下:** + +- rows:保存着\元素中行的 HTMLCollection。 +- deleteRow(pos):删除指定位置的行。 +- insertRow(pos):向 rows 集合中的指定位置插入一行,返回对新插入行的引用。 + +**为\元素添加的属性和方法如下:** + +- cells:保存着\元素中单元格的 HTMLCollection。 +- deleteCell(pos):删除指定位置的单元格。 +- insertCell(pos):向 cells 集合中的指定位置插入一个单元格,返回对新插入单元格的引用。 + +```javascript +//创建 table +var table = document.createElement("table"); +table.border = 1; +table.width = "100%"; +//创建 tbody +var tbody = document.createElement("tbody"); +table.appendChild(tbody); +//创建第一行 +tbody.insertRow(0); +tbody.rows[0].insertCell(0); +tbody.rows[0].cells[0].appendChild(document.createTextNode("Cell 1,1")); +tbody.rows[0].insertCell(1); +tbody.rows[0].cells[1].appendChild(document.createTextNode("Cell 2,1")); +//创建第二行 +tbody.insertRow(1); +tbody.rows[1].insertCell(0); +tbody.rows[1].cells[0].appendChild(document.createTextNode("Cell 1,2")); +tbody.rows[1].insertCell(1); +tbody.rows[1].cells[1].appendChild(document.createTextNode("Cell 2,2")); +//将表格添加到文档主体中 +document.body.appendChild(table); +``` + + + +## 第十一章 DOM扩展 + +**1. querySelector()方法** + +querySelector()方法接收一个 **CSS 选择符**,返回与该模式匹配的第一个元素,如果没有找到匹配的元素,返回 null。 + +```javascript +//取得 body 元素 +var body = document.querySelector("body"); +//取得 ID 为"myDiv"的元素 +var myDiv = document.querySelector("#myDiv"); +//取得类为"selected"的第一个元素 +var selected = document.querySelector(".selected"); +//取得类为"button"的第一个图像元素 +var img = document.body.querySelector("img.button"); +``` + +**2.querySelectorAll()方法** + +querySelectorAll()方法接收的参数与 querySelector()方法一样,**都是一个 CSS 选择符**,但返回的是所有匹配的元素而不仅仅是一个元素。这个方法返回的是一个 NodeList 的实例。 如果没有找到匹配的元素, NodeList 就是空的。 + +```javascript +//取得某
中的所有元素(类似于 getElementsByTagName("em")) +var ems = document.getElementById("myDiv").querySelectorAll("em"); +//取得类为"selected"的所有元素 +var selecteds = document.querySelectorAll(".selected"); +//取得所有

元素中的所有元素 +var strongs = document.querySelectorAll("p strong"); + +//要取得返回的 NodeList 中的每一个元素,可以使用 item()方法,也可以使用方括号语法,比如: +var i, len, strong; +for (i=0, len=strongs.length; i < len; i++){ + strong = strongs[i]; //或者 strongs.item(i) + strong.className = "important"; +} +``` + +**3.元素遍历** + +Element Traversal API 为 DOM 元素添加了以下 5 个属性。 + +- childElementCount:返回子元素(不包括文本节点和注释)的个数。 +- firstElementChild:指向第一个子元素; firstChild 的元素版。 +- lastElementChild:指向最后一个子元素; lastChild 的元素版。 +- previousElementSibling:指向前一个同辈元素; previousSibling 的元素版。 +- nextElementSibling:指向后一个同辈元素; nextSibling 的元素版。 + +**支持的浏览器为 DOM 元素添加了这些属性,利用这些元素不必担心空白文本节点。** + + + +**4.getElementsByClassName()方法** + +```javascript +//取得所有类中包含"username"和"current"的元素,类名的先后顺序无所谓 +var allCurrentUsernames = document.getElementsByClassName("username current"); +//取得 ID 为"myDiv"的元素中带有类名"selected"的所有元素 +var selected = document.getElementById("myDiv").getElementsByClassName("selected"); +``` + + + +**5.classList 属性** + +classList 属性是新集合类型 DOMTokenList 的实例。与其他 DOM 集合类似,OMTokenList 有一个表示自己包含多少元素的 length 属性,而要取得每个元素可以使用 item()方法,也可以使用方括号语法。此外,这个新类型还定义如下方法。 + +- add(value):将给定的字符串值添加到列表中。如果值已经存在,就不添加了。 +- contains(value):表示列表中是否存在给定的值,如果存在则返回 true,否则返回 false。 +- remove(value):从列表中删除给定的字符串。 +- toggle(value):如果列表中已经存在给定的值,删除它;如果列表中没有给定的值,添加它。 + +```javascript +

...
+ +//删除"disabled"类 +div.classList.remove("disabled"); +//添加"current"类 +div.classList.add("current"); +//切换"user"类 +div.classList.toggle("user"); +//确定元素中是否包含既定的类名 +if (div.classList.contains("bd") && !div.classList.contains("disabled")){ +//执行操作 +) +//迭代类名 +for (var i=0, len=div.classList.length; i < len; i++){ +doSomething(div.classList[i]); +} +``` + +**6. 焦点管理** + +```javascript +var button = document.getElementById("myButton"); +button.focus(); +alert(document.activeElement === button); //true + +var button = document.getElementById("myButton"); +button.focus(); +alert(document.hasFocus()); //true +``` + +**7.readyState 属性** + +```javascript +//loading,正在加载文档; +//complete,已经加载完文档。 +if (document.readyState == "complete"){ +//执行操作 +} +``` + +**8.自定义属性** + +```javascript +
+//本例中使用的方法仅用于演示 +var div = document.getElementById("myDiv"); +//取得自定义属性的值 +var appId = div.dataset.appId; +var myName = div.dataset.myname; +//设置值 +div.dataset.appId = 23456; +div.dataset.myname = "Michael"; +//有没有"myname"值呢? +if (div.dataset.myname){ + alert("Hello, " + div.dataset.myname); +} +``` + + + +**9.innerHTML 属性 和 outerHTML 属性** + +​ 在读模式下, **innerHTML** 属性返回与调用元素的所有子节点(包括元素、注释和文本节点)对应的 HTML 标记。在写模式下, innerHTML 会根据指定的值创建新的 DOM 树,然后用这个 DOM 树完全替换调用元素原先的所有子节点。下面是一个例子。 + +​ 在读模式下, **outerHTML** 返回调用它的元素及所有子节点的 HTML 标签。在写模式下, outerHTML会根据指定的 HTML 字符串创建新的 DOM 子树,然后用这个 DOM 子树完全替换调用元素。 + + + +**10.insertAdjacentHTML()方法** + +- "**beforebegin**",在当前元素之前插入一个紧邻的同辈元素; + +- "**afterbegin**",在当前元素之下插入一个新的子元素或在第一个子元素之前再插入新的子元素; + +- "**beforeend**",在当前元素之下插入一个新的子元素或在最后一个子元素之后再插入新的子元素; + +- "**afterend**",在当前元素之后插入一个紧邻的同辈元素。 + + ```javascript + //作为前一个同辈元素插入 + element.insertAdjacentHTML("beforebegin", "

Hello world!

"); + //作为第一个子元素插入 + element.insertAdjacentHTML("afterbegin", "

Hello world!

"); + //作为最后一个子元素插入 + element.insertAdjacentHTML("beforeend", "

Hello world!

"); + //作为后一个同辈元素插入 + element.insertAdjacentHTML("afterend", "

Hello world!

"); + ``` + +**11.滚动** + +**scrollIntoView()** :可以在所有 HTML 元素上调用,通过滚动浏览器窗口或某个容器元素,调用元素就可以出现在视口中。如果给这个方法传入 true 作为参数,或者不传入任何参数,那么窗口滚动之后会让调用元素的顶部与视口顶部尽可能平齐。如果传入 false 作为参数,调用元素会尽可能全部出现在视口中,(可能的话,调用元素的底部会与视口顶部平齐。)不过顶部不一定平齐。 + +**scrollIntoViewIfNeeded(alignCenter)** :只在当前元素在视口中不可见的情况下,才滚动浏览器窗口或容器元素,最终让它可见。如果当前元素在视口中可见,这个方法什么也不做。如果将可选的 alignCenter 参数设置为 true,则表示尽量将元素显示在视口中部(垂直方向)。Safari 和 Chrome 实现了这个方法。 + **scrollByLines(lineCount):**将元素的内容滚动指定的行高, lineCount 值可以是正值,也可以是负值。 Safari 和 Chrome 实现了这个方法。 + **scrollByPages(pageCount):**将元素的内容滚动指定的页面高度,具体高度由元素的高度决定。 Safari 和 Chrome 实现了这个方法。 + +```javascript +//让元素可见 +document.forms[0].scrollIntoView(); + +//将页面主体滚动 5 行 +document.body.scrollByLines(5); + +//在当前元素不可见的时候,让它进入浏览器的视口 +document.images[0].scrollIntoViewIfNeeded(); + +//将页面主体往回滚动 1 页 +document.body.scrollByPages(-1); +``` + + + +**12.contains()方法** + +判断某个节点是不是另一个节点的后代。 + +```javascript +alert(document.documentElement.contains(document.body)); //true +``` + + + +## 第十二章 DOM2和DOM3 + +**1.偏移量、客户区大小、滚动大小** + +

+ +
+ +

+ +
+ +

+ +
+ +## 第十三 事件 + +### 13.1 DOM事件流 + +“DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。首先发生的是事件捕获,为截获事件提供了机会。然后是实际的目标接收到事件。最后一个阶段是冒泡阶段,可以在这个阶段对事件做出响应。 + +

+ +
+ +### 13.2 事件处理程序 + +#### 1. DOM0 级事件处理程序 + +使用 DOM0 级方法指定的事件处理程序被认为是元素的方法。因此,这时候的事件处理程序是在元素的作用域中运行;换句话说,程序中的 this 引用当前元素。 + +```javascript +// 绑定事件 +var btn = document.getElementById("myBtn"); +btn.onclick = function(){ + alert(this.id); //"myBtn" +}; +btn.onclick = null; //删除事件处理程序 +``` + +#### 2. DOM2 级事件处理程序 + +“DOM2 级事件” 定义了两个方法,用于处理指定和删除事件处理程序的操作: addEventListener()和 removeEventListener()。所有 DOM 节点中都包含这两个方法,并且它们都接受 3 个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是 true,表示在捕获阶段调用事件处理程序;如果是 false,表示在冒泡阶段调用事件处理程序。 **使用 DOM2 级方法添加事件处理程序的主要好处是可以添加多个事件处理程序。** + +```javascript +var btn = document.getElementById("myBtn"); +btn.addEventListener("click", function(){ + alert(this.id); +}, false); +btn.addEventListener("click", function(){ + alert("Hello world!"); +}, false); + +btn.removeEventListener("click", function(){ //没有用! +alert(this.id); +}, false); + + +//有效解绑 +var btn = document.getElementById("myBtn"); +var handler = function(){ +alert(this.id); +}; +btn.addEventListener("click", handler, false); +//这里省略了其他代码 +btn.removeEventListener("click", handler, false); //有效 +``` + +#### 3.兼容IE + +```javascript +var EventUtil = { +addHandler: function(element, type, handler){ + if (element.addEventListener){ + element.addEventListener(type, handler, false); + } else if (element.attachEvent){ + element.attachEvent("on" + type, handler); + } else { + element["on" + type] = handler; + } + }, + removeHandler: function(element, type, handler){ + if (element.removeEventListener){ + element.removeEventListener(type, handler, false); + } else if (element.detachEvent){ + element.detachEvent("on" + type, handler); + } else { + element["on" + type] = null; + } +} +}; +``` + + + +### 13.3 事件处理程序 + +兼容 DOM 的浏览器会将一个 event 对象传入到事件处理程序中。无论指定事件处理程序时使用什么方法(DOM0 级或 DOM2 级),都会传入 event 对象。 + +```javascript +var btn = document.getElementById("myBtn"); +btn.onclick = function(event){ + alert(event.type); //"click" +}; +btn.addEventListener("click", function(event){ + alert(event.type); //"click" +}, false); +``` + +event 对象包含与创建它的特定事件有关的属性和方法。触发的事件类型不一样,可用的属性和方法也不一样。不过,所有事件都会有下表列出的成员。 + +| 属性/方法 | 类 型 | 读/写 | 说 明 | +| :------------------------: | :----------: | :---: | :----------------------------------------------------------- | +| target | Element | 只读 | 事件的目标 | +| bubbles | Boolean | 只读 | 表明事件是否冒泡 | +| cancelable | Boolean | 只读 | 表明是否可以取消事件的默认行为 | +| currentTarget | Element | 只读 | 其事件处理程序当前正在处理事件的那个元素 | +| defaultPrevented | Boolean | 只读 | 为 true 表 示 已 经 调 用 了 preventDefault()(DOM3级事件中新增) | +| detail | Integer | 只读 | 与事件相关的细节信息 | +| eventPhase | Integer | 只读 | 调用事件处理程序的阶段: 1表示捕获阶段, 2表示“处于目标”, 3表示冒泡阶段 | +| preventDefault() | Function | 只读 | 取 消 事 件 的 默 认 行 为 。 如 果 cancelable是true,则可以使用这个方法 | +| stopImmediatePropagation() | Function | 只读 | 取消事件的进一步捕获或冒泡,同时阻止任何事件处理程序被调用(DOM3级事件中新增) | +| stopPropagation() | Function | 只读 | 取消事件的进一步捕获或冒泡。如果bubbles为true,则可以使用这个方法 | +| trusted | Boolean | 只读 | 为true表示事件是浏览器生成的。为false表示 事 件 是 由 开 发 人 员 通 过 JavaScript 创 建 的(DOM3级事件中新增) | +| type | String | 只读 | 被触发的事件的类型 | +| view | AbstractView | 只读 | 与事件关联的抽象视图。等同于发生事件的window对象 | + +```javascript +//点击myBtn元素 +var btn = document.getElementById("myBtn"); +btn.onclick = function(event){ + alert(event.currentTarget === this); //true + alert(event.target === this); //true +}; + +document.body.onclick = function(event){ + alert(event.currentTarget === document.body); //true + alert(this === document.body); //true + alert(event.target === document.getElementById("myBtn")); //true +}; + +//阻止默认行为 +var link = document.getElementById("myLink"); + link.onclick = function(event){ + event.preventDefault(); +}; + +//阻止冒泡 +var btn = document.getElementById("myBtn"); + btn.onclick = function(event){ + alert("Clicked"); + event.stopPropagation(); +}; +``` + +事件对象的 eventPhase 属性,可以用来确定事件当前正位于事件流的哪个阶段。如果是在捕获阶段调用的事件处理程序,那么 eventPhase 等于 1;如果事件处理程序处于目标对象上,则 eventPhase 等于 2;如果是在冒泡阶段调用的事件处理程序,eventPhase 等于 3。这里要注意的是,尽管“处于目标”发生在冒泡阶段,但 eventPhase 仍然一直等于 2。 + +```javascript +var btn = document.getElementById("myBtn"); +btn.onclick = function(event){ + alert(event.eventPhase); //2 +}; +document.body.addEventListener("click", function(event){ + alert(event.eventPhase); //1 +}, true); +document.body.onclick = function(event){ + alert(event.eventPhase); //3 +}; +``` + +### 13.4 事件类型 + +#### 1.UI 事件 + +UI 事件指的是那些不一定与用户操作有关的事件。这些事件在 DOM 规范出现之前,都是以这种或那种形式存在的,而在 DOM 规范中保留是为了向后兼容。现有的 UI 事件如下。 + +- **load**:当页面完全加载后在 window 上面触发,当所有框架都加载完毕时在框架集上面触发,当图像加载完毕时在\元素上面触发,或者当嵌入的内容加载完毕时在\元素上面触发。 +- **unload**:当页面完全卸载后在 window 上面触发,当所有框架都卸载后在框架集上面触发,或者当嵌入的内容卸载完毕后在\元素上面触发。 +- **abort**:在用户停止下载过程时,如果嵌入的内容没有加载完,则在\元素上面触发。 +- **error**:当发生 JavaScript 错误时在 window 上面触发,当无法加载图像时在\元素上面触发,当无法加载嵌入内容时在\元素上面触发,或者当有一或多个框架无法加载时在框架集上面触发。 +- **select**:当用户选择文本框(\或\)中的一或多个字符时触发。 +- **resize**:当窗口或框架的大小变化时在 window 或框架上面触发。 +- **scroll**:当用户滚动带滚动条的元素中的内容时,在该元素上面触发。 \元素中包含所加载页面的滚动条。 + +多数这些事件都与 window 对象或表单控件相关。 + +```javascript +EventUtil.addHandler(window, "load", function(event){ + alert("Loaded!"); +}); + + + +// 加载图片 + + +var image = document.getElementById("myImage"); +EventUtil.addHandler(image, "load", function(event){ + event = EventUtil.getEvent(event); + alert(EventUtil.getTarget(event).src); +}); + +//创建并加载图片 +EventUtil.addHandler(window, "load", function(){ +var image = document.createElement("img"); +EventUtil.addHandler(image, "load", function(event){ + event = EventUtil.getEvent(event); + alert(EventUtil.getTarget(event).src); +}); +document.body.appendChild(image); + image.src = "smile.gif"; +}); + +//创建并加载图片2 +EventUtil.addHandler(window, "load", function(){ +var image = new Image(); +EventUtil.addHandler(image, "load", function(event){ +alert("Image loaded!"); +}); +image.src = "smile.gif"; +}); + +//创建并加载脚本 +EventUtil.addHandler(window, "load", function(){ +var script = document.createElement("script"); +EventUtil.addHandler(script, "load", function(event){ +alert("Loaded"); +}); +script.src = "example.js"; +document.body.appendChild(script); +}); +``` + +#### 2.焦点事件 + +**blur**:在元素失去焦点时触发。这个事件不会冒泡;所有浏览器都支持它。 + +**focus**:在元素获得焦点时触发。这个事件不会冒泡;所有浏览器都支持它。 + +#### 3.鼠标和滚轮事件 + +**3.1 鼠标事件** + +鼠标事件是 Web 开发中最常用的一类事件,毕竟鼠标还是最主要的定位设备。 DOM3 级事件中定义了 9 个鼠标事件。 + +- **click**:在用户单击主鼠标按钮(一般是左边的按钮)或者按下回车键时触发。这一点对确保易访问性很重要,意味着 onclick 事件处理程序既可以通过键盘也可以通过鼠标执行。 + +- **dblclick**:在用户双击主鼠标按钮(一般是左边的按钮)时触发。从技术上说,这个事件并不是 DOM2 级事件规范中规定的,但鉴于它得到了广泛支持,所以 DOM3 级事件将其纳入了标准。 + +- **mousedown**:在用户按下了任意鼠标按钮时触发。不能通过键盘触发这个事件。 + +- **mouseenter**:在鼠标光标从元素外部首次移动到元素范围之内时触发。这个事件不冒泡,而且在光标移动到后代元素上不会触发。 DOM2 级事件并没有定义这个事件,但 DOM3 级事件将它纳入了规范。 IE、 Firefox 9+和 Opera 支持这个事件。 + +- **mouseleave**:在位于元素上方的鼠标光标移动到元素范围之外时触发。这个事件不冒泡,而且在光标移动到后代元素上不会触发。 DOM2 级事件并没有定义这个事件,但 DOM3 级事件将它纳入了规范。 IE、 Firefox 9+和 Opera 支持这个事件。 + +- **mousemove**:当鼠标指针在元素内部移动时重复地触发。不能通过键盘触发这个事件。 + +- **mouseout**:在鼠标指针位于一个元素上方,然后用户将其移入另一个元素时触发。又移入的另一个元素可能位于前一个元素的外部,也可能是这个元素的子元素。不能通过键盘触发这个事件。 + +- **mouseover**:在鼠标指针位于一个元素外部,然后用户将其首次移入另一个元素边界之内时触发。不能通过键盘触发这个事件。 + +- **mouseup**:在用户释放鼠标按钮时触发。不能通过键盘触发这个事件。 + + +**3.2 位置:** + +```javascript +//客户区坐标位置 相对于浏览器可视窗口 +var div = document.getElementById("myDiv"); +EventUtil.addHandler(div, "click", function(event){ + event = EventUtil.getEvent(event); + alert("Client coordinates: " + event.clientX + "," + event.clientY); +}); + +//页面坐标位置 相对于整个html页面 +var div = document.getElementById("myDiv"); +EventUtil.addHandler(div, "click", function(event){ + event = EventUtil.getEvent(event); + alert("Page coordinates: " + event.pageX + "," + event.pageY); +}); + +//屏幕坐标位置 相对于整个电脑屏幕 +var div = document.getElementById("myDiv"); +EventUtil.addHandler(div, "click", function(event){ + event = EventUtil.getEvent(event); + alert("Screen coordinates: " + event.screenX + "," + event.screenY); +}); +``` + + + +**3.3 修改键** + +虽然鼠标事件主要是使用鼠标来触发的,但在按下鼠标时键盘上的某些键的状态也可以影响到所要采取的操作。这些修改键就是 Shift、 Ctrl、 Alt 和 Meta(在 Windows 键盘中是 Windows 键,在苹果机中是 Cmd 键),它们经常被用来修改鼠标事件的行为。 DOM 为此规定了 4 个属性,表示这些修改键的状态: shiftKey、 ctrlKey、 altKey 和 metaKey。这些属性中包含的都是布尔值,如果相应的键被按下了,则值为 true,否则值为 false。 + +```javascript +var div = document.getElementById("myDiv"); +EventUtil.addHandler(div, "click", function(event){ +event = EventUtil.getEvent(event); +var keys = new Array(); + if (event.shiftKey){ +keys.push("shift"); +} +if (event.ctrlKey){ + keys.push("ctrl"); +} +if (event.altKey){ + keys.push("alt"); +} +if (event.metaKey){ + keys.push("meta"); +} + alert("Keys: " + keys.join(",")); +}); +``` + +**3.4 相关元素** + +在发生 mouseover 和 mouserout 事件时,还会涉及更多的元素。这两个事件都会涉及把鼠标指针从一个元素的边界之内移动到另一个元素的边界之内。对 mouseover 事件而言,事件的主目标是获得光标的元素,而相关元素就是那个失去光标的元素。类似地,对 mouseout 事件而言,事件的主目标是失去光标的元素,而相关元素则是获得光标的元素。DOM 通过 event 对象的 **relatedTarget** 属性提供了相关元素的信息。 **这个属性只对于 mouseover和 mouseout 事件才包含值**;对于其他事件,这个属性的值是 null。 + +**3.5 鼠标按钮** + +只有在主鼠标按钮被单击(或键盘回车键被按下)时才会触发 click 事件,因此检测按钮的信息并不是必要的。但对mousedown 和 mouseup 事件来说,则在其 event 对象存在一个 button 属性,表示按下或释放的按钮。 DOM 的 button 属性可能有如下 3 个值: 0 表示主鼠标按钮, 1 表示中间的鼠标按钮(鼠标滚轮按钮), 2 表示次鼠标按钮。 + +**3.6 鼠标滚轮事件** + +```javascript +EventUtil.addHandler(document, "mousewheel", function(event){ + event = EventUtil.getEvent(event); + alert(event.wheelDelta); +}); +``` + + + +#### 4.键盘与文本事件 + +**4.1 键盘事件与文本事件** + +**键盘事件**: + +- **keydown**:当用户按下键盘上的任意键时触发,而且如果按住不放的话,会重复触发此事件。 +- **keypress**:当用户按下键盘上的字符键时触发,而且如果按住不放的话,会重复触发此事件。按下 Esc 键也会触发这个事件。Safari 3.1 之前的版本也会在用户按下非字符键时触发 keypress事件。 +- **keyup**:当用户释放键盘上的键时触发。 + +**文本事件:** + +- **textInput**。这个事件是对 keypress 的补充,用意是在将文本显示给用户之前更容易拦截文本。在文本插入文本框之前会触发 textInput 事件。 + +**4.2 键码** + +在发生 **keydown** 和 **keyup** 事件时, event 对象的 **keyCode** 属性中会包含一个代码,与键盘上一个特定的键对应。 (回车键定对应的键码为13) + +**4.3 字符编码** + +发生 **keypress** 事件意味着按下的键会影响到屏幕中文本的显示。在所有浏览器中,按下能够插入或删除字符的键都会触发 keypress 事件。IE9、 Firefox、 Chrome 和 Safari 的 event 对象都支持一个 **charCode** 属性,这个属性只有在发生keypress 事件时才包含值,而且这个值是按下的那个键所代表字符的 ASCII 编码。此时的 keyCode通常等于 0 或者也可能等于所按键的键码。 + +**4.5 textInput 事件** + +由于 textInput 事件主要考虑的是字符,因此它的 event 对象中还包含一个 data 属性,这个属性的值就是用户输入的字符(而非字符编码)。 + +```javascript +var textbox = document.getElementById("myText"); +EventUtil.addHandler(textbox, "textInput", function(event){ + event = EventUtil.getEvent(event); + alert(event.data); +}); +``` + +另外, event 对象上还有一个属性,叫 **inputMethod**,表示把文本输入到文本框中的方式。 + +- 0,表示浏览器不确定是怎么输入的。 +- 1,表示是使用键盘输入的。 +- 2,表示文本是粘贴进来的。 +- 3,表示文本是拖放进来的。 +- 4,表示文本是使用 IME 输入的。 +- 5,表示文本是通过在表单中选择某一项输入的。 +- 6,表示文本是通过手写输入的(比如使用手写笔)。 +- 7,表示文本是通过语音输入的。 +- 8,表示文本是通过几种方法组合输入的。 +- 9,表示文本是通过脚本输入的。 + + + +#### 5.变动事件 + +DOM2 级的变动(mutation)事件能在 DOM 中的某一部分发生变化时给出提示。变动事件是为 XML或 HTML DOM 设计的,并不特定于某种语言。 DOM2 级定义了如下变动事件。 + +- **DOMSubtreeModified**:在 DOM 结构中发生任何变化时触发。这个事件在其他任何事件触发后都会触发。 +- **DOMNodeInserted**:在一个节点作为子节点被插入到另一个节点中时触发。 +- **DOMNodeRemoved**:在节点从其父节点中被移除时触发。 +- **DOMNodeInsertedIntoDocument**:在一个节点被直接插入文档或通过子树间接插入文档之后触发。这个事件在 DOMNodeInserted 之后触发。 +- **DOMNodeRemovedFromDocument**:在一个节点被直接从文档中移除或通过子树间接从文档中移除之前触发。这个事件在 DOMNodeRemoved 之后触发。 +- **DOMAttrModified**:在特性被修改之后触发。 +- **DOMCharacterDataModified**:在文本节点的值发生变化时触发。 + + + +#### 6.HTML5事件 + +**6.1 beforeunload 事件** + +之所以有发生在 window 对象上的 beforeunload 事件,是为了让开发人员有可能在页面卸载前阻止这一操作。这个事件会在浏览器卸载页面之前触发,可以通过它来取消卸载并继续使用原有页面。 + +为了显示这个弹出对话框,必须将 event.returnValue 的值设置为要显示给用户的字符串(对IE 及 Fiefox 而言),同时作为函数的值返回(对 Safari 和 Chrome 而言),如下面的例子所示。 + +```javascript +EventUtil.addHandler(window, "beforeunload", function(event){ + event = EventUtil.getEvent(event); + var message = "I'm really going to miss you if you go."; + event.returnValue = message; + return message; +}); +``` + +**6.2 DOMContentLoaded 事件** + +如前所述, window 的 load 事件会在页面中的一切都加载完毕时触发,但这个过程可能会因为要加载的外部资源过多而颇费周折。而 DOMContentLoaded 事件则在形成完整的 DOM 树之后就会触发,不理会图像、 JavaScript 文件、 CSS 文件或其他资源是否已经下载完毕。与 load 事件不同,DOMContentLoaded 支持在页面下载的早期添加事件处理程序,这也就意味着用户能够尽早地与页面进行交互。 +要处理 DOMContentLoaded 事件,可以为 document 或 window 添加相应的事件处理程序(尽管这个事件会冒泡到 window,但它的目标实际上是 document)。来看下面的例子。 + +```javascript +EventUtil.addHandler(document, "DOMContentLoaded", function(event){ + alert("Content loaded"); +}); +``` + +**6.3 hashchange 事件** + +HTML5 新增了 hashchange 事件,以便在 URL 的参数列表(及 URL 中“#”号后面的所有字符串)发生变化时通知开发人员。之所以新增这个事件,是因为在 Ajax 应用中,开发人员经常要利用 URL 参数列表来保存状态或导航信息。 +必须要把 hashchange 事件处理程序添加给 window 对象,然后 URL 参数列表只要变化就会调用它。此时的 event 对象应该额外包含两个属性: oldURL 和 newURL。 + +```javascript +EventUtil.addHandler(window, "hashchange", function(event){ + alert("Old URL: " + event.oldURL + "\nNew URL: " + event.newURL); +}); + +//保证兼容性的情况下可以按照此方法调用得到当前参数列表 +EventUtil.addHandler(window, "hashchange", function(event){ +alert("Current hash: " + location.hash); +}); +``` + + + +### 第十四章 表单脚本 + +### 14.1 表单基础知识 + +在 HTML 中,表单是由\
元素来表示的,而在 JavaScript 中,表单对应的则是 HTMLFormElement 类型。HTMLFormElement 继承了 HTMLElement,因而与其他 HTML 元素具有相同的默认属性。不过, HTMLFormElement 也有它自己下列独有的属性和方法。 + +- **acceptCharset**:服务器能够处理的字符集;等价于 HTML 中的 accept-charset 特性。 +- **action**:接受请求的 URL;等价于 HTML 中的 action 特性。 +- **elements**:表单中所有控件的集合(HTMLCollection)。 +- **enctype**:请求的编码类型;等价于 HTML 中的 enctype 特性。 +- **length**:表单中控件的数量。 +- **method**:要发送的 HTTP 请求类型,通常是"get"或"post";等价于 HTML 的 method 特性。 +- **name**:表单的名称;等价于 HTML 的 name 特性。 +- **reset()**:将所有表单域重置为默认值。 +- **submit()**:提交表单。 +- **target**:用于发送请求和接收响应的窗口名称;等价于 HTML 的 target 特性。 + + + +#### 1.提交表单 + +```html + + + + + + + +``` + +以这种方式提交表单时,浏览器会在将请求发送给服务器之前触发 submit 事件。这样,我们就有机会验证表单数据,并据以决定是否允许表单提交。阻止这个事件的默认行为就可以取消表单提交。例如,下列代码会阻止表单提交。 + +```javascript +var form = document.getElementById("myForm"); +EventUtil.addHandler(form, "submit", function(event){ + //取得事件对象 + event = EventUtil.getEvent(event); + //阻止默认事件 + EventUtil.preventDefault(event); +}); +``` + +```javascript +//提交表单方式二 +var form = document.getElementById("myForm"); +//提交表单 +form.submit(); +``` + +调用 submit()方法的形式提交表单时,**不会触发 submit 事件**,因此要记得在调用此方法之前先验证表单数据 。 + + + +#### 2.重置表单 + +```html + + + + +``` + +```javascript +var form = document.getElementById("myForm"); +EventUtil.addHandler(form, "reset", function(event){ + //取得事件对象 + event = EventUtil.getEvent(event); + //阻止表单重置 + EventUtil.preventDefault(event); +}); +``` + +```javascript +var form = document.getElementById("myForm"); +//重置表单 +form.reset(); +``` + +**与调用 submit()方法不同,调用 reset()方法会像单击重置按钮一样触发 reset 事件。** + + + +#### 3.表单字段 + +```javascript +var form = document.getElementById("form1"); +//取得表单中的第一个字段 +var field1 = form.elements[0]; +//取得名为"textbox1"的字段 +var field2 = form.elements["textbox1"]; +//取得表单中包含的字段的数量 +var fieldCount = form.elements.length; + + +
    +
  • Red
  • +
  • Green
  • +
  • Blue
  • +
+ + +var form = document.getElementById("myForm"); +var colorFields = form.elements["color"]; +alert(colorFields.length); //3 +var firstColorField = colorFields[0]; +var firstFormField = form.elements[0]; +alert(firstColorField === firstFormField); //true +``` + +##### 3.1 共有的表单字段属性 + +除了\
元素之外,所有表单字段都拥有相同的一组属性。由于\类型可以表示多种表单字段,因此有些属性只适用于某些字段,但还有一些属性是所有字段所共有的。表单字段共有的属性如下。 + +- **disabled**:布尔值,表示当前字段是否被禁用。 +- **form**:指向当前字段所属表单的指针;**只读**。 +- **name**:当前字段的名称。 +- **readOnly**:布尔值,表示当前字段是否只读。 +- **tabIndex**:表示当前字段的切换(tab)序号。 +- **type**:当前字段的类型,如"checkbox"、 "radio",等等。 +- **value**:当前字段将被提交给服务器的值。对文件字段来说,这个属性是只读的,包含着文件在计算机中的路径。 + +```javascript +var form = document.getElementById("myForm"); +var field = form.elements[0]; +//修改 value 属性 +field.value = "Another value"; +//检查 form 属性的值 +alert(field.form === form); //true +//把焦点设置到当前字段 +field.focus(); +//禁用当前字段 +field.disabled = true; + +//避免多次提交表单 +EventUtil.addHandler(form, "submit", function(event){ +event = EventUtil.getEvent(event); +var target = EventUtil.getTarget(event); +//取得提交按钮 +var btn = target.elements["submit-btn"]; +//禁用它 +btn.disabled = true; +}); +``` + +##### 3.2 共有的表单字段方法 + +每个表单字段都有两个方法: 即 **focus()** 和 **blur()** + +```javascript +//html5 新增属性 autofocus + + +// js 设置方式 +EventUtil.addHandler(window, "load", function(event){ + var element = document.forms[0].elements[0]; + if (element.autofocus !== true){ + element.focus(); + console.log("JS focus"); + } +}); +document.forms[0].elements[0].blur(); +``` + +##### 3.3 共有的表单字段事件 + +除了支持鼠标、键盘、更改和 HTML 事件之外,所有表单字段都支持下列 3 个事件。 +**blur**:当前字段失去焦点时触发。 +**change**:对于\和\ + +var textbox = document.forms[0].elements["textbox1"]; +alert(textbox.value); +textbox.value = "Some new value"; +``` + +#### 1. 选择文本 + + 选择(**select**)事件 + +```javascript +//鼠标选择文本时候触发 +var textbox = document.forms[0].elements["textbox1"]; +EventUtil.addHandler(textbox, "select", function(event){ +var alert("Text selected" + textbox.value); +}); +``` + +获取选中的内容**selectionStart**,**selectionEnd**,设置选中的范围**setSelectionRange** + +```html + + + + + Title + + + + + + +``` + +#### 2. 过滤输入(**keypress**) + +```html + + + + + Title + + + + + + + +``` + +#### 3.自动切换焦点 + +```html + + + + + Title + + + + + + + + + + + + +``` + +#### 4. HTML5 验证约束API + +```html + + +var isUsernameRequired = document.forms[0].elements["username"].required + + + + + +input.stepUp(); //加 1 +input.stepUp(5); //加 5 +input.stepDown(); //减 1 +input.stepDown(10); //减 10 + + + + +
+ +
+ + + + +document.forms[0].elements["btnNoValidate"].formNoValidate = true; +``` + +通过设置 **novalidate** 属性,可以告诉表单不进行验证。 + +如果一个表单中有多个提交按钮,为了指定点击某个提交按钮不必验证表单,可以在相应的按钮上添加 **formnovalidate** 属性。 + +**约束校验** + +```html + + + + + Title + + + + + + + +``` + +与 checkValidity()方法简单地告诉你字段是否有效相比, **validity** 属性则会告诉你为什么字段有效或无效。这个对象中包含一系列属性,每个属性会返回一个布尔值。 + +- **customError** :如果设置了 setCustomValidity(),则为 true,否则返回 false。 +- **patternMismatch**:如果值与指定的 pattern 属性不匹配,返回 true。 +- **rangeOverflow**:如果值比 max 值大,返回 true。 +- **rangeUnderflow**:如果值比 min 值小,返回 true。 +- **stepMisMatch**:如果 min 和 max 之间的步长值不合理,返回 true。 +- **tooLong**:如果值的长度超过了 maxlength 属性指定的长度,返回 true。有的浏览器(如 Firefox 4) +- 会自动约束字符数量,因此这个值可能永远都返回 false。 +- **typeMismatch**:如果值不是"mail"或"url"要求的格式,返回 true。 +- **valid**:如果这里的其他属性都是 false,返回 true。 checkValidity()也要求相同的值。 +- **valueMissing**:如果标注为 required 的字段中没有值,返回 true。 +- 因此,要想得到更具体的信息,就应该使用 validity 属性来检测表单的有效性。下面是一个例子。 + +```javascript +if (input.validity && !input.validity.valid){ + if (input.validity.valueMissing){ + alert("Please specify a value.") + } else if (input.validity.typeMismatch){ + alert("Please enter an email address."); + } else { + alert("Value is invalid."); + } +} +``` + + + +### 14.3 选择框脚本 + +选择框是通过\ + + + + + + + ``` + +在 DOM 中,每个\
元素(如果有)的指针。 +- tBodies:是一个\元素,将其放到表格中,返回引用。 +- deleteTHead():删除\元素。 +- deleteRow(pos):删除指定位置的行。 +- insertRow(pos):向 rows 集合中的指定位置插入一行。 + +**为\