主页

应用层

2022-05-05
计算机网络, c语言

HTTP #

  • 网页包含多个对象,如HTML文件、JPEG图片、视频文件、动态脚本等,基本HTML文件包含对其他对象引用的链接。通过URL可以进行对象的寻址。
  • HTTP协议流程:
    1. 服务器在80端口等待客户的请求
    2. 浏览器发起到服务器的TCP连接(创建套接字Socket)
    3. 浏览器与Web服务器交换HTTP消息
    4. 关闭TCP连接
  • HTTP是无状态的协议,即服务器不维护任何有关客户端过去所发请求的信息。

HTTP连接 #

  • HTTP包含非持久性连接和持久性连接
    • 非持久性连接指的是每个TCP连接最多允许传输一个对象,HTTP1.0使用非持久性连接。
    • 持久性连接指每个TCP连接可以传输多个对象,HTTP1.1默认使用持久性连接。

HTTP消息格式 #

  • HTTP有两类消息,请求消息(request)和响应消息(response)。
  • HTTP请求消息的格式如下:
  • HTTP请求格式
  • HTTP响应消息的格式如下:
  • HTTP请求格式

Web缓存 #

  • 如果浏览器缓存过数据,当下次发送相同请求时,浏览器向服务器发送HTTP请求,并带上If-modified-since: <date>
  • 如果对象未改变,则返回304 Not Modified,不反会对象,表示对象未被修改。
  • 如果对象发生改变,服务器会返回对象。

DNS #

  • DNS的是分布式数据库。提供了域名向IP地址的翻译、主机别名等功能。
  • 一般来说是本地域名解析服务器代替进行域名解析的,当主机进行DNS查询时,查询会被发送到本地域名服务器,当本地域名服务器无法解析域名时,就会访问根域名服务器。全球共13个根域名服务器。
  • 顶级域名服务器(TLD,top-level domain),负责com、org、net等顶级域名和国家顶级域名,如cn、uk等。
  • 权威域名服务器是组织的域名解析服务器,提供组织内部服务器的解析服务。
  • cis.poly.edu想获取gaia.cs.umass.edu的IP地址时,迭代查询流程如下
  • 迭代查询流程
  • 递归查询的流程如下(将域名解析的任务交给所联系的服务器)
  • 递归查询流程
  • 只要域名解析服务器获得域名时,就会缓存这一映射,一段时间后缓存条目才会失效。

DNS记录 #

  • 资源记录格式为(name, value, type, ttl),类型如下
  • type=A,Name为主机域名,Value为IP地址
  • type=NS,Name为域(如edu.cn),value为该域权威域名解析服务器的主机域名。
  • type=CNAME,name为某一真实域名的别名,value为真实域名
  • type=MX,value是与name对应的邮件服务器。

DNS协议消息格式 #

  • DNS查询(query)和回复(reply消息)的格式相同。
  • 消息头部
    • Identification:16为查询编号,回复使用相同的编号。
    • flags表示查询或回复、期望递归、递归可用、权威回答。
  • DNS查询流程

socket #

  • 对外通过IP地址+端口号表示通信端点。
  • 操作系统通过套接字描述符(socket descriptor)来管理套接字。
  • socket类似于文件,当应用进程创建套接字时,操作系统分配一个数据结构存储该套接字相关信息。
  • socket描述符表

地址结构 #

  • 使用TCP/IP协议簇的网络应用程序声明端点地址变量时,使用结构socketaddr_in
struct sockaddr_in
{
  u_char sin_len; /*地址长度 */
  u_char sin_family; /*地址族(TCP/IP:AF_INET) */
  u_short sin_port; /*端口号 */
  struct in_addr sin_addr; /*IP地址 */
  char sin_zero[8]; /*未用(置0) */
}

socket api函数 #

socket函数 #

  • sd = socket(protofamily,type,proto); 创建套接字并返回套接字描述符。
  • 第一个参数指定协议族:protofamily=PF_INET(TCP/IP)
  • 第二个参数指定套接字类型:SOCK_STREAM(TCP), SOCK_DGRAM(UDP), SOCK_RAW(面向网络层)
  • 第三个参数指定协议号:0为默认
struct protoent *p;
p=getprotobyname("tcp");
SOCKET sd=socket(PF_INET,SOCKET_STREAM,p->p_proto);

close #

  • int close(SOCKET sd) 关闭一个描述符为sd的套接字
  • 如果多个进程共享一个套接字,调用close将套接字引用计数减1,减至0才关闭。
  • 一个进程中的多线程对一个套接字的使用无计数。
  • 返回值 0:成功,SOCKET_ERROR:失败。

bind #

  • int bind(sd,localaddr,addrlen); 绑定套接字的本地端点地址
  • 客户端一般不必调用bind函数,一般由服务端调用。
  • 一台机器可能由多个网卡,可用使用地址通配符INADDR_ANY来绑定。

listen #

  • int listen(sd,queuesize);置服务器端的流套接字处理处于监听状态。
  • 仅服务端调用,仅用于面向连接的流套接字。
  • queuesize表示连接请求的队列大小。
  • 返回值 0:成功,SOCKET_ERROR:失败。

connect #

  • connect(sd,saddr,saddrlen) 客户端调用connect函数来使客户端套接字(sd)与特定计算机的特定端口(saddr)的套接字服务进行连接。
  • 仅用于客户端,可用于TCP客户端也可以用于UDP客户端。

accept #

  • newsock = accept(sd,caddr,caddrlen);服务程序调用accept函数从处于监听状态的流套接字sd的客户端连接请求队列中取出排在最前的一个客户请求,并且创建一个新的套接字来与客户套接字创建连接通道。
  • 仅用于TCP套接字,仅用于服务器。
  • 服务器会利用新创建的套接字(newsock)与客户端通信。

