diff --git a/Cache/Redis.md b/Cache/Redis.md index b43dba6..54cfc97 100644 --- a/Cache/Redis.md +++ b/Cache/Redis.md @@ -39,3 +39,10 @@ redis是一个开源的支持多种数据类型的key=>value的存储数据库 > hset、hget、hmget、hmset、hkeys、hlen、hsetnx、hvals +### redis 各种类型的场景使用 + +- string 就是存储简单的key=>value的字符串 +- list 使用场景。做先进先出的消费队列 +- set 进行集合过滤重复元素 +- zset 有序集合,排行榜 TOP N +- hash 适合存储一组数据,比如用户的信息 以用户id为键,里面记录用户的昵称等信息。 \ No newline at end of file diff --git "a/Linux/AWK\347\273\203\344\271\240.md" b/Linux/AWK.md similarity index 96% rename from "Linux/AWK\347\273\203\344\271\240.md" rename to Linux/AWK.md index 06aaa83..c920209 100644 --- "a/Linux/AWK\347\273\203\344\271\240.md" +++ b/Linux/AWK.md @@ -100,13 +100,13 @@ awk '/^UUID/{fs[$3]++}END{for(i in fs){print i,fs[i]}}' /etc/fstab - 统计日志最多的10个IP ```shell -awk '{arr[$1]++} END {for(i in arr) {print arr[i]}}' access.log | sort -k1 -nr | head -n10 +awk '{arr[$1]++} END {for(i in arr) {print i}}' access.log | sort -k1 -nr | head -n10 ``` - 统计日志访问次数大于100次的IP ```shell -awk '{arr[$1]++} END{for (i in arr) {if(arr[i] > 100){print $i}}}' access.log +awk '{arr[$1]++} END{for (i in arr) {if(arr[i] > 100){print i}}}' access.log ``` - 统计2016年4月9日内访问最多的10个ip diff --git "a/Linux/LinuxIO\346\250\241\345\236\213.md" "b/Linux/LinuxIO\346\250\241\345\236\213.md" index c1d34d8..fd585c1 100644 --- "a/Linux/LinuxIO\346\250\241\345\236\213.md" +++ "b/Linux/LinuxIO\346\250\241\345\236\213.md" @@ -119,8 +119,8 @@ aysnc_read("http://www.qq.com/",function($data){ - 非阻塞IO -还是张三去买书,老板去查询。这是时候,张三可以玩手机,然后隔段时间问,找到了没有,张三的进程没有被阻塞。但是这个任务是同步的,必须等待这个结果。就是老板没有告诉张三结果,张三是不能离开干其他的事。这个过程是同步非阻塞的。 +还是张三去买书,老板去查询。这是时候,张三可以玩手机,然后隔段时间问,张三问:"找到了没有",张三的进程没有被阻塞。但是这个任务是同步的,必须等待这个结果。就是老板没有告诉张三结果,张三是不能离开干其他的事。这个过程是同步非阻塞的。 - 异步IO -张三去买书。然后去书店问老板有没有了。老板需要查询,张三告诉老板自己的手机号,找到了打电话给我,然后就去干其他的事了。这个过程是异步的。张三的进程没有被阻塞在这个买书的环节上。这就是异步非阻塞。 \ No newline at end of file +张三去买书。然后去书店问老板有没有了。老板需要查询,张三告诉老板自己的手机号,找到了打电话给我,然后就去干其他的事了。这个过程是异步的。张三的进程没有被阻塞在这个买书的环节上。这就是异步非阻塞。 diff --git "a/Linux/Linux\345\221\275\344\273\244.md" "b/Linux/Linux\345\221\275\344\273\244.md" index c59a44a..7dfbd32 100644 --- "a/Linux/Linux\345\221\275\344\273\244.md" +++ "b/Linux/Linux\345\221\275\344\273\244.md" @@ -47,6 +47,7 @@ cat a.text| less ```shell ls /proc && echo suss! || echo failed. +cat access.log >> test.log ``` ## 文本处理 diff --git "a/Linux/Linux\345\221\275\344\273\2442.md" "b/Linux/Linux\345\221\275\344\273\2442.md" index b9f99fd..0b05ed1 100644 --- "a/Linux/Linux\345\221\275\344\273\2442.md" +++ "b/Linux/Linux\345\221\275\344\273\2442.md" @@ -2,13 +2,13 @@ 查看磁盘空间利用大小 -``` +```shell df -h ``` 查看当前目录所占空间大小 -``` +```shell du -sh ``` @@ -59,21 +59,21 @@ netstat 命令用于显示各种网络相关信息,如网络连接,路由表 列出所有端口 (包括监听和未监听的): -``` +```shell netstat -a ``` 列出所有 tcp 端口: -``` +```shell netstat -at ``` 列出所有有监听的服务状态: -``` +```shell netstat -l ``` @@ -82,7 +82,7 @@ netstat -l 缺省时free的单位为KB ```shell -$free +$ free total used free shared buffers cached Mem: 8175320 6159248 2016072 0 310208 5243680 -/+ buffers/cache: 605360 7569960 diff --git a/Linux/Nginx.md b/Linux/Nginx.md new file mode 100644 index 0000000..fac991c --- /dev/null +++ b/Linux/Nginx.md @@ -0,0 +1,269 @@ +## nginx +Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,在BSD-like 协议下发行。其特点是占有内存少,并发能力强,已经渐渐取代老牌Apache 作为新的web服务器使用。 +### 安装 + +依赖环境介绍 + +- gcc gcc-c++ + +> gcc为GNU Compiler Collection的缩写,可以编译C和C++源代码等,它是GNU开发的C和C++以及其他很多种语言 的编译器(最早的时候只能编译C,后来很快进化成一个编译多种语言的集合,如Fortran、Pascal、Objective-C、Java、Ada、 Go等。) +gcc 在编译C++源代码的阶段,只能编译 C++ 源文件,而不能自动和 C++ 程序使用的库链接(编译过程分为编译、链接两个阶段,注意不要和可执行文件这个概念搞混,相对可执行文件来说有三个重要的概念:编译(compile)、链接(link)、加载(load)。源程序文件被编译成目标文件,多个目标文件连同库被链接成一个最终的可执行文件,可执行文件被加载到内存中运行)。因此,通常使用 g++ 命令来完成 C++ 程序的编译和连接,该程序会自动调用 gcc 实现编译。 +gcc-c++也能编译C源代码,只不过把会把它当成C++源代码,后缀为.c的,gcc把它当作是C程序,而g++当作是c++程序;后缀为.cpp的,两者都会认为是c++程序,注意,虽然c++是c的超集,但是两者对语法的要求是有区别的。 +- make automake + +> make是一个用来控制可执行文件和其他一些从源文件来的非源代码文件版本的软件。Make可以从一个名为makefile的文件中获得如何构建你所写程序的依赖关系,Makefile中列出了每个目标文件以及如何由其他文件来生成它。 +`automake`是一个从`Makefile.am`文件自动生成`Makefile.in`的工具。为了生成`Makefile.in`,automake还需用到perl,由于`automake`创建的发布完全遵循GNU标准,所以在创建中不需要`perl`。libtool是一款方便生成各种程序库的工具。 + +- autoconf + +> autoconf是用来生成自动配置软件源代码脚本(configure)的工具 + +- pcre pcre-devel + +> 在Nginx编译需要 PCRE(Perl Compatible Regular Expression),因为Nginx 的Rewrite模块和HTTP 核心模块会使用到PCRE正则表达式语法。 + +- zlip zlib-devel + +> nginx启用压缩功能的时候,需要此模块的支持。 +- openssl openssl-devel + +> 开启SSL的时候需要此模块的支持。 +- libtool + +> libtool是一个通用库支持脚本,将使用动态库的复杂性隐藏在统一、可移植的接口中;使用libtool的标准方法,可以在不同平台上创建并调用动态库。 +libtool主要的一个作用是在编译大型软件的过程中解决了库的依赖问题;将繁重的库依赖关系的维护工作承担下来,从而释放了程序员的人力资源。libtool提供统一的接口,隐藏了不同平台间库的名称的差异等细节,生成一个抽象的后缀名为la高层库`libxx.la`(其实是个文本文件),并将该库对其它库的依赖关系,都写在该la的文件中。 + +```shell + +$ sudo yum -y install gcc gcc-c++ make automake autoconf pcre pcre-devel zlib zlib-devel openssl openssl-devel libtool +$ wget http://nginx.org/download/nginx-1.14.0.tar.gz +$ tar zxvf nginx-1.14.0.tar.gz +$ ./configure --prefix=/usr/local/nginx --sbin-path=/usr/local/nginx/sbin/nginx --conf-path=/usr/local/nginx/conf/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx/nginx.pid --lock-path=/var/lock/nginx.lock --user=nginx --group=nginx --with-http_ssl_module --with-http_stub_status_module --with-http_gzip_static_module --http-client-body-temp-path=/var/tmp/nginx/client/ --http-proxy-temp-path=/var/tmp/nginx/proxy/ --http-fastcgi-temp-path=/var/tmp/nginx/fcgi/ --http-uwsgi-temp-path=/var/tmp/nginx/uwsgi --http-scgi-temp-path=/var/tmp/nginx/scgi --with-pcre +$ make && make install +``` + +### nginx 配置 + +nginx的关于web服务器的配置,在http项中.每个server 对应一个主机 + +``` +http { + server {} + server{} +} +``` + + +``` +user www-data; +pid /run/nginx.pid; +worker_processes auto; +worker_rlimit_nofile 65535; + +events { + multi_accept on; + worker_connections 65535; +} + +http { + charset utf-8; + sendfile on; + tcp_nopush on; + tcp_nodelay on; + server_tokens off; + log_not_found off; + types_hash_max_size 2048; + client_max_body_size 16M; + + # MIME + include mime.types; + default_type application/octet-stream; + + # logging + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log warn; + + # SSL + ssl_session_timeout 1d; + ssl_session_cache shared:SSL:10m; + ssl_session_tickets off; + + # Diffie-Hellman parameter for DHE ciphersuites + ssl_dhparam /etc/nginx/dhparam.pem; + + # Mozilla Intermediate configuration + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; + + # OCSP Stapling + ssl_stapling on; + ssl_stapling_verify on; + resolver 1.1.1.1 1.0.0.1 8.8.8.8 8.8.4.4 208.67.222.222 208.67.220.220 valid=60s; + resolver_timeout 2s; + #负载均衡 + upstream backend{ + server 127.0.0.1:8050 weight=1 max_fails=2 fail_timeout=10 ; + server 127.0.0.1:8060 weight=2 max_fails=2 fail_timeout=10 ; + } + + + # gzip + gzip on; + gzip_vary on; + gzip_proxied any; + gzip_comp_level 6; + gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml; + # HTTP 301 重定向 + server { + listen 80; + listen [::]:80; + server_name .example.com; + location / { + return 301 https://www.example.com$request_uri; + } + } + + server { + listen 80;#监听端口 + server_name example.com;#绑定域名 + root /var/www/example.com/public;#网站根目录 + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log warn; + + # index.html fallback + location / { + try_files $uri $uri/ /index.html; + } + # favicon.ico + location = /favicon.ico { + log_not_found off; + access_log off; + } + + # robots.txt + location = /robots.txt { + log_not_found off; + access_log off; + } + + # assets, media + location ~* \.(?:css(\.map)?|js(\.map)?|jpe?g|png|gif|ico|cur|heic|webp|tiff?|mp3|m4a|aac|ogg|midi?|wav|mp4|mov|webm|mpe?g|avi|ogv|flv|wmv)$ { + expires 7d; + access_log off; + } + + # svg, fonts + location ~* \.(?:svgz?|ttf|ttc|otf|eot|woff2?)$ { + add_header Access-Control-Allow-Origin "*"; + expires 7d; + access_log off; + } + # php配置 + location ~ \.php$ { + include fastcgi_params; + # fastcgi settings + # fastcgi_pass 127.0.0.1:9000 + fastcgi_pass unix:/var/run/php/php7.2-fpm.sock; + fastcgi_index index.php; + fastcgi_buffers 8 16k; + fastcgi_buffer_size 32k; + # fastcgi params + fastcgi_param DOCUMENT_ROOT $realpath_root; + fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; + fastcgi_param PHP_ADMIN_VALUE "open_basedir=$base/:/usr/lib/php/:/tmp/"; + } + #反向代理 + location ^~/api/ { + proxy_pass http://127.0.0.1:3000; + proxy_http_version 1.1; + proxy_cache_bypass $http_upgrade; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Port $server_port; + } + # 负载 + location ^~/user/ { + proxy_set_header X-Real-IP $remote_addr; + proxy_pass http://backend + } + } + +} +``` + +### nginx 负载均衡的方式 + +- 权重 +``` +upstream backend{ + server 10.0.0.77 weight=5; + server 10.0.0.88 weight=10; +} +``` +- ip_hash + +根据客户端的ip的hash结果进行分配。这样每一个访客的固定访问的服务器都是一台机器 +``` +upstream backend{ + server 10.0.0.77; + server 10.0.0.88; +} +``` +- fair 第三方 + +按后端服务器的响应时间来分配请求。响应时间短的优先分配。 + +``` + upstream backend{ + server 10.0.0.10:8080; + server 10.0.0.11:8080; + fair; +} +``` +- url_hash +按訪问url的hash结果来分配请求,使每一个url定向到同一个后端服务器。后端服务器为缓存时比較有效。 + +``` + upstream backend{ + server 10.0.0.10:7777; + server 10.0.0.11:8888; + hash $request_uri; + hash_method crc32; +} + +upstream bakend{ + #定义负载均衡设备的Ip及设备状态 + ip_hash; + server 10.0.0.11:9090 down; + server 10.0.0.11:8080 weight=2; + server 10.0.0.11:6060; + server 10.0.0.11:7070 backup; +} +``` + +upstream还能够为每一个设备设置状态值,这些状态值的含义分别例如以下: + +- down 表示单前的server临时不參与负载. + +- weight 默觉得1.weight越大,负载的权重就越大。 + +- max_fails :同意请求失败的次数默觉得1.当超过最大次数时,返回proxy_next_upstream 模块定义的错误. + +- fail_timeout : max_fails次失败后。暂停的时间。 + +- backup: 其他全部的非backup机器down或者忙的时候,请求backup机器。所以这台机器压力会最轻。 + +### nginx启动 和停止停止 +```shell +$ /usr/local/nginx/sbin/nginx + +#平滑启动 +$ /usr/local/nginx/sbin/nginx -s reload +#停止 +$ /usr/local/nginx/sbin/nginx -s stop +``` \ No newline at end of file diff --git a/Linux/README.md b/Linux/README.md index abb385f..b06ebf7 100644 --- a/Linux/README.md +++ b/Linux/README.md @@ -1,17 +1,3 @@ -## 目录 - -- [操作系统概述](Linux#%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E6%A6%82%E8%BF%B0) -- [Linux历史](#gnu%E5%92%8Cgpl) -- [Linux基本命令](Linux%E5%91%BD%E4%BB%A4.md) -- [Linux(磁盘网络相关命令)](Linux%E5%91%BD%E4%BB%A42.md) -- [Crontab计划任务](crontab.md) -- [shell](shell.md) -- [进程和线程](进程和线程.md) -- [AWK命令](AWK%E7%BB%83%E4%B9%A0.md) -- [SED命令](Sed%E7%BB%83%E4%B9%A0.md) - - - ## 操作系统概述 操作系统,英文名称Operating System,简称OS,是计算机系统中必不可少的基础系统软件,它是应用程序运行以及用户操作必备的基础环境支撑,是计算机系统的核心。 @@ -20,7 +6,36 @@  -## Linux和Unix +### 内核态和用户态 + +操作系统为了管理内存。将内存分为**内核空间**(内核态)和**用户空间**。内存空间和用户空间之间有隔离。程序需要访问系统资源必须向内核空间进行申请。由内核把数据读取到用户空间。 + +Linux操作系统中主要采用了0和3两个特权级,分别对应的就是内核态和用户态。运行于用户态的进程可以执行的操作和访问的资源都会受到极大的限制,而运行在内核态的进程则可以执行任何操作并且在资源的使用上没有限制。很多程序开始时运行于用户态,但在执行的过程中,一些操作需要在内核权限下才能执行,这就涉及到一个从用户态切换到内核态的过程 + +应用程序访问内核,一般有两种调用方式:系统调用和库函数调用 + +**系统调用**:应用程序直接调用操作系统提供的接口 如write 函数 + +**库函数调用**:应用程序通过一些库函数直接调用 如 fwrite + +系统调用(英语:system call),指运行在用户空间的应用程序向操作系统内核请求某些服务的调用过程。 系统调用提供了用户程序与操作系统之间的接口。一般来说,系统调用都在内核态执行。由于系统调用不考虑平台差异性,由内核直接提供,因而移植性较差(几乎无移植性)。 + +库函数(library function),是由用户或组织自己开发的,具有一定功能的函数集合,一般具有较好平台移植性,通过库文件(静态库或动态库)向程序员提供功能性调用。程序员无需关心平台差异,由库来屏蔽平台差异性。 + +| 函数库调用 | 系统调用 | +| ----------------------------- | ----------------------- | +| 平台移植性好 | 依赖于内核,不保证移植性 | +| 调用函数库中的一段程序(或函数) | 调用系统内核的服务 | +| 一个普通功能函数的调用 | 是操作系统的一个入口点 | +| 在**用户空间**执行 | 在**内核空间**执行 | +| 它的运行时间属于“用户时间” | 它的运行时间属于“系统”时间 | +| 属于过程调用,调用开销较小 | 在用户空间和内核上下文环境间切换,开销较大 | +| 库函数数量较多 | UNIX中大约有90个系统调用,较少 | +| 典型的C函数库调用:printf scanf malloc | 典型的系统调用:fork open write | + +**用户空间即上层应用程序的活动空间**,应用程序的执行必须依托于内核提供的资源,包括CPU资源、存储资源、I/O资源等。为了使上层应用能够访问到这些资源,内核必须为上层应用提供访问的接口:即系统调用 + +## Linux和Unix Unix系统于1969年在AT&T的贝尔实验室诞生,20世纪70年代,它逐步盛行,这期间,又产生了一个比较重要的分支,就是大约1977年诞生的BSD(Berkeley Software Distribution)系统。从BSD系统开始,各大厂商及商业公司开始了根据自身公司的硬件架构,并以BSD系统为基础进行Unix系统的研发,从而产生了各种版本的Unix系统. diff --git "a/Linux/Sed\347\273\203\344\271\240.md" b/Linux/Sed.md similarity index 100% rename from "Linux/Sed\347\273\203\344\271\240.md" rename to Linux/Sed.md diff --git a/Linux/lanmp.md b/Linux/lanmp.md index 0d3a928..e8e2528 100644 --- a/Linux/lanmp.md +++ b/Linux/lanmp.md @@ -119,13 +119,4 @@ AddType application/x-httpd-php .php ```SHELL service httpd restart -``` - - - -### Lnmp安装 - - - - - +``` \ No newline at end of file diff --git a/Linux/shell.md b/Linux/shell.md index 252ee07..e91873f 100644 --- a/Linux/shell.md +++ b/Linux/shell.md @@ -7,7 +7,7 @@ - 头声明 -shell脚本第一行必须以 #!开头,它表示该脚本使用后面的解释器解释执行。 +shell脚本第一行必须以 #!开头,它表示该脚本使用后面的解释器解释执行。 ```shell #!/bin/bash diff --git "a/Mysql/\347\264\242\345\274\225.md" "b/Mysql/\347\264\242\345\274\225.md" index 829abde..b203f38 100644 --- "a/Mysql/\347\264\242\345\274\225.md" +++ "b/Mysql/\347\264\242\345\274\225.md" @@ -1,60 +1,163 @@ -**Mysql索引概念:** +## 索引分类 -说说Mysql索引,看到一个很少比如:索引就好比一本书的目录,它会让你更快的找到内容,显然目录(索引)并不是越多越好,假如这本书1000页,有500页是目录,它当然效率低,目录是要占纸张的,而索引是要占磁盘空间的。 +**从物理存储角度** -**Mysql索引主要有两种结构:B+tree和hash.** +1. 聚集索引(clustered index) -hash:hsah索引在mysql比较少用,他以把数据的索引以hash形式组织起来,因此当查找某一条记录的时候,速度非常快.当时因为是hash结构,每个键只对应一个值,而且是散列的方式分布.所以他并不支持范围查找和排序等功能. +2. 非聚集索引(non-clustered index)、 二级索引 -B+树:b+tree是mysql使用最频繁的一个索引数据结构,数据结构以平衡树的形式来组织,因为是树型结构,所以更适合用来处理排序,范围查找等功能.相对hash索引,B+树在查找单条记录的速度虽然比不上hash索引,但是因为更适合排序等操作,所以他更受用户的欢迎.毕竟不可能只对数据库进行单条记录的操作. +**从逻辑角度** -**Mysql常见索引:**主键索引、唯一索引、普通索引、全文索引、组合索引 +1. 主键索引:主键索引是一种特殊的唯一索引,不允许有空值 -PRIMARY KEY(主键索引) ALTER TABLE \`table_name\` ADD PRIMARY KEY ( \`column\` ) +2. 普通索引或者单列索引 - UNIQUE(唯一索引) ALTER TABLE \`table_name\` ADD UNIQUE (\`column\`) +3. 多列索引(联合索引):复合索引指多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用复合索引时遵循最左前缀集合 -INDEX(普通索引) ALTER TABLE \`table\_name\` ADD INDEX index\_name ( \`column\` ) +4. 唯一索引或者非唯一索引 -FULLTEXT(全文索引) ALTER TABLE \`table_name\` ADD FULLTEXT ( \`column\` ) +5. 空间索引:空间索引是对空间数据类型的字段建立的索引,MYSQL中的空间数据类型有4种,分别是GEOMETRY、POINT、LINESTRING、POLYGON。 + MYSQL使用SPATIAL关键字进行扩展,使得能够用于创建正规索引类型的语法创建空间索引。创建空间索引的列,必须将其声明为NOT NULL,空间索引只能在存储引擎为MYISAM的表中创建 -组合索引 ALTER TABLE \`table\_name\` ADD INDEX index\_name ( \`column1\`, \`column2\`, \`column3\` ) +**从数据结构角度** -**Mysql各种索引区别:** +1. B-Tree索引 -普通索引:最基本的索引,没有任何限制 -唯一索引:与"普通索引"类似,不同的就是:索引列的值必须唯一,但允许有空值。 +2. Hash索引: + a 仅仅能满足"=","IN"和"<=>"查询,不能使用范围查询 + b 其检索效率非常高,索引的检索可以一次定位,不像B-Tree 索引需要从根节点到枝节点,最后才能访问到页节点这样多次的IO访问,所以 Hash 索引的查询效率要远高于 B-Tree 索引 + c 只有Memory存储引擎显示支持hash索引 -主键索引:它 是一种特殊的唯一索引,不允许有空值。 +3. FULLTEXT索引(现在MyISAM和InnoDB引擎都支持了) -全文索引:仅可用于 MyISAM 表,针对较大的数据,生成全文索引很耗时好空间。 +4. R-Tree索引 -组合索引:为了更多的提高mysql效率可建立组合索引,遵循”最左前缀“原则。 -**B+Tree** - +## 聚簇索引(cluster index) -1. 所有关键字都在叶子结点出现 +**聚簇索引、聚集索引 一个意思**。 -2. 所有叶子结点增加一个链指针 +指索引项的排序方式和表中数据记录排序方式一致的索引。一个表至少有一个聚集索引。 -## 聚集索引和辅助索引、覆盖索引 +1. 如果表设置了主键,则主键就是聚簇索引 -- 聚集索引(主键索引) +2. 如果表没有主键,则会默认第一个NOT NULL,且唯一(UNIQUE)的列作为聚簇索引 -—innodb存储引擎是索引组织表,即表中的数据按照主键顺序存放。而聚集索引就是按照每张表的主键构造一颗B+树,同时叶子节点中存放的即为整张表的记录数据 +3. 以上都没有,则会默认创建一个隐藏的row_id作为聚簇索引 -—聚集索引的叶子节点称为数据页,数据页,数据页!重要的事说三遍。聚集索引的这个特性决定了索引组织表中的数据也是索引的一部分。 +InnoDB的聚簇索引的叶子节点存储的是行记录(其实是页结构,一个页包含多行数据),InnoDB必须要有至少一个聚簇索引。 -- 辅助索引(二级索引) +由此可见,使用聚簇索引查询会很快,因为可以直接定位到行记录。 -—非主键索引 +> InnoDB的聚簇索引的叶子节点存储的是行记录(其实是页结构,一个页包含多行数据),InnoDB必须要有至少一个聚簇索引。 +> +> 由此可见,使用聚簇索引查询会很快,因为可以直接定位到行记录。 -—叶子节点=键值+书签。Innodb存储引擎的书签就是相应行数据的主键索引值 +## 普通索引 -- 覆盖索引 +普通索引也叫**二级索引**,除聚簇索引外的索引,即非聚簇索引。 -如果查询的列恰好是索引的一部分,那么查询只需要在索引文件上进行,不需要进行到磁盘中找数据,若果查询得列不是索引的一部分则要到磁盘中找数据 +InnoDB的普通索引叶子节点存储的是主键(聚簇索引)的值,而MyISAM的普通索引存储的是记录指针。 -使用explain,可以通过输出的extra列来判断,对于一个索引覆盖查询,显示为**using index**,MySQL查询优化器在执行查询前会决定是否有索引覆盖查询 +```mysql +mysql> create table user( + -> id int(10) auto_increment, + -> name varchar(30), + -> age tinyint(4), + -> primary key (id), + -> index idx_age (age) + -> )engine=innodb charset=utf8mb4; +insert into user(name,age) values('张三',30); +insert into user(name,age) values('李四',20); +insert into user(name,age) values('王五',40); +insert into user(name,age) values('刘八',10); +``` + + + +### 聚簇索引的结构 + +> 叶子节点存储行信息 + + + +### 非聚簇索引结构 + +> 非聚簇索引,其叶子节点存储的是聚簇索引的的值 + + + +```sql +select * from user where id =1 +select * from user where age = 20 +``` + +对于聚簇索引查询,需要扫描聚簇索引,一次即可扫描到记录, + +对于非聚簇索引,利用非非聚簇索引,扫到聚簇索引的值,然后会再次到聚簇索引中查找, + +### 回表查询 + +先通过普通索引的值定位聚簇索引值,再通过聚簇索引的值定位行记录数据,需要扫描两次索引B+树,它的性能较扫一遍索引树更低。这个过程叫做回表 + +### 索引覆盖 + +查询的结果字段包含所有索引信息,不会回表,比如 + +```sql +select id,age from user where age = 10 +``` + +当age索引一次扫描到聚簇索引的值的时候,正好得到了所有的结果,避免了回表操作。 + +实现: 将被查询的字段,建立到联合索引里去。 + +可以通过`explain` ,查看extra字段。如果是`use index` 则使用了索引,如果是`null` 则进行了回表 + +- count 优化 +- 列查询优化 +- 分页查询优化 + + + +## 索引下推 + +索引下推(index condition pushdown) 指的是Mysql需要一个表中检索数据的时候,会使用索引过滤掉不符合条件的数据,然后再返回给客户端 + +```sql +"select * from user where username like '张%' and age > 10" +``` + +**没有ICP** + +根据(username,age)联合索引查询所有满足名称以“张”开头的索引,然后回表查询出相应的全行数据,然后再筛选出满足年龄小于等于10的用户数据 + +1,获取下一行,首先读索引元组,然后使用索引去查找并读取所有的行 + +2,根据WHERE条件部分,判断数据是否符合。根据判断结果接受或拒绝该行 + +**使用ICP** + +这个过程则会变成这样: + +根据(username,age)联合索引查询所有满足名称以“张”开头的索引,然后直接再筛选出年龄小于等于10的索引,之后再回表查询全行数据 + + + +1,获取下一行的索引元组(不是所有行) + +2,根据WHERE条件部分,判断是否可以只通过索引列满足条件。如果不满足,则获取下一行索引元组 + +3,如果满足条件,则通过索引元组去查询并读取所有的行 + +4,根据遗留的WHERE子句中的条件,在当前表中进行判断,根据判断结果接受或者拒绝改行 + + + +ICP默认启动。可以通过optimizer_switch系统变量去控制它是否开启: + +```ini +SET optimizer_switch = 'index_condition_pushdown=off'; +SET optimizer_switch = 'index_condition_pushdown=on'; +``` diff --git "a/Mysql/\351\224\201.md" "b/Mysql/\351\224\201.md" new file mode 100644 index 0000000..2cfa199 --- /dev/null +++ "b/Mysql/\351\224\201.md" @@ -0,0 +1,64 @@ +## 乐观锁 +乐观锁是指操作数据库时(更新操作),想法很乐观,认为这次的操作不会导致冲突,在操作数据时,并不进行任何其他的特殊处理(也就是不加锁),而在进行更新后,再去判断是否有冲突了。通常的做法,是在表中加个版本字段,更新的时候和读取的时候做比对。如果一致则进行修改。 +```sql +select v,name from table where id =1 ;// 假设v = 1 + +update table set name ="test" and v = v+1 where id = 1 and v =1;// 如果v之前有过修改,则此次修改失败。 +``` +## 悲观锁 +与乐观锁相对应的就是悲观锁了。悲观锁就是在操作数据时,认为此操作会出现数据冲突,所以在进行每次操作时都要通过获取锁才能进行对相同数据的操作,所以悲观锁需要耗费较多的时间。另外与乐观锁相对应的,悲观锁是由数据库自己实现了的,要用的时候,我们直接调用数据库的相关语句就可以 +### 共享锁 [读锁] + +共享锁指的就是对于多个不同的事务,对同一个资源共享同一个锁。相当于对于同一把门,它拥有多个钥匙一样。就像这样,你家有一个大门,大门的钥匙有好几把,你有一把,你女朋友有一把,你们都可能通过这把钥匙进入你们家,这个就是所谓的共享锁。 +```sql +begin +select id from `table` where id = 1 lock in share mode; + +# 第二个客户端 +update `table` set name = "ddte" where id =1 ;//报错 第一个事务没提交 +``` +### 排它锁 [写锁] +排它锁与共享锁相对应,就是指对于多个不同的事务,对同一个资源只能有一把锁。 + +与共享锁类型,在需要执行的语句后面加上for update就可以了(对于Innodb引擎语句后面加上for update表示把此行数据锁定,MyISAM则是锁定整个表。) +```sql +select …… for update; +``` +## MVCC + +mysql的innodb采用的是行锁,而且采用了多版本并发控制来提高读操作的性能。 + +MVVC (Multi-Version Concurrency Control) (注:与MVCC相对的,是基于锁的并发控制,Lock-Based Concurrency Control)是一种基于多版本的并发控制协议,只有在InnoDB引擎下存在。MVCC是为了实现事务的隔离性,通过版本号,避免同一数据在不同事务间的竞争,你可以把它当成基于多版本号的一种乐观锁。当然,这种乐观锁只在事务级别未提交锁和已提交锁时才会生效。MVCC最大的好处,相信也是耳熟能详:读不加锁,读写不冲突。在读多写少的OLTP应用中,读写不冲突是非常重要的,极大的增加了系统的并发性能 + +### 实现方式 + InnoDB在每行数据都增加两个隐藏字段,一个记录创建的版本号,一个记录删除的版本号。 +版本链 + +在InnoDB引擎表中,它的聚簇索引记录中有两个必要的隐藏列: + +- trx_id +这个id用来存储的每次对某条聚簇索引记录进行修改的时候的事务id。 +- roll_pointer +每次对哪条聚簇索引记录有修改的时候,都会把老版本写入undo日志中。这个roll_pointer就是存了一个指针,它指向这条聚簇索引记录的上一个版本的位置,通过它来获得上一个版本的记录信息。(注意插入操作的undo日志没有这个属性,因为它没有老版本) + +| id | name | trx_id | roll_pointer | +| ---- | ---- | ------ | ------------ | +| 1 | 1 | 50 | 49 | +| | | | | + +在多版本并发控制中,为了保证数据操作在多线程过程中,保证事务隔离的机制,降低锁竞争的压力,保证较高的并发量。在每开启一个事务时,会生成一个事务的版本号,被操作的数据会生成一条新的数据行(临时),但是在提交前对其他事务是不可见的,对于数据的更新(包括增删改)操作成功,会将这个版本号更新到数据的行中,事务提交成功,将新的版本号更新到此数据行中,这样保证了每个事务操作的数据,都是互不影响的,也不存在锁的问题。 +### MVVC下的CRUD +- SELECT: + 当隔离级别是REPEATABLE READ时select操作,InnoDB必须每行数据来保证它符合两个条件: + 1、InnoDB必须找到一个行的版本,它至少要和事务的版本一样老(也即它的版本号不大于事务的版本号)。这保证了不管是事务开始之前,或者事务创建时,或者修改了这行数据的时候,这行数据是存在的。 + 2、这行数据的删除版本必须是未定义的或者比事务版本要大。这可以保证在事务开始之前这行数据没有被删除。 +符合这两个条件的行可能会被当作查询结果而返回。 + + +- INSERT:InnoDB为这个新行记录当前的系统版本号。 + +- DELETE:InnoDB将当前的系统版本号设置为这一行的删除ID。 +- UPDATE:InnoDB会写一个这行数据的新拷贝,这个拷贝的版本为当前的系统版本号。它同时也会将这个版本号写到旧行的删除版本里。 + +这种额外的记录所带来的结果就是对于大多数查询来说根本就不需要获得一个锁。他们只是简单地以最快的速度来读取数据,确保只选择符合条件的行。这个方案的缺点在于存储引擎必须为每一行存储更多的数据,做更多的检查工作,处理更多的善后操作。 + MVCC只工作在REPEATABLE READ和READ COMMITED隔离级别下。READ UNCOMMITED不是MVCC兼容的,因为查询不能找到适合他们事务版本的行版本;它们每次都只能读到最新的版本。SERIABLABLE也不与MVCC兼容,因为读操作会锁定他们返回的每一行数据。 \ No newline at end of file diff --git a/PHP/PHP8.1.md b/PHP/PHP8.1.md new file mode 100644 index 0000000..6987891 --- /dev/null +++ b/PHP/PHP8.1.md @@ -0,0 +1,91 @@ +PHP8.1在2021年11月25日发布了。又带来了很多很特性和性能改进。 + +## 枚举 +Enum 只支持整型和字符串两种类型 +```php +enum Status: int { + case SUCCESS = 0 + case ERROR = 1 +} + +function test (Status $status) { + +} +test(Status::SUCCESS) +``` + +### 枚举属性和方法 + +枚举有两个属性 `name` 、`value` +```php + $status->name; + $status->value; +``` +Enum 提供了 from () 方法来通过选项 value 的值来获取对应的选项 + +```php +dump(Status::from(0)); + +``` + +## 数组解包 +```php +$array_1 = [ + 'key1' => 'foo', + 'key2' => 'bar' +]; +$array_2 = [ + 'key3' => 'baz', + 'key4' => 'qux' +]; + +$array_unpacked = [...$array_1, ...$array_2]; +dd($array_unpacked); +``` +新增`array_is_list ` 判断是否是从 0 开始递增的数字数组 +```php +dump(array_is_list(['apple', 'orange'])); +``` + +## 类相关 + +### 只读属性readonly + +只读属性只允许初始化一次,修改 readonly 属性就会报错,只能在类的内部使用。 + +```php +class User { + public readonly int $uid; + + public function __construct(int $uid) { + $this->uid = $uid; + } +} +$user = new User(12) +$user->uid = 1;//error +``` +### final 类常量 + +```php +class Foo { + final public const TEST = '1'; +} + +``` + +## 函数相关 + +1. First-class Callable + +```php +$callable = strtoupper(...); +echo $callable('hello, world') . PHP_EOL; +``` +2. Never 返回类型 + +```php +function redirect(string $url): never { + header('Location: ' . $url); + exit(); +} +``` diff --git a/PHP/PHP8.2.md b/PHP/PHP8.2.md new file mode 100644 index 0000000..c0d6206 --- /dev/null +++ b/PHP/PHP8.2.md @@ -0,0 +1,72 @@ +## PHP8.2的变化 +- [PHP8.2](https://www.php.net/releases/8.2/zh.php) +### 只读类 +使用`readonly` 修饰类名 +```php +title = $title; + $this->status = $status; + } +} +``` +### 析取范式 (DNF)类型 +简单的理解,就是定义参数支持交集和并集,'组合并集和交集类型时,交集类型必须用括号进行分组' +```php +class Foo { + public function bar((A&B)|null $entity) { + return $entity; + } +} +``` +### 允许 null、false 和 true 作为独立类型 +```php + +function f(): false { + return false; +} +function f1(): true { + return true; +} +function f2(): null { + return null; +} +``` +### 新的“随机”扩展 +`\Random\Randomizer` 类提供了一个高级接口来使用引擎的随机性来生成随机整数、随机排列数组或字符串、选择随机数组键等。 + +### Traits 中允许常量 +```php +trait T +{ + public const CONSTANT = 1; +} +``` +### 弃用动态属性 +动态属性的创建已被弃用,以帮助避免错误和拼写错误,除非该类通过使用 `#[\AllowDynamicProperties]` 属性来选择。`stdClass` 允许动态属性。 +__get/__set 魔术方法的使用不受此更改的影响。 +```php +class User +{ + public $name; +} + +$user = new User(); +$user->last_name = 'Doe'; // Deprecated notice + +$user = new stdClass(); +$user->last_name = 'Doe'; // Still allowed +``` + +## 弃用和向后不兼容 +- 弃用 ${} 字符串插值。 +- 弃用 utf8_encode 和 utf8_decode 函数。 +- DateTime::createFromImmutable 和 DateTimeImmutable::createFromMutable 方法暂定返回类型为 static。 +- strtolower 和 strtoupper 函数不再对语言环境敏感。 diff --git a/PHP/PHP8.md b/PHP/PHP8.md new file mode 100644 index 0000000..8644cc8 --- /dev/null +++ b/PHP/PHP8.md @@ -0,0 +1,78 @@ +## PHP8新特性 + +#### 1. 命名参数 + +命名参数实现了我们调用函数的时候,不用严格函数的定义顺序。 + +```php +function test($a,$b,$c) { + echo sprintf("a=%s,b=%s,c=%s \n",$a,$b,$c); +} +test("1",c:'2',b:"3"); +test(c: 'c',b: '2',a:"1"); +``` + +### 2. 注解 + + PHP 原生语法来使用结构化的元数据 + +```php +#[Route("/api/posts/{id}")] +function Attribute() { +} +$ref = new ReflectionFunction("Attribute"); +var_dump($ref->getAttributes("Route")[0]->getName()); //Route +var_dump($ref->getAttributes("Route")[0]->getArguments());// +``` + +### 3. 联合类型 + +联合类型 就是一个类型可以多个类型的其中一个 + +```php +class C +{ + private string|int $name; + + public function setName($name){ + $this->name = $name; + echo $this->name.PHP_EOL; + } +} + +$c = new C(); +$c->setName(1); +$c->setName("123"); +$c->setName([]);//error +``` + +### 4. Match表达式 + +新的 match 类似于 switch,并具有以下功能: + +- Match 是一个表达式,它可以储存到变量中亦可以直接返回。 +- Match 分支仅支持单行,它不需要一个 break; 语句。 +- Match 使用严格比较 + +```php +echo match (8.0) { + '8.0' => "Oh no!", + 8.0 => "This is what I expected", +}; +``` + +### 5. **字符串与数字的比较逻辑** + +```php +#php8 +0 == 'foobar' // false +#php7 +0 == 'foobar' // true + +``` + +### 6. JIT 即时编译 + +### 7. 新的类、接口、函数 + +- `str_contains` 字符串包含 、`str_starts_with` 以字符串开始、`str_ends_with` diff --git a/README.md b/README.md index 456b7dd..35d7eaa 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -## PHP面试准备的资料 +##
+
+
+**前序遍历,先访问根结点,然后在访问左子树,最后访问右子树。可以利用栈的特点,这里我结合了队列和栈的特点来实现。先压入树,取出根节点。先把根节点值push到队列中,然后把右子树压入栈中,最后压入左子树。返回队列。当然你可以调整成你想要的实现方式。(只要前中后序顺序理解正确即可)**
+
+```php
+
+/**
+ * Definition for a binary tree node.
+ * class TreeNode {
+ * public $val = null;
+ * public $left = null;
+ * public $right = null;
+ * function __construct($value) { $this->val = $value; }
+ * }
+ */
+
+class Solution {
+
+ /**
+ * @param TreeNode $root
+ * @return Integer[]
+ */
+ function preorderTraversal($root) {
+ $res=[];
+ $list=[];
+ array_unshift($res,$root);
+ while(!empty($res)){
+ $current=array_shift($res);
+ if($current==null) continue;
+ array_push($list,$current->val);
+ array_unshift($res,$current->right);
+ array_unshift($res,$current->left);
+ }
+ return $list;
+ }
+}
+
+```
+
+****
+
+### :pencil2:2.二叉树的中序遍历(leetcode94)
+
+
+
+
+
+```php
+ /**
+ * @param TreeNode $root
+ * @return Integer[]
+ */
+ function inorderTraversal($root) {
+ $res=[];
+ $this->helper($root,$res);
+ return $res;
+ }
+ function helper($root,&$res){
+ if($root !=null){
+ if($root->left !=null) $this->helper($root->left,$res);
+ array_push($res,$root->val);
+ if($root->right !=null) $this->helper($root->right,$res);
+ }
+
+ }
+```
+
+**或者不用递归**
+
+```php
+ /**
+ * @param TreeNode $root
+ * @return Integer[]
+ */
+ function inorderTraversal($root) {
+ $res=[];
+ $list=[];
+ while(!empty($list) || $root !=null){
+ while($root != null){
+ array_unshift($list,$root);
+ $root=$root->left;
+ }
+ $root=array_shift($list);
+ array_push($res,$root->val);
+ $root=$root->right;
+ }
+ return $res;
+ }
+```
+****
+
+
+### :pencil2:3.二叉树的后序遍历(leetcode145)
+
+
+
+
+
+```php
+ /**
+ * @param TreeNode $root
+ * @return Integer[]
+ */
+ function postorderTraversal($root) {
+ $list=[];
+ $res=[];
+ array_push($list,$root);
+ while(!empty($list)){
+ $node=array_shift($list);
+ if(!$node) continue;
+ array_unshift($res,$node->val);
+ array_unshift($list,$node->left);
+ array_unshift($list,$node->right);
+ }
+ return $res;
+ }
+ }
+```
+****
+
+### :pencil2:4.二叉树的层次遍历(leetcode102)
+
+
+
+
+
+**DFS和BFS都可以解,竟然已经要我们按照层打印了,那么先使用BFS,思路就是先判断树是否是空,不是空加入一个队列的结构中,如果队列不为空,取出头元素,那么当前元素表示的就是当前这一层了,所以只需要遍历这一层里的所有的元素即可,然后下一层....**
+
+```php
+ class Solution {
+
+ /**
+ * @param TreeNode $root
+ * @return Integer[][]
+ */
+ function levelOrder($root) {
+ if(empty($root)) return [];
+ $result = [];
+ $queue = [];
+ array_push($queue,$root);
+ while(!empty($queue)){
+ $count = count($queue);
+ $leveQueue = [];
+ for($i = 0;$i<$count;$i++){
+ $node = array_shift($queue);
+ array_push($leveQueue,$node->val);
+ if($node->left) array_push($queue,$node->left);
+ if($node->right) array_push($queue,$node->right);
+ }
+ array_push($result,$leveQueue);
+ }
+ return $result;
+ }
+ }
+```
+
+**如果使用DFS的话,就是一条路走到黑,然后再重新一路路的退回来再找下一路,所以这样的话,每一次我们需要记录一下当前他所在的这个点属于哪一层即可,代码用递归实现。**
+```php
+
+class Solution {
+
+ /**
+ * @param TreeNode $root
+ * @return Integer[][]
+ */
+ function levelOrder($root) {
+ if(empty($root)) return [];
+ $result=[];
+ $this->helper($result,$root,0);
+ return $result;
+ }
+
+ function helper(&$result,$node,$level){
+ if(empty($node)) return ;
+ if(count($result)<$level+1){
+ array_push($result,[]); //说明当前行没有结果
+ }
+ array_push($result[$level],$node->val);
+ $this->helper($result,$node->left,$level+1);
+ $this->helper($result,$node->right,$level+1);
+ }
+}
+
+```
+****
+
+### :pencil2:5.二叉树的最大深度(leetcode104)
+
+
+
+
+
+**DFS和BFS都可以解,竟然已经要我们按照层打印了,那么先使用BFS,思路就是先判断树是否是空,不是空加入一个队列的结构中,如果队列不为空,取出头元素,那么当前元素表示的就是当前这一层了,所以只需要遍历这一层里的所有的元素即可,然后下一层....**
+
+```php
+
+ /**
+ * @param TreeNode $root
+ * @return Integer
+ */
+ function maxDepth($root) {
+ if(empty($root)) return 0;
+ $left = $this->maxDepth($root->left);
+ $right = $this->maxDepth($root->right);
+ return $left<$right? $right+1:$left+1;
+ return max($left,$right)+1;
+ }
+```
+****
+
+### :pencil2:6.二叉树的最小深度(leetcode111)
+
+
+
+
+
+**DFS和BFS都可以求解**
+
+```php
+
+ //BFS
+ /**
+ * @param TreeNode $root
+ * @return Integer
+ */
+ function minDepth($root) {
+ if(empty($root)) return 0;
+ if(!$root->right) return $this->minDepth($root->left)+1;
+ if(!$root->left) return $this->minDepth($root->right)+1;
+ $left=$this->minDepth($root->left);
+ $right=$this->minDepth($root->right);
+ return min($left,$right)+1;
+
+ }
+```
+
+```php
+
+//DFS
+ /**
+ * @param TreeNode $root
+ * @return Integer
+ */
+ function minDepth($root) {
+ if(empty($root)) return 0;
+ $left=$this->minDepth($root->left);
+ $right=$this->minDepth($root->right);
+ if($left==0 || $right==0) return $left+$right+1;
+ return min($left,$right)+1;
+ }
+```
+****
+
+
+### :pencil2:7.判断是否是平衡二叉树(leetcode110)
+
+
+
+
+
+**每一节点的两个子树的深度相差不能超过1。如果是空树,直接true。**
+
+```php
+
+
+class Solution {
+
+ /**
+ * @param TreeNode $root
+ * @return Boolean
+ */
+ private $result=true;
+ function isBalanced($root) {
+ if(empty($root)) return true;
+ $this->helper($root);
+ return $this->result;
+ }
+ function helper($root)
+{
+ if(!$root) return ;
+ $left=$this->helper($root->left);
+ $right=$this->helper($root->right);
+ if(abs($left-$right)>1) $this->result=false;
+ return max($left,$right)+1;
+ }
+}
+```
+****
+
+
+### :pencil2:8.判断是否是对称二叉树(leetcode101)
+
+
+
+
+
+**1.两个子节点都是空,那说明他们是对称的返回true**
+
+**2.一个子节点为空,另一个子节点不为空,false**
+
+**3.两个子节点都不为空,但是他们不相等,false**
+
+**4.两个子节点不为空且相等,继续判断他们的左子树和右子树,把左子树的左子节点和右子树的右子节点进行比较,把左子树的右子节点和右子树的左子节点进行比较**
+
+```php
+ /**
+ * @param TreeNode $root
+ * @return Boolean
+ */
+ function isSymmetric($root) {
+ if(empty($root)) return true;
+ return $this->helper($root->left,$root->right);
+ }
+ function helper($l,$r){
+ if(!$l && !$r) return true;
+ if(!$l || !$r || $l->val != $r->val) return false;
+ return $this->helper($l->left ,$r->right) && $this->helper($l->right,$r->left);
+ }
+```
+****
+
+### :pencil2:9.反转二叉树(leetcode226)
+
+
+
+
+
+```php
+
+ /**
+ * @param TreeNode $root
+ * @return TreeNode
+ */
+ function invertTree($root) {
+ if(!$root) return null;
+ $list=[];
+ array_push($list,$root);
+ while(!empty($list)){
+ $node=array_shift($list);
+ $temp=$node->left;
+ $node->left=$node->right;
+ $node->right=$temp;
+ if($node->left) array_push($list,$node->left);
+ if($node->right) array_push($list,$node->right);
+ }
+ return $root;
+ }
+```
+**递归解**
+```php
+ /**
+ * @param TreeNode $root
+ * @return TreeNode
+ */
+ function invertTree($root) {
+ if(empty($root)){
+ return null;
+ }
+ $right=$this->invertTree($root->right);
+ $left=$this->invertTree($root->left);
+ $root->left=$right;
+ $root->right=$left;
+ return $root;
+ }
+```
+****
+
+
+### :pencil2:10.给定单链表(值有序)转化成平衡二叉查找树(leetcode109)
+
+
+
+
+
+**先将链表数据转换成有序数组,然后利用二分查找的特性,构建左右子树。**
+
+```php
+
+/**
+ * Definition for a singly-linked list.
+ * class ListNode {
+ * public $val = 0;
+ * public $next = null;
+ * function __construct($val) { $this->val = $val; }
+ * }
+ */
+/**
+ * Definition for a binary tree node.
+ * class TreeNode {
+ * public $val = null;
+ * public $left = null;
+ * public $right = null;
+ * function __construct($value) { $this->val = $value; }
+ * }
+ */
+class Solution {
+
+ /**
+ * @param ListNode $head
+ * @return TreeNode
+ */
+ function sortedListToBST($head) {
+ $data=[];
+ while($head){
+ array_push($data,$head->val);
+ $head=$head->next;
+ }
+ return $this->helper($data);
+ }
+
+ function helper($data)
+{
+ if(!$data) return ;
+ $middle=floor(count($data)/2);
+ $node=new TreeNode($data[$middle]);
+ $node->left=$this->helper(array_slice($data,0,$middle));
+ $node->right=$this->helper(array_slice($data,$middle+1));
+ return $node;
+ }
+}
+```
+****
+
+
+### :pencil2:11.强盗打劫版本3(leetcode337)
+
+
+
+
+
+**最后的目的算出最多能抢金额数而不触发报警器。除了根节点,每一个结点只有一个父节点,能直接相连的两个节点不能同时抢,比如图1,抢了根节点,直接相连的左右子结点就不能抢。所以要么抢根节点的左右子结点,要么根结点+根结点->left->right+根结点->right->right。**
+
+
+```php
+
+//递归
+/**
+ * @param TreeNode $root
+ * @return Integer
+ */
+ function rob($root) {
+ if($root==null){
+ return 0;
+ }
+ $res1=$root->val;
+ if($root->left !=null) {
+ $res1 +=$this->rob($root->left->left)+$this->rob($root->left->right);
+ }
+ if($root->right !=null){
+ $res1 +=$this->rob($root->right->left)+$this->rob($root->right->right);
+ }
+
+ $res2=$this->rob($root->left)+$this->rob($root->right);
+ return max($res1,$res2);
+
+ }
+```
+
+**上面那种大量的重复计算,改进一下。**
+
+ **如果结点不存在直接返回0,对左右结点分别递归,设置了4个变量,ll和lr分别表示左子结点的左右子结点的最大金额数,rl和rr分别表示右子结点的左右子结点的最大金额数。所以我们最后比较的还是两种情况,第一种就是当前结点+左右子结点的左右子结点的值(即这里定义的ll,lr,rl,rr).第二种是当前结点的左右子结点的值(也就是说我只抢当前结点的子结点,不抢当前结点和孙子结点),再通俗的说就是如果树的层数是3层,要么抢中间一层,要么抢上下两层,谁钱多抢谁。**
+
+ ```php
+/**
+ * @param TreeNode $root
+ * @return Integer
+ */
+ function rob($root) {
+ $l=0;$r=0;
+ return $this->countMax($root,$l,$r);
+ }
+ function countMax($root,&$l,&$r){
+ if($root==null) return 0;
+ $ll=0;$lr=0;$rl=0;$rr=0;
+ $l=$this->countMax($root->left,$ll,$lr);
+ $r=$this->countMax($root->right,$rl,$rr);
+ return max($root->val+$ll+$lr+$rl+$rr,$l+$r);
+ }
+```
+****
+
+### :pencil2:12.判断二叉树路径和是否存在(leetcode112)
+
+
+
+
+
+**只要使用深度优先算法思想遍历每一条完整的路径,如果是个空树直接false,如果结点没有左右子树(说明此时已然是叶子结点,判断值是否是给定值,这个条件正好是递归终止的条件),相等直接返回true,根据这个推导出递归公式。**
+
+```php
+/**
+ * @param TreeNode $root
+ * @param Integer $sum
+ * @return Boolean
+ */
+ function hasPathSum($root, $sum) {
+ if($root==null){
+ return false;
+ }
+ if($root->left ==null && $root->right==null && $root->val==$sum) return true;
+ return $this->hasPathSum($root->left,$sum-$root->val) || $this->hasPathSum($root->right,$sum-$root->val);
+ }
+```
+
+**改成迭代**
+
+```php
+
+/**
+ * @param TreeNode $root
+ * @param Integer $sum
+ * @return Boolean
+ */
+ function hasPathSum($root, $sum) {
+ if($root==null){
+ return false;
+ }
+ $res=[];
+ array_push($res,$root);
+ while(!empty($res)){
+ $node=array_shift($res);
+ if(!$node->left && !$node->right ){
+ if($node->val==$sum) return true;
+ }
+ if($node->left){
+ $node->left->val +=$node->val;
+ array_push($res,$node->left);
+ }
+ if($node->right){
+ $node->right->val +=$node->val;
+ array_push($res,$node->right);
+ }
+ }
+ return false;
+ }
+```
+****
+
+
+### :pencil2:13.判断是否是二叉查找树(leetcode98)
+
+
+
+
+
+**思路有两种,二叉查找树的特点就是左子树上的结点都小于根结点,右子树上的结点都大于根节点。所以有两个方向,可以分别递归的判断左子树,右子树。或者拿左子树上的最大值,右子树上的最小值分别对应根结点进行判断。**
+
+```php
+
+/**
+ * Definition for a binary tree node.
+ * class TreeNode {
+ * public $val = null;
+ * public $left = null;
+ * public $right = null;
+ * function __construct($value) { $this->val = $value; }
+ * }
+ */
+class Solution {
+
+ /**
+ * @param TreeNode $root
+ * @return Boolean
+ */
+ function isValidBST($root) {
+ return $this->helper($root,null,null);
+ }
+ function helper($root,$lower,$upper){
+ if($root==null) return true;
+ $res=$root->val;
+ if($lower !==null && $res<=$lower) return false;
+ if($upper !==null && $res>=$upper) return false;
+ if(!$this->helper($root->left,$lower,$res)) return false;
+ if(!$this->helper($root->right,$res,$upper)) return false;
+ return true;
+ }
+}
+```
+****
+
+
+### :pencil2:14.找出二叉树最后一层最左边的值(leetcode513)
+
+
+
+
+
+**思路有两种,二叉查找树的特点就是左子树上的结点都小于根结点,右子树上的结点都大于根节点。所以有两个方向,可以分别递归的判断左子树,右子树。或者拿左子树上的最大值,右子树上的最小值分别对应根结点进行判断。**
+
+```php
+ /**
+ * @param TreeNode $root
+ * @return Integer
+ */
+ function findBottomLeftValue($root) {
+ $data=[];
+ array_push($data,$root);
+ while(!empty($data)){
+ $node = array_shift($data);
+ if($node->right) array_push($data,$node->right);
+ if($node->left) array_push($data,$node->left);
+ }
+ return $node->val;
+ }
+```
+****
+
+### 联系
+
+
+
+
+
+
+
+
+
diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204/\344\272\214\345\217\211\346\240\221\345\237\272\346\234\254\346\223\215\344\275\234.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\344\272\214\345\217\211\346\240\221\345\237\272\346\234\254\346\223\215\344\275\234.md"
new file mode 100644
index 0000000..766ea8e
--- /dev/null
+++ "b/\346\225\260\346\215\256\347\273\223\346\236\204/\344\272\214\345\217\211\346\240\221\345\237\272\346\234\254\346\223\215\344\275\234.md"
@@ -0,0 +1,228 @@
+## :数据结构线性表之二叉树1
+#### php-leetcode之路 [Leetcode-php](https://github.com/wuqinqiang/leetcode-php)
+
+
+
+
+
+### :pencil2:一.二叉树的定义
+**既然都叫二叉树了,那么二叉树的特点是每个至多只有两棵子树。换句话来说,每个结点的度不超过两个,并且二叉树的子树还存在左右之分,它的次序不能任意颠倒。**
+### :pencil2:二叉树的表现形态
+
+
+
+
+
+**1.满二叉树:棵深度为k有且有2^k-1结点的树称之为满二叉树,比如图中的a(深度为4,拥有15个结点)。这里我们就可以得到二叉树的一个特性:在二叉树的第i层中至多有2^i-1个结点(i>=1)。当然了他不止这一个特性。。。**
+
+**2.完全二叉树:深度为k,有n个结点的二叉树,当且仅当其每一个结点都与深度为k的满二叉树中编号从1至n一一对应时,才是完全二叉树。这里图c不是完全二叉树,假设根节点A是1,那么图c在n等于12这个点上就没有对应。所以不是完全二叉树。所以完全二叉树的特点:(1)叶子结点只可能在层次最大的两层上出现。(2)对任意一个结点,若其右子树的最大层次是l,那么其左子树的最大层数必须是l或者l+1。**
+
+**三·二叉树的存储树这种结构的存储不像线性结构那样简单,并不能直接用前后关系来描述。一个树有有很多的子树,一个结点不止一个后继。下面用图来说明用数组存储和链表存储。**
+
+### :pencil2:三.二叉树的存储
+
+
+
+
+
+**按照顺序存储的结构。用一组连续的存储单元至上而下,至左到右存储,图中的0表示不存在该结点,每一个不存在的点我们都需要浪费一个空间。所以在最坏的情况下,一个深度为k且只有k个结点的单支树(树中不存在度为2的结点)却需要长度为2^k-1的一维数组。所以这种顺序存储适合完全二叉树。**
+
+
+
+
+
+**链式存储的结构是由一个数据元素和分别指向其左右子树的两个分支构成。包含了三个域:数据域,左右指针域。链表的头指向树的根节点。**
+
+### :pencil2:四·二叉树的前中后序遍历
+```php
+
+//二叉树
+class Tree{
+ public $res='';
+ public $left=null;
+ public $right=null;
+ public function __construct($res)
+{
+ $this->res=$res;
+ }
+
+}
+//前序遍历
+ function front($tree){
+ if($tree==null){
+ return ;
+ }
+ echo $tree->res.'
+
+
+```php
+
+class Tree{
+ public $res='';
+ public $left=null;
+ public $right=null;
+ public function __construct($res)
+{
+ $this->res=$res;
+ }
+
+}
+
+class BinarySortTree
+{
+ public $tree;
+
+ public function getTree()
+{
+ return $this->tree;
+ }
+
+ //插入
+ public function insertTree($data)
+ {
+ if(!$this->tree){
+ $this->tree=new Tree($data);
+ return ;
+ }
+ $p=$this->tree;
+ while($p){
+ if($data<$p->res){ //如果插入结点当前结点
+ if(!$p->left){ //并且不存在左子结点
+ $p->left=new Tree($data);
+ return ;
+ }
+ $p=$p->left;
+ }elseif ($data>$p->res){
+ if(!$p->right){
+ $p->right=new Tree($data);
+ return ;
+ }
+ $p=$p->right;
+ }else{
+ return ;
+ }
+ }
+ }
+
+
+
+ //删除
+ public function deleteTree($data)
+{
+ if (!$this->tree) {
+ return;
+ }
+ $p = $this->tree;
+ $fatherP = null;
+ while ($p && $p->res !== $data) {
+ $fatherP=$p; //结点的父结点
+ if ($data > $p->res) {
+ $p = $p->right;
+ }else{
+ $p=$p->left;
+ }
+ }
+
+ //如果二叉树不存在
+ if($p==null){
+ var_dump('当前树中没有此结点');return;
+ }
+
+ //待删除待有两个子结点
+ if($p->left && $p->right){
+ $minR=$p->right;
+ $minRR=$p;// 最小结点的父结点
+ //查找右子树的最小结点
+ while($minR->left){
+ $minRR=$minR;
+ $minR=$minR->left;
+ }
+ $p->res=$minR->res;//把右子树上最小结点的值赋值给待删除结点
+ $p=$minR;
+ $fatherP=$minRR;
+
+ }
+ $child=null;
+ if($p->left){
+ $child=$p->left;
+ }elseif($p->right){
+ $child=$p->right;
+ }else{
+ $child=null;
+ }
+
+ if(!$fatherP){ //待删除结点是根结点
+ $this->tree=$child;
+ }elseif ($fatherP->left==$p){ //待删除结点只有一个左结点,把待删除结点的父结点的left指向待删除结点的子节点
+ $fatherP->left=$child;
+ }else{ //待删除结点只有一个右结点,把待删除结点的父结点的right指向待删除结点的子节点
+ $fatherP->right=$child;
+ }
+
+ }
+}
+
+$sortTree=new BinarySortTree();
+$sortTree->insertTree(9);
+$sortTree->insertTree(8);
+$sortTree->insertTree(10);
+$sortTree->insertTree(5);
+$sortTree->insertTree(6);
+$sortTree->insertTree(4);
+//$sortTree->search(1);
+//$sortTree->deleteTree(8);
+//var_dump($sortTree->getTree());
+```
+
+### 联系
+
+
+
+
+
+
+
diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204/\345\240\206\346\240\210.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\345\240\206\346\240\210.md"
new file mode 100644
index 0000000..42ed110
--- /dev/null
+++ "b/\346\225\260\346\215\256\347\273\223\346\236\204/\345\240\206\346\240\210.md"
@@ -0,0 +1,115 @@
+## :数据结构线性表之堆栈
+
+#### php-leetcode之路 [Leetcode-php](https://github.com/wuqinqiang/leetcode-php)
+
+
+
+
+
+### :pencil2:一.栈的特点
+**栈是仅限在栈顶进行插入和删除的线性表.对栈来说,表尾端称之为栈顶,表头端称之为栈底.假设栈S=(a1,a2,a3,a4,a5....an),那么a1为栈的栈底,an为栈顶元素.对栈的操作是按照后进先出的原则,因此栈又称之为(LIFO)数据结构.**
+
+### :pencil2:二.栈的表示和实现
+**和线性表类似,栈也有两种存储的方式.顺序栈,即栈的存储是利用一组连续的存储单元依次存放自栈底到栈顶的元素.同时还要定义指针top指向栈顶元素在顺序栈中的位置.通常的做法是top=0表示空栈.一般在使用栈的过程中难以预料所需要的空间大小.所以预先分配大小的容量,当栈的使用空间不够时,再进行动态的扩容.top作为栈顶的指针,初始的时候指向栈底,当有元素入栈时,top自增,当从栈中删除一个元素时,top--,然后删除元素,所以top始终指向栈顶的下一个空位置.**
+ ```php
+LOC(ai+1)=LOC(ai)+L //申明 这里的i,i+1是下标
+```
+**线性结构的顺序表示以元素在计算机中的物理位置相邻来表示逻辑位置相邻,相邻两个元素之间的位置以物理位置的角度分析就是相差一位,只要确定了存储线性表的起始位,那么我们就能任意存储线性表中元素,下图具体表示**
+
+
+
+
+
+### :pencil2:三.栈的使用场景
+**栈的使用场景很多,例如idea的撤销回退功能,浏览器前进和后退功能.数制的转换,表达式求值,八皇后.......**
+### :pencil2:四.用栈实现代码
+```php
+
+/**
+ * 使用栈实现十进制转8精制,其他转换类似
+ */
+function tenChageEight($num)
+{
+ $data=[];
+ while($num) {
+ $val = $num % 8;
+ $num = intval(floor($num / 8));
+ array_unshift($data, $val);
+ }
+ $data2=[];
+ while(!empty($data)){
+ array_push($data2,array_shift($data));
+ }
+ return implode($data2);
+}
+var_dump(tenChageEight(1348));
+```
+****
+```php
+
+class Stack
+{
+ private $stack=[];
+ private $size=0;
+ public function __construct($size)
+{
+ $this->size=$size;
+ }
+
+ /**
+ *推入栈顶
+ */
+ public function push($value)
+{
+ if(count($this->stack)>=$this->size){
+ return false;
+ }
+ array_unshift($this->stack,$value);
+
+ }
+ /**
+ *出栈
+ */
+ public function pop()
+{
+ if($this->size==0){
+ return false;
+ }
+ array_shift($this->stack);
+ }
+ /**
+ *获取栈顶元素
+ */
+ public function top()
+{
+ if($this->size==0){
+ return false;
+ }
+ return current($this->stack);
+ }
+
+ public function data()
+{
+ return $this->stack;
+ }
+
+}
+
+$stack=new Stack(10);
+$stack->push(2);
+$stack->push(10);
+$stack->push(8);
+$stack->pop();
+var_dump($stack->top());
+var_dump($stack->data());
+```
+### 联系
+
+
+
+
+
+
+
+
+
diff --git "a/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\243\345\210\227\350\241\250.md" "b/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\243\345\210\227\350\241\250.md"
new file mode 100644
index 0000000..5174d2b
--- /dev/null
+++ "b/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\243\345\210\227\350\241\250.md"
@@ -0,0 +1,114 @@
+## 数据结构之散列表
+#### php-leetcode之路 [Leetcode-php](https://github.com/wuqinqiang/leetcode-php)
+
+
+
+
+### :pencil2:一.散列表的介绍
+**散列表也叫哈希表,在前面的各种数据结构中(数组,链表,树等),记录在结构中的相对位置是随机的,和记录的关键字不存在确定的关系。在顺序结构中,顺序查找,比较的结果是'='或者'!=',在二分查找,二叉排序树比较的关系是'>','=‘,’<‘,查找的效率依赖于查找过程中比较的次数。**
+**什么样的方法能使得理想情况下不需要进行任何比较,通过一次存储就能得到所查的记录。答案就是记录存储位置和关键字之间的对应关系。**
+
+
+
+
+
+**我们记录了存储位置和关键字的对应关系h,因此在查找的过程中,只要根据对应关系h找到给定值k的像h(k),如果结构中存在关建字和k相等的记录,那么肯定是存储在h(k)的位置上,就像上图k1..k5的一样,我们称这个对应关系h为散列函数或者是哈希函数。**
+****
+**从上面的例子可以看出:**
+
+**1.散列函数是一个映像,因此设定散列函数可以非常灵活。**
+
+**2.从图中可以看出,k2 != k5 ,但是h(k2)===h(k5),这就是散列冲突。**
+
+### :pencil2:二.散列冲突
+
+
+
+
+
+**我们看当前例子,关键字 John Smith !=Sandra Dee,但是通过散列函数之后h(John Smith)===h(Sandra Dee),他们的位置都在散列表01这个位置,但是这个位置只能存储一个记录,那多出来的记录存放在哪里?**
+
+**正因为散列函数是一个映像,我们在构造散列函数的时候,只能去分析关键字的特性,选择一个恰当的散列函数来避免冲突的发生。一般情况下,冲突只能尽可能的减少,而不能完全避免。**
+
+### :pencil2:三.散列函数的构造
+**先来看看什么是好的散列函数。如果对于关键字集合中任意一个,经过散列函数映射到地址集合中任何一个位置的概率都是相等的,就可以称为均匀散列函数。**
+
+**1.直接定址法**
+
+**取关键字或者关键字的某个线性函数值为哈希地址。**
+
+**h(key)=key或者h(key)=a*key+b a,b为常量。比如说做一个地区年龄段的人数统计,我们就可以把年龄1-100作为关键字,现在你要查38岁的人口数时只需要h(38)=???。**
+****
+
+**2.数字分析法**
+
+**要通过具体的数字分析,找出关键字集合的特点和关系,通过这组关系构造一个散列函数。**
+****
+**3.平方取中法**
+
+**取关键字平方后的中间几位为哈希地址,因为一般情况下,选定哈希函数时并不一定知道关键字的全部情况,取哪几位也不一定合适,而一个平方后的中间几位数和数的每一位都有关,由此使随机分布的关键字得到的哈希地址也是随机的。**
+****
+**4.折叠法**
+
+**将关键字分割成位数相同的几部分,然后取这几部分的叠加和,也就是舍去它的进位,作为他的哈希地址。**
+****
+**5.除留余数法**
+**取关键字被某个不大于散列表表长m的数(暂且称之为p)除后所得余数为哈希地址。**
+```php
+h(key)=key MOD p, p<=m
+```
+**以上都偏于理论,具体的使用需要视情况而定,通常,考虑的因素有以下几点**
+
+**1.计算散列函数所需的时间**
+
+**2.关键字的长度**
+
+**3.哈希表的大小**
+
+**4.关键字的分布情况**
+
+ **5.记录查找的频率**
+
+ ### :pencil2:四.处理冲突
+ **之前说过,散列函数可以减少冲突,但是不能避免,这时候我们就需要处理冲突。**
+
+ **1.开放寻址法**
+
+ **简单的来说,下图中当前哈希表的长度是8,现在已填入关键字1,9,3,4,5的记录,假如当前我们需要再填入关键字10这条记录。通过散列函数得出h(3)===h(10),但是此时哈希地址2已被3占领。现在我们咋么处理?**
+
+ **线性探测再散列得到下一个地址3,发现3也被占领,再求下一个地址4,还被占领,直到5这个位置时,处理冲突过程结束,把当前h(10)记录在地址为5的位置即可。也可以使用二次探测再散列或者伪随机再散列都是属于开放寻址法。**
+
+
+
+
+
+****
+
+**2.再散列法**
+
+**即在同义词产生地址冲突时计算另一个散列函数的地址,直到冲突不再发生,这种方法毋庸置疑,增加了计算的时间。**
+****
+**3.链地址法**
+
+**将所有关键字为同义词的记录存储在同一个线性链表中。初始状态是一个空指针。凡是通过关键字计算出地址一样的记录都插入到当前位置的链表中,在链表中插入的位置可以是表头,表尾,或者表中,以保持同义词在同一个线性表中按关键字有序。**
+
+
+
+
+
+ ****
+
+ **4.建立一个公共溢出区**
+
+ **假设散列函数的值域为[0,m-1],则设置向量HashTable[0,m-1]为基本表,每一个分量存储一个记录,另外设置向量OverTable[0,v]为溢出表。所有关键字和基本表中关键字为同义词的记录,不管它们由哈希函数得到的哈希地址是什么,一旦发生冲突,都填入溢出表中。**
+****
+ ### 联系
+
+
+
+
+
+
+
+
+
diff --git "a/\347\210\254\350\231\253\350\204\232\346\234\254/README.md" "b/\347\210\254\350\231\253\350\204\232\346\234\254/README.md"
deleted file mode 100644
index 89b10b1..0000000
--- "a/\347\210\254\350\231\253\350\204\232\346\234\254/README.md"
+++ /dev/null
@@ -1,20 +0,0 @@
-## 爬取招聘网数据
-
-在我们找工作的时候,有些时候,需要到各个网站去找岗位,程序员当然是使用自己的方式来快速的寻找自己的岗位了。于是就想到使用python来抓取最新的符合自己薪资条件的岗位,以及对应的岗位需要的JD,然后如果匹配的话,那么自己可以一键投递。
-
-
-
-- [x] 模拟登录
-
-- [x] 抓取拉钩最新数据
-- [ ] 爬取51job网站数据
-- [ ] 爬取智联数据
-- [ ] 爬取boss数据
-- [ ] 一键投递
-- [ ] 岗位分析【分析出现最多的关键词】
-- [ ] web可视化
-
-
-
-
-
diff --git "a/\347\210\254\350\231\253\350\204\232\346\234\254/__pycache__/lagou.cpython-36.pyc" "b/\347\210\254\350\231\253\350\204\232\346\234\254/__pycache__/lagou.cpython-36.pyc"
deleted file mode 100644
index a44a28e..0000000
Binary files "a/\347\210\254\350\231\253\350\204\232\346\234\254/__pycache__/lagou.cpython-36.pyc" and /dev/null differ
diff --git "a/\347\210\254\350\231\253\350\204\232\346\234\254/boss.py" "b/\347\210\254\350\231\253\350\204\232\346\234\254/boss.py"
deleted file mode 100644
index 374811a..0000000
--- "a/\347\210\254\350\231\253\350\204\232\346\234\254/boss.py"
+++ /dev/null
@@ -1,60 +0,0 @@
-import requests
-from pyquery import PyQuery as pq
-headers = {
- "x-requested-with":"XMLHttpRequest",
- "user-agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1"
-}
-
-session =requests.session()
-
-def fetch_list(url):
- response = session.get(url,headers=headers)
- if response.status_code != 200:
- print("请求失败"+response.text)
- exit(-1)
- data = response.json()
- html = (data['html'])
- q = pq(html)
- temp = {}
- result = []
- for x in q(".item"):
- temp['detail_url'] = 'https://www.zhipin.com/'+q(x).find('a').attr('href')
- temp['title'] = q(x).find("h4").text()
- temp['salary'] = q(x).find('.salary').text()
- temp['company'] = q(x).find('.name').text()
- temp['city'] = q(x).find('.msg em').eq(0).text()
- temp['workyear'] = q(x).find('.msg em').eq(1).text()
- temp['collect'] = q(x).find('.msg em').eq(2).text()
- result.append(temp)
- return result
-
-
-def fetch_boss_detail(url):
- response = requests.get(url,headers=headers)
- if response.status_code != 200:
- print(url+"抓取失败")
- else:
- result = {}
- body = response.text
- q = pq(body)
-
- result['salary'] = q(".salary").text()
- all = q('.job-banner p').text().split(" ")
- result['city'] = all[0]
- result['workyear'] = all[1]
- result['educational'] =all[2]
- result['name'] = (q('.job-banner .name').text().split(" "))[0]
- result['create_time'] = q('.job-tags .time').text()
- result['body'] = q('.text').text()
- result['location-address'] = q('.location-address').text()
- if "工作职责" in result['body']:
- print(1)
- return result
- pass
-
-
-#上海PHP
-url = 'https://www.zhipin.com/mobile/jobs.json?page=3&city=101020100&query=PHP'
-detail_url = 'https://www.zhipin.com/job_detail/3b31d8738e6d77421Xd_3tu4F1Q~.html'
-data = fetch_boss_detail(detail_url)
-print(data)
diff --git "a/\347\210\254\350\231\253\350\204\232\346\234\254/lagou.py" "b/\347\210\254\350\231\253\350\204\232\346\234\254/lagou.py"
deleted file mode 100644
index a29dea9..0000000
--- "a/\347\210\254\350\231\253\350\204\232\346\234\254/lagou.py"
+++ /dev/null
@@ -1,125 +0,0 @@
-import requests
-import json
-import redis
-from pyquery import PyQuery as pq
-import hashlib
-import re
-
-#请求对象
-session = requests.session()
-
-#请求头信息
-HEADERS = {
- 'Referer': 'https://passport.lagou.com/login/login.html',
- 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:51.0) Gecko/20100101 Firefox/51.0',
-}
-headers = {}
-cookies = {}
-
-def get_password(passwd):
- '''这里对密码进行了md5双重加密 veennike 这个值是在main.html_aio_f95e644.js文件找到的 '''
- passwd = hashlib.md5(passwd.encode('utf-8')).hexdigest()
- passwd = 'veenike' + passwd + 'veenike'
- passwd = hashlib.md5(passwd.encode('utf-8')).hexdigest()
- return passwd
-
-def get_token():
- Forge_Token = ""
- Forge_Code = ""
- login_page = 'https://passport.lagou.com/login/login.html'
- data = session.get(login_page, headers=HEADERS)
- match_obj = re.match(r'.*X_Anti_Forge_Token = \'(.*?)\';.*X_Anti_Forge_Code = \'(\d+?)\'', data.text, re.DOTALL)
- if match_obj:
- Forge_Token = match_obj.group(1)
- Forge_Code = match_obj.group(2)
- return Forge_Token, Forge_Code
-
-def login(username, passwd):
- X_Anti_Forge_Token, X_Anti_Forge_Code = get_token()
- login_headers = HEADERS.copy()
- login_headers.update({'X-Requested-With': 'XMLHttpRequest', 'X-Anit-Forge-Token': X_Anti_Forge_Token, 'X-Anit-Forge-Code': X_Anti_Forge_Code})
- postData = {
- 'username': username,
- 'password': get_password(passwd),
- 'request_form_verifyCode': '',
- 'submit': '',
- }
- response = session.post('https://passport.lagou.com/login/login.json', data=postData, headers=login_headers)
- json_data = response.json()
- if(json_data['state'] != 1):
- print("登录失败,退出")
- exit(-1)
-
-def get_cookies():
- session.get("https://passport.lagou.com/grantServiceTicket/grant.html")
- return requests.utils.dict_from_cookiejar(session.cookies)
-
-"""
-获取职位列表
-"""
-def fetch(url,headers,cookies):
- #用户登录后的cookie
- try:
- res = session.get(url,headers=headers,cookies=cookies)
- except Exception as e:
- print(e)
- return False
-
- if res.status_code != 200:
- print("接口请求出错"+str(res.text))
- return False
-
- jsons = (res.json())
- try:
- data = (jsons['content']['data']['page']['result'])
- except Exception as e:
- print(e)
- return False
- return data
-
-"""
-插入数据到redis
-"""
-def insert(data):
- pool= redis.ConnectionPool(host='localhost',port=6379,decode_responses=True)
- r=redis.Redis(connection_pool=pool)
- if data is not False:
- for t in data:
- #公司id
- id = t['companyId']
- key = "companyId:"+str(id)
- #职位id
- positionId = t['positionId']
- #公司的id加入集合中去重
- resA = r.sadd("company",id)
-
- if resA == 1:
- res = fetch_detail(positionId,headers,cookies)
- r.hmset("postion:"+str(positionId),res)
- print(str(positionId)+"已经写入redis中")
- r.hmset(key,t)
-
-# 获取职位详情
-def fetch_detail(id,headers,cookies):
- detailUrl = 'http://m.lagou.com/jobs/'+str(id)+".html"
- res = session.get(detailUrl,headers=headers,cookies=cookies)
- if res.status_code != 200:
- print("请求出错"+str(res.text))
- return False
- html = res.text
- q = pq(html)
- d = {}
- d['title'] = q(".postitle h2").text()
- d['salary'] = q(".salary").text()
- d['workaddress'] = q(".workaddress").text()
- d['workyear'] = q('.workyear').text()
- d['education'] = q('.education').text()
- d['company'] = q('.dleft h2').text()
- d['positiondesc'] = q('.positiondesc').text()
- return d
- # print(q(".content").text())
-
-
-
-
-
diff --git "a/\347\210\254\350\231\253\350\204\232\346\234\254/lagoutest.py" "b/\347\210\254\350\231\253\350\204\232\346\234\254/lagoutest.py"
deleted file mode 100644
index 1e159af..0000000
--- "a/\347\210\254\350\231\253\350\204\232\346\234\254/lagoutest.py"
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/usr/bin/env python
-# -*- coding:utf-8 -*-
-from lagou import login,get_cookies,fetch_detail,fetch
-
-if __name__ == "__main__":
- username = '13856077103'
- passwd = '4591341123'
- login(username, passwd)
- cookies = get_cookies()
- print(cookies)
- try:
- # 同步session
- url = 'https://m.lagou.com/listmore.json?pageNo=1&pageSize=10'
- headers = {}
- print("数据如下")
- data = fetch(url,headers=headers,cookies=cookies)
- print(data)
- except Exception as e:
- print(e)
diff --git "a/\347\210\254\350\231\253\350\204\232\346\234\254/stop.txt" "b/\347\210\254\350\231\253\350\204\232\346\234\254/stop.txt"
deleted file mode 100644
index 912206e..0000000
--- "a/\347\210\254\350\231\253\350\204\232\346\234\254/stop.txt"
+++ /dev/null
@@ -1,47 +0,0 @@
-岗位
-职责
-职位
-描述
-公司
-经验
-相关
-熟悉
-掌握
-熟练掌握
-岗位职责
-以上学历
-学历
-开发
-PHP
-设计
-技术
-数据库
-优化
-优先
-系统
-工作
-负责
-精通
-能力
-框架
-代码
-团队
-编写
-编程
-文档
-维护
-良好
-熟练
-网站
-项目
-以上
-产品
-需求
-参与
-使用
-要求
-具备
-互联网
-了解
-任职
-分析
diff --git "a/\347\210\254\350\231\253\350\204\232\346\234\254/word.py" "b/\347\210\254\350\231\253\350\204\232\346\234\254/word.py"
deleted file mode 100644
index b4fdd6a..0000000
--- "a/\347\210\254\350\231\253\350\204\232\346\234\254/word.py"
+++ /dev/null
@@ -1,44 +0,0 @@
-
-import matplotlib.pyplot as plt #数学绘图库
-import jieba #分词库
-from wordcloud import WordCloud #词云库
-import jieba.analyse
-
-def stopwordslist(filepath):
- stopwords = [line.strip() for line in open(filepath, 'r', encoding='utf-8').readlines()]
- return stopwords
-
-# 对句子去除停用词
-def movestopwords(sentence):
- stopwords = stopwordslist('stop.txt') # 这里加载停用词的路径
- outstr = ''
- for word in sentence:
- if word not in stopwords:
- if word != '\t'and'\n':
- outstr += word
- # outstr += " "
- return outstr
-
-
-#1、读入txt文本数据
-text = open(r'lz.txt',"r").read()
-text = movestopwords(text)
-#2、结巴分词,默认精确模式。可以添加自定义词典userdict.txt,然后jieba.load_userdict(file_name) ,file_name为文件类对象或自定义词典的路径
-# 自定义词典格式和默认词库dict.txt一样,一个词占一行:每一行分三部分:词语、词频(可省略)、词性(可省略),用空格隔开,顺序不可颠倒
-stopWord = './stop.txt'
-jieba.analyse.set_stop_words(stopWord)
-cut_text= jieba.cut(text)
-result= "/".join(cut_text)#必须给个符号分隔开分词结果来形成字符串,否则不能绘制词云
-#print(result)
-
-#3、生成词云图,这里需要注意的是WordCloud默认不支持中文,所以这里需已下载好的中文字库
-#无自定义背景图:需要指定生成词云图的像素大小,默认背景颜色为黑色,统一文字颜色:mode='RGBA'和colormap='pink'
-
-
-jieba.analyse.set_stop_words(stopWord)
-data = jieba.analyse.extract_tags(result.replace('/',''), topK=100, withWeight=False, allowPOS=())
-print(",".join(data))
-wc = WordCloud(font_path=r"simsun.ttc",background_color='white',width=800,height=600,max_font_size=50,
- max_words=1000)#,min_font_size=10)#,mode='RGBA',colormap='pink')
-wc.generate("/".join(data))
-wc.to_file(r"wordcloud.png") #按照设置的像素宽高度保存绘制好的词云图,比下面程序显示更清晰
diff --git "a/\347\256\227\346\263\225/Readme.md" "b/\347\256\227\346\263\225/Readme.md"
index 18952d5..73cdf7e 100644
--- "a/\347\256\227\346\263\225/Readme.md"
+++ "b/\347\256\227\346\263\225/Readme.md"
@@ -131,25 +131,33 @@ function SelectSort(array $container)
- [快速排序](https://github.com/xianyunyh/arithmetic-php/blob/master/package/Sort/QuickSort.php)
+> 快速排序(Quicksort)是对冒泡排序的一种改进。他的基本思想是:通过一趟排序将待排记录分割成独立的两部分,其中一部分的关键字均比另一部分记录的关键字小,则可分别对这两部分记录继续进行快速排序,整个排序过程可以递归进行,以达到整个序列有序的目的。
+
```php
-function QuickSort(array $container)
+function QuickSort(&$arr,$low,$high)
{
- $count = count($container);
- if ($count <= 1) { // 基线条件为空或者只包含一个元素,只需要原样返回数组
- return $container;
+ if ($low < $high) {
+ $middle = getMiddle($arr,$low,$high);
+ QuickSort($arr,$low,$middle-1);
+ QuickSort($arr,$middle+1,$high);
}
- $pivot = $container[0]; // 基准值 pivot
- $left = $right = [];
- for ($i = 1; $i < $count; $i++) {
- if ($container[$i] < $pivot) {
- $left[] = $container[$i];
- } else {
- $right[] = $container[$i];
+}
+
+function getMiddle (&$arr,$low,$high)
+{
+ $temp = $arr[$low];
+ while ($low < $high) {
+ while($low < $high && $temp <= $arr[$high]) {
+ $high --;
+ }
+ $arr[$low] = $arr[$high];
+ while ($low < $high && $temp >= $arr[$low]) {
+ $low ++;
}
+ $arr[$high] = $arr[$low];
}
- $left = QuickSort($left);
- $right = QuickSort($right);
- return array_merge($left, [$container[0]], $right);
+ $arr[$low] = $temp;
+ return $low;
}
```
diff --git "a/\347\256\227\346\263\225/\344\272\214\345\210\206\346\237\245\346\211\276.md" "b/\347\256\227\346\263\225/\344\272\214\345\210\206\346\237\245\346\211\276.md"
new file mode 100644
index 0000000..251be5d
--- /dev/null
+++ "b/\347\256\227\346\263\225/\344\272\214\345\210\206\346\237\245\346\211\276.md"
@@ -0,0 +1,156 @@
+## :算法之排序二分查找
+### php-leetcode之路 [Leetcode-php](https://github.com/wuqinqiang/leetcode-php)
+
+### :pencil2:二分查找
+
+
+
+
+
+
+**二分查找是一种常见的查找方式。在生活中有一个猜大小的例子。比如给定一个0-100的范围,让你猜出事先我所设置的数字,每次把数字的范围缩小到一半。第一次猜50,大了还是小了,继续缩小数字的范围。但是有一个前提,我们得保证我们查找的是一个有序的范围。**
+
+****
+
+### :pencil2:查找思想
+
+**二分查找针对的是一个有序的集合。它的查找算法有点类型分治的思想,就像上面所说的。每次我通过中间的数和指定的数进行比较,判断大小,缩小猜测的区间,最多到区间等于0的时候,结果也就是最终指定的答案。二分查找是一种高效的查找算法,它的时间复杂度是O(logn).**
+
+****
+
+### :pencil2:php实现二分查找
+
+**1.我们先实现一个最基础的,在一个有序数组中(数组没有重复的值)查找给定值。(迭代)**
+
+```php
+function binarySerach($data,$res)
+{
+ $l=0;
+ $r=count($data)-1;
+ while($l<=$r){
+ // $middle=floor(($l+$r)/2);
+ // $middle=$l+floor(($r-$l)/2);
+ $middle=$l+(($r-$l)>>1); //使用位运算查找更高效
+ if($data[$middle]==$res) return $middle;
+ elseif ($data[$middle]>$res) $r=$middle-1;
+ else $l=$middle+1;
+ }
+ return -1;
+}
+$data=[2,5,6,7,12,34];
+$res=12;
+var_dump(binarySerach($data,$res));
+```
+****
+
+**使用递归实现刚才的操作。**
+
+```php
+function binarySerach($data,$res){
+ return recursion($data,0,count($data)-1,$res);
+}
+
+function recursion($data,$l,$r,$res)
+{
+ if($l>$r){
+ return -1;
+ }
+ $middle=$l+(($r-$l)>>1);
+ if($data[$middle]==$res) return $middle;
+ elseif ($data[$middle]>$res) return recursion($data,$l,$middle-1,$res);
+ else return recursion($data,$middle+1,$r,$res);
+}
+$data=[2,5,6,7,12,34];
+$res=12;
+var_dump(binarySerach($data,$res));
+```
+****
+
+
+### :pencil2:二分查找的变形问题
+
+**上面的那个注释是在数字没有重复的情况下,现在我们来实现数组中有重复值的情况下,如何查找出第一个等于给定值的元素。其实就是在做等于判断的时候如果索引是0那么是第一个等于给定值的数,或者当前等于给定值的上一个索引值不等于给定值。**
+
+```php
+
+function binarySerach($data,$res){
+ $l=0;
+ $r=count($data);
+ while($l<=$r){
+ $middle=$l+(($r-$l)>>1);
+ if($data[$middle]>$res) $r=$middle-1;
+ elseif($data[$middle]<$res) $l=$middle+1;
+ else{
+ if($middle==0 || $data[$middle-1] !==$res) return $middle;
+ else $r=$middle-1;
+ }
+ }
+}
+$data=[2,5,6,7,8,8,10];
+$res=8;
+var_dump(binarySerach($data,$res));
+```
+****
+**查找第一个大于等于给定值的元素。**
+```php
+
+//查找第一个大于等于给定值的数
+function binarySerach($data,$res)
+{
+ $l=0;
+ $r=count($data)-1;
+ while($l<=$r){
+ $middle=$l+(($r-$l)>>1);
+ if($data[$middle]<$res) $l=$middle+1;
+ else{
+ if($middle==0 || $data[$middle-1]<$res ) return $middle;
+ else $r=$middle-1;
+ }
+ }
+
+}
+$data=[2,5,6,7,8,8,10];
+$res=9;
+var_dump(binarySerach($data,$res));
+```
+****
+
+**针对数组是一个循环有序的数组Leetcode35题**
+```php
+
+function binarySerach($data,$res)
+{
+ $l=0;
+ $r=count($data)-1;
+ while($l<=$r){
+ $middle=$l+(($r-$l)>>1);
+ if($data[$middle]==$res) return $middle;
+ elseif ($data[$middle]>$data[$r]){
+ if($data[$l]<=$res && $data[$middle]>$res) $r=$middle-1;
+ else $l=$middle+1;
+ }else{
+ if($data[$middle]<=$res && $data[$r] >$res) $r=$middle-1;
+ else $l=$middle+1;
+ }
+ }
+}
+$data=[5,6,7,8,1,2,3,4];
+$res=7;
+var_dump(binarySerach($data,$res));
+```
+
+**当然二分查找还有很多的应用场景,我就总结到这了。**
+
+
+
+
+### 联系
+
+
+
+
+
+
+
+
+
diff --git "a/\347\256\227\346\263\225/\345\212\250\346\200\201\350\247\204\345\210\222.md" "b/\347\256\227\346\263\225/\345\212\250\346\200\201\350\247\204\345\210\222.md"
new file mode 100644
index 0000000..4443d82
--- /dev/null
+++ "b/\347\256\227\346\263\225/\345\212\250\346\200\201\350\247\204\345\210\222.md"
@@ -0,0 +1,263 @@
+## :算法算法之动态规划
+
+#### php-leetcode之路 [Leetcode-php](https://github.com/wuqinqiang/leetcode-php)
+
+***动态规划需要花很长时间去练习巩固的。其实在理解动态规划之前正确的顺序应该是先写递归,因为很多动态规划的转移方程都是通过递归加记忆法从而推导出公式,但是我随性,所以就不按套路出牌了。接下来就是简单介绍一下动态规划以及应用场景,最后把之前刷过的动态规划的题目全放出来,方便一次性查看。***
+****
+
+### :pencil2:动态规划的介绍
+
+***动态规划背后的思想简单概括就是,若要解一个指定的问题,我们需要解它的不同部分问题(子问题),再合并子问题求出最终想要的解。***
+
+***在现实生活中,有一类活动的过程,由于它的特殊性,可将过程分成若干个互相联系的阶段,在它的每一阶段都需要作出决策,从而使整个过程达到最好的活动效果。因此各个阶段决策的选取不能任意确定,它依赖于当前面临的状态,又影响以后的发展。当各个阶段决策确定后,就组成一个决策序列,因而也就确定了整个过程的一条活动路线。这种把一个问题看做是一个前后关联具有链状结构的多阶段过程就称为多阶段决策过程,这种问题称为多阶段决策最优化问题。每个阶段中,都求出本阶段的各个初始状态到过程终点的最短路径和最短距离,当逆序倒推到过程起点时,便得到了全过程的最短路径及最短距离,同时附带得到了一组最优结果。***
+
+
+****
+
+### :pencil2:动态规划的步骤
+
+ ***1.递归+记忆化 ->反向推出递推公式***
+
+ ***2.状态的定义 opt[n],dp[n].***
+
+ ***3.状态转移的方程dp[n]=dp[n-1]+dp[n-2]***
+
+ ***4.最优子结构***
+******
+**最经典的爬楼梯问题,给定一个数字代表着楼梯的层数,每次你可以走一步或者两步,求最终你可以有几种方式到达顶峰。递归是自上而下,一层层调用,到了递归的出口,又一层层的回来。而动态规划是从下而上的去思考,比如还是这个爬楼梯问题,如果是动态规划的思想就是**
+```php
+
+f[n]=f[n-1]+f[n+2];
+//一个斐波那契数列
+```
+
+***动态规划需要大量的实战练习,一般你只要记住两步,定义好状态,有些时候单单定义一个一维数组的状态是不够的,二通过状态的定义推出递推公式。具体看场景使用。(下面是leetcode70,72,120,121,122,123,152,300动态规划的题目)***
+
+
+
+
+
+```php
+ /**
+ * @param Integer $n
+ * @return Integer
+ */
+ function climbStairs($n) {
+ if($n<=1){
+ return 1;
+ }
+ $res[0]=1;
+ $res[1]=1;
+ for($i=2;$i<=$n;$i++){
+ $res[$i]=$res[$i-1]+$res[$i-2];
+ }
+ return $res[$n];
+
+ }
+```
+******
+
+
+
+
+
+
+```php
+ /**
+ * @param String $word1
+ * @param String $word2
+ * @return Integer
+ */
+ function minDistance($word1, $word2) {
+
+ for($i=0;$i<=strlen($word1);$i++) $dp[$i][0]=$i;
+ for($i=0;$i<=strlen($word2);$i++) $dp[0][$i]=$i;
+
+ for($i=1;$i<=strlen($word1);$i++){
+ for($j=1;$j<=strlen($word2);$j++){
+ if(substr($word1,$i-1,1)==substr($word2,$j-1,1)){
+ $dp[$i][$j]=$dp[$i-1][$j-1];
+ }else{
+ $dp[$i][$j]=min($dp[$i-1][$j],$dp[$i][$j-1],$dp[$i-1][$j-1])+1;
+ }
+ }
+ }
+ return $dp[strlen($word1)][strlen($word2)];
+ }
+```
+******
+
+
+
+
+
+
+```php
+ /**
+ * @param Integer[][] $triangle
+ * @return Integer
+ */
+ function minimumTotal($triangle) {
+ if(empty(count($triangle))){
+ return 0;
+ }
+ for($i=count($triangle)-1;$i>=0;$i--){
+ for($j=0;$j
+
+
+```php
+ /**
+ * @param Integer[] $prices
+ * @return Integer
+ */
+ function maxProfit($prices) {
+
+ //二维数组的0,1,2表示的状态分别是没买股票,买了股票, 卖了股票
+ $res=0;
+ $pro[0][0]=$pro[0][2]=0;
+ $pro[0][1]=-$prices[0];
+ for($i=1;$i
+
+
+```php
+ /**
+ * @param Integer[] $prices
+ * @return Integer
+ */
+ function maxProfit($prices) {
+ $dp[0][0]=0;
+ $dp[0][1]= -$prices[0];
+ $res=0;
+ for($i=1;$i
+
+
+```php
+ /**
+ * @param Integer[] $prices
+ * @return Integer
+ */
+ function maxProfit($prices) {
+ $res=0;
+ $dp[0][0][0]=0;
+ $dp[0][0][1]= -$prices[0];
+ $dp[0][1][0]= -$prices[0];
+ $dp[0][1][1]= -$prices[0];
+ $dp[0][2][0]=0;
+
+ for($i=1;$i
+
+
+```php
+ /**
+ * @param Integer[] $nums
+ * @return Integer
+ */
+ function maxProduct($nums) {
+ $max=$min=$res=$nums[0];
+ for($i=1;$i
+
+
+```php
+
+/**
+ * @param Integer[] $nums
+ * @return Integer
+ */
+ function lengthOfLIS($nums) {
+ if(empty($nums)){
+ return 0;
+ }
+ $res=1;
+ for($i=0;$i
+
+
+
+
+
+
diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP2.md" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP2.md"
index e8ae82a..2f329c8 100644
--- "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP2.md"
+++ "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/HTTP2.md"
@@ -39,7 +39,7 @@ HTTP1.0最早在网页中使用是在1996年,那个时候只是使用一些较
3. HTTP和HTTPS使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
4. HTTPS可以有效的防止运营商劫持,解决了防劫持的一个大问题。
-
+
## 使用SPDY加快你的网站速度
@@ -64,5 +64,23 @@ SPDY位于HTTP之下,TCP和SSL之上,这样可以轻松兼容老版本的HTT
- **header压缩,**如上文中所言,对前面提到过HTTP1.x的header带有大量信息,而且每次都要重复发送,HTTP2.0使用encoder来减少需要传输的header大小,通讯双方各自cache一份header fields表,既避免了重复header的传输,又减小了需要传输的大小。
- **服务端推送**(server push),同SPDY一样,HTTP2.0也具有server push功能。目前,有大多数网站已经启用HTTP2.0,例如[YouTuBe](https://www.youtube.com/),[淘宝网](http://www.taobao.com/)等网站,利用chrome控制台可以查看是否启用H2
-
+
+```conf
+server {
+ listen 443 ssl http2;
+ server_name example.com;
+ root /var/www/example.com/public;
+
+ # SSL
+ ssl_certificate /etc/live/example.com/fullchain.pem;
+ ssl_certificate_key /etc/live/example.com/privkey.pem;
+ ssl_trusted_certificate /etc/live/example.com/chain.pem;
+
+ # index.html fallback
+ location / {
+ try_files $uri $uri/ /index.html;
+ }
+}
+
+```
\ No newline at end of file
diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/TCP\345\215\217\350\256\256.md" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/TCP\345\215\217\350\256\256.md"
index 6b3cb1a..8d6b12a 100644
--- "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/TCP\345\215\217\350\256\256.md"
+++ "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/TCP\345\215\217\350\256\256.md"
@@ -191,3 +191,16 @@

+## TCP粘包
+
+> 世界上本没有“粘包”,只不过是少数人没有正确处理TCP数据边界问题,成熟的应用层协议(http、ssh)都不会存在这个问题。但是如果你使用纯TCP自定义协议,那就需要自己处理好了。
+TCP是面向字节流的传输,一个数据包可能会被拆成多个小的数据包进行发送,这样就会和其他的数据包连在一起,形成粘包。
+举个例子,比如发送两个"hello world" 但是可能服务端收到的是"hello" 和 "worldhello world"。第一次的数据包和第二次的数据连在一起了。
+
+"粘包"的原因
+ - 当连续发送数据时,由于tcp协议的nagle算法,会将较小的内容拼接成大的内容,一次性发送到服务器端,因此造成粘包
+ - 当发送内容较大时,由于服务器端的recv(buffer_size)方法中的buffer_size较小,不能一次性完全接收全部内容,因此在下一次请求到达时,接收的内容依然是上一次没有完全接收完的内容,因此造成粘包现象
+解决粘包的方案
+- 使用特殊的字符或字符串作为消息的边界,例如 HTTP 协议的 headers 以“\r\n”为字段的分隔符,但是如果消息中不能含有定界符,否则会导致消息混乱
+- 消息长度固定,消息长度不足,空白填充 提前确定包长度,读取的时候也安固定长度读取,适合定长消息包。
+- 自定义协议,将消息分为消息头和消息体,消息头中包含表示消息总长度。
diff --git "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/Webscokt.md" "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/Webscokt.md"
index 08b8f20..37e0ab4 100644
--- "a/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/Webscokt.md"
+++ "b/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234/Webscokt.md"
@@ -2,7 +2,7 @@
> Websocket是html5提出的一个协议规范,参考rfc6455。
>
-> websocket约定了一个通信的规范,通过一个握手的机制,客户端(浏览器)和服务器(webserver)之间能建立一个类似tcp的连接,从而方便c-s之间的通信。在websocket出现之前,web交互一般是基于http协议的短连接或者长连接。
+> websocket约定了一个通信的规范,通过一个握手的机制,客户端(浏览器)和服务器(webserver)之间能建立一个类似tcp的连接,从而方便C-S之间的通信。在websocket出现之前,web交互一般是基于http协议的短连接或者长连接。
>
> WebSocket是为解决客户端与服务端实时通信而产生的技术。websocket协议本质上是一个基于tcp的协议,是先通过HTTP/HTTPS协议发起一条特殊的http请求进行握手后创建一个用于交换数据的TCP连接,此后服务端与客户端通过此TCP连接进行实时通信。
@@ -10,7 +10,7 @@
### Websocket和HTTP协议的关系
-同样作为应用层的协议,WebSocket在现代的软件开发中被越来越多的实践,和HTTP有很多相似的地方,这里将它们简单的做一个纯个人、非权威的比较:
+同样作为应用层的协议,WebSocket在现代的软件开发中被越来越多的实践,和HTTP有很多相似的地方,这里将它们简单比较:
#### 相同点
@@ -58,12 +58,10 @@ Sec-WebSocket-Version: 13
```http
HTTP/1.1 101 Switching Protocols
-Content-Length: 0
Upgrade: websocket
Sec-Websocket-Accept: ZEs+c+VBk8Aj01+wJGN7Y15796g=
-Server: TornadoServer/4.5.1
Connection: Upgrade
-Date: Wed, 21 Jun 2017 03:29:14 GMT
+Sec-WebSocket-Protocol: chat, superchat
```
Sec-Websocket-Accept 是一个校验。用客户端发来的sec_key 服务器通过sha1计算拼接商GUID【258EAFA5-E914-47DA-95CA-C5AB0DC85B11 】 。然后再base64encode
@@ -96,10 +94,95 @@ Sec-Websocket-Accept 是一个校验。用客户端发来的sec_key 服务器通
- Payload data:任意长度数据。包含有扩展定义数据和应用数据,如果没有定义扩展则没有此项,仅含有应用数据。
+### 服务端简单实现
+```php
+// 封装ws 协议的数据包
+function build($msg) {
+ $frame = [];
+ $frame[0] = '81'; // 81 就是 10000001 第一位1表示最后一个数据段,最后一位1表示这是文本数据
+ $len = strlen($msg);
+ if ($len < 126) {
+ //7位长度 第一个是掩码 默认是0
+ //小于126的时候 也是 01111110 数据包第二个字节表示长度
+ $frame[1] = $len < 16 ? '0' . dechex($len) : dechex($len);
+ } else if ($len < 65025) {
+ //7位 + 16位 01111110 00000000 00000000
+ $s = dechex($len);
+ $frame[1] = '7e' . str_repeat('0', 4 - strlen($s)) . $s;
+ } else {
+ //7位 + 64位 01111111 00000000 00000000
+ $s = dechex($len);
+ $frame[1] = '7f' . str_repeat('0', 16 - strlen($s)) . $s;
+ }
+ $data = '';
+ $l = strlen($msg);
+ for ($i = 0; $i < $l; $i++) {
+ $data .= dechex(ord($msg{$i}));
+ }
+ //最后是数据内容
+ $frame[2] = $data;
+ $data = implode('', $frame);
+ return pack("H*", $data);
+}
+//拆包
+function parse($buffer) {
+ $decoded = '';
+ $len = ord($buffer[1]) & 127;
+ if ($len === 126) {
+ $masks = substr($buffer, 4, 4);
+ $data = substr($buffer, 8);
+ } else if ($len === 127) {
+ $masks = substr($buffer, 10, 4);
+ $data = substr($buffer, 14);
+ } else {
+ $masks = substr($buffer, 2, 4);
+ $data = substr($buffer, 6);
+ }
+ for ($index = 0; $index < strlen($data); $index++) {
+ $decoded .= $data[$index] ^ $masks[$index % 4];
+ }
+ return $decoded;
+ }
+$socket = stream_socket_server("tcp://0.0.0.0:8888", $errno, $errstr);
+if (!$socket) {
+ echo "$errstr ($errno)