diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..cfe0b71f --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: https://raw.githubusercontent.com/frank-lam/fullstack-tutorial/master/assets/wechat/wx-green.png diff --git a/README.md b/README.md index c198e7d8..0a492743 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![full stack developer tutorial](assets/fullstack-tutorial-logo.png) +

| I | II | III | IV | V | VI | VII | VIII | IX | X | XI | XII | | :--------------------------: | :-------------------: | :----------------------: | :---------------------: | :--------------: | :---------------: | :----------------------: | :----------------------: | :----------------------: | :----------------------: | :----------------------: | :----------------------: | @@ -6,58 +6,35 @@

- 📢📢📢 + ✨✨✨

- 和 400+ 技术达人在线交流: - QQ技术交流群 + 和 500+ 技术达人在线交流: + 🤟 快来吧,和大家一起技术互动交流

- 欢迎光临我的技术博客:www.frankfeekr.cn + 『技术博客』:www.frankfeekr.cn | 『开源贡献』:⊱ 英雄招募令 | 『微信订阅号』:全栈开发社区

-

- 欢迎志同道合的小伙伴加入开源小组:⊱ 开源小组,英雄招募令 -

-
-
-

- 🎯🎯🎯 -

-

- 为极客打造,阅读体验更好的在线文档(与 GitHub 实时同步) -

-

- 支持手机阅读,目录导航,Gitalk 评论,搜索定位 -

-

- 📖 点击,即刻开始阅读! -

-
-
-
-

- 🎯🎯🎯 -

-

- 也许你特别钟爱 GitHub 的阅读体验,没关系 -

-

- 我也为您打造了一款 Chrome 插件 Chrome Extension:GitHub Markdown -

-

- 方便在 GitHub 长文阅读时,在侧边栏生成标题目录定位 -

-

- 🎅 特别鸣谢共同完成两位小伙伴:@dxiaoqi @dzeze -

+

+ + +🔥🔥🔥 + +欢迎光临 LinTools 开发者的在线导航: https://tools.frankfeekr.cn + +如果你有更好的在线工具,[请点击留言](https://github.com/frank-lam/fullstack-tutorial/issues/65),持续更新! ## 前言 -- [全栈修炼手册:如何选择自己的技术栈?](notes/如何选择自己的技术栈.md) +- [谈谈技术学习的一些方法论](https://www.frankfeekr.cn/2019/05/09/谈谈技术学习的一些方法论/) + + 在学习技术这条路上并不是一帆风顺,也一直在探索一条适合自己的学习方法。从一开始的技术小白,到现在还比较上道的老鸟,在这个过程中走了太多的弯路,想在这里和大家分享一些我的经历和学习方法。 + +- [如何选择自己的技术栈](https://www.frankfeekr.cn/2019/05/27/如何选择自己的技术栈/) 在编程的世界里,该如何选择自己的技术栈呢。学前端?学 APP 开发?对于 Java、C++、C#、Python、PHP 又如何选择呢?人工智能现如今这么火,是不是机器学习、深度学习更高级一些呢?那么程序员又如何修炼内功呢? @@ -126,35 +103,35 @@ ## 三、Python - [Python 语言基础](notes/Python/Python简介及基础语法.md) -- Scrapy 爬虫框架 -- Flask -- Django ## 四、前端 - [前端知识体系](notes/Frontend/前端知识体系.md) -- Vue - - 第一个 Vue 应用程序 - - Vue-cli 脚手架 - - vue-router 路由 - - vuex 状态管理 -- Webpack +- [Angular 基础知识](notes/Frontend/Angular.md) +- [ES6+ 语法全解析](https://notes.frankfeekr.cn/docs/frontend/es6/%E9%A1%B9%E7%9B%AE%E5%87%86%E5%A4%87/%E5%89%8D%E8%A8%80) + +
TODO LIST -- HTML(5) +- HTML5 -- CSS(3) +- CSS3 - sass、scss、stylus +- CSS 预处理 + + - sass(scss) + - less + - stylus - CSS 框架 - BootStarp、LayUI + - BootStarp + - LayUI - JavaScript @@ -163,7 +140,7 @@ - JavaScript 框架 - Vue - - React + - React - Angular - jQuery @@ -171,22 +148,30 @@ 常用 api、对象池、异常处理、进程通信、高并发 -- 静态编译 - - Flow +- 静态类型检查 + - TypeScript + - Flow + +- 构建/打包工具 -- 打包工具 - webpack - - glup + - gulp - rollup -- 工具 +- 包管理工具 + - npm - yarn + +- 服务端渲染 -
- + - koa2 + - express + - nuxt + - next + @@ -200,14 +185,18 @@ Redis 核心知识 -- MongoDB - - 基于分布式文件存储的数据库 - - [SQL](notes/SQL.md) 常用 SQL 语句 +- [PostgreSQL](notes/PostgreSQL.md) + + 一个开源的关系数据库,是从伯克利写的 POSTGRES 软件包发展而来的 + +- [InfluxDB](https://www.frankfeekr.cn/2019/07/24/influxdb-tutorial-start/) + + 玩转时序数据库 + ## 六、操作系统 @@ -236,7 +225,6 @@ web前后端漏洞分析与防御,XSS 攻击、CSRF 攻击、DDoS 攻击、SQL 注入 -- Socket 网络编程 @@ -254,15 +242,10 @@ 分布式协调服务,服务注册发现 -- [玩转时序数据库 InfluxDB(一)初体验](https://www.frankfeekr.cn/2019/07/24/influxdb-tutorial-start/) +- [Kafka](notes/MicroService/kafka/README.md) -- 分布式锁 + 深入浅出 Kafka,将用最极简的语言带你走进 Kafka 的消息中间件世界 - 基于 Redis、MySQL、Zookeeper 的分布式锁实现 - -- FastDFS - - 轻量级分布式文件管理系统 【说明】**分布式专题** 笔者也在学习中,这里列举了一些技能列表,笔者将局部更新。敬请期待 @@ -317,6 +300,15 @@ 自动化运维,持续集成、持续交付、持续部署 + +- 分布式锁 + + 基于 Redis、MySQL、Zookeeper 的分布式锁实现 + +- FastDFS + + 轻量级分布式文件管理系统 + - Go 并发的、带垃圾回收的、快速编译的语言 @@ -402,21 +394,33 @@ Please make sure to read the [Contributing Guide/如何给我的仓库贡献](no +## Stargazers over time + +![Stargazers over time](https://starcharts.herokuapp.com/frank-lam/fullstack-tutorial.svg) + + + ## License 知识共享许可协议 -Copyright (c) 2019-present, Frank Lam +Copyright (c) 2021-present, Frank Lam ## 关于作者 :boy: +
+

+ 『作者简介』:https://www.frankfeekr.cn/author +

+
+ +

在颠覆世界的同时,也要好好关照自己。

- QQ群 BLOG 邮箱 QQ群 @@ -425,4 +429,5 @@ Copyright (c) 2019-present, Frank Lam from zero to hero.

-

+
+ diff --git a/assets/fullstack-tutorial-logo-2019.png b/assets/fullstack-tutorial-logo-2019.png new file mode 100644 index 00000000..3038642b Binary files /dev/null and b/assets/fullstack-tutorial-logo-2019.png differ diff --git a/assets/fullstack-tutorial-logo.png b/assets/fullstack-tutorial-logo.png index 3038642b..f1fc94d7 100644 Binary files a/assets/fullstack-tutorial-logo.png and b/assets/fullstack-tutorial-logo.png differ diff --git a/assets/fullstack-tutorial-logo.svg b/assets/fullstack-tutorial-logo.svg index b6b44c2b..80396fa2 100644 --- a/assets/fullstack-tutorial-logo.svg +++ b/assets/fullstack-tutorial-logo.svg @@ -1,2 +1,3 @@ + -
Since 2018/5/20
Since 2018/5/20
Fullstack Tutorial, 2019
Fullstack Tutorial, 2019
Hey, welcome to visit and start the computer science learning journey.
Hey, welcome to visit and start the computer science learning journey.
\ No newline at end of file +
Since 2018/5/20
Since 2018/5/20
Fullstack Tutorial, 2020
Fullstack Tutorial, 2...
Hey, welcome to visit and start the computer science learning journey.
Hey, welcome to visit and start the computer science learning...
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/assets/fullstack-tutorial-logo2.svg b/assets/fullstack-tutorial-logo2.svg deleted file mode 100644 index 105a823a..00000000 --- a/assets/fullstack-tutorial-logo2.svg +++ /dev/null @@ -1,2 +0,0 @@ - -
Since 2018/5/20
Since 2018/5/20
Fullstack Tutorial, 2019
Fullstack Tutorial, 2019
\ No newline at end of file diff --git a/assets/logo-2021.svg b/assets/logo-2021.svg new file mode 100644 index 00000000..9e367ee9 --- /dev/null +++ b/assets/logo-2021.svg @@ -0,0 +1,3 @@ + + +
Since 2018/5/20
Since 2018/5/20
Fullstack Tutorial, 2021
Fullstack Tutorial, 2...
Hey, welcome to visit and start the computer science learning journey.
Hey, welcome to visit and start the computer science learning...
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/assets/wechat/wx-green-raw.png b/assets/wechat/wx-green-raw.png new file mode 100644 index 00000000..db0e96ff Binary files /dev/null and b/assets/wechat/wx-green-raw.png differ diff --git a/assets/wechat/wx-green.png b/assets/wechat/wx-green.png new file mode 100644 index 00000000..0e966a8f Binary files /dev/null and b/assets/wechat/wx-green.png differ diff --git a/assets/wechat/wx-white.png b/assets/wechat/wx-white.png new file mode 100644 index 00000000..f1d94dae Binary files /dev/null and b/assets/wechat/wx-white.png differ diff --git "a/assets/wechat/\346\220\234\347\264\242\346\241\206\344\274\240\346\222\255\346\240\267\345\274\217-\346\240\207\345\207\206\350\211\262\347\211\210.png" "b/assets/wechat/\346\220\234\347\264\242\346\241\206\344\274\240\346\222\255\346\240\267\345\274\217-\346\240\207\345\207\206\350\211\262\347\211\210.png" new file mode 100644 index 00000000..ec9e6501 Binary files /dev/null and "b/assets/wechat/\346\220\234\347\264\242\346\241\206\344\274\240\346\222\255\346\240\267\345\274\217-\346\240\207\345\207\206\350\211\262\347\211\210.png" differ diff --git "a/assets/wechat/\346\220\234\347\264\242\346\241\206\344\274\240\346\222\255\346\240\267\345\274\217-\347\231\275\350\211\262\347\211\210.png" "b/assets/wechat/\346\220\234\347\264\242\346\241\206\344\274\240\346\222\255\346\240\267\345\274\217-\347\231\275\350\211\262\347\211\210.png" new file mode 100644 index 00000000..761365d0 Binary files /dev/null and "b/assets/wechat/\346\220\234\347\264\242\346\241\206\344\274\240\346\222\255\346\240\267\345\274\217-\347\231\275\350\211\262\347\211\210.png" differ diff --git a/assets/wx_group_qrcode.png b/assets/wx_group_qrcode.png new file mode 100644 index 00000000..44479f4d Binary files /dev/null and b/assets/wx_group_qrcode.png differ diff --git a/assets/zhishixingqiu.JPG b/assets/zhishixingqiu.JPG new file mode 100644 index 00000000..afdc791a Binary files /dev/null and b/assets/zhishixingqiu.JPG differ diff --git a/notes/EalsticSearch/Untitled.md b/notes/EalsticSearch/Untitled.md new file mode 100644 index 00000000..4f52fe14 --- /dev/null +++ b/notes/EalsticSearch/Untitled.md @@ -0,0 +1,63 @@ +elasticsearch-head的使用 - 仅此而已-远方 - 博客园 +https://www.cnblogs.com/xuwenjin/p/8792919.html + + + + + +tyrival/gitbook: Logstash + Elasticsearch 6.7 用户指南中文版 +https://github.com/tyrival/gitbook + + + + + +Mac下ElasticSearch安装 - 简书 +https://www.jianshu.com/p/df4af12a420a + + + + + +## 安装 + +Logstash在Linux上安装部署 - haw2106 - 博客园 +https://www.cnblogs.com/haw2106/p/10410916.html + +ElasticSearch在linux上安装部署 - socket强 - 博客园 +https://www.cnblogs.com/socketqiang/p/11363024.html + +详解Docker下使用Elasticsearch可视化Kibana_docker_脚本之家 +https://www.jb51.net/article/138582.htm + + + + + +docker下载镜像报net/http: TLS handshake timeout-西风未眠-51CTO博客 +https://blog.51cto.com/10950710/2122702 + + + +Docker安装部署ELK教程 (Elasticsearch+Kibana+Logstash+Filebeat) - 万能付博 - 博客园 +https://www.cnblogs.com/fbtop/p/11005469.html + + + + + +1.拉取镜像 + +``` +docker pull elasticsearch:6.5.4 +docker pull kibana:6.5.4 +``` + + + +2.启动容器 + +``` +docker run -d --name es1 -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" elasticsearch:6.5.4 +docker run -d -p 5601:5601 --name kibana --link es1:elasticsearch kibana:6.5.4 +``` \ No newline at end of file diff --git a/notes/Frontend/Angular.md b/notes/Frontend/Angular.md new file mode 100644 index 00000000..24315da9 --- /dev/null +++ b/notes/Frontend/Angular.md @@ -0,0 +1,868 @@ +# Angular + +## 1. 框架背景 + +Angular 是一个由 Google维护的开源JavaScript框架,用于在HTML和JavaScript中构建Web应用程序,是三大框架之首。 + +不管是1还是2,Angular最显著的特征就是其**整合性**。涵盖了M、V、C/VM等各个层面,不需要组合、评估其它技术就能完成大部分前端开发任务。这样可以有效降低决策成本,提高决策速度,对需要快速起步的团队是非常有帮助的。 + +由于它是从一个用来做原型的框架演化而来的,加上诞生时间很早(2009年,作为对比,jQuery诞生于2006年),当时生态不完善,连模块化机制都得自己实现。 + +但Angular 2就不同了,发布于2016年9月份,它是基于ES6来开发的,它的起点更高,整合了现代前端的各种先进理念,在框架、文档、工具等各个层面提供了全方位的支持 + +在Angular 中最具特色的就是**依赖注入**系统了,它把后端开发中用来解决复杂问题、实现高弹性设计的技术引入了前端 + + + +## 2. Angular CLI + +### 2.1 安装 + +​ Angular CLI用于简单,快速构建Angular2项目,只要掌握几行命令就能扣减前端架构。依赖于NodeJs和npm。 + +```jsx +//安装脚手架 +npm install -g angular-cli +//创建项目 +ng new project_name(项目名称) +//启动项目 +cd project_name +ng serve --open +``` + + + +### 2.2 主要特性 + +1. Angular CLI 可以快速搭建框架,创建module,service,class,directive等; +2. 具有webpack的功能,代码分割,按需加载; +3. 代码打包压缩; +4. 模块测试; +5. 热部署,有改动立即重新编译,不用刷新浏览器;而且速度很快 +6. 有开发环境,测试环境,生产环境的配置,不用自己操心; +7. sass,less的预编译Angular CLI都会自动识别后缀来编译; +8. typescript的配置,Angular CLI在创建应用时都可以自己配置; +9. 在创建好的工程也可以做一些个性化的配置,webpack的具体配置还不支持,未来可能会增加; +10. Angular CLI创建的工程结构是最佳实践,生产可用; + + + +### 2.3 创建module,component,service,class + + + +​ + +## 3. 架构 + +### 3.1 模块 + +​ 模块组件的特征在于可以用于执行单个任务的代码块。 您可以从代码(类)中导出值。 Angular应用程序被称为模块,并使用许多模块构建您的应用程序。 Angular 的基本构建块是可以从模块导出的**组件**类。 + +```js +export class AppComponent { + title = '朝夕教育'; +} + +``` + +### 3.2 组件 + +组件是拥有模板的控制器类,主要处理页面上的应用程序和逻辑的视图。 组件可以拥有独立的样式。 +注册组件,使用 *@Component* 注释,可以将应用程序拆分为更小的部分。 + +#### 3.2.1 创建组件 + +使用ng命令`ng generate component `创建的组件会自动生成在`app.module`中的引用,推荐使用ng命令生成组件 + +```jsx +//快速创建 +ng g c xxx +``` + +```jsx +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.scss'] +}) +``` + +@Component最常用的几个选项: + +**selector**:这个 CSS 选择器用于在模板中标记出该指令,并触发该指令的实例化。 + +**template**:组件的内联模板 + +**templateUrl**:组件模板文件的 URL + +**styleUrls**:组件样式文件 + +**styles**:组件内联样式 + + + +#### 3.2.2 组件生命周期 + +​ Angular 会按以下顺序执行钩子方法。你可以用它来执行以下类型的操作。 + +| 钩子方法 | 用途 | 时机 | +| :------------------------ | :----------------------------------------------------------- | :----------------------------------------------------------- | +| `ngOnChanges()` | 当 Angular 设置或重新设置数据绑定的输入属性时响应。 该方法接受当前和上一属性值的 `SimpleChanges` 对象注意,这发生的非常频繁,所以你在这里执行的任何操作都会显著影响性能。 | 在 `ngOnInit()` 之前以及所绑定的一个或多个输入属性的值发生变化时都会调用。注意,如果你的组件没有输入,或者你使用它时没有提供任何输入,那么框架就不会调用 `ngOnChanges()`。 | +| `ngOnInit()` | 在 Angular 第一次显示数据绑定和设置指令/组件的输入属性之后,初始化指令/组件。 | 在第一轮 `ngOnChanges()` 完成之后调用,只调用**一次**。 | +| `ngDoCheck()` | 检测,并在发生 Angular 无法或不愿意自己检测的变化时作出反应。 | 紧跟在每次执行变更检测时的 `ngOnChanges()` 和 首次执行变更检测时的 `ngOnInit()` 后调用。 | +| `ngAfterContentInit()` | 当 Angular 把外部内容投影进组件视图或指令所在的视图之后调用。 | 第一次 `ngDoCheck()` 之后调用,只调用一次。 | +| `ngAfterContentChecked()` | 每当 Angular 检查完被投影到组件或指令中的内容之后调用 | `ngAfterContentInit()` 和每次 `ngDoCheck()` 之后调用 | +| `ngAfterViewInit()` | 当 Angular 初始化完组件视图及其子视图或包含该指令的视图之后调用 | 第一次 `ngAfterContentChecked()` 之后调用,只调用一次。 | +| `ngAfterViewChecked()` | 每当 Angular 做完组件视图和子视图或包含该指令的视图的变更检测之后调用。 | `ngAfterViewInit()` 和每次 `ngAfterContentChecked()` 之后调用。 | +| `ngOnDestroy()` | 每当 Angular 每次销毁指令/组件之前调用并清扫。 在这儿反订阅可观察对象和分离事件处理器,以防内存泄漏 | 在 Angular 销毁指令或组件之前立即调用。 | + +#### 3.2.3 组件交互 + +1. ​ **@Input** + + 父组件通过`@Input`给子组件绑定属性设置输入类数据 + + ```jsx + //父组件 + + + //子组件 + import { Component, Input } from '@angular/core'; + @Input() + name!: string; + ngOnInit(): void { + console.log(this.name) + } + ``` + +2. ​ **@Output** + + ​ 父组件给子组件传递一个事件,子组件通过`@Output`弹射触发事件 + + ```jsx + //父组件 + + + list:number[] = [1,2,3,4] + addListFun(num:number){ + this.list.push(num) + } + + //子组件 + import { Component, Output,EventEmitter } from '@angular/core'; + @Output() addList = new EventEmitter() + pushList(v:string){ + console.log(this.inpValue) + this.addList.emit(v) + } + ``` + +3. ​ **@ViewChild()** + + 通过`ViewChild`获取子组件实例,获取子组件数据 + + ```jsx + + + + + @ViewChild('myChild') child: any; + constructor() { } + + ngOnInit(): void { + } + getView(e:any){ + console.log(this.child) + this.child.setInpValue('我是一段数据') + } + ``` + + + +### 3.3 模板 + +在 Angular 中,**模板**就是一块 HTML。在模板中,你可以通过一种特殊语法来使用 Angular 的许多功能 + +#### 3.3.1 插值语法 + +​ 所谓 "插值" 是指将表达式嵌入到标记文本中。 默认情况下,插值会用双花括号 `{{` 和 `}}` 作为分隔符 + +```js +

hello {{ name }}

+``` + +​ 花括号之间的文本通常是组件属性的名字。Angular 会把这个名字替换为响应组件属性的字符串值 + +​ 括号间的素材是一个**模板表达式**我们可以在`{{}}`内编写js运算 + +```jsx +

hello {{ 1+1 }}

+``` + +#### 3.3.2 属性绑定 + +1. **Attribute绑定** + + ```jsx +

hello {{ 1+1 }}

+ ``` + +2. **类绑定** + + ```jsx + //单一类绑定 +

hello {{ 1+1 }}

+ + //多重类绑定 +

hello {{ 1+1 }}

+

hello {{ 1+1 }}

+

hello {{ 1+1 }}

+ + //ngClass + export class AppComponent { + isActive = true; + } + +

hello {{ 1+1 }}

+ + ``` + +3. **样式绑定** + + ```jsx + //单一样式绑定 +

hello {{ 1+1 }}

+ + //带单位的单一样式绑定 +

hello {{ 1+1 }}

+ + //多重样式绑定 +

hello {{ 1+1 }}

+

hello {{ 1+1 }}

+ + //ngStyle + export class AppComponent { + isMax = false; + } +

hello {{ 1+1 }}

+

hello {{ 1+1 }}

+ ``` + +#### 3.3.3 条件判断 + +​ ***ngIf**是直接影响元素是否被渲染,而非控制元素的显示和隐藏 + +```jsx +export class AppComponent { + isMax = false; + isMin = false; +} +
Max title
+
Min title
+ +//解析完 + +
Max title
+
+ + + isMax为true + + + isMax为false + +``` + +#### 3.3.4 循环语句 + +​ 解析器会把 `let color`、`let i` 和 `let odd` 翻译成命名变量 `color`、`i` 和 `odd` + +微语法解析器接收of,会将它的首字母大写(Of),然后加上属性的指令名(ngFor)前缀,它最终生成的名字是 ngFor 的输入属性(colors) + +`NgFor` 指令在列表上循环,每个循环中都会设置和重置它自己的上下文对象上的属性。 这些属性包括 `index` 和 `odd` 以及一个特殊的属性名 `$implicit`(隐式变量) + +Angular 将 let-color 设置为此上下文中 $implicit 属性的值, 它是由 NgFor 用当前迭代中的 colors 初始化的 + +```jsx +export class AppComponent { + colors:Array = [ 'red', 'blue', 'yellow', 'green' ]; +} + +
+ {{odd}} + {{i}} + {{color}} +
+ +//解析完 + +
{{odd}} {{i}} {{color}}
+
+``` + +```jsx +export class AppComponent { + status = 1; +} + + +``` + +#### 3.3.5 事件绑定 + +​ Angular 的事件绑定语法由等号左侧括号内的目标事件名和右侧引号内的模板语句组成。目标事件名是 `click` ,模板语句是 `onSave()` + +事件对象通过`$event`传递 + +```jsx +export class AppComponent { + onSave(){ + console.log('点击了按钮') + } +} + + +``` + +#### 3.3.6 双向绑定 + +双向绑定是应用中的组件共享数据的一种方式。使用双向绑定绑定来侦听事件并在父组件和子组件之间同步更新值 + +ngModel指令**只对表单元素有效**,所以在使用之前需要导入`FormsModule`板块 + +```jsx +import { FormsModule } from '@angular/forms'; + +@NgModule({ + // 申明组件内用到的视图 + declarations: [ + AppComponent, + HelloComponent, + ], + //引入模块需要的类 + imports: [ + BrowserModule, + AppRoutingModule, + FormsModule + ], + //全局服务 + providers: [], + //根组件 + bootstrap: [AppComponent] +}) +``` + +```jsx +export class AppComponent { + userName=''; +} +
+ 输入: +

你输入了: {{userName}}

+
+``` + +#### 3.3.7 模板引用变量 + +模板变量可以帮助你在模板的另一部分使用这个部分的数据。使用模板变量,你可以执行某些任务,比如响应用户输入或微调应用的表单 + +在模板中,要使用井号 `#` 来声明一个模板变量。下列模板变量 `#userName` 语法在 `` 元素上声明了一个名为 `userName` 的变量 + +```html + +``` + +可以在组件模板中的任何地方引用某个模板变量 + +```jsx + + + +export class AppComponent { + callUserName(v){ + console.log(v) + } +} +``` + +Angular 根据你所声明的变量的位置给模板变量赋值: + +- 如果在组件上声明变量,该变量就会引用该组件实例。 +- 如果在标准的 HTML 标记上声明变量,该变量就会引用该元素。 +- 如果你在 `` 元素上声明变量,该变量就会引用一个 `TemplateRef` 实例来代表此模板。 + +#### 3.3.8 表单控件 + +使用表单控件有三个步骤。 + +1. 在你的应用中注册响应式表单模块。该模块声明了一些你要用在响应式表单中的指令。 +2. 生成一个新的 `FormControl` 实例,并把它保存在组件中。 +3. 在模板中注册这个 `FormControl`。 + +**注册响应式表单模块** + +要使用响应式表单控件,就要从 `@angular/forms` 包中导入 `ReactiveFormsModule`,并把它添加到你的 NgModule 的 `imports` 数组中。 + +```js +import { ReactiveFormsModule } from '@angular/forms'; + +@NgModule({ + imports: [ + // other imports ... + ReactiveFormsModule + ], +}) +export class AppModule { } +``` + +要注册一个表单控件,就要导入 `FormControl` 类并创建一个 `FormControl` 的新实例,将其保存为类的属性。 + +```js +import { Component } from '@angular/core'; +import { FormControl } from '@angular/forms'; + +@Component({ + selector: 'app-name-editor', + templateUrl: './name-editor.component.html', + styleUrls: ['./name-editor.component.css'] +}) +export class NameEditorComponent { + name = new FormControl(''); +} + +//使用这种模板绑定语法,把该表单控件注册给了模板中名为 name 的输入元素。这样,表单控件和 DOM 元素就可以互相通讯了:视图会反映模型的变化,模型也会反映视图中的变化 + + +

+ Value: {{ name.value }} +

+``` + +修改name值可以通过`FormControl` 提供的 `setValue()` 方法 + +```js +updateName() { + this.name.setValue('Tina'); +} +``` + +#### 3.3.9 表单控件分组 + +表单中通常会包含几个相互关联的控件。响应式表单提供了两种把多个相关控件分组到同一个输入表单中的方法 + +要将表单组添加到此组件中,请执行以下步骤。 + +1. 创建一个 `FormGroup` 实例。 +2. 把这个 `FormGroup` 模型关联到视图。 +3. 保存表单数据。 + +**创建一个 FormGroup 实例** + +在组件类中创建一个名叫 `loginForm` 的属性,并设置为 `FormGroup` 的一个新实例。要初始化这个 `FormGroup`,请为构造函数提供一个由控件组成的对象,对象中的每个名字都要和表单控件的名字一一对应 + +```js +import { Component } from '@angular/core'; +import { FormGroup, FormControl } from '@angular/forms'; + +@Component({ + selector: 'app-profile-editor', + templateUrl: './profile-editor.component.html', + styleUrls: ['./profile-editor.component.css'] +}) +export class ProfileEditorComponent { + loginForm = new FormGroup({ + userName: new FormControl(''), + password: new FormControl(''), + }); +} + +//模板渲染 +
+ + + + + +
+``` + + + +#### 3.3.10 表单验证 + +​ 表单元素添加`required`关键字表示必填,通过绑定`ngModel`的引用可以拿到到当前组件的信息,通过引用获取到验证的信息 + +```jsx +export class AppComponent { + fromData={ + name:'', + password:'' + }; + + subBtnFUn(obj){ + console.log(obj) + } +} + +
+ 账号: +
+ {{nameInp.valid }} +
+ 密码: +
+ {{pasInp.valid }} +
+ +
+``` + +我们还可以通过 **ngModel** 跟踪修改状态与有效性验证,它使用了三个 CSS 类来更新控件,以便反映当前状态。 + +| 状态 | 为 true 时的类 | 为 false 时的类 | +| :--------------- | :------------- | :-------------- | +| 控件已经被访问过 | `ng-touched` | `ng-untouched` | +| 控件值已经变化 | `ng-dirty` | `ng-pristine` | +| 控件值是有效的 | `ng-valid` | `ng-invalid` | + +#### 3.3.11 自定义表单验证 + +​ 先引入表单的一些内置依赖 + +```js +import { FormGroup, FormBuilder,Validators } from '@angular/forms'; + +//构造函数里注入FormBuilder +constructor(private fb:FormBuilder) { } + +//错误提醒数据 +formErrors = { + 'title': '', + 'content': '' +}; + + +//在组件类的初始化函数里对表单中的元素的校验进行定义,并调用表单的valueChanges方法,检测表单的输入的变化 +ngOnInit():void { + this.taskInfo.isComplete = 1; + this.tasksForm = this.fb.group({ + userName: ['', [Validators.required, + Validators.maxLength(18), + Validators.minLength(6) ] ], + password: ['', [this.passWordVal]], + phone: ['', [Validators.required,this.phoneVal],] + }); + + phoneVal(phone: FormControl): object { + const value = phone.value || ''; + if(!value) return {desc:'请输入手机号'} + const valid = /[0-9]{11}/.test(value); + return valid ? {} :{desc:'联系电话必须是11位数字'} + } + passWordVal(password:FormControl):object{ + const value = password.value || ''; + const valid = value.match(/^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,20}$/); + return valid ? {} : {passwordValidator: {desc:'密码至少包含 数字和英文,长度6-20'}} + } +} +``` + +#### 3.3.12 管道 + +​ 管道的作用就是传输。不同的管道具有不同的作用。(其实就是处理数据) + +`angular`中自带的`pipe`函数 + +| 管道 | 功能 | +| ------------- | ------------------------------------------------------------ | +| DatePipe | 日期管道,格式化日期 | +| JsonPipe | 将输入数据对象经过JSON.stringify()方法转换后输出对象的字符串 | +| UpperCasePipe | 将文本所有小写字母转换成大写字母 | +| LowerCasePipe | 将文本所有大写字母转换成小写字母 | +| DecimalPipe | 将数值按照特定的格式显示文本 | +| CurrentcyPipe | 将数值进行货币格式化处理 | +| SlicePipe | 将数组或者字符串裁剪成新子集 | +| PercentPipe | 将数值转百分比格式 | + +`pipe`用法 + +- {{ 输入数据 | 管道 : 管道参数}} (其中‘|’是管道操作符) + +- 链式管道 {{ 输入数据 | date | uppercase}} + +- 管道流通方向自左向右,逐层执行 + + 使用脚手架命令:**ng g p test** + + ```js + import { Pipe, PipeTransform } from '@angular/core'; + + @Pipe({ + name: 'testTitle' + }) + export class TestPipe implements PipeTransform { + + transform(value: unknown, ...args: unknown[]): unknown { + console.log(value) + return 'title'; + } + } + + +

{{ 'Angular' | testTitle }}

+ + ``` + + + +### 3.4 服务 + + angular中,把从组件内抽离出来的代码叫服务,服务的本质就是函数 + + 官方认为组件不应该直接获取或保存数据, 它们应该聚焦于展示数据,而把数据访问的职责委托给某个服务。而服务就充当着数据访问,逻辑处理的功能。把组件和服务区分开,以提高模块性和复用性。通过把组件中和视图有关的功能与其他类型的处理分离开,可以让组件类更加精简、高效。 + +使用命令ng g s xxx创建一个服务,通过**@Injectable()**装饰器标识服务。 + +```js +//导入Injectable装饰器 +import { Injectable } from '@angular/core'; +//使用Injectable装饰器声明服务 +@Injectable({ + //作用域设定,'root'表示默认注入,注入到AppModule里 + providedIn: 'root', +}) +export class TestService { +} + +``` + +组件中如何使用服务呢,必须将服务依赖注入系统、组件或者模块,才能够使用服务。我们可以用**注册提供商**和**根注入器**实现**。** + + 该服务本身是 CLI 创建的一个类,并且加上了 `@Injectable()` 装饰器。默认情况下,该装饰器是用 `providedIn` 属性进行配置的,它会为该服务创建一个提供商。 + + + +### 3.5 依赖注入 + +​ 在这个例子中,`providedIn: 'root'` 指定 Angular 应该在根注入器中提供该服务,从而实现**根注入器**将服务注入,它就在整个应用程序中可用了**。** + +**providedIn**: + +​ 'root' :注入到AppModule,提供该服务,所有子组件都可以使用(推荐) + +​ null : 不设定服务作用域(不推荐) + +​ 组件名:只作用于该组件(懒加载模式) + +```js + +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root', +}) +export class TestService { +} + + +import { Component, OnInit } from '@angular/core'; +import {HeroService} from '../hero.service' + +@Component({ + selector: 'app-test', + templateUrl: './test.component.html', + styleUrls: ['./test.component.scss'], +}) +export class TestComponent implements OnInit { + + constructor(private heroService:HeroService) { } + + ngOnInit(): void { + console.log(this.heroService.getHeroList()) + } +} +``` + +​ 也可以使用 `@Component` 或 `@Directive` 内部的 `providers: []`,为特定的组件子树提供服务,这也将导致创建多个服务实例(每个组件使用一个服务实例) + +```js + +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root', +}) +export class TestService { +} + + +import { Component, OnInit } from '@angular/core'; +import {HeroService} from '../hero.service' + +@Component({ + selector: 'app-test', + templateUrl: './test.component.html', + styleUrls: ['./test.component.scss'], + providers: [HeroService] +}) +export class TestComponent implements OnInit { + + constructor(private heroService:HeroService) { } + + ngOnInit(): void { + console.log(this.heroService.getHeroList()) + } +} +``` + + + +### 3.6 路由 + +​ 路由就是连接组件的筋络,它也是树形结构的.有了它,就可以在angular中实现路径的导航模式 + +可以把路由看成是一组规则,它决定了url的变化对应着哪一种状态,具体表现就是不同视图的切换 + +在angular中,路由是非常重要的组成部分, 组件的实例化与销毁,模块的加载,组件的某些生命周期钩子的发起,都是与它有关 + +#### 3.6.1 路由基本使用 + +**路由器**是一个调度中心,它是一套规则的列表,能够查询当前URL对应的规则,并呈现出相应的视图. + +**路由**是列表里面的一个规则,即路由定义,它有很多功能字段: + +- **path**字段,表示该路由中的URL路径部分 +- **Component**字段,表示与该路由相关联的组件 + +每个带路由的Angular应用都有一个路由器服务的单例对象,通过路由定义的列表进行配置后使用。 + +```js +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import {HomeComponent} from './home/home.component' + +const routes: Routes = [ + {path:'home',component:HomeComponent} +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule] +}) +export class AppRoutingModule { } + +``` + +```jsx +//路由导航 +home + hello + +//组件渲染输出 + +``` + +上述具体的工作流程,可以举例简单描述为: + +- 当浏览器地址栏的URL变化时,路径部分`/home`满足了列表中path为"**home**"的这个路由定义,激活对应**HomeComponent**的实例,显示它的视图 +- 当应用程序请求导航到路径`/hello`时,符合了另外的规则,激活对应视图且展示内容,并将该路径更新到浏览器地址栏和历史 + +#### 3.6.2 路由嵌套 + +​ 父子路由嵌套配置: + +```js +const routes: Routes = [ + {path:'home', + component:HomeComponent, + children:[ + {path:'hello',component:HelloComponent} + ] + }, +]; +``` + +​ 在**home Component**内这是`router-outlet`路由出口,即可在**home** 路由内渲染子级路由 + +```jsx +//home template +

home Component

+hello + +``` + +​ 在非`home Component`内跳转到`/home/hello`路由需要写全路径 + +```jsx +//app template +

app Component

+hello + +``` + +​ + +#### 3.6.3 路由传参 + +- **query** + + 在a标签上添加一个参数queryParams,并通过`this.routerinfo.snapshot.queryParams`获取参数 + + ```jsx + hello + ``` + + ```js + import {ActivatedRoute} from '@angular/router'; + constructor(private routerinfo:ActivatedRoute) { } + + ngOnInit() { + //id为参数名字 + this.id=this.routerinfo.snapshot.queryParams["id"] + } + ``` + + + +- **params** + + 修改路由配置文件`path`,路由导航`a`标签`routerLink`后面数组的第二个参数为传递的值 + + 并且通过`subscribe`请阅的方式获取`name`参数 + + ```js + { + path: 'hello/:name', + component:HelloComponent, + }, + ``` + + ```jsx + //我们在后面添加/:name此时name即为传递的参数名字 + //a标签设置如下 + hello + ``` + + ```js + ngOnInit() { + this.routerinfo.params.subscribe((params:Params)=>{ + this.name=params['name'] + }) + } + ``` + diff --git "a/notes/Frontend/\345\211\215\347\253\257\347\237\245\350\257\206\344\275\223\347\263\273.md" "b/notes/Frontend/\345\211\215\347\253\257\347\237\245\350\257\206\344\275\223\347\263\273.md" index 7c155a20..ddd0e049 100644 --- "a/notes/Frontend/\345\211\215\347\253\257\347\237\245\350\257\206\344\275\223\347\263\273.md" +++ "b/notes/Frontend/\345\211\215\347\253\257\347\237\245\350\257\206\344\275\223\347\263\273.md" @@ -1,37 +1,40 @@ - [前端知识体系](#前端知识体系) - - [前端三要素](#前端三要素) - - [结构层(HTML)](#结构层html) - - [表现层(CSS)](#表现层css) - - [什么是 CSS 预处理器](#什么是-css-预处理器) - - [常用的 CSS 预处理器有哪些](#常用的-css-预处理器有哪些) - - [行为层(JavaScript)](#行为层javascript) - - [Native 原生 JS 开发](#native-原生-js-开发) - - [TypeScript 微软的标准](#typescript-微软的标准) - - [JavaScript 框架](#javascript-框架) - - [UI 框架](#ui-框架) - - [JavaScript 构建工具](#javascript-构建工具) - - [三端统一](#三端统一) - - [混合开发(Hybrid App)](#混合开发hybrid-app) - - [微信小程序](#微信小程序) - - [后端技术](#后端技术) - - [附:当前主流前端框架](#附当前主流前端框架) - - [Vue.js](#vuejs) - - [iView](#iview) - - [ElementUI](#elementui) - - [ICE](#ice) - - [VantUI](#vantui) - - [AtUI](#atui) - - [CubeUI](#cubeui) - - [混合开发](#混合开发) - - [Flutter](#flutter) - - [Ionic](#ionic) - - [微信小程序](#微信小程序-1) - - [mpvue](#mpvue) - - [WeUI](#weui) + - [前端三要素](#前端三要素) + - [结构层(HTML)](#结构层html) + - [表现层(CSS)](#表现层css) + - [什么是 CSS 预处理器](#什么是-css-预处理器) + - [常用的 CSS 预处理器有哪些](#常用的-css-预处理器有哪些) + - [行为层(JavaScript)](#行为层javascript) + - [Native 原生 JS 开发](#native-原生-js-开发) + - [TypeScript 微软的标准](#typescript-微软的标准) + - [JavaScript 框架](#javascript-框架) + - [UI 框架](#ui-框架) + - [JavaScript 构建工具](#javascript-构建工具) + - [三端统一](#三端统一) + - [混合开发(Hybrid App)](#混合开发hybrid-app) + - [微信小程序](#微信小程序) + - [后端技术](#后端技术) + - [附:当前主流前端框架](#附当前主流前端框架) + - [Angular](#angular) + - [Angular Material](#angular-material) + - [Vue.js](#vuejs) + - [iView](#iview) + - [ElementUI](#elementui) + - [ICE](#ice) + - [VantUI](#vantui) + - [AtUI](#atui) + - [CubeUI](#cubeui) + - [混合开发](#混合开发) + - [Flutter](#flutter) + - [Ionic](#ionic) + - [微信小程序](#微信小程序-1) + - [mpvue](#mpvue) + - [WeUI](#weui) + # 前端知识体系 ## 前端三要素 @@ -40,14 +43,10 @@ - CSS(表现):层叠样式表(Cascading Style Sheets),设定网页的表现样式 - JavaScript(行为):是一种弱类型脚本语言,其源代码不需经过编译,而是由浏览器解释运行,用于控制网页的行为 - - ### 结构层(HTML) 略 - - ### 表现层(CSS) CSS 层叠样式表是一门标记语言,并不是编程语言,因此不可以自定义变量,不可以引用等,换句话说就是不具备任何语法支持,它主要缺陷如下: @@ -57,14 +56,10 @@ CSS 层叠样式表是一门标记语言,并不是编程语言,因此不可 这就导致了我们在工作中无端增加了许多工作量。为了解决这个问题,前端开发人员会使用一种称之为 **【CSS 预处理器】** 的工具,提供 CSS 缺失的样式层复用机制、减少冗余代码,提高样式代码的可维护性。大大提高了前端在样式上的开发效率。 - - #### 什么是 CSS 预处理器 CSS 预处理器定义了一种新的语言,其基本思想是,用一种专门的编程语言,为 CSS 增加了一些编程的特性,将 CSS 作为目标生成文件,然后开发者就只要使用这种语言进行 CSS 的编码工作。转化成通俗易懂的话来说就是“**用一种专门的编程语言,进行 Web 页面样式设计,再通过编译器转化为正常的 CSS 文件,以供项目使用**”。 - - #### 常用的 CSS 预处理器有哪些 - SASS:基于 Ruby,通过服务端处理,功能强大。解析效率高。需要学习 Ruby 语言,上手难度高于 LESS。 @@ -97,7 +92,7 @@ TypeScript 是一种由微软开发的自由和开源的编程语言。它是 Ja #### JavaScript 框架 - jQuery:大家熟知的 JavaScript 框架,优点是简化了 DOM 操作,缺点是 DOM 操作太频繁,影响前端性能;在前端眼里使用它仅仅是为了兼容 IE6、7、8; -- Angular:Google 收购的前端框架,由一群 Java 程序员开发,其特点是将后台的 MVC 模式搬到了前端并增加了模块化开发的理念,与微软合作,采用 TypeScript 语法开发;对后台程序员友好,对前端程序员不太友好;最大的缺点是版本迭代不合理(如:1代 -> 2代,除了名字,基本就是两个东西;截止发表博客时已推出了 Angular6) +- Angular:Google 收购的前端框架,由一群 Java 程序员开发,其特点是将后台的 MVC 模式搬到了前端并增加了模块化开发的理念,与微软合作,采用 TypeScript 语法开发;对后台程序员友好,对前端程序员不太友好;最大的缺点是版本迭代不合理(如:1 代 -> 2 代,除了名字,基本就是两个东西;截止发表博客时已推出了 Angular8)。Angular 作为 Google 推出的一个“重型”的框架,内置包括路由、HTTP 等常用组件,同时深度集成 TypeScript,WebPack,RxJS,angular-cli 等工具,可以方便地进行大型前端项目的开发以及调试。但是由于 Angular 引入了许多概念,增加了新手上手的复杂性。特别是组件、模块、服务、指令、依赖注入的引入,对于新手而言,厘清其中各个概念的作用域是就不是一件容易的事情。同时 RxJS 的引入,也进一步使得学习曲线变得陡峭。 - React:Facebook 出品,一款高性能的 JS 前端框架;特点是提出了新概念 **【虚拟 DOM】** 用于减少真实 DOM 操作,在内存中模拟 DOM 操作,有效的提升了前端渲染效率;缺点是使用复杂,因为需要额外学习一门 **【JSX】** 语言; - `Vue`:一款渐进式 JavaScript 框架,所谓渐进式就是逐步实现新特性的意思,如实现模块化开发、路由、状态管理等新特性。其特点是综合了 Angular(模块化) 和 React(虚拟 DOM) 的优点; - `Axios`:前端通信框架;因为 `Vue` 的边界很明确,就是为了处理 DOM,所以并不具备通信能力,此时就需要额外使用一个通信框架与服务器交互;当然也可以直接选择使用 jQuery 提供的 AJAX 通信功能; @@ -144,6 +139,21 @@ NodeJS 的作者已经声称放弃 NodeJS(说是架构做的不好再加上笨 ## 附:当前主流前端框架 +### Angular + +#### Angular Material + +Angular Material 是 Angular 官方团队维护的一个遵从 Material Design 的 Angular UI 库。它提供了丰富的组件库、CDK、以及内置主题,开箱即用,并且与 Angular 版本同步。 + +- [官网地址](https://material.angular.io/) +- [Github](https://github.com/angular/components) + +#### ngx-admin + +ngx-admin 是一款开源的 Angualr UI 库,目前仍处于维护和活跃状态。它也提供了丰富的开箱即用的组件库、内置主题,并且与 Angular 版本同步。代码风格遵循 Angular 最佳实践,也是一个不错的学习资源。 + +- [Github](https://github.com/akveo/ngx-admin) + ### Vue.js #### iView @@ -231,4 +241,4 @@ mpvue 是美团开发的一个使用 `Vue.js` 开发小程序的前端框架, WeUI 是一套同微信原生视觉体验一致的基础样式库,由微信官方设计团队为微信内网页和微信小程序量身设计,令用户的使用感知更加统一。包含 button、cell、dialog、toast、article、icon 等各式元素。 - [官网地址](https://weui.io/) -- [Github](https://github.com/weui/weui.git) \ No newline at end of file +- [Github](https://github.com/weui/weui.git) diff --git a/notes/Git.md b/notes/Git.md index e187b143..e2ca1f02 100644 --- a/notes/Git.md +++ b/notes/Git.md @@ -475,3 +475,17 @@ https://blog.csdn.net/u011478909/article/details/77683754 +## 8. Git 回退到制定版本 + +**回滚到指定的版本** + +```shell +git reset --hard e377f60e28c8b84158 +``` + +**强制提交** + +```shell +git push -f origin master +``` + diff --git "a/notes/JavaArchitecture/02-Java\351\233\206\345\220\210\346\241\206\346\236\266.md" "b/notes/JavaArchitecture/02-Java\351\233\206\345\220\210\346\241\206\346\236\266.md" index 03f69434..1873bcb3 100644 --- "a/notes/JavaArchitecture/02-Java\351\233\206\345\220\210\346\241\206\346\236\266.md" +++ "b/notes/JavaArchitecture/02-Java\351\233\206\345\220\210\346\241\206\346\236\266.md" @@ -1761,7 +1761,7 @@ List list = Arrays.asList(1,2,3); **Hashtable 使用链地址法进行元素存储,通过一个实际的例子来演示一下插入元素的过程:** -假设我们现在 Hashtable 的容量为 5,已经存在了 (5,5),(13,13),(16,16),(17,17),(21,21) 这 5 个键值对,目前他们在 Hashtable 中的位置如下: +假设我们现在 Hashtable 的容量为 5,已经存在了 (8,8),(10,10),(13,13),(16,16),(17,17),(21,21) 这 6 个键值对,目前他们在 Hashtable 中的位置如下:

@@ -1814,7 +1814,7 @@ List list = Arrays.asList(1,2,3);   把访问逻辑从不同类型的集合类中抽取出来,从而避免向外部暴露集合的内部结构。 -  **迭代器模式**:就是提供一种方法对一个容器对象中的各个元素进行访问,而又不暴露该对象容器的内部细。 +  **迭代器模式**:就是提供一种方法对一个容器对象中的各个元素进行访问,而又不暴露该对象容器的内部细节。 ```java public static void main(String[] args) { @@ -1928,4 +1928,4 @@ static int indexFor(int h, int length) { # 更新日志 - 2018/8/3 v2.5 基础版 -- 2018/9/1 v3.0 初稿版 \ No newline at end of file +- 2018/9/1 v3.0 初稿版 diff --git "a/notes/JavaArchitecture/03-Java\345\271\266\345\217\221\347\274\226\347\250\213.md" "b/notes/JavaArchitecture/03-Java\345\271\266\345\217\221\347\274\226\347\250\213.md" index 8149fe1c..f90fdfb1 100644 --- "a/notes/JavaArchitecture/03-Java\345\271\266\345\217\221\347\274\226\347\250\213.md" +++ "b/notes/JavaArchitecture/03-Java\345\271\266\345\217\221\347\274\226\347\250\213.md" @@ -770,7 +770,7 @@ ReentrantLock 多了一些高级功能。 - (用法)synchronized(隐式锁):在需要同步的对象中加入此控制,synchronized 可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。 - (用法)lock(显示锁):需要显示指定起始位置和终止位置。一般使用 ReentrantLock 类做为锁,多个线程中必须要使用一个 ReentrantLock 类做为对象才能保证锁的生效。且在加锁和解锁处需要通过 lock() 和 unlock() 显示指出。所以一般会在 finally 块中写 unlock() 以防死锁。 - (性能)synchronized 是托管给 JVM 执行的,而 lock 是 Java 写的控制锁的代码。在 Java1.5 中,synchronize 是性能低效的。因为这是一个重量级操作,需要调用操作接口,导致有可能加锁消耗的系统时间比加锁以外的操作还多。相比之下使用 Java 提供的 Lock 对象,性能更高一些。但是到了 Java1.6 ,发生了变化。synchronize 在语义上很清晰,可以进行很多优化,有适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致 在 Java1.6 上 synchronize 的性能并不比 Lock 差。 -- (机制)**synchronized 原始采用的是 CPU 悲观锁机制,即线程获得的是独占锁**。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。**Lock 用的是乐观锁方式**。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁实现的机制就是 CAS 操作(Compare and Swap)。 +- (机制)**synchronized 原始采用的是 CPU 悲观锁机制,即线程获得的是独占锁。Lock 也属于悲观锁**。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。相对而言乐观锁每次不加锁,而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁实现的机制就是 CAS 操作(Compare and Swap)。 diff --git a/notes/Linux.md b/notes/Linux.md index 745a95f5..5d9fadd8 100644 --- a/notes/Linux.md +++ b/notes/Linux.md @@ -742,12 +742,12 @@ cp [-adfilprsu] source destination 和 cat 不同的是它可以一页一页查看文件内容,比较适合大文件的查看。 -- 按Space键:显示文本的下一屏内容。 -- 按Enier键:只显示文本的下一行内容。 -- 按斜线符`/`:接着输入一个模式,可以在文本中寻找下一个相匹配的模式。 -- 按H键:显示帮助屏,该屏上有相关的帮助信息。 -- 按B键:显示上一屏内容。 -- 按Q键:退出rnore命令。 +- 按 Space 键:显示文本的下一屏内容。 +- 按 Enter 键:只显示文本的下一行内容。 +- 按斜线符 `/` :接着输入一个模式,可以在文本中寻找下一个相匹配的模式。 +- 按 H 键:显示帮助屏,该屏上有相关的帮助信息。 +- 按 B 键:显示上一屏内容。 +- 按 Q 键:退出 more 命令。 ### 4. less @@ -2197,4 +2197,4 @@ https://www.cnblogs.com/CEO-H/p/7794306.html - 2018/8/23 v2.0 基础版1 -- 2018/8/26 - 27 v2.5 基础版2 \ No newline at end of file +- 2018/8/26 - 27 v2.5 基础版2 diff --git a/notes/MicroService/kafka/README.md b/notes/MicroService/kafka/README.md new file mode 100644 index 00000000..5abbcf0e --- /dev/null +++ b/notes/MicroService/kafka/README.md @@ -0,0 +1,24 @@ +
+ +# Kafka Tutorial + +深入浅出 Kafka,将用最极简的语言带你走进 Kafka 的消息中间件世界。 + +| 部分 | 章节 | 概要 | 进度 | +| :--- | :----------------------------------------------------------- | :---------------------------------------------- | :--- | +| Ⅰ | [深入浅出 Kafka(一)初识](kafka-tutorial-1_%E5%88%9D%E8%AF%86.md) | 背景、核心概念、架构设计 | √ | +| Ⅱ | [深入浅出 Kafka(二)单节点部署](kafka-tutorial-2_%E5%8D%95%E8%8A%82%E7%82%B9%E9%83%A8%E7%BD%B2.md) | 单节点下,宿主机和容器的两种部署方式 | √ | +| Ⅲ | 深入浅出 Kafka(三)集群化部署 | 集群下的,宿主机和容器的两种部署方式 | | +| Ⅳ | [深入浅出 Kafka(四)架构深入](kafka-tutorial-4_%E6%9E%B6%E6%9E%84%E6%B7%B1%E5%85%A5.md) | 深入理解 Kafka 的架构细节 | √ | +| Ⅴ | [深入浅出 Kafka(五)Kafka API](kafka-tutorial-5_kafka-api.md) | Kafka API 接口使用 | √ | +| Ⅵ | [深入浅出 Kafka(六)Spring Kafka API](kafka-tutorial-6_spring-kafka-api.md) | Kafka 与 SpringBoot 框架整合,常见 API 接口使用 | √ | +| Ⅶ | 深入浅出 Kafka(七)监控 | Kafka Monitor,Kafka Manager | | + + + +## 参考资料 + +- 官网:[Apache Kafka](https://kafka.apache.org/) +- 中文社区:[Kafka 中文文档 - ApacheCN](http://kafka.apachecn.org/) +- 快速上手:[尚硅谷大数据课程之Kafka(2019新版)](https://www.bilibili.com/video/av65544753?from=search&seid=14596778029771113163) + diff --git a/notes/MicroService/kafka/assets/1567511408405.png b/notes/MicroService/kafka/assets/1567511408405.png new file mode 100644 index 00000000..65857898 Binary files /dev/null and b/notes/MicroService/kafka/assets/1567511408405.png differ diff --git a/notes/MicroService/kafka/assets/1567511461051.png b/notes/MicroService/kafka/assets/1567511461051.png new file mode 100644 index 00000000..494d9519 Binary files /dev/null and b/notes/MicroService/kafka/assets/1567511461051.png differ diff --git a/notes/MicroService/kafka/assets/1567511487508.png b/notes/MicroService/kafka/assets/1567511487508.png new file mode 100644 index 00000000..4abe3a13 Binary files /dev/null and b/notes/MicroService/kafka/assets/1567511487508.png differ diff --git a/notes/MicroService/kafka/assets/1567511669385.png b/notes/MicroService/kafka/assets/1567511669385.png new file mode 100644 index 00000000..f8b85711 Binary files /dev/null and b/notes/MicroService/kafka/assets/1567511669385.png differ diff --git a/notes/MicroService/kafka/assets/1567513334508.png b/notes/MicroService/kafka/assets/1567513334508.png new file mode 100644 index 00000000..5cbf5abf Binary files /dev/null and b/notes/MicroService/kafka/assets/1567513334508.png differ diff --git a/notes/MicroService/kafka/assets/1567513364566.png b/notes/MicroService/kafka/assets/1567513364566.png new file mode 100644 index 00000000..398a8efa Binary files /dev/null and b/notes/MicroService/kafka/assets/1567513364566.png differ diff --git a/notes/MicroService/kafka/assets/1567513386020.png b/notes/MicroService/kafka/assets/1567513386020.png new file mode 100644 index 00000000..373d5add Binary files /dev/null and b/notes/MicroService/kafka/assets/1567513386020.png differ diff --git a/notes/MicroService/kafka/assets/kafka-0copy.png b/notes/MicroService/kafka/assets/kafka-0copy.png new file mode 100644 index 00000000..5ea67b6d Binary files /dev/null and b/notes/MicroService/kafka/assets/kafka-0copy.png differ diff --git a/notes/MicroService/kafka/assets/kafka-ack.png b/notes/MicroService/kafka/assets/kafka-ack.png new file mode 100644 index 00000000..e4550fe9 Binary files /dev/null and b/notes/MicroService/kafka/assets/kafka-ack.png differ diff --git a/notes/MicroService/kafka/assets/kafka-acks-1.png b/notes/MicroService/kafka/assets/kafka-acks-1.png new file mode 100644 index 00000000..536aff61 Binary files /dev/null and b/notes/MicroService/kafka/assets/kafka-acks-1.png differ diff --git a/notes/MicroService/kafka/assets/kafka-acks1.png b/notes/MicroService/kafka/assets/kafka-acks1.png new file mode 100644 index 00000000..ce48a9b1 Binary files /dev/null and b/notes/MicroService/kafka/assets/kafka-acks1.png differ diff --git a/notes/MicroService/kafka/assets/kafka-failover.png b/notes/MicroService/kafka/assets/kafka-failover.png new file mode 100644 index 00000000..fd187c14 Binary files /dev/null and b/notes/MicroService/kafka/assets/kafka-failover.png differ diff --git a/notes/MicroService/kafka/assets/kafka-index.png b/notes/MicroService/kafka/assets/kafka-index.png new file mode 100644 index 00000000..9cb49d1d Binary files /dev/null and b/notes/MicroService/kafka/assets/kafka-index.png differ diff --git a/notes/MicroService/kafka/assets/kafka-index2.png b/notes/MicroService/kafka/assets/kafka-index2.png new file mode 100644 index 00000000..be624421 Binary files /dev/null and b/notes/MicroService/kafka/assets/kafka-index2.png differ diff --git a/notes/MicroService/kafka/assets/kafka-interceptor.png b/notes/MicroService/kafka/assets/kafka-interceptor.png new file mode 100644 index 00000000..2b300a16 Binary files /dev/null and b/notes/MicroService/kafka/assets/kafka-interceptor.png differ diff --git a/notes/MicroService/kafka/assets/kafka-mq1.png b/notes/MicroService/kafka/assets/kafka-mq1.png new file mode 100644 index 00000000..51104cdc Binary files /dev/null and b/notes/MicroService/kafka/assets/kafka-mq1.png differ diff --git a/notes/MicroService/kafka/assets/kafka-mq2.png b/notes/MicroService/kafka/assets/kafka-mq2.png new file mode 100644 index 00000000..04e195d8 Binary files /dev/null and b/notes/MicroService/kafka/assets/kafka-mq2.png differ diff --git a/notes/MicroService/kafka/assets/kafka-offset.png b/notes/MicroService/kafka/assets/kafka-offset.png new file mode 100644 index 00000000..80987dfd Binary files /dev/null and b/notes/MicroService/kafka/assets/kafka-offset.png differ diff --git a/notes/MicroService/kafka/assets/kafka-partition.png b/notes/MicroService/kafka/assets/kafka-partition.png new file mode 100644 index 00000000..b6ab9817 Binary files /dev/null and b/notes/MicroService/kafka/assets/kafka-partition.png differ diff --git a/notes/MicroService/kafka/assets/kafka-produce.png b/notes/MicroService/kafka/assets/kafka-produce.png new file mode 100644 index 00000000..e9d34bb9 Binary files /dev/null and b/notes/MicroService/kafka/assets/kafka-produce.png differ diff --git a/notes/MicroService/kafka/assets/kafka-work.png b/notes/MicroService/kafka/assets/kafka-work.png new file mode 100644 index 00000000..1ff18892 Binary files /dev/null and b/notes/MicroService/kafka/assets/kafka-work.png differ diff --git a/notes/MicroService/kafka/assets/kafka-zk.png b/notes/MicroService/kafka/assets/kafka-zk.png new file mode 100644 index 00000000..3c267c2f Binary files /dev/null and b/notes/MicroService/kafka/assets/kafka-zk.png differ diff --git a/notes/MicroService/kafka/assets/logo.png b/notes/MicroService/kafka/assets/logo.png new file mode 100644 index 00000000..9227b04e Binary files /dev/null and b/notes/MicroService/kafka/assets/logo.png differ diff --git a/notes/MicroService/kafka/assets/nAc6zQ.png b/notes/MicroService/kafka/assets/nAc6zQ.png new file mode 100644 index 00000000..389146e7 Binary files /dev/null and b/notes/MicroService/kafka/assets/nAc6zQ.png differ diff --git a/notes/MicroService/kafka/assets/nAcIiT.png b/notes/MicroService/kafka/assets/nAcIiT.png new file mode 100644 index 00000000..002f6983 Binary files /dev/null and b/notes/MicroService/kafka/assets/nAcIiT.png differ diff --git "a/notes/MicroService/kafka/kafka-tutorial-1_\345\210\235\350\257\206.md" "b/notes/MicroService/kafka/kafka-tutorial-1_\345\210\235\350\257\206.md" new file mode 100644 index 00000000..d70e7413 --- /dev/null +++ "b/notes/MicroService/kafka/kafka-tutorial-1_\345\210\235\350\257\206.md" @@ -0,0 +1,109 @@ + + + +- [深入浅出 Kafka(一)初识](#深入浅出-kafka一初识) + - [一、定义](#一定义) + - [二、消息队列(Message Queue)](#二消息队列message-queue) + - [传统消息队列的应用场景](#传统消息队列的应用场景) + - [使用消息队列的好处](#使用消息队列的好处) + - [消息队列的两种模式](#消息队列的两种模式) + - [1. 点对点(Queue,不可重复消费)](#1-点对点queue不可重复消费) + - [2. 发布/订阅(Topic,可以重复消费)](#2-发布订阅topic可以重复消费) + - [三、Kafka基础架构](#三kafka基础架构) + - [参考资料](#参考资料) + + +# 深入浅出 Kafka(一)初识 + +> 开始前,可以阅读 Kafka 官方介绍:[Kafka 中文文档 - ApacheCN](http://kafka.apachecn.org/intro.html) + + + +## 一、定义 + +Kafka 是一个分布式的基于发布/订阅模式的消息队列(Message Queue),主要应用于大数据实时处理领域。 + + + +## 二、消息队列(Message Queue) + +### 传统消息队列的应用场景 + +![nAc6zQ.png](assets/nAc6zQ.png) + + + + +### 使用消息队列的好处 + +- 解耦 + - 允许你独立的扩展或修改两边的处理过程,只要确保它们遵守同样的接口约束。 + +- 可恢复性 + - 系统的一部分组件失效时,不会影响到整个系统。消息队列降低了进程间的耦合度,所以即使一个处理消息的进程挂掉,加入队列中的消息仍然可以在系统恢复后被处理。 + +- 缓冲 + - 有助于控制和优化数据流经过系统的速度,解决生产消息和消费消息的处理速度不一致的情况。 + +- 灵活性 & 峰值处理能力 + - 在访问量剧增的情况下,应用仍然需要继续发挥作用,但是这样的突发流量并不常见。如果为以能处理这类峰值访问为标准来投入资源随时待命无疑是巨大的浪费。使用消息队列能够使关键组件顶住突发的访问压力,而不会因为突发的超负荷的请求而完全崩溃。 + +- 异步通信 + - 很多时候,用户不想也不需要立即处理消息。消息队列提供了异步处理机制,允许用户把一个消息放入队列,但并不立即处理它。想向队列中放入多少消息就放多少,然后在需要的时候再去处理它们。 + + + + + +### 消息队列的两种模式 + +Java消息服务(Java Message Service,JMS)规范目前支持两种消息模型:点对点(point to point, queue)和发布/订阅(publish/subscribe,topic)。 + +#### 1. 点对点(Queue,不可重复消费) + +消息生产者生产消息发送到queue中,然后消息消费者从queue中取出并且消费消息。 +消息被消费以后,queue中不再有存储,所以消息消费者不可能消费到已经被消费的消息。Queue支持存在多个消费者,但是对一个消息而言,只会有一个消费者可以消费。 + +![kafka-mq1](assets/kafka-mq1.png) + + + +#### 2. 发布/订阅(Topic,可以重复消费) + +Pub/Sub发布订阅(广播):使用topic作为通信载体 + +消息生产者(发布)将消息发布到topic中,同时有多个消息消费者(订阅)消费该消息。和点对点方式不同,发布到topic的消息会被所有订阅者消费。 + +topic实现了发布和订阅,当你发布一个消息,所有订阅这个topic的服务都能得到这个消息,所以从1到N个订阅者都能得到一个消息的拷贝。 + +![kafka-mq2](assets/kafka-mq2.png) + + + +## 三、Kafka基础架构 + +![nAcIiT.png](assets/nAcIiT.png) + +- **Producer**:消息生产者,就是向kafka broker发消息的客户端; + +- **Consumer**:消息消费者,向kafka broker取消息的客户端; + +- **Consumer Group(CG)**:消费者组,由多个consumer组成。****消费者组内每个消费者负责消费不同分区的数据,一个分区只能由一个消费者消费;消费者组之间互不影响。所有的消费者都属于某个消费者组,即消费者组是逻辑上的一个订阅者。 + +- **Broker** 一台kafka服务器就是一个broker。一个集群由多个broker组成。一个broker可以容纳多个topic。 + +- **Topic** 可以理解为一个队列,**生产者和消费者面向的都是一个topic**; + +- **Partidion** 为了实现扩展性,一个非常大的topic可以分布到多个broker(即服务器)上,**一个topic可以分为多个partition**,每个partition是一个有序的队列;(分区主要使用来实现负载均衡) + +- **Replica** 副本,为保证集群中的某个节点发生故障时,该节点上的partition数据不丢失,且kafka仍然能够继续工作,kafka提供了副本机制,一个topic的每个分区都有若干个副本,一个**leader**和若干个**follower**。 + +- **Leader** 每个分区多个副本的“主”,生产者发送数据的对象,以及消费者消费数据的对象都是leader。 + +- **Follower** 每个分区多个副本中的“从”,实时从leader中同步数据,保持和leader数据的同步。leader发生故障时,某个follower会成为新的follower。 + + + +## 参考资料 + +- [Note/Kafka.md at master · Tiankx1003/Note](https://github.com/Tiankx1003/Note/blob/master/Markdown/HadoopEcosys/Kafka.md) diff --git "a/notes/MicroService/kafka/kafka-tutorial-2_\345\215\225\350\212\202\347\202\271\351\203\250\347\275\262.md" "b/notes/MicroService/kafka/kafka-tutorial-2_\345\215\225\350\212\202\347\202\271\351\203\250\347\275\262.md" new file mode 100644 index 00000000..f07e3930 --- /dev/null +++ "b/notes/MicroService/kafka/kafka-tutorial-2_\345\215\225\350\212\202\347\202\271\351\203\250\347\275\262.md" @@ -0,0 +1,409 @@ + + +- [深入浅出 Kafka(二)单节点部署](#深入浅出-kafka二单节点部署) + - [系统环境](#系统环境) + - [一、宿主机部署](#一宿主机部署) + - [安装 Zookeeper(可选择,自带或是独立的 zk 服务)](#安装-zookeeper可选择自带或是独立的-zk-服务) + - [下载 Kafka](#下载-kafka) + - [启动 Zookeeper 服务(可选择,自带或是独立的 zk 服务)](#启动-zookeeper-服务可选择自带或是独立的-zk-服务) + - [启动 Kafka 服务](#启动-kafka-服务) + - [创建 Topic](#创建-topic) + - [查看 Topic](#查看-topic) + - [产生消息](#产生消息) + - [消费消息](#消费消息) + - [删除 Topic](#删除-topic) + - [查看描述 Topic 信息](#查看描述-topic-信息) + - [二、容器化部署](#二容器化部署) + - [1 Zookeeper + 1 Kafka](#1-zookeeper--1-kafka) + - [与容器内的开发环境交互](#与容器内的开发环境交互) + - [三、Kafka 配置说明](#三kafka-配置说明) + - [参考资料](#参考资料) + + + + +# 深入浅出 Kafka(二)单节点部署 + +> 单节点部署环境,主要用于学习与调试。集群化部署方案,请访问「深入浅出 Kafka(三)集群化部署」;若部署完单节点想进一步学习,请转向「深入浅出 Kafka(四)架构深入」。 + + + +## 系统环境 + +- CentOS 7.4 +- Kafka 2.11 + + + +## 一、宿主机部署 + +### 安装 Zookeeper(可选择,自带或是独立的 zk 服务) + +下载 + +```shell +wget https://mirrors.huaweicloud.com/apache/zookeeper/zookeeper-3.4.10/zookeeper-3.4.10.tar.gz +``` + +启动 + +```SHELL +sh zkServer.sh start +``` + + + +### 下载 Kafka + +从[官网下载](https://kafka.apache.org/downloads)Kafka 安装包,解压安装,或直接使用命令下载。 + +```shell +wget https://mirrors.huaweicloud.com/apache/kafka/1.1.0/kafka_2.12-1.1.0.tgz +``` + +解压安装 + +```shell +tar -zvxf kafka_2.11-1.0.0.tgz -C /usr/local/ +cd /usr/local/kafka_2.11-1.0.0/ +``` + +修改配置文件 + +``` +vim config/server.properties +``` + +修改其中 + +``` +log.dirs=data/kafka-logs +listeners=PLAINTEXT://192.168.72.133:9092 +``` + +> 另外 advertised.listeners,是暴露给外部的 listeners,如果没有设置,会用 listeners + + + +### 启动 Zookeeper 服务(可选择,自带或是独立的 zk 服务) + +使用安装包中的脚本启动单节点 Zookeeper 实例: + +``` +bin/zookeeper-server-start.sh -daemon config/zookeeper.properties +``` + + + +### 启动 Kafka 服务 + +使用 kafka-server-start.sh 启动 kafka 服务 + +前台启动 + +``` +bin/kafka-server-start.sh config/server.properties +``` + +后台启动 + +``` +bin/kafka-server-start.sh -daemon config/server.properties +``` + + + +### 创建 Topic + +使用 kafka-topics.sh 创建但分区单副本的 topic test + +``` +bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test +``` + + + +### 查看 Topic + +``` +bin/kafka-topics.sh --list --zookeeper localhost:2181 +``` + + + +### 产生消息 + +使用 kafka-console-producer.sh 发送消息 + +``` +bin/kafka-console-producer.sh --broker-list localhost:9092 --topic test +``` + + + +### 消费消息 + +使用 kafka-console-consumer.sh 接收消息并在终端打印 + +``` +bin/kafka-console-consumer.sh --zookeeper localhost:2181 --topic test --from-beginning +``` + + + +### 删除 Topic + +``` +bin/kafka-topics.sh --delete --zookeeper localhost:2181 --topic test +``` + + + +### 查看描述 Topic 信息 + +```shell +[root@localhost kafka_2.11-1.0.0]# bin/kafka-topics.sh --describe --zookeeper +Topic:test PartitionCount:1 ReplicationFactor:1 Configs: + Topic: test Partition: 0 Leader: 1 Replicas: 1 Isr: 1 +``` + +第一行给出了所有分区的摘要,每个附加行给出了关于一个分区的信息。 由于我们只有一个分区,所以只有一行。 + +- Leader + - 是负责给定分区的所有读取和写入的节点。 每个节点将成为分区随机选择部分的领导者。 + +- Replicas + - 是复制此分区日志的节点列表,无论它们是否是领导者,或者即使他们当前处于活动状态。 + +- Isr + - 是一组 “同步” 副本。这是复制品列表的子集,当前活着并被引导到领导者。 + + + +## 二、容器化部署 + +  在上述的篇幅中,实现了宿主机上部署单节点环境(1 Zookeeper + 1 Kafka)。但是在不同环境配置上具有差异性,初学者入门需要进行复杂的配置,可能会造成配置失败。 + +  使用 Docker 容器化部署可以实现开箱即用,免去了很多安装配置的时间。 + +### 1 Zookeeper + 1 Kafka + +  以 [wurstmeister/kafka - Docker Hub](https://hub.docker.com/r/wurstmeister/kafka/) 为例,使用 docker-compose 运行一个只有一个 ZooKeeper node 和一个 Kafka broker 的开发环境: + +```yaml +version: '2' +services: + zoo1: + image: wurstmeister/zookeeper + restart: unless-stopped + hostname: zoo1 + ports: + - "2181:2181" + container_name: zookeeper + + # kafka version: 1.1.0 + # scala version: 2.12 + kafka1: + image: wurstmeister/kafka + ports: + - "9092:9092" + environment: + KAFKA_ADVERTISED_HOST_NAME: localhost + KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://192.168.72.133:9092 + KAFKA_ZOOKEEPER_CONNECT: "zoo1:2181" + KAFKA_BROKER_ID: 1 + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 + KAFKA_CREATE_TOPICS: "stream-in:1:1,stream-out:1:1" + depends_on: + - zoo1 + container_name: kafka +``` + +  这里利用了 wurstmeister/kafka 提供的环境参数 `KAFKA_CREATE_TOPICS` 使Kafka运行后自动创建 topics。 + + + +### 与容器内的开发环境交互 + +  可以使用 `docker exec` 命令直接调用 kafka 容器内的脚本来进行创建/删除 topic,启动 console producer 等等操作。 + +  如果本地存有与容器内相同的 Kafka 版本文件,也可以直接使用本地脚本文件。如上述 docker-compose.yml 文件所示,kafka1 的 hostname 即是 kafka1,端口为 9092,通过 kafka1:9092 就可以连接到容器内的 Kafka 服务。 + + + +**列出所有 topics** (在本地 kafka 路径下) + +```shell +$ bin/kafka-topics.sh --zookeeper localhost:2181 --list +``` + +**列出所有 Kafka brokers** + +```shell +$ docker exec zookeeper bin/zkCli.sh ls /brokers/ids +``` + + + +## 三、Kafka 配置说明 + +详细:[server.properties - Kafka 中文文档 - ApacheCN](http://kafka.apachecn.org/documentation.html#configuration) + +```properties +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# see kafka.server.KafkaConfig for additional details and defaults + +############################# Server Basics ############################# + +# The id of the broker. This must be set to a unique integer for each broker. +broker.id=0 + +############################# Socket Server Settings ############################# + +# The address the socket server listens on. It will get the value returned from +# java.net.InetAddress.getCanonicalHostName() if not configured. +# FORMAT: +# listeners = listener_name://host_name:port +# EXAMPLE: +# listeners = PLAINTEXT://your.host.name:9092 +listeners=PLAINTEXT://192.168.72.133:9092 + +# Hostname and port the broker will advertise to producers and consumers. If not set, +# it uses the value for "listeners" if configured. Otherwise, it will use the value +# returned from java.net.InetAddress.getCanonicalHostName(). +#advertised.listeners=PLAINTEXT://your.host.name:9092 + +# Maps listener names to security protocols, the default is for them to be the same. See the config documentation for more details +#listener.security.protocol.map=PLAINTEXT:PLAINTEXT,SSL:SSL,SASL_PLAINTEXT:SASL_PLAINTEXT,SASL_SSL:SASL_SSL + +# The number of threads that the server uses for receiving requests from the network and sending responses to the network +num.network.threads=3 + +# The number of threads that the server uses for processing requests, which may include disk I/O +num.io.threads=8 + +# The send buffer (SO_SNDBUF) used by the socket server +socket.send.buffer.bytes=102400 + +# The receive buffer (SO_RCVBUF) used by the socket server +socket.receive.buffer.bytes=102400 + +# The maximum size of a request that the socket server will accept (protection against OOM) +socket.request.max.bytes=104857600 + + +############################# Log Basics ############################# + +# A comma separated list of directories under which to store log files +log.dirs=/tmp/kafka-logs + +# The default number of log partitions per topic. More partitions allow greater +# parallelism for consumption, but this will also result in more files across +# the brokers. +num.partitions=1 + +# The number of threads per data directory to be used for log recovery at startup and flushing at shutdown. +# This value is recommended to be increased for installations with data dirs located in RAID array. +num.recovery.threads.per.data.dir=1 + +############################# Internal Topic Settings ############################# +# The replication factor for the group metadata internal topics "__consumer_offsets" and "__transaction_state" +# For anything other than development testing, a value greater than 1 is recommended for to ensure availability such as 3. +offsets.topic.replication.factor=1 +transaction.state.log.replication.factor=1 +transaction.state.log.min.isr=1 + +############################# Log Flush Policy ############################# + +# Messages are immediately written to the filesystem but by default we only fsync() to sync +# the OS cache lazily. The following configurations control the flush of data to disk. +# There are a few important trade-offs here: +# 1. Durability: Unflushed data may be lost if you are not using replication. +# 2. Latency: Very large flush intervals may lead to latency spikes when the flush does occur as there will be a lot of data to flush. +# 3. Throughput: The flush is generally the most expensive operation, and a small flush interval may lead to excessive seeks. +# The settings below allow one to configure the flush policy to flush data after a period of time or +# every N messages (or both). This can be done globally and overridden on a per-topic basis. + +# The number of messages to accept before forcing a flush of data to disk +#log.flush.interval.messages=10000 + +# The maximum amount of time a message can sit in a log before we force a flush +#log.flush.interval.ms=1000 + +############################# Log Retention Policy ############################# + +# The following configurations control the disposal of log segments. The policy can +# be set to delete segments after a period of time, or after a given size has accumulated. +# A segment will be deleted whenever *either* of these criteria are met. Deletion always happens +# from the end of the log. + +# The minimum age of a log file to be eligible for deletion due to age +log.retention.hours=168 + +# A size-based retention policy for logs. Segments are pruned from the log unless the remaining +# segments drop below log.retention.bytes. Functions independently of log.retention.hours. +#log.retention.bytes=1073741824 + +# The maximum size of a log segment file. When this size is reached a new log segment will be created. +log.segment.bytes=1073741824 + +# The interval at which log segments are checked to see if they can be deleted according +# to the retention policies +log.retention.check.interval.ms=300000 + +############################# Zookeeper ############################# + +# Zookeeper connection string (see zookeeper docs for details). +# This is a comma separated host:port pairs, each corresponding to a zk +# server. e.g. "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002". +# You can also append an optional chroot string to the urls to specify the +# root directory for all kafka znodes. +zookeeper.connect=localhost:2181 + +# Timeout in ms for connecting to zookeeper +zookeeper.connection.timeout.ms=6000 + + +############################# Group Coordinator Settings ############################# + +# The following configuration specifies the time, in milliseconds, that the GroupCoordinator will delay the initial consumer rebalance. +# The rebalance will be further delayed by the value of group.initial.rebalance.delay.ms as new members join the group, up to a maximum of max.poll.interval.ms. +# The default value for this is 3 seconds. +# We override this to 0 here as it makes for a better out-of-the-box experience for development and testing. +# However, in production environments the default value of 3 seconds is more suitable as this will help to avoid unnecessary, and potentially expensive, rebalances during application startup. +group.initial.rebalance.delay.ms=0 +``` + +- advertised.listeners and listeners 两个配置文件的区别:[kafka - advertised.listeners and listeners - fxjwind - 博客园](https://www.cnblogs.com/fxjwind/p/6225909.html?utm_source=tuicool&utm_medium=referral) + + + +## 参考资料 + +- [CentOS7 下 Kafka 的安装介绍 - 个人文章 - SegmentFault 思否](https://segmentfault.com/a/1190000012990954) + +- 非常重要:[kafka 踩坑之消费者收不到消息 - kris - CSDN 博客](https://blog.csdn.net/qq_25868207/article/details/81516024) + +- [kafka 安装搭建(整合 springBoot 使用) - u010391342 的博客 - CSDN 博客](https://blog.csdn.net/u010391342/article/details/81430402) + +- [Zookeeper+Kafka 的单节点配置 - 紫轩弦月 - 博客园](https://www.cnblogs.com/ALittleMoreLove/archive/2018/07/31/9396745.html) + +- [@KafkaListener 注解解密 - laomei - CSDN 博客](https://blog.csdn.net/sweatOtt/article/details/86714272) + +- [使用Docker快速搭建Kafka开发环境 - 简书](https://www.jianshu.com/p/ac03f126980e) + + diff --git "a/notes/MicroService/kafka/kafka-tutorial-3_\351\233\206\347\276\244\345\214\226\351\203\250\347\275\262.md" "b/notes/MicroService/kafka/kafka-tutorial-3_\351\233\206\347\276\244\345\214\226\351\203\250\347\275\262.md" new file mode 100644 index 00000000..b76f8a83 --- /dev/null +++ "b/notes/MicroService/kafka/kafka-tutorial-3_\351\233\206\347\276\244\345\214\226\351\203\250\347\275\262.md" @@ -0,0 +1,2 @@ +# 深入浅出 Kafka(三)集群化部署 + diff --git "a/notes/MicroService/kafka/kafka-tutorial-4_\346\236\266\346\236\204\346\267\261\345\205\245.md" "b/notes/MicroService/kafka/kafka-tutorial-4_\346\236\266\346\236\204\346\267\261\345\205\245.md" new file mode 100644 index 00000000..8085750f --- /dev/null +++ "b/notes/MicroService/kafka/kafka-tutorial-4_\346\236\266\346\236\204\346\267\261\345\205\245.md" @@ -0,0 +1,287 @@ + + +- [深入浅出 Kafka(四)架构深入](#深入浅出-kafka四架构深入) + - [一、Kafka 工作流程及文件存储机制](#一kafka-工作流程及文件存储机制) + - [二、Kafka 生产者](#二kafka-生产者) + - [1. 分区策略](#1-分区策略) + - [(1)分区的原因](#1分区的原因) + - [(2)分区的原则](#2分区的原则) + - [2. 数据可靠性保证](#2-数据可靠性保证) + - [(1)副本数据同步策略](#1副本数据同步策略) + - [(2)ISR](#2isr) + - [(3)ack 应答机制](#3ack-应答机制) + - [(4)ack 参数设置(asks)](#4ack-参数设置asks) + - [(4)数据一致性问题(故障处理)](#4数据一致性问题故障处理) + - [3. Exactly Once 语义](#3-exactly-once-语义) + - [三、Kafka 消费者](#三kafka-消费者) + - [1. 消费方式](#1-消费方式) + - [2. 分区分配策略](#2-分区分配策略) + - [3. offset 的维护](#3-offset-的维护) + - [四、Kafka 高效读写数据](#四kafka-高效读写数据) + - [1. 顺序写磁盘](#1-顺序写磁盘) + - [2. 零拷贝技术](#2-零拷贝技术) + - [五、Zookeeper 在 Kafka 中的作用](#五zookeeper-在-kafka-中的作用) + - [六、Kafka 事务](#六kafka-事务) + - [1. Producer事务事务](#1-producer事务事务) + - [2. Consumer **事务**](#2-consumer-事务) + + + +# 深入浅出 Kafka(四)架构深入 + +## 一、Kafka 工作流程及文件存储机制 + +![](assets/kafka-work.png) + + + +  Kafka 中消息是以 topic 进行分类的,生产者生产消息,消费者消费消息,都是面向 topic 的。 + +  topic 是逻辑上的概念,而 partition 是物理上的概念,每个 partition 对应于一个 log 文件,该 log 文件中存储的就是 producer 生产的数据。Producer 生产的数据会被不断追加到该 log 文件末端,且每条数据都有自己的 offset。消费者组中的每个消费者,都会实时记录自己消费到了哪个 offset,以便出错恢复时,从上次的位置继续消费。 + +![](assets/kafka-index.png) + +  由于生产者生产的消息会不断追加到 log 文件末尾,为防止 log 文件过大导致数据定位效率低下,Kafka 采取了**分片**和**索引**机制,将每个 partition 分为多个 segment。每个 segment 对应两个文件——“.index”文件和 “.log” 文件。这些文件位于一个文件夹下,该文件夹的命名规则为:topic 名称 + 分区序号。例如,first 这个 topic 有三个分区,则其对应的文件夹为 first-0,first-1,first-2。 + +``` +00000000000000000000.index +00000000000000000000.log +00000000000000170410.index +00000000000000170410.log +00000000000000239430.index +00000000000000239430.log +``` + +  index 和 log 文件以当前 segment 的第一条消息的 offset 命名。 + +![](assets/kafka-index2.png) + +  **“.index”文件存储大量的索引信息,“.log”文件存储大量的数据**,**索引文件中的元数据指向对应数据文件中**message 的物理偏移地址。 + + + +## 二、Kafka 生产者 + +### 1. 分区策略 + +#### (1)分区的原因 + +- **方便在集群中扩展**,每个 Partition 可以通过调整以适应他所在的机器,而一个 topic 可以有多个 Partition 组成,因此这个集群就可以适应任意大小的数据了; +- **可以提高并发**,因为可以以 Partition 为单位读写了。 + + + +#### (2)分区的原则 + +- 我们将 producer 发送的数据封装成一个 ProducerRecord 对象。 + +![](assets/kafka-partition.png) + +1. 指明 partition 的情况下,直接将指明的值直接作为 partition 值; +2. 没有指明 partition 值但有 key 的情况下,将 key 的 hash 值与 topic 的 partition 数进行取余得到 partition 值; +3. 既没有 partition 值有没有 key 值的情况下,第一次调用时随机生成一个整数(后面调用在这个整数上自增),将这个值的 topic 可用的 partition 总数取余得到 partition 值,也就是常说的 Round Robin(轮询调度)算法。 + + + +### 2. 数据可靠性保证 + +  为保证 producer 发送的数据,能可靠的发送到指定的 topic,topic 的每个 partition 收到 producer 发送的数据后,都需要向 producer 发送 ack(acknowledgement 确认收到),如果 producer 收到 ack,就会进行下一轮的发送,否则重新发送数据。 + +![](assets/kafka-ack.png) + +#### (1)副本数据同步策略 + +|**方案** |**优点** |**缺点** | +| ------------------------------- | -------------------------------------------------- | --------------------------------------------------- | +|**半数以上完成同步,就发送 ack**| 延迟低 | 选举新的 leader 时,容忍 n 台节点的故障,需要 2n+1 个副本 | +|**全部完成同步,才发送 ack** | 选举新的 leader 时,容忍 n 台节点的故障,需要 n+1 个副本 | 延迟高 | + +Kafka 选择了第二种方案,原因如下: +1. 同样为了容忍 n 台节点的故障,第一种方案需要 2n+1 个副本,而第二种方案只需要 n+1 个副本,而 Kafka 的每个分区都有大量的数据,第一种方案会造成大量数据的冗余。 +2. 虽然第二种方案的网络延迟会比较高,但网络延迟对 Kafka 的影响较小(同一网络环境下的传输)。 + + + +#### (2)ISR + +  采用第二种方案之后,设想以下情景:leader 收到数据,所有 follower 都开始同步数据,但有一个 follower,因为某种故障,迟迟不能与 leader 进行同步,那 leader 就要一直等下去,直到它完成同步,才能发送 ack。这个问题怎么解决呢? + +  Leader 维护了一个动态的 **in-sync replica set** (ISR),意为和 leader 保持同步的 follower 集合。当 ISR 中的 follower 完成数据的同步之后,leader 就会给 producer 发送 ack。如果 follower 长时间未向 leader 同步数据,则该 follower 将被踢出 ISR,该时间阈值由 **replica.lag.time.max.ms** 参数设定。Leader 发生故障之后,就会从 ISR 中选举新的 leader。 + + + +#### (3)ack 应答机制 + +  对于某些不太重要的数据,对数据的可靠性要求不是很高,能够容忍数据的少量丢失,所以没必要等 ISR 中的 follower 全部接收成功。 + +  所以 Kafka 为用户提供了三种可靠性级别,用户根据对可靠性和延迟的要求进行权衡,选择以下的配置。 + + + +#### (4)ack 参数设置(asks) + +- 0:producer 不等待 broker 的 ack,这一操作提供了一个最低的延迟,broker 一接收到还没有写入磁盘就已经返回,当 broker 故障时有可能**丢失数据**。 +- 1:producer 等待 broker 的 ack,partition 的 leader 落盘成功后返回 ack,如果在 follower 同步成功之前 leader 故障,那么就会**丢失数据**。 + +![](assets/kafka-acks1.png) + +- -1(all):producer 等待 broker 的 ack,partition 的 leader 和 follower(是 ISR 中的) 全部落盘成功后才返回 ack,但是如果 follower 同步完成后,broker 发送 ack 之前,leader 发生故障,producer 重新发送消息给新 leader 那么会造成**数据重复**。 + +![](assets/kafka-acks-1.png) + +#### (4)数据一致性问题(故障处理) + +![](assets/kafka-failover.png) + +- **follower 故障** + follower 发生故障后会被临时踢出 ISR,待该 follower 恢复后,follower 会读取本地磁盘记录的上次的 HW,并将 log 文件高于 HW 的部分截取掉,从 HW 开始向 leader 进行同步。等该 **follower 的 LEO 大于等于该 Partition 的 HW**,即 follower 追上 leader 之后,就可以重新加入 ISR 了。 + +- **leader 故障** + leader 发生故障之后,会从 ISR 中选出一个新的 leader,之后,为保证多个副本之间的数据一致性,其余的 follower 会先将各自的 log 文件高于 HW 的部分截掉,然后从新的 leader 同步数据。 + +**== 注意:这只能保证副本之间的数据一致性,并不能保证数据不丢失或者不重复。==** + +**注意**:这只能保证副本之间的数据一致性,并不能保证数据不丢失或者不重复。 + + + +### 3. Exactly Once 语义 + +  将服务器的 ACK 级别设置为 -1,可以保证 Producer 到 Server 之间不会丢失数据,即 At Least Once 语义。相对的,将服务器 ACK 级别设置为 0,可以保证生产者每条消息只会被 发送一次,即 At Most Once 语义。 + +  **At Least Once 可以保证数据不丢失,但是不能保证数据不重复**;相对的,At Least Once 可以保证数据不重复,但是不能保证数据不丢失。但是,对于一些非常重要的信息,比如说交易数据,下游数据消费者要求数据既不重复也不丢失,即 Exactly Once 语义。在 0.11 版本以前的 Kafka,对此是无能为力的,只能保证数据不丢失,再在下游消费者对数据做全局去重。对于多个下游应用的情况,每个都需要单独做全局去重,这就对性能造成了很大影响。 + +  0.11 版本的 Kafka,引入了一项重大特性:幂等性。所谓的幂等性就是指 Producer 不论向 Server 发送多少次重复数据,Server 端都只会持久化一条。幂等性结合 At Least Once 语义,就构成了 Kafka 的 Exactly Once 语义。即: + +
At Least Once + 幂等性 = Exactly Once
+  要启用幂等性,只需要将 Producer 的参数中 enable.idompotence 设置为 true 即可。Kafka 的幂等性实现其实就是将原来下游需要做的去重放在了数据上游。开启幂等性的 Producer 在初始化的时候会被分配一个 PID,发往同一 Partition 的消息会附带 Sequence Number。而 Broker 端会对 做缓存,当具有相同主键的消息提交时,Broker 只会持久化一条。 + +  但是 PID 重启就会变化,同时不同的 Partition 也具有不同主键,**所以幂等性无法保证跨分区跨会话**的 Exactly Once。 + + + +## 三、Kafka 消费者 + +### 1. 消费方式 + +  **consumer 采用 pull(拉)模式从 broker 中读取数据。** + +  **push(推)模式很难适应消费速率不同的消费者,因为消息发送速率是由 broker 决定的。**它的目标是尽可能以最快速度传递消息,但是这样很容易造成 consumer 来不及处理消息,典型的表现就是拒绝服务以及网络拥塞。而 pull 模式则可以根据 consumer 的消费能力以适当的速率消费消息。 + +  **pull 模式不足之处是,如果 kafka 没有数据,消费者可能会陷入循环中,一直返回空数据。**针对这一点,Kafka 的消费者在消费数据时会传入一个时长参数 timeout,如果当前没有数据可供消费,consumer 会等待一段时间之后再返回,这段时长即为 timeout。 + + + +### 2. 分区分配策略 + +  一个 consumer group 中有多个 consumer,一个 topic 有多个 partition,所以必然会涉及到 partition 的分配问题,即确定那个 partition 由哪个 consumer 来消费。 + +  Kafka 有两种分配策略,一是 **RoundRobin**,一是 **range**。 + +![1567511408405](assets/1567511408405.png) + + + +  **roundrobin**根据 partition 号对 consumer 个数取模后轮循分配 + +![1567511461051](assets/1567511461051.png) + + + +  **range**提前按照均匀分配的原则计算个数后直接分配。 + +![1567511487508](assets/1567511487508.png) + +  在订阅多个 partition 时 range 会有**不均匀**问题,kafka 默认为 range,因为不考虑多 partition 订阅时,range 效率更高。 + + + +### 3. offset 的维护 + +  由于 consumer 在消费过程中可能会出现断电宕机等故障,consumer 恢复后,需要从故障前的位置的继续消费,所以 consumer 需要实时记录自己消费到了哪个 offset,以便故障恢复后继续消费。 + +  group + topic + partition(GTP) 才能确定一个 offset! + +![1567511669385](assets/1567511669385.png) + +  Kafka 0.9 版本之前,consumer 默认将 offset 保存在 Zookeeper 中,从 0.9 版本开始,consumer 默认将 offset 保存在 Kafka 一个内置的 topic 中,该 topic 为 `__consumer_offsets`(此时消费者对于 offset 相当于生产者)。 + +1)修改配置文件 consumer.properties + +```properties +exclude.internal.topics=false +``` + +2)读取offset + +- 0.11.0.0 之前版本: + +```shell +bin/kafkabin/kafka--consoleconsole--consumer.sh consumer.sh ----topic __consumer_offsets topic __consumer_offsets ----zookeeper zookeeper hadoophadoop102102:2181 :2181 ----formatter formatter + +"kafka.coordinator.GroupMetadataManager"kafka.coordinator.GroupMetadataManager\\$OffsetsMessageFormatter" $OffsetsMessageFormatter" ----consumer.config config/consumer.properties consumer.config config/consumer.properties ----fromfrom--beginningbeginning +``` + +- 0.11.0.0 之后版本(含): + +```shell +bin/kafkabin/kafka--consoleconsole--consumer.sh consumer.sh ----topic __consumer_offsets topic __consumer_offsets ----zookeeper zookeeper hadoophadoop102102:2181 :2181 ----formatter formatter + +"kafka.coordinator.group.GroupMetadataManager"kafka.coordinator.group.GroupMetadataManager\\$OffsetsMessageForm$OffsetsMessageFormatter" atter" ----consumer.config config/consumer.propertiesconsumer.config config/consumer.properties ----fromfrom--beginningbeginning +``` + +  同一个消费者组中的消费者, 同一时刻只能有一个消费者消费。 + + + +## 四、Kafka 高效读写数据 + +### 1. 顺序写磁盘 + +  Kafka 的 producer 生产数据,要写入到 log 文件中,写的过程是一直追加到文件末端,为顺序写。官网有数据表明,同样的磁盘,顺序写能到到 600M/s,而随机写只有 100k/s。这与磁盘的机械机构有关,顺序写之所以快,是因为其省去了大量磁头寻址的时间。 + + + +### 2. 零拷贝技术 + +  零拷贝主要的任务就是避免 CPU 将数据从一块存储拷贝到另外一块存储,主要就是利用各种零拷贝技术,避免让 CPU 做大量的数据拷贝任务,减少不必要的拷贝,或者让别的组件来做这一类简单的数据传输任务,让 CPU 解脱出来专注于别的任务。这样就可以让系统资源的利用更加有效。 + +  更详细,请参考:[浅析Linux中的零拷贝技术 - 简书](https://www.jianshu.com/p/fad3339e3448) + +![1567513386020](assets/1567513386020.png) + +![1567513364566](assets/1567513364566.png) + + + +## 五、Zookeeper 在 Kafka 中的作用 + +  Kafka 集群中有一个 broker 会被选举为 Controller,负责管理集群 broker 的上下线,所有 topic 的分区副本分配和 leader 选举等工作。 + +  Controller 的管理工作都是依赖于 Zookeeper 的。 + +  以下为 partition 的 leader 选举过程: + +![](assets/kafka-zk.png) + + + +## 六、Kafka 事务 + +  Kafka 从0.11 版本开始引入了事务支持。事务可以保证 Kafka 在 Exactly Once 语义的基础上,生产和消费可以跨分区和会话,要么全部成功,要么全部失败。 + +  **注意**:这里的事务主要谈的是生产者(Producer)的事务 + + + +### 1. Producer事务事务 + +  为了实现跨分区跨会话的事务,需要引入一个全局唯一的 Transaction ID(**一定是客户端给的**),并将 Producer 获得的 PID 和 Transaction ID 绑定。这样当 Producer 重启后就可以通过正在进行的 Transaction ID 获得原来的 PID。 + +  为了管理 Transaction,Kafka 引入了一个新的组件 Transaction Coordinator。Producer 就是通过和 Transaction Coordinator 交互获得 Transaction ID 对应的任务状态。Transaction Coordinator 还负责将事务所有写入 Kafka 的一个内部 Topic,这样即使整个服务重启,由于事务状态得到保存,进行中的事务状态可以得到恢复,从而继续进行。 + + + +### 2. Consumer **事务** + +  上述事务机制主要是从 Producer 方面考虑,对于 Consumer 而言,事务的保证就会相对较弱,尤其时无法保证 Commit 的信息被精确消费。这是由于 Consumer 可以通过 offset 访问任意信息,而且不同的 Segment File 生命周期不同,同一事务的消息可能会出现重启后被删除的情况。 diff --git a/notes/MicroService/kafka/kafka-tutorial-5_kafka-api.md b/notes/MicroService/kafka/kafka-tutorial-5_kafka-api.md new file mode 100644 index 00000000..67e8c006 --- /dev/null +++ b/notes/MicroService/kafka/kafka-tutorial-5_kafka-api.md @@ -0,0 +1,634 @@ + + +- [深入浅出 Kafka(五)Kafka API](#深入浅出-kafka五kafka-api) + - [一、Producer API](#一producer-api) + - [1. 消息发送流程](#1-消息发送流程) + - [2. 异步发送 API](#2-异步发送-api) + - [(1)不带回调函数的异步(AsyncProducer)](#1不带回调函数的异步asyncproducer) + - [(2)带回调函数的异步(CallbackProducer)](#2带回调函数的异步callbackproducer) + - [3. 同步发送 API](#3-同步发送-api) + - [(1)同步发送(SyncProducer)](#1同步发送syncproducer) + - [二、Consumer API](#二consumer-api) + - [1. 自动提交 offset](#1-自动提交-offset) + - [2. 手动提交 offset](#2-手动提交-offset) + - [(1)同步提交 commitSync offset](#1同步提交-commitsync-offset) + - [(2)异步提交 commitAsync offset](#2异步提交-commitasync-offset) + - [(3)数据漏消费和重复消费分析](#3数据漏消费和重复消费分析) + - [3. 自定义存储 offset](#3-自定义存储-offset) + - [三、自定义 Interceptor](#三自定义-interceptor) + - [1. 拦截器原理](#1-拦截器原理) + - [2. 拦截器案例](#2-拦截器案例) + + + +# 深入浅出 Kafka(五)Kafka API + +## 一、Producer API + +### 1. 消息发送流程 + +  Kafka 的 Producer 发送消息采用的是**异步发送**的方式。 + +  在消息发送的过程中,涉及到了两个线程 —— main 线程和 Sender 线程,以及一个线程共享变量——RecordAccumulator(接收器)。 + +  main 线程将消息发送给 RecordAccumulator,Sender 线程不断从 RecordAccumulator 中拉取消息发送到 Kafka broker。 + +![](assets/kafka-produce.png) + +相关参数: +- **batch.size**:只有数据积累到 batch.size 之后,sender 才会发送数据。 +- **linger.ms**:如果数据迟迟未达到 batch.size,sender 等待 linger.time 之后就会发送数据。 + + + + + + + +### 2. 异步发送 API + +- **KafkaProducer** 需要创建一个生产者对象,用来发送数据 +- **ProducerConfig** 获取所需的一系列配置参数 +- **ProducerRecord** 每条数据都要封装成一个 ProducerRecord 对象 + +```xml + + org.apache.kafka + kafka-clients + 0.11.0.0 + +``` + +#### (1)不带回调函数的异步(AsyncProducer) + +```java +package com.tian.kafka.producer; + +import org.apache.kafka.clients.producer.KafkaProducer; +import org.apache.kafka.clients.producer.ProducerConfig; +import org.apache.kafka.clients.producer.ProducerRecord; +import org.apache.kafka.common.serialization.StringSerializer; + +import java.util.Properties; + +/** + * 不带回调函数的异步 Producer API + */ +public class AsyncProducer { + public static void main(String[] args) { + Properties props = new Properties(); + props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, + "hadoop101:9092,hadoop102:9092,hadoop103:9092"); + props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, + StringSerializer.class.getName()); + props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, + StringSerializer.class.getName()); + props.put(ProducerConfig.ACKS_CONFIG, "all"); + props.put(ProducerConfig.RETRIES_CONFIG, 1); + props.put(ProducerConfig.LINGER_MS_CONFIG, 1); + // 配置拦截器 + + // 通过配置创建 KafkaProducer 对象 + KafkaProducer producer = new KafkaProducer<>(props); + for (int i = 0; i < 1000; i++) { + ProducerRecord record = new ProducerRecord<>("first", "message" + i); + producer.send(record); + } + producer.close(); + } +} +``` + +#### (2)带回调函数的异步(CallbackProducer) + +```java +package com.tian.kafka.producer; + +import org.apache.kafka.clients.producer.*; +import org.apache.kafka.common.serialization.StringSerializer; + +import java.util.Properties; + +/** + * 带回调函数的异步Producer API + */ +public class CallbackProducer { + public static void main(String[] args) { + Properties props = new Properties(); + props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, + "192.168.72.133:9092"); + props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, + StringSerializer.class.getName()); + props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, + StringSerializer.class.getName()); + props.put(ProducerConfig.ACKS_CONFIG, "all"); + props.put(ProducerConfig.RETRIES_CONFIG, 1); + KafkaProducer producer = new KafkaProducer<>(props); + for (int i = 0; i < 1000; i++) { + ProducerRecord record = new ProducerRecord<>("first", "message" + i); + producer.send(record, new Callback() { + @Override + public void onCompletion(RecordMetadata recordMetadata, Exception e) { + if (e == null) + System.out.println("success:" + recordMetadata.topic() + + "-" + recordMetadata.partition() + + "-" + recordMetadata.offset()); + else e.printStackTrace(); + } + }); + + } + producer.close(); + } +} +``` + + + +### 3. 同步发送 API + +#### (1)同步发送(SyncProducer) + +```java +package com.tian.kafka.producer; + +import org.apache.kafka.clients.producer.KafkaProducer; +import org.apache.kafka.clients.producer.ProducerConfig; +import org.apache.kafka.clients.producer.ProducerRecord; +import org.apache.kafka.clients.producer.RecordMetadata; +import org.apache.kafka.common.serialization.StringSerializer; + +import java.util.Properties; +import java.util.concurrent.ExecutionException; + +/** + * 同步 Producer API + */ +public class SyncProducer { + public static void main(String[] args) throws ExecutionException, InterruptedException { + // 创建 properties 对象用于存放配置 + Properties props = new Properties(); + // 添加配置 + props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop101:9092,hadoop102:9092,hadoop103:9092"); + props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName()); + props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName()); + props.put(ProducerConfig.ACKS_CONFIG, "all"); + props.put(ProducerConfig.RETRIES_CONFIG, 1); // 重试次数 + props.put(ProducerConfig.LINGER_MS_CONFIG, 500); + // 通过已有配置创建 kafkaProducer 对象 + KafkaProducer producer = new KafkaProducer<>(props); + // 循环调用 send 方法不断发送数据 + for (int i = 0; i < 100; i++) { + ProducerRecord record = new ProducerRecord<>("first", "message" + i); + RecordMetadata metadata = producer.send(record).get();// 通过 get()方法实现同步效果 + if (metadata != null) + System.out.println("success:" + metadata.topic() + "-" + + metadata.partition() + "-" + metadata.offset()); + } + producer.close(); // 关闭生产者对象 + } +} +``` + + + +## 二、Consumer API + +  Consumer 消费数据时的可靠性是很容易保证的,因为数据在 Kafka 中是持久化的,故不用担心数据丢失问题。 +  由于 consumer 在消费过程中可能会出现断电宕机等故障,consumer 恢复后,需要从故障前的位置的继续消费,所以 consumer 需要实时记录自己消费到了哪个 offset,以便故障恢复后继续消费。 +  所以 offset 的维护是 Consumer 消费数据是必须考虑的问题。 + +### 1. 自动提交 offset + +```xml + + org.apache.kafka + kafka-clients + 0.11.0.0 + +``` + +- **KafkaConsumer**:需要创建一个消费者对象,用来消费数据 +- **ConsumerConfig**:获取所需的一系列配置参数 +- **ConsuemrRecord**:每条数据都要封装成一个 ConsumerRecord 对象 + +为了使我们能够专注于自己的业务逻辑,Kafka 提供了自动提交 offset 的功能。 + +自动提交 offset 的相关参数: + +- **enable.auto.commit**:是否开启自动提交 offset 功能 +- **auto.commit.interval.ms**:自动提交 offset 的时间间隔 + +```java +package com.tian.kafka.consumer; + +import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.clients.consumer.ConsumerRecords; +import org.apache.kafka.clients.consumer.KafkaConsumer; +import org.apache.kafka.common.serialization.StringDeserializer; + +import java.util.Arrays; +import java.util.Properties; + +/** + * 自动提交 offset + */ +public class AutoCommitOffset { + public static void main(String[] args) { + Properties props = new Properties(); + props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, + "hadoop101:9092,hadoop102:9092"); + props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, + StringDeserializer.class.getName()); + props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, + StringDeserializer.class.getName()); + props.put(ConsumerConfig.GROUP_ID_CONFIG,"tian"); // groupid + props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,"earliest"); + props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,true); // 自动提交 + KafkaConsumer consumer = new KafkaConsumer<>(props); + consumer.subscribe(Arrays.asList("first")); // 添加需要消费的 topic + try { + while(true){ + ConsumerRecords records = consumer.poll(100); + for (ConsumerRecord record : records) { + System.out.println(record.value()); + } + } + } finally { + consumer.close();// 在死循环中无法调用 close 方法,所以需要使用 finally + } + } +} +``` + + + +### 2. 手动提交 offset + +  虽然自动提交 offset 十分简介便利,但由于其是基于时间提交的,开发人员难以把握 offset 提交的时机。因此 Kafka 还提供了手动提交 offset 的 API。 +  手动提交 offset 的方法有两种: 分别是 **commitSync**(**同步提交**)和 **commitAsync**(**异步提交**)。两者的相同点是,都会将本次 poll 的一批数据最高的偏移量提交; 不同点是,commitSync 阻塞当前线程,一直到提交成功,并且会自动失败充实(由不可控因素导致,也会出现提交失败); 而 commitAsync 则没有失败重试机制,故有可能提交失败。 + +#### (1)同步提交 commitSync offset + +  由于同步提交 offset 有失败重试机制,故更加可靠,以下为同步提交 offset 的示例 + +```java +package com.tian.kafka.consumer; + +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.apache.kafka.clients.consumer.ConsumerRecords; +import org.apache.kafka.clients.consumer.KafkaConsumer; + +import java.util.Arrays; +import java.util.Properties; + +/** + * 同步手动提交 offset + */ +public class CustomComsumer { + + public static void main(String[] args) { + + Properties props = new Properties(); + props.put("bootstrap.servers", "hadoop102:9092");//Kafka 集群 + props.put("group.id", "test");// 消费者组,只要 group.id 相同,就属于同一个消费者组 + props.put("enable.auto.commit", "false");// 关闭自动提交 offset + props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); + props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); + + KafkaConsumer consumer = new KafkaConsumer<>(props); + consumer.subscribe(Arrays.asList("first"));// 消费者订阅主题 + + while (true) { + ConsumerRecords records = consumer.poll(100);// 消费者拉取数据 + for (ConsumerRecord record : records) { + System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value()); + } + consumer.commitSync();// 同步提交,当前线程会阻塞直到 offset 提交成功 + } + } +} +``` + + + +#### (2)异步提交 commitAsync offset + +  虽然同步提交 offset 更可靠一些,但是由于其会阻塞当前线程,直到提交成功。因此吞吐量会收到很大的影响,因此更多的情况下,会选用异步提交 offset 的方式。 + +```java +package com.tian.kafka.consumer; + +import org.apache.kafka.clients.consumer.*; +import org.apache.kafka.common.TopicPartition; +import org.apache.kafka.common.serialization.StringDeserializer; + +import java.util.Arrays; +import java.util.Map; +import java.util.Properties; + +/** + * 异步手动提交 offset + */ +public class AsyncManualCommitOffset { + public static void main(String[] args) { + Properties props = new Properties(); + props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop101:9092"); + props.put(ConsumerConfig.GROUP_ID_CONFIG, "tian"); + props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false); + props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, + StringDeserializer.class.getName()); + props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, + StringDeserializer.class.getName()); + KafkaConsumer consumer = new KafkaConsumer(props); + consumer.subscribe(Arrays.asList("first")); + while (true) { + ConsumerRecords records = consumer.poll(100); + for (ConsumerRecord record : records) { + System.out.println("offset:" + record.offset() + + "key:" + record.key() + "value" + record.value()); + } + consumer.commitAsync(new OffsetCommitCallback() { + public void onComplete(Map map, Exception e) { + if (e != null) + System.out.println("commit failed for" + map); + } + });// 异步提交 + } + } +} +``` + + + +#### (3)数据漏消费和重复消费分析 + +  无论是同步提交还是异步提交 offset,都有可能会造成数据的漏消费或者重复消费。先提交 offset 后消费,有可能造成数据的漏消费;而先消费后提交 offset,有可能会造成数据的重复消费。 + +![](assets/kafka-offset.png) + + + +### 3. 自定义存储 offset + +  Kafka 0.9 版本之前,offset 存储在 zookeeper,0.9 版本之后,默认将 offset 存储在 Kafka 的一个内置的 topic 中。除此之外,Kafka 还可以选择自定义存储 offset。 +  offset 的维护是相当繁琐的,因为需要考虑到消费者的 Rebalance。 +  当有新的消费者加入消费者组、已有的消费者推出消费者组或者所订阅的主题的分区发生变化,就会触发到分区的重新分配,重新分配的过程叫做 Rebalance。 +  消费者发生 Rebalance 之后,每个消费者消费的分区就会发生变化。因此消费者要首先获取到自己被重新分配到的分区,并且定位到每个分区最近提交的 offset 位置继续消费。 +  要实现自定义存储 offset,需要借助 ConsumerRebalanceListener,以下为示例代码,其中提交和获取 offset 的方法,需要根据所选的 offset 存储系统自行实现。 + +```java +package com.tian.kafka.consumer; + +import org.apache.kafka.clients.consumer.*; +import org.apache.kafka.common.TopicPartition; + +import java.util.*; + +/** + * 自定义存储 offset + */ +public class CustomConsumer { + + private static Map currentOffset = new HashMap<>(); + + public static void main(String[] args) { + Properties props = new Properties(); + props.put("bootstrap.servers", "hadoop102:9092");//Kafka 集群 + props.put("group.id", "test");// 消费者组,只要 group.id 相同,就属于同一个消费者组 + props.put("enable.auto.commit", "false");// 关闭自动提交 offset + props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); + props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); + + KafkaConsumer consumer = new KafkaConsumer<>(props); + // 消费者订阅主题 + consumer.subscribe(Arrays.asList("first"), new ConsumerRebalanceListener() { + + // 该方法会在 Rebalance 之前调用 + @Override + public void onPartitionsRevoked(Collection partitions) { + commitOffset(currentOffset); + } + + // 该方法会在 Rebalance 之后调用 + @Override + public void onPartitionsAssigned(Collection partitions) { + currentOffset.clear(); + for (TopicPartition partition : partitions) { + consumer.seek(partition, getOffset(partition));// 定位到最近提交的 offset 位置继续消费 + } + } + }); + + while (true) { + ConsumerRecords records = consumer.poll(100);// 消费者拉取数据 + for (ConsumerRecord record : records) { + System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value()); + currentOffset.put(new TopicPartition(record.topic(), record.partition()), record.offset()); + } + commitOffset(currentOffset); + } + } + + // 获取某分区的最新 offset + private static long getOffset(TopicPartition partition) { + return 0; + } + + // 提交该消费者所有分区的 offset + private static void commitOffset(Map currentOffset) { + + } +} +``` + + + +## 三、自定义 Interceptor + +### 1. 拦截器原理 + +  Producer 拦截器 (interceptor) 是在 Kafka 0.10 版本被引入的,主要用于实现 clients 端的定制化控制逻辑。 +对于 producer 而言,interceptor 使得用户在消息发送前以及 producer 回调逻辑前有机会对消息做一些定制化需求,比如修改消息等。同时,producer 允许用户指定多个 interceptor 按序作用于同一条消息从而形成一个拦截链(interceptor chain)。Intercetpor 的实现接口是 `org.apache.kafka.clients.producer.ProducerInterceptor`, + +其定义的方法包括: + +1. configure (configs) + 获取配置信息和初始化数据时调用。 +2. onSend (ProducerRecord): + 该方法封装进 KafkaProducer.send 方法中,即它运行在用户主线程中。Producer 确保在消息被序列化以及计算分区前调用该方法。用户可以在该方法中对消息做任何操作,但最好保证不要修改消息所属的 topic 和分区,否则会影响目标分区的计算。 +3. onAcknowledgement (RecordMetadata, Exception): + 该方法会在消息从 RecordAccumulator 成功发送到 Kafka Broker 之后,或者在发送过程中失败时调用。并且通常都是在 producer 回调逻辑触发之前。onAcknowledgement 运行在 producer 的 IO 线程中,因此不要在该方法中放入很重的逻辑,否则会拖慢 producer 的消息发送效率。 +4. close: + 关闭 interceptor,主要用于执行一些资源清理工作 + 如前所述,interceptor 可能被运行在多个线程中,因此在具体实现时用户需要自行确保线程安全。另外倘若指定了多个 interceptor,则 producer 将按照指定顺序调用它们,并仅仅是捕获每个 interceptor 可能抛出的异常记录到错误日志中而非在向上传递。这在使用过程中要特别留意。 + + + +### 2. 拦截器案例 + +- **需求 ** + 实现一个简单的双 interceptor 组成的拦截链。第一个 interceptor 会在消息发送前将时间戳信息加到消息 value 的最前部;第二个 interceptor 会在消息发送后更新成功发送消息数或失败发送消息数。 + +![](assets/kafka-interceptor.png) + +```java +package com.tian.kafka.interceptor; + +import org.apache.kafka.clients.producer.ProducerInterceptor; +import org.apache.kafka.clients.producer.ProducerRecord; +import org.apache.kafka.clients.producer.RecordMetadata; + +import java.util.Map; + +/** + * 增加时间戳 + */ +public class TimeInterceptor implements ProducerInterceptor { + /** + * 返回一个新的 recorder,把时间戳写入消息体的最前部 + * @param record + * @return + */ + @Override + public ProducerRecord onSend(ProducerRecord record) { + return new ProducerRecord(record.topic(),record.partition(),record.timestamp(), + record.key(),System.currentTimeMillis() + "," + record.value().toString()); + } + + @Override + public void onAcknowledgement(RecordMetadata recordMetadata, Exception e) { + + } + + @Override + public void close() { + + } + + @Override + public void configure(Map map) { + + } +} +``` + +```java +package com.tian.kafka.interceptor; + +import org.apache.kafka.clients.producer.ProducerInterceptor; +import org.apache.kafka.clients.producer.ProducerRecord; +import org.apache.kafka.clients.producer.RecordMetadata; + +import java.util.Map; + +/** + * 统计发送消息成功和发送消息失败数, + * 并在 producer 关闭时打印这连个计时器 + */ +public class CountInterceptor implements ProducerInterceptor { + private int errorCounter = 0; + private int successCounter = 0; + + /** + * 直接返回传入的参量 + * @param producerRecord + * @return producerRecord + */ + @Override + public ProducerRecord onSend(ProducerRecord producerRecord) { + return producerRecord; + } + + /** + * 统计成功和失败的次数 + * @param recordMetadata + * @param e + */ + @Override + public void onAcknowledgement(RecordMetadata recordMetadata, Exception e) { + if(e == null) + successCounter++; + else + errorCounter++; + } + + /** + * 打印结果 + */ + @Override + public void close() { + System.out.println("Successful sent:" + successCounter); + System.out.println("Failed sent:" + errorCounter); + } + + @Override + public void configure(Map map) { + + } +} +``` + +```java +package com.tian.kafka.interceptor; + +import org.apache.kafka.clients.producer.KafkaProducer; +import org.apache.kafka.clients.producer.ProducerConfig; +import org.apache.kafka.clients.producer.ProducerRecord; +import org.codehaus.jackson.map.ser.std.StringSerializer; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +/** + * 主程序 + */ +public class InterceptorProducer { + public static void main(String[] args) { + // 配置信息 + Properties props = new Properties(); + props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop101:9092"); + props.put(ProducerConfig.ACKS_CONFIG,"all"); + props.put(ProducerConfig.RETRIES_CONFIG,0); + props.put(ProducerConfig.BATCH_SIZE_CONFIG,16384); + props.put(ProducerConfig.LINGER_MS_CONFIG,1); + props.put(ProducerConfig.BUFFER_MEMORY_CONFIG,33664432); + props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, + StringSerializer.class.getName()); + props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, + StringSerializer.class.getName()); + // 构建拦截链 + List interceptors = new ArrayList(); + interceptors.add("com.tian.kafka.interceptor.TimeInterceptor"); + interceptors.add("com.tian.kafka.interceptor.CountInterceptor"); + props.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, interceptors); + String topic = "first"; + KafkaProducer producer = new KafkaProducer<>(props); + // 发送消息 + for (int i = 0; i < 100; i++) { + ProducerRecord record = new ProducerRecord<>(topic, "message" + 1); + producer.send(record); + } + // 关闭 producer + producer.close(); + } +} +``` + +```bash +# 测试 +# 在 kafka 上启动消费者,然后运行客户端 java 程序。 +kafka-console-consumer.sh --bootstrap-server hadoop102:9092 --from-beginning --topic first +# 1501904047034,message0 +# 1501904047225,message1 +# 1501904047230,message2 +# 1501904047234,message3 +# 1501904047236,message4 +# 1501904047240,message5 +# 1501904047243,message6 +# 1501904047246,message7 +# 1501904047249,message8 +# 1501904047252,message9 +``` diff --git a/notes/MicroService/kafka/kafka-tutorial-6_spring-kafka-api.md b/notes/MicroService/kafka/kafka-tutorial-6_spring-kafka-api.md new file mode 100644 index 00000000..8c595af4 --- /dev/null +++ b/notes/MicroService/kafka/kafka-tutorial-6_spring-kafka-api.md @@ -0,0 +1,282 @@ + + +- [深入浅出 Kafka(六)Spring Kafka API](#深入浅出-kafka六spring-kafka-api) + - [一、Producer](#一producer) + - [1. KafkaProducer](#1-kafkaproducer) + - [2. KafkaProducerConfig](#2-kafkaproducerconfig) + - [二、Consumer](#二consumer) + - [3. KafkaConsumer](#3-kafkaconsumer) + - [4. KafkaConsumerConfig](#4-kafkaconsumerconfig) + - [三、配置文件](#三配置文件) + - [四、pom 依赖](#四pom-依赖) + - [参考资料](#参考资料) + + + +# 深入浅出 Kafka(六)Spring Kafka API + +  在上一节中学习了如何通过 Kafka API 的方式进行生产者和消费者及其配置,但是主要是通过手动编写 Java 代码的方式实现。在项目开发中,我们主要会使用到 SpringBoot 框架,这里就将带你 SpringBoot 与 Kafka 整合,通过注解和配置的方式轻松集成。 + +  这里将列举,最常见的生产者和消费者使用方式,更完整的 API 文档,请转向 [Overview (Spring Kafka 2.2.8.RELEASE API)](https://docs.spring.io/spring-kafka/api/) 学习。 + +  送上本节 DEMO 项目代码,[点击预览](https://github.com/frank-lam/open-code-lab/tree/master/springboot/springboot-kafka-sample)。 + + + +## 一、Producer + +### 1. KafkaProducer + +```java +package cn.frankfeekr.springbootkafkasample.client; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + + +@RestController +public class KafkaProducer { + + private static final Logger LOG = LoggerFactory.getLogger(KafkaProducer.class); + + @Autowired + private KafkaTemplate kafkaTemplate; + + @Value("${app.topic.foo}") + private String topic; + + + @GetMapping("demo") + public String send(@RequestParam String msg){ +// LOG.info("sending message='{}' to topic='{}'", message, topic); + kafkaTemplate.send(topic,"key", msg); + return "send success"; + } +} +``` + +### 2. KafkaProducerConfig + +```java +package cn.frankfeekr.springbootkafkasample.client; + +import org.apache.kafka.clients.producer.ProducerConfig; +import org.apache.kafka.common.serialization.StringSerializer; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.kafka.core.DefaultKafkaProducerFactory; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.kafka.core.ProducerFactory; + +import java.util.HashMap; +import java.util.Map; + +@Configuration +public class KafkaSenderConfig { + + @Value("${spring.kafka.bootstrap-servers}") + private String bootstrapServers; + + @Bean + public Map producerConfigs() { + Map props = new HashMap<>(); + props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); + props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + return props; + } + + @Bean + public ProducerFactory producerFactory() { + return new DefaultKafkaProducerFactory<>(producerConfigs()); + } + + @Bean + public KafkaTemplate kafkaTemplate() { + return new KafkaTemplate<>(producerFactory()); + } + +} +``` + + + +## 二、Consumer + +### 3. KafkaConsumer + +```java +package cn.frankfeekr.springbootkafkasample.client; + +import org.apache.kafka.clients.consumer.Consumer; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.kafka.support.Acknowledgment; +import org.springframework.stereotype.Service; + +@Service +public class KafkaConsumer { + + private static final Logger LOG = LoggerFactory.getLogger(KafkaConsumer.class); + + @KafkaListener(topics = "${app.topic.foo}") + public void listen(ConsumerRecord record, Acknowledgment ack, Consumer consumer) { + LOG.warn("topic:{},key: {},partition:{}, value: {}, record: {}",record.topic(), record.key(),record.partition(), record.value(), record); + if (record.topic().equalsIgnoreCase("test")){ + throw new RuntimeException(); + } + System.out.println("提交 offset "); + consumer.commitAsync(); + } +} +``` + + + +### 4. KafkaConsumerConfig + +```java +package cn.frankfeekr.springbootkafkasample.client; + +import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.apache.kafka.common.serialization.StringDeserializer; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.kafka.annotation.EnableKafka; +import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory; +import org.springframework.kafka.config.KafkaListenerContainerFactory; +import org.springframework.kafka.core.ConsumerFactory; +import org.springframework.kafka.core.DefaultKafkaConsumerFactory; +import org.springframework.kafka.listener.ConcurrentMessageListenerContainer; +import org.springframework.kafka.listener.ContainerProperties; + +import java.util.HashMap; +import java.util.Map; + +@EnableKafka +@Configuration +public class KafkaConsumerConfig { + + @Value("${spring.kafka.bootstrap-servers}") + private String bootstrapServers; + + @Bean + public Map consumerConfigs() { + Map props = new HashMap<>(); + props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); + props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); + props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); + props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false); + props.put(ConsumerConfig.GROUP_ID_CONFIG, "foo"); + props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest"); + + return props; + } + + + @Bean + public ConsumerFactory consumerFactory() { + return new DefaultKafkaConsumerFactory<>(consumerConfigs()); + } + + @Bean + public KafkaListenerContainerFactory> kafkaListenerContainerFactory() { + ConcurrentKafkaListenerContainerFactory factory = new ConcurrentKafkaListenerContainerFactory<>(); + factory.setConcurrency(3); + factory.getContainerProperties().setAckMode(ContainerProperties.AckMode.MANUAL); + factory.setConsumerFactory(consumerFactory()); + return factory; + } + +} +``` + + + +## 三、配置文件 + +```yaml +server: + port: 9090 +spring: + kafka: + bootstrap-servers: 127.0.0.1:9092 +app: + topic: + foo: frankfeekr +``` + + + +## 四、pom 依赖 + +```xml + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.1.7.RELEASE + + + cn.frankfeekr + springboot-kafka-sample + 0.0.1-SNAPSHOT + springboot-kafka-sample + Demo project for Spring Boot + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.kafka + spring-kafka + 2.2.7.RELEASE + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + +``` + + + +## 参考资料 + +- [Spring for Apache Kafka](https://docs.spring.io/spring-kafka/reference/html) +- [Overview (Spring Kafka 2.2.8.RELEASE API)](https://docs.spring.io/spring-kafka/docs/2.2.8.RELEASE/api/) +- [SpringBoot整合Kafka实现发布订阅 - 简书](https://www.jianshu.com/p/7a284bf4efc9) +- [SpringBoot 集成 Spring For Kafka 操作 Kafka 详解 · 小豆丁个人博客](http://www.mydlq.club/article/34/) diff --git "a/notes/MicroService/kafka/kafka-tutorial-7_\347\233\221\346\216\247.md" "b/notes/MicroService/kafka/kafka-tutorial-7_\347\233\221\346\216\247.md" new file mode 100644 index 00000000..be058c22 --- /dev/null +++ "b/notes/MicroService/kafka/kafka-tutorial-7_\347\233\221\346\216\247.md" @@ -0,0 +1,4 @@ +# 深入浅出 Kafka(七)监控 + +Kafka Monitor,Kafka Manager + diff --git a/notes/PostgreSQL.md b/notes/PostgreSQL.md new file mode 100644 index 00000000..f7223f31 --- /dev/null +++ b/notes/PostgreSQL.md @@ -0,0 +1,598 @@ + + +- [PostgreSQL](#postgresql) + - [1. 什么是PostgreSql?](#1-什么是postgresql) + - [知识点](#知识点) + - [数据库排名](#数据库排名) + - [官方网站](#官方网站) + - [技术准备](#技术准备) + - [使用环境](#使用环境) + - [安装](#安装) + - [2. 初来乍到数据库](#2-初来乍到数据库) + - [知识点](#知识点-1) + - [实战演习](#实战演习) + - [3. 操作数据表](#3-操作数据表) + - [知识点](#知识点-2) + - [实战演习](#实战演习-1) + - [4. 字段类型](#4-字段类型) + - [PostgreSql的基础数据类型](#postgresql的基础数据类型) + - [5. 添加表约束](#5-添加表约束) + - [知识点](#知识点-3) + - [实战演习](#实战演习-2) + - [db.sql](#dbsql) + - [6. INSERT语句](#6-insert语句) + - [知识点](#知识点-4) + - [实战演习](#实战演习-3) + - [SQL部分](#sql部分) + - [7. SELECT语句](#7-select语句) + - [知识点](#知识点-5) + - [实战演习](#实战演习-4) + - [init.sql](#initsql) + - [SQL实战](#sql实战) + - [8. WHERE语句](#8-where语句) + - [知识点](#知识点-6) + - [实战演习](#实战演习-5) + - [9. 数据抽出选项](#9-数据抽出选项) + - [知识点](#知识点-7) + - [实战演习](#实战演习-6) + - [10. 统计抽出数据](#10-统计抽出数据) + - [知识点](#知识点-8) + - [实战演习](#实战演习-7) + - [11. 方便的函数](#11-方便的函数) + - [知识点](#知识点-9) + - [实战演习](#实战演习-8) + - [12. 更新和删除](#12-更新和删除) + - [知识点](#知识点-10) + - [实战演习](#实战演习-9) + - [13. 变更表结构](#13-变更表结构) + - [知识点](#知识点-11) + - [实战演习](#实战演习-10) + - [14. 操作多个表](#14-操作多个表) + - [知识点](#知识点-12) + - [实战演习](#实战演习-11) + - [renew.sql](#renewsql) + - [SQL实行](#sql实行) + - [15. 使用视图](#15-使用视图) + - [视图概念](#视图概念) + - [简单解释](#简单解释) + - [知识点](#知识点-13) + - [实战演习](#实战演习-12) + - [实战建议](#实战建议) + - [16. 使用事务](#16-使用事务) + - [知识点](#知识点-14) + - [实战演习](#实战演习-13) + - [参考资料](#参考资料) + + + +# PostgreSQL + +> 本节参考:[PostgreSql入门](http://komavideo.com/postgresql/index.html),特别感谢小马视频的学习课程,深入浅出了 PostgreSQL。在此基础上针对自己的学习过程,修改了部分内容。 + + + +本文为 PostgreSQL 入门指南,通过极简的语言,带你走进 PostgreSQL 世界的大门。 + +## 1. 什么是PostgreSql? + +PostgreSQL 是一个自由的对象-关系数据库服务器(数据库管理系统),是从伯克利写的 POSTGRES 软件包发展而来的。经过十几年的发展, PostgreSQL 是世界上可以获得的最先进的开放源码的数据库系统, 它提供了多版本并发控制,支持几乎所有SQL语句(包括子查询,事务和用户定义类型和函数),并且可以获得非常广阔范围的(开发)语言绑定 (包括C,C++,Java,perl,python,php,nodejs,ruby)。 + +### 知识点 + +* 面向关系的数据库 + + Oracle + + MySql + + SQLServer + + PostgreSql +* NoSql + + MongoDB + + Redis + +### 数据库排名 + +https://db-engines.com/en/ranking + +### 官方网站 + +https://www.postgresql.org/ + +### 技术准备 + +* SQL语言基础 + +### 使用环境 + +* Ubuntu Server 16 LTS +* PostgreSql 9.5.x + +### 安装 + +~~~bash +$ sudo apt-get install postgresql +$ psql --version +~~~ + + + +## 2. 初来乍到数据库 + +### 知识点 + +* psql的基础 +* 数据库简单操作 +* 写个SQL + +### 实战演习 + +~~~bash +$ sudo su postgres +$ psql --version +$ psql -l +$ createdb komablog +$ psql -l +$ psql komablog +> help +> \h +> \? +> \l +> \q +$ psql komablog +> select now(); +> select version(); +> \q +$ dropdb komablog +$ psql -l +~~~ + + + +## 3. 操作数据表 + +### 知识点 + +* create table +* drop table +* psql使用 + +### 实战演习 + +~~~bash +$ sudo su postgres +$ createdb komablog +$ psql -l +$ psql komablog +> create table posts (title varchar(255), content text); +> \dt +> \d posts +> alter table posts rename to komaposts; +> \dt +> drop table komaposts; +> \dt +> \q +$ nano db.sql +... +create table posts (title varchar(255), content text); +... +$ psql komablog +> \i db.sql +> \dt +~~~ + + + +## 4. 字段类型 + + ### 知识点 + +* PostgreSql的基础数据类型 + +### PostgreSql的基础数据类型 + +* 数值型: + + integer(int) + + real + + serial +* 文字型: + + char + + varchar + + text +* 布尔型: + + boolean +* 日期型: + + date + + time + + timestamp +* 特色类型: + + Array + + 网络地址型(inet) + + JSON型 + + XML型 + +参考网站: + +https://www.postgresql.org/docs/9.5/static/datatype.html + + + +## 5. 添加表约束 + +### 知识点 + +- 表子段的约束条件 + +### 实战演习 + +#### db.sql + +```sql +create table posts ( + id serial primary key, + title varchar(255) not null, + content text check(length(content) > 8), + is_draft boolean default TRUE, + is_del boolean default FALSE, + created_date timestamp default 'now' +); + +-- 说明 +/* +约束条件: + +not null:不能为空 +unique:在所有数据中值必须唯一 +check:字段设置条件 +default:字段默认值 +primary key(not null, unique):主键,不能为空,且不能重复 +*/ +``` + + + +## 6. INSERT语句 + +### 知识点 + +- insert into [tablename] (field, ...) values (value, ...) + +### 实战演习 + +```sql +$ psql komablog +> \dt +> \d posts +``` + +#### SQL部分 + +```sql +> insert into posts (title, content) values ('', ''); +> insert into posts (title, content) values (NULL, ''); +> insert into posts (title, content) values ('title1', 'content11'); +> select * from posts; +> insert into posts (title, content) values ('title2', 'content22'); +> insert into posts (title, content) values ('title3', 'content33'); +> select * from posts; +``` + + + +## 7. SELECT语句 + +### 知识点 + +- select 基本使用 + +### 实战演习 + +#### init.sql + +```sql +create table users ( + id serial primary key, + player varchar(255) not null, + score real, + team varchar(255) +); + +insert into users (player, score, team) values +('库里', 28.3, '勇士'), +('哈登', 30.2, '火箭'), +('阿杜', 25.6, '勇士'), +('阿詹', 27.8, '骑士'), +('神龟', 31.3, '雷霆'), +('白边', 19.8, '热火'); +``` + +#### SQL实战 + +```bash +$ psql komablog +> \i init.sql +> \dt +> \d users +> select * from users; +> \x +> select * from users; +> \x +> select * from users; +> select player, score from users; +``` + + + +## 8. WHERE语句 + +### 知识点 + +- where语句的使用 + +使用where语句来设定select,update,delete语句数据抽出的条件。 + +### 实战演习 + +```sql +> select * from users; +> select * from users where score > 20; +> select * from users where score < 30; +> select * from users where score > 20 and score < 30; +> select * from users where team = '勇士'; +> select * from users where team != '勇士'; +> select * from users where player like '阿%'; +> select * from users where player like '阿_'; +``` + + + +## 9. 数据抽出选项 + +### 知识点 + +select语句在抽出数据时,可以对语句设置更多的选项,已得到想要的数据。 + +- order by +- limit +- offset + +### 实战演习 + +```sql +> select * from users order by score asc; +> select * from users order by score desc; +> select * from users order by team; +> select * from users order by team, score; +> select * from users order by team, score desc; +> select * from users order by team desc, score desc; +> select * from users order by score desc limit 3; +> select * from users order by score desc limit 3 offset 1; +> select * from users order by score desc limit 3 offset 2; +> select * from users order by score desc limit 3 offset 3; +``` + + + +## 10. 统计抽出数据 + +### 知识点 + +- distinct +- sum +- max/min +- group by/having + +### 实战演习 + +```sql +> select distinct team from users; +> select sum(score) from users; +> select max(score) from users; +> select min(score) from users; +> select * from users where score = (select max(score) from users); +> select * from users where score = (select min(score) from users); +> select team, max(score) from users group by team; +> select team, max(score) from users group by team having max(score) >= 25; +> select team, max(score) from users group by team having max(score) >= 25 order by max(score); +``` + + + +## 11. 方便的函数 + +### 知识点 + +- length +- concat +- alias +- substring +- random + +参考网站: + +https://www.postgresql.org/docs/9.5/static/functions.html + +### 实战演习 + +```sql +> select player, length(player) from users; +> select player, concat(player, '/', team) from users; +> select player, concat(player, '/', team) as "球员信息" from users; +> select substring(team, 1, 1) as "球队首文字" from users; +> select concat('我', substring(team, 1, 1)) as "球队首文字" from users; +> select random(); +> select * from users order by random(); +> select * from users order by random() limit 1; +``` + + + +## 12. 更新和删除 + +### 知识点 + +- update [table] set [field=newvalue,...] where ... +- delete from [table] where ... + +### 实战演习 + +```sql +> update users set score = 29.1 where player = '阿詹'; +> update users set score = score + 1 where team = '勇士'; +> update users set score = score + 100 where team IN ('勇士', '骑士'); +> delete from users where score > 30; +``` + + + +## 13. 变更表结构 + +### 知识点 + +- alter table [tablename] ... +- create index ... +- drop index ... + +### 实战演习 + +```sql +> \d users; +> alter table users add fullname varchar(255); +> \d users; +> alter table users drop fullname; +> \d users; +> alter table users rename player to nba_player; +> \d users; +> alter table users alter nba_player type varchar(100); +> \d users; +> create index nba_player_index on users(nba_player); +> \d users; +> drop index nba_player_index; +> \d users; +``` + + + +## 14. 操作多个表 + +### 知识点 + +- 表结合查询的基础知识 + +### 实战演习 + +#### renew.sql + +```sql +create table users ( + id serial primary key, + player varchar(255) not null, + score real, + team varchar(255) +); +insert into users (player, score, team) values +('库里', 28.3, '勇士'), +('哈登', 30.2, '火箭'), +('阿杜', 25.6, '勇士'), +('阿詹', 27.8, '骑士'), +('神龟', 31.3, '雷霆'), +('白边', 19.8, '热火'); + +create table twitters ( + id serial primary key, + user_id integer, + content varchar(255) not null +); +insert into twitters (user_id, content) values +(1, '今天又是大胜,克莱打的真好!'), +(2, '今晚我得了60分,哈哈!'), +(3, '获胜咱不怕,缺谁谁尴尬.'), +(4, '明年我也可能转会西部'), +(5, '我都双20+了,怎么球队就是不胜呢?'), +(1, '明年听说有条大鱼要来,谁呀?'); +``` + +#### SQL实行 + +```sql +$ dropdb komablog; +$ createdb komablog; +$ psql komablog; +> \i renew.sql +> select * from users; +> select * from twitters; +> select users.player, twitters.content from users, twitters where users.id = twitters.user_id; +> select u.player, t.content from users as u, twitters as t where u.id = t.user_id; +> select u.player, t.content from users as u, twitters as t where u.id = t.user_id and u.id = 1; +``` + + + +## 15. 使用视图 + +### 视图概念 + +视图(View)是从一个或多个表导出的对象。视图与表不同,视图是一个虚表,即视图所对应的数据不进行实际存储,数据库中只存储视图的定义,在对视图的数据进行操作时,系统根据视图的定义去操作与视图相关联的基本表。 + +### 简单解释 + +视图就是一个SELECT语句,把业务系统中常用的SELECT语句简化成一个类似于表的对象,便于简单读取和开发。 + +### 知识点 + +- 使用数据库视图(view) + - create view ... + - drop view ... + +### 实战演习 + +```sql +> select u.player, t.content from users as u, twitters as t where u.id = t.user_id and u.id = 1; +> create view curry_twitters as select u.player, t.content from users as u, twitters as t where u.id = t.user_id and u.id = 1; +> \dv +> \d curry_twitters +> select * from curry_twitters; +> drop view curry_twitters; +> \dv +``` + +### 实战建议 + +在自己项目中,为了提高数据查询速度,可在表中加入索引index。同时对于经常需要查询的语句,可以提前建立视图view,方便于编码和管理。 + + + +## 16. 使用事务 + +数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。事务是数据库运行中的逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理。 + +### 知识点 + +- PostgreSql数据库事务使用 + - begin + - commit + - rollback + +### 实战演习 + +```sql +> select * from users; +> begin; +> update users set score = 50 where player = '库里'; +> update users set score = 60 where player = '哈登'; +> commit; +> select * from users; +> begin; +> update users set score = 0 where player = '库里'; +> update users set score = 0 where player = '哈登'; +> rollback; +> select * from users; +``` + + + + + +## 参考资料 + +- [【小马技术】PostgreSql 关系型数据库入门_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili](https://www.bilibili.com/video/av24590479?from=search&seid=9519508000355338670) + + + diff --git "a/notes/Python/Python\345\256\211\350\243\205.md" "b/notes/Python/Python\345\256\211\350\243\205.md" index 8f344f09..36a6c156 100755 --- "a/notes/Python/Python\345\256\211\350\243\205.md" +++ "b/notes/Python/Python\345\256\211\350\243\205.md" @@ -1,8 +1,8 @@ -#### Windows安装 +#### Windows 安装 --- -> [Python官网](https://www.python.org/) +> [Python 官网](https://www.python.org/) - 打开官网下载安装包 @@ -20,22 +20,21 @@ X86-64 --> 64位系统 ``` - ![安装包介绍](assets/安装包介绍.png) - 安装 - > # 勾选 Add Python 3.6 to PATH ---> 自动配置环境变量 + > # 勾选 Add Python 3.6 to PATH ---> 自动配置环境变量 - - Install Now ` 默认设置` + - Install Now `默认设置` ![安装界面](assets/安装.png) - - Customize installation ` 自定义安装选项` + - Customize installation `自定义安装选项` - ![Customize installation](assets/Customize installation.png) + ![Customize_installation](assets/Customize_installation.png) - > 勾选 pip + > 勾选 pip > > 其他可根据情况勾选 @@ -58,7 +57,7 @@ ```python #命令行界面输入 pip -V - python # 进入后 exit()可退出 + python # 进入后 exit()可退出 # 输出如图示 ``` @@ -66,14 +65,15 @@ - 安装结束 -#### Liunx安装 +#### Liunx 安装 + --- ##### 前言 -> Liunx发行版自带Python2版本 +> Liunx 发行版自带 Python2 版本 > -> 命令行 python2 | python 即可进入交互式界面 +> 命令行 python2 | python 即可进入交互式界面 ##### 下载安装包 @@ -90,7 +90,7 @@ # 其他版本可在官网下载,以3.65为例 ``` - > [Python官网](https://www.python.org/ftp/python) + > [Python 官网](https://www.python.org/ftp/python) ![liunx_下载](assets/liunx_下载.png) @@ -106,7 +106,7 @@ ```python # 进入解压后文件夹 - cd Python-3.6.5 + cd Python-3.6.5 # 创建安装目录 mkdir /usr/local/python3 # 指明安装路径 @@ -121,29 +121,29 @@ ```python #为python3创建软连接 - sudo ln -s /usr/local/python3/bin/python3 /usr/bin/python3 + sudo ln -s /usr/local/python3/bin/python3 /usr/bin/python3 #为pip3创建软连接 - sudo ln -s /usr/local/python3/bin/pip3 /usr/bin/pip3 + sudo ln -s /usr/local/python3/bin/pip3 /usr/bin/pip3 ``` ##### 验证 - ```python +```python # shell界面 输入 python3 -# shell输入 +# shell输入 pip3 -V # V大写 - ``` +``` -![liun_python 验证.png](assets/liun_python 验证.png) +![linux_python_验证.png](assets/linux_python_验证.png) -![liunx_pip 验证](assets/liunx_pip 验证.png) +![liunx_pip 验证](assets/liunx_pip_验证.png) -##### Python2 +##### Python2 -- 前面提到Liunx自带Python2版本,但是没有pip包管理工具 +- 前面提到 Liunx 自带 Python2 版本,但是没有 pip 包管理工具 -- 安装Python2 pip包管理工具 +- 安装 Python2 pip 包管理工具 ```python # 安装依赖 @@ -158,23 +158,23 @@ pip3 -V # V大写 --- -#### Mac下安装Python +#### Mac 下安装 Python -> [Python官网](https://www.python.org) +> [Python 官网](https://www.python.org) ##### 源码安装 -> Mac也是类unix系统,也可参考Liunx下安装Python方式一致 +> Mac 也是类 unix 系统,也可参考 Liunx 下安装 Python 方式一致 ##### 安装包安装 -- 下载Mac安装包 +- 下载 Mac 安装包 ![Mac安装包](assets/Mac包下载.png) - ------ + *** - > 根据系统位数,下载相应版本pkg安装即可 + > 根据系统位数,下载相应版本 pkg 安装即可 ![Mac下载1](assets/Mac下载1.png) @@ -190,4 +190,4 @@ pip -V #v 大写 python3 ``` -![mac验证](assets/Mac验证.png) \ No newline at end of file +![mac验证](assets/Mac验证.png) diff --git a/notes/Python/assets/Customize installation.png b/notes/Python/assets/Customize_installation.png old mode 100755 new mode 100644 similarity index 100% rename from notes/Python/assets/Customize installation.png rename to notes/Python/assets/Customize_installation.png diff --git "a/notes/Python/assets/liun_python \351\252\214\350\257\201.png" "b/notes/Python/assets/linux_python_\351\252\214\350\257\201.png" old mode 100755 new mode 100644 similarity index 100% rename from "notes/Python/assets/liun_python \351\252\214\350\257\201.png" rename to "notes/Python/assets/linux_python_\351\252\214\350\257\201.png" diff --git "a/notes/Python/assets/liunx_pip \351\252\214\350\257\201.png" "b/notes/Python/assets/liunx_pip_\351\252\214\350\257\201.png" old mode 100755 new mode 100644 similarity index 100% rename from "notes/Python/assets/liunx_pip \351\252\214\350\257\201.png" rename to "notes/Python/assets/liunx_pip_\351\252\214\350\257\201.png" diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/README.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/README.md" new file mode 100644 index 00000000..17b6150c --- /dev/null +++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/README.md" @@ -0,0 +1,39 @@ +# python 学习笔记 + + + +## 目录 +| 章节 | 标题 | 进度 | +| :----: | :--------------------------------------------------------------: | :------: | +| 第1章 | [序幕](./序幕.md) | `完成` | +| 第2章 | [序列构成的数组](./序列构成的数组.md) | `完成` | +| 第3章 | [字典和集合](./字典和集合.md) | `完成` | +| 第4章 | [文本和字节序列](./文本和字节序列.md) | `完成` | +| 第5章 | [函数](./函数.md) | `完成` | +| 第6章 | [使用一等函数实现设计模式](./使用一等函数实现设计模式.md) | `完成` | +| 第7章 | [函数装饰器和闭包](./函数装饰器和闭包.md) | `完成` | +| 第7章 | [扩展内容](./扩展内容.md) | `完成` | +| 第8章 | [对象引用、可变性和垃圾回收](./对象引用-可变性和垃圾回收.md) | `完成` | +| 第9章 | [符合Python风格的对象](./符合Python风格的对象.md) | `完成` | +| 第10章 | [序列的修改、散列和切片](./序列的修改-散列和切片.md) | `完成` | +| 第11章 | [抽象类](./抽象类.md) | `完成` | +| 第12章 | [类继承](./类继承.md) | `完成` | +| 第13章 | [正确重载运算符](./正确重载运算符.md) | `完成` | +| 第14章 | [可迭代的对象、迭代器和生成器](./可迭代的对象-迭代器和生成器.md) | `完成` | +| 第15章 | [上下文管理器和else块](./上下文管理器和else块.md) | `完成` | +| 第16章 | [协程](./协程.md) | `完成` | +| 第16章 | [协程-补充内容](./协程-补充内容.md) | `完成` | +| 第17章 | [使用future处理并发](./使用future处理并发.md) | `完成` | +| 第18章 | [使用asyncio包处理并发](./使用asyncio包处理并发.md) | `完成` | +| 第19章 | [动态属性和特性](./动态属性和特性.md) | `完成` | +| 第20章 | [属性描述符](./属性描述符.md) | `待完善` | +| 第21章 | [类元编程](./类元编程.md) | `完成` | +| 第22章 | [python字节码-补充内容](./python字节码-补充内容.md) | `完成` | +| 第23章 | [python时间模块](./python时间模块.md) | `完成` | +| 第24章 | [python命名空间](./python命名空间.md) | `完成` | +| 第25章 | [常见问题答疑](./常见问题答疑.md) | `待补充` | + + +## 参考 + +- [x] 《流畅的Python》([巴西] Luciano Ramalho著) diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/python\345\221\275\345\220\215\347\251\272\351\227\264.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/python\345\221\275\345\220\215\347\251\272\351\227\264.md" new file mode 100644 index 00000000..45b4af57 --- /dev/null +++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/python\345\221\275\345\220\215\347\251\272\351\227\264.md" @@ -0,0 +1,240 @@ +# 第24章 python命名空间 + + + +- [第24章 python命名空间](#%e7%ac%ac24%e7%ab%a0-python%e5%91%bd%e5%90%8d%e7%a9%ba%e9%97%b4) + - [内置命令空间](#%e5%86%85%e7%bd%ae%e5%91%bd%e4%bb%a4%e7%a9%ba%e9%97%b4) + - [全局命名空间](#%e5%85%a8%e5%b1%80%e5%91%bd%e5%90%8d%e7%a9%ba%e9%97%b4) + - [局部命名空间](#%e5%b1%80%e9%83%a8%e5%91%bd%e5%90%8d%e7%a9%ba%e9%97%b4) + - [区别](#%e5%8c%ba%e5%88%ab) + - [func函数内存地址](#func%e5%87%bd%e6%95%b0%e5%86%85%e5%ad%98%e5%9c%b0%e5%9d%80) + - [NotImplementedError](#notimplementederror) + + + +## 内置命令空间 + +就是python解释器 ,启动就可以使用的名字存储在内置命名空间中 +内置的名字在启动解释器的时候被加载进内存里 + + +## 全局命名空间 + +自己写的代码,但不是函数中的代码 + +是在程序从上到下被执行的过程中依次加载进内存的 +放置了我们设置的所有变量名和函数名 + + +## 局部命名空间 + +就是函数内部定义的名字 +当调用函数的时候,才会产生这个名称空间,随着函数执行的结束,这个命名空间就消失了 + + +## 区别 + +1、在局部: 可以使用全局,内置命名空间中的名字 + +2、在全局:可以使用内置命名空间中的名字,但是不能使用局部中变量使用 + +例子: +```python +def func(): + a = 1 + + +func() +print(a) +``` + +运行结果: + +``` +NameError: name 'a' is not defined +``` + +3、在内置: 不能使用局部和全局的名字的 + +顺序是这样的: +**`内置>全局>局部`** + +例子: +```python +def max(): + print("in max func") + + +max() +``` + +运行结果: +```python +in max func +``` + +在正常情况下,直接使用内置的名字 +当我们在全局定义了和内置名字空间中同名的名字时,就会使用全局的名字 +一级一级找 + + +## func函数内存地址 + +```python +# 函数名() 函数的调用 +# 加入id 就是函数的内存地址 + + +def max(): + print("in max func") + + +print(max) +print(id(max)) +``` +运行结果: +``` + +2600343820760 + +``` + + + +## NotImplementedError + +Python编程中raise可以实现报出错误的功能,而报错的条件可以由程序员自己去定制。 + +在面向对象编程中,可以先预留一个方法接口不实现,在其子类中实现。 + +如果要求其子类一定要实现,不实现的时候会导致问题,那么采用raise的方式就很好。 + +而此时产生的问题分类是NotImplementedError + + +例子: +```python +class ClassDemo(object): + def run(self): + raise NotImplementedError + + +class ChildClass(ClassDemo): + def run(self): + print("Hello world") + + +ChildClass().run() +``` +
+ +例子: +```python +class ClassDemo(object): + def run(self): + raise NotImplementedError + + def wrong(self): + # Will raise a TypeError + NotImplemented = "don't do this" + return NotImplemented + + +class ChildClass(ClassDemo): + def run(self): + print("Hello world") + + def wrong(self): + print("wrong") + + +ChildClass().run() # Hello world + +wrong = ClassDemo().wrong() +print(wrong) # don't do this + +``` + + +这里区分下 NotImplemented && NotImplementedError + +``` + type(NotImplemented) + + type(NotImplementedError) + +issubclass(NotImplementedError,Exception) +True +``` + + + +NotImplemented 是 Python 内建命名空间内仅有的 6 个常量(Python 中没有真正的常量)之一, +其它几个分别是 False、True、None、Ellipsis 和` __debug__`。 + +和 Ellipsis 一样,NotImplemented 也可以被重新赋值: +NotImplemented = "don't do this" + +两者是什么关系呢?答案是**“没啥关系”**。 + +Python 中 NotImplemented 广泛应用于二元魔术方法中,比如 `__eq__()、__lt__() `等等,表示该类型无法和其它类型进行对应的二元运算 + + +例子: +```python +class A(object): + def __init__(self, value): + self.value = value + + def __eq__(self, other): + if isinstance(other, A): + print('Comparing an A with an A') + return other.value == self.value + if isinstance(other, B): + print('Comparing an A with a B') + return other.value == self.value + print('Could not compare A with the other class') + return NotImplemented + + +class B(object): + def __init__(self, value): + self.value = value + + def __eq__(self, other): + # raise NotImplementedError + if isinstance(other, B): + print('Comparing a B with another B') + return other.value == self.value + print('Could not compare B with the other class') + return NotImplemented + + +a, b = A(1), B(1) +aa, bb = A(1), B(1) +a == aa # True +b == bb # True +a == b # True +b == a # True + +``` + +运行结果: +``` +Comparing an A with an A +Comparing a B with another B +Comparing an A with a B +``` + + + +说明 == 运算符执行时会先寻找 B 的 `__eq__() `方法, +遇到 NotImplemented 返回值则反过来去寻找 A 的 `__eq__()` 方法。 + + +什么时候该使用 NotImplementedError? + +>NotImplementedError 是 RuntimeError 的子类: +>issubclass(NotImplementedError, RuntimeError) # True + +>官网 的建议是当你需要一个方法必须覆盖才能使用时,其效果类似于 Java 中的接口,用于定义一个未实现的抽象方法。 \ No newline at end of file diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/python\345\255\227\350\212\202\347\240\201-\350\241\245\345\205\205\345\206\205\345\256\271.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/python\345\255\227\350\212\202\347\240\201-\350\241\245\345\205\205\345\206\205\345\256\271.md" new file mode 100644 index 00000000..f9d7e513 --- /dev/null +++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/python\345\255\227\350\212\202\347\240\201-\350\241\245\345\205\205\345\206\205\345\256\271.md" @@ -0,0 +1,186 @@ +# 第22章 python字节码-补充内容 + + + +- [第22章 python字节码-补充内容](#%e7%ac%ac22%e7%ab%a0-python%e5%ad%97%e8%8a%82%e7%a0%81-%e8%a1%a5%e5%85%85%e5%86%85%e5%ae%b9) + - [python编译过程](#python%e7%bc%96%e8%af%91%e8%bf%87%e7%a8%8b) + - [py文件](#py%e6%96%87%e4%bb%b6) + - [__pycache__](#pycache) + - [pyc文件](#pyc%e6%96%87%e4%bb%b6) + - [pyo文件](#pyo%e6%96%87%e4%bb%b6) + - [python程序执行原理](#python%e7%a8%8b%e5%ba%8f%e6%89%a7%e8%a1%8c%e5%8e%9f%e7%90%86) + + + + + +## python编译过程 + + +在日常生活中,Python代码一般是不编译的,几个py文件复制来就能用。再加上脚本语言的名头,有些不太了解Python的朋友就以为Python没有编译这个过程。 + +其实,虽然Python是脚本语言,但它与Java和C#一样,**`只能执行字节码`**。 + +只是Python将编译过程隐藏起来,不大明显而已。 + + +这一章就详细记述一下Python的编译过程以及一些技巧。 + +`这里使用的python版本是3.6.6` + + +### py文件 + +**py文件 就是python代码文件** + +这里准备两个py文件为下面分析使用 + +mymodule.py +```python +class MyModule(object): + def say(self, name): + print("Say...", name) +``` + +demo.py +```python +from mymodule import MyModule + +test = MyModule() + +test.say("Hello") +``` + +
+运行demo.py方法在当前目录会产生一个`__pycache__`目录 + +进入这个目录我们看到里面有个文件mymodule.cpython-36.pyc + +
+这里就有两个问题了 + +`__pycache__`目录是什么? + +pyc文件又是什么? + + +#### `__pycache__` + +`__pycache__`是包含编译并准备执行Python 3字节码的目录 + +当第一次运行 python 脚本时,解释器会将 *.py 脚本进行编译并保存到 `__pycache__` 目录 + +**这里有个问题?** + +我就写个一个文件,里面就打印一句话,会不会产生pycache目录呢? + +``` +print("hello world") +``` + +实验表明: +> 并不会产生这个pycache目录,这个目录只有当**`import xxx`**才会生成 + + +**`这个目录能不能删掉呢?删掉有什么影响呢?`** + +下次执行脚本时,若解释器发现你的 *.py 脚本没有变更,便会跳过编译一步,直接运行保存在 `__pycache__ `目录下的 *.pyc 文件 + + +**执行python脚本会导致字节代码在内存中生成并保持到程序关闭。** + +**如果导入模块,为了更快的可重用性,Python将创建一个缓存.pyc(PYC是'Python''Compiled')文件,其中导入的模块的字节代码被缓存。** + +想法是通过避免在重新导入时重新编译(编译一次,运行多次策略)来加速python模块的加载。 + +文件名与模块名称相同。 +初始点后面的部分表示创建缓存的Python实现(可能是CPython),后跟其版本号。 + +如前面说的mymodule.cpython-36.pyc文件 + +#### pyc文件 + +python2 代码 运行是直接在本地产生pyc文件 +python3 代码运行是把pyc文件放在pycache目录 + + +.pyc文件是由.py文件经过编译后生成的字节码文件,其加载速度相对于之前的.py文件有所提高,而且还可以实现源码隐藏,以及一定程度上的反编译 + + +pyc文件,是python编译后的字节码(bytecode)文件。 +只要你运行了py文件,python编译器就会自动生成一个对应的pyc字节码文件。 +这个pyc字节码文件,经过python解释器,会生成机器码运行 + +下次调用直接调用pyc,而不调用py文件。直到你这个py文件有改变。 + +python解释器会检查pyc文件中的生成时间,对比py文件的修改时间,如果py更新,那么就生成新的pyc。 + + +#### pyo文件 + +pyo文件也是优化(注意这两个字,便于后续的理解)编译后的程序(相比于.pyc文件更小),也可以提高加载速度。 + +但对于嵌入式系统,它可将所需模块编译成.pyo文件以减少容量。 但总的来说,作用上是几乎与原来的.py脚本没有区别的, + + +在所有的Python选项中: + +>-O,表示优化生成.pyo字节码 + +>-OO,表示进一步移除-O选项生成的字节码文件中的文档字符串 + +>-m,表示导入并运行指定的模块 + + +执行下面的命令查看是否生成pyo文件 + +```python +#py_compile是Python的自带模块 +python -O -m py_compile demo.py +python -OO -m py_compile demo.py +``` + +到pycache目录看到生成了cpython-36.opt-1.pyc文件 + +![Alt text](https://raw.githubusercontent.com/Syncma/Figurebed/master/img/1578027718663.png) + +我们发现下面的结果: + +1.`咦,说好的生成pyo文件的呢?怎么又生成了pyc文件?` + +2.两个pyc文件 +-O 选择生成的是xxxx-1.pyc +-OO 选项生成的是xxxx-2.pyc + +`这两个pyc又有啥区别呢?` + + +>查资料,发现从python3.5+开始就去除了pyo文件后缀名, +[消除pyo文件原文链接](https://www.python.org/dev/peps/pep-0488/) +[PYC目录链接](https://www.python.org/dev/peps/pep-3147/) + +为啥官方要去除pyo呢? + +我读完这两篇文章,简单总结下: + +由于pyc 文件在 Python 主要版本之间并不兼容 +所以引入了一种更灵活的替代机制 -pyc + +格式包含实现名称和版本号 + +*.pyc 文件可以表示优化和未优化的字节码。 + +优化级别信息可以包含在 *.pyc 文件的名字中, + +优化级别: +``` +0: .pyc +1 (-O): .pyo +2 (-OO): .pyo +``` + + + +## python程序执行原理 + +![Alt text](https://raw.githubusercontent.com/Syncma/Figurebed/master/img/1578032890559.png) diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/python\346\227\266\351\227\264\346\250\241\345\235\227.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/python\346\227\266\351\227\264\346\250\241\345\235\227.md" new file mode 100644 index 00000000..b700a7aa --- /dev/null +++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/python\346\227\266\351\227\264\346\250\241\345\235\227.md" @@ -0,0 +1,206 @@ +# 第23章 python时间模块 + + + + +- [第23章 python时间模块](#%e7%ac%ac23%e7%ab%a0-python%e6%97%b6%e9%97%b4%e6%a8%a1%e5%9d%97) + - [time 模块](#time-%e6%a8%a1%e5%9d%97) + - [datetime模块](#datetime%e6%a8%a1%e5%9d%97) + - [字符串转换成datetime类型](#%e5%ad%97%e7%ac%a6%e4%b8%b2%e8%bd%ac%e6%8d%a2%e6%88%90datetime%e7%b1%bb%e5%9e%8b) + - [datetime转换成字符串类型](#datetime%e8%bd%ac%e6%8d%a2%e6%88%90%e5%ad%97%e7%ac%a6%e4%b8%b2%e7%b1%bb%e5%9e%8b) + - [时间戳转string](#%e6%97%b6%e9%97%b4%e6%88%b3%e8%bd%acstring) + + + + +## time 模块 + + +time模块提供各种操作时间的函数 + +一般有两种表示时间的方式: + +第一种: 是时间戳的方式(相对于1970.1.1 00:00:00以秒计算的偏移量),时间戳是惟一的 + +第二种: 以数组的形式表示即(struct_time),共有九个元素,分别表示,同一个时间戳的struct_time会因为时区不同而不同 + + +例子: +```python +import time + +print(time.time()) +# 1578030257.4879787 + +print(time.localtime()) +# time.struct_time(tm_year=2020, tm_mon=1, tm_mday=3, tm_hour=13, tm_min=44, tm_sec=17, tm_wday=4, tm_yday=3, tm_isdst=0) + +``` + + + +## datetime模块 + +Python提供了多个内置模块用于操作日期时间,像calendar,time,datetime。time模块。 +相比于time模块,datetime模块的接口则更直观、更容易调用。 + +datetime模块定义了下面这几个类: + +datetime.date:表示日期的类。常用的属性有year, month, day, today; + +datetime.time:表示时间的类。常用的属性有hour, minute, second, microsecond; + +datetime.datetime:表示日期时间。 + +datetime.timedelta:表示时间间隔,即两个时间点之间的长度。 + +datetime.tzinfo:与时区有关的相关信息。 + +datetime中,表示日期时间的是一个datetime对象 + +datetime中提供了strftime方法,可以将一个datetime型日期转换成字符串 + + +举一个例子: +```python +import datetime + + +def datetostr(date): + return str(date)[0:10] + +def getDaysByNum(num): + today = datetime.date.today() + oneday = datetime.timedelta(days=1) + li = [] + for i in range(0, num): + today = today - oneday + li.append(datetostr(today)) + return li + + +print(getDaysByNum(3)) +``` + + + +## 字符串转换成datetime类型 + +```python +import datetime +s = "2016-01-12" +print(datetime.datetime.strptime(s, '%Y-%m-%d') +``` + + +## datetime转换成字符串类型 +```python +import datetime +s = "2016-01-12" +print(datetime.datetime.strptime(s, '%Y-%m-%d').strftime('%Y-%m-%d')) + +``` + +## 时间戳转string +```python +import time + +time1 = 1353254400 + +time_str = time.strftime('%Y-%m-%d', time.localtime(time1)) +print(time_str) +``` + +##几个例子 + +获取小时: +```python +import datetime as dt +hours = [dt.time(i).strftime('%H:%M') for i in range(24)] + +print(hours) +``` + + +时间相减: +```python +import time +import datetime + + +def String2Time(s): + return time.strptime(s, "%Y-%m-%d %H:%M:%S") + + +def dateMinDate(d1, d2): + d1 = String2Time(d1) + d2 = String2Time(d2) + # 这里的*号表示将time.struct_time类型转换成datetime.datetime + delta = datetime.datetime(*d1[:6]) - datetime.datetime(*d2[:6]) + print(delta) + + +if __name__ == "__main__": + dateMinDate("2012-06-26 15:20:00", "2012-06-26 15:10:00") + +``` + + +时间间隔: +```python +import datetime +date1 = '2011-05-03' +date2 = '2011-05-10' +datelist = [] + +start = datetime.datetime.strptime(date1, '%Y-%m-%d') +end = datetime.datetime.strptime(date2, '%Y-%m-%d') +step = datetime.timedelta(days=1) + +while start <= end: + days = start.strftime("%Y-%m-%d") # 此处要加入这一句 + datelist.append(days) + start += step + +print(datelist) +``` + + +计算多久之前: +```python +import datetime +import sys +if sys.version_info[0] >= 3: + unicode = str + + +def timebefore(d): + chunks = ( + (60 * 60 * 24 * 365, u'年'), + (60 * 60 * 24 * 30, u'月'), + (60 * 60 * 24 * 7, u'周'), + (60 * 60 * 24, u'天'), + (60 * 60, u'小时'), + (60, u'分钟'), + ) + # 如果不是datetime类型转换后与datetime比较 + if not isinstance(d, datetime.datetime): + d = datetime.datetime(d.year, d.month, d.day) + now = datetime.datetime.now() + delta = now - d + # 忽略毫秒 + before = delta.days * 24 * 60 * 60 + delta.seconds # python2.7直接调用 delta.total_seconds() + # 刚刚过去的1分钟 + if before <= 60: + return u'刚刚' + for seconds, unit in chunks: + count = before // seconds + if count != 0: + break + return unicode(count) + unit + u"前" + + +d = datetime.datetime.fromtimestamp(1571500800) +print(timebefore(d)) + +``` diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\344\270\212\344\270\213\346\226\207\347\256\241\347\220\206\345\231\250\345\222\214else\345\235\227.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\344\270\212\344\270\213\346\226\207\347\256\241\347\220\206\345\231\250\345\222\214else\345\235\227.md" new file mode 100644 index 00000000..caaf5ea6 --- /dev/null +++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\344\270\212\344\270\213\346\226\207\347\256\241\347\220\206\345\231\250\345\222\214else\345\235\227.md" @@ -0,0 +1,190 @@ +# 第15章 上下文管理器和else块 + + + +- [第15章 上下文管理器和else块](#%e7%ac%ac15%e7%ab%a0-%e4%b8%8a%e4%b8%8b%e6%96%87%e7%ae%a1%e7%90%86%e5%99%a8%e5%92%8celse%e5%9d%97) + - [try..except..finally](#tryexceptfinally) + - [with语句](#with%e8%af%ad%e5%8f%a5) + - [上下文管理器](#%e4%b8%8a%e4%b8%8b%e6%96%87%e7%ae%a1%e7%90%86%e5%99%a8) + - [@contextmanager 装饰器](#contextmanager-%e8%a3%85%e9%a5%b0%e5%99%a8) + + + +## try..except..finally + +看一个例子: +```python +def get_result(): + a = 3 + b = 0 + try: + b = 3 / 0 + except Exception as e: + print("ERROR=", e) + finally: + print("a=", a) + + +if __name__ == "__main__": + get_result() + +``` + +返回结果是: +```python +ERROR= division by zero +a= 3 +``` + +
+总结: +>try..except..else没有捕获到异常,执行else语句 +try..except..finally不管是否捕获到异常,都执行finally语句 + + + +## with语句 + + +python文本文件读写的3种方法 + +```python +# 第一种方法: +file1 = open("test.txt") +file2 = open("output.txt", "w") +while True: + line = file1.readline() + # 这里可以进行逻辑处理 + file2.write(line) + if not line: + break + +# 记住文件处理完,关闭是个好习惯 +file1.close() +file2.close() + +# 读文件有3种方法: +# - read()将文本文件所有行读到一个字符串中。 +# - readline()是一行一行的读 +# - readlines()是将文本文件中所有行读到一个list中,文本文件每一行是list的一个元素。 + +# 优点:readline()可以在读行过程中跳过特定行。 + +# 第二种方法:文件迭代器,用for循环的方法 +file2 = open("output.txt", "w") +for line in open("test.txt"): + # 这里可以进行逻辑处理 + file2.write(line) + +# 第三种方法: 推荐使用这个方法文件上下文管理器 +with open('somefile.txt', 'r') as f: + data = f.read() + +with open('somefile.txt', 'r') as f: + for line in f: + print(line) + +with open('somefile.txt', 'w') as f: + f.write("hello") +``` + + + +**这里重点说说第三种方法** + +打开文件在进行读写的时候可能会出现一些异常状况,如果按照常规的f.open +写法,我们需要try,except,finally,做异常判断,并且文件最终不管遇到什么情况,都要执行finally f.close()关闭文件,with方法帮我们实现了finally中f.close + + +## 上下文管理器 + +上下文管理器协议包含`__enter__ 和 __exit__` 两个方法。 + +with 语句开始运行时,会在上下文管理器对象上调用 `__enter__ `方法。 +with 语句运行结束后,会在上下文管理器对象上调用` __exit__ `方法,以此扮演 finally 子句的角色。 + +最常见的例子是确保关闭文件对象 +```python +class T(object): + def __enter__(self): + print('T.__enter__') + return '我是__enter__的返回值' + + def __exit__(self, exc_type, exc_val, exc_tb): + print('T.__exit__') + + +with T() as t: + print(t) +``` + +返回结果: +``` +T.__enter__ +我是__enter__的返回值 +T.__exit__ +``` + + +with之于上下文管理器,就像for之于迭代器一样。with就是为了方便上下文管理器的使用。 + + +## @contextmanager 装饰器 + + +下文管理器就不得不提一下@contextmanager 装饰器,它能减少创建上下文管理器的样板代码量, + +因为不用编写一个完整的类,定义` __enter__和 __exit__ `方法,而只需实现有一个 yield 语句的生成器,生成想让` __enter__ `方法返回的值。 + + +@contextmanager 装饰器优雅且实用,把三个不同的 Python 特性结合到了一起:**函数装饰器、生成器和 with 语句。** + +在使用 @contextmanager 装饰的生成器中,yield 语句的作用是把函数的定义体分成两部分: + + 1. yield 语句前面的所有代码在 with 块开始时(即解释器调用` __enter__` 方法时)执行 + 2. yield 语句后面的代码在with 块结束时(即调用 `__exit__` 方法时)执行。 + + +例子: +```python +import sys +import contextlib + + +@contextlib.contextmanager +def WoHa(n): + original_write = sys.stdout.write + + def reverse_write(text): + original_write(text[::-1]) + + sys.stdout.write = reverse_write + yield n + sys.stdout.write = original_write + return True + + +obj1 = WoHa('你手机拿反了') +with obj1 as content: + print('哈哈镜花缘') + print(content) + +print('#### with 执行完毕后,在输出content: ####') +print(content) + +``` + + +返回结果: +``` +缘花镜哈哈 +了反拿机手你 +#### with 执行完毕后,在输出content: #### +你手机拿反了 +``` + + +这里我们需要注意的是: + +代码执行到yield时,会产出一个值,这个值会绑定到 with 语句中 as 子句的变量上。 +执行 with 块中的代码时,这个函数会在yield这里暂停。 diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\344\275\277\347\224\250asyncio\345\214\205\345\244\204\347\220\206\345\271\266\345\217\221.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\344\275\277\347\224\250asyncio\345\214\205\345\244\204\347\220\206\345\271\266\345\217\221.md" new file mode 100644 index 00000000..c8a186b8 --- /dev/null +++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\344\275\277\347\224\250asyncio\345\214\205\345\244\204\347\220\206\345\271\266\345\217\221.md" @@ -0,0 +1,617 @@ +# 第18章 使用asyncio包处理并发 + + + +- [第18章 使用asyncio包处理并发](#第18章-使用asyncio包处理并发) + - [主线程与子线程](#主线程与子线程) + - [守护线程](#守护线程) + - [线程同步](#线程同步) + - [asyncio介绍](#asyncio介绍) + - [关键组件说明](#关键组件说明) + - [回调](#回调) + - [asyncio与gevent关系](#asyncio与gevent关系) + - [asyncio与Flask](#asyncio与flask) + - [aiohttp](#aiohttp) + - [与单进程、多进程对比](#与单进程多进程对比) + + + + +## 主线程与子线程 + + +当一个进程启动之后,会默认产生一个主线程,因为线程是程序执行流的最小单元。 + +当设置多线程时,主线程会创建多个子线程,在python中,默认情况下(其实就是`setDaemon(False)`),主线程执行完自己的任务以后,就退出了。 + +此时子线程会继续执行自己的任务,直到自己的任务结束。 + + +例子: +```python +import time +import threading + + +def run(): + time.sleep(2) + print('当前线程名称是:%s' % threading.currentThread().name) + time.sleep(2) + + +if __name__ == "__main__": + + start_time = time.time() + print('这是主线程:%s' % threading.current_thread().name) + + thread_list = [] + + for i in range(5): + t = threading.Thread(target=run) + thread_list.append(t) + + for t in thread_list: + t.start() + + print('主线程结束:%s' % threading.current_thread().name) + +print('一共用时:%f' % float(time.time() - start_time)) +``` + +运行结果: +``` +这是主线程:MainThread +主线程结束:MainThread +一共用时:0.002953 +当前线程名称是:Thread-2 +当前线程名称是:Thread-1 +当前线程名称是:Thread-5 +当前线程名称是:Thread-3 +当前线程名称是:Thread-4 +``` + + +##守护线程 +当我们使用`setDaemon(True)`方法,设置子线程为守护线程时,主线程一旦执行结束,则全部线程全部被终止执行 + +可能出现的情况就是,子线程的任务还没有完全执行结束,就被迫停止。 + +例子: +```python +import time +import threading + + +def run(): + time.sleep(2) + print('当前线程名称是:%s' % threading.currentThread().name) + time.sleep(2) + + +if __name__ == "__main__": + + start_time = time.time() + print('这是主线程:%s' % threading.current_thread().name) + + thread_list = [] + + for i in range(5): + t = threading.Thread(target=run) + thread_list.append(t) + + for t in thread_list: + t.setDaemon(True) + t.start() + + print('主线程结束:%s' % threading.current_thread().name) + +print('一共用时:%f' % float(time.time() - start_time)) +``` + +运行结果: +``` +这是主线程:MainThread +主线程结束:MainThread +一共用时:0.002954 +``` + + +## 线程同步 + +join所完成的工作就是线程同步,即主线程任务结束之后,进入阻塞状态,一直等待其他的子线程执行结束之后,主线程在终止 + +例子: +```python +import time +import threading + + +def run(): + time.sleep(2) + print('当前线程名称是:%s' % threading.currentThread().name) + time.sleep(2) + + +if __name__ == "__main__": + + start_time = time.time() + print('这是主线程:%s' % threading.current_thread().name) + + thread_list = [] + + for i in range(5): + t = threading.Thread(target=run) + thread_list.append(t) + + for t in thread_list: + t.start() + t.join() + + print('主线程结束:%s' % threading.current_thread().name) + +print('一共用时:%f' % float(time.time() - start_time)) +``` + +运行结果: +``` +这是主线程:MainThread +当前线程名称是:Thread-1 +当前线程名称是:Thread-2 +当前线程名称是:Thread-3 +当前线程名称是:Thread-4 +当前线程名称是:Thread-5 +主线程结束:MainThread +一共用时:20.015406 +``` + + + + +##asyncio介绍 + +asyncio的编程模型就是一个`消息循环`, 异步非阻塞的协程 + + +### 关键组件说明 + +asyncio使用了与以往python用法完全不同的构造:`事件循环、协程和futures` + + +关于asyncio的一些关键字的说明: + +>* event_loop 事件循环:程序开启一个无限循环,把一些函数注册到事件循环上,当满足事件发生的时候,调用相应的协程函数 + +>* coroutine 协程:协程对象,指一个使用async关键字定义的函数,它的调用不会立即执行函数,而是会返回一个协程对象。协程对象需要注册到事件循环,由事件循环调用。 + +>* task 任务:一个协程对象就是一个原生可以挂起的函数,任务则是对协程进一步封装,其中包含了任务的各种状态 + +>* future: 代表将来执行或没有执行的任务的结果。它和task上没有本质上的区别 + +>* async/await 关键字:python3.5用于定义协程的关键字,async定义一个协程,await用于挂起阻塞的异步调用接口。 + + +例子: + + + +```python +import asyncio +import datetime + + +# 写法1 +# @asyncio.coroutine +# def hello(): + +# print('hello world') +# r = yield from asyncio.sleep(1) + +# print('hello again') + +# 写法2 +async def hello(): + print('hello world') + await asyncio.sleep(1) + print('hello again') + + +if __name__ == "__main__": + + loop = asyncio.get_event_loop() + task = loop.create_task(hello()) + + print(datetime.datetime.now()) + + # print(task) + loop.run_until_complete(task) + + # print(task) + print(datetime.datetime.now()) + loop.close() +``` + + + +协程对象不能直接运行,在注册事件循环的时候,其实是run_until_complete方法将协程包装成为了一个任务(task)对象。 + +所谓task对象是Future类的子类。保存了协程运行后的状态,用于未来获取协程的结果。 + +在通过loop.create_task(hello())的时候,任务其实是处于pending状态。 + +在hello中通过asyncio.sleep(1)耗时一秒最后任务执行完后状态变为done. + +asyncio.ensure_future(coroutine) 和 loop.create_task(coroutine)都可以创建一个task,run_until_complete的参数是一个futrue对象。 + +当传入一个协程,其内部会自动封装成task,task是Future的子类 + + + +再来看一个例子: +```python +import asyncio +import random + + +async def MyCoroutine(id): + process_time = random.randint(1, 5) + # 使用asyncio.sleep模拟一些耗时的操作 + await asyncio.sleep(process_time) + print("协程: {}, 执行完毕。用时: {} 秒".format(id, process_time)) + + +async def main(): + # ensure_future方法 接收协程或者future作为参数,作用是排定他们的执行时间 + tasks = [asyncio.ensure_future(MyCoroutine(i)) for i in range(10)] + + # 返回结果 + await asyncio.gather(*tasks) + + +# 事件循环 +loop = asyncio.get_event_loop() +try: + loop.run_until_complete(main()) +finally: + loop.close() +``` + +运行结果: +``` +协程: 7, 执行完毕。用时: 1 秒 +协程: 9, 执行完毕。用时: 1 秒 +协程: 5, 执行完毕。用时: 2 秒 +协程: 0, 执行完毕。用时: 4 秒 +协程: 6, 执行完毕。用时: 4 秒 +协程: 2, 执行完毕。用时: 4 秒 +协程: 3, 执行完毕。用时: 4 秒 +协程: 8, 执行完毕。用时: 4 秒 +协程: 4, 执行完毕。用时: 4 秒 +协程: 1, 执行完毕。用时: 4 秒 +``` + + +从输出结果可以看出两点: +1.协程并没有按照顺序返回结果; +2.批量运行的任务所用的时间和所有任务中用时最长的相同。 + + + + +### 回调 + +```python +import asyncio +import requests + +async def request(): + url = 'https://www.baidu.com' + status = requests.get(url) + return status + +def callback(task): + print('Status:', task.result()) + +coroutine = request() +task = asyncio.ensure_future(coroutine) +task.add_done_callback(callback) +print('Task:', task) + +loop = asyncio.get_event_loop() +loop.run_until_complete(task) +print('Task:', task) +``` + +也可以不用回调: +```python +import asyncio +import requests + + +async def request(): + url = 'https://www.baidu.com' + status = requests.get(url) + return status + + +coroutine = request() +task = asyncio.ensure_future(coroutine) +print('Task:', task) + +loop = asyncio.get_event_loop() +loop.run_until_complete(task) +print('Task:', task) +print('Task Result:', task.result()) + +``` + +## asyncio与gevent关系 + + +gevent是第三方库,通过greenlet实现协程 + +其基本思路是: +当一个greenlet遇到IO操作时,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。 + + +asyncio是Python 3.4版本引入的标准库,直接内置了对异步IO的支持,不需要第三方的支持, + +asyncio的编程模型就是一个消息循环。 + +我们从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,就实现了异步IO。 + +很多异步io操作这两个库都可以用,只是他们在不同场景下的效率和易用性可能有区别,当然这个得进行深入的测试和研究,单就现在普通的场景来说 区别并不大 + + + +## asyncio与Flask + +首先使用flask编写一个web服务器 +```python +from flask import Flask +import time + +app = Flask(__name__) + +@app.route('/') +def index(): + time.sleep(3) + return 'Hello!' + +if __name__ == '__main__': + app.run(threaded=True) +``` + +这里run() 方法加了一个参数 threaded,这表明 Flask 启动了多线程模式,不然默认是只有一个线程的。 + +如果不开启多线程模式,同一时刻遇到多个请求的时候,只能顺次处理,这样即使我们使用协程异步请求了这个服务,也只能一个一个排队等待,瓶颈就会出现在服务端。 + +所以,多线程模式是有必要打开的。 + + +程序运行后会开启一个web, 端口默认5000 + +使用asyncio模块进行测试 +```python +import asyncio +import requests +import time + +start = time.time() + +async def request(): + url = 'http://127.0.0.1:5000' + print('Waiting for', url) + response = requests.get(url) + print('Get response from', url, 'Result:', response.text) + +tasks = [asyncio.ensure_future(request()) for _ in range(5)] +loop = asyncio.get_event_loop() +loop.run_until_complete(asyncio.wait(tasks)) + +end = time.time() +print('Cost time:', end - start) +``` + + +运行结果: +``` +Waiting for http://127.0.0.1:5000 +Get response from http://127.0.0.1:5000 Result: Hello! +Waiting for http://127.0.0.1:5000 +Get response from http://127.0.0.1:5000 Result: Hello! +Waiting for http://127.0.0.1:5000 +Get response from http://127.0.0.1:5000 Result: Hello! +Waiting for http://127.0.0.1:5000 +Get response from http://127.0.0.1:5000 Result: Hello! +Waiting for http://127.0.0.1:5000 +Get response from http://127.0.0.1:5000 Result: Hello! +Cost time: 15.0814049243927 +``` + +可以发现和正常的请求并没有什么两样,依然还是顺次执行的,耗时 15 秒,平均一个请求耗时 3 秒,说好的异步处理呢? + +其实,要实现异步处理,我们得先要有挂起的操作,当一个任务需要等待 IO 结果的时候,可以挂起当前任务,转而去执行其他任务,这样我们才能充分利用好资源,上面方法都是一本正经的串行走下来,连个挂起都没有,怎么可能实现异步? + +要实现异步,接下来我们再了解一下 await 的用法,使用 await 可以将耗时等待的操作挂起,让出控制权。 + +当协程执行的时候遇到 await,时间循环就会将本协程挂起,转而去执行别的协程,直到其他的协程挂起或执行完毕。 + +改进后代码: +```python +import asyncio +import requests +import time + +start = time.time() + + +# 这里增加异步get +# 否则会报错:TypeError: object Response can't be used in 'await' expression +async def get(url): + return requests.get(url) + + +async def request(): + url = 'http://127.0.0.1:5000' + print('Waiting for', url) + response = await get(url) # 修改这里增加await + print('Get response from', url, 'Result:', response.text) + + +tasks = [asyncio.ensure_future(request()) for _ in range(5)] +loop = asyncio.get_event_loop() +loop.run_until_complete(asyncio.wait(tasks)) + +end = time.time() +print('Cost time:', end - start) +``` + +运行结果: +``` +Waiting for http://127.0.0.1:5000 +Get response from http://127.0.0.1:5000 Result: Hello! +Waiting for http://127.0.0.1:5000 +Get response from http://127.0.0.1:5000 Result: Hello! +Waiting for http://127.0.0.1:5000 +Get response from http://127.0.0.1:5000 Result: Hello! +Waiting for http://127.0.0.1:5000 +Get response from http://127.0.0.1:5000 Result: Hello! +Waiting for http://127.0.0.1:5000 +Get response from http://127.0.0.1:5000 Result: Hello! +Cost time: 15.083374977111816 +``` + +还是不行,它还不是异步执行,也就是说我们仅仅将涉及 IO 操作的代码封装到 async 修饰的方法里面是不可行的! + +我们必须要使用支持**异步操作的请求方式**才可以实现真正的异步,所以这里就需要 aiohttp 派上用场了. + + +### aiohttp + +首先执行下面命令进行安装这个模块: + +```python +pip install aiohttp +``` + + +例子: +```python +import asyncio +import aiohttp +import time + +start = time.time() + +async def get(url): + session = aiohttp.ClientSession() + response = await session.get(url) + result = await response.text() + session.close() + return result + +async def request(): + url = 'http://127.0.0.1:5000' + print('Waiting for', url) + result = await get(url) + print('Get response from', url, 'Result:', result) + +tasks = [asyncio.ensure_future(request()) for _ in range(5)] +loop = asyncio.get_event_loop() +loop.run_until_complete(asyncio.wait(tasks)) + +end = time.time() +print('Cost time:', end - start) +``` + +运行结果: +``` +xx +Cost time: 3.056924819946289 +``` + + +我们发现这次请求的耗时由 15 秒变成了 3 秒,耗时直接变成了原来的 1/5 + +这就是异步操作的便捷之处,当遇到阻塞式操作时,任务被挂起,程序接着去执行其他的任务,而不是傻傻地等着,这样可以充分利用 CPU 时间,而不必把时间浪费在等待 IO 上 + + +这里将 task 数量设置成 100 ,发现结果也是3秒 +``` +Cost time: 3.4409260749816895 +``` + +### 与单进程、多进程对比 + + +单进程代码: +```python +import requests +import time + +start = time.time() + + +def request(): + url = 'http://127.0.0.1:5000' + print('Waiting for', url) + result = requests.get(url).text + print('Get response from', url, 'Result:', result) + + +for _ in range(100): + request() + +end = time.time() +print('Cost time:', end - start) +``` + +运行结果: +``` +Cost time: 301.17162680625916 +``` + + + + +多进程版本: + + +```python +import requests +import time +import multiprocessing + +start = time.time() + + +def request(_): + url = 'http://127.0.0.1:5000' + print('Waiting for', url) + result = requests.get(url).text + print('Get response from', url, 'Result:', result) + + +if __name__ == "__main__": # 这一行在windows下执行一定要添加,否则会报错 + + cpu_count = multiprocessing.cpu_count() + print('Cpu count:', cpu_count) + pool = multiprocessing.Pool(cpu_count) + + pool.map(request, range(100)) + + end = time.time() + print('Cost time:', end - start) +``` + +运行结果: +``` +Cost time: 48.85933017730713 +``` + + +这里想查看cpu个数也可以使用: +```python + import psutil + psutil.cpu_count() +``` \ No newline at end of file diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\344\275\277\347\224\250future\345\244\204\347\220\206\345\271\266\345\217\221.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\344\275\277\347\224\250future\345\244\204\347\220\206\345\271\266\345\217\221.md" new file mode 100644 index 00000000..b62a9718 --- /dev/null +++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\344\275\277\347\224\250future\345\244\204\347\220\206\345\271\266\345\217\221.md" @@ -0,0 +1,170 @@ +# 第17章 使用future处理并发 + + + +- [第17章 使用future处理并发](#%e7%ac%ac17%e7%ab%a0-%e4%bd%bf%e7%94%a8future%e5%a4%84%e7%90%86%e5%b9%b6%e5%8f%91) + - [futures模块](#futures%e6%a8%a1%e5%9d%97) + - [多线程模式ThreadPoolExecutor](#%e5%a4%9a%e7%ba%bf%e7%a8%8b%e6%a8%a1%e5%bc%8fthreadpoolexecutor) + - [多进程模式ProcessPoolExecutor](#%e5%a4%9a%e8%bf%9b%e7%a8%8b%e6%a8%a1%e5%bc%8fprocesspoolexecutor) + - [深入原理](#%e6%b7%b1%e5%85%a5%e5%8e%9f%e7%90%86) + + +## futures模块 + +Python3引入的concurrent.futures模块。concurrent.futures 是python3新增加的一个库,用于并发处理,提供了多线程和多进程的并发功能 类似于其他语言里的线程池(也有一个进程池),他属于上层的封装,对于用户来说,不用在考虑那么多东西了 + + +concurrent提供了两种并发模型,一个是多线程ThreadPoolExecutor,一个是多进程ProcessPoolExecutor。对于IO密集型任务宜使用多线程模型。对于计算密集型任务应该使用多进程模型。 + + +### 多线程模式ThreadPoolExecutor +多线程模式适合IO密集型运算,这里我要使用sleep来模拟一下慢速的IO任务 +这里使用Google fire开源库来简化命令行参数处理 + + +```python +import time +import fire +import threading +from concurrent.futures import ThreadPoolExecutor, wait + + +# 分割子任务 +def each_task(index): + time.sleep(1) # 睡1s,模拟IO + print("thread %d square %d" % (threading.current_thread().ident, index)) + return index * index # 返回结果 + + +def run(thread_num, task_num): + # 实例化线程池,thread_num个线程 + executor = ThreadPoolExecutor(thread_num) + start = time.time() + + fs = [] # future列表 + for i in range(task_num): + fs.append(executor.submit(each_task, i)) # 提交任务 + + wait(fs) # 等待计算结束 + end = time.time() + duration = end - start + + s = sum([f.result() for f in fs]) # 求和 + print("total result=%d cost: %s" % (s, duration)) + executor.shutdown() # 销毁线程池 + + +if __name__ == '__main__': + fire.Fire(run) +``` + +执行返回结果: +``` +python 1.py 2 10 # 也就是2个线程跑10个任务 +thread 5216 square 0 +thread 5856 square 1 +thread 5216 square 2 +thread 5856 square 3 +thread 5216 square 4 +thread 5856 square 5 +thread 5216 square 6 +thread 5856 square 7 +thread 5856 square 9 +thread 5216 square 8 +total result=285 cost: 5.06045389175415 +``` + +为什么输出乱了,这是因为print操作不是原子的,它是两个连续的write操作合成的,第一个write输出内容,第二个write输出换行符,write操作本身是原子的,但是在多线程环境下,这两个write操作会交错执行,所以输出就不整齐了。如果将代码稍作修改,将print改成单个write操作,输出就整齐了 ---`发现改了还是不行` + + + +调大参数看看效果: +``` +python 1.py 10 10 +thread 4792 square 9 +thread 10380 square 6 +thread 14420 square 7 +thread 5720 square 3 +thread 13808 square 4 +thread 17344 square 1 +thread 9172 square 5 +thread 10684 square 8 +thread 11696 square 0 +thread 6300 square 2 +total result=285 cost: 1.05 +``` + +可以看到1s中就完成了所有的任务。这就是多线程的魅力,可以将多个IO操作并行化,减少整体处理时间。 + + + +### 多进程模式ProcessPoolExecutor + +相比多线程适合处理IO密集型任务,多进程适合计算密集型。 + +```python +import os +import sys +import math +import time +import fire +from concurrent.futures import ProcessPoolExecutor, wait + + +# 分割子任务 +def each_task(n): + # 按公式计算圆周率 + s = 0.0 + for i in range(n): + s += 1.0 / (i + 1) / (i + 1) + pi = math.sqrt(6 * s) + # os.getpid可以获得子进程号 + sys.stdout.write("process %s n=%d pi=%s\n" % (os.getpid(), n, pi)) + return pi + + +def run(process_num, *ns): # 输入多个n值,分成多个子任务来计算结果 + # 实例化进程池,process_num个进程 + executor = ProcessPoolExecutor(process_num) + start = time.time() + fs = [] # future列表 + for n in ns: + fs.append(executor.submit(each_task, int(n))) # 提交任务 + wait(fs) # 等待计算结束 + end = time.time() + duration = end - start + print("total cost: %.2f" % duration) + executor.shutdown() # 销毁进程池 + + +if __name__ == '__main__': + fire.Fire(run) +``` + +执行返回结果: +``` +python 1.py 1 5000000 5001000 5002000 5003000 +process 10024 n=5000000 pi=3.141592462603821 +process 10024 n=5001000 pi=3.141592462641988 +process 10024 n=5002000 pi=3.1415924626801544 +process 10024 n=5003000 pi=3.141592462718321 +total cost: 4.62 +``` + +增大一个进程看看效果: +``` +python 1.py 2 5000000 5001000 5002000 5003000 +process 5860 n=5000000 pi=3.141592462603821 +process 14948 n=5001000 pi=3.141592462641988 +process 14948 n=5003000 pi=3.141592462718321 +process 5860 n=5002000 pi=3.1415924626801544 +total cost: 2.78 +``` + +从耗时上看缩短了接近1半,说明多进程确实起到了计算并行化的效果 + + +### 深入原理 + + +[Python最广为使用的并发库futures使用入门与内部原理](https://mp.weixin.qq.com/s/NBBDou4rIMo9KibVb4fjeg) \ No newline at end of file diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\344\275\277\347\224\250\344\270\200\347\255\211\345\207\275\346\225\260\345\256\236\347\216\260\350\256\276\350\256\241\346\250\241\345\274\217.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\344\275\277\347\224\250\344\270\200\347\255\211\345\207\275\346\225\260\345\256\236\347\216\260\350\256\276\350\256\241\346\250\241\345\274\217.md" new file mode 100644 index 00000000..1424db87 --- /dev/null +++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\344\275\277\347\224\250\344\270\200\347\255\211\345\207\275\346\225\260\345\256\236\347\216\260\350\256\276\350\256\241\346\250\241\345\274\217.md" @@ -0,0 +1,223 @@ +# 第6章 使用一等函数实现设计模式 + + + +- [第6章 使用一等函数实现设计模式](#第6章-使用一等函数实现设计模式) + - [单例模式](#单例模式) + - [为什么](#为什么) + - [是什么](#是什么) + - [怎么用](#怎么用) + + +设计模式 +每个设计模式都是围绕如下三个问题: + +>1.为什么?即为什么要使用这个设计模式,在使用这个模式之前存在什么样的问题? + +>2.是什么?通过Python语言来去实现这个设计模式,用于解决为什么中提到的问题。 + +>3.怎么用?理解了为什么我们也就基本了解了什么情况下使用这个模式,不过在这里还是会细化使用场景,阐述模式的局限和优缺点 + + +## 单例模式 +这一篇我们先来看看单例模式。单例模式是设计模式中逻辑最简单,最容易理解的一个模式,简单到只需要一句话就可以理解,即**`保证只有一个对象实例的模式`**。 + +问题的关键在于实现起来并没有想象的那么简单。 + +不过我们还是先来讨论下为什么需要这个模式吧。 + +### 为什么 + +我们首先来看看单例模式的使用场景,然后再来分析为什么需要单例模式。 +* **Python的logger就是一个单例模式,用以日志记录** +* **Windows的资源管理器是一个单例模式** +* **线程池,数据库连接池等资源池一般也用单例模式** +* **网站计数器** + +
+ +从这些使用场景我们可以总结下什么情况下需要单例模式: +1.当每个实例都会占用资源,而且实例初始化会影响性能,这个时候就可以考虑使用单例模式,**`它给我们带来的好处是只有一个实例占用资源,并且只需初始化一次;`** + +2.当有同步需要的时候,可以通过一个实例来进行同步控制,比如对某个共享文件(如日志文件)的控制,对计数器的同步控制等,这种情况下由于只有一个实例,所以不用担心同步问题。 + +
+当然所有使用单例模式的前提是我们的确用一个实例就可以搞定要解决的问题,而不需要多个实例,如果每个实例都需要维护自己的状态,这种情况下单例模式肯定是不适用的。 + + +接下来看看如何使用Python来实现一个单例模式。 + +### 是什么 + +最开始的想法很简单,实现如下: + +```python +class Singleton(object): + __instance = None + + def __init__(self): + pass + + def __new__(cls, *args, **kwd): + if Singleton.__instance is None: + Singleton.__instance = object.__new__(cls, *args, **kwd) + return Singleton.__instance + + +s1 = Singleton() +s2 = Singleton() +print(s1) +print(s2) + +if s1 == s2: + print("True") +else: + print("False") + +``` + +运行结果是: +``` +<__main__.Singleton object at 0x000002176FE884A8> +<__main__.Singleton object at 0x000002176FE884A8> +True + +``` + +### 怎么用 + +在 Python 中,我们可以用多种方法来实现单例模式: + +> 使用模块 +使用 `__new__` +使用装饰器(decorator) +使用元类(metaclass) + + +1.使用模块 +其实,Python 的模块就是天然的单例模式,因为模块在第一次导入时,会生成 .pyc 文件,当第二次导入时,就会直接加载 .pyc 文件,而不会再次执行模块代码。 + +因此,我们只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了。 +如果我们真的想要一个单例类,可以考虑这样做: + +```python +# mysingleton.py +class My_Singleton(object): + def foo(self): + pass + + +my_singleton = My_Singleton() + +# 将上面的代码保存在文件 mysingleton.py 中,然后这样使用: + +from mysingleton import my_singleton +my_singleton.foo() + +``` +
+ +2.使用`__new__`方法 +为了使类只能出现一个实例,我们可以使用 __new__ 来控制实例的创建过程,代码如下: + +```python +class Singleton(object): + _instance = None + + def __new__(cls, *args, **kw): + if not cls._instance: + cls._instance = super(Singleton, cls).__new__(cls, *args, **kw) + return cls._instance + + +class MyClass(Singleton): + a = 1 + + +one = MyClass() +two = MyClass() + +print(one == two) +print(one is two) +print(id(one), id(two)) +``` + +运行结果: +``` +True +True +2483856443152 2483856443152 +``` +
+3.使用装饰器 +我们知道,装饰器(decorator)可以动态地修改一个类或函数的功能。这里,我们也可以使用装饰器来装饰某个类,使其只能生成一个实例,代码如下: + +```python +from functools import wraps + + +def singleton(cls): + instances = {} + + @wraps(cls) + def getinstance(*args, **kw): + if cls not in instances: + instances[cls] = cls(*args, **kw) + return instances[cls] + + return getinstance + + +@singleton +class MyClass(object): + a = 1 + + +one = MyClass() +two = MyClass() + +print(one == two) + +``` + +在上面,我们定义了一个装饰器 singleton,它返回了一个内部函数 getinstance,该函数会判断某个类是否在字典 instances 中, + +如果不存在,则会将 cls 作为 key,cls(*args, **kw) 作为 value 存到 instances 中,否则,直接返回 instances[cls]。 + +
+4.使用元类 + +元类(metaclass)可以控制类的创建过程,它主要做三件事: + +拦截类的创建 +修改类的定义 +返回修改后的类 +使用元类实现单例模式的代码如下: + +```python +class Singleton(type): + _instances = {} + + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + cls._instances[cls] = super(Singleton, + cls).__call__(*args, **kwargs) + return cls._instances[cls] + + +# # Python2 +# class MyClass(object): +# __metaclass__ = Singleton + + +#Python3 +class MyClass(metaclass=Singleton): + pass + + +one = MyClass() +two = MyClass() + +print(one == two) + +``` diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\207\275\346\225\260.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\207\275\346\225\260.md" new file mode 100644 index 00000000..0d966f7a --- /dev/null +++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\207\275\346\225\260.md" @@ -0,0 +1,313 @@ +# 第5章 函数 + + + +- [第5章 函数](#第5章-函数) + - [函数](#函数) + - [高阶函数](#高阶函数) + - [map函数](#map函数) + - [filter函数](#filter函数) + - [reduce函数](#reduce函数) + - [lambda 函数](#lambda-函数) + - [sorted函数](#sorted函数) + - [`__call__`方法](#__call__方法) + - [函数注解](#函数注解) + - [冻结参数的函数](#冻结参数的函数) + + + + +## 函数 + +在python中一切都可以视作为对象,包括函数 + +```python +def function_try(): + '''it is funciton try doc''' + + print('function_try doc') + + +if __name__ == "__main__": + + # __doc__属性用于生成对象的帮助文本 + print(function_try.__doc__) + # __name__则是输出了具体的函数名 + print(function_try.__name__) +``` +
+我们还可以将函数名赋值给变量,通过变量的形式来访问。 + +```python +def function_try(): + '''it is funciton try doc''' + + print('function_try doc') + + +if __name__ == "__main__": + + fun = function_try + + # __doc__属性用于生成对象的帮助文本 + print(fun.__doc__) + # __name__则是输出了具体的函数名 + print(fun.__name__) +``` +
+函数也可以通过参数被传递给另外一个函数。 + +```python +def function_try(): + '''it is funciton try doc''' + + print('function_try doc') + + +def function_try1(func): + print(func.__doc__) + print(func.__name__) + func() + + +if __name__ == "__main__": + + fun = function_try + function_try1(fun) + +``` + +## 高阶函数 + +>**接受函数为参数,或者把函数作为结果返回的函数是高阶函数** + +### map函数 +```python +def cube(x): + return x * x + + +data = map(cube, range(1, 11)) +print(list(data)) # 此处要加list + + +def add_num(n): + return n + 1 + + +print(list(map(add_num, range(1, 6)))) +``` + +再看几个例子: +```python +result = ['1', '2', '3'] + +print(list(map(int, result))) # [1, 2, 3] + +# 也可以用列表推导式 +print([int(i) for i in result]) + +``` + +```python +t = ((1, 'a'), (2, 'b')) +print(type(t)) +print(dict(t)) +print(dict((y, x) for x, y in t)) + +# map方法 +# print(dict(map(None, t))) # 这个python3已废弃 +print(dict(map(reversed, t))) + +``` + + +### filter函数 +Filter的作用在于根据第一个参数为true还是false来进行过滤。 +下面例子中将大于3的数值过滤出来。 +```python +s = [1, 2, 3, 4, 5] + +print(list(filter(lambda x: x > 3, s))) +# [4, 5] +``` + +### reduce函数 +就是将参数累加的应用于函数中。就好比我们要对1到100进行求值。 +代码如下。效果等同于sum(range(100)) +```python +# python3 已经将reduce移到functools模块中 +from functools import reduce +from operator import add + +print(reduce(add, range(100))) + +``` + +### lambda 函数 +这是Python支持一种有趣的语法,`它允许你快速定义单行的最小函数` +例子: +```python +g = lambda x: x * 2 +print(g(3)) + +# 相等于下面的表达式 +print((lambda x: x * 2)(3)) + +D = {'jack': 23, 'rose': 21, 'flank': 22} + +value_sort = sorted(D.items(), key=lambda d: d[1]) # 值(value)排序 +print(value_sort) +# [('rose', 21), ('flank', 22), ('jack', 23)] + +key_sort = sorted(D.items(), key=lambda d: d[0]) # 按键(key)排序按 +print(key_sort) +# [('flank', 22), ('jack', 23), ('rose', 21)] + +key_reverse = sorted(D.items(), key=lambda d: d[0], reverse=True) # 按键(key)降序 +print(key_reverse) +# [('rose', 21), ('jack', 23), ('flank', 22)] + +# 值(value)降序 +value_reverse = sorted(D.items(), key=lambda d: d[1], reverse=True) +print(value_reverse) +# [('jack', 23), ('flank', 22), ('rose', 21)] + +``` +python lambda是在python中使用lambda来创建匿名函数,而用def创建的方法是有名称的,除了从表面上的方法名不一样外,python lambda还有哪些和def不一样呢? + +>1 **python lambda会创建一个函数对象,但不会把这个函数对象赋给一个标识符,而def则会把函数对象赋值给一个变量**。 + +>2 **python lambda它只是一个表达式,而def则是一个语句**。 + +下面是python lambda的格式,看起来好精简阿。 +```python +lambda x: print x +``` + +lambda表达式在“:”后只能有一个表达式。 +也就是说,在 def中,用return可以返回的也可以放在lambda后面,不能用return返回的也不能定义在python lambda后面。 + +因此,像if或for或print这种语句就不能用于lambda中,lambda一般只用来定义简单的函数。 + +下面举几个python lambda的例子吧 +1. 单个参数的: +```python +g = lambda x:x*2 +print(g(3)) +# 结果是6 +``` +2.多个参数的: +```python +m = lambda x,y,z: (x-y)*z +print(m(3,1,2)) + +#结果是4 +``` + + +### sorted函数 + +**`sorted是另外返回一个排序后的列表,原列表fruits并没有发生改变`** + +```python +fruits = ['strawberry', 'fig', 'apple', 'cherry', 'rasberry', 'banana'] + +ret = sorted(fruits, key=len) + +print('before sorted %s' % fruits) +# before sorted ['strawberry', 'fig', 'apple', 'cherry', 'rasberry', 'banana'] + +print('after sorted %s' % ret) +# after sorted ['fig', 'apple', 'cherry', 'banana', 'rasberry', 'strawberry'] + +``` + +与它很相像的`sort函数`,看下面的例子: +```python +a = [5, 4, 3, 2, 1] + +sorted(a) # 将a从大到小排序,不影响a本身结构 +print("a=", a) # a= [5, 4, 3, 2, 1] + +a.sort() # 将a从小到大排序,影响a本身结构 +print(a) # [1, 2, 3, 4, 5] + +b = a.sort() +print("b=", b) # b= None + +c = ['aa', 'bb', 'BB', 'CC', 'zz'] +print(sorted(c)) +# 按列表中元素每个字母的ascii码从小到大排序 +# 如果要从大到小,请用sorted(b,reverse=True) +# ['BB', 'CC', 'aa', 'bb', 'zz'] +``` + +## `__call__`方法 + +前面介绍到可以把函数当做对象。那么我们可以像调用函数那样调用类么。 +答案是肯定的。 + +只需要我们重写类中的`__call__`就可以了 + +```python +class function_try(object): + def __init__(self, value): + self.data = value + + def __call__(self, *args, **kwargs): + print('function try was called') + + for a in args: + print(a) + + +if __name__ == "__main__": + + f = function_try(3) + f('hello', 'world') +``` + + +除了__doc__, __name__这些函数还有很多属性。可以用**dir(func)**的方法进行查看 + +## 函数注解 + +Python 3提供了一种句法,用于为函数声明中的参数和返回值附加元数据 +Python不做检查、不做强制、不做验证,什么操作都不做 + +```python +# def test(text, max_len=5): +# 下面是python3 新写法 +def test(text: str, max_len: 'int>0' = 5) -> str: + if len(text) > max_len: + return "OK" + + +text = "hello world" + +# print(len(text)) +a = test(text) +print(a, type(a)) + +print(test.__annotations__) +#{'text': , 'max_len': 'int>0', 'return': } + +``` + +## 冻结参数的函数 +最后介绍一个可以冻结参数的功能。**Functions.partial.** + +在某些场景下,我们期望可以冻结一个参数,并将这个参数作用于其他参数。 + +```python +from functools import partial +from operator import mul + +triple = partial(mul, 3) # 将第一个参数固定为3 + +print(triple(7)) # 7*3=21 + +print(list(map(triple, range(1, 10)))) # 1,10分别和3相乘 + +``` \ No newline at end of file diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\207\275\346\225\260\350\243\205\351\245\260\345\231\250\345\222\214\351\227\255\345\214\205.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\207\275\346\225\260\350\243\205\351\245\260\345\231\250\345\222\214\351\227\255\345\214\205.md" new file mode 100644 index 00000000..3e81e32d --- /dev/null +++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\207\275\346\225\260\350\243\205\351\245\260\345\231\250\345\222\214\351\227\255\345\214\205.md" @@ -0,0 +1,1085 @@ +# 第7章 函数装饰器和闭包 + + + +- [第7章 函数装饰器和闭包](#第7章-函数装饰器和闭包) + - [装饰器](#装饰器) + - [基本概念](#基本概念) + - [优点](#优点) + - [使用](#使用) + - [装饰器分类](#装饰器分类) + - [无参数装饰器 – 包装无参数函数](#无参数装饰器--包装无参数函数) + - [无参数装饰器 – 包装带参数函数](#无参数装饰器--包装带参数函数) + - [带参数装饰器 – 包装无参数函数](#带参数装饰器--包装无参数函数) + - [带参数装饰器 – 包装带参数函数](#带参数装饰器--包装带参数函数) + - [装饰器总结](#装饰器总结) + - [几个内置的装饰器](#几个内置的装饰器) + - [`@staticmethod`](#staticmethod) + - [`@classmethod`](#classmethod) + - [普通方法、类方法和静态方法的比较](#普通方法类方法和静态方法的比较) + - [`@property`](#property) + - [变量作用域](#变量作用域) + - [Local作用域](#local作用域) + - [Enclosing作用域](#enclosing作用域) + - [关键字nolocal](#关键字nolocal) + - [Global作用域](#global作用域) + - [build-in作用域](#build-in作用域) + - [python命名空间规则](#python命名空间规则) + - [可变数据类型和不可变数据类型](#可变数据类型和不可变数据类型) + - [可变数据类型:列表list和字典dict](#可变数据类型列表list和字典dict) + - [不可变数据类型:数值型、字符串型string和元组tuple](#不可变数据类型数值型字符串型string和元组tuple) + - [字符串不可变的原因](#字符串不可变的原因) + - [闭包](#闭包) + - [什么是闭包](#什么是闭包) + - [为什么要使用闭包](#为什么要使用闭包) + - [怎么使用闭包](#怎么使用闭包) + + +## 装饰器 + +### 基本概念 +装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理, Web权限校验, Cache等。 + +很有名的例子,就是咖啡,加糖的咖啡,加牛奶的咖啡。 本质上,还是咖啡,只是在原有的东西上,做了“装饰”,使之附加一些功能或特性。 + +例如记录日志,需要对某些函数进行记录 + +笨的办法,每个函数加入代码,如果代码变了,就悲催了。 + +装饰器的办法,定义一个专门日志记录的装饰器,对需要的函数进行装饰 + +### 优点 +抽离出大量函数中与函数功能本身无关的雷同代码并继续重用 + +即,可以将函数“修饰”为完全不同的行为,可以有效的将业务逻辑正交分解,如用于将权限和身份验证从业务中独立出来 + +概括的讲,**`装饰器的作用就是为已经存在的对象添加额外的功能`** + +### 使用 +在Python中,装饰器实现是十分方便的 +原因是:函数可以被扔来扔去。 + +函数作为一个对象: + +A.可以被赋值给其他变量,可以作为返回值 + +B.可以被定义在另外一个函数内 + +def: +装饰器是一个函数,一个用来包装函数的函数,装饰器在函数申明完成的时候被调用,调用之后返回一个修改之后的函数对象,将其重新赋值原来的标识符,并永久丧失对原始函数对象的访问(申明的函数被换成一个被装饰器装饰过后的函数) + +当我们对某个方法应用了装饰方法后, 其实就改变了被装饰函数名称所引用的函数代码块入口点,使其重新指向了由装饰方法所返回的函数入口点。 + +由此我们可以用decorator改变某个原有函数的功能,添加各种操作,或者完全改变原有实现。 + +### 装饰器分类 +装饰器分为无参数decorator,有参数decorator + +>* 无参数decorator 生成一个新的装饰器函数 + +>* 有参decorator 有参装饰,装饰函数先处理参数,再生成一个新的装饰器函数,然后对函数进行装饰 + +**`装饰器有参/无参,函数有参/无参,组合共4种`** + +#### 无参数装饰器 – 包装无参数函数 + +```python +def decorator(func): + print("hello") + return func + +@decorator +def foo(): + print("this is foo") + return "111" + +print(foo()) + +# 等价于 +# foo = decorator(foo) +# foo() +``` + + + +#### 无参数装饰器 – 包装带参数函数 + +```python +def decorator_func_args(func): + def inner(*args, **kwargs): # 处理传入函数的参数 + print("begin") + ret = func(*args, **kwargs) # 函数调用 + print("end") + return ret + return inner + +@decorator_func_args +def foo2(a, b=2): + print(a, b) + return "111" + +print(foo2(1)) +``` + +#### 带参数装饰器 – 包装无参数函数 + +```python +def decorator_with_params(arg_of_decorator): # 这里是装饰器的参数 + print(arg_of_decorator) + # 最终被返回的函数 + def newDecorator(func): + print(func) + return func + return newDecorator + +@decorator_with_params("deco_args") +def foo3(): + print("Hello") + return "111" + +print(foo3()) + +``` + +#### 带参数装饰器 – 包装带参数函数 +```python +def decorator_whith_params_and_func_args(arg_of_decorator): + def handle_func(func): + def handle_args(*args, **kwargs): + print("begin") + func(*args, **kwargs) + print("end") + print(arg_of_decorator, func, args, kwargs) + return func + return handle_args + return handle_func + +@decorator_whith_params_and_func_args("123") +def foo4(a, b=2): + print("Content") + return "111" + +print(foo4(1, b=3)) + +``` + +### 装饰器总结 +1.装饰器是用来装饰函数 + +2.返回的是一个函数对象 + +3.被修饰函数标识符 指向返回的函数对象 + +4.语法糖 @deco + +### 几个内置的装饰器 +#### `@staticmethod` +staticmethod,静态方法在调用时,对类及实例一无所知 + +仅仅是获取传递过来的参数,没有隐含的第一个参数,在Python里基本上用处不大,你完全可以用一个模块函数替换它 + +```python +class Test(object): + # 定义类Test的属性 + name = 'python' + content = '人生苦短,我用python!' + + def instance_method(self): + print("是类{}的实例方法,只能被实例对象调用".format(Test)) + + @staticmethod # 静态方法,无法访问Test类的属性 + def static_method(): # 默认没有参数?能不能给个参数? + print("Call static method ()\n") + # print(self.name, self.content) # 错误调用 + + +# 静态方法调用方法, 可以通过类调用,类的实例调用 +Test.static_method() +Test().static_method() + +# 类的实例方法只能实例调用 +Test().instance_method() + +``` + +`@staticmethod`装饰(静态方法):**经常有一些跟类有关系的功能但在运行时又不需要实例和类参与的情况下需要用到静态方法。** + + **`比如更改环境变量或者修改其他类的属性等能用到静态方法`** + +例子: +```python +class Test: + def __init__(self, num1, num2): + self.num1 = num1 + self.num2 = num2 + + def plus(self): + result = self.num1 + self.num2 + return result + + def multiply(self): + result = self.num1 * self.num2 + return result + + @staticmethod + def sum(num1, num2): + s = num1 * num1 + num2 * num2 + print(s) + + +Test.sum(3, 4) + +``` + + +```python +IND = 'ON' + + +class Kls(object): + def __init__(self, data): + self.data = data + + @staticmethod + def checkind(): + return IND == 'ON' + + def do_reset(self): + if self.checkind(): + print('Reset done for: %s' % self.data) + + def set_db(self): + if self.checkind(): + print('DB connection made for: %s' % self.data) + + +ik1 = Kls(24) +ik1.do_reset() +ik1.set_db() +``` + +```python +class Foo(object): + X = 1 + Y = 2 + + @staticmethod + def averag(*mixes): + return sum(mixes) / len(mixes) + + @staticmethod + def static_method(): + return Foo.averag(Foo.X, Foo.Y) + + +foo = Foo() +# 子类的实例继承了父类的static_method静态方法, +# 调用该方法,还是调用的父类的方法和类属性。 +print(foo.static_method()) +``` + + +总结: + +静态方法:staticmethod修饰,无需参数,类与实例共享。 + +无法访问类属性、实例属性,相当于一个相对独立的方法, + +跟类其实没什么关系,换个角度来讲,其实就是放在一个类的作用域里的函数而已。 + +**静态方法中引用类属性的话,必须通过类对象来引用。** + + +#### `@classmethod` + +`@classmethod` 是一个函数修饰符,它表示接下来的是一个类方法,**类方法的第一个参数cls** + +```python +class Test(object): + # 定义类Test的属性 + name = 'python' + content = '人生苦短,我用python!' + + @classmethod # 类方法访问Test类的属性 + def class_method(cls): + # 参数cls必须要有 + print(cls.name, cls.content) + + +class Son(Test): + content = "你好" + name = "世界" + + +# 调用方法 +Test.class_method() +Test().class_method() + +# 类方法有类变量cls传入,从而可以用cls做一些相关的处理。 +# 并且有子类继承时,调用该类方法时,传入的类变量cls是子类,而非父类。 +# 调用的是子类的方法和子类的类属性 +print("*" * 10) +Son.class_method() +Son().class_method() +``` + + +#### 普通方法、类方法和静态方法的比较 + +> 普通方法:**由对象调用。至少一个self参数**,执行普通方法时, +自动将调用该方法的对象赋值给self + +>类方法:**由类调用; 至少一个cls参数**;执行类方法时,自动将调用该方法的类复制给cls + +>静态方法:**由类调用;无默认参数** + +```python +class Foo(object): + # def __init__(self, name): + # self.name = name + + def ord_func(self): + """ 定义普通方法,至少有一个self参数 """ + + # print self.name + print('普通方法') + + @classmethod + def class_func(cls): + """ 定义类方法,至少有一个cls参数 """ + + print('类方法') + + @staticmethod + def static_func(): + """ 定义静态方法 ,无默认参数""" + + print('静态方法') + + +# 调用普通方法 +f = Foo() +f.ord_func() + +# 调用类方法 +Foo.class_func() +f.class_func() + +# 调用静态方法 +Foo.static_func() +f.static_func() +``` + + +相同点:对于所有的方法而言,均属于类(非对象)中,所以,在内存中也只保存一份。 +不同点:方法调用者不同、调用方法时自动传入的参数不同。 + + +使用普通方法、类方法和静态方法都可以通过对象(t)进行调用,但是静态方法和类方法无法访问对象的属性,所以**`更改对象(t)的属性仅仅只是对普通方法起作用`** + +```python +class Test(object): + # 定义类Test的属性 + name = 'python' + content = '人生苦短,我用python!' + + def normal_method(self): + print(self.content) + + @classmethod + def class_method(cls): + print(cls.content) + + @staticmethod + def static_method(): + print('content') + + +t = Test() +t.content = '人生苦短,及时行乐' # 设置对象t的属性 + +t.normal_method() # 人生苦短,及时行乐 +t.class_method() # 人生苦短,我用python! +t.static_method() # content + +``` + +```python +class P(object): + tip = 'Persion tips' # 类属性,类及类成员共享 + + @classmethod + def clsFunc(cls): + """类方法共享""" + return 'Welcome:' + cls.tip + + def insFunc(self): + """实例方法""" + return self.__class__ + + @staticmethod + def staFunc(): + """静态方法""" + return P.tip + + +p = P() +print(P.tip) +print(p.tip) +print(p.insFunc()) + +p.tip = "Hello" +print(p.clsFunc()) +print(p.staFunc()) + +print(P.clsFunc()) +print(P.staFunc()) +``` +
+总结: + +>三种方法都可以通过对象进行调用,但类方法和静态方法无法访问对象属性,类方法通过对象调用获取的仍是类属性(并非对象属性); + +>普通方法无法通过类名调用,类方法和静态方法可以,但静态方法不能进行访问,仅仅只是通过传值得方式(与函数调用相同) + + + + +#### `@property` + +访问类或实例的属性时是直接通过obj.XXX的形式访问,但property是一个特性的属性 + +访问添加了装饰器@property的方法时无须额外添加()来调用,而是以**属性的形式来访问**。 + +所以,利用这个特性可以虚拟实现类属性的只读设置。 + +```python +class Student(object): + # 上面的birth是可读写属性,而age就是一个只读属性 + # 因为age可以根据birth和当前时间计算出来。 + @property # 相当于property.getter(birth) 或者property(birth) + def birth(self): + return self._birth + + @birth.setter # 相当于birth = property.setter(birth) + def birth(self, value): + self._birth = value + + @property + def age(self): + return 2019 - self._birth + + +student = Student() + +student.birth = 1987 +print(student.birth) +print(student.age) + +``` + +这里有个问题,**`为什么property属性只能类的实例调用?直接使用类调用可以不可以?`** + + +```python +class Test(object): + @property + def p(self): + return "Hello World" + + +# 类调用 +print(Test.p) # + +# 实例调用 +test = Test() +print(test.p) # Hello World +print(Test.p.__get__(test)) # Hello World + +print(test.p()) # TypeError: 'str' object is not callable + +``` + +这里要看下什么是[python描述符](https://docs.python.org/3.6/howto/descriptor.html) + +>Test.p 返回的是一个property,它的属性是描述符. +它是一个类,用很多魔法方法,比如`__get__,__set__...` + +>当property被类的实例调用时候,就会触发其中的`__get__`方法. +所以**可以通过类调用property,也可以使用类的实例调用property** + + + +最后在重复一下@property的含义: +>它是一个装饰器,作用是把类中的方法变成属性来进行调用。 + + + + +## 变量作用域 + +主要有下面几种作用域 + +**`L: Local 函数内部作用域 -局部名字空间中的名字`** + +**`E: Enclosing 封闭作用域-函数内部与内嵌函数之间`** + +**`G: Global全局作用域 -内置和全局命名空间中的名字都属于全局作用域`** + +**`B: build-in 内置作用域`** + +顺序: L>E>G>B + +下面分别介绍这几个作用域 + +### Local作用域 +```python +# 这里所有的变量都在 local 作用域 +def foo(a, b): + c = 1 + d = a + b + c + return d + +``` + + +### Enclosing作用域 +```python +i = 0 + + +def a(): + i = 1 + + # 对函数b来说就是Enclosing 封闭作用域 + def b(): + i = 2 + + b() + print(i) + + +a() # 1 + +``` + +#### 关键字nolocal +修改Enclosing变量的问题,就需要使用nonlocal关键字。 +还是刚才的例子,增加一个nonlocal: + +```python +i = 0 + + +def a(): + i = 1 + + # 对函数b来说就是Enclosing 封闭作用域 + def b(): + nonlocal i + i = 2 + + b() + print(i) + + +a() # 2 + +``` +
+注意两点: + +>nonlocal不能代替global。 如果在上述代码的函数a中,就只能使用global。 + 因为,外层就是Global作用域了。 + +> **在多重嵌套中,nonlocal只会上溯一层; 而如果上一层没有,则会继续上溯**。 + +举个例子来说明: + +```python +def a(): + i = 1 + + def b(): + # i = 2 + def c(): + nonlocal i + i = 3 + + c() + print('b:', i) + + b() + print('a:', i) + + +a() +``` + +返回结果: +``` +b: 3 +a: 3 +``` + + +把上面的注释打开,对比这个: +```python +def a(): + i = 1 + + def b(): + i = 2 + + def c(): + nonlocal i + i = 3 + + c() + print('b:', i) + + b() + print('a:', i) + + +a() +``` +返回结果: +``` +b: 3 +a: 1 + +``` + + + +### Global作用域 + +看一个例子: +```python +i = 0 + + +def a(): + i = 1 + print('local:', i) + + +a() +print('global:', i) +``` + +返回结果: +``` +local: 1 +global: 0 +``` +
+ +**如果在一个局部(函数)内声明一个global变量,** + +**那么这个变量在局部的所有操作将对全局的变量有效** + +```python +i = 0 + + +def a(): + global i + i = 1 + print('local:', i) + + +a() +print('global:', i) +``` + +返回结果: +``` +local: 1 +global: 1 + +``` + + +```python +i = 0 + + +def a(): + i = 1 + print('local:', i) + # 可以查看局部作用域 + print(locals()) + # 查看全局作用域 + print(globals()) + + +a() +print('global:', i) +print("*" * 10) +print(locals()) +print(globals()) +``` + +
+ +**记住两点:** + +**`global永远打印全局的名字`** + +**`locals 输出什么 根据locals所在位置`** + + +### build-in作用域 + +Python内置模块的名字空间 + +```python +def list_length(a): + return len(a) + + +a = [1, 2, 3] +print(list_length(a)) # 3 + +# python3 写法 +import builtins +print(builtins.len) # + +``` + +### python命名空间规则 + +python使用命名空间记录变量 + +python中的命名空间就像是一个dict,key是变量的名字,value是变量的值。 + +python中,每个函数都有一个自己的命名空间,叫做local namespace,它记录了函数的变量。 + +python中,每个module有一个自己的命名空间,叫做global namespace,它记录了module的变量,包括 functions, classes 和其它imported modules,还有 module级别的 变量和常量。 + +还有一个build-in 命名空间,可以被任意模块访问,这个build-in命名空间中包含了build-in function 和 exceptions。 + +当python中的某段代码要访问一个变量x时,python会在所有的命名空间中寻找这个变量,查找的顺序为: +>local namespace - 指的是当前函数或者当前类方法。 +>如果在当前函数中找到了变量,停止搜索 + +>global namespace - 指的是当前的模块。如果在当前模块中找到了变量,停止搜索 + +>build-in namespace - 如果在之前两个namespace中都找不到变量x,python会假设x是build-in的函数或者变量。 +>如果x不是内置函数或者变量,python会报错NameError。 + + +对于闭包来说,这里有一点区别,如果在local namespace中找不到变量的话,还会去父函数的local namespace中找变量 + + +直接看例子: +```python +len = len([]) +print("befoe global len=", len) + + +def a(): + len = 1 + + def b(): + len = 2 + + def c(): + len = 3 + print('locals len:', len) + + c() + print('enclosing function locals len:', len) + + b() + print("after global len=", len) + + +a() +``` + + +运行结果: +``` +befoe global len= 0 +locals len: 3 +enclosing function locals len: 2 +after global len= 1 + +``` + +* from module import 和 import module + +使用import module时,module本身被引入,但是保存它原有的命名空间,所以我们需要使用module.name这种方式访问它的 函数和变量。 + +from module import这种方式,是将其它模块的函数或者变量引到当前的命名空间中 + +所以就不需要使用module.name这种方式访问其它的模块的方法了。 + +* if `__name__ `trick + +python中的module也是对象,所有的modules都有一个内置的属性`__name__`, + +模块的`__name__`属性的值取决于如何使用这个模块, + +如果import module,那么`__name__`属性的值是模块的名字 + +如果直接执行这个模块的话,那么`__name__`属性的值就是默认值`__main_`_。 + + + +## 可变数据类型和不可变数据类型 + + +### 可变数据类型:列表list和字典dict + +允许变量的值发生变化,即如果对变量进行append、+=等这种操作后,只是改变了变量的值,而不会新建一个对象,变量引用的对象的地址也不会变化 + +不过对于相同的值的不同对象,在内存中则会存在不同的对象,即每个对象都有自己的地址,相当于内存中对于同值的对象保存了多份,这里不存在引用计数,是实实在在的对象。 + +```python +a = [1, 2] +b = [1, 2] + +print("ID a=", id(a)) # ID a= 1236557349896 +print("ID b=", id(b)) # ID b= 1236556806216 + +print(a == b) # 判断值是否相等 True + +print(a is b) # 判断地址是否一样 False + +``` + +
+ +### 不可变数据类型:数值型、字符串型string和元组tuple + +不允许变量的值发生变化,如果改变了变量的值,相当于是新建了一个对象,而对于相同的值的对象,在内存中则只有一个对象(一个地址) + +```python +a = 3 +b = 3 + +print("ID a=", id(a)) # ID a= 1570991200 +print("ID b=", id(b)) # ID b= 1570991200 + +print(a == b) # 判断值是否相等 True + +print(a is b) # 判断地址是否一样 True + +``` + +
+再看一个例子: + +```python +# 列表是可变的 +a = [1, 2, 3, 4] +a[0] = 100 +a.insert(3, 300) + +print(a) + +# 字符串是不可变的 +s = "Hello world" +# s[0] = "z" +# print(s) # TypeError: 'str' object does not support item assignment + +# 字符串replace方法只是返回一个新字符串,并不改变原来的值 +print(s.replace('world', 'Mars')) +print(s) + +# 如果想改变字符串的值,可以用重新赋值的方法 +s = s.replace('world', 'Mars') +print(s) + +# 或者使用bytearray代替字符串 +c = bytearray('abcde', 'utf-8') +c[1:3] = b'12' +print(c) # bytearray(b'a12de') + +``` + +
+ +* 总结: + +可变数据类型: +>**list, dictionary, set**, numpy array, user defined objects + + +不可变数据类型 +> **integer, float,** long, complex, **string, tuple**, frozenset + +
+ +### 字符串不可变的原因 + +1.`列表可以通过以下的方法改变,而字符串不支持这样的变化` + +```python +a = [1, 2, 3, 4] + +# 此时a 和 b 指向同一块区域,改变 b 的值,a 也会同时改变 +b = a + +print("ID a=", id(a)) +print("ID b=", id(b)) + +b[0] = 100 +print(a) # [100, 2, 3, 4] +print(b) # [100, 2, 3, 4] + +``` + +2.`字符串与整数浮点数一样被认为是基本类型,而基本类型在Python中是不可变的。 + + + + +## 闭包 + +### 什么是闭包 +**一个闭包就是你调用了一个函数A,这个函数A返回了一个函数B给你。** + +这个返回的函数B就叫做闭包。你在调用函数A的时候传递的参数就是**自由变量**。 + +也就是说闭包是嵌套函数,内部函数调用外部函数的变量 + +例子: +```python +def outer(): + a = 1 + + def inner(): + print(a) + print(inner.__closure__) + + return inner + + +inn = outer() +inn() +``` + + +返回结果: +``` +1 +(, ) +``` + +
+这里的返回值 两个cell的意思是: + +**`内部函数引用了外部函数的变量, int和func类型`** + + +这里inner函数就是一个闭包,并且拥有该闭包持有的自由变量a + +总结创建一个闭包必须满足以下几点: + +1.必须有一个内嵌函数 + +2.内嵌函数必须引用外部函数中的变量 + +3.外部函数的返回值必须是内嵌函数 + + +### 为什么要使用闭包 + +两个特点: +* **封装** +* **代码复用** + + +```python +def set_passline(passline): + def cmp(val): + if val >= passline: + print("pass") + else: + print("failed") + + return cmp + + +f_100 = set_passline(60) +f_100(89) +f_100(59) +``` + +### 怎么使用闭包 + + +* 在python中很重要也很常见的一个使用场景就是装饰器 +`装饰器是特殊的闭包形式` + +```python +def decorator_func(func): + def wrapper(*args, **kwargs): + return func(*args, **kwargs) + + return wrapper + + +@decorator_func +def func(name): + print('my name is', name) + + +# 等价于 +# decorator_func(func) + +func("tony") +``` + +
+ +* 惰性求值 +惰性求值属性可以在对象被使用的时候才进行计算,这样可以减少一些资源消耗,提高程序效率 + +```python +def out_func(a): + def inner_func(): + return a + + return inner_func + + +func = out_func(1) +print(func()) # 1 +``` + +
+ +* 需要对某个函数的参数提前赋值的情况 + +当然在Python中已经有了很好的解决访问 functools.parial,但是用闭包也能实现 + +```python +def partial(**outer_kwargs): + def wrapper(func): + def inner(*args, **kwargs): + for k, v in outer_kwargs.items(): + kwargs[k] = v + return func(*args, **kwargs) + + return inner + + return wrapper + + +@partial(age=15) +def say(name=None, age=None): + print(name, age) + + +say(name="tony") + +# 当然用functools比这个简单多了 +# import functools +# functools.partial(say, age=15)(name='tony') +``` \ No newline at end of file diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\212\250\346\200\201\345\261\236\346\200\247\345\222\214\347\211\271\346\200\247.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\212\250\346\200\201\345\261\236\346\200\247\345\222\214\347\211\271\346\200\247.md" new file mode 100644 index 00000000..4560002a --- /dev/null +++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\212\250\346\200\201\345\261\236\346\200\247\345\222\214\347\211\271\346\200\247.md" @@ -0,0 +1,13 @@ +# 第19章 动态属性和特性 + + +这里主要讲: + +`_getattr__方法` + +`__new__方法` + +`@property` + + +可以查看以前章节内容 \ No newline at end of file diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\215\217\347\250\213-\350\241\245\345\205\205\345\206\205\345\256\271.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\215\217\347\250\213-\350\241\245\345\205\205\345\206\205\345\256\271.md" new file mode 100644 index 00000000..991094f1 --- /dev/null +++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\215\217\347\250\213-\350\241\245\345\205\205\345\206\205\345\256\271.md" @@ -0,0 +1,609 @@ +# 第16章 协程-补充内容 + + + +- [第16章 协程-补充内容](#第16章-协程-补充内容) + - [多进程VS多线程](#多进程vs多线程) + - [多进程](#多进程) + - [multiprocessing 模块](#multiprocessing-模块) + - [多进程通讯共享数据](#多进程通讯共享数据) + - [队列](#队列) + - [管道](#管道) + - [Managers](#managers) + - [进程池](#进程池) + - [多线程 GIL](#多线程-gil) + - [GIL是什么](#gil是什么) + - [GIL例子](#gil例子) + - [GIL释放](#gil释放) + - [为什么这样设计](#为什么这样设计) + - [能不能去掉GIL](#能不能去掉gil) + - [threading模块](#threading模块) + - [multiprocessing.dummy 模块](#multiprocessingdummy-模块) + - [两者关系](#两者关系) + - [多进程分析详解](#多进程分析详解) + + +## 多进程VS多线程 + + +### 多进程 + +**`多进程就是利用 CPU 的多核优势`**,在同一时间并行地执行多个任务,可以大大提高执行效率。 + + +#### multiprocessing 模块 + +例子: +```python +from multiprocessing import Process + + +def fun1(name): + print('测试%s多进程' % name) + + +if __name__ == '__main__': + + process_list = [] + for i in range(5): # 开启5个子进程执行fun1函数 + p = Process(target=fun1, args=('Python', )) # 实例化进程对象 + p.start() + process_list.append(p) + + for i in process_list: + p.join() + + print('结束测试') +``` + +返回结果: +``` +测试Python多进程 +测试Python多进程 +测试Python多进程 +测试Python多进程 +测试Python多进程 +结束测试 +``` + +
+ +类继承方式: +```python +from multiprocessing import Process + + +class MyProcess(Process): # 继承Process类 + def __init__(self, name): + super(MyProcess, self).__init__() + self.name = name + + def run(self): + print('测试%s多进程' % self.name) + + +if __name__ == '__main__': + process_list = [] + for i in range(5): # 开启5个子进程执行fun1函数 + p = MyProcess('Python') # 实例化进程对象 + p.start() + process_list.append(p) + + for i in process_list: + p.join() + + print('结束测试') +``` + +#### 多进程通讯共享数据 + +多进程之间可以通过管道,队列,Managers来实现通讯和共享数据 + + +##### 队列 + +```python +from multiprocessing import Process, Queue + + +def fun1(q, i): + print('子进程%s 开始put数据' % i) + q.put('我是%s 通过Queue通信' % i) + + +if __name__ == '__main__': + q = Queue() + + process_list = [] + for i in range(3): + # 注意args里面要把q对象传给我们要执行的方法,这样子进程才能和主进程用Queue来通信 + p = Process(target=fun1, args=( + q, + i, + )) + p.start() + process_list.append(p) + + for i in process_list: + p.join() + + print('主进程获取Queue数据') + print(q.get()) + print(q.get()) + print(q.get()) + print('结束测试') +``` + +运行结果: +``` +子进程0 开始put数据 +子进程1 开始put数据 +子进程2 开始put数据 +主进程获取Queue数据 +我是0 通过Queue通信 +我是1 通过Queue通信 +我是2 通过Queue通信 +结束测试 +``` + + + + +##### 管道 +```python +from multiprocessing import Process, Pipe + + +def fun1(conn): + print('子进程发送消息:') + conn.send('你好主进程') + print('子进程接受消息:') + print(conn.recv()) + conn.close() + + +if __name__ == '__main__': + conn1, conn2 = Pipe() # 关键点,pipe实例化生成一个双向管 + p = Process(target=fun1, args=(conn2, )) # conn2传给子进程 + p.start() + print('主进程接受消息:') + print(conn1.recv()) + print('主进程发送消息:') + conn1.send("你好子进程") + p.join() + print('结束测试') +``` + +返回结果: +``` +主进程接受消息: +子进程发送消息: +子进程接受消息: +你好主进程 +主进程发送消息: +你好子进程 +结束测试 +``` + + +##### Managers +Queue和Pipe只是实现了数据交互,并没实现数据共享,即一个进程去更改另一个进程的数据。那么要用到Managers + +```python +from multiprocessing import Process, Manager + + +def fun1(dic, lis, index): + + dic[index] = 'a' + dic['2'] = 'b' + lis.append(index) # [0,1,2,3,4,0,1,2,3,4,5,6,7,8,9] + #print(l) + + +if __name__ == '__main__': + with Manager() as manager: + dic = manager.dict() # 注意字典的声明方式,不能直接通过{}来定义 + l = manager.list(range(5)) # [0,1,2,3,4] + + process_list = [] + for i in range(10): + p = Process(target=fun1, args=(dic, l, i)) + p.start() + process_list.append(p) + + for res in process_list: + res.join() + print(dic) + print(l) +``` + +返回结果: +``` +{0: 'a', '2': 'b', 2: 'a', 1: 'a', 3: 'a', 5: 'a', 4: 'a', 9: 'a', 6: 'a', 8: 'a', 7: 'a'} +[0, 1, 2, 3, 4, 0, 2, 1, 3, 5, 4, 9, 6, 8, 7] +``` + +可以看到主进程定义了一个字典和一个列表,在子进程中,可以添加和修改字典的内容,在列表中插入新的数据,实现进程间的数据共享,即可以共同修改同一份数据 + + +#### 进程池 + +进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。就是固定有几个进程可以使用。 + +进程池中有两个方法: +apply:同步,一般不使用 +apply_async:异步 + +异步: +```python +from multiprocessing import Process, Pool +import os, time, random + + +def fun1(name): + print('Run task %s (%s)...' % (name, os.getpid())) + start = time.time() + time.sleep(random.random() * 3) + end = time.time() + print('Task %s runs %0.2f seconds.' % (name, (end - start))) + + +if __name__ == '__main__': + pool = Pool(5) # 创建一个5个进程的进程池 + + for i in range(10): + pool.apply_async(func=fun1, args=(i, )) + + pool.close() + pool.join() + print('结束测试') +``` + +运行结果是: +``` + +Run task 0 (13892)... +Run task 1 (15796)... +Run task 2 (6648)... +Run task 3 (9260)... +Run task 4 (1872)... +Task 3 runs 0.79 seconds. +Run task 5 (9260)... +Task 5 runs 0.10 seconds. +Run task 6 (9260)... +Task 2 runs 1.43 seconds. +Run task 7 (6648)... +Task 4 runs 2.44 seconds. +Task 1 runs 2.44 seconds. +Run task 8 (1872)... +Run task 9 (15796)... +Task 0 runs 2.58 seconds. +Task 9 runs 0.14 seconds. +Task 8 runs 0.88 seconds. +Task 6 runs 2.55 seconds. +Task 7 runs 2.07 seconds. +结束测试 +``` + +对Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close(),调用close()之后就不能继续添加新的Process了。 + + + +另外一个例子: +```python +from multiprocessing import Manager, Pool +import os, time + + +def reader(q): + print("reader 启动(%s),父进程为(%s)" % (os.getpid(), os.getpid())) + for i in range(q.qsize()): + print("reader 从Queue获取到消息:%s" % q.get(True)) + + +def writer(q): + print("writer 启动(%s),父进程为(%s)" % (os.getpid(), os.getpid())) + for i in "itcast": + q.put(i) + + +if __name__ == "__main__": + print("(%s)start" % os.getpid()) + + q = Manager().Queue() # 使用Manager中的Queue + po = Pool() + + po.apply_async(writer, (q, )) + time.sleep(1) + po.apply_async(reader, (q, )) + + po.close() + po.join() + + print("(%s)End" % os.getpid()) +``` + +运行结果是: +``` +(9516)start +writer 启动(10852),父进程为(10852) +reader 启动(13392),父进程为(13392) +reader 从Queue获取到消息:i +reader 从Queue获取到消息:t +reader 从Queue获取到消息:c +reader 从Queue获取到消息:a +reader 从Queue获取到消息:s +reader 从Queue获取到消息:t +(9516)End +``` + +同步: +```python +from multiprocessing import Process, Pool +import os, time, random + + +def fun1(name): + print('Run task %s (%s)...' % (name, os.getpid())) + start = time.time() + time.sleep(random.random() * 3) + end = time.time() + print('Task %s runs %0.2f seconds.' % (name, (end - start))) + + +if __name__ == '__main__': + pool = Pool(5) # 创建一个5个进程的进程池 + + for i in range(10): + pool.apply(func=fun1, args=(i, )) + + pool.close() + pool.join() + print('结束测试') +``` + +运行结果: +``` +Run task 0 (12996)... +Task 0 runs 0.96 seconds. +Run task 1 (5704)... +Task 1 runs 2.68 seconds. +Run task 2 (6808)... +Task 2 runs 1.31 seconds. +Run task 3 (3020)... +Task 3 runs 0.85 seconds. +Run task 4 (16980)... +Task 4 runs 2.35 seconds. +Run task 5 (12996)... +Task 5 runs 1.25 seconds. +Run task 6 (5704)... +Task 6 runs 2.43 seconds. +Run task 7 (6808)... +Task 7 runs 1.79 seconds. +Run task 8 (3020)... +Task 8 runs 0.72 seconds. +Run task 9 (16980)... +Task 9 runs 0.77 seconds. +结束测试 +``` + + +### 多线程 GIL + + +#### GIL是什么 + +GIL 是python的全局解释器锁,同一进程中假如有多个线程运行,一个线程在运行python程序的时候会霸占python解释器(加了一把锁即GIL),使该进程内的其他线程无法运行,等该线程运行完后其他线程才能运行。如果线程运行过程中遇到耗时操作,则解释器锁解开,使其他线程运行。所以在多线程中,线程的运行仍是有先后顺序的,并不是同时进行。 + +多进程中因为每个进程都能被系统分配资源,相当于每个进程有了一个python解释器,所以多进程可以实现多个进程的同时运行,缺点是进程系统资源开销大 + +#### GIL例子 + + +```python +import time + + +def decrement(n): + while n > 0: + n -= 1 + + +start = time.time() +decrement(100000000) +cost = time.time() - start +print(cost) # 4.968633651733398 + +``` + +使用threading多线程模块: +```python +import time +import threading + + +def decrement(n): + while n > 0: + n -= 1 + + +start = time.time() + +t1 = threading.Thread(target=decrement, args=[50000000]) +t2 = threading.Thread(target=decrement, args=[50000000]) + +t1.start() # 启动线程,执行任务 +t2.start() # 同上 + +t1.join() # 主线程阻塞,直到t1执行完成,主线程继续往后执行 +t2.join() # 同上 + +cost = time.time() - start +print(cost) # 4.755946159362793 +``` + + +按理来说,两个线程同时并行地运行在两个 CPU 之上,时间应该减半才对,现在不减反增。 + +是什么原因导致多线程不快反慢的呢? + +**原因就在于 GIL ,在` Cpython 解释器`(Python语言的主流解释器)中,有一把全局解释锁(Global Interpreter Lock)**, + +在解释器解释执行 Python 代码时,先要得到这把锁,意味着,任何时候只可能有一个线程在执行代码,其它线程要想获得 CPU 执行代码指令, + +就必须先获得这把锁,如果锁被其它线程占用了,那么该线程就只能等待,直到占有该锁的线程释放锁才有执行代码指令的可能。 + +因此,这也就是为什么两个线程一起执行反而更加慢的原因,因为同一时刻,只有一个线程在运行,其它线程只能等待,即使是多核CPU, + +也没办法让多个线程「并行」地同时执行代码,只能是交替执行,因为多线程涉及到上线文切换、锁机制处理(获取锁,释放锁等), + +所以,多线程执行不快反慢。 + + +#### GIL释放 +什么时候 GIL 被释放呢? + +当一个线程遇到 I/O 任务时,将释放GIL。 + +计算密集型(CPU-bound)线程执行 100 次解释器的计步(ticks)时(计步可粗略看作 Python 虚拟机的指令),也会释放 GIL。 + +可以通过 sys.setcheckinterval()设置计步长度,sys.getcheckinterval() 查看计步长度。相比单线程,这些多是多线程带来的额外开销 + +#### 为什么这样设计 +CPython 解释器为什么要这样设计? + +多线程是为了适应现代计算机硬件高速发展充分利用多核处理器的产物,通过多线程使得 CPU 资源可以被高效利用起来, + +但是多线程有个问题,怎么解决共享数据的同步、一致性问题,因为,对于多个线程访问共享数据时,可能有两个线程同时修改一个数据情况, + +如果没有合适的机制保证数据的一致性,那么程序最终导致异常, + +所以,Python之父就搞了个全局的线程锁,不管你数据有没有同步问题,反正一刀切,上个全局锁,保证数据安全。 + +这也就是多线程鸡肋的原因,因为它没有细粒度的控制数据的安全,而是用一种简单粗暴的方式来解决。 + + +#### 能不能去掉GIL +那么把 GIL 去掉可行吗? + +去掉GIL的 Python 在单线程条件下执行效率将近慢了2倍。 + + + +#### threading模块 + +```python +import threading +from time import sleep + + +class Mythead(threading.Thread): + def run(self): + for i in range(3): + sleep(1) # 挂起1s + # self.name是该线程的名字,默认会分配一个形如“Thread-N”的名字,其中 N 是一个十进制数 + print(u'线程:%s,索引%s' % (self.name, i)) + + +if __name__ == '__main__': + for i in range(3): + t = Mythead() + t.start() +``` + +运行结果: +``` +线程:Thread-2,索引0 +线程:Thread-3,索引0 +线程:Thread-4,索引0 +线程:Thread-2,索引1 +线程:Thread-3,索引1 +线程:Thread-4,索引1 +线程:Thread-2,索引2 +线程:Thread-3,索引2 +线程:Thread-4,索引2 +``` + +从上面的运行结果来看,多线程程序的执行顺序是确定的。 + + +```python +import threading +import logging +from time import sleep + +#配置logging,以“模式名/线程名字(10个字符)/信息”方式输出。 +logging.basicConfig(level=logging.DEBUG, + format='[%(levelname)s](%(threadName)-10s) %(message)s') + + +def foo(): + logging.debug('Starting...') + sleep(2) + logging.debug('Exiting...') + + +threading.Thread(name='mythread', target=foo).start() +threading.Thread(target=foo).start() + + +``` +返回结果是: +``` +[DEBUG](mythread ) Starting... +[DEBUG](Thread-2 ) Starting... +[DEBUG](mythread ) Exiting... +[DEBUG](Thread-2 ) Exiting... +``` + + +比较有趣的是, multiprocessing中提供了一个dummy module, 以multiprocessing API的方式提供了对threading模块的封装, + +这就意味着使用如下代码时: + +#### multiprocessing.dummy 模块 + + +multiprocessing.dummy 模块与 multiprocessing 模块的区别: +dummy 模块是多线程,而 multiprocessing 是多进程, api 都是通用的。 + +from multiprocessing.dummy import Pool, Process +Pool和Process的底层其实都是使用threading的实现(即ThreadPool和Thread), + +```python +# from multiprocessing import Pool +from multiprocessing.dummy import Pool as ThreadPool +import time +from urllib.request import urlopen + +urls = [ + 'http://www.baidu.com', + 'http://home.baidu.com/', + 'http://tieba.baidu.com/', +] +start = time.time() +results = map(urlopen, urls) +print('Normal:', time.time() - start) + +start2 = time.time() # 开8个 worker,没有参数时默认是 cpu 的核心数 +pool = ThreadPool(processes=8) +results2 = pool.map(urlopen, urls) +pool.close() +pool.join() + +print('Thread Pool:', time.time() - start2) +``` + + +### 两者关系 + +**多进程适合在CPU密集操作**(cpu操作指令比较多,如位多的的浮点运算)。 +**多线程适合在IO密性型操作**(读写数据操作比多的的,比如爬虫) + + +### 多进程分析详解 + + +[深入Python多进程编程基础](https://mp.weixin.qq.com/s/7cM0CmtklFQs0tlnw8mFGg) +[深入Python多进程通信原理与实战](https://mp.weixin.qq.com/s/qKU6z1PvBENTy8QxdZxCzg) \ No newline at end of file diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\215\217\347\250\213.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\215\217\347\250\213.md" new file mode 100644 index 00000000..730b9fed --- /dev/null +++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\215\217\347\250\213.md" @@ -0,0 +1,528 @@ +# 第16章 协程 + + + + +- [第16章 协程](#第16章-协程) + - [进程 VS 线程](#进程-vs-线程) + - [进程](#进程) + - [堆栈](#堆栈) + - [数据结构的堆栈](#数据结构的堆栈) + - [内存分配中的堆和栈](#内存分配中的堆和栈) + - [线程](#线程) + - [线程安全](#线程安全) + - [互斥锁](#互斥锁) + - [阻塞VS非阻塞](#阻塞vs非阻塞) + - [阻塞](#阻塞) + - [非阻塞](#非阻塞) + - [同步VS异步](#同步vs异步) + - [同步](#同步) + - [异步](#异步) + - [区别](#区别) + - [并发 VS并行](#并发-vs并行) + - [协程](#协程) + - [greenlet](#greenlet) + - [gevent](#gevent) + - [协程VS子程序](#协程vs子程序) + + + +## 进程 VS 线程 + + +进程和线程说明 + +* 是程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位 + +* 线程是进程的一个执行实例,是程序执行的最小单位,它是比进程更小的能独立运行的基本单位 + +* 一个进程可以创建和销毁多个线程,同一个进程中的多个线程可以并发执行 +* 一个程序至少有一个进程,一个进程至少有一个线程 + +可以联想 +打开迅雷.exe 就是打开进程 +多个任务 就是多个线程** + +### 进程 +进程:一个运行的程序(代码)就是一个进程,没有运行的代码叫程序,进程是系统资源分配的最小单位,进程拥有自己独立的内存空间,所有进程间数据不共享,开销大。 + + +进程: +1、操作系统进行资源分配和调度的基本单位,多个进程之间相互独立 +2、稳定性好,如果一个进程崩溃,不影响其他进程,但是进程消耗资源大,开启的进程数量有限制 + + +**进程拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度。** + +`怎么得出来这结论的?` + +#### 堆栈 + +什么是堆栈?在计算机中堆栈的概念分为:**数据结构的堆栈和内存分配中堆栈**。 + +
+ +#### 数据结构的堆栈 + +堆:堆可以被看成是一棵树,如:**堆排序**。在队列中,调度程序反复提取队列中第一个作业并运行,因为实际情况中某些时间较短的任务将等待很长时间才能结束,或者某些不短小,但具有重要性的作业,同样应当具有优先权。堆即为解决此类问题设计的一种数据结构。 + +栈:一种先进后出的数据结构。 + + + +####内存分配中的堆和栈 +内存分配中的堆和栈 +栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。 + +堆(操作系统): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。 + +堆栈缓存方式 +栈使用的是一级缓存, 他们通常都是被调用时处于存储空间中,调用完毕立即释放。 +堆则是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。 + +### 线程 +线程: cpu调度执行的最小单位,也叫执行路径,不能独立存在,依赖进程存在,一个进程至少有一个线程,叫主线程,而多个线程共享内存(数据共享,共享全局变量),从而极大地提高了程序的运行效率。 + + +线程: +1、CPU进行资源分配和调度的基本单位,线程是进程的一部分,是比进程更小的能独立运行的基本单位,一个进程下的多个线程可以共享该进程的所有资源 +2、如果IO操作密集,则可以多线程运行效率高,缺点是如果一个线程崩溃,都会造成进程的崩溃 + + +线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程亦由操作系统调度(标准线程是的)。 + + +### 线程安全 + +因为线程间共享进程中的全局变量,所以当其他线程改变了共享的变量时,可能会对本线程产生影响。 + +所谓线程安全的约束是指一个函数被多个并发线程反复调用时,要一直产生正确的结果。要保证线程安全,主要是通过加锁的方式保证共享变量的正确访问。 + + +换句话说, **线程安全 是在多线程的环境下, 线程安全能够保证多个线程同时执行时程序依旧运行正确, 而且要保证对于共享的数据,可以由多个线程存取,但是同一时刻只能有一个线程进行存取**. + +既然,多线程环境下必须存在资源的竞争,那么如何才能保证同一时刻只有一个线程对共享资源进行存取? + +`加锁可以保证存取操作的唯一性, 从而保证同一时刻只有一个线程对共享数据存取`. + +通常加锁也有2种不同的粒度的锁: + +1. fine-grained(所谓的**细粒度**), 那么程序员需要自行地加,解锁来保证线程安全 + +2. coarse-grained(所谓的**粗粒度**), 那么语言层面本身维护着一个全局的锁机制,用来保证线程安全 + + +前一种方式比较典型的是 java, Jython 等, 后一种方式比较典型的是 CPython (即Python). +至于Python中的全局锁机制,也即 `GIL (Global Interpreter Lock)` + + + +### 互斥锁 +每个对象都对应于一个可称为’互斥锁‘的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。 + +同一进程中的多线程之间是共享系统资源的, + +多个线程同时对一个对象进行操作,一个线程操作尚未结束,另一线程已经对其进行操作,导致最终结果出现错误,此时需要对被操作对象添加互斥锁,保证每个线程对该对象的操作都得到正确的结果。 + + + +## 阻塞VS非阻塞 + +### 阻塞 +阻塞状态指程序未得到所需计算资源时被挂起的状态。程序在等待某个操作完成期间,自身无法继续干别的事情,则称该程序在该操作上是阻塞的。 + + +常见的阻塞形式有:网络 I/O 阻塞、磁盘 I/O 阻塞、用户输入阻塞等。 + +阻塞是无处不在的,包括 CPU 切换上下文时,所有的进程都无法真正干事情,它们也会被阻塞。 +如果是多核 CPU 则正在执行上下文切换操作的核不可被利用。 + + +### 非阻塞 + +程序在等待某操作过程中,自身不被阻塞,可以继续运行干别的事情,则称该程序在该操作上是非阻塞的。 + +非阻塞并不是在任何程序级别、任何情况下都可以存在的。 + +仅当程序封装的级别可以囊括独立的子程序单元时,它才可能存在非阻塞状态。 + +非阻塞的存在是因为阻塞存在,正因为某个操作阻塞导致的耗时与效率低下,我们才要把它变成非阻塞的。 + + + + +## 同步VS异步 + +### 同步 + +多个任务之间有先后顺序执行,一个执行完下个才能执行 + + +### 异步 + +多个任务之间没有先后顺序,可以同时执行,有时候一个任务可能要在必要的时候获取另一个同时执行的任务的结果,这个就叫回调 + + + + +## 区别 + +同步异步相对于多任务而言,阻塞非阻塞相对于代码执行而言。 + + + + +## 并发 VS并行 + +1、多线程程序在单核上运行,就是并发 +多线程,看起来一起执行,GIL在同一时刻限制了多个线程只能有一个线程被CPU执行 + +2、多线程程序在多核上运行,就是并行 +多进程,多个进程在同一时刻可以占用多个CPU + +
+举个例子: +1、并发: 因为是在一个cpu上,比如有十个线程,每个线程进行10毫秒(进行轮询操作) +从人的角度看,好像这10个线程都在运行,但是从微观上看,在某个时间点看, +其实只有一个线程在执行,这就是并发 + +2.并行:因为在多个CPU上(比如10个CPU),比如有10个线程,每个线程执行10毫秒(各自在不同CPU上执行), +从人的角度看,好像这10个线程都在运行,但是从微观上看,在某个时间点看, +也同时有10个线程在执行,这就是并行 + + + + + +## 协程 +协程: 是一种用户态的轻量级线程,协程的调度完全由用户控制。 + +协程和线程一样共享堆,不共享栈,协程由程序员在协程的代码里显示调度。 + + +协程拥有自己的寄存器上下文和栈。 + +协程调度时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操中栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以**上下文的切换非常快。** + +简单点说协程是进程和线程的升级版,进程和线程都面临着内核态和用户态的切换问题而耗费许多切换时间,而协程就是用户自己控制切换的时机,不再需要陷入系统的内核态. + + +能够在一个线程中实现并发的概念 +能够规避在一些任务中的IO操作 +在任务的执行过程中,检测到IO就切换到其他任务 +协程在一个线程上 提高CPU的利用率 +协程相比与多线程优势 切换的效率更快 + +应用场景: +爬虫的例子,请求过程中的IO等待 + +```python +from gevent import monkey +monkey.patch_all() +import gevent +from urllib.request import urlopen + + +def get_url(url): + response = urlopen(url) + content = response.read().decode('utf-8') + return len(content) + + +g1 = gevent.spawn(get_url, 'http://www.baidu.com') +g2 = gevent.spawn(get_url, 'http://www.taobao.com') +g3 = gevent.spawn(get_url, 'http://www.hao123.com') +gevent.joinall([g1, g2, g3]) +print(g1.value) +print(g2.value) +print(g3.value) +``` + +运行结果: +``` +156535 +133524 +298483 +``` +可以看到结果是同时显示出来的 + +
+ + +**Python里最常见的yield就是协程的思想** + + +举个例子: +```python +import threading + + +class Thread(threading.Thread): + def __init__(self, name): + threading.Thread.__init__(self) + self.name = name + + def run(self): + for i in range(10): + print(self.name) + + +threadA = Thread("A") +threadB = Thread("B") + +threadA.start() +threadB.start() + +``` + + +返回结果是: +``` +A +A +A +B +B +B +B +B +B +B +B +B +BA +A +A +A +A + +A +A +``` +那么总共发生了 20 次切换:主线程 -> A -> B -> A -> B … + +使用协程方式: + +```python +import greenlet + + +def run(name, nextGreenlets): + for i in range(10): + print(name) + if nextGreenlets: + nextGreenlets.pop(0).switch(chr(ord(name) + 1), nextGreenlets) + + +greenletA = greenlet.greenlet(run) +greenletB = greenlet.greenlet(run) + +greenletA.switch('A', [greenletB]) + +``` + + +返回结果: +``` +A +B +B +B +B +B +B +B +B +B +B + +``` +此时发生了 2 次切换:主协程 -> A -> B + + +yield例子: +```python +def consumer(): + print(123) + while True: + x = yield + print("处理了数据:", x) + + +def producer(): + c = consumer() + next(c) + for i in range(10): + print("生产了数据:", i) + c.send(i) + + +producer() +``` + +### greenlet +greenlet 是一个轻量级的协程实现,使用的方法简单而清晰。创建 greenlet 实例执行方法,在方法内部可通过 **`greenlet.switch()`** 切换至其他 greenlet 实例进行执行 + +例子: +```python +from greenlet import greenlet + + +def eat(): + print("eating start") + g2.switch() + print("eating end") + g2.switch() + + +def play(): + print("playing start") + g1.switch() + print("playing end") + + +g1 = greenlet(eat) +g2 = greenlet(play) +g1.switch() + +``` + +运行结果: +``` +eating start +playing start +eating end +playing end + +``` + + +### gevent + +gevent 基于 greenlet 库进行了封装,基于 libev 和 libuv 提供了高效的同步API。 + +对 greenlet 在业务开发中的不便之处,提供了很好的解决方案: + +对于 greenlet 实例的管理,不使用树形关系进行组织,隐藏不必要的复杂性 + +因为协程还是运行在一个OS进程中,所以协程不能跑阻塞任务,否则就要将整个OS进程阻塞住了。 + +采用 monkey patching 与第三方库协作,将阻塞任务变成非阻塞,也不需要手工通过greenlet.switch() 切换; + + +例子: +```python +import gevent + + +def foo(): + print('Running in foo') + gevent.sleep(0) + print('Explicit context switch to foo again') + + +def bar(): + print('Explicit context to bar') + gevent.sleep(0) + print('Implicit context switch back to bar') + + +gevent.joinall([ + gevent.spawn(foo), + gevent.spawn(bar), +]) + +``` + +运行结果: +``` +Running in foo +Explicit context to bar +Explicit context switch to foo again +Implicit context switch back to bar +``` + +通过这个例子可以看到 两个上下文通过调用 gevent.sleep()来互相切换。 + + +再看一个例子: +```python +import gevent +import random + + +def task(pid): + """ + Some non-deterministic task + """ + gevent.sleep(random.randint(0, 2) * 0.001) + print('Task', pid, 'done') + + +def synchronous(): + for i in range(1, 10): + task(i) + + +def asynchronous(): + threads = [gevent.spawn(task, i) for i in range(10)] + gevent.joinall(threads) + + +print('Synchronous:') +synchronous() + +print('Asynchronous:') +asynchronous() + +``` + +运行结果是: +``` +Synchronous: +Task 1 done +Task 2 done +Task 3 done +Task 4 done +Task 5 done +Task 6 done +Task 7 done +Task 8 done +Task 9 done +Asynchronous: +Task 0 done +Task 4 done +Task 5 done +Task 1 done +Task 3 done +Task 7 done +Task 2 done +Task 6 done +Task 8 done +Task 9 done +``` + + +在同步的情况下,任务是按顺序执行的,在执行各个任务的时候会阻塞主线程。 + +而gevent.spawn 的重要功能就是封装了greenlet里面的函数。 +初始化的greenlet放在了threads这个list里面,被传递给了 gevent.joinall 这个函数,它会阻塞当前的程序来执行所有的greenlet。 + +在异步执行的情况下,所有任务的执行顺序是完全随机的。 +每一个greenlet的都不会阻塞其他greenlet的执行。 + + + + + + + +## 协程VS子程序 + +`协程是为了实现单线程的i/o密集型的任务并发(通过event loop) +如果子程序的话,永远是串行的` + +什么是子程序? + +协程比子程序的优势在哪里? + diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\217\257\350\277\255\344\273\243\347\232\204\345\257\271\350\261\241-\350\277\255\344\273\243\345\231\250\345\222\214\347\224\237\346\210\220\345\231\250.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\217\257\350\277\255\344\273\243\347\232\204\345\257\271\350\261\241-\350\277\255\344\273\243\345\231\250\345\222\214\347\224\237\346\210\220\345\231\250.md" new file mode 100644 index 00000000..3c957db9 --- /dev/null +++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\217\257\350\277\255\344\273\243\347\232\204\345\257\271\350\261\241-\350\277\255\344\273\243\345\231\250\345\222\214\347\224\237\346\210\220\345\231\250.md" @@ -0,0 +1,472 @@ +# 第14章 可迭代的对象、迭代器和生成器 + + +- [第14章 可迭代的对象、迭代器和生成器](#第14章-可迭代的对象迭代器和生成器) + - [可迭代对象](#可迭代对象) + - [迭代器](#迭代器) + - [迭代器是什么](#迭代器是什么) + - [迭代器内存消耗检测](#迭代器内存消耗检测) + - [迭代器使用](#迭代器使用) + - [生成器](#生成器) + - [yield生成器](#yield生成器) + - [生成器执行过程](#生成器执行过程) + - [生成器创建](#生成器创建) + - [生成器判断](#生成器判断) + - [yield与协程](#yield与协程) + + + +## 可迭代对象 + +举个例子: + +```python +import re + +re_word = re.compile(r'\w+') + + +class Sentence(object): + def __init__(self, text): + self.text = text + self.word = re_word.findall(text) + + def __getitem__(self, item): + return self.word[item] + + def __len__(self): + return len(self.word) + + def __str__(self): + return 'Sentence(%s)' % self.word + + +if __name__ == "__main__": + + s = Sentence("Today is Tuesday") + print(s) + + for word in s: + print(word) + +``` + +返回结果: +``` +Sentence(['Today', 'is', 'Tuesday']) +Today +is +Tuesday +``` + +我们知道一个对象可以迭代是因为实现了`__iter__`方法, +但是在Sentence中并没有实现`__iter__`方法。那为什么可以迭代呢。 + +原因在于在python中实现了iter和getitem的都是可迭代的。首先会检查是否实现了iter方法,如果实现了则调用,如果没有但是实现了`__getitem__`方法。 + +Python就会创建一个迭代器。 + +尝试按照顺序获取元素。如果尝试失败则会抛出typeerror异常,提示object is not iterable. + +因此: + +> 如果对象实现了能返回迭代器的`__iter__`方法,那么对象就是可迭代的。 +> +>如果实现了`__getitem__`方法,而且其参数是从零开始的索引。 + +这种对象也可以迭代。 + + +我们用`__iter__`方法来改造之前的Sentence。 +在`__iter__`中返回一个可迭代对象iter(self.word)。 +当执行for word in s的时候就会调用`__iter__`方法 + +```python +import re + +re_word = re.compile(r'\w+') + + +class Sentence(object): + def __init__(self, text): + self.text = text + self.word = re_word.findall(text) + + def __iter__(self): + return iter(self.word) + + def __len__(self): + return len(self.word) + + def __str__(self): + return 'Sentence(%s)' % self.word + + +if __name__ == "__main__": + + s = Sentence("Today is Tuesday") + print(s) + + for word in s: + print(word) + +``` + + + +再来看下next, next的作用是返回下一个元素,如果没有元素了,抛出stopIteration异常。 + +我们来看下next的使用方法。如果要遍历一个字符串,最简单的方法如下: + +```python +s = 'abc' + +for char in s: + print(char) + +``` + +如果不用for方法,代码需要修改如下: +```python +s = 'abc' + +it = iter(s) + +while True: + try: + print(next(it)) + except StopIteration: + del it + break +``` + +首先将s变成一个iter对象,然后不断调用next获取下一个字符,如果没有字符了,则会抛出StopIteration异常释放对it的引用 + +```python +s = 'abc' + +it = iter(s) + +print(next(it)) +print(next(it)) +print(next(it)) +print(next(it)) # StopIteration + + +``` + +因为只有3个字符,但是调用了4次it.next()导致已经找不到字符因此抛出异常。 +
+ +总结: +> 可迭代对象:实现了`__iter__`方法,就是可迭代的,可以返回自身作为迭代器。 +> 也可以返回其他一个可迭代对象 + + +## 迭代器 + +### 迭代器是什么 + +迭代器:在Python2中实现了next方法,在python3中实现了`__next__`方法。 + +首先要让x通过iter变成一个可迭代对象,然后使用迭代器来调用元素 + +使用迭代器好处就是:**每次只从对象中读取一条数据,不会造成内存的过大开销。** + + +![Alt text](https://raw.githubusercontent.com/Syncma/Figurebed/master/img/迭代器.png) + + + +### 迭代器内存消耗检测 + +可以看看它的内存占用: + +使用python2.7.15+: + +```python +import sys +i = iter(range(1000000)) +print sys.getsizeof(i) + + +r = range(1000000) +print sys.getsizeof(r) + + +y = xrange(1000000) # 注意 xrange跟range的区别: xrange是range的迭代器的表达方式 +print sys.getsizeof(y) +``` + +结果返回: +``` +56 +8000064 +32 +``` + + +使用python 3.6+ +```python +import sys +i = iter(range(1000000)) +print (sys.getsizeof(i)) + + +r = range(1000000) +print (sys.getsizeof(r)) + +``` + +返回结果: +``` +32 +48 +``` + + +这里有个问题:为什么在python2跟Python3的运行结果相差这么大呢? +**这是因为python3内部机制已经将range转换成了一个迭代器了。** + +这里可以看的出来适合大的数据,比如好几个G的数据, 使用了迭代器 内存使用大幅度减少,这是迭代器最大的优点。 + + +总结下: +我们简单说迭代器就是访问集合元素,迭代器就是有一个next()方法的对象,而不是通过索引来计数的。 + +### 迭代器使用 + +那么我们怎么能访问迭代器里面的元素呢? + +迭代器有两个方法 ,分别是**iter()和next()**方法 + +这两个方法是迭代器最基本的方法 +一个用来获得迭代器对象,一个用来获取容器中的下一个元素。 + +**itertools是python提供的非常高效创建与使用迭代器的模块** + +```python +from itertools import chain +test = chain.from_iterable('ABCDEFG') +# print(test) +# print(dir(test)) # 查看类具体方法 + +print(test.__next__()) # 'A' +print(test.__next__()) # 'B' +print(test.__next__()) # 'C' + +test2 = chain('AB', 'CDE', 'F') +print(list(test2)) # ['A', 'B', 'C', 'D', 'E', 'F'] +``` + + +我们知道迭代器是不支持索引的,原因就是索引需实现明元素确占用的内存地址,而迭代器是用到元素的时候才会创建。如下: + +```python +i = iter(range(3)) # 创建迭代器 +# i.index(2) # 获取元素为2的索引 +# AttributeError: 'range_iterator' object has no attribute 'index' + +# 列表 +l = range(3) +print(l.index(2)) # 获取索引2 + +``` + +这个时候可以使用内建函数enumerate(),这个函数很重要。 + +```python +for i, j in enumerate(iter(['A', 'B', 'C'])): + # for i, j in enumerate(['A', 'B', 'C']): + print(i, j) + +``` + +运行结果返回: +``` +0 A +1 B +2 C +``` + +
+ +可以看下这个函数的源码是怎么写的 + +```python +class enumerate(Iterator[Tuple[int, _T]], Generic[_T]): + def __init__(self, iterable: Iterable[_T], start: int = ...) -> None: ... + def __iter__(self) -> Iterator[Tuple[int, _T]]: ... + if sys.version_info >= (3,): + def __next__(self) -> Tuple[int, _T]: ... + else: + def next(self) -> Tuple[int, _T]: .. +``` + +## 生成器 +生成器是迭代器,但你只能遍历它一次(iterate over them once) + +因为生成器并没有将所有值放入内存中,而是实时地生成这些值 + +```python +mygenerator = (x * x for x in range(3)) # 生成器 +# mygenerator = [x * x for x in range(3)] # 列表 +for i in mygenerator: + print("i=", i) + +for i in mygenerator: + print("New=", i) +``` + +运行结果: +``` +i= 0 +i= 1 +i= 4 +``` + +注意,你不能执行for i in mygenerator第二次,因为每个生成器只能被使用一次 + + +### yield生成器 + +在Python中,使用生成器可以很方便的支持迭代器协议。 + +生成器通过生成器函数产生,生成器函数可以通过常规的def语句来定义,但是不用return返回, +而是用yield一次返回一个结果,在每个结果之间挂起和继续它们的状态,来自动实现迭代协议。 + +也就是说,yield是一个语法糖,内部实现支持了迭代器协议 + +同时yield内部是一个状态机,维护着挂起和继续的状态。 + + +下面看看生成器的使用: +```python +def Zrange(n): + i = 0 + while i < n: + yield i + i += 1 + + +zrange = Zrange(3) +print(zrange) # +print([i for i in zrange]) # [0, 1, 2] +``` + +在这个例子中,定义了一个生成器函数,函数返回一个生成器对象,然后就可以通过for语句进行迭代访问了。 + +其实,生成器函数返回生成器的迭代器。 “生成器的迭代器”这个术语通常被称作”生成器”。 +要注意的是生成器就是一类特殊的迭代器。作为一个迭代器,生成器必须要定义一些方法,其中一个就是next()。 + +如同迭代器一样,我们可以使用next()函数来获取下一个值。 + + +### 生成器执行过程 + +例子: +```python +def Zrange(n): + print("begin of Zrange") + i = 0 + while i < n: + print("before yield:", i) + yield i + i += 1 + print("after yield:", i) + + print("begin of Zrange") + + +zrange = Zrange(3) +# print(zrange) + +print(zrange.__next__()) +print("-" * 10) +print(zrange.__next__()) +print("-" * 10) +print(zrange.__next__()) +# print(zrange.__next__()) # StopIteration +``` + +运行结果: +``` +begin of Zrange +before yield: 0 +0 +---------- +after yield: 1 +before yield: 1 +1 +---------- +after yield: 2 +before yield: 2 +2 +``` + + +通过结果可以看到: +* 当调用生成器函数的时候,函数只是返回了一个生成器对象,并没有 执行。 +* 当next()方法第一次被调用的时候,生成器函数才开始执行,执行到yield语句处停止 +* next()方法的返回值就是yield语句处的参数(yielded value) +* 当继续调用next()方法的时候,函数将接着上一次停止的yield语句处继续执行,并到下一个yield处停止;如果后面没有yield就抛出StopIteration异常 + + + +总结:生成器是迭代器的一种,但功能方法比迭代器多 + + + + +### 生成器创建 + + 生成器的创建可以使用yield关键字, 也可以使用生成器表达式 + +```python +(x*2 for i in range(10)) +``` + + +### 生成器判断 + +判断是否为生成器函数可用isgeneratorfunction, 判断是否为生成器对象可用isgenerator + +```python +from inspect import isgeneratorfunction, isgenerator + +g = (i for i in range(3)) +print(isgenerator(g)) # True + + +def demo(): + yield 1 + + +print(isgenerator(demo())) # True +print(isgeneratorfunction(demo())) # False +print(isgeneratorfunction(demo)) # True +``` + + +### yield与协程 + +```python +def coroutine(): + print("coroutine start...") + result = None + while True: + s = yield result + result = 'result:{}'.format(s) + + +c = coroutine() +c.send(None) # coroutine start... + +print(c.send("first")) # result:first +print(c.send("second")) # result:second + +c.close() + +# c.send("hello") # StopIteration +``` + diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210.md" new file mode 100644 index 00000000..a74b204e --- /dev/null +++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\255\227\345\205\270\345\222\214\351\233\206\345\220\210.md" @@ -0,0 +1,424 @@ +# 第3章 字典和集合 + + + +- [第3章 字典和集合](#第3章-字典和集合) + - [字典推导式](#字典推导式) + - [setdefault用法](#setdefault用法) + - [defaultdict用法](#defaultdict用法) + - [字典的变种](#字典的变种) + - [collections.Counter 用法](#collectionscounter-用法) + - [集合](#集合) + - [关于字典,列表,集合运行效率的对比](#关于字典列表集合运行效率的对比) + + + +## 字典推导式 + +可能你见过列表推导时,却没有见过字典推导式,在2.7中才加入的: + +d = {key: value for (key, value) in iterable} + +举个例子说明: +```python +iterable = [(1, 'hello'), (2, 'world'), (3, 'happy')] +d = {key: value for (key, value) in iterable} + +print(d) # {1: 'hello', 2: 'world', 3: 'happy'} + +``` +
+ +## setdefault用法 + +使用setdefault处理找不到的键 + +简单例子: +```python +d = {'a': 1, 'b': 2} +d['c'] = 3 + +# 若原来没有,设置,否则原值不变 +d.setdefault('c', 4) + +print(d) # {'a': 1, 'b': 2, 'c': 3} + +``` + +字典也有get方法,如下所示: +```python +d = {'a': 1, 'b': 2} +# d['c'] = 3 + + +# 使用get 方法设置的默认值不会改变原字典 +a = d.get('c', 4) +print(a) # 4 + +print(d) # {'a': 1, 'b': 2} + +``` + +`可以看到两者的区别:` +> get 方法设置的默认值不会改变原字典, 而setdefault设置的默认值会改变原字典的值。 + + +再看一个例子: + +```python +some_dict = {'abc': 'a', 'cdf': 'b', 'gh': 'a', 'fh': 'g', 'hfz': 'g'} +new_dict = {} + +for k, v in some_dict.items(): + new_dict.setdefault(v, []).append(k) + +print(new_dict) # {'a': ['abc', 'gh'], 'b': ['cdf'], 'g': ['fh', 'hfz']} + +``` + + +
+ +## defaultdict用法 + +所有的映射类型在处理找不到的键的时候,都会牵扯到`__missing__`方法。这也是这个方法称作“missing”的原因。虽然基类dict并没有定义这个方法,但是dict是知道有这么个东西存在的. + +举个例子来说: +```python +class Dict(dict): + def __missing__(self, key): + self[key] = [] + return self[key] + + +dct = Dict() +print(dct["foo"]) # [] + +dct["foo"].append(1) +dct["foo"].append(2) +print(dct["foo"]) # [1,2] + +``` +defaultdict是属于collections 模块下的一个工厂函数,用于构建字典对象, +接收一个函数(可调用)对象为作为参数。 +参数返回的类型是什么,key对应value就是什么类型 + +```python + +from collections import defaultdict + +# 参数为 list,它就会构建一个默认value为list的字典, +# 例如result['a']的值默认就是list对象。 +dct = defaultdict(list) + +print(dct) # defaultdict(, {}) + +print(dct["a"]) # [] +dct["a"].append("hello") +print(dct) # defaultdict(, {'a': ['hello']}) + +``` + +看一个例子: +```python +from collections import defaultdict + +result = defaultdict(list) + +data = [("p", 1), ("p", 2), ("p", 3), ("h", 1), ("h", 2), ("h", 3)] + +for (key, value) in data: + result[key].append(value) + +print(result) # defaultdict(, {'p': [1, 2, 3], 'h': [1, 2, 3]}) +print(dict(result)) # {'p': [1, 2, 3], 'h': [1, 2, 3]} + +``` + +再看一个例子: +```python +# 字典的统计 +names = ['leo', 'sam', 'jack', 'peter', 'joe', 'susan'] +# 要变成这样:{3: ['leo', 'sam', 'joe'], 4: ['jack'], 5: ['peter', 'susan']} + +# bad +nums = [len(n) for n in names] +contain = {} +for k1, k2 in zip(nums, names): + print(k1, k2) + if k1 not in contain: + contain[k1] = [k2] + else: + contain[k1].append(k2) +print(contain) + +print("#" * 10) + +# better +from collections import defaultdict +d = defaultdict(list) +for name in names: + key = len(name) + d[key].append(name) + +print(dict(d)) +``` +
+ +default性能效率检测 + +```python +import timeit +from collections import defaultdict + + +def way1(): + d = {} + chars = ['a', 'b', 'c', 'c', 'a', 'a'] * 10000 + for c in chars: + if c in d: + d[c] += 1 + else: + d[c] = 1 + return d + + +def way2(): + d = defaultdict(int) + chars = ['a', 'b', 'c', 'c', 'a', 'a'] * 10000 + for c in chars: + d[c] += 1 + + return d + + +if __name__ == "__main__": + t1 = timeit.timeit("way1", setup="from __main__ import way1", number=10) + print(t1) # 6e-07(表示6x10^(-7),也就是6x0.0000001,如果写成6e07表示6x10^7) + + t2 = timeit.timeit("way2", setup="from __main__ import way2", number=10) + print(t2) # 4.000000000000097e-07 + +``` + + +> 对于字典的使用,我们要学会用**defaultdict**来代替, +> 一来是因为有缺省值非常安全,如果访问不存在的key,不会报错; +> 二来是Pyhon性能会大幅提高。 +仅仅换了字典数据结构,性能就大幅的提高了很多。 + +
+ +## 字典的变种 + + +python字典 dict 默认是无序的,要有序请用collection中的orderdict + +python 2 代码: +```python +import collections + +d = {} +d['name'] = 'zhf' +d['age'] = 33 +d['city'] = 'chengdu' + + + +for k, v in d.items(): + print k, v + +a = collections.OrderedDict() +a['name'] = 'zhf' +a['age'] = 33 +a['city'] = 'chengdu' + + +print "*" * 10 +for k, v in a.items(): + print k,v + +``` +返回结果: +``` +city chengdu +age 33 +name zhf +********** +name zhf +age 33 +city chengdu + +``` + +python3.6.7 代码: +```python +import collections + +d = {} +d['name'] = 'zhf' +d['age'] = 33 +d['city'] = 'chengdu' + +for k, v in d.items(): + print(k, v) + +a = collections.OrderedDict() +a['name'] = 'zhf' +a['age'] = 33 +a['city'] = 'chengdu' + +print("*" * 10) +for k, v in a.items(): + print(k, v) + +``` + +运行结果: +``` +name zhf +age 33 +city chengdu +********** +name zhf +age 33 +city chengdu + +``` + +> `可以看到3.6版本的Python已经使得dict变得紧凑以及关键字变得有序` + + +
+ +## collections.Counter 用法 + +简单例子: +```python +import collections + +string = "aaabbbc" + +ct = collections.Counter(string) +print(ct) # Counter({'a': 3, 'b': 3, 'c': 1}) +print(dict(ct)) # {'a': 3, 'b': 3, 'c': 1} + +ct.update('abcdef') +print(dict(ct)) # {'a': 4, 'b': 4, 'c': 2, 'd': 1, 'e': 1, 'f': 1} + +``` +
+ +## 集合 + +集合的本质是许多唯一对象的聚集。因此集合可以去掉重复的元素 + + +```python +d = ['abc', 'def', 'abc', 'def'] + +s = set(d) + +print(s) # {'def', 'abc'} + +``` + +假设有2个集合a和b,需要统计集合a的哪些元素在集合b中也出现。 +如果不使用集合,代码只能写成下面的形式: +```python +a = ['abc', 'def', 'aaa'] +b = ['abc', 'bbb', 'ccc', 'def'] + +for n in a: + if n in b: + print(n) +``` +但是如果使用集合,则不用这么麻烦。 +在集合中。a|b返回的是合集,a&b返回的是交集。 +a-b返回的是差集。**-差集是指属于A但不属于B的结合** + +```python +a = ['abc', 'def', 'aaa'] +b = ['abc', 'bbb', 'ccc', 'def'] + +print(set(a) & set(b)) +print(set(a) | set(b)) +print(set(a) - set(b)) # 相等于 print(set(a).difference(b)) +``` + +结果返回: +``` +{'def', 'abc'} +{'aaa', 'def', 'ccc', 'abc', 'bbb'} +{'aaa'} +``` +
+ +## 关于字典,列表,集合运行效率的对比 + +列表交集代码: + +```python +from time import time + +t = time() +lista = [1, 2, 3, 4, 5, 6, 7, 8, 9, 13, 34, 53, 42, 44] +listb = [2, 4, 6, 9, 23] +intersection = [] + +for i in range(1000000): + for a in lista: + for b in listb: + if a == b: + intersection.append(a) + +print("total run time:%s" % (time() - t)) +# total run time:5.015027046203613 +``` + +集合交集代码: +```python +from time import time +t = time() + +lista = [1, 2, 3, 4, 5, 6, 7, 8, 9, 13, 34, 53, 42, 44] +listb = [2, 4, 6, 9, 23] +intersection = [] + +for i in range(1000000): + list(set(lista) & set(listb)) + +print("total run time:%s" % (time() - t)) +# total run time:1.0969467163085938 +``` + + +字典代码: +```python +from time import time + +t = time() +list = [ + 'a', 'b', 'is', 'python', 'jason', 'hello', 'hill', 'with', 'phone', + 'test', 'dfdf', 'apple', 'pddf', 'ind', 'basic', 'none', 'baecr', 'var', + 'bana', 'dd', 'wrd' +] +# list = dict.fromkeys(list, True) # total run time:0.9107456207275391 +print(list) +filter = [] +for i in range(1000000): + for find in ['is', 'hat', 'new', 'list', 'old', '.']: + if find not in list: + filter.append(find) +print("total run time:%s" % (time() - t)) +# total run time:2.0197031497955322 +``` + + +Python 字典中使用了 hash table,因此查找操作的复杂度为 O(1),而 list 实际是个数组,在 list 中,查找需要遍历整个 list,其复杂度为 O(n),因此对成员的查找访问等操作字典要比 list 更快。 + +因此在需要多数据成员进行频繁的查找或者访问的时候,`使用 dict 而不是 list 是一个较好的选择` + +> 一个良好的算法能够对性能起到关键作用,因此性能改进的首要点是对算法的改进。 +> 在算法的时间复杂度排序上依次是: +**O(1) -> O(lg n) -> O(n lg n) -> O(n^2) -> O(n^3) -> O(n^k) -> O(k^n) -> O(n!)** \ No newline at end of file diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\257\271\350\261\241\345\274\225\347\224\250-\345\217\257\345\217\230\346\200\247\345\222\214\345\236\203\345\234\276\345\233\236\346\224\266.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\257\271\350\261\241\345\274\225\347\224\250-\345\217\257\345\217\230\346\200\247\345\222\214\345\236\203\345\234\276\345\233\236\346\224\266.md" new file mode 100644 index 00000000..afdb5cfc --- /dev/null +++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\257\271\350\261\241\345\274\225\347\224\250-\345\217\257\345\217\230\346\200\247\345\222\214\345\236\203\345\234\276\345\233\236\346\224\266.md" @@ -0,0 +1,104 @@ +# 第8章-对象引用、可变性和垃圾回收 + + + + +- [第8章-对象引用、可变性和垃圾回收](#%e7%ac%ac8%e7%ab%a0-%e5%af%b9%e8%b1%a1%e5%bc%95%e7%94%a8%e5%8f%af%e5%8f%98%e6%80%a7%e5%92%8c%e5%9e%83%e5%9c%be%e5%9b%9e%e6%94%b6) + - [浅拷贝](#%e6%b5%85%e6%8b%b7%e8%b4%9d) + - [深拷贝](#%e6%b7%b1%e6%8b%b7%e8%b4%9d) + - [两者区别](#%e4%b8%a4%e8%80%85%e5%8c%ba%e5%88%ab) + + + +## 浅拷贝 + +**`copy 浅复制,不会拷贝其子对象,修改子对象,将受影响`** + +```python +import copy +a = [1, 2, 3] +b = copy.copy(a) + +print("id a=", id(a)) # id a= 1669348705224 +print("id b=", id(b)) # id b= 1669348161608 + +print("id a 0=", id(a[0])) # id a 0= 1586588704 +print("id b 0=", id(b[0])) # id b 0= 1586588704 + +a[0] = "hello" +print("new a=", a) # new a= ['hello', 2, 3] +print("new b=", b) # new b= [1, 2, 3] + +b[0] = "world" +print("new 2 a=", a) # new a= ['hello', 2, 3] +print("new 2 b=", b) # new 2 b= ['world', 2, 3] + + +``` + +
+ +## 深拷贝 +**`deepcopy 深复制,将拷贝其子对象,修改子对象,将不受影响`** + +```python +import copy +a = [1, 2, 3] +b = copy.deepcopy(a) + +print("id a=", id(a)) # id a= 2760920581064 +print("id b=", id(b)) # id b= 2760920037448 + +print("id a 0=", id(a[0])) # id a 0= 1586588704 +print("id b 0=", id(b[0])) # id b 0= 1586588704 + +a[0] = "hello" +print("new a=", a) # new a= ['hello', 2, 3] +print("new b=", b) # new b= [1, 2, 3] + +b[0] = "world" +print("new 2 a=", a) # new a= ['hello', 2, 3] +print("new 2 b=", b) # new 2 b= ['world', 2, 3] +``` + +## 两者区别 + +发现**对于不可变对象**,比如整数、字符串、元组、还有由这些不可变对象组成的集合对象,**浅拷贝和深拷贝没有区别,都是拷贝一个新对象** + + +**`两者的区别在于拷贝组合对象`**,比如列表中还有列表,字典中还有字典或者列表的情况时,浅拷贝只拷贝了外面的壳子,里面的元素并没有拷贝,而深拷贝则是把壳子和里面的元素都拷贝了一份新的。 + +看一个例子: + +```python +import copy + +a = [0, [1, 2], 3] +b = copy.copy(a) +c = copy.deepcopy(a) + +c[1][0] = "hello" +print("a=", a) # a= [0, [1, 2], 3] +print("c=", c) # c= [0, ['hello', 2], 3] + +b[1][0] = "world" + +print("new a=", a) # new a= [0, ['world', 2], 3] +print("new b=", b) # new b= [0, ['world', 2], 3] +``` + +
+ +**小提示:** + +> 可以利用[python在线调试](http://www.pythontutor.com/visualize.html#mode=display)进行相关调试分析 + + + + + + + + + + diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\261\236\346\200\247\346\217\217\350\277\260\347\254\246.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\261\236\346\200\247\346\217\217\350\277\260\347\254\246.md" new file mode 100644 index 00000000..8f364fe8 --- /dev/null +++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\261\236\346\200\247\346\217\217\350\277\260\347\254\246.md" @@ -0,0 +1,2 @@ +# 第20章 属性描述符 + diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\270\270\350\247\201\351\227\256\351\242\230\347\255\224\347\226\221.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\270\270\350\247\201\351\227\256\351\242\230\347\255\224\347\226\221.md" new file mode 100644 index 00000000..7e471795 --- /dev/null +++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\270\270\350\247\201\351\227\256\351\242\230\347\255\224\347\226\221.md" @@ -0,0 +1,63 @@ +# 常见问题答疑 + + +- [常见问题答疑](#%e5%b8%b8%e8%a7%81%e9%97%ae%e9%a2%98%e7%ad%94%e7%96%91) + - [1.Python 类self方法](#1python-%e7%b1%bbself%e6%96%b9%e6%b3%95) + + + +## 1.Python 类self方法 + +Python的类的方法和普通的函数有一个很明显的区别,在类的方法必须有个额外的第一个参数(self),但在调用这个方法的时候不必为这个参数赋值 + +Python的类的方法的这个特别的参数指代的是对象本身,而按照Python的惯例,它用self来表示。(当然我们也可以用其他任何名称来代替,只是规范和标准在那建议我们一致使用self) + +**为何Python给self赋值而你不必给self赋值?** + +例子说明: + +创建了一个类MyClass,实例化MyClass得到了MyObject这个对象,然后调用这个对象的方法MyObject.method(arg1,arg2), + +这个过程中,Python会自动转为Myclass.mehod(MyObject,arg1,arg2) +这就是Python的self的原理了。 + +即使你的类的方法不需要任何参数,但还是得给这个方法定义一个self参数,虽然我们在实例化调用的时候不用理会这个参数不用给它赋值。 + + +实例: + +```python +class Demo(object): + def hello(self): + print('Hello') + + +p = Demo() +p.hello() # hello +Demo.hello(p) # hello + +``` + +如果把self去掉的话,这样就报错了 +``` +TypeError: hello() takes no arguments (1 given) +``` + + +**self在Python里不是关键字** + +**self代表当前对象的地址** + +```python +class Demo(object): + def hello(self): + print("self=", self) + print('Hello') + + +p = Demo() +p.hello() +# self= <__main__.Demo object at 0x000001CD84029EF0> +# hello +``` + diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\272\217\345\210\227\346\236\204\346\210\220\347\232\204\346\225\260\347\273\204.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\272\217\345\210\227\346\236\204\346\210\220\347\232\204\346\225\260\347\273\204.md" new file mode 100644 index 00000000..2ed73f16 --- /dev/null +++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\272\217\345\210\227\346\236\204\346\210\220\347\232\204\346\225\260\347\273\204.md" @@ -0,0 +1,767 @@ +# 第2章 序列构成的数组 + + + +- [第2章 序列构成的数组](#第2章-序列构成的数组) + - [列表推导](#列表推导) + - [数组与列表效率对比](#数组与列表效率对比) + - [使用memoryview模块](#使用memoryview模块) + - [bisect模块用法](#bisect模块用法) + - [pickle模块用法](#pickle模块用法) + - [双向队列](#双向队列) + + + +第二章开始介绍了列表这种数据结构,这个在python是经常用到的结构 + +## 列表推导 +列表的推导,将一个字符串编程一个列表,有下面的2种方法。 +其中第二种方法更简洁。可读性也比第一种要好。 + + +```python +str = 'abc' +string = [] + +# 第一种方法 +for s in str: + print(string.append(s)) + +# 第二种方法 +ret = [s for s in str] +print(ret) +``` + +> 用这种for…in的方法来推导列表,有个好处就是不会有变量泄露也就是越界的问题 + +列表的推导还有一种方式,称为生成器表达式。 +表达式都差不多,不过是方括号编程了圆括号而已 + +`生成器的好处是什么呢?` + +> 列表推导是首先生成一个组合的列表,这会占用到内存。 +而生成式则是在每一个for循环运行时才生成一个组合,这样就不会预先占用内存 +生成器表达式不会一次将整个列表加载到内存之中,而是生成一个生成器对象(Generator objector),所以一次只加载一个列表元素 + +举个例子来说明: + +有20000个数组的列表。分别用列表推导法和生成式表达法来进行遍历。 +并用`memory_profiler` 来监控代码占用的内存 + +列表推导法例子: +```python +# 如果系统没有这个模块就执行pip install memory_profiler进行安装 +from memory_profiler import profile + + +@profile +def fun_try(): + test = [] + + for i in range(20000): + test.append(i) + + for num in [t for t in test]: + print(num) + + +fun_try() +``` + +这个代码运行结果是: + +``` + Line # Mem usage Increment Line Contents +================================================ + 5 39.9 MiB 39.9 MiB @profile + 6 def fun_try(): + 7 39.9 MiB 0.0 MiB test = [] + 8 + 9 40.8 MiB 0.0 MiB for i in range(20000): + 10 40.8 MiB 0.1 MiB test.append(i) + 11 + 12 41.2 MiB 0.2 MiB for num in [t for t in test]: + 13 41.2 MiB 0.0 MiB print(num) + +``` + +
+生成式例子: + +```python +# 如果系统没有这个模块就执行pip install memory_profiler进行安装 +from memory_profiler import profile + + +@profile +def fun_try(): + test = [] + + for i in range(20000): + test.append(i) + + for num in (t for t in test): + print(num) + + +fun_try() + +``` + +这个代码运行结果是: +``` +Line # Mem usage Increment Line Contents +================================================ + 5 40.1 MiB 40.1 MiB @profile + 6 def fun_try(): + 7 40.1 MiB 0.0 MiB test = [] + 8 + 9 41.1 MiB 0.0 MiB for i in range(20000): + 10 41.1 MiB 0.1 MiB test.append(i) + 11 + 12 41.1 MiB 0.0 MiB for num in (t for t in test): + 13 41.1 MiB 0.0 MiB print(num) + +``` +
+ +结论: + + `通过这两个结果可以看到列表推导法增加了0.2MB的内存 + 除非特殊的原因,应该经常在代码中使用生成器表达式。 + 但除非是面对非常大的列表,否则是不会看出明显区别的。 +` + +
+下面介绍下元组。说到元组,第一个反应就应该是不可变列表。 +但作者同时还介绍了元组的很多其他特性。 +首先来看下元组的拆包。 + +```python +#元组拆包 +t = (20, 8) +a, b = t +print(a, b) +``` + +如果在进行拆包的同时,并不是对所有的元组数据都感兴趣。 +**_占位符**就能帮助处理这种情况 + +```python +import os + _, filename = os.path.split("/home/jian/prj/demo.txt") +print(_, filename) #/home/jian/prj demo.txt +``` +上面元组拆包的时候是对可迭代对象进行遍历,然后一一赋值到变量。 +但是如果想通过给每个元组元素的命名来访问,则需用到命名元组namdtuple + +```python +# collections.namedtuple构建一个带有字段名的元组和一个有名字的类 +from collections import namedtuple, OrderedDict + +City = namedtuple('City', 'name country population') +tokyo = City('Tokyo', 'JP', '123') +tokyo_data = ('Tokyo', 'JP', '123') + +print(tokyo) # City(name='Tokyo', country='JP', population='123') +print(tokyo.name, tokyo.country, tokyo.population) # Tokyo JP 123 +print(City._fields) # ('name', 'country', 'population') + +tokyo = City._make(tokyo_data) +print(tokyo) # City(name='Tokyo', country='JP', population='123') + +OrderedDict([('name', 'Tokyo'), ('country', 'JP'), ('population', '123')]) +print(tokyo._asdict()) +# OrderedDict([('name', 'Tokyo'), ('country', 'JP'), ('population', '123')]) + +``` +
+ +**符号用来处理剩下的元素** + +```python +a, b, *rest = range(5) +print(a, b, rest) # 0 1 [2, 3, 4] + +a, *b, rest = range(5) +print(a, b, rest) # 0 [1,2,3] 4 + +``` + + +* 序列的操作: +增量赋值: +增量运算符+,*等其实是调用`__iadd__/__add__/__mul__`方法。 +**对于可变对象来说,+调用的是`__iadd__`,对于不可变对象来说对象调用的是`__add__`。** + +**两者有什么区别呢。** + +先看下面的例子 +```python +str = [1, 2, 3] +str1 = (1, 2, 3) + +print("str:%d" % id(str)) +print("str1:%d" % id(str1)) + +str += str +str1 += str1 + +print("str:%d" % id(str)) +print("str1:%d" % id(str1)) +``` + +得到的结果如下: + +``` +str:2256630301640 +str1:2256630239736 +str:2256630301640 +str1:2256630606152 + +``` + +str是列表,str1是元组,列表是可变对象,元组是不可变对象。 + +在进行加法运算后,str的id没有改变,因此还是之前的对象,但是str1的id却发生了改变,不是之前的对象了。 + +这种的差别在于`__iadd__`的方法类似调用a.extend(b)的方式,是在原有的对象上进行扩展操作。 + +但是`__add__`的方式类似于a=a+b。 +首先a+b得到一个新的的对象,然后复制给a,因此变量和之前的对象没有任何联系。 +而是被关联到一个新的对象。同样的乘法`__imul__/__mul__`也是类似的道理 + +列表组成的列表: +```python +board = [['_'] * 3 for i in range(3)] +print(board) + +board[1][2] = 'x' +print(board) + +``` +运行结果: +``` +[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']] +[['_', '_', '_'], ['_', '_', 'x'], ['_', '_', '_']] + +``` + +输出三个列表的ID +```python +board = [['_'] * 3 for i in range(3)] +print(board) + +board[1][2] = 'x' +print(board) + +# 输出3个列表的ID +print(id(board[0])) +print(id(board[1])) +print(id(board[2])) + +``` + +结果是分别属于不同的ID: +``` +3221578302664 +3221578302536 +3221578302600 +``` + +我们再来看下另外一种用法。下面的代码对一个包含3个列表的列表进行*3的操作。 +```python +board = [['_'] * 3] * 3 +print(board) + +board[1][2] = 'x' +print(board) +print(id(board[0])) +print(id(board[1])) +print(id(board[2])) +``` + +运行结果是: +发现id都一样。说明了全部指向的是同一个对象。 +``` +[['_', '_', '_'], ['_', '_', '_'], ['_', '_', '_']] +[['_', '_', 'x'], ['_', '_', 'x'], ['_', '_', 'x']] +1195278432328 +1195278432328 +1195278432328 +``` + +如果对不可变对象中的可变对象进行赋值会产生什么后果,比如下面的这段代码 +```python +t = (1, 2, [30, 40]) +t[2] += [50, 60] + +print(t) +``` + +运行结果直接报错: +``` +TypeError: 'tuple' object does not support item assignment +``` + +我们可以把代码放在[python在线调式网站](http://pythontutor.com/visualize.html#mode=edit)进行调试 + +把代码稍微修改下然后再看看情况 +```python +t = (1, 2, [30, 40]) +t[2].append([50, 60]) + +print(t) +``` + +`为什么这两种实现会带来不同的结果呢?` + +原因在于t是一个元组属于不可变对象。但用t[2]+=[50,60]的时候是对一个元组进行赋值。 +所以报错误。 + +但是同时t[2]又属于一个列表可变对象。因此数据也更新成功了 + +但是如果用t[2].append([50,60])的操作则是对一个列表进行操作,而并没有对一个元组进行赋值。因此能够更新成功且不会报错误。 + +这是一个很有趣的例子。 + +对于理解可变对象和不可变对象的操作很有帮助。 +
+ +`查看背后的字节码情况:` +```python +import dis +a = (1, 2, 3) + +byte_info = dis.dis('a[2]+=1') +print(byte_info) +``` + +打印出反编译的结果(反编译后的代码与汇编语言接近)是: +``` + 1 0 LOAD_NAME 0 (a) + 2 LOAD_CONST 0 (2) + 4 DUP_TOP_TWO + 6 BINARY_SUBSCR + 8 LOAD_CONST 1 (1) + 10 INPLACE_ADD + 12 ROT_THREE + 14 STORE_SUBSCR + 16 LOAD_CONST 2 (None) + 18 RETURN_VALUE + +``` + +>插播一个有趣的例子 + +python之交换变量值 + +```python +import dis + +def swap1(): + x = 5 + y = 6 + x, y = y,x + +def swap2(): + x = 5 + y = 6 + tmp = x + x = y + y = tmp + +if __name__ == "__main__": + print ("***SWAP1***") + print (dis.dis(swap1)) + print ("***SWAP2***") + print (dis.dis(swap2)) +``` + +查看结果: +``` +*** SWAP1*** + 5 0 LOAD_CONST 1 (5) + 3 STORE_FAST 0 (x) + 6 6 LOAD_CONST 2 (6) + 9 STORE_FAST 1 (y) + 7 12 LOAD_FAST 1 (y) + 15 LOAD_FAST 0 (x) + 18 ROT_TWO + 19 STORE_FAST 0 (x) + 22 STORE_FAST 1 (y) + 25 LOAD_CONST 0 (None) + 28 RETURN_VALUE + +***SWAP2*** +10 0 LOAD_CONST 1 (5) + 3 STORE_FAST 0 (x) +11 6 LOAD_CONST 2 (6) + 9 STORE_FAST 1 (y) +12 12 LOAD_FAST 0 (x) + 15 STORE_FAST 2 (tmp) +13 18 LOAD_FAST 1 (y) + 21 STORE_FAST 0 (x) +14 24 LOAD_FAST 2 (tmp) + 27 STORE_FAST 1 (y) + 30 LOAD_CONST 0 (None) + 33 RETURN_VALUE +``` + +得到结论 +>通过字节码可以看到,swap1和swap2最大的区别在于,swap1中通过ROT_TWO交换栈顶的两个元素实现x和y值的互换,swap2中引入了tmp变量,多了一次LOAD_FAST, STORE_FAST的操作。 +> +>执行一个ROT_TWO指令比执行一个LOAD_FAST+STORE_FAST的指令快, +> +>**这也是为什么swap1比swap2性能更好的原因**。 + +
+ +* 数组: + +在列表和元组中,存放的是具体的对象,如整数对象,字符对象。 +如下面的整数b。占据28个字节。因为存放的是整数对象,而非整数本身。 + +```python +import sys +b = 1 + +print(sys.getsizeof(b)) # 28 +``` + +对于存放大量数据来说。我们选择用数组的形式要好很多。 +**因为数组存储的不是对象,而是数字的机器翻译。也就是字节表述。** + +![Alt text](https://raw.githubusercontent.com/Syncma/Figurebed/master/img/1183125-20170617154733759-2053769655.png) + + +在array中需要规定各个字符的类型,如上表中的Type code。 +定义好类型后则数组内的元素必须全是这个类型,否则会报错。 + +就像下面的代码。类型规定的是b也就是单字节的整数。 +但是在插入的时候却是c字符,则报错:`TypeError: an integer is required` + +```python +import array + +num = array.array('b') +num.append('c') + +print(num) +``` + +
+ +**在上表中,每个类型都有字节大小的限制。如果超出了字节大小的限制也是会报错的** + +还是b的这个类型,是有符号的单字节整数,那么范围是-128到127. +如果我们插入128.则报错:`signed char is greater than maximum` 提示超过了最大 + +```python +import array + +num = array.array('b') +num.append(128) + +print(num) +``` + +对于数组这种结构体来说,由于占用的内存小,因此在读取和写入文件的时候的速度更快,相比于列表来说的话。 + +## 数组与列表效率对比 +下面来对比下: +首先是用列表生成并写入txt文档的用法 + +```python +import time +import struct + +# struct 例子 +# a = 20 +# 'i'表示一个int +# # struct.pack把python值转化成字节流 +# data = struct.pack('i', a) +# print(len(data)) +# print(repr(data)) +# print(data) + +# 拆包 +# a1 = struct.unpack('i', data) +# print("a1=", a1) + + +def arry_try_list(): + + floats = [float for float in range(10**7)] + + fp = open('list.bin', 'wb') + start = time.clock() + + for f in floats: + + strdata = struct.pack('i', f) + fp.write(strdata) + + fp.close() + + end = time.clock() + + print(end - start) + + +arry_try_list() + +``` + +执行结果:5.8789385 + + +再看数组的形式: +```python +from array import array +from random import random +import time + + +def array_try(): + + floats = array('d', (random() for i in range(10**7))) + start = time.clock() + + fp = open('floats.bin', 'wb') + floats.tofile(fp) + + fp.close() + + end = time.clock() + print(end - start) + + +array_try() +``` +执行结果: 0.045192899999999994 + +可以看到速度明显提升了很多 + +再来对比读文件的速度 +```python +from array import array +import time + + +def array_try(): + + floats = array('d') + start = time.clock() + + fp = open('floats.bin', 'rb') + # 比直接从文本文件里面读快,后者使用内置的float方法把每一行文字变成浮点数 + floats.fromfile(fp, 10**7) + fp.close() + + end = time.clock() + print(end - start) + + +array_try() +``` + +执行结果是:0.1172238 + +*** +## 使用memoryview模块 +精准地修改了一个数组的某个字节 +```python +#memoryview内存视图 +#在不复制内容的情况下操作同一个数组的不同切片 +from array import array + +#5个短整型有符号整数的数组,类型码是h +numbers = array('h', [-2, -1, 0, 1, 2]) + +memv = memoryview(numbers) +print(len(memv)) + +#转成B类型,无符号字符 +memv_oct = memv.cast('B') +tolist = memv_oct.tolist() +print(tolist) + +memv_oct[5] = 4 +print(numbers) +``` + +*** +## bisect模块用法 + +```python + +import bisect + +# 以空格作为分隔打印S中所有元素再换行. +def print_all(S): + for x in S: + print(x, end = " ") + print("") + +# 有序向量SV. +SV = [1, 3, 6, 6, 8, 9] + +#查找元素. +key = int(input()) +print(bisect.bisect_left(SV, key)) +print(bisect.bisect_right(SV, key)) + +# 插入新元素. +key = 0 +bisect.insort_right(SV, key) + +# 删除重复元素的最后一个. 思考: 如何删除第一个? +key = 6 +i = bisect.bisect_right(SV, key) +i -= 1 +# 注意此时i < len(SV)必然成立. 如果确实有key这个元素则删除. +if (i > 0 and SV[i] == key): del(SV[i]) +print_all(SV) + +# 删除重复key所在区间: [bisect.bisect_left(SV, key):bisect.bisect_right(SV, key)). +del SV[bisect.bisect_left(SV, key):bisect.bisect_right(SV, key)] +print_all(SV) + +# 无序向量USV. +USV = [9, 6, 1, 3, 8, 6] + +# 插入新元素 +key = 0 +USV.append(key) + +# 删除重复元素的最后一个. +key = 6 +# 逆向遍历, 如果存在则删除. +i = len(USV) - 1 +while i > 0: + if USV[i] == key: + USV[i] = USV[-1] + USV.pop() + break + i -= 1 +print_all(USV) + +# 删除重复元素的第一个. +try: + i = USV.index(key) +except: + pass +else: + USV[i] = USV[-1] + USV.pop() +print_all(USV) +``` +*** +##pickle模块用法 +pickle模块是将python值转成成byte +```python +try: + import cPickle as pickle +except: + import pickle + +data = [{'a': 'A', 'b': 2, 'c': 3.0}] + +# 编码 +data_string = pickle.dumps(data) + +print("DATA:", data) +print("PICKLE:", data_string) +print(type(data_string)) + +# 解码 +data_from_string = pickle.loads(data_string) +print(data_from_string) + +``` + +*** +## 双向队列 +双向队列deque + +```python +import timeit +from collections import deque + + +def way1(): + mylist = list(range(100000)) + for i in range(100000): + mylist.pop() + + +def way2(): + mydeque = deque(range(100000)) + for i in range(100000): + mydeque.pop() + + +if __name__ == "__main__": + t1 = timeit.timeit("way1", setup="from __main__ import way1", number=10) + print(t1) + + t2 = timeit.timeit("way2", setup="from __main__ import way2", number=10) + print(t2) + +``` + +结果: +``` +6e-07 +1.0000000000001327e-06 +``` + + + +> **deque是双向队列,如果你的业务逻辑里面需要大量的从队列的头或者尾部删除,添加,用deque的性能会大幅提高!** + +> 如果只是小队列,并且对元素需要随机访问操作,那么list会快一些。 + + +```python +# 双向队列用法 + +from collections import deque + +# 创建一个队列 +q = deque([1]) +print(q) + +# 往队列中添加一个元素 +q.append(2) +print(q) + +# 往队列最左边添加一个元素 +q.appendleft(3) +print(q) + +# 同时入队多个元素 +q.extend([4, 5, 6]) +print(q) + +# 在最左边同时入队多个元素 +q.extendleft([7, 8, 9]) +print(q) + +# 剔除队列中最后一个 +q.pop() +print(q) + +# 删除队列最左边的一个元素 +q.popleft() +print(q) + +# 清空队列 +# q.clear() + +# 获取队列长度 +# length = q.maxlen +# print(length) + +``` \ No newline at end of file diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\272\217\345\210\227\347\232\204\344\277\256\346\224\271-\346\225\243\345\210\227\345\222\214\345\210\207\347\211\207.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\272\217\345\210\227\347\232\204\344\277\256\346\224\271-\346\225\243\345\210\227\345\222\214\345\210\207\347\211\207.md" new file mode 100644 index 00000000..84ef729d --- /dev/null +++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\272\217\345\210\227\347\232\204\344\277\256\346\224\271-\346\225\243\345\210\227\345\222\214\345\210\207\347\211\207.md" @@ -0,0 +1,159 @@ +# 第10章-序列的修改、散列和切片 + + + +- [第10章-序列的修改、散列和切片](#%e7%ac%ac10%e7%ab%a0-%e5%ba%8f%e5%88%97%e7%9a%84%e4%bf%ae%e6%94%b9%e6%95%a3%e5%88%97%e5%92%8c%e5%88%87%e7%89%87) + - [反射](#%e5%8f%8d%e5%b0%84) + - [作用](#%e4%bd%9c%e7%94%a8) + - [Python反射方法](#python%e5%8f%8d%e5%b0%84%e6%96%b9%e6%b3%95) + + + + +## 反射 + +**反射主要是指程序可以访问、检测和修改它本身状态或行为的一种能力**,在python中一切皆对象(类,实例,模块等等都是对象),那么我们就可以通过反射的形式操作对象相关的属性。 + +用字符串数据类型的变量名或者函数名来调用对应的属性 +A.b getattr(A, 'b') + + +### 作用 + +1.实现可插拔机制 + +**可以事先定义好接口,接口只有在被完成后才会真正执行,这实现了即插即用**,这其实是一种‘后期绑定’,什么意思?即你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能 + + +举个例子: + +demo.py +```python +class FanShe(object): + x = 1 + + def test1(self): + print('this is test1') + + def test(self): + print("this is test") + +``` + + +其他文件调用: +```python +import demo as tf + +c = tf.FanShe + +# 判断c对象中的test是否存在,存在即调用执行,不存在就执行其他逻辑代码 +if hasattr(c, 'test'): + func = getattr(c, 'test') + print(func("hello")) + +else: + print('不存在此方法') + print('处理其他的逻辑') +``` + + +
+ +2.动态导入模块 + +demo/`__init__.py` +demo/work.py + +work.py + +```python +def test(): + print('this is test') +``` + +在外层想要调用这个work.py里面的test方法可以使用下面的方法: + +```python +M = __import__('demo.work') +print(M) +M.work.test() + +# 可以写成这样 +import importlib + +M = importlib.import_module('demo.work') +print(M) +M.test() + +``` + +### Python反射方法 + +Python中的反射主要有下面几个方法: + +1.hasattr(object,name) +判断对象中有没有一个name字符串对应的方法或属性 + +2.getattr(object, name, default=None) +获取对象name字符串属性的值,如果不存在返回default的值 + +3.setattr(object, key, value) +设置对象的key属性为value值,等同于object.key = value + +4.delattr(object, name) +删除对象的name字符串属性 + + +```python +class Cat(object): + class_level = '贵族' + + def __init__(self, name, type, speed, age): + self.name = name + self.type = type + self.speed = speed + self.age = age + + def run(self): + print('%s岁的%s%s正在以%s的速度奔跑' % + (self.age, self.type, self.name, self.speed)) + + +xiaohua = Cat('小花', '波斯猫', '10m/s', 10) +xiaohua.run() # 10岁的波斯猫小花正在以10m/s的速度奔跑 + +# hasattr(object, name) 判断对象中有没有一个name字符串对应的方法或属性 +print(hasattr(xiaohua, 'name')) # 判断xiaohua有没有name的属性,返回True,说明小花有name的属性, +# 注意'name'一定要是字符串类型 + +print(hasattr(xiaohua, 'size')) # 返回False说明小花没有size的属性 + +# 2.getattr(object, name, default=None) 获取对象name字符串属性的值, +# 如果不存在返回default的值 +print(getattr(xiaohua, 'speed', '20m/s')) # 10m/s + +# 获取xiaohua的speed的属性,speed的属性存在 +print(xiaohua.__dict__) +# {'name': '小花', 'type': '波斯猫', 'speed': '10m/s', 'age': 10} + +print(getattr(xiaohua, 'weight', '5kg')) +# 5kg, 获取xiaohua的weight属性,此属性不存在,返回default的值 + +# print(getattr(xiaohua, 'weight')) # 不设置default时,属性不存在 +# 会报错 AttributeError: 'Cat' object has no attribute 'weigh' + +# 3.setattr(object, key, value)# 设置对象的key属性为value值 +# 等同于object.key = value + +setattr(xiaohua, 'weight', '5kg') +# 给xiaohua增加weight的属性 +print(xiaohua.__dict__) +# {'name': '小花', 'type': '波斯猫', 'speed': '10m/s', 'age': 10, 'weight': '5kg'} + +# 4.delattr(object, name) 删除对象的name字符串属性 + +delattr(xiaohua, 'weight') +print(xiaohua.__dict__) +# {'name': '小花', 'type': '波斯猫', 'speed': '10m/s', 'age': 10} +``` \ No newline at end of file diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\272\217\345\271\225.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\272\217\345\271\225.md" new file mode 100644 index 00000000..c01cda06 --- /dev/null +++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\345\272\217\345\271\225.md" @@ -0,0 +1,383 @@ +# 第1章 序幕 + + +- [第1章 序幕](#%e7%ac%ac1%e7%ab%a0-%e5%ba%8f%e5%b9%95) + - [tuple和nametuple的区别](#tuple%e5%92%8cnametuple%e7%9a%84%e5%8c%ba%e5%88%ab) + - [__repr__和__str__的区别](#repr%e5%92%8cstr%e7%9a%84%e5%8c%ba%e5%88%ab) + + + +这一章中作者简要的介绍了python数据模型,主要是python的一些特殊方法。比如`__len__`,`__getitem__`. 并用一个纸牌的程序来讲解了这些方法 + +## tuple和nametuple的区别 +>**Nametuple是类似于元组的数据类型。** + +>**除了能够用索引来访问数据,还支持用方便的属性名来访问数据。** + +传统的元组访问如下。 +```python +tup1 = ('abc', 'def', 'ghi') +print(tup1[1]) +``` + +对每个元素的访问都必须通过索引来找到。这种找法很不直观 + +使用nametuple来构造: +```python +import collections +tup2 = collections.namedtuple('tuple2', ['name', 'age', 'height']) +t1 = tup2('zhf', '33', '175') +print(t1) +print(t1.age) +print(t1.height) +print(t1.name) +``` + +得到结果如下,namedtupel中tuple2是类型名,name,age,height是属性名字从上面的访问可以看到,直接用t1.age的方法访问更加直观。 + +当然也可以用索引比如t1[0]的方法来访问 + +程序执行返回结果如下: +```python +tuple2(name='zhf', age='33', height='175') +33 +175 +zhf +``` + + +namedtupe1也支持迭代访问: +```python +for t in t1: + print(t) +``` + +
+ +**`和元组一样,namedtupel中的元素也是不可变更的`** + +如果执行t1.age+=1。将会提示无法设置元素 + +```python + t1.age += 1 +AttributeError: can't set attribute +``` + +下面来看下书中的纸牌例子,代码如下 +```python +from collections import namedtuple +Card = namedtuple('Card', ['rank', 'suit']) +class FrenchDeck(object): + ranks = [str(n) for n in range(2, 11)] + list('JQKA') + suits = 'spades diamonds clubs hearts'.split() + def __init__(self): + self._cards = [Card(rank, suit) for suit in self.suits for rank in self.ranks] + def __len__(self): + return len(self._cards) + def __getitem__(self, position): + return self._cards[position] +if __name__ == '__main__': + deck = FrenchDeck() + # 如果类中有一个__len__方法, len函数首先就会调用类里面的方法 + print(len(deck)) + print(deck[1]) +``` +
+首先定义了的纸牌元组Card, rank代表纸牌数字,suit代表纸牌花色。 +然后在FrenchDeck首先定义了ranks和suit的具体值。 +在`__init__`中对self._cards进行初始化。 +`__len__`反馈self._cards的长度。`__getitem__`反馈具体的纸牌值。 + +结果如下: +纸牌的长度为52,其中deck[1]为Card(rank=’3’,suit=’spades’) + +可以看到len(deck)其实调用的是`__len__`方法。 +deck[1]调用的是`__getitem__`由于有了`__getitem__`方法, + +还可以进行迭代访问,如下: +```python +for d in deck: + print (d) +``` +
+既然是可迭代的,那么我们可以模拟随机发牌的机制。 + +```python +from random import choice + print (choice(deck)) +``` +>随机抽取,使用random.choice标准库 +`要看看choice底层实现原理,搞明白机制` + +
+接下来看另外一个例子,关于向量运算的。 +比如有向量1 vector1(1,2),向量2 vector2(3,4)。 +那么vector1+vector2的结果应该是(4,6)。 + +Vector1和vector2都是向量,如何实现运算呢。`方法是__add__,__mul__` + +代码如下: + +```python +from math import hypot +class vector(object): + def __init__(self, x=0, y=0): + self.x = x + self.y = y + def __repr__(self): + return 'Vector(%r,%r)' % (self.x, self.y) + def __abs__(self): + return hypot(self.x, self.y) + def __bool__(self): + return bool(abs(self)) + def __add__(self, other): + x = self.x + other.x + y = self.y + other.y + return vector(x, y) + def __mul__(self, scalar): + return vector(self.x * scalar, self.y * scalar) +if __name__ == '__main__': + v1 = vector(1, 2) + v2 = vector(2, 3) + print(v1 + v2) + print(abs(v1)) + print(v1 * 3) + +``` +返回结果: +``` +Vector(3,5) +2.23606797749979 +Vector(3,6) +``` + + + +在这里`__add__,_ _mul_ __,__abs__`分别实现了向量加法,乘法,以及求模的运算。 + +值得一提的是`__repr__`的方法。 + +这个方法是在需要打印对象的时候调用。 + +例如print vector(1,2)的时候得到vector(1,2). + +否则就是表示对象的字符串:. + +这个__repr__和__str__的作用是类似的 + +这里要知道`__repr__和__str__`的区别在哪里 [^1] + + +
+ +## `__repr__和__str__`的区别 + +主要区别如下: + +* **`区别1`** +> 如果_`_str__ 存在会首先调用__str__ ` +> 如果`__str__ 不存在会调用__repr__` + +测试代码如下: +``` python + +class DemoClass(object): + def __repr__(self): + return 'DemoClass repr' + + def __str__(self): + return 'DemoClass str' + + +demo = DemoClass() +print(demo) #返回是:DemoClass str +``` + +```python +class DemoClass(object): + def __repr__(self): + return 'DemoClass repr' + + +demo = DemoClass() +print(demo) # DemoClass repr + +``` +
+ +* **`区别2`** +```python +class DemoClass(object): + def __init__(self, name): + self.name = name + + def __repr__(self): + # %r 字符串 (采用repr()的显示) + return "repr My name is %r" % (self.name) + + def __str__(self): + return "str My name is %s" % (self.name) + + +demo = DemoClass("tony") +print(demo) # 返回"str My name is tony" + +# 使用repr(obj)的时候,会自动调用__repr__函数 +print(repr(demo)) # 返回"repr My name is tony" + +demo1 = DemoClass("'tony1") +print(demo1) # str My name is 'tony1 +print(repr(demo1)) # repr My name is "'tony1" + +``` + + 可以看到有一些区别的 +> `__repr__`所返回的字符串应该准确、无歧义,并且尽可能表达出如何用代码创建出这个被打印的对象。 + +> `__repr__和__str__`的区别在于,后者是在str()函数被使用,或是在用print函数打印一个对象的时候才被调用的,并且它返回的字符串对终端用户更友好。 + +
+再比如下面一个例子: + +``` python +class DemoClass(object): + def __init__(self, name): + self.name = name + + def say(self): + print(self.name) + + def __repr__(self): + return "DemoClass('jacky')" + + def __str__(self): + return "str My name is %s" % (self.name) + + +demo = DemoClass("tony") +demo.say() # tony + +demo_repr = type(repr(demo)) +print(demo_repr) # + +demo_eval = type(eval(repr(demo))) +print(demo_eval) # + +# 用于重建对象,如果eval(repr(obj))会得到一个对象的拷贝。 +study = eval(repr(demo)) +study.say() # jacky +``` + + +
+ +* **`区别3`** +> **这里额外比较repr和str函数的区别** + +在 Python 中要将某一类型的变量或者常量转换为字符串对象通常有两种方法,即str() 或者 repr() +```python +>>> a = 10 +>>> type(str(a)) + +>>> type(repr(a)) + +``` + +但是这二者之间有什么区别呢? +因为提供两个功能完全相同的内建函数是没有意义的。 + +先看一个例子。 + +```python +>>> print(str('123')) +123 +>>> print(str(123)) +123 +>>> print(repr('123')) +'123' +>>> print(repr(123)) +123 +``` +从例子中不难发现,当我们把一个字符串传给 str() 函数再打印到终端的时候,输出的字符不带引号。 + +而将一个字符串传给 repr() 函数再打印到终端的时候,输出的字符带有引号。 + +造成这两种输出形式不同的原因在于: + +print 语句结合 str() 函数实际上是调用了对象的` __str__ `方法来输出结果。 +而 print 结合 repr() 实际上是调用对象的` __repr__` 方法输出结果。 + + +下例中我们用 str 对象直接调用这两个方法,输出结果的形式与前一个例子保持一致。 + +```python +>>> print('123'.__repr__()) +'123' +>>>> print('123'.__str__()) +123 +``` +
+不过这个例子可能还是无法很好表达到底 str() 与 repr() 各有什么意义 +我们再来看一个例子。 + +```python +>>> from datetime import datetime +>>> now = datetime.now() +>>> print(str(now)) +2017-04-22 15:41:33.012917 +>>> print(repr(now)) +datetime.datetime(2017, 4, 22, 15, 41, 33, 12917) +``` +通过 str() 的输出结果我们能很好地知道 now 实例的内容, +但是却丢失了 now 实例的数据类型信息。 + +而通过 repr() 的输出结果我们不仅能获得 now 实例的内容, +还能知道 now 是 datetime.datetime 对象的实例。 + + +再比如: +```python + +string = "Hello\tWill\n" +print(str(string)) +print(repr(string)) +``` + +结果: + +```python +Hello Will +'Hello\tWill\n' +``` +
+ +再比如: +```python + +a = "哈哈" + +print(str(a)) +print(repr(a)) + +# 返回结果 +""" +哈哈 +'哈哈' +""" +``` + + +
+ +因此 str() 与 repr() 的不同在于: +* **str() 的输出追求可读性,输出格式要便于理解,适合用于输出内容到用户终端。** +* **repr() 的输出追求明确性,除了对象内容,还需要展示出对象的数据类型信息,适合开发和调试阶段使用。** + +
+ +另外如果想要自定义类的实例能够被 str() 和 repr() 所调用, +那么就需要在自定义类中重载` __str__ 和 __repr__` 方法。 + + + +[^1]: `__repr__和__str__`的区别 diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\346\211\251\345\261\225\345\206\205\345\256\271.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\346\211\251\345\261\225\345\206\205\345\256\271.md" new file mode 100644 index 00000000..b9e9ec3d --- /dev/null +++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\346\211\251\345\261\225\345\206\205\345\256\271.md" @@ -0,0 +1,1231 @@ +# 第7章-扩展内容 + + + +- [第7章-扩展内容](#第7章-扩展内容) + - [python面向对象](#python面向对象) + - [基础篇](#基础篇) + - [三种编程方式](#三种编程方式) + - [创建类和对象](#创建类和对象) + - [面向对象三大特性](#面向对象三大特性) + - [封装](#封装) + - [继承](#继承) + - [新式类与经典类](#新式类与经典类) + - [多态](#多态) + - [进阶篇](#进阶篇) + - [类的成员](#类的成员) + - [字段](#字段) + - [方法](#方法) + - [属性](#属性) + - [属性的两种定义方式](#属性的两种定义方式) + - [公有成员和私有成员](#公有成员和私有成员) + - [特殊的类的成员](#特殊的类的成员) + - [`__module__ 和 __class__`](#__module__-和-__class__) + - [`__init__`](#__init__) + - [`__del__`](#__del__) + - [`__call__`](#__call__) + - [`__dict__`](#__dict__) + - [` __getitem__&__setitem__&__delitem__`](#-__getitem____setitem____delitem__) + - [` __getslice__&__setslice__&__delslice__`](#-__getslice____setslice____delslice__) + - [` __iter__`](#-__iter__) + - [` __new__`](#-__new__) + + +## python面向对象 + +### 基础篇 + +#### 三种编程方式 + +>面向过程:根据业务逻辑从上到下写垒代码 +函数式:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可 +面向对象:对函数进行分类和封装,让开发“更快更好更强…” + + +面向过程编程最易被初学者接受,其往往用一长段代码来实现指定功能,开发过程中最常见的操作就是粘贴复制,即:将之前实现的代码块复制到现需功能处。 + +```python +while True: + if cpu利用率 > 90%: + # 发送邮件提醒 + 连接邮箱服务器 + 发送邮件 + 关闭连接 + + if 硬盘使用空间 > 90%: + # 发送邮件提醒 + 连接邮箱服务器 + 发送邮件 + 关闭连接 + + if 内存占用 > 80%: + # 发送邮件提醒 + 连接邮箱服务器 + 发送邮件 + 关闭连接 +``` + +随着时间的推移,开始使用了函数式编程,增强代码的重用性和可读性,就变成了这样: +```python +def 发送邮件(内容) + #发送邮件提醒 + 连接邮箱服务器 + 发送邮件 + 关闭连接 + +while True: + + if cpu利用率 > 90%: + 发送邮件('CPU报警') + + if 硬盘使用空间 > 90%: + 发送邮件('硬盘报警') + + if 内存占用 > 80%: + 发送邮件('内存报警') +``` + +今天我们来学习一种新的编程方式:面向对象编程(Object Oriented Programming,OOP,面向对象程序设计)。 + +#### 创建类和对象 +面向对象编程是一种编程方式,此编程方式的落地需要使用 “类” 和 “对象” 来实现, +所以面向对象编程其实就是对 “类” 和 “对象” 的使用。 + +* 类就是一个模板,模板里可以包含多个函数,函数里实现一些功能 +* 对象则是根据模板创建的实例,通过实例对象可以执行类中的函数 + + +```python +# 创建Test类,python3 初始类都继承object类 +class Test(object): + # 类中定义的函数也叫方法 + def demo(self): # 类中的函数第一个参数必须是self + print("self=", self) + print("Method=", dir(self)) + print("Hello") + + +# 根据类Test创建对象obj +obj = Test() # 类名加括号 +obj.demo() # 执行demo方法 + +# 获取一个实例的类名 +print(obj.__class__.__name__) + +# 判断一个对象是否拥有某个属性 +if hasattr(obj, 'demo'): + print("it has demo method") +else: + print("have no method") +``` + +返回结果: +``` +self= <__main__.Test object at 0x00000224F3539A58> +Method= ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'demo'] +Hello +``` +
+ +结论: + +>方法的第一个参数是调用这个方法的实例对象本身. +>从上面结果可以看出来self对于代码不是特殊的,只是另一个对象. + + + + +#### 面向对象三大特性 + +##### 封装 + +封装,顾名思义就是将内容封装到某个地方,以后再去调用被封装在某处的内容。 +所以,在使用面向对象的封装特性时,需要: + +* 将内容封装到某处 +* 从某处调用被封装的内容 + +```python +# 1. 将内容封装到某处 +class Test(object): + # __init__方法成为构造方法,根据类创建对象时自动执行 + def __init__(self, name): + self.name = name + + def detail(self): + print(self.name) + + +# 自动执行__init__方法 +# 将tony封装到obj self的name属性中 +# 当执行obj,self等于obj +obj = Test('tony') + +# 2.从某处调用封装的内容 +# 通过对象直接调用, 格式:对象.属性名 +print(obj.name) + +# 通过self调用 +# python默认会将obj传给self参数,即obj.detail(obj) +# 此时方法内部的self=obj,所以 self.name='tony' +print(obj.detail()) + +``` + +
+综上所述,对于面向对象的封装来说,其实就是使用构造方法将内容封装到 对象 中,然后通过对象直接或者self间接获取被封装的内容。 + + +##### 继承 +继承,面向对象中的继承和现实生活中的继承相同,即:**子可以继承父的内容**。 + +例如: +猫可以:喵喵叫、吃、喝、拉、撒 +狗可以:汪汪叫、吃、喝、拉、撒 + +如果我们要分别为猫和狗创建一个类,那么就需要为 猫 和 狗 实现他们所有的功能 + +```python +class 猫: + def 喵喵叫(self): + print '喵喵叫' + def 吃(self): + # do something + def 喝(self): + # do something + def 拉(self): + # do something + def 撒(self): + # do something + + +class 狗: + def 汪汪叫(self): + print '喵喵叫' + def 吃(self): + # do something + def 喝(self): + # do something + def 拉(self): + # do something + def 撒(self): + # do something +``` + +上述代码不难看出,吃、喝、拉、撒是猫和狗都具有的功能,而我们却分别的猫和狗的类中编写了两次。如果使用 继承 的思想,如下实现: + +动物:吃、喝、拉、撒 +猫:喵喵叫(猫继承动物的功能) +狗:汪汪叫(狗继承动物的功能) + +```python +class 动物: + + def 吃(self): + # do something + + def 喝(self): + # do something + + def 拉(self): + # do something + + def 撒(self): + # do something + +# 在类后面括号中写入另外一个类名,表示当前类继承另外一个类 +class 猫(动物): + + def 喵喵叫(self): + print '喵喵叫' + +# 在类后面括号中写入另外一个类名,表示当前类继承另外一个类 +class 狗(动物): + + def 汪汪叫(self): + print '喵喵叫' +``` + +所以,对于面向对象的继承来说,**其实就是将多个类共有的方法提取到父类中,子类仅需继承父类而不必一一实现每个方法。** + +```python +class Animal(object): + def eat(self): + print("%s 吃 " % self.name) + + def drink(self): + print("%s 喝 " % self.name) + + def shit(self): + print("%s 拉 " % self.name) + + def pee(self): + print("%s 撒 " % self.name) + + +class Cat(Animal): + def __init__(self, name): + self.name = name + self.breed = '猫' + + def cry(self): + print('喵喵叫') + + +class Dog(Animal): + def __init__(self, name): + self.name = name + self.breed = '狗' + + def cry(self): + print('汪汪叫') + + +c1 = Cat('小白家的小黑猫') +c1.eat() + +c2 = Cat('小黑的小白猫') +c2.drink() + +d1 = Dog('胖子家的小瘦狗') +d1.eat() +``` + +那么问题又来了,多继承呢? +* 是否可以继承多个类 +* 如果继承的多个类每个类中都定了相同的函数,那么那一个会被使用呢? + + +1、Python的类可以继承多个类,Java和C#中则只能继承一个类 +2、Python的类如果继承了多个类,那么其寻找方法的方式有两种,分别是:深度优先和广度优先 + +###### 新式类与经典类 +注意: + +当类是经典类时,多继承情况下,会按照**深度优先**方式查找 -`找到第一个` + +当类是新式类时,多继承情况下,会按照**广度优先**方式查找 -`找到最后一个` + + +经典类和新式类,从字面上可以看出一个老一个新,新的必然包含了跟多的功能,也是之后推荐的写法 + +从写法上区分的话,如果 当前类或者父类继承了object类,那么该类便是新式类,否则便是经典类。 + +经典类 -python2.7测试 +```python +class D: + def bar(self): + print 'D.bar' + + +class C(D): + def bar(self): + print 'C.bar' + + +class B(D): + def bar(self): + print 'hhhhh' + print 'B.bar' + + +class A(B, C): + pass + # def bar(self): + # print 'A.bar' + + +a = A() +a.bar() + +print A.__mro__ # 报错 ,__mro__只对新式类有效 + +``` + +执行bar方法时 +首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去D类中找,如果D类中么有,则继续去C类中找,如果还是未找到,则报错 + +所以,查找顺序:**A --> B --> D --> C** + +在上述查找bar方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了 + + +新式类-python3.6+ +```python + class D(object): + def bar(self): + print('D.bar') + + +class C(D): + def bar(self): + print('C.bar') + + +class B(D): + def bar(self): + print('B.bar') + + +class A(B, C): + pass + # print('A.bar') + + +a = A() +a.bar() + +# 方法解析顺序Method Resolution Order - MRO +print(A.__mro__) +# (, , , , ) + + + +``` + + +执行bar方法时 +首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去C类中找,如果C类中么有,则继续去D类中找,如果还是未找到,则报错 + +所以,查找顺序:**A --> B --> C --> D** + + 在上述查找bar方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了 + + +再看一个例子: +```python +class D: + def __init__(self): + self.a = 1 + + +class B(D): + pass + + +class C(B): + def __init__(self): + self.a = 2 + + +class A(B,C): + pass + + +a = A() +print (a.a) # 1 +``` + +```python +class D(object): + def __init__(self): + self.a = 1 + + +class B(D): + pass + + +class C(D): + def __init__(self): + self.a = 2 + + +class A(B,C): + pass + + +a = A() +print (a.a) # 2 +``` + +##### 多态 + +Pyhon不支持Java和C#这一类强类型语言中多态的写法,但是原生多态,其Python崇尚“鸭子类型”。 + + +```python +class Animal(object): + def run(self): + print('Animal is running...') + + +class Dog(Animal): + pass + + +class Cat(Animal): + pass + + +c = Dog() +b = Animal() + +isok = isinstance(c, Animal) +print(isok) + +isok = isinstance(b, Dog) +print(isok) + + +def run_twice(animal): + animal.run() + animal.run() + + +class Tortoise(Animal): + def run(self): + print('Tortoise is running slowly...') + + +class people(object): + def run(self): + print("People run...") + + +isok = run_twice(Tortoise()) +print(isok) + +# people这个类却和animal没有任何关系,但是其中却有run这个方法 +# 鸭子类型 +isok = run_twice(people()) +print(isok) +``` + + +### 进阶篇 + + +#### 类的成员 +类的成员可以分为三大类:**`字段、方法和属性`** + +##### 字段 + +字段包括:普通字段和静态字段,他们在定义和使用中有所区别,而最本质的区别是内存中保存的位置不同, + +* **`普通字段属于对象`** +* **`静态字段属于类`** + +```python +class Province: + # 静态字段 + country = '中国' + + def __init__(self, name): + # 普通字段 + self.name = name + + +# 直接访问普通字段 +obj = Province('河北省') +print(obj.name) + +# 直接访问静态字段 +print(Province.country) + +``` + + +##### 方法 + + +##### 属性 + +如果你已经了解Python类中的方法,那么属性就非常简单了,因为Python中的属性其实是普通方法的变种。 +对于属性,有以下三个知识点: + +* 属性的基本使用 +* 属性的两种定义方式 + + +```python +class Goods(object): + @property + def price(self): + print("100") + + +obj = Goods() +obj.price +``` +由属性的定义和调用要注意一下几点: +* 定义时,在普通方法的基础上添加 @property 装饰器; +* 定义时,属性仅有一个self参数 + +* 调用时,无需括号 +方法:foo_obj.func() +属性:foo_obj.prop + + +注意:属性存在意义是:访问属性时可以制造出和访问字段完全相同的假象 + +属性由方法变种而来,如果Python中没有属性,方法完全可以代替其功能 + + + +1.特殊的类属性 + +`class.__class__` 类的类型 +`class.__bases__` 父类名称 +`class.__dict__` 类所有属性 +`class.__module__` 类所在的模块 + + + +2.特殊的实例属性 +`__class__` 实例所对应的类 +`__dict__` 实例的属性 + + + +
+ +###### 属性的两种定义方式 + +属性的定义有两种方式: + +* 装饰器 即:在方法上应用装饰器 +* 静态字段 即:在类中定义值为property对象的静态字段 + +装饰器方式:在类的普通方法上应用@property装饰器 + +静态字段方式,创建值为property对象的静态字段 +当使用静态字段的方式创建属性时,经典类和新式类无区别 + +```python +class Foo(object): + def get_bar(self): + return 'hello' + + BAR = property(get_bar) + + +obj = Foo() +reuslt = obj.BAR # 自动调用get_bar方法,并获取方法的返回值 +print(reuslt) + +``` + + +property的构造方法中有个四个参数 +>* 第一个参数是方法名,调用 对象.属性 时自动触发执行方法 +>* 第二个参数是方法名,调用 对象.属性 = XXX 时自动触发执行方法 +>* 第三个参数是方法名,调用 del 对象.属性 时自动触发执行方法 +>* 第四个参数是字符串,调用 对象.属性.`__doc__` ,此参数是该属性的描述信息 + + +```python +class Foo(object): + def get_bar(self): + return 'hello' + + # 必须两个参数 + def set_bar(self, value): + return 'set value' + value + + def del_bar(self): + return 'hello' + + BAR = property(get_bar, set_bar, del_bar, 'description...') + + +obj = Foo() + +print(obj.BAR) # 自动调用第一个参数中定义的方法:get_bar +obj.BAR = "alex" # 自动调用第二个参数中定义的方法:set_bar方法,并将“alex”当作参数传入 +print(obj.BAR) + +print(obj.BAR.__doc__) # 自动获取第四个参数中设置的值:description... +del Foo.BAR # 自动调用第三个参数中定义的方法:del_bar方法 + +``` +
+实例属性和类属性关系 + +记住: +>**实例属性不能左右类属性** + +>**但是类属性可以左右实例属性** + +```python +class A(object): + x = 7 + + +foo = A() +foo.x += 7 +print(A.x) # 7 +print(foo.x) # 14 + +print("####") +A.x = A.x + 1 +print(A.x) # 8 +print(foo.x) # 14 + + +``` + +`也有特例`: +```python +class B(object): + y = [1, 2, 3] + + +bar = B() +bar.y.append(4) + +print(bar.y) # [1, 2, 3, 4] +print(B.y) # [1, 2, 3, 4] + +``` + +
+ +##### 公有成员和私有成员 + + +>* `公有成员,在任何地方都能访问` +>* `私有成员,只有在类的内部才能方法` + + +私有成员和公有成员的定义不同:**私有成员命名时,前两个字符是下划线**。 +(特殊成员除外,例如:`__init__、__call__、__dict__`等) + + +```python +class C(object): + def __init__(self): + self.name = '公有字段' + self.__foo = "私有字段" +``` + +
+ +1. 静态字段 + +>* `公有静态字段:类可以访问;类内部可以访问;派生类中可以访问` + +>* `私有静态字段:仅类内部可以访问` + +```python +class C(object): + + name = "公有静态字段" + + def func(self): + print(C.name) + + +class D(C): + def show(self): + print(C.name) + + +C.name # 类访问 + +obj = C() +obj.func() # 类内部可以访问 + +obj_son = D() +obj_son.show() # 派生类中可以访问 + +``` + + +```python +class C(object): + + __name = "公有静态字段" + + def func(self): + print(C.__name) + + +class D(C): + def show(self): + print(C.__name) + + +C.__name # 类访问 ==> 错误 + +obj = C() +obj.func() # 类内部可以访问 ==> 正确 + +obj_son = D() +obj_son.show() # 派生类中可以访问 ==> 错误 + +``` +
+ +2.普通字段 + +>* **`公有普通字段:对象可以访问;类内部可以访问;派生类中可以访问`** +>* **`私有普通字段:仅类内部可以访问`** + + +```python +class C(object): + def __init__(self): + self.foo = "公有字段" + + def func(self): + print(self.foo) # 类内部访问 + + +class D(C): + def show(self): + print(self.foo) # 派生类中访问 + + +obj = C() + +obj.foo # 通过对象访问 +obj.func() # 类内部访问 + +obj_son = D() +obj_son.show() # 派生类中访问 + +``` + +```python +class C: + def __init__(self): + self.__foo = "私有字段" + + def func(self): + print(self.foo) # 类内部访问 + + def __doSomething(self): + print("do something") + + +class D(C): + def show(self): + print(self.foo) # 派生类中访问 + + +obj = C() + +obj.__foo # 通过对象访问 ==> 错误 +obj.func() # 类内部访问 ==> 正确 + +obj_son = D() +obj_son.show() # 派生类中访问 ==> 错误 + +# 强制访问私有字段-不建议这样使用 +print(obj._C__foo) + +# 访问私有方法 +print(obj._C__doSomething()) + +``` + +
+ +总结: + +> 在python里,标识符有字母、数字、下划线组成。 + +>在python中,所有标识符可以包括英文、数字以及下划线(_),但不能以数字开头。 +>python中的标识符是**区分大小写**的。 + +>以下划线开头的标识符是有特殊意义的。以**单下划线开头(`_foo`)**的代表不能直接访问的类属性,需通过类提供的接口进行访问,不能用"from xxx import *"而导入; + +>以双下划线开头的(`__foo`)代表类的私有成员 + +>以双下划线开头和结尾的(`__foo__`)代表python里特殊方法专用的标识,如`__init__()`代表类的构造函数。 + + +#### 特殊的类的成员 + + +##### `__module__ 和 __class__` + + +>`__module__` 表示当前操作的对象在那个模块 +>`__class__` 表示当前操作的对象的类是什么 + + +demo.py 单独访问: +可以看到`__module__`表示的main模块,`__class__`表示main模块中的Test类 + +```python +class Test(object): + def __init__(self): + self.name = "tony" + + +obj = Test() +print(obj.__module__) # __main__ +print(obj.__class__) # + +``` +
+被其他文件引用: +可以看到`__module__`表示的demo模块,`__class__`表示demo模块中的Test类 + +```python +from demo import Test + +obj = Test() +print(obj.__module__) # demo + +print(obj.__class__) # +``` + + +##### `__init__` + +构造方法,通过类创建对象时,自动触发执行。 + +```python +class Test(object): + def __init__(self, name): + self.name = name + + +test = Test("tony") # 自动执行类中的__init__方法 +``` + +
这里要`注意`: +```python +class Test(object): + def __init__(self, name): + self.name = name + # return "111" # 问题:能不能给它加个返回值? + # 默认的返回值 + # <__main__.Test object at 0x000001E27BA18518> + + +test = Test("tony") # 自动执行类中的__init__方法 +print(test) +# TypeError: __init__() should return None, not 'str' + +``` + +
+ +##### `__del__` + +析构方法,当对象在内存中被释放时,自动触发执行。 + +>此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行 + +>所以,**析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。** + +```python +class Test(object): + def __del__(self): + print("I am deleted...") + + +test = Test() +del test # I am deleted... + +``` + +
+ +##### `__call__` + + +对象后面加括号,触发执行。 + +>构造方法的执行是由创建对象触发的,即:对象 = 类名() +而对于` __call__ `方法的执行是由对象后加括号触发的,即:对象() 或者 类()() + +```python +class Foo(object): + def __init__(self): + print('init') + + def __call__(self): + print('call') + + +obj = Foo() +print(obj) # 执行__init__方法 +# init +# <__main__.Foo object at 0x000001EC6ACC8470> + +print(obj()) # 执行__call__方法 + +``` + +
+ +##### `__dict__` + +类或对象中的所有成员 + +```python +class Province: + + country = 'China' + + def __init__(self, name, count): + self.name = name + self.count = count + + def func(self, *args, **kwargs): + print('func') + + +# 获取类的成员,即:静态字段、方法、 +print(Province.__dict__) + +obj1 = Province('HeBei', 10000) +print(obj1.__dict__) +# 获取 对象obj1 的成员 +# 输出:{'count': 10000, 'name': 'HeBei'} + +obj2 = Province('HeNan', 3888) +print(obj2.__dict__) +# 获取 对象obj2 的成员 +# 输出:{'count': 3888, 'name': 'HeNan'} +``` + +
+ +##### ` __getitem__&__setitem__&__delitem__` + +用于索引操作,如字典。以上分别表示获取、设置、删除数据 + + +```python +class Foo(object): + def __getitem__(self, key): + print('__getitem__', key) + + def __setitem__(self, key, value): + print('__setitem__', key, value) + + def __delitem__(self, key): + print('__delitem__', key) + + +obj = Foo() + +result = obj['k1'] # 自动触发执行 __getitem__ +obj['k1'] = 'tony' # 自动触发执行 __setitem__ +del obj['k1'] # 自动触发执行 __delitem__ + +print(obj['k1']) # 自动触发执行 __getitem__ + +``` + +
+ +##### ` __getslice__&__setslice__&__delslice__` + +该三个方法用于分片操作,如:列表 + + **`仅限于python2.6+`** +~~python3已废弃这些方法~~ + +python3 已经变成`__getitem__`&`__setitem__`&`__delitem__` + +
+ +##### ` __iter__` + +用于迭代器,之所以列表、字典、元组可以进行for循环,是因为类型内部定义了` __iter__` + +```python +class Foo(object): + pass + + +# obj = Foo() + +# for i in obj: +# print(i) # TypeError: 'Foo' object is not iterable + + +class FooX(object): + def __iter__(self): + pass + + +objx = FooX() + +# for i in objx: +# print(i) # TypeError: iter() returned non-iterator of type 'NoneType' + + +class FooY(object): + def __init__(self, sq): + self.sq = sq + + def __iter__(self): + return iter(self.sq) + + +objy = FooY([11, 22, 33, 44]) + +for i in objy: + print(i) + +# 11 +# 22 +# 33 +# 44 + +# 也可也写成 +bj = iter([11, 22, 33, 44]) + +for i in bj: + print(i) + +``` + +
+ +##### ` __new__` + +`__new__()`: 构造方法 + + +**1.如果有`__new__`和`__init__`都存在, 优先找`__new__`** + + +这两个方法是用来创建object的子类对象,静态方法`__new__()`用来**创建类的实例**, + +然后再调用`__init__()`来**初始化实例**。 + +```python +class A(object): + def __init__(self, *args, **kwargs): + print("init %s" % self.__class__) + + def __new__(cls, *args, **kwargs): + print("new %s" % cls) + return object.__new__(cls, *args, **kwargs) + + +a = A() +a + +# new +# init + +``` + +
+ +**`2.其他区别`** + +`__new__`方法默认返回实例对象供`__init__`方法、实例方法使用。 + +```python +class Foo(object): + price = 50 + + def __new__(cls, *agrs, **kwds): + inst = object.__new__(cls, *agrs, **kwds) + return inst + + def how_much_of_book(self, n): + print(self) + return self.price * n + + +foo = Foo() +print(foo.how_much_of_book(8)) +# Foo类中重载了__new__方法,它的返回值为Foo类的实例对象 +# <__main__.Foo object at 0x0000017BF8678630> +# 400 +``` + +
+`__init__ `方法为初始化方法,为类的实例提供一些属性或完成一些动作 + +```python +class Foo(object): + def __new__(cls, *agrs, **kwds): + inst = object.__new__(cls, *agrs, **kwds) + return inst + + def __init__(self, price=50): + self.price = price + + def how_much_of_book(self, n): + print(self) + return self.price * n + + +foo = Foo() +print(foo.how_much_of_book(8)) +``` + +
+ +**3.几点注意事项** + +`__new__ `方法创建实例对象供`__init__ `方法使用,`__init__`方法定制实例对象。 + +`__new__` 方法必须返回值,`__init__`方法不需要返回值。(如果返回非None值就报错) + +
+**4. 一般用不上`__new__`方法** + +`__new__`方法可以用在下面二种情况。 + +1> 继承不可变数据类型时需要用到`__new__`方法(like int, str, or tuple) 。 + +```python +class Inch(float): + def __new__(cls, arg=0.0): + return float.__new__(cls, arg * 0.0254) + + +print(Inch(12)) +``` + + +2>用在元类,定制创建类对象 + +元类: + +当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。 + +但是如果我们想创建出类呢?那就必须根据metaclass创建出类,所以:先定义metaclass,然后创建类。 + +**连接起来就是:先定义metaclass,就可以创建类,最后创建实例。** + +```python +# metaclass是类的模板,所以必须从`type`类型派生: +class MetaClass(type): + """ + 参数必须是这四个 + cls:当前准备创建的类 + name:类的名字 + bases:类的父类集合 + attrs:类的属性和方法,是一个字典 + """ + def __new__(cls, name, bases, attrs): + print("Allocating memory for class", name) + print("new cls=", cls) + print("new name=", name) + print("new bases=", bases) + print("new attrs=", attrs) + return super(MetaClass, cls).__new__(cls, name, bases, attrs) + + def __init__(cls, name, bases, attrs): + print("Initializing class", name) + print("int cls=", cls) + print("int name=", name) + print("int bases=", bases) + print("int attrs=", attrs) + super(MetaClass, cls).__init__(name, bases, attrs) + + +class Myclass(metaclass=MetaClass): + def foo(self, param): + print(param) + + +p = Myclass() +p.foo("hello") + +``` \ No newline at end of file diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\346\212\275\350\261\241\347\261\273.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\346\212\275\350\261\241\347\261\273.md" new file mode 100644 index 00000000..76d74786 --- /dev/null +++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\346\212\275\350\261\241\347\261\273.md" @@ -0,0 +1,240 @@ +# 第11章-抽象类 + + + + +- [第11章-抽象类](#%e7%ac%ac11%e7%ab%a0-%e6%8a%bd%e8%b1%a1%e7%b1%bb) + - [ABC类](#abc%e7%b1%bb) + + +## ABC类 + +python中并没有提供抽象类与抽象方法, +但是提供了内置模块**abc(abstract base class)**来模拟实现抽象类。 + +可以通过abc将基类声明为抽象类的方式,然后注册具体类作为这个基类的实现。 + + +假设我们在写一个关于动物的代码。涉及到的动物有鸟,狗,牛。 + +首先鸟,狗,牛都是属于动物的。既然是动物那么肯定需要吃饭,发出声音。 + +但是具体到鸟,狗,牛来说吃饭和声音肯定是不同的。 + +需要具体去实现鸟,狗,牛吃饭和声音的代码。 + +概括一下抽象基类的作用:定义一些共同事物的规则和行为 + + +例子: + +```python +class Animal(object): + def eat(self): + raise NotImplementedError + + def voice(self): + raise NotImplementedError + + +class Dog(Animal): + def voice(self): + print('wow....') + + +class Bird(Animal): + def voice(self): + print('jiji....') + + +class Cow(Animal): + def voice(self): + print('Oh.....') + + +if __name__ == "__main__": + + d = Dog() + d.voice() + d.eat() # raise NotImplementedError + +``` + +这样实现有个缺点,就是只有子类调用eat方法的时候才会报错。 + +子类是可以正常实例化的。 + +但是你能够想象鸟,狗,牛不会吃饭么? 如果不会吃饭那肯定不算是动物了。 + +所以正常的实现应该是如果没有实现eat方法,实例化就应该是失败的。 + +那么这里就要用到抽象基类的一般使用方法. + + +```python +import abc + + +class Animal(object): + + __metaclass__ = abc.ABCMeta + + @abc.abstractmethod + def eat(self): + return + + @abc.abstractmethod + def voice(self): + return + + +class Dog(Animal): + def voice(self): + print('wow....') + + def eat(self): + print("Dog eat...") + + +class Bird(Animal): + def voice(self): + print('jiji....') + + def eat(self): + print("Bird eat...") + + +class Cow(Animal): + def voice(self): + print('Oh.....') + + def eat(self): + print("Cow eat...") + + +if __name__ == "__main__": + + d = Dog() + b = Bird() + c = Cow() + + d.voice() + d.eat() + + b.voice() + b.eat() + + c.voice() + c.eat() + +``` + +返回结果: +``` +wow.... +Dog eat... +jiji.... +Bird eat... +Oh..... +Cow eat... +``` + + +使用注册的方式: + +```python +from abc import ABCMeta, abstractmethod + + +class Animal(metaclass=ABCMeta): + @abstractmethod + def eat(self): + return + + @abstractmethod + def voice(self): + return + + +class Dog(object): + def voice(self): + print('wow....') + + def eat(self): + print("Dog eat...") + + +# 使用注册方式,必须两个方法都要写,否则会报错 +Animal.register(Dog) + +if __name__ == "__main__": + + d = Dog() + + d.voice() + d.eat() + +``` + +
+ +**`继承和注册这两种方法有什么区别呢?`** + + +animals.py +```python +from abc import ABCMeta, abstractmethod + + +class Animal(metaclass=ABCMeta): + @abstractmethod + def eat(self): + return + + @abstractmethod + def voice(self): + return + +``` + +sub_class.py +```python +from animals import Animal + + +class Dog(Animal): + def voice(self): + print('wow....') + + def eat(self): + print("Dog eat...") + + +print('subclass=', issubclass(Dog, Animal)) +print('Instance=', isinstance(Dog(), Animal)) + +``` + +abc_register.py +```python +from animals import Animal + + +class Dog(object): + def voice(self): + print('wow....') + + def eat(self): + print("Dog eat...") + + +Animal.register(Dog) + +print('subclass=', issubclass(Dog, Animal)) +print('Instance=', isinstance(Dog(), Animal)) +``` + + + + + diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\346\226\207\346\234\254\345\222\214\345\255\227\350\212\202\345\272\217\345\210\227.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\346\226\207\346\234\254\345\222\214\345\255\227\350\212\202\345\272\217\345\210\227.md" new file mode 100644 index 00000000..23169388 --- /dev/null +++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\346\226\207\346\234\254\345\222\214\345\255\227\350\212\202\345\272\217\345\210\227.md" @@ -0,0 +1,317 @@ +# 第4章 文本和字节序列 + + + +- [第4章 文本和字节序列](#%e7%ac%ac4%e7%ab%a0-%e6%96%87%e6%9c%ac%e5%92%8c%e5%ad%97%e8%8a%82%e5%ba%8f%e5%88%97) + - [编码和解码](#%e7%bc%96%e7%a0%81%e5%92%8c%e8%a7%a3%e7%a0%81) + - [chardet模块](#chardet%e6%a8%a1%e5%9d%97) + - [json与字典区别](#json%e4%b8%8e%e5%ad%97%e5%85%b8%e5%8c%ba%e5%88%ab) + - [json模块和simplejson模块](#json%e6%a8%a1%e5%9d%97%e5%92%8csimplejson%e6%a8%a1%e5%9d%97) + + + +## 编码和解码 + +> markdom可以插入emoji表情包 +其中&#作为前缀,x1f54为对应表情的unicode编码 +🥺 +[emoji](https://unicode.org/Public/emoji/12.1/emoji-data.txt) + + +把码位转换成字节序列的过程是编码;把字节序列转换成码位的过程是解码 + +> decode的作用是将其他编码的字符串转换成unicode编码 +> 如str1.decode('gb2312'),表示将gb2312编码的字符串转换成unicode编码。 +> +> encode的作用是将unicode编码转换成其他编码的字符串 +> 如str2.encode('gb2312'),表示将unicode编码的字符串转换成gb2312编码。 + + +**`Python 3默认使用UTF-8编码源码`,Python 2(从2.5开始)则默认使用ASCII。** +如果加载的.py模块中包含UTF-8之外的数据,而且没有声明编码,会报错 + +```sequence +title: python 2 编解码过程 +编码前其他编码字符串->unicode: 编码 decode +unicode->>编码后其他编码字符串:解码 encode +``` + +>**在新版本的python3中,取消了unicode类型,代替它的是使用unicode字符的字符串类型(str),字符串类型(str)成为基础类型** + +如下所示,而编码后的变为了字节类型(bytes) + +```sequence +title: python 3 编解码过程 + +编码前其他编码字符串->str(unicode): 编码 decode +str(unicode)->>编码后其他编码字符串:解码 encode +``` + + +```python +# 指定字符串类型对象u +u = "中文" +print(u, type(u)) + +# 以gb2312编码对u进行编码,获得bytes类型对象str +str = u.encode('gb2312') +print(str, type(str)) + +# 以gb2312编码对字符串str进行解码,获得字符串类型对象u1 +u1 = str.decode('gb2312') +print(u1, type(u1)) +``` + +返回结果: +``` +中文 +b'\xd6\xd0\xce\xc4' +中文 + +``` +
+ +## chardet模块 + +> **chardet模块检测读取出来的str是什么编码格式的** + +```python +from urllib.request import urlopen +import chardet + +rawdata = urlopen('http://baidu.com/').read() +chr = chardet.detect(rawdata) +print(chr) # {'encoding': 'ascii', 'confidence': 1.0, 'language': ''} +``` +
+json.dumps在默认情况下,对于非ascii字符生成的是相对应的字符编码,而非原始字符. + +>首先明确,chardet这个是**`判断编码`**而不是判断数据类型的, + +>其次,加encode只是将其变成了字节 + +**chardet只能够接受字节而不能接收字符类型的** + +```python +import json +import chardet + +dict1 = {"haha": "哈哈"} + +# json.dumps 默认ascii编码 +print(json.dumps(dict1)) # {"haha": "\u54c8\u54c8"} + +# 禁止ascii编码后默认utf-8 +print(json.dumps(dict1, ensure_ascii=False)) # {"haha": "哈哈"} + +# ascii +ss = chardet.detect(json.dumps(dict1).encode()) +print(ss) + +# utf-8 +ss = chardet.detect(json.dumps(dict1, ensure_ascii=False).encode()) +print(ss) + +``` + +
+ +## json与字典区别 + +在python中,字典的输出内容跟json格式内容一样,但是字典的格式是字典,json的格式是字符串,所以在传输的时候(特别是网页)要转换使用。 + +`重要函数` +>编码:**把一个Python对象编码转换成Json字符串 json.dumps()** + +>解码:**把Json格式字符串解码转换成Python对象 json.loads()** + +举个例子: +```python +import json +dic = {'str': 'this is a string', 'list': [1, 2, 'a', 'b']} +print(type(dic)) # +print(dic) + +json_obj = json.dumps(dic) +print(type(json_obj)) # +print(json_obj) + +dic1 = json.loads(json_obj) +print(type(dic1)) # +print(dic1) + +``` +
+ +`几个主要函数` +>dump,dumps,load,loads + +>**带s跟不带s的区别是 带s的是对 字符串的处理,而不带 s的是对文件对像的处理。** + + +```python +import json +from io import StringIO + +# 创建文件流对象 +io = StringIO() + +# 把 json编码数据导向到此文件对象 +json.dump(['streaming API'], io) + +# 取得文件流对象的内容 +print(io.getvalue()) # ["streaming API"] +``` + +
+ +**参数ensure_ascii** +>默认为True,所有的非ascii字符在输出时都会被转义为\uxxxx的序列, +返回的对象是一个只由ascii字符组成的str类型,为False时不会进行转义输出,反回的对象是个unicode。(**这个参数对包含中文的json输出时非常有用**) + + +
+举个例子: + +```python +import json + +data = {u'我': u'是', u'美': u'女'} + +print(json.dumps(data)) # {"\u6211": "\u662f", "\u7f8e": "\u5973"} + +print(json.dumps(data, ensure_ascii=False)) # {"我": "是", "美": "女"} + +``` +
+处理中文json时,要想不每次都给一堆重复的参数,可以用partial + +```python +import json +from functools import partial + +json_dumps = partial(json.dumps, ensure_ascii=False, sort_keys=True) + +data = {u'我': u'是', u'美': u'女'} + +print(json_dumps(data)) # {"我": "是", "美": "女"} + +``` +
+ +看一个例子: +```python +import json + +# 使用双引号、单引号都可以的,建议使用双引号 +h = '{"foo":"bar", "foo2":"bar2"}' +d = "{'muffin' : 'lolz', 'foo' : 'kitty'}" + +json_obj1 = json.dumps(h) +json_obj2 = json.dumps(d) + +dic1 = json.loads(json_obj1) +dic2 = json.loads(json_obj2) + +print("json_obj1=", json_obj1) +print("json_obj2=", json_obj2) +print("dic1=", dic1) +print("dic1 type=", type(dic1)) +print("dic2=", dic2) +print("dic2 type=", type(dic2)) +``` +
+ +总结: +>**Python 的字典是一种数据结构,JSON 是一种数据格式。** +>**Python的字典key可以是任意可hash对象,json只能是字符串** + + +
+ +## json模块和simplejson模块 + +
+ +环境: +**python 3.6+** +**json: 2.0.9** +**simplejson: 3.16.0** + +
+ +* 区别1 + +```python +import json +import simplejson + +json_str = b'hello' +json_str = json_str.decode() # 使用json模块必须要先加入这一行否则会报错 +print(json.dumps(json_str)) +print(simplejson.dumps(json_str)) +``` + +比较性能方面的差异: + +[压测数据包链接](http://abral.altervista.org/jsonpickle-bench.zip) + +压测脚本: +```python +import timeit +import simplejson +import pickle +import json + +times = 100 +print("Times are for %i iterations" % times) + +f = open('words.pickle', "rb") +words = pickle.load(f) +f.close() + + +def bench(cmd, imprt): + t = timeit.Timer(cmd, imprt) + s = t.timeit(number=times) + print("%s total=%02f avg=%02f" % (cmd, s, (s / times))) + return s + + +def simplejson_loads(): + simplejson.loads(simplejson.dumps(words)) + + +def simplejson_dumps(): + simplejson.dumps(words) + + +b1 = bench('simplejson_loads()', 'from __main__ import simplejson_loads') +b2 = bench('simplejson_dumps()', 'from __main__ import simplejson_dumps') + + +def json_dumps(): + json.dumps(words) + + +def json_loads(): + json.loads(json.dumps(words)) + + +b3 = bench('json_loads()', 'from __main__ import json_loads') +b4 = bench('json_dumps()', 'from __main__ import json_dumps') + +``` + + +返回结果是: +``` +Times are for 100 iterations +simplejson_loads() total=1.760785 avg=0.017608 +simplejson_dumps() total=0.921571 avg=0.009216 +json_loads() total=1.351725 avg=0.013517 +json_dumps() total=0.716219 avg=0.007162 +``` + +> `从上面结果可以看到json 在loads操作和dumps操作都比simplejson好` +~~网上说的simplejson比json快,感觉说的不对~~ diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\346\255\243\347\241\256\351\207\215\350\275\275\350\277\220\347\256\227\347\254\246.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\346\255\243\347\241\256\351\207\215\350\275\275\350\277\220\347\256\227\347\254\246.md" new file mode 100644 index 00000000..ef55c8ca --- /dev/null +++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\346\255\243\347\241\256\351\207\215\350\275\275\350\277\220\347\256\227\347\254\246.md" @@ -0,0 +1,59 @@ +# 第13章 正确重载运算符 + + +- [第13章 正确重载运算符](#%e7%ac%ac13%e7%ab%a0-%e6%ad%a3%e7%a1%ae%e9%87%8d%e8%bd%bd%e8%bf%90%e7%ae%97%e7%ac%a6) + - [几个不常用的运算符](#%e5%87%a0%e4%b8%aa%e4%b8%8d%e5%b8%b8%e7%94%a8%e7%9a%84%e8%bf%90%e7%ae%97%e7%ac%a6) + + + +## 几个不常用的运算符 + +这里我们看几个之前没讲过的运算符`__neg__,__pos__,__invert__` + +`__neg__`是在-v的时候调用 + +`__pos__`是在+v的时候调用 + +`__invert__`是在~v的时候调用 + + +看下面的例子: + +```python +class Vector(object): + def __init__(self, x): + self.x = x + + def __neg__(self): + return "Vector(%d)" % (-self.x) + + def __str__(self): + return "Vector(%s)" % (str(self.data)) + + def __iter__(self): + return iter(self.data) + + def __pos__(self): + return "Vector(%d)" % (self.x + 1) + + def __invert__(self): + return "Vector(%d)" % (~self.x) + + +if __name__ == "__main__": + + v = Vector(1) + + print(-v) # Vector(-1) + print(+v) # Vector(2) + print(~v) # Vector(-2) + +``` + +返回结果: +``` +Vector(-1) +Vector(2) +Vector(-2) + +``` diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\347\254\246\345\220\210Python\351\243\216\346\240\274\347\232\204\345\257\271\350\261\241.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\347\254\246\345\220\210Python\351\243\216\346\240\274\347\232\204\345\257\271\350\261\241.md" new file mode 100644 index 00000000..18cb8588 --- /dev/null +++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\347\254\246\345\220\210Python\351\243\216\346\240\274\347\232\204\345\257\271\350\261\241.md" @@ -0,0 +1,244 @@ +# 第9章-符合Python风格的对象 + + + +- [第9章-符合Python风格的对象](#%e7%ac%ac9%e7%ab%a0-%e7%ac%a6%e5%90%88python%e9%a3%8e%e6%a0%bc%e7%9a%84%e5%af%b9%e8%b1%a1) + - [format表示法](#format%e8%a1%a8%e7%a4%ba%e6%b3%95) + - [__slots__方法](#slots%e6%96%b9%e6%b3%95) + + + +## format表示法 + +```python + +>>> print "Hello %(name)s !" % {'name': 'James'} +Hello James ! + +>>> print "I am years %(age)i years old" % {'age': 18} +I am years 18 years old + +#format的写法: + +>>> print "Hello {name} !".format(name="James") +Hello James ! +``` + + +## `__slots__`方法 + + +Python内置属性很多,其中`__slots__`属性比较少会被用到,基本不会被当作是必选项。但如果您对内存使用有颇高的要求,`__slots__`会给你很大帮助。 + +`__slots__`的目的又是什么呢? + +答案是:优化内存使用。限制实例属性的自由添加只是副作用而已。 + +那么`__slots__`属性究竟如何神奇? +这要先从`__dict_`_属性说起。 + + +` __dict__`属性的用途在于记录实例属性: + +`__dict__`属性用来记录实例中的属性信息,如果对实例中的属性信息进行修改或者添加新的属性,`__dict__`都会对其进行记录。 + +```python +class Person(object): + def __init__(self, name, age): + self.name = name + self.age = age + + +person = Person("tony", 20) # 对Person类实例化 +print(person.__dict__) # 记录实例所有的属性 {'name': 'tony', 'age': 20} + +person.age = 'jacky' +person.gender = 'male' + +print(person.__dict__) # {'name': 'tony', 'age': 'jacky', 'gender': 'male'} + +``` + + + +那么`__slots__`跟`__dict__`又有什么关系呢? + +简单点理解: + + `__slots_`_存在的价值在于删除`__dict__`属性,从而来**优化类实例对内存的需求**。 + +而这带来的副作用就是:由于缺少了`__dict__`,类实例木有办法再自由添加属性了! + + +```python +class Person(object): + __slots__ = ("name", "age") + + def __init__(self, name, age): + self.name = name + self.age = age + + +person = Person("tony", 20) # 对Person类实例化 + +print("__dict__" in dir(person)) # False, 没有了__dict__属性 + +person.age = 'jacky' +person.gender = 'male' # AttributeError: 'Person' object has no attribute 'gender' +# 这就是__slots__的副作用,不能自由添加实例属性了 + +``` + + +总结: + +默认情况下,自定义的对象都使用dict来存储属性(通过obj.`__dict__`查看),而python中的dict大小一般比实际存储的元素个数要大(以此降低hash冲突概率),因此会浪费一定的空间。 + +在新式类中使用`__slots__`,就是告诉Python虚拟机,这种类型的对象只会用到这些属性, +因此虚拟机预留足够的空间就行了 + +如果声明了`__slots__`,那么对象就不会再有`__dict__`属性。 + + +到底能省多少,取决于类自身有多少属性、属性的类型,以及同时存在多少个类的实例 + + +可以查看这个[链接](http://tech.oyster.com/save-ram-with-python-slots/) + + + +我们也可以自己测试下结果: + +```python +# 使用 profile 进行性能分析 +import profile + + +class A(object): + def __init__(self, x, y): + self.x = x + self.y = y + + +def profile_result(): + f = [A(1, 2) for i in range(100000)] + + +if __name__ == "__main__": + profile.run("profile_result()") +``` + + +返回结果: +``` + 100006 function calls in 0.297 seconds + + Ordered by: standard name + + ncalls tottime percall cumtime percall filename:lineno(function) + 1 0.000 0.000 0.297 0.297 :0(exec) + 1 0.000 0.000 0.000 0.000 :0(setprofile) + 1 0.016 0.016 0.297 0.297 :1() + 1 0.000 0.000 0.281 0.281 demo.py:11(profile_result) + 1 0.141 0.141 0.281 0.281 demo.py:12() + 100000 0.141 0.000 0.141 0.000 demo.py:6(__init__) + 1 0.000 0.000 0.297 0.297 profile:0(profile_result()) + 0 0.000 0.000 profile:0(profiler) + +``` + + +这里返回值参数意思: +``` +其中输出每列的具体解释如下: + +ncalls:表示函数调用的次数; + +tottime:表示指定函数的总的运行时间,除掉函数中调用子函数的运行时间; + +percall:(第一个 percall)等于 tottime/ncalls; + +cumtime:表示该函数及其所有子函数的调用运行的时间,即函数开始调用到返回的时间; + +percall:(第二个 percall)即函数运行一次的平均时间,等于 cumtime/ncalls; + +filename:lineno(function):每个函数调用的具体信息; +``` + + +查看内存占用情况: + +```python +# 使用 profile 进行性能分析 +from memory_profiler import profile + + +class A(object): + def __init__(self, x, y): + self.x = x + self.y = y + + +@profile +def profile_result(): + f = [A(1, 2) for i in range(100000)] + + +if __name__ == "__main__": + profile_result() + +``` + +返回结果: +```python +Filename: d:\学习\python\demo\demo.py +Line # Mem usage Increment Line Contents +================================================ + 11 50.8 MiB 50.8 MiB @profile + 12 def profile_result(): + 13 68.8 MiB 0.6 MiB f = [A(1, 2) for i in range(100000)] + + +``` + +
+改用`__slots__`的方式: + +```python +# 使用 profile 进行性能分析 +from memory_profiler import profile + + +class A(object): + __slots__ = ('x', 'y') + + def __init__(self, x, y): + self.x = x + self.y = y + + +@profile +def profile_result(): + f = [A(1, 2) for i in range(100000)] + + +if __name__ == "__main__": + profile_result() + +``` + + +返回结果: + +``` +Filename: d:\学习\python\demo\demo.py +Line # Mem usage Increment Line Contents +================================================ + 13 50.6 MiB 50.6 MiB @profile + 14 def profile_result(): + 15 57.9 MiB 0.7 MiB f = [A(1, 2) for i in range(100000)] + +``` +
+ +**可以看到内存使用由原先的68.8MB->57.9MB** \ No newline at end of file diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\347\261\273\345\205\203\347\274\226\347\250\213.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\347\261\273\345\205\203\347\274\226\347\250\213.md" new file mode 100644 index 00000000..83baddc4 --- /dev/null +++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\347\261\273\345\205\203\347\274\226\347\250\213.md" @@ -0,0 +1,15 @@ +# 第21章 类元编程 + + + +- [第21章 类元编程](#%e7%ac%ac21%e7%ab%a0-%e7%b1%bb%e5%85%83%e7%bc%96%e7%a8%8b) + - [类工厂函数 collections.namedtuple](#%e7%b1%bb%e5%b7%a5%e5%8e%82%e5%87%bd%e6%95%b0-collectionsnamedtuple) + - [ABC元类](#abc%e5%85%83%e7%b1%bb) + + +## 类工厂函数 collections.namedtuple + + +## ABC元类 + +这些内容可以查看第11章 \ No newline at end of file diff --git "a/notes/Python/\346\265\201\347\225\205\347\232\204Python/\347\261\273\347\273\247\346\211\277.md" "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\347\261\273\347\273\247\346\211\277.md" new file mode 100644 index 00000000..a2170f82 --- /dev/null +++ "b/notes/Python/\346\265\201\347\225\205\347\232\204Python/\347\261\273\347\273\247\346\211\277.md" @@ -0,0 +1,262 @@ +# 第12章-类继承 + + + +- [第12章-类继承](#%e7%ac%ac12%e7%ab%a0-%e7%b1%bb%e7%bb%a7%e6%89%bf) + - [super函数](#super%e5%87%bd%e6%95%b0) + - [问题](#%e9%97%ae%e9%a2%98) + + + + +## super函数 + + +Py 2.x 和 Py 3.x 中有一个很大的区别就是类,无论是类的定义还是类的继承。 +**Py 3.x 中类的继承可以直接使用 super() 关键字代替原来的 super(Class, self)。** + +那么 super() 到底是依据什么来继承的呢? + +super()函数根据传进去的两个参数具体作用如下: + +>通过第一参数传进去的类名确定当前在MRO中的哪个位置。MRO(Method Resolution Order) +>通过第二个参数传进去的self,确定当前的MRO列表。 + +```python +class A(object): + def name(self): + print('name is xiaoming') + # super(A, self).name() + + +class B(object): + def name(self): + print('name is cat') + + +class C(A, B): + def name(self): + print('name is wang') + super(C, self).name() + + +if __name__ == '__main__': + + c = C() + print(c.__class__.__mro__) + c.name() +``` + +返回结果是: +``` +(, , , ) +name is wang +name is xiaoming +``` + +如果把注释去掉的结果是: +```python +(, , , ) +name is wang +name is xiaoming +name is cat + +``` + +
+再看一个例子: + +```python +class Base(object): + def func_1(self): + print('this is base') + + +class A(Base): + def func_1(self): + super().func_1() + print('this is A') + + +class B(Base): + def func_1(self): + super().func_1() + print('this is B') + + +class C(A, B): + def func_1(self): + super().func_1() + print('this is c') + + +print(C.mro()) +C().func_1() + +print(help(C)) +``` + + +返回结果: +``` +Help on class C in module __main__: + +class C(A, B) +| Method resolution order: +| C +| A +| B +``` + +“从左到到右”的意思,C继承关系最近的是A, 然后是B,最后是上层BASE。 +调用方法时的入栈顺序是 C A B Base, +出栈顺序是Base B A C, + +```python +class Base(object): + def __init__(self): + print("Base created") + + +class ChildA(Base): + def __init__(self): + Base.__init__(self) + + +class ChildB(Base): + def __init__(self): + # super(ChildB, self).__init__() + # 或者更简单的写法 + # super().__init__() + + # 如果不用super写法就要这样写 + print("self=", self) # <__main__.ChildB object at 0x000001EA8CD085F8> + mro = type(self).mro() + print(mro) + # [, , ] + + for next_class in mro[mro.index(ChildB) + 1:]: + print(next_class) # + if hasattr(next_class, '__init__'): + next_class.__init__(self) + break + + +print(ChildA()) +print("#" * 10) +print(ChildB()) + +``` + + +## 问题 + +看下面的代码: + +```python +class Base(object): + def __init__(self): + print("Base created") + + +class ChildA(Base): + def __init__(self): + print("ChildA init...") + Base.__init__(self) + + +class ChildB(Base): + def __init__(self): + print("ChildB init..") + # super(ChildB, self).__init__() + # 或者更简单的写法 + super().__init__() + + +if __name__ == "__main__": + print(ChildA()) + print("#" * 10) + print(ChildB()) + +``` + +返回值: +``` +ChildA init... +Base created +<__main__.ChildA object at 0x000001D089FE85F8> +########## +ChildB init.. +Base created +<__main__.ChildB object at 0x000001D089FE85F8> + +``` + +
+可以看到结果是一样的,那么 `Base.__init__(self)` 和 `super().__init__()` +有什么区别呢? + +请看这个例子: + +```python +class Base(object): + def __init__(self): + print("Base created") + + +class UserDependency(Base): + def __init__(self): + print("UserDependency init...") + super().__init__() + + +class ChildA(Base): + def __init__(self): + print("ChildA init...") + Base.__init__(self) + + +class ChildB(Base): + def __init__(self): + print("ChildB init..") + # super(ChildB, self).__init__() + # 或者更简单的写法 + super().__init__() + + +class UserA(ChildA, UserDependency): + def __init__(self): + print("UserA init...") + super(UserA, self).__init__() + + +class UserB(ChildB, UserDependency): + def __init__(self): + print("UserB init...") + super(UserB, self).__init__() + + +if __name__ == "__main__": + print(UserA()) + print("#" * 10) + print(UserB()) +``` + +返回结果: +``` +UserA init... +ChildA init... +Base created +<__main__.UserA object at 0x0000019295A38B00> +########## +UserB init... +ChildB init.. +UserDependency init... +Base created +<__main__.UserB object at 0x0000019295A38B00> + +``` + +
+ +**`这里我们看到了区别,在多重继承中,没有使用super方法的类,没有调用UserDenpendency方法`** \ No newline at end of file diff --git a/notes/Redis.md b/notes/Redis.md index 796903f9..e7f19030 100644 --- a/notes/Redis.md +++ b/notes/Redis.md @@ -46,7 +46,7 @@ 缓存系统 -排行版 +排行榜 计数器 @@ -57,7 +57,6 @@ 实时系统 - ## 3. 数据类型

diff --git a/notes/SpringBoot/README.md b/notes/SpringBoot/README.md new file mode 100644 index 00000000..f54e6448 --- /dev/null +++ b/notes/SpringBoot/README.md @@ -0,0 +1,2 @@ +## Spring Boot 学习指南 + diff --git a/notes/assets/regexr-table.pdf b/notes/assets/regexr-table.pdf new file mode 100644 index 00000000..54966e1e Binary files /dev/null and b/notes/assets/regexr-table.pdf differ diff --git a/notes/assets/regexr-table.png b/notes/assets/regexr-table.png new file mode 100644 index 00000000..4cba70c1 Binary files /dev/null and b/notes/assets/regexr-table.png differ diff --git a/notes/data-structures-and-algorithms/LeetCode.md b/notes/data-structures-and-algorithms/LeetCode.md new file mode 100644 index 00000000..a98174fb --- /dev/null +++ b/notes/data-structures-and-algorithms/LeetCode.md @@ -0,0 +1 @@ +# LeetCode \ No newline at end of file diff --git a/notes/data-structures-and-algorithms/README.md b/notes/data-structures-and-algorithms/README.md new file mode 100644 index 00000000..e824a536 --- /dev/null +++ b/notes/data-structures-and-algorithms/README.md @@ -0,0 +1,51 @@ +# 数据结构与算法 + +> 欢迎来到数据结构与算法的世界,带你从基础数据结构层层深入。本章以 Java 代码为例,结合 LeetCode 题解示例进行数据结构学习。 + + + +## 学习目录 + +| 部分 | 章节 | 概要 | +| :--- | :--------------------------- | :--------------------------------------------------- | +| Ⅰ | [数据结构](数据结构.md) | 线性表、栈和队列、树和二叉树等经典数据结构 | +| Ⅱ | [算法思想](算法思想.md) | 排序算法、动态规划、递归、回溯法、贪心算法等经典算法 | +| Ⅲ | [LeetCode 题解](LeetCode.md) | LeetCode 热题 HOT 100 | + + + +## 在线刷题 + +- 题库 - 力扣 (LeetCode) 全球极客挚爱的技术成长平台 + + https://leetcode-cn.com/problemset/all + +- 牛客网 - 找工作神器|笔试题库|面试经验|实习招聘内推,求职就业一站解决 + + https://www.nowcoder.com/ + + + +## 在线工具 + +- VisuAlgo - visualising data structures and algorithms through animation + + https://visualgo.net/en + +- Data Structure Visualization + + https://www.cs.usfca.edu/~galles/visualization/Algorithms.html + + + +## 学习课程 + +| 课程 | 链接 | 代码 | 推荐 | +| ------------------------------------------------------ | ------------------------------------------------------- | ------------------------------------------------------------ | ----------------------------------------- | +| 【慕课网】玩转数据结构 从入门到进阶(Java) | [官网](https://coding.imooc.com/class/chapter/207.html) | [代码](https://github.com/liuyubobobo/Play‑with‑Data‑Structures) | 数据结构从底层到实现,浅显易懂 | +| 【慕课网】算法与数据结构体系课(Java)
| [官网](https://class.imooc.com/datastructure#Anchor) | [代码](https://github.com/frank‑lam/Algorithm) | 《玩转数据结构 从入门到进阶》 - 升级版 | +| 【慕课网】程序员的内功修炼,学好算法与数据结构(C++) | [官网](https://coding.imooc.com/class/chapter/71.html) | [代码](https://github.com/liuyubobobo/Play‑with‑Algorithms) | 程序员的内功修炼,强烈推荐 | +| 【慕课网】玩转算法面试 LeetCode 题库(C++) | [官网](https://coding.imooc.com/class/chapter/82.html) | [代码](https://github.com/liuyubobobo/Play‑with‑Algorithm‑Interview) | LeetCode 刷题入门,强烈推荐 | +| 【极客时间】覃超:算法面试通关40讲 | [官网](https://time.geekbang.org/course/intro/130) | / | 市面上比较新的课程,推荐 | +| 【慕课网】刘宇波:看得见的算法 7个经典应用诠释算法精髓 | [官网](https://coding.imooc.com/class/chapter/138.html) | / | 通过7款经典好玩游戏,真正将算法用于实际开 | + diff --git a/notes/data-structures-and-algorithms/assets/image-20210821164803357.png b/notes/data-structures-and-algorithms/assets/image-20210821164803357.png new file mode 100644 index 00000000..1b18aee3 Binary files /dev/null and b/notes/data-structures-and-algorithms/assets/image-20210821164803357.png differ diff --git a/notes/data-structures-and-algorithms/assets/image-20210821164814332.png b/notes/data-structures-and-algorithms/assets/image-20210821164814332.png new file mode 100644 index 00000000..1b18aee3 Binary files /dev/null and b/notes/data-structures-and-algorithms/assets/image-20210821164814332.png differ diff --git a/notes/data-structures-and-algorithms/assets/image-20210821164819266.png b/notes/data-structures-and-algorithms/assets/image-20210821164819266.png new file mode 100644 index 00000000..1b18aee3 Binary files /dev/null and b/notes/data-structures-and-algorithms/assets/image-20210821164819266.png differ diff --git a/notes/data-structures-and-algorithms/assets/image-20210821172111765.png b/notes/data-structures-and-algorithms/assets/image-20210821172111765.png new file mode 100644 index 00000000..5ab3b50e Binary files /dev/null and b/notes/data-structures-and-algorithms/assets/image-20210821172111765.png differ diff --git a/notes/data-structures-and-algorithms/assets/logo7.svg b/notes/data-structures-and-algorithms/assets/logo7.svg new file mode 100644 index 00000000..bfc750fb --- /dev/null +++ b/notes/data-structures-and-algorithms/assets/logo7.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/notes/data-structures-and-algorithms/assets/logo8.svg b/notes/data-structures-and-algorithms/assets/logo8.svg new file mode 100644 index 00000000..49e3c852 --- /dev/null +++ b/notes/data-structures-and-algorithms/assets/logo8.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git "a/notes/data-structures-and-algorithms/leetcode/Leetcode 307. \345\214\272\345\237\237\345\222\214\346\243\200\347\264\242 - \346\225\260\347\273\204\345\217\257\344\277\256\346\224\271.md" "b/notes/data-structures-and-algorithms/leetcode/Leetcode 307. \345\214\272\345\237\237\345\222\214\346\243\200\347\264\242 - \346\225\260\347\273\204\345\217\257\344\277\256\346\224\271.md" new file mode 100644 index 00000000..78b304a4 --- /dev/null +++ "b/notes/data-structures-and-algorithms/leetcode/Leetcode 307. \345\214\272\345\237\237\345\222\214\346\243\200\347\264\242 - \346\225\260\347\273\204\345\217\257\344\277\256\346\224\271.md" @@ -0,0 +1,213 @@ +https://leetcode-cn.com/problems/range-sum-query-mutable + +## 307. 区域和检索 - 数组可修改 + +给你一个数组 `nums` ,请你完成两类查询,其中一类查询要求更新数组下标对应的值,另一类查询要求返回数组中某个范围内元素的总和。 + +实现 `NumArray` 类: + +- `NumArray(int[] nums)` 用整数数组 `nums` 初始化对象 +- `void update(int index, int val)` 将 `nums[index]` 的值更新为 `val` +- `int sumRange(int left, int right)` 返回子数组 `nums[left, right]` 的总和(即,`nums[left] + nums[left + 1], ..., nums[right]`) + + + +**示例:** + +``` +输入: +["NumArray", "sumRange", "update", "sumRange"] +[[[1, 3, 5]], [0, 2], [1, 2], [0, 2]] +输出: +[null, 9, null, 8] + +解释: +NumArray numArray = new NumArray([1, 3, 5]); +numArray.sumRange(0, 2); // 返回 9 ,sum([1,3,5]) = 9 +numArray.update(1, 2); // nums = [1,2,5] +numArray.sumRange(0, 2); // 返回 8 ,sum([1,2,5]) = 8 +``` + + + +**提示:** + +- `1 <= nums.length <= 3 * 104` +- `-100 <= nums[i] <= 100` +- `0 <= index < nums.length` +- `-100 <= val <= 100` +- `0 <= left <= right < nums.length` +- 最多调用 `3 * 104` 次 `update` 和 `sumRange` 方法 + + + +**提交代码:** + +```java +class NumArray { + public class SegmentTree { + + private E[] tree; + private E[] data; + private Merger merger; + + public SegmentTree(E[] arr, Merger merger){ + + this.merger = merger; + + data = (E[])new Object[arr.length]; + for(int i = 0 ; i < arr.length ; i ++) + data[i] = arr[i]; + + tree = (E[])new Object[4 * arr.length]; + buildSegmentTree(0, 0, arr.length - 1); + } + + // 在treeIndex的位置创建表示区间[l...r]的线段树 + private void buildSegmentTree(int treeIndex, int l, int r){ + + if(l == r){ + tree[treeIndex] = data[l]; + return; + } + + int leftTreeIndex = leftChild(treeIndex); + int rightTreeIndex = rightChild(treeIndex); + + // int mid = (l + r) / 2; + int mid = l + (r - l) / 2; + buildSegmentTree(leftTreeIndex, l, mid); + buildSegmentTree(rightTreeIndex, mid + 1, r); + + tree[treeIndex] = merger.merge(tree[leftTreeIndex], tree[rightTreeIndex]); + } + + public int getSize(){ + return data.length; + } + + public E get(int index){ + if(index < 0 || index >= data.length) + throw new IllegalArgumentException("Index is illegal."); + return data[index]; + } + + // 返回完全二叉树的数组表示中,一个索引所表示的元素的左孩子节点的索引 + private int leftChild(int index){ + return 2*index + 1; + } + + // 返回完全二叉树的数组表示中,一个索引所表示的元素的右孩子节点的索引 + private int rightChild(int index){ + return 2*index + 2; + } + + // 返回区间[queryL, queryR]的值 + public E query(int queryL, int queryR){ + + if(queryL < 0 || queryL >= data.length || + queryR < 0 || queryR >= data.length || queryL > queryR) + throw new IllegalArgumentException("Index is illegal."); + + return query(0, 0, data.length - 1, queryL, queryR); + } + + // 在以treeIndex为根的线段树中[l...r]的范围里,搜索区间[queryL...queryR]的值 + private E query(int treeIndex, int l, int r, int queryL, int queryR){ + + if(l == queryL && r == queryR) + return tree[treeIndex]; + + int mid = l + (r - l) / 2; + // treeIndex的节点分为[l...mid]和[mid+1...r]两部分 + + int leftTreeIndex = leftChild(treeIndex); + int rightTreeIndex = rightChild(treeIndex); + if(queryL >= mid + 1) + return query(rightTreeIndex, mid + 1, r, queryL, queryR); + else if(queryR <= mid) + return query(leftTreeIndex, l, mid, queryL, queryR); + + E leftResult = query(leftTreeIndex, l, mid, queryL, mid); + E rightResult = query(rightTreeIndex, mid + 1, r, mid + 1, queryR); + return merger.merge(leftResult, rightResult); + } + + // 将index位置的值,更新为e + public void set(int index, E e){ + + if(index < 0 || index >= data.length) + throw new IllegalArgumentException("Index is illegal"); + + data[index] = e; + set(0, 0, data.length - 1, index, e); + } + + // 在以treeIndex为根的线段树中更新index的值为e + private void set(int treeIndex, int l, int r, int index, E e){ + + if(l == r){ + tree[treeIndex] = e; + return; + } + + int mid = l + (r - l) / 2; + // treeIndex的节点分为[l...mid]和[mid+1...r]两部分 + + int leftTreeIndex = leftChild(treeIndex); + int rightTreeIndex = rightChild(treeIndex); + if(index >= mid + 1) + set(rightTreeIndex, mid + 1, r, index, e); + else // index <= mid + set(leftTreeIndex, l, mid, index, e); + + tree[treeIndex] = merger.merge(tree[leftTreeIndex], tree[rightTreeIndex]); + } + + @Override + public String toString(){ + StringBuilder res = new StringBuilder(); + res.append('['); + for(int i = 0 ; i < tree.length ; i ++){ + if(tree[i] != null) + res.append(tree[i]); + else + res.append("null"); + + if(i != tree.length - 1) + res.append(", "); + } + res.append(']'); + return res.toString(); + } + } + + public interface Merger { + E merge(E a, E b); + } + + private SegmentTree segTree; + + public NumArray(int[] nums) { + + if(nums.length != 0){ + Integer[] data = new Integer[nums.length]; + for(int i = 0 ; i < nums.length ; i ++) + data[i] = nums[i]; + segTree = new SegmentTree<>(data, (a, b) -> a + b); + } + } + + public void update(int i, int val) { + if(segTree == null) + throw new IllegalArgumentException("Error"); + segTree.set(i, val); + } + + public int sumRange(int i, int j) { + if(segTree == null) + throw new IllegalArgumentException("Error"); + return segTree.query(i, j); + } +} +``` \ No newline at end of file diff --git "a/notes/data-structures-and-algorithms/\346\225\260\346\215\256\347\273\223\346\236\204.md" "b/notes/data-structures-and-algorithms/\346\225\260\346\215\256\347\273\223\346\236\204.md" new file mode 100644 index 00000000..b3ffc0cd --- /dev/null +++ "b/notes/data-structures-and-algorithms/\346\225\260\346\215\256\347\273\223\346\236\204.md" @@ -0,0 +1,286 @@ +# 数据结构 + +## 一、基础数据结构 + +### 字符串 + +- 标准库,解析,匹配等 + +### 线性表 + +- 数组 + +- 动态数组 + +### 栈和队列 + +### 链表 + +### 哈希表 + + + +## 二、高级数据结构 + +### 1. 线段树 + +线段树(segment tree),顾名思义, 是用来存放给定区间(segment, or interval)内对应信息的一种数据结构。与树状数组(binary indexed tree)相似,线段树也用来处理数组相应的区间查询(range query)和元素更新(update)操作。与树状数组不同的是,线段树不止可以适用于区间求和的查询,也可以进行区间最大值,区间最小值(Range Minimum/Maximum Query problem)或者区间异或值的查询。 + +对应于树状数组,线段树进行更新(update)的操作为 O(logn),进行区间查询(range query)的操作也为 O(logn)。 + +![image-20210821164803357](assets/image-20210821164803357.png) + +在线可视化:https://visualgo.net/en/segmenttree + + + +#### 303. 区域和检索 - 数组不可变 (LeetCode) + + +https://leetcode-cn.com/problems/range-sum-query-immutable + +```java +class NumArray { + private int[] data; + + public NumArray(int[] nums) { + this.data = new int[nums.length]; + for (int i = 0; i < nums.length; i++) { + this.data[i] = nums[i]; + } + } + + public void update(int index, int val) { + this.data[index] = val; + } + + public int sumRange(int left, int right) { + int sum = 0; + for (int i = left; i <= right; i++) { + sum += this.data[i]; + } + return sum; + } +} +``` + + + +#### 307. 区域和检索 - 数组可修改 (LeetCode) + +https://leetcode-cn.com/problems/range-sum-query-mutable + +```java + +class NumArray { + public class SegmentTree { + private E[] tree; + private E[] data; + private Merger merger; + + public SegmentTree(E[] arr, Merger merger){ + + this.merger = merger; + + data = (E[])new Object[arr.length]; + for(int i = 0 ; i < arr.length ; i ++) + data[i] = arr[i]; + + tree = (E[])new Object[4 * arr.length]; + buildSegmentTree(0, 0, arr.length - 1); + } + + // 在treeIndex的位置创建表示区间[l...r]的线段树 + private void buildSegmentTree(int treeIndex, int l, int r){ + + if(l == r){ + tree[treeIndex] = data[l]; + return; + } + + int leftTreeIndex = leftChild(treeIndex); + int rightTreeIndex = rightChild(treeIndex); + + // int mid = (l + r) / 2; + int mid = l + (r - l) / 2; + buildSegmentTree(leftTreeIndex, l, mid); + buildSegmentTree(rightTreeIndex, mid + 1, r); + + tree[treeIndex] = merger.merge(tree[leftTreeIndex], tree[rightTreeIndex]); + } + + public int getSize(){ + return data.length; + } + + public E get(int index){ + if(index < 0 || index >= data.length) + throw new IllegalArgumentException("Index is illegal."); + return data[index]; + } + + // 返回完全二叉树的数组表示中,一个索引所表示的元素的左孩子节点的索引 + private int leftChild(int index){ + return 2*index + 1; + } + + // 返回完全二叉树的数组表示中,一个索引所表示的元素的右孩子节点的索引 + private int rightChild(int index){ + return 2*index + 2; + } + + // 返回区间[queryL, queryR]的值 + public E query(int queryL, int queryR){ + + if(queryL < 0 || queryL >= data.length || + queryR < 0 || queryR >= data.length || queryL > queryR) + throw new IllegalArgumentException("Index is illegal."); + + return query(0, 0, data.length - 1, queryL, queryR); + } + + // 在以treeIndex为根的线段树中[l...r]的范围里,搜索区间[queryL...queryR]的值 + private E query(int treeIndex, int l, int r, int queryL, int queryR){ + + if(l == queryL && r == queryR) + return tree[treeIndex]; + + int mid = l + (r - l) / 2; + // treeIndex的节点分为[l...mid]和[mid+1...r]两部分 + + int leftTreeIndex = leftChild(treeIndex); + int rightTreeIndex = rightChild(treeIndex); + if(queryL >= mid + 1) + return query(rightTreeIndex, mid + 1, r, queryL, queryR); + else if(queryR <= mid) + return query(leftTreeIndex, l, mid, queryL, queryR); + + E leftResult = query(leftTreeIndex, l, mid, queryL, mid); + E rightResult = query(rightTreeIndex, mid + 1, r, mid + 1, queryR); + return merger.merge(leftResult, rightResult); + } + + // 将index位置的值,更新为e + public void set(int index, E e){ + + if(index < 0 || index >= data.length) + throw new IllegalArgumentException("Index is illegal"); + + data[index] = e; + set(0, 0, data.length - 1, index, e); + } + + // 在以treeIndex为根的线段树中更新index的值为e + private void set(int treeIndex, int l, int r, int index, E e){ + + if(l == r){ + tree[treeIndex] = e; + return; + } + + int mid = l + (r - l) / 2; + // treeIndex的节点分为[l...mid]和[mid+1...r]两部分 + + int leftTreeIndex = leftChild(treeIndex); + int rightTreeIndex = rightChild(treeIndex); + if(index >= mid + 1) + set(rightTreeIndex, mid + 1, r, index, e); + else // index <= mid + set(leftTreeIndex, l, mid, index, e); + + tree[treeIndex] = merger.merge(tree[leftTreeIndex], tree[rightTreeIndex]); + } + + @Override + public String toString(){ + StringBuilder res = new StringBuilder(); + res.append('['); + for(int i = 0 ; i < tree.length ; i ++){ + if(tree[i] != null) + res.append(tree[i]); + else + res.append("null"); + + if(i != tree.length - 1) + res.append(", "); + } + res.append(']'); + return res.toString(); + } + } + + public interface Merger { + E merge(E a, E b); + } + + private SegmentTree segTree; + + public NumArray(int[] nums) { + + if(nums.length != 0){ + Integer[] data = new Integer[nums.length]; + for(int i = 0 ; i < nums.length ; i ++) + data[i] = nums[i]; + segTree = new SegmentTree<>(data, (a, b) -> a + b); + } + } + + public void update(int i, int val) { + if(segTree == null) + throw new IllegalArgumentException("Error"); + segTree.set(i, val); + } + + public int sumRange(int i, int j) { + if(segTree == null) + throw new IllegalArgumentException("Error"); + return segTree.query(i, j); + } +} +``` + + + +### 2. Trie + +https://blog.csdn.net/jt102605/article/details/84258314 + +https://blog.csdn.net/longgeqiaojie304/article/details/106316103 + +发生在微软的一个真实案例: + +在一个古老的手持设备中实现一个通讯录功能,但是当时的手持设备的芯片运算能力是非常低的,所以他们发现当通讯录中记录的条目非常多的时候,搜索通讯录中的内容是非常慢的。但是这个问题是被微软的一个实习生解决了。其实他解决的方式非常简单,他就是使用了这种Trie数据结构来解决的。 + + +![image-20210821172111765](assets/image-20210821172111765.png) + +### 树和二叉树 + +- 二叉树 + +- 二分搜索树 + +- AVL 树 + +- B 和 B+ + +- 二分查找法 + - Leetcode 704. + +- 红黑树 + + + +### 集合和映射 + + + +### 优先队列和堆 + + + + + +### Trie + +### 并查集 diff --git "a/notes/data-structures-and-algorithms/\347\256\227\346\263\225\346\200\235\346\203\263.md" "b/notes/data-structures-and-algorithms/\347\256\227\346\263\225\346\200\235\346\203\263.md" new file mode 100644 index 00000000..28a34932 --- /dev/null +++ "b/notes/data-structures-and-algorithms/\347\256\227\346\263\225\346\200\235\346\203\263.md" @@ -0,0 +1,730 @@ +# 算法思想 + +## 一、排序算法 + +### 1. 选择排序(Selection Sort) + +选择出数组中的最小元素,将它与数组的第一个元素交换位置。再从剩下的元素中选择出最小的元素,将它与数组的第二个元素交换位置。不断进行这样的操作,直到将整个数组排序。 + +![](https://images2017.cnblogs.com/blog/849589/201710/849589-20171015224719590-1433219824.gif) + +**代码实现** + +```java +public static void sort(int[] arr) { + for (int i = 0; i < arr.length; i++) { + // 寻找[i, n)区间里的最小值的索引 + int minIndex = i; + for (int j = i + 1; j < arr.length; j++) { + if(arr[minIndex] > arr[j]){ + minIndex = j; + } + } + swap( arr , i , minIndex); + } +} + +private static void swap(int[] arr, int i, int j) { + int t = arr[i]; + arr[i] = arr[j]; + arr[j] = t; +} +``` + +**算法分析** + +表现最稳定的排序算法之一,因为无论什么数据进去都是O(n2)的时间复杂度,所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间了吧。理论上讲,选择排序可能也是平时排序一般人想到的最多的排序方法了吧。 + + + +### 2. 插入排序(Insertion Sort) + +插入排序从左到右进行,每次都将当前元素插入到左侧已经排序的数组中,使得插入之后左部数组依然有序。 + +第 j 元素是通过不断向左比较并交换来实现插入过程:当第 j 元素小于第 j - 1 元素,就将它们的位置交换,然后令 j 指针向左移动一个位置,不断进行以上操作。 + +![](https://images2017.cnblogs.com/blog/849589/201710/849589-20171015225645277-1151100000.gif) + +**代码实现** + +```java +public static void sort(int[] arr) { + for (int i = 0; i < arr.length; i++) { + // 选择 arr[i...n) 中的最小值 + int minIndex = i; + for (int j = i + 1; j < arr.length; j++) { + if (arr[minIndex] > arr[j]) { + minIndex = j; + } + } + swap(arr, i, minIndex); + } +} + +// 改进版插入排序(减少了数组元素的操作次数) +public static void better_sort(int[] arr) { + for (int i = 0; i < arr.length; i++) { + int e = arr[i]; + int j = i; + for (; j > 0; j--) { + if (e < arr[j - 1]) + arr[j] = arr[j - 1]; + else + break; + } + arr[j] = e; + } +} + +private static void swap(int[] arr, int i, int j) { + int t = arr[i]; + arr[i] = arr[j]; + arr[j] = t; +} +``` + +**算法分析** + +插入排序在实现上,通常采用 in-place 排序(即只需用到 O(1) 的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。 + + + +### 3. 冒泡排序(Bubble Sort) + +通过从左到右不断交换相邻逆序的相邻元素,在一轮的交换之后,可以让未排序的元素上浮到右侧。 + +在一轮循环中,如果没有发生交换,就说明数组已经是有序的,此时可以直接退出。 + +![](https://images2017.cnblogs.com/blog/849589/201710/849589-20171015223238449-2146169197.gif) + +**代码实现** + +```java +private static void sort(int[] arr) { + for (int i = arr.length - 1; i > 0; i--) { // 从最后一位开始确定 + boolean swapped = false; + for (int j = 0; j < i; j++) { + if(arr[j] > arr[j+1]){ + swapped = true; + swap(arr,j,j+1); + } + } + if(!swapped) + return; + } +} + +private static void swap(int[] arr, int i, int j) { + int t = arr[i]; + arr[i] = arr[j]; + arr[j] = t; +} +``` + + + +### 4. 希尔排序(Shell Sort) + +1959年Shell发明,第一个突破O(n2)的排序算法,是简单插入排序的改进版。它与插入排序的不同之处在于,它会优先比较距离较远的元素。希尔排序又叫**缩小增量排序**。 + + + +**算法描述** + +先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述: + +- 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1; +- 按增量序列个数k,对序列进行k 趟排序; +- 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。 + +![](https://images2018.cnblogs.com/blog/849589/201803/849589-20180331170017421-364506073.gif) + +**代码实现** + +```java +// 希尔排序 +public static void sort(int[] arr) { + int n = arr.length; + for (int h = n / 2; h > 0; h = h / 2) { + // 内部是一个插入排序 + for (int i = 0; i < n; i = i + h) { + + int e = arr[i]; + int j = i; + for (; j > 0; j = j - h) { + if (e < arr[j - h]) + arr[j] = arr[j - h]; + else + break; + } + arr[j] = e; + } + } +} + + +// 希尔排序2 +public static void sort2(int[] arr) { + int n = arr.length; + // 计算 increment sequence: 1, 4, 13, 40, 121, 364, 1093... + int h = 1; + while (h < n / 3) h = 3 * h + 1; + + System.out.println(h); + + while (h >= 1) { + // h-sort the array + for (int i = h; i < n; i++) { + + // 对 arr[i], arr[i-h], arr[i-2*h], arr[i-3*h]... 使用插入排序 + int e = arr[i]; + int j = i; + for (; j >= h && e < arr[j - h]; j -= h) + arr[j] = arr[j - h]; + arr[j] = e; + } + + h /= 3; + } +} +``` + +**算法分析** + +对于大规模的数组,插入排序很慢,因为它只能交换相邻的元素,每次只能将逆序数量减少 1。 + +希尔排序的出现就是为了改进插入排序的这种局限性,它通过交换不相邻的元素,每次可以将逆序数量减少大于 1。 + +希尔排序使用插入排序对间隔 h 的序列进行排序。通过不断减小 h,最后令 h=1,就可以使得整个数组是有序的。 + + + +### 5. 归并排序(Merge Sort) + +归并排序的思想是将数组分成两部分,分别进行排序,然后归并起来。把长度为n的输入序列分成两个长度为n/2的子序列;对这两个子序列分别采用归并排序;将两个排序好的子序列合并成一个最终的排序序列。 + +![](https://images2017.cnblogs.com/blog/849589/201710/849589-20171015230557043-37375010.gif) + +**代码实现** + +> 1.归并方法 +> +> 归并方法将数组中两个已经排序的部分归并成一个。 + +```java +private static void sort(int[] arr) { + __MergeSort(arr, 0, arr.length - 1); +} + +private static void __MergeSort(int[] arr, int l, int r) { + if (l >= r) + return; + int mid = (l + r) / 2; + __MergeSort(arr, l, mid); + __MergeSort(arr, mid + 1, r); + merge(arr, l, mid, r); +} + +// 将arr[l...mid]和arr[mid+1...r]两部分进行归并 +private static void merge(int[] arr, int l, int mid, int r) { + int[] aux = Arrays.copyOfRange(arr, l, r + 1); + + // 初始化,i指向左半部分的起始索引位置l;j指向右半部分起始索引位置mid+1 + int i = l, j = mid + 1; + for (int k = l; k <= r; k++) { + if (i > mid) { // 如果左半部分元素已经全部处理完毕 + arr[k] = aux[j - l]; + j++; + } else if (j > r) { // 如果右半部分元素已经全部处理完毕 + arr[k] = aux[i - l]; + i++; + } else if (aux[i - l] < aux[j - l]) { // 左半部分所指元素 < 右半部分所指元素 + arr[k] = aux[i - l]; + i++; + } else { // 左半部分所指元素 >= 右半部分所指元素 + arr[k] = aux[j - l]; + j++; + } + } +} +``` + +> 2.自底向上归并排序 + +```java +private static void sort(int[] arr) { + int N = arr.length; + int[] aux = new int[N]; + for (int sz = 1; sz < N; sz += sz) + for (int i = 0; i + sz < N; i += sz + sz) + merge(arr, i, i + sz - 1, Math.min(i + sz + sz - 1, N - 1)); +} +``` + + + +### 6. 快速排序(Quick Sort) + +快速排序可以说是20世纪最伟大的算法之一了。相信都有所耳闻,它的速度也正如它的名字那样,是一个非常快的算法了。当然它也后期经过了不断的改进和优化,才被公认为是一个值得信任的非常优秀的算法。 + +![](https://images2017.cnblogs.com/blog/849589/201710/849589-20171015230936371-1413523412.gif) + +**代码实现** + +#### 1. 普通快速排序 + +```java +// 递归使用快速排序,对arr[l...r]的范围进行排序 +public static void QuickSort(int[] arr,int l,int r){ + if(l>=r) + return; + int p = partition(arr,l,r); + QuickSort(arr,l,p-1); + QuickSort(arr,p+1,r); +} + +// 将数组通过p分割成两部分 +// 对arr[l...r]部分进行partition操作 +// 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p] +public static int partition(int[] arr, int l, int r) { + swap(arr, l, (int) (Math.random() % (r - l + 1)) + l); // 加入这一行变成随机快速排序 + + int v = arr[l]; + int j = l; + for(int i = j +1;i<=r;i++){ + if(arr[i] < v){ + j++; + swap(arr,i,j); + } + } + swap(arr,l,j); + return j; +} + +public static void swap(int[] arr,int i,int j) { + int temp = arr[i]; + arr[i] = arr[j]; + arr[j] = temp; +} +``` + +快速排序是原地排序,不需要辅助数组,但是递归调用需要辅助栈。 + +快速排序最好的情况下是每次都正好能将数组对半分,这样递归调用次数才是最少的。这种情况下比较次数为 CN=2CN/2+N,复杂度为 O(NlogN)。 + +最坏的情况下,第一次从最小的元素切分,第二次从第二小的元素切分,如此这般。因此最坏的情况下需要比较 N2/2。为了防止数组最开始就是有序的,在进行快速排序时需要随机打乱数组。 + + + +#### 2. 双路快速排序 + +若果数组中含有大量重复的元素,则partition很可能把数组划分成两个及其不平衡的两部分,时间复杂度退化成O(n²)。这时候应该把小于v和大于v放在数组两端。 + +![](../pics/partition2.jpg) + +```java +// 双路快速排序的partition +// 返回p, 使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p] +private static int partition(int[] arr, int l, int r) { + + // 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot + // swap(arr, l, (int) (Math.random() % (r - l + 1)) + l); + + int v = arr[l]; + + // arr[l+1...i) <= v; arr(j...r] >= v + int i = l + 1, j = r; + while (true) { + // 注意这里的边界, arr[i].compareTo(v) < 0, 不能是arr[i].compareTo(v) <= 0 + // 思考一下为什么? + while (i <= r && arr[i] < v) + i++; + + // 注意这里的边界, arr[j].compareTo(v) > 0, 不能是arr[j].compareTo(v) >= 0 + // 思考一下为什么? + while (j >= l + 1 && arr[j] > v) + j--; + + // 对于上面的两个边界的设定, 有的同学在课程的问答区有很好的回答:) + // 大家可以参考: http://coding.imooc.com/learn/questiondetail/4920.html + if (i > j) + break; + + swap(arr, i, j); + i++; + j--; + } + + swap(arr, l, j); + + return j; +} + +// 递归使用快速排序,对arr[l...r]的范围进行排序 +private static void QuickSort2Ways(int[] arr, int l, int r) { + // 对于小规模数组, 使用插入排序 + if (l >= r) return; + int p = partition(arr, l, r); + QuickSort2Ways(arr, l, p - 1); + QuickSort2Ways(arr, p + 1, r); +} +``` + +#### 3. 三路快速排序 + +数组分成三个部分,大于v 等于v 小于v + +在具有大量重复键值对的情况下使用三路快排 + +![](../pics/partition3.jpg) + + + +```java +// 递归使用快速排序,对arr[l...r]的范围进行排序 +private static void QuickSort3Ways(int[] arr, int l, int r){ + + // 随机在arr[l...r]的范围中, 选择一个数值作为标定点pivot + swap( arr, l, (int)(Math.random()*(r-l+1)) + l ); + + int v = arr[l]; + + int lt = l; // arr[l+1...lt] < v + int gt = r + 1; // arr[gt...r] > v + int i = l+1; // arr[lt+1...i) == v + while( i < gt ){ + if( arr[i] < v){ + swap( arr, i, lt+1); + i ++; + lt ++; + } + else if( arr[i] > v ){ + swap( arr, i, gt-1); + gt --; + } + else{ // arr[i] == v + i ++; + } + } + swap( arr, l, lt ); + + QuickSort3Ways(arr, l, lt-1); + QuickSort3Ways(arr, gt, r); +} +``` + + + +### 7. 堆排序(Heap Sort) + +#### 1. 堆 + +堆的某个节点的值总是大于等于子节点的值,并且堆是一颗完全二叉树。 + +堆可以用数组来表示,因为堆是完全二叉树,而完全二叉树很容易就存储在数组中。位置 k 的节点的父节点位置为 k/2,而它的两个子节点的位置分别为 2k 和 2k+1。这里不使用数组索引为 0 的位置,是为了更清晰地描述节点的位置关系。 + +![](../pics/heap.png) + +#### 2. 上浮和下沉 + +在堆中,当一个节点比父节点大,那么需要交换这个两个节点。交换后还可能比它新的父节点大,因此需要不断地进行比较和交换操作,把这种操作称为**上浮(ShiftUp)**。 + +![](../pics/shiftup_heap.png) + +```java +private void shiftUp(int k){ + while( k > 1 && data[k/2] < data[k])){ + swap(k, k/2); + k /= 2; + } +} +``` + + + +类似地,当一个节点比子节点来得小,也需要不断地向下进行比较和交换操作,把这种操作称为**下沉(Shift Down)**。一个节点如果有两个子节点,应当与两个子节点中最大那么节点进行交换。 + +![](../pics/shiftdown_heap.png) + +```java +private void shiftDown(int k){ + while( 2*k <= count ){ // 当前结点有左孩子 + int j = 2*k; // 在此轮循环中,data[k]和data[j]交换位置 + if( j+1 <= count && data[j+1] > data[j] ) + j ++; + // data[j] 是 data[2*k]和data[2*k+1]中的最大值 + + if( data[k] >= data[j] ) + break; + swap(k, j); + k = j; + } +} +``` + +#### 3.插入元素 + +将新元素放到数组末尾,然后上浮到合适的位置。 + +```java +// 向最大堆中插入一个新的元素 item +public void insert(Item item){ + assert count + 1 <= capacity; + data[count+1] = item; + count ++; + shiftUp(count); +} +``` + +#### 4. 删除最大元素 + +```java +// 从最大堆中取出堆顶元素, 即堆中所存储的最大数据 +public Item extractMax(){ + assert count > 0; + Item ret = data[1]; + + swap( 1 , count ); + count --; + shiftDown(1); + return ret; +} +``` + +#### 5. 堆排序 + +由于堆可以很容易得到最大的元素并删除它,不断地进行这种操作可以得到一个递减序列。如果把最大元素和当前堆中数组的最后一个元素交换位置,并且不删除它,那么就可以得到一个从尾到头的递减序列,从正向来看就是一个递增序列。因此很容易使用堆来进行排序。并且堆排序是原地排序,不占用额外空间。 + +```java +// 不使用一个额外的最大堆, 直接在原数组上进行原地的堆排序 +public class HeapSort { + + // 对整个arr数组使用HeapSort1排序 + // HeapSort1, 将所有的元素依次添加到堆中, 在将所有元素从堆中依次取出来, 即完成了排序 + // 无论是创建堆的过程, 还是从堆中依次取出元素的过程, 时间复杂度均为O(nlogn) + // 整个堆排序的整体时间复杂度为O(nlogn) + public static void sort1(Comparable[] arr){ + + int n = arr.length; + MaxHeap maxHeap = new MaxHeap(n); + for( int i = 0 ; i < n ; i ++ ) + maxHeap.insert(arr[i]); + + for( int i = n-1 ; i >= 0 ; i -- ) + arr[i] = maxHeap.extractMax(); + } + + + // 只通过shiftDown操作进行排序 + public static void sort2(Comparable[] arr){ + int n = arr.length; + + // 注意,此时我们的堆是从0开始索引的 + // 从(最后一个元素的索引-1)/2开始 + // 最后一个元素的索引 = n-1 + for( int i = (n-1-1)/2 ; i >= 0 ; i -- ) + shiftDown2(arr, n, i); + + for( int i = n-1; i > 0 ; i-- ){ // 这个的目的是让序列从小到大排序 + swap( arr, 0, i); + shiftDown2(arr, i, 0); + } + } + + // 交换堆中索引为i和j的两个元素 + private static void swap(Object[] arr, int i, int j){ + Object t = arr[i]; + arr[i] = arr[j]; + arr[j] = t; + } + + // 原始的shiftDown过程 + private static void shiftDown(Comparable[] arr, int n, int k){ + while( 2*k+1 < n ){ + int j = 2*k+1; + if( j+1 < n && arr[j+1].compareTo(arr[j]) > 0 ) + j += 1; + + if( arr[k].compareTo(arr[j]) >= 0 )break; + + swap( arr, k, j); + k = j; + } + } + + // 优化的shiftDown过程, 使用赋值的方式取代不断的swap, + // 该优化思想和我们之前对插入排序进行优化的思路是一致的 + private static void shiftDown2(Comparable[] arr, int n, int k){ + + Comparable e = arr[k]; + while( 2*k+1 < n ){ + int j = 2*k+1; + if( j+1 < n && arr[j+1].compareTo(arr[j]) > 0 ) + j += 1; + + if( e.compareTo(arr[j]) >= 0 ) + break; + + arr[k] = arr[j]; + k = j; + } + + arr[k] = e; + } + + // 测试 HeapSort + public static void main(String[] args) { + Integer[] arr = {10, 91, 8, 7, 6, 5, 4, 3, 2, 1}; + HeapSort.sort2(arr); + PrintHelper.printArray(arr); + } +} +``` + +#### 6. 堆排序的应用——Top K问题 + +例如,有1亿个浮点数,如何找出其中最大的10000个?(B326) + + + +### 8. 计数排序 + +https://www.cnblogs.com/freedom314/p/5847092.html + + + +### 9. 排序算法总结 + +| | 平均时间复杂度 | 原地排序 | 额外空间 | 稳定排序 | +| :------: | :------------: | :------: | :------: | :------: | +| 插入排序 | O(n^2) | √ | O(1) | √ | +| 归并排序 | O(nlogn) | × | O(n) | √ | +| 快速排序 | O(nlogn) | √ | O(logn) | × | +| 堆排序 | O(nlogn) | √ | O(1) | × | + +稳定排序:对于相等的元素,在排序后,原来靠前的元素依然靠前。相等元素的相对位置没有发生变化。 + +```java +// 可以通过⾃自定义⽐比较函数,让排序算法不不存在稳定性的问题。 +bool operator<(const Student& otherStudent){ + return score != otherStudent.score ? + score > otherStudent.score : + name < otherStudent.name; +} +``` + + + + + + + +![](../pics/sort_algorithm_analyze.png) + + + +## 二、二分查找法 + +Leetcode 704. + + + + + +## 三、递归和回溯法 + +- Leetcode 70. + +- Leetcode 17. + + + +## 四、分治的思想 + + + + + +## 五、动态规划 + +- 9-1 什么是动态规划 (20:27) + +- 9-2 第一个动态规划问题 Climbing Stairs (14:02) + + - [70. 爬楼梯](https://leetcode-cn.com/problems/climbing-stairs/) + + - 【作业】[120. 三角形最小路径和](https://leetcode-cn.com/problems/triangle/) + + - 【作业】[64. 最小路径和](https://leetcode-cn.com/problems/minimum-path-sum/) + +- 9-3 发现重叠子问题 Integer Break (25:10) + + - [343. 整数拆分](https://leetcode-cn.com/problems/integer-break/) + + - 【作业】279 + + - 【作业】91 + + - 【作业】62 + + - 【作业】63 + +- 9-4 状态的定义和状态转移 House Robber (27:19) + + - [198. 打家劫舍](https://leetcode-cn.com/problems/house-robber/) + + - 【作业】213 + + - 【作业】337 + + - 【作业】309 + +- 9-5 0-1背包问题 (32:37) + +- 9-6 0-1背包问题的优化和变种 (20:18) + +- 9-7 面试中的0-1背包问题 Partition Equal Subset Sum (27:34) + +- 9-8 LIS问题 Longest Increasing Subsequence (25:12) + + - [300. 最长递增子序列](https://leetcode-cn.com/problems/longest-increasing-subsequence/) + + - 【作业】[376. 摆动序列](https://leetcode-cn.com/problems/wiggle-subsequence/) + +- 9-9 LCS,最短路,求动态规划的具体解以及更多 (21:00) + - [1143. 最长公共子序列](https://leetcode-cn.com/problems/longest-common-subsequence/) + +- 9-10 动态规划的经典问题 + +- https://www.cxyzjd.com/article/qq_42874319/115740445 + +- + +## 六、贪心算法 08/10 + +- 10-1 贪心基础 Assign Cookies (12:12) + + - [455. 分发饼干](https://leetcode-cn.com/problems/assign-cookies/) + + - 【作业】[392. 判断子序列](https://leetcode-cn.com/problems/is-subsequence/) + +- 10-2 贪心算法与动态规划的关系 Non-overlapping Intervals (17:58) + - [435. 无重叠区间](https://leetcode-cn.com/problems/non-overlapping-intervals/) + +- 10-3 贪心选择性质的证明 (15:19) + + + +## 七、模式匹配 + +- KMP + +- RK + + + +## 八、搜索算法(深搜,广搜等) + + + +## 九、并查集 diff --git "a/notes/\345\205\250\346\240\210\345\274\200\345\217\221\347\245\236\345\205\265\345\210\251\345\231\250.md" "b/notes/\345\205\250\346\240\210\345\274\200\345\217\221\347\245\236\345\205\265\345\210\251\345\231\250.md" index 42929fb0..0e392c8c 100644 --- "a/notes/\345\205\250\346\240\210\345\274\200\345\217\221\347\245\236\345\205\265\345\210\251\345\231\250.md" +++ "b/notes/\345\205\250\346\240\210\345\274\200\345\217\221\347\245\236\345\205\265\345\210\251\345\231\250.md" @@ -87,6 +87,7 @@ - [ProcessOn](https://www.processon.com/):支持流程图、思维导图、原型图、UML、网络拓扑图、组织结构图等 - [draw.io](https://www.draw.io/):free online diagram software for making flowcharts, process diagrams, org charts, UML, ER and network diagrams - [StarUML](http://staruml.io/):A sophisticated software modeler for agile and concise modeling(总之 UML 绘图神器) +- logomakr:https://logomakr.com @@ -147,8 +148,6 @@ - - ## 四、全栈开发 ### 数据库管理(以Mysql为例) diff --git "a/notes/\346\212\200\346\234\257\344\272\244\346\265\201\347\276\244.md" "b/notes/\346\212\200\346\234\257\344\272\244\346\265\201\347\276\244.md" index 48ad00fe..441f6716 100644 --- "a/notes/\346\212\200\346\234\257\344\272\244\346\265\201\347\276\244.md" +++ "b/notes/\346\212\200\346\234\257\344\272\244\346\265\201\347\276\244.md" @@ -1,6 +1,20 @@ ▲ [GO HOME](https://github.com/frank-lam/2019_campus_apply) -# 技术交流群 +# 技术交流互动 + + + +## 1. 微信交流群 + +由于微信群二维码只有七天的有效期,大家可加我的微信(**frank_lin5**),拉你进群。 + +这里没有广告,只有纯粹的技术问题分享,不定期的干货和学习资源分享 + +
+ + + +## 2. QQ 交流群 技术交流、租房交流、健身交流、恋爱交流哈哈哈。 @@ -12,4 +26,10 @@ QQ 技术交流群:862619503 -
\ No newline at end of file +
+ + + +## 3. 微信订阅号 + +
\ No newline at end of file diff --git "a/notes/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225.md" "b/notes/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225.md" index 8353d27d..74a71393 100644 --- "a/notes/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225.md" +++ "b/notes/\346\225\260\346\215\256\347\273\223\346\236\204\344\270\216\347\256\227\346\263\225.md" @@ -1,73 +1,6 @@ - - -- [前言](#前言) -- [第一部分:数据结构](#第一部分数据结构) - - [一、线性表](#一线性表) - - [二、栈和队列](#二栈和队列) - - [三、树和二叉树](#三树和二叉树) - - [1. 红黑树](#1-红黑树) - - [2. 二叉树](#2-二叉树) - - [二分查找法](#二分查找法) - - [二叉树遍历](#二叉树遍历) - - [3. 二分搜索树](#3-二分搜索树) - - [深度优先遍历(前序、中序、后序遍历)](#深度优先遍历前序中序后序遍历) - - [广度优先遍历(层序遍历)](#广度优先遍历层序遍历) - - [4. AVL树](#4-avl树) - - [5. B和B+](#5-b和b) - - [四、字符串和数组](#四字符串和数组) -- [第二部分:算法思想](#第二部分算法思想) - - [一、排序](#一排序) - - [1. 选择排序(Selection Sort)](#1-选择排序selection-sort) - - [2. 插入排序(Insertion Sort)](#2-插入排序insertion-sort) - - [3. 冒泡排序(Bubble Sort)](#3-冒泡排序bubble-sort) - - [4. 希尔排序(Shell Sort)](#4-希尔排序shell-sort) - - [5. 归并排序(Merge Sort)](#5-归并排序merge-sort) - - [6. 快速排序(Quick Sort)](#6-快速排序quick-sort) - - [1. 普通快速排序](#1-普通快速排序) - - [2. 双路快速排序](#2-双路快速排序) - - [3. 三路快速排序](#3-三路快速排序) - - [7. 堆排序(Heap Sort)](#7-堆排序heap-sort) - - [1. 堆](#1-堆) - - [2. 上浮和下沉](#2-上浮和下沉) - - [3.插入元素](#3插入元素) - - [4. 删除最大元素](#4-删除最大元素) - - [5. 堆排序](#5-堆排序) - - [6. 堆排序的应用——Top K问题](#6-堆排序的应用top-k问题) - - [8. 计数排序和流排序](#8-计数排序和流排序) - - [9. 排序算法总结](#9-排序算法总结) - - [二、递归和回溯法](#二递归和回溯法) - - [1. 例题](#1-例题) - - [2. 排列问题](#2-排列问题) - - [3. 组合问题](#3-组合问题) - - [4. 回溯法的剪枝](#4-回溯法的剪枝) - - [5. 二维平面回溯法](#5-二维平面回溯法) - - [6. floodfill算法](#6-floodfill算法) - - [三、动态规划](#三动态规划) - - [1. 斐波那契数列](#1-斐波那契数列) - - [1.1 递归方式(自顶向下)](#11-递归方式自顶向下) - - [1.2 记忆化搜索(自底向上)](#12-记忆化搜索自底向上) - - [1.3 动态规划](#13-动态规划) - - [2. 背包问题](#2-背包问题) - - [(1)记忆化搜索](#1记忆化搜索) - - [(2)动态规划](#2动态规划) - - [(3)动态规划优化思路1](#3动态规划优化思路1) - - [(4)动态规划优化思路2](#4动态规划优化思路2) - - [(5)背包问题更多变种](#5背包问题更多变种) - - [3. 最长上升子序列](#3-最长上升子序列) - - [4. 最长公共子序列](#4-最长公共子序列) - - [四、贪心算法](#四贪心算法) - - [1. assign-cookies](#1-assign-cookies) -- [第三部分:面试指南](#第三部分面试指南) - - [1. 判单链表是否对称](#1-判单链表是否对称) - - [2. 合并两个有序数组成一个有序数组](#2-合并两个有序数组成一个有序数组) - - [3. 求二叉树中值为x的结点的层号](#3-求二叉树中值为x的结点的层号) - - [阿里面经OneNote](#阿里面经onenote) -- [第四部分:参考资料](#第四部分参考资料) - - # 前言 -本文将系统总结算法面试和经典数据结构相关知识点,在这里分成 【数据结构】 和 【算法】 两部分展开。这里将展示主要的核心知识点,关于代码面试的Leetcode习题请转向代码仓库:[Interview-code](https://github.com/frank-lam/interview_code) +本文将系统总结算法面试和经典数据结构相关知识点,在这里分成 【数据结构】 和 【算法】 两部分展开。这里将展示主要的核心知识点,关于代码面试的 Leetcode 习题请转向代码仓库:[Interview-code](https://github.com/frank-lam/interview_code) @@ -94,6 +27,8 @@ - 数组 - 链表 + + ## 二、栈和队列 @@ -168,9 +103,9 @@ private static int searchDfs(int[] data, int l, int r, int target) { } ``` -##### -### 4.. 二叉树遍历 + +### 4. 二叉树遍历 #### 深度优先遍历(前序、中序、后序遍历) @@ -300,6 +235,14 @@ public: +## 五、图结构 + +### 邻接矩阵 + +### 邻接表 + + + # 第二部分:算法思想 ## 一、排序 @@ -351,13 +294,15 @@ private static void swap(int[] arr, int i, int j) { ```java public static void sort(int[] arr) { - for (int i = 0; i < arr.length - 1; i++) { - for (int j = i + 1; j > 0; j--) { - if (arr[j] < arr[j - 1]) - swap(arr, j, j - 1); // 大量的交换会消耗时间 - else - break; + for (int i = 0; i < arr.length; i++) { + // 选择 arr[i...n) 中的最小值 + int minIndex = i; + for (int j = i + 1; j < arr.length; j++) { + if (arr[minIndex] > arr[j]) { + minIndex = j; + } } + swap(arr, i, minIndex); } } @@ -385,7 +330,7 @@ private static void swap(int[] arr, int i, int j) { **算法分析** -插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。 +插入排序在实现上,通常采用 in-place 排序(即只需用到 O(1) 的额外空间的排序),因而在从后向前扫描过程中,需要反复把已排序元素逐步向后挪位,为最新元素提供插入空间。 @@ -1676,7 +1621,6 @@ void level_in_x(BinTree BT,char x,int level) # 第四部分:参考资料 - [数据结构与算法系列 目录 - 如果天空不死 - 博客园](http://www.cnblogs.com/skywang12345/p/3603935.html) -- [Interview-Notebook/算法.md at master · CyC2018/Interview-Notebook](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/%E7%AE%97%E6%B3%95.md#%E9%80%89%E6%8B%A9%E6%8E%92%E5%BA%8F) - [十大经典排序算法](https://www.cnblogs.com/onepixel/articles/7674659.html) - [VisuAlgo - visualising data structures and algorithms through animation](https://visualgo.net/en) - [Data Structure Visualization](https://www.cs.usfca.edu/~galles/visualization/Algorithms.html) @@ -1684,4 +1628,3 @@ void level_in_x(BinTree BT,char x,int level) - diff --git "a/notes/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.md" "b/notes/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.md" index c3524ab2..0caef439 100644 --- "a/notes/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.md" +++ "b/notes/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.md" @@ -53,10 +53,9 @@ # Cheat Sheet -[![building...](https://si9ma.github.io/cool-cheatsheet/img/regexr.png)](https://si9ma.github.io/cool-cheatsheet/pdf/regexr.pdf) +[![regexr-table](assets/regexr-table.png)](https://si9ma.github.io/cool-cheatsheet/pdf/regexr.pdf) # 正则在线验证 - [RegExr: Learn, Build, & Test RegEx](https://regexr.com/) - - [Online regex tester, debugger with highlighting for PHP, PCRE, Python, Golang and JavaScript](https://regex101.com/)