send #

  • send(sd,*buf,len,flags);
  • sendto(sd,*buf,len,flags,destaddr,addrlen);
  • send:发送数据(用于TCP套接字或连接模式(调用了connect函数)的客户端UDP套接字)
  • sendto函数用于UDP服务器端套接字与未调用connect函数的UDP客户端套接字发送数据

recv #

  • recv(sd,buffer,len,flags);
  • recvfrom(sd,buf,len,flags,senderaddr,saddrlen);
  • recv函数从TCP连接的另一端接收数据,或者从调用了connect函数的UDP客户端套接接收服务器发来的数据
  • recvfrom函数用于从UDP服务器端套接字与未调用connect函数的UDP客户端套接字接收对端数据

setsockopt,getsockopt #

  • int setsockopt(int sd, int level, int optname, *optval, int optlen);
  • int setsockopt(int sd, int level, int optname, *optval, int optlen);
  • setsockopt()函数用来设置套接字sd的选项参数
  • getsockopt()函数用于获取任意类型、任意状态套接口的选项当前值,并把结果存入optval

网络字节序 #

  • 网络字节序采用大端排序方式(低位低地址,高位高地址)
  • 某些Socket API函数的参数需要存储为网络字节顺序(如IP地址、端口号等)
  • 转换函数
    • htons: 本地字节顺序→网络字节顺序(16bits)
    • ntohs: 网络字节顺序→本地字节顺序(16bits)
    • htonl: 本地字节顺序→网络字节顺序(32bits)
    • ntohl: 网络字节顺序→本地字节顺序(32bits)

解析服务器IP地址 #

  • 客户端可能使用域名或IP地址标识服务器,IP协议需要使用32为二进制IP地址,需要将函数名或IP地址转换为32为IP地址。
  • inet_addr可用实现点分十进制IP地址到32位IP地址转换。
  • gethostbyname实现域名到32位IP地址转换。会返回一个指向结构hostent的指针。
struct hostent {
 char FAR* h_name; /*official host name */
 char FAR* FAR* h_aliases; /*other aliases */
 short h_addrtype; /*address type */
 short h_lengty; /*address length */
 char FAR* FAR* h_addr_list; /*list of address */
};
#define h_addr h_addr_list[0] 

解析服务器端口号 #

  • 客户端可能使用服务名(如HTTP)标识服务器端口,需要将服务名转换为熟知端口号
  • getservbyname会返回一个指向结构servent的指针。
struct servent {
 char FAR* s_name; /*official service name */
 char FAR* FAR* s_aliases; /*other aliases */
 short s_port; /*port for this service */
 char FAR* s_proto; /*protocol to use */
}; 

解析协议号 #

  • 客户端可能使用协议名来指定协议,需要将协议名转换为协议号
  • 函数getprotobyname实现协议名到协议号的转换。会返回一个protoent的指针。
struct protoent {
 char FAR* p_name; /*official protocol name */
 char FAR* FAR* p_aliases; /*list of aliases allowed */
 short p_proto; /*official protocol number*/
 }; 

TCP客户端软件流程 #

  1. 确定服务器IP地址和端口号
  2. 创建套接字
  3. 分配本地端点地址(可选)
  4. 连接服务器(套接字)
  5. 遵循应用层协议进行通信
  6. 关闭/释放连接

UDP客户端软件流程 #

  1. 确定服务器IP地址与端口号
  2. 创建套接字
  3. 分配本地端点地址(IP地址+端口号)
  4. 指定服务器端点地址,构造UDP数据报
  5. 遵循应用层协议进行通信
  6. 关闭/释放套接字

Https配置

2022-05-05
nginx

nginx的https配置 #

配置流程 #

  1. 编译nginx需要带上--with-http_ssl_module选项。编译失败可能是缺少依赖,安装对应依赖即可。
./configure --prefix=/opt/nginx --with-http_ssl_module
make
make install
  1. 生成证书,在控制台中依次执行以下命令,执行完成后将生成的文件移动到/opt/nginx/ssl下。

openssl genrsa -des3 -out uam.key

输入密码:********
再次输入密码:********

openssl req -new -key uam.key -out uam.csr

输入密码:********
依次输入如下
Country Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:shaanxi
Locality Name (eg, city) []:xian
Organization Name (eg, company) [Internet Widgits Pty Ltd]:xiaoxiang
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:
Email Address []:
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

openssl rsa -in uam.key -out uam.nopass.key

输入密码:********

openssl req -new -x509 -days 3650 -key uam.nopass.key -out uam.crt

依次输入如下
Country Name (2 letter code) [AU]:CN
State or Province Name (full name) [Some-State]:shaanxi
Locality Name (eg, city) []:xian
Organization Name (eg, company) [Internet Widgits Pty Ltd]:xiaoxiang
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:
Email Address []:
  1. nginx中http块中添加如下配置:
    server {
        listen       443 ssl;

        #使用https,证书位置
        ssl_certificate /opt/nginx/ssl/uam.crt;
        ssl_certificate_key /opt/nginx/ssl/uam.nopass.key;

        location /{
            proxy_pass http://localhost:80; 
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
        #http转发到https
        error_page 497 https://$host:$server_port$request_uri;
        error_page   500 502 503 504  /50x.html;
     }

配置介绍 #

https配置 #

  • nginx配置https,只有listen后面的sslssl_certificatessl_certificate_key这三个参数是必须的。
server {
    listen              443 ssl;
    server_name         www.example.com;
    ssl_certificate     www.example.com.crt;
    ssl_certificate_key www.example.com.key;
    ...
}
  • ssl_protocolsssl_ciphers可以限制SSL/TLS的版本和密码,默认情况下nginx会使用ssl_protocols TLSv1 TLSv1.1 TLSv1.2ssl_ciphers HIGH:!aNULL:!MD5,所以一般情况下不需要手动配置。

https优化 #

  • SSL会消耗额外的CPU资源,在多核的CPU上应该使worker_processes配置为不少于CPU核心数的值(可以将该参数设置为auto,这时nginx会自动调整工作进程数为CPU数量)。
  • CPU最密集的行为是SSL握手,有两种方式可以降低握手次数:
    • 使用keepalive_timeout,使用该参数可以是多个请求通过同一个连接,后面的请求可以复用SSL会话。
    • 通过ssl_session_cache可以将会话存储到工作进程之间共享的SSL会话缓存中,1MB的缓存可以包含大约4000个会话,默认缓存超时时间为5分钟,可以使用ssl_session_timeout来设置超时时间。
worker_processes auto;

http {
    ssl_session_cache   shared:SSL:10m;
    ssl_session_timeout 10m;

    server {
        listen              443 ssl;
        server_name         www.example.com;
        keepalive_timeout   70;

        ssl_certificate     www.example.com.crt;
        ssl_certificate_key www.example.com.key;
        ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers         HIGH:!aNULL:!MD5;
        ...
}

一个server中包含http和https #

  • 可以在一个server块中处理http请求和https请求
server {
    listen              80;
    listen              443 ssl;
    server_name         www.example.com;
    ssl_certificate     www.example.com.crt;
    ssl_certificate_key www.example.com.key;
    ...
}

无法使用基于server_name的多个HTTPS服务 #

  • 当为一个IP配置了多个域名时,使用server_name并不能区分这些域名所对应的server块。案例如下:
server {
    listen          443 ssl;
    server_name     www.example.com;
    ssl_certificate www.example.com.crt;
    ...
}

server {
    listen          443 ssl;
    server_name     www.example.org;
    ssl_certificate www.example.org.crt;
    ...
}
  • 这个例子中无论输入哪个域名,浏览器都返回默认的证书(即www.example.com的证书)。因为SSL连接是建立在浏览器发送HTTP请求之前,nginx不能知道当前发送请求的是哪个域名,所以它只能返回默认的服务证书。
  • 目前的解决方法是为每个HTTPS的server分配一个IP地址。
server {
    listen          192.168.1.1:443 ssl;
    server_name     www.example.com;
    ssl_certificate www.example.com.crt;
    ...
}

server {
    listen          192.168.1.2:443 ssl;
    server_name     www.example.org;
    ssl_certificate www.example.org.crt;
    ...
}

相关链接 Configuring HTTPS servers (nginx.org)

serve_name

2022-04-05
nginx

server_name可以决定使用哪个server块来处理请求,server_name可以是精确的名称、通配符和正则表达式。

server {
    listen       80;
    server_name  example.org  www.example.org;
    ...
}

server {
    listen       80;
    server_name  *.example.org;
    ...
}

server {
    listen       80;
    server_name  mail.*;
    ...
}

server {
    listen       80;
    server_name  ~^(?<user>.+)\.example\.net$;
    ...
}

当按照域名来访问服务时,如果可以匹配多个server_name,就会按照如下的顺序来进行匹配:

  1. 精确的名称。
  2. 以星号开头的最长通配符,如*.exmaple.org
  3. 以星号结束的最长通配符,如mail.*
  4. 第一个匹配的正则表达式(按照在配置文件出现的先后顺序)。

通配符 #

包含星号的通配符只能出现在name的开始或者结束的地方,且只能在点号.的旁边。www.*.example.orgw*.example.org都是非法的。这种情况下可以使用正则表达式。如~^www\..+\.example\.org$~^w.*\.example\.org$。星号可以匹配name的好几个部分,如*.example.org不仅可以匹配www.example.org也可以匹配www.sub.example.org

.example.org这种特殊格式的通配符不仅可以匹配example.org,还可以匹配通配符*.example.org

正则表达式 #

nginx使用的正则表达式和Perl编程语言(即PCRE)兼容。如果要使用正则表达式,server_name必须以波浪线开始。

server_name ~^www\d+\.example\.net$;`

如果不以波浪线开头,它会被认为是一个精确的名称,如果表达式中包含星号,它会被认为是一个通配符名称。^$在逻辑上是必须要有的。域名中的.需要被转义,因为.也是正则的元字符。当正则表达式中包含{}时,正则需要被括起来。

server_name  "~^(?<name>\w\d{1,3}+)\.example\.net$";

否则nginx在启动时会报错。

命名正则表达式捕获组可以当作一个变量使用:

server {
    server_name   ~^(www\.)?(?<domain>.+)$;

    location / {
        root   /sites/$domain;
    }
}

PCRE支持下面这几种语法

  1. ?<name>?'name'Perl5.10兼容语法,自PCRE-7.0开始支持。
  2. ?P<name> Python兼容依法,自PCRE-4.0开始支持。

当nginx启动失败并显示以下错误时:

pcre_compile() failed: unrecognized character after (?< in ...

这表示PCRE库版本太久,可以使用?P<name>语法代替。捕获组也可以使用数字形式。

server {
    server_name   ~^(www\.)?(.+)$;

    location / {
        root   /sites/$2;
    }
}

然而,这种用法仅限于简单的情况,因为数字引用很容易被覆盖。

InnoDB

2022-03-20
数据库

InnoDB #

  • mysql服务器中负责对表中的数据读取和写入工作的部分是存储引擎,真实数据在不同存储引擎中存放的格式一般是不同的。
  • innodb会将数据划分为若干个页,以页作为磁盘和内存交互的基本单位,页也是innodb管理存储空间的基本单位,页的默认大小为16KB。一般情况下,一次最少从磁盘中读取16KB的内容到内存中,一次最少把内存中的16KB内容刷新到磁盘中。
  • innodb有4中行格式COMPACT、PEDUNDANT、DYNAMIC、COMPRESSED。
CREATE TABLE 表名(列的信息) ROW_FORMAT=行格式名称;
ALTER TABLE 表名 ROW_FORMAT=行格式名称;

COMPACT行格式 #

COMPACT行格式

  1. 记录的额外信息,包含3个部分:变长字段的长度列表、NULL值列表和记录头信息。
    1. mysql支持一些变长的数据类型,如varchar、text和blob,变长数据类型占用的字节数也是不固定的,所以在存储数据时要把这些数据占用的字节数也存起来。一个变长的数据占用的存储空间分为两部分:真正的数据内容和该数据占用的字节数。各变长字段的真实数据占用的字节数按照列的顺序逆序存放。
    2. 当使用定长编码的字符集时(如ascii),CHAR(10)列所占用的字节数不会被加到变长字段长度列表中,而当采用变长编码的字符集时,CHAR(10)列所占用的字节数就会被加到变长字段的长度列表中。此外,采用变长编码字符集的CHAR(M)至少占用M个字节,而VARCHAR(M)没有这个要求。
  • 记录头信息由固定的5字节组成。

名称 大小(bit) 描述
deleted_flag 1 标识该记录是否被删除。所有被删除的记录会组成一个垃圾链表,之后若有新的记录插入到表中,他们就可能覆盖掉这些记录所占用的存储空间。
min_rec_flag 1 B+树每层非叶子节点中的最小的目录项记录都会添加该标记
n_owned 4 一个页面中的记录会被分为多个组,每个组的最后那条记录会在n_owned中记录该组内由几条记录。其他的记录中该字段为0
heap_no 13 表示当前记录在页面堆中的相对位置
record_type 3 表示当前记录的类型,0表示普通记录,1表示B+树非叶节点的目录项,2表示Infimum记录,3表示Supremum记录
next_record 16 表示下一条记录的相对位置。下一条记录指的是按照主键值从小到大的顺序排列的下一条记录。infimum记录的下一条记录就是本页中主键最小的记录,本页中主键最大的记录的下一条记录就是supremum记录。
  • mysql默认会为每个记录添加一些隐藏列。innodb的主键生成策略:优先使用用户自定义的主键作为主键,如果没有主键,就选取一个不允许存储为NULL的UNIQUE键作为主键,如果没有,innodb会为表默认添加一个名为row_id的隐藏列作为主键。

    列名 占用空间 描述
    row_id(DB_ROW_ID) 6字节 非必须,行ID,唯一标识一条记录
    trx_id(DB_TRX_ID) 6字节 必须,事务ID
    roll_pointer(DB_ROLL_PTR) 7字节 必须,回滚指针。
  • innodb是以页为基本单位来管理存储空间的,一个页的大小通常是16KB,有时候一个页存不了一条记录,所以mysql中会有溢出列的概念。

  • COMPACT和REDUNDANT行格式中,对于存储占用的空间非常多的列,在记录的真实数据处只会存储该列的一部分数据,而把剩余的数据分散存储到其他的几个页中,然后在记录真实数据处用20字节存储指向这些页的地址和这些页的数据占用的字节数。溢出列之间是使用链表连接起来的。

  • DYNAMIC和COMPRESSED类似于COMPACT行格式,但是在处理溢出列有点差异,他们不会在记录真实数据处存储列真实数据的前768字节,而是把所有的列都存到溢出页中。COMPRESSED行格式会采用压缩算法对页面进行压缩。

innodb数据页结构 #

  • 一个数据页可以被划分为7个部分,分别如下:
    • File Header:表示页的一些通用信息,占固定的38字节。
    • Page Header:表示数据页专有的一些信息,占固定的56字节。
    • Infimum和Supremum:两个虚拟的伪记录,分别表示页中最小记录和最大记录,占固定的26字节。
    • User Records:真正存储插入的记录,大小不固定。
    • Free Space:页中尚未使用的部分,大小不固定。
    • Page Directory:页中某些记录的相对位置,是各个槽对应的记录在页面中的地址偏移量;大小不固定,插入的记录越多,这部分占用的空间越多。
    • File Trailer:用于检验页是否完整,占固定的8字节。
  • 一条完整的记录时可以比大小的,比较记录的大小就是比较主键的大小。
  • User Records中的记录之间亲密物件的排列,这种结构称为堆(heap)。把一条记录在堆中的相对位置称为heap_no,靠前的heap_no相对较小,innodb会自动给每个页中加上两条记录,这两条记录一条为最小记录(infimum),一条为页面的最大记录(supremum)。这两条记录的heap_no最小。堆中的heap_no在分配之后就不会发生改变了,即使之后删除了堆中的某条记录。
  • 每个记录的头信息中都有一个next_record属性,可以是页面中的所有记录串联成一个单向链表。
  • Innodb会把页中的记录划分为若干个组,每个组的最后一个记录的地址偏移量作为一个槽,存放在Page Directory中,一个槽占用2字节。
  • 在一个页中根据主键查找记录是非常快的,步骤为:通过二分法确定该记录所在分组对应的槽,并找到改槽所在分组中主键值最小的那条记录;通过记录的next_record遍历改槽所在组中的各个记录。
  • 每个数据页的File Header都有上一页和下一页的编号,所有的数据页会组成一个双向链表。

Javascript

2022-01-31
前端

基本语法 #

  • 变量如果只声明而没有赋值,则该变量的值是undefined

  • js是一种动态类型语言,变量的类型没有限制,变量可以随时更改类型。

  • js引擎的工作方式是先解析代码,获取所有被声明的变量,然后再一行行的运行,这会使得所有变量的声明语句,都会被提升到代码的头部。

  • 变量命名规则如下:第一个字符可以是任意Unicode字母以及$_,第二个字符及后面的字符还可以用数字0-9。

if…else #

if (m === 3) {
  // 满足条件时,执行的语句
} else {
  // 不满足条件时,执行的语句
}

switch #

  • switch语句后面的表达式,与case语句后面的表示式比较运行结果时,采用的是严格相等运算符(===),这意味着比较时不会发生类型转换。
switch (x) {
  case 1:
    console.log('x 等于1');
    break;
  case 2:
    console.log('x 等于2');
    break;
  default:
    console.log('x 等于其他值');
}

三元运算符 #

(条件) ? 表达式1 : 表达式2

循环语句 #

while #

var i = 0;

while (i < 100) {
  console.log('i 当前为:' + i);
  i = i + 1;
}

for #

var x = 3;
for (var i = 0; i < x; i++) {
  console.log(i);
}

do…while #

var x = 3;
var i = 0;

do {
  console.log(i);
  i++;
} while(i < x);

break和continue #

for (var i = 0; i < 5; i++) {
  console.log(i);
  if (i === 3)
    break;
}

var i = 0;

while (i < 100){
  i++;
  if (i % 2 === 0) {
      continue;
  }
  console.log('i 当前为:' + i);
}

数据类型 #

六种数据类型 #

  • 数值(number):整数和小数(比如13.14)。
  • 字符串(string):文本(比如Hello World)。
  • 布尔值(boolean):表示真伪的两个特殊值,即true(真)和false(假)。
  • undefined:表示“未定义”或不存在,即由于目前没有定义,所以此处暂时没有任何值。
  • null:表示空值,即此处的值为空。
  • 对象(object):各种值组成的集合。

typeof运算符 #

typeof运算符可以返回一个值的数据类型。

数值、字符串、布尔值分别返回numberstringboolean

typeof 123 // "number"
typeof '123' // "string"
typeof false // "boolean"

函数返回function

function f() {}
typeof f

undefined返回undefined

typeof undefined
// "undefined"

typeof可以用来检查一个没有声明的变量,而不报错。

typeof v
// "undefined"

对象返回object

typeof window // "object"

null返回object

typeof null

空数组([])的类型也是object,在 JavaScript 内部,数组本质上只是一种特殊的对象。

null、undefined和布尔值 #

  • null是一个表示“空”的对象,转为数值时为0;undefined是一个表示"此处无定义"的原始值,转为数值时为NaN。

  • undefined表示“未定义”。

布尔值 #

下面的值都会被转为false

  • undefined
  • null
  • false
  • 0
  • NaN
  • ""''(空字符串)

空数组([])和空对象({})对应的布尔值,都是true

数值 #

整数和浮点数 #

JavaScript 内部,所有数字都是以64位浮点数形式储存,即使整数也是如此。所以,11.0是相同的,是同一个数。

数值的表示方法 #

JavaScript 的数值有多种表示方法,默认情况下,JavaScript 内部会自动将八进制、十六进制、二进制转为十进制。

  • 十进制:没有前导0的数值。
  • 八进制:有前缀0o0O的数值,或者有前导0、且只用到0-7的八个阿拉伯数字的数值。
  • 十六进制:有前缀0x0X的数值。
  • 二进制:有前缀0b0B的数值。
0xff // 255
0o377 // 255
0b11 // 3

数值也可以采用科学计数法表示,下面是几个科学计数法的例子。

123e3 // 123000
123e-3 // 0.123
-3.1E+12
.1e-23

NaN #

NaN是 JavaScript 的特殊值,表示“非数字”(Not a Number),主要出现在将字符串解析成数字出错的场合。

5 - 'x' // NaN

NaN不是独立的数据类型,而是一个特殊数值,它的数据类型依然属于Number,使用typeof运算符可以看得很清楚。

NaN不等于任何值,包括它本身。

NaN === NaN // false

Infinity #

Infinity表示“无穷”,用来表示两种场景。一种是一个正的数值太大,或一个负的数值太小,无法表示;另一种是非0数值除以0,得到Infinity。

Infinity有正负之分,Infinity表示正的无穷,-Infinity表示负的无穷。

Infinity大于一切数值(除了NaN),-Infinity小于一切数值(除了NaN)。

Infinity的四则运算,符合无穷的数学计算规则。

5 * Infinity // Infinity
5 - Infinity // -Infinity
Infinity / 5 // Infinity
5 / Infinity // 0

相关函数 #

parseInt #

parseInt方法用于将字符串转为整数。

parseInt('123') // 123

如果字符串头部有空格,空格会被自动去除。

parseInt('   81') // 81

如果parseInt的参数不是字符串,则会先转为字符串再转换。

parseInt(1.23) // 1
// 等同于
parseInt('1.23') // 1

字符串转为整数的时候,是一个个字符依次转换,如果遇到不能转为数字的字符,就不再进行下去,返回已经转好的部分。

parseInt('8a') // 8
parseInt('12**') // 12
parseInt('12.34') // 12
parseInt('15e2') // 15
parseInt('15px') // 15

如果字符串的第一个字符不能转化为数字(后面跟着数字的正负号除外),返回NaN

parseInt('abc') // NaN
parseInt('+1') // 1

对于那些会自动转为科学计数法的数字,parseInt会将科学计数法的表示方法视为字符串,因此导致一些奇怪的结果。

parseInt(1000000000000000000000.5) // 1
// 等同于
parseInt('1e+21') // 1

parseFloat #

parseFloat方法用于将一个字符串转为浮点数。

parseFloat('3.14') // 3.14

如果字符串符合科学计数法,则会进行相应的转换。

parseFloat('314e-2') // 3.14
parseFloat('0.0314E+2') // 3.14

如果字符串包含不能转为浮点数的字符,则不再进行往后转换,返回已经转好的部分。

parseFloat('3.14more non-digit characters') // 3.14

parseFloat方法会自动过滤字符串前导的空格。

parseFloat('\t\v\r12.34\n ') // 12.34

如果参数不是字符串,或者字符串的第一个字符不能转化为浮点数,则返回NaN

parseFloat([]) // NaN
parseFloat('') // NaN

isNaN #

isNaN方法可以用来判断一个值是否为NaN

isNaN(NaN) // true
isNaN(123) // false

但是,isNaN只对数值有效,如果传入其他值,会被先转成数值。比如,传入字符串的时候,字符串会被先转成NaN,所以最后返回trueisNaNtrue的值,有可能不是NaN,而是一个字符串。

判断NaN更可靠的方法是,利用NaN为唯一不等于自身的值的这个特点,进行判断。

function myIsNaN(value) {
  return value !== value;
}

isFinite #

isFinite方法返回一个布尔值,表示某个值是否为正常的数值。

isFinite(Infinity) // false
isFinite(-Infinity) // false
isFinite(NaN) // false
isFinite(undefined) // false
isFinite(null) // true
isFinite(-1) // true

字符串 #

字符串就是零个或多个排在一起的字符,放在单引号或双引号之中。

字符串可以被视为字符数组,因此可以使用数组的方括号运算符,用来返回某个位置的字符(位置编号从0开始)。但无法改变字符串之中的单个字符。

var s = 'hello';
s[0] // "h"

length #

length属性返回字符串的长度。

var s = 'hello';
s.length // 5

对象 #

对象就是一组“键值对”(key-value)的集合,是一种无序的复合数据集合。

var obj = {
  foo: 'Hello',
  bar: 'World'
};

对象的所有键都是字符串,所以加不加引号都可以。

var obj = {
  'foo': 'Hello',
  'bar': 'World'
};

对象的每一个键又称为“属性”(property),它的“键值”可以是任何数据类型。如果一个属性的值为函数,通常把这个属性称为“方法”,它可以像函数那样调用。

var obj = {
  p: function (x) {
    return 2 * x;
  }
};

obj.p(1) // 2

属性可以动态创建,不必在对象声明时就指定。

var obj = {};
obj.foo = 123;
obj.foo // 123

对象引用 #

如果不同的变量名指向同一个对象,那么它们都是这个对象的引用,也就是说指向同一个内存地址。修改其中一个变量,会影响到其他所有变量。

对象属性 #

读取对象的属性,有两种方法,一种是使用点运算符,还有一种是使用方括号运算符。

var obj = {
  p: 'Hello World'
};

obj.p // "Hello World"
//如果使用方括号运算符,键名必须放在引号里面
obj['p'] // "Hello World"

点运算符和方括号运算符,不仅可以用来读取值,还可以用来赋值。

var obj = {};

obj.foo = 'Hello';
obj['bar'] = 'World';

属性的查看 #

查看一个对象本身的所有属性,可以使用Object.keys方法。

var obj = {
  key1: 1,
  key2: 2
};

Object.keys(obj);

属性的删除 #

delete命令用于删除对象的属性,删除成功后返回true

删除一个不存在的属性,delete不报错,而且返回true

var obj = { p: 1 };
Object.keys(obj) // ["p"]

delete obj.p // true

属性是否存在:in 运算符 #

in运算符用于检查对象是否包含某个属性。

var obj = { p: 1 };
'p' in obj // true
'toString' in obj // true

属性的遍历:for…in 循环 #

for...in循环用来遍历一个对象的全部属性。

var obj = {a: 1, b: 2, c: 3};

for (var i in obj) {
  console.log('键名:', i);
  console.log('键值:', obj[i]);
}

for...in循环有两个使用注意点。

  • 它遍历的是对象所有可遍历(enumerable)的属性,会跳过不可遍历的属性。
  • 它不仅遍历对象自身的属性,还遍历继承的属性。

函数 #

function #

function命令声明的代码区块,就是一个函数。function命令后面是函数名,函数名后面是一对圆括号,里面是传入函数的参数。函数体放在大括号里面。

function print(s) {
  console.log(s);
}

函数表达式 #

除了用function命令声明函数,还可以采用变量赋值的写法。

var print = function(s) {
  console.log(s);
};

name属性 #

函数的name属性返回函数的名字。

function f1() {}
f1.name // "f1"

数组 #

var arr = ['a', 'b', 'c'];

//除了在定义时赋值,数组也可以先定义后赋值。
//任何类型的数据,都可以放入数组。
var arr = [];
arr[0] = 'a';
arr[1] = [1, 2, 3];
arr[2] = {a: 1};

数组属于一种特殊的对象。typeof运算符会返回数组的类型是object

Object.keys方法返回数组的所有键名。可以看到数组的键名就是整数0、1、2。

JavaScript 语言规定,对象的键名一律为字符串,所以,数组的键名其实也是字符串。

length 属性 #

数组的length属性,返回数组的成员数量。

['a', 'b', 'c'].length // 3

for…in 循环 #

var a = [1, 2, 3];

for (var i in a) {
  console.log(a[i]);
}

Elasticsearch起步

2022-01-15
java

  • es是面向文档型数据库,一条数据在这里就是一条文档。

索引 #

  • 创建索引PUT http://ip:9200/索引名称
  • 获取索引的相关信息GET http://ip:9200/索引名称
  • 获取所有的索引GET http://ip:9200/_cat/indices?v
  • 删除索引DELETE http://ip:9200/索引名称

文档 #

添加文档 #

POST /索引名称/_doc
{
    "title": "xiaoxiang",
    "url": "xiaoxiang.space"
}

PUT /索引名称/_doc/id
{
    "title": "xiaoxiang",
    "url": "xiaoxiang.space"
}

PUT /<target>/_doc/<_id> 
POST /<target>/_doc/ 
PUT /<target>/_create/<_id> 
POST /<target>/_create/<_id>

查询数据 #

//查询一条数据
GET /索引名称/_doc/id

//全部查询
GET /索引名称/_search?pretty

修改数据 #

POST /索引名称/_update/12
{
    "doc": {
        "title": "xiaoxiangmax"
    }
}

#可以使用PUT进行数据修改,但需要罗列所有的字段
PUT my_goods/_doc/10 
{ 
	"goodsName": "三星 UA55RU7520JXXZ 52 英寸 4K 超高清",
	"skuCode": "skuCode10",
	...
}

删除数据 #

DELETE /索引名称/_doc/id

#查询删除
POST /my_goods/_delete_by_query 
{ 
	"query": { 
		"match": { 
			"shopCode": "sc00002" 
		} 
	} 
}

文档复杂查询 #

添加查询条件 #

//增加条件查询
GET /索引名称/_search
{
    "query": {
        "match": {
            "category": "xiaoxiang"
        }
    }
}

//不拆解搜索字段
GET /gw_audit-*/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match_phrase": {
            "user_name": "zhao fei"
          }
        }
      ]
    }
  }
}

添加高亮显示 #

//对某个条件添加高亮显示
GET /gw_audit-*/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match_phrase": {
            "user_name": "kpf"
          }
        }
      ]
    }
  },
  "highlight": {
    "fields": {
      "user_name": {}
    }
  }
}

多条件查询 #

//多条件查询,(相当于and)
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "category": "xiaoxiang"
          }
        },
        {
          "match": {
            "price": "5999"
          }
        }
      ]
    }
  }
}

//多条件查询,一个字段多个查询的值(相当于or)
GET /gw_audit-*/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "user_name": "kpf"
          }
        },
        {
          "match": {
            "user_name": "zj"
          }
        }
      ]
    }
  },
  "size": 20000
}

区间查询 #

#区间查询
GET /gw_audit-*/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "range": {
            "spent": {
              "gte": 10,
              "lte": 200
            }
          }
        }
      ]
    }
  }
}

分页查询 #

//全量分页查询
GET /gw_audit-*/_search
{
	"query": {
		"match_all": {
		
		}
	},
	"from": 0,
	"size": 10
}

只查询指定字段 #

//只查询指定字段
GET /索引名称/_search
{
	"query": {
		"match_all": {
		
		}
	},
	"from": 0,
	"size": 10,
	"_source": ["title"]
}

对结果排序 #

//对结果排序
GET /索引名称/_search
{
	"query": {
		"match_all": {
		
		}
	},
	"from": 0,
	"size": 10,
	"sort": {
		"price": {
			"order": "desc"
		}
	}
}

聚合操作,求每组的数量 #

#对查询结果进行聚合操作,统计每一组的数量
GET /gw_audit-*/_search
{
  "aggs": {
    "user_name_group": {
      "terms": {
        "field": "user_name"
      }
    }
  },
  "size": 0
}

聚合操作,统计平均值 #

#对查询结果进行聚合操作,统计平均值
GET /gw_audit-*/_search
{
  "aggs": {
    "user_name_avg": {
      "avg": {
        "field": "spent"
      }
    }
  },
  "size": 0
}

设置映射关系 #

//设置映射关系
PUT test/_mapping
{
  "properties": {
    "name": {
      "type": "text", 
      "index": true
    },
    "sex": {
      "type": "keyword", //关键字不会被分开
      "index": "true"
    },
    "tel": {
      "type": "keyword",
      "index": false
    }
  }
}

_reindex #

PUT cpu-2022
{
  "mappings": {
    "dynamic": "false",
    "properties": {
      "cpu": {
        "type": "keyword"
      },
      "host_name": {
        "type": "keyword"
      }
    }
  }
}

POST _reindex
{
  "source": {
    "index": "cpu-2022.07.*"
  },
  "dest": {
    "index": "cpu-2022"
  }
}

DELETE cpu-2022.07.*


GET cpu-2022/_search
{
  "size": 0,
  "track_total_hits": true
}

正则

2022-01-12
java

  • 完整的正则表达式由两种字符构成,特殊字符(元字符)和文字(普通字符)。

egrep命令 #

元字符 案例
^匹配行的开始,$匹配行的结束。 ^cat$ 匹配以c作为一行的第一个字符,紧接着一个a,紧接着以t结尾。
[abc] (字符组)匹配多个字符之一 [-a-cA-D1.?_] 匹配-abcABCD1.?_中的一个。-(连字符)表示一个范围,只有在字符组内他才可能是一个元字符,如果连字符出现在字符组开头,那么它表示的就是一个普通符号。在字符组中,`?.*
[^ABC]排除型字符组 q[^u] 匹配q后面的字符不为u的情况,注意:这要求q后面必须要有一个字符。
[^-中的连字符也不是一个元字符。
.匹配任意字符 注意:在字符组中的.是普通字符。
` ` 表示或
egrep的-i参数表示忽略大小写。
\<\>单词的起始位置和结束位置。 \<cat 匹配以cat开头的单词,cat\> 匹配以cat结尾的单词。
? 表示可选 July?表示y是可选的,该正则可以匹配July和Jul。
July? 4(th)?中的th是可选的,可以匹配July 4July 4th
+ 表示紧邻的元素出现一次或多次
*表示紧邻的元素出现0次或多次
.+ 表示匹配任意字符至少一次。
.* 表示匹配任意字符0次到无数次。
{min,max} 区间量词 允许重现的次数在[min,max]之间。
反向引用 ([a-z]{3})([0-9]).+\1\2 括号()可以记住子表达式的文本,元字符\1\2可以引用这些文本。从而这个正则可以匹配the9 the9
\ 转义:将元字符转义为普通字符 \. 表示一个普通的.
() 限制多选结构、分组、捕获文本 限制多选结构指的是和`
(?:…) 仅用于分组,但是不捕获

Perl #

使用正则匹配文本 #

  • Perl用$variable =~ m/regex/来判断一个正则表达式能否匹配某个字符串,m表示匹配match,可以省略,斜线用来标注正则表达式的边界。
  • 元字符是具有特殊意义的字符,各个语言中对元字符的定义并不是统一的。
  • Perl和其他流派的正则表达式提供了许多有用的简记法。
\b 作为单词起始和单词结束的元字符
\t 制表符
\n 换行符
\r 回车符
\s 任何空白字符,如空格符、制表符等
\S 除了\s外的任何字符
\w [a-zA-Z0-9_]
\W 除了\w外的任何符号
\d [0-9]
\D 除了\d外的任何符号
  • /i修饰符表示此测试不区分大小写。
  • (?:...)表示可以用来分组文本,但是不捕获。
  • 匹配成功后,Perl可以使用$1 $2 $3 之类的变量来保存相对应的(...)括号内的子表达式匹配的文本。
  • 子表达式的编号按照小括号出现的先后排序,从1开始,子表达式可以嵌套。
# perl温度转换
print "enter a temperature(e.g., 32F, 100C):\n";
$input = <STDIN>;	#接收用户输入
chomp($input);	#去掉输入末尾的换行
if ($input =~ m/^([-+]?[0-9]+(\.[0-9]*)?)\s*([CF])$/i)
{
	$InputNum = $1;
	$type = $3;
	if ($type =~ m/c/i) {
		#输入的是摄氏温度,计算华氏温度
		$celsius = $InputNum;
		$fahrenheit = ($celsius * 9 / 5) + 32;
	} else {
		#输入的是华氏温度,计算摄氏温度
		$fahrenheit = $InputNum;
		$celsius = ($fahrenheit - 32) * 5 / 9;
	}
	printf "%.2f C is %.2f F\n", $celsius, $fahrenheit;
} else {
	print "expecting a number followed by \"C\" or \"F\",\n";
	print "so I don't understand \"$input\" .\n";
}

使用正则修改文本 #

  • $var =~ s/regex/replacement/ 当正则表达式能够匹配$var中的某段文本,则将这段文本替换为replacement。此替换只发生一次。
  • /g用于在s/.../.../第一次替换完成后继续搜索更多的匹配文本,进行更多的替换。

环视功能 #

  • 环视是在文本的特定位置上匹配左边或者右边的文本,但是不会占用字符,类似\b ^ $,但是更加通用。
介绍
(?=...)肯定顺序环视 在当前位置向右查看文本,尝试匹配子表达式,如果能匹配,就返回匹配成功信息。
(?!...)否定顺序环视 在当前位置向右查看文本,尝试匹配子表达式,如果不能够匹配,就返回成功信息。
(?<=...)肯定逆序环视 在当前位置向左查看文本,尝试匹配子表达式,如果匹配成功,就返回匹配成功信息。
(?<!...)否定逆序环视 在当前位置向左查看文本,尝试匹配子表达式,如果不能匹配,就返回成功信息。

匹配原理 #

优先选择最左边的匹配结果 #

  • 起始位置最靠左的匹配结果总是优于其他可能的匹配结果。
  • 匹配会从需要查找的字符串的起始位置开始尝试匹配,在起始位置测试正则表达式不能匹配后,就从第二个字符开始测试匹配,直到找到能匹配成功的情况或到了字符串的最后一个字符。
正则:fat|cat|belly|your
文本:the dragging belly indicates that your cat is too fat
结果:belly

标准量词?、*、+、{min,max}是优先匹配的。 #

  • 例:^.*([0-9][0-9])匹配abot24characterslong的过程。
    • .*匹配整个字符串以后,第一个[0-9]的匹配要求.*吐出来一个字符g,但这并不能让[0-9]匹配,所以.*必须继续吐字符,接下来的字符是n,如此循环15次,直到.*吐出来了4
    • 即使第一个[0-9]能匹配4,但是第二个[0-9]仍然不能匹配,为了匹配正则表达式,[.*]必须再次释放一个字符,这次是2,第一个由[0-9]匹配,4能够由[0-9]匹配,所以刺配成功,\1的值是24

传统NFA的多选结构是匹配优先的 #

  • 传统NFA遇到多选结构是,会按照从左到右的顺序检查表达式中的多选分支。如^(subject|date):*,当遇到此选择分支时,首先尝试匹配subject,如果可以匹配,就匹配接下来的:*。如果无法匹配,就尝试其他多选分支(尝试匹配date)。即多选结构既不是匹配优先,也不是忽略优先,而是按照多选结构的顺序。
  • tour|to|tournament来匹配three tournaments时会得到什么呢?

回溯 #

  • NFA最重要的性质是,它会依次处理各个子表达式或组成元素,需要在两个可能成功的可能中进行选择的时候,它会选择其一,同时记住另一个,以备稍后可能的需要。
  • 面对多个选择时,选择哪个分支呢?如果在进行尝试和跳过尝试之间选择,对于匹配优先量词,引擎会优先选择进行尝试,而对于忽略优先量词,会选择跳过尝试。
  • 当发生回溯时,距离当前最近存储的选项就是当本地失败强制回溯返回的,使用的原则是LIFO(后进先出)。
  • 回溯不但需要重新计算正则表达式和文本的对应位置,也需要维护括号内的子表达式所匹配的文本的状态。

固化分组 #

  • 固化分组可能会放弃某些可能的路径,使用(?>...)
  • 使用固化分组与正常的匹配毫无差别,但是当匹配到固化分组结构之后,在固化分组中的所有备用状态都会被放弃。在固化分组匹配结束时,他已匹配的文本已经固化为一个单元,只能作为整体而保留或放弃。
  • (?>.*?)永远无法匹配任何字符。

占有优先量词?= *+ ++ {m,n}+ #

  • 占有优先量词和匹配优先量词很相似,但是占有优先量词从不归还已匹配的字符。
  • 占有优先量词和固化分组非常相似,如w++(?>w+)的匹配结果完全相同。

Log4j2

2022-01-07
java

log4j2文件加载顺序 #

  1. 检查是否配置了log4j2.configurationFile系统属性,如果设置了,就会尝试使用匹配文件后缀的ConfigurationFactory去加载配置。该配置不限制为本地文件,也可以包含URL。
  2. 在classpath路径下找log4j2-test.properties。
  3. 在classpath路径下找log4j2-test.ymal 或log4j2-test.yml文件。
  4. 在classpath路径下找log4j2-test.json或Log4j2.jsn文件。
  5. 在classpath路径下找log4j2-test.xml。
  6. 在classpath路径下找log4j2.properties文件。
  7. 在classpath路径下找log4j2.ymal或log4j2.yml文件。
  8. 在classpath路径下找log4j2.json或log4j2.jsn文件。
  9. 在classpath路径下找log4j2.xml文件。
  10. 如果没有找到配置文件,则会使用默认配置,将日志输出到控制台。