• 主页
  • 标签
所有文章 关于我

  • 主页
  • 标签

网页前端之HTML

2021-04-03

<HTML>

<html>标签相当于java类的大括号

HTML:(Hyper Text Markup Language)超文本标记语言,是用来写网页的,是设计页面的基础。

HTML是由标签所组成的语言,能展示超文本效果,由头和体组成:

1
2
3
4
5
6
<html>    
<head>
<title>标题</title>
</head>
<font color="red">需要展示给用户看的信息内容</font>
</html>

HTML文件的扩展名为html或者htm:htm是老的命名规范,html是新的

HTML文件由浏览器直接解析运行,无需编译,直接由上到下依次解析执行

HTML标签通常由开始标签和结束标签组成,开始标签和结束标签之间的内容叫做内容体。

没有内容体的标签叫做空标签。仅由一个标签组成,例如:<br/> 自关闭

HTML标签不区分大小写,为了方便阅读,建议小写

HTML标签是有属性的,格式为属性名=“属性值”,属性值用引号引起来(单引号双引号都可以,建议双引号)

HTML标签建议包裹嵌套,不建议交叉嵌套

注释

<!--HTML注释内容-->用于注释HTML源码,不在HTML效果中展示

<head>

<head>中存放的是网页的说明性内容

<meta>

是 HTML 语言头部的一个辅助性标签,我们可以定义页面编码语言、搜索引擎优化、自动刷新并指向新的页面、控制页面缓冲、响应式视窗等。·

<meta> 元素可提供有关页面的元信息(meta-information,元数据总是以名称/值的形式被成对传递的),比如针对搜索引擎和更新频度的描述和关键词。

<meta> 标签位于文档的头部,不包含任何内容。

<meta> 标签的属性定义了与文档相关联的名称/值对。

整体来看,HTML5之前,meta标签只有两个主要属性,分别是 name 属性和 http-equiv 属性。 HTML5新添加”charset”

1
2
3
4
5
6
7
8
9
10
11
<meta name="参数" content="参数值" />  
//name属性主要用于描述网页,对应属性是 content ,以便于搜索引擎机器人查找、分类(目前几乎所有的搜索引擎都使用网上机器人自动查找meta值来给网页分类)
这里的name参数可以是多种值,对应不同效果:
Keywords(关键字)
Description(简介)
robots(机器人向导) 用来告诉搜索机器人哪些页面需要索引,哪些页面不需要索引
参数为 all :默认值,文件将被检索,且页面上的链接可以被查询; 参数为 none :文件将不被检索,且页面上的链接不可以被查询; 参数为 index :文件将被检索; 参数为 follow :页面上的链接可以被查询; 参数为 noindex :文件将不被检索,但页面上的链接可以被查询; 参数为 nofollow :文件将被检索,但页面上的链接不可以被查询;
author(作者)
copyright(版权)
generator(编辑器)
revisit-after(网站重访)
1
<meta property="参数"  content="参数值"/>
1
2
3
4
5
6
7
8
9
10
<meta http-equiv="参数"  content="参数值"/>
//http-equiv类似于HTTP的头部协议,它回应给浏览器一些有用的信息,以帮助正确和精确地显示网页内容。与之对应的属性值为content,content中的内容其实就是各个参数的变量值
Expires(期限) 指定网页在缓存中的过期时间,一旦网页过期,必须到服务器上重新传输 必须使用GMT的时间格式,或者直接设为0(数字表示多久后过期)
Pragma(cache模式) 禁止浏览器从本地计算机的缓存中访问页面内容 网页不保存在缓存中,每次访问都刷新页面。这样设定,访问者将无法脱机浏览
Refresh(刷新)自动刷新并指向新页面 `<meta http-equiv="refresh"content="5; url=http://www.baidu.com/"/>`
Set-Cookie(cookie设定)
Window-target(显示窗口的设定) 强制页面在当前窗口以独立页面显示,可以用来防止别人在框架里调用你的页面
content-Type(显示字符集的设定)设定页面使用的字符集,一般不用
content-Language(显示语言的设定)
http-equiv="imagetoolbar" 指定是否显示图片工具栏,当为false代表不显示,当为true代表显示
1
<meta charset="UTF-8">

<link>

<link> 标签定义该文档与外部资源的关系。最常见的用途是链接样式表,也可以被用来创建站点图标(比如PC端的“favicon”图标和移动设备上用以显示在主屏幕的图标)

网站站点的小图标:

1
2
<link rel="icon" href="images/logo.png" type="image/png" sizes="1651*1024"/>  指定格式为icon,然后类型指定图片,href放资源位置,可以设置大小
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon" /> 指定格式为icon,类型指定为ico,href放资源位置

<script>

1、async 表示立即下载该脚本,但不妨碍页面中的其他操作(比如:下载其他资源或等待加载其他脚本),只对外部文件有效。
2、charset 属性与 src 属性一起使用,告诉浏览器用来编码这个 javascript 程序的字符集。它的值是任何一个 ISO 标准字符集编码的名称。由于大多数浏览器会忽略它的值,因此这个属性很少有人用。

3、defer 表示脚本可以延迟到文档完全被解析和显示后再执行。只对外部文件有效。
4、language 已废弃。 原来用于表示编写代码用的脚本语言,因大多数浏览器都会忽略这个属性,因此也没必要再用了。
5、src 表示包含要执行代码的外部文件。

6、type 可以看成language的替代属性;表示编写代码使用的脚本语言的内容类型。默认值为text/javascript

注意:
1、传统做法是将所有的<script>元素都放在页面的<head>元素中,但是这样页面响应就会比较慢(必须等到全部的Javascript代码都被下载、解析和执行完后,才能呈现页面内容)。现在的做法一般都是将Javascript引用放在<body>元素中页面内容的后面。

2、defer和asyncd的作用基本相似。定义defer=“defer”的脚本将延迟到浏览器遇到</html>标签后再执行,原则上是延迟脚本还是按照原来的顺序执行,但是实际上执行的顺序不一定按照指定顺序执行。但是HTML5(以后称为H5)中已经明确规定,defer属性只使用于外部脚本文件,因此支持H5的实现会忽略给嵌入脚本设置的defer属性。async也只适用于外部脚本文件,并告诉浏览器立即下载脚本文件。但与defer不同的是,它并不保证按照指定顺序执行,又称为异步脚本标签(各脚本文件相互独立,互不依赖)

<body>

分类:

1.单标签:在⾃身标签标识结束,主要应⽤场景为功能性标签

双标签:有成对的结束标识,主要应⽤场景为内容性标签

2.行标签:⼜名内联标签,内联标签⾃身不具备宽⾼,可以和其他元素保持在同一行。根据内容多少来占用行内空间,不会自动换行。例:<br>, <td>, <a>, <img>,<span>strong,u(下划线),em(强调),i(斜体),sub(下标),sup(上标)

块标签:又名块级标签,块标签可以设置宽⾼,不可以和其他元素保持在同一行,通常独⾃占据⼀⾏。以区域块方式出现,每个块标签独自占据一整行或多整行,块结束会自动换行。例:<h1>, <p>, <ul>, <table>,<div>li,dl,dt,dd,form,hr,pre

行内块级标签:可以和其他元素保持在在一行,还能能设置宽高。例:textarea,input,img,button

所谓的行级标签,块级标签,其实可以根据需要,在开发中通过css样式互相转换。即通过设置display属性,它的属性值中,inline(元素以行内标签进行展示),block(元素以块级标签进行展示),inline-block(元素以行内块级标签进行展示)

3.单一标签:单独出现,表示具体的功能或展示具体的内容

组合标签:配合使⽤,才能产⽣相应的内容与效果

<h>标题

标题(Heading)是通过 <h1> - <h6> 等标签进行定义的。

<h1> 定义最大的标题 <h6> 定义最小的标题

注意:

  1. 浏览器会自动地在标题的前后添加空行。
  2. 默认情况下,HTML 会自动地在块级元素前后添加一个额外的空行,比如段落、标题元素前后

<p>段落

用于展示效果中划分段落,并且自动在段前和段后自动加空行

align:段落内容的对齐方式,默认是left,内容居左;right,右;center,居中。

(这个将要分段的内容放在<p></p>中即可)

注意:

  1. 浏览器会自动地在段落的前后添加空行。(<p> 是块级元素)

  2. 使用空的段落标记 <p></p> 去插入一个空行是个坏习惯。用 <br /> 标签代替它!(但是不要用 <br /> 标签去创建列表。)

<br />换行

用于展示效果中换行

HTML源码中换行,浏览器解析时会自动忽略

(这个放在要换行的地方就行)

<font>字体

用于展示效果中修饰文字样式

<font 属性名=”属性值”> 文字</font>

size:控制字体大小。最小1~最大7

color:控制字体颜色.使用英文设置

face:控制字体类型。只能设置系统字库中存在的字体类型
这个font可以加载在文字中间,不一定非要在开始,从哪开始的字就是从哪变化)

<hr />分割线

水平线,多用于段落之间的分割

<pre>原样标签

原样标签会保留空格和换行符,显示原文本文字。格式化文本。预定义格式文本。

在该元素中的文本通常按照原文件中的编排,以等宽字体的形式展现出来,文本中的空白符(比如空格和换行符)都会显示出来。(紧跟在 <pre> 开始标签后的换行符也会被省略)

<section>段落标签

和p同为块标签,自动换行显示每一段,但是section不存在行距,p自带行距

注:section和P放在一起,p优先,使用行距

<div>块级的块标签

用于在效果中 定义一块,默认占满一行,进行内容的显示 (会自动换行,默认占满一行 适用于大量信息展示)

是块级元素,内容会独立占一行。它是可用于组合其他 HTML 元素的容器。<div> 元素没有特定的含义。除此之外,由于它属于块级元素,浏览器会在其前后显示折行。

如果与 CSS 一同使用,<div> 元素可用于对大的内容块设置样式属性。

<div> 元素的另一个常见的用途是文档布局。它取代了使用表格定义布局的老式方法。使用 <table> 元素进行文档布局不是表格的正确用法。<table> 元素的作用是显示表格化的数据

<span>行级的块标签

用于在效果中 一行上定义一个块,进行内容显示 (有多少内容就会占用多大空间 span不会自动换行 适用于少量信息展示,比如提示密码错误)

HTML <span> 元素是内联元素,行内标签,可用作文本的容器。

<span> 元素也没有特定的含义。

当与 CSS 一同使用时,<span> 元素可用于为部分文本设置样式属性

<ol> <li> 有序的列表标签

<ol></ol> 用于在效果中定义一个有序列表 (会表明每个条目的顺序)

列表条项目标签:<li></li> 用于在效果中定义一个列表的项目

<ul> <li> 无序的列表标签

<ul></ul> 用于在效果中定义一个无序列表 (使用最多)

<dl><dt><dd>项目列表标签

dl(定义列表,跟ul…li类似),dt(定义了定义列表中的项目),dd(定义描述项目的内容,跟dt一起搭配)

<sub> 下标

<sup> 上标

实体标签(转义字符)

空格 &nbsp; HTML源码中的多个空格,效果中最终会合并成一个

小于号 &lt;

大于号 &gt;

人民币 &yen;¥

版权 &copy;©

商标 &reg;®

<embed></embed>媒体标签

hidden : 设置隐藏插件是否隐藏

src :用于指定音乐的路径 例:<embed src="1.mp3" ></embed>

<marquee> 滚动标签 (已废弃)

用来插入一段滚动的文字。可以使用它的属性控制当文本到达容器边缘发生的事情

direction : 指定滚动的方向

scrollamount : 指定滚动的速度。

loop :指定滚动的次数

<a> 超链接标签

<a></a>用于在效果中定义一个可以点击跳转的链接
href:超链接跳转的路径

​ 内网本机路径:相对路径和绝对路径

​ 互联网路径:http://地址

​ 本页:默认跳转到本页

href : 用于指定链接的资源

target: 设置打开新资源的目标

_Blank 在独立的窗口上打开新资源

_self 在当前窗口打开新资源

file: file协议(文件协议)这种协议主要是用于搜索本地机器的资源文件的。 格式:file:///f:/图片/1.jpg

邮件 的协议: mailTo

迅雷的协议: thunder

超链接标签的作用:

  1. 可以用于链接资源。

  2. 锚点点位:首先编写一个锚点 ,锚点的格式: <a name="锚点名字"> 数据</a> ,再使用a标签 的herf属性连接到锚点出。 href=”#锚点的名字“

超链接正常工作:1. a标签中必须有内容 2.a标签必须有href属性

注意:a标签内容体,不仅仅是文字,也可以是其他内容,例如图片

a标签的href属性,不仅仅可以链接到html上,也可以链接到其他文件上。例如图片

<img> 图片标签

<img /> 用于在页面效果中展示一张图片
src:图片的路径。(必有属性)

路径的写法:内网路径:绝对路径:C:\javaweb001_html\img\c_1.jpg文件在硬盘上的具体位置 (不建议使用)

相对路径:../img/c_1.jpg 从引入者所在目录出发 ../表示上一层目录 ./表示当前目录 (建议使用)

互联网路径:http://www.baidu.com/xxx.jpg 必须在前面加上http://

​ width: 设置图片宽度

​ height: 设置图片高度

​ 宽度和高度的设置:

​ 默认单位是px,像素 “400” 固定的

​ 百分比设置:是父标签的百分比 “50%” 动态改变的

​ alt: 如果图片资源无法找到,那么就显示对应的文字对图片进行说明。

​ (这些属性放在img和/之间)

<table>表格标签

表格使用到的标签:

<table> 表格

<tr> 定义表格中的一行

<td> 在表格一行中定义单元格

<th> 表头 。默认的样式是居中,加粗。区别于普通单元格标签,里面的内容居中加粗了

<caption> 表格的标题

表格常用的属性:

​ border :设置表格的边框

​ width : 设置表格的宽度

​ height: 设置表格的高度

单元格合并:<td> <th>都有这个合并属性
colspan: 设置单元格占据指定的列数;跨列合并单元格 colspan=”n”

rowspan: 设置单元格占据指定的行数; 跨行合并单元格 rowspan=”n”

合并步骤:A:确定合并哪几个单元格,确定是跨行合并还是跨列合并

B:在第一个出现的单元格上书写合并单元格属性

C:合并几个单元格,属性值就写几

D:被合并的单元格必须删掉

<thead> 标签用于组合 HTML 表格的表头内容。

<thead> 元素应该与<tbody>和<tfoot>元素结合起来使用,用来规定表格的各个部分(表头、主体、页脚)。

通过使用这些元素,使浏览器有能力支持独立于表格表头和表格页脚的表格主体滚动。当包含多个页面的长的表格被打印时,表格的表头和页脚可被打印在包含表格数据的每张页面上。

<thead> 标签必须被用在以下情境中:作为 <table> 元素的子元素,出现在 <caption>、<colgroup> 元素之后,<tbody>、 <tfoot> 和 <tr> 元素之前。

<form>表单标签

表单标签的根标签是<form>标签

用来将用户输入的数据提交给服务器的程序 相当于一个WEB程序的入口

1.定义一个表单 <form>表单内容</form>

2.在表单中定义对应的表单输入项
<input /> 是一种自关闭的标签,用户可以在该标签上通过填写和选择进行数据的输入

​ type:设置该标签的种类

​ text:文本框 默认 不一定需要指定value值

​ password:密码框 内容为非明文

​ radio:单选框 在同一组内才具有单选效果(分组需要用到name属性,将name设置成相同的就变为一组)(单选框只能选择,不能输入 需要指定value属性,否则提交的都是on)(可以checked设置默认选中)

​ checkbox:复选框 在同一组内才具有复选效果

​ submit:提交按钮 用于控制表单提交数据(当表单提交后,会出现 ?参数列表 参数列表格式:参数1=参数值1&参数2=参数值2&参数3=参数值3 这些参数都是之前设置的name,参数值是value)

​ rest:重置按钮 用于将表单输入项恢复到默认状态

​ file:附件框 用于文件上传

​ hidden:隐藏域 一般用于提交时,服务器需要拿到,但是用户不需要看到的数据

​ button:普通按钮 需要和js的事件一起使用

​ name:单选框、复选框进行数据的分组;设置该标签对应的参数名

​ value:设置该标签对应的参数值;作为按钮的名字

​ checked:设置单选框/复选框的默认选中状态 值就是”cheked”

​ readonly:设置该标签的参数值只读,用户无法手动更改,但是数据可以正常提交 readonly=“readonly”

​ disabled:设置该标签不可用,参数值无法更改且参数值也无法提交

​ <select></select>选择框标签 定义一个选择框

​ name:设置该标签对应的参数名

​ multiple:设置该标签选项全部显示,并且可以进行多选提交,默认为单选

​ <option></option>选项标签 用于为一个选择添加一个选项

​ value:设置需要提交的参数值(一般选择的东西都需要指定的返回值value)

​ selected:设置选项的默认选中状态

注意事项:option的内容体一般是用来展示的 参数值应该是option的value属性值

​ <textarea></textarea>文本域标签

表单输入项标签之一,用户可以在该标签上通过输入进行数据的输入

name:设置该标签对应的参数名

文本域和文本框的区别:文本框不能换行,文本域可以

文本框参数值是value属性,文本域参数值是标签的内容体

提交表单

action:指定提交数据的地址.将数据提交到何处(提交的地方其实是?前面的地址) 默认提交到本页

​ 本机内网路径:相对路径: 绝对路径:

​ 互联网路径:http://

method:指定表单的提交方式.将数据以何种方式提交 默认为get 提交方式可定义为get或者post

get提交方式的特点:把数据拼接到地址栏上

post提交方式的特点:没有把数据拼接到地址栏上。请求体

get和post的区别:

​ get会把提交的参数列表拼接到了地址栏后面,post不会

​ get方式提交的数据敏感信息不安全,post提交的数据相对安全

​ get方式提交的数据量是有限的,post方式从理论上提交的数据量是无限大的。所以尽量使用post方式提交表单

提交表单的注意事项:

提交中文问题:为什么要用URL编码:(因为你可能提交的数据中包含&符号,后台无法正确切割)

URL编码解决方式:对特殊符号,中文进行编码,为了保证表单数据传递时能更好区分出name和value,保证数据传递的完整性

例子:中文的你好进行URL编码:

(1)你好,进行普通的编码,编码成字节数组(使用的是页面规定的字符集,例如utf-8)

(2)字节数组中的每一个元素都会从10进制转为16进制

(3)把已经转为16进制的字节数组,以%进行拼接,拼接出的字符串,就是URL编码后的结果

注意: 表单项的数据如果需要提交到服务器上面,那么表单项必须要有name的属性值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<body>
<form action="http://www.baidu.com" method="post"> <!-- 文本输入框 单 行- -> 用户名:<input name="userName" type="text"/><br/>
<!-- 密码框 -->
密码:<input name="password" type="password"/><br/>
<!-- 单选框 -->
性别: 男<input checked="true" value="man" name="sex" type="radio"/>
女<input name="sex" value="woman" type="radio"/><br/>
< !-- 下拉框 -->
来自的城市:<select name="city">
<option value="BJ">北京</option>
<option value="SH">上海</option>
<option value="GZ">广州</option>
<option value="SZ">深圳</option>
</select><br/>
<!-- 复选框 同一组的复选框name的属性值要一致 -->
兴趣爱好:java <input value="java" name="hobit" checked="checked" type ="checkbox" />
javascript <input type="checkbox" value="javascript" name="hobit" />
android <input value="android" name="hobit" type="checkbox" /><br/> <!-- 文件上传框-->
大头照:<input name="image" type="file" /><br/>
个人简介:
<!-- 文本域 -->
<textarea name="intro" rows="10" cols="30"></textarea><br/>
<!-- 提交按钮 -->
<input type="submit" value="注册"/>
<!-- 重置按钮 -->
<input type="reset" value="重置"/>
</form>
</body>

<figure> 标签

代表一段独立的内容, 经常与说明(caption) <figcaption> 配合使用, 并且作为一个独立的引用单元。当它属于主内容流(main flow)时,它的位置独立于主体。这个标签经常是在主文中引用的图片,插图,表格,代码段等等,当这部分转移到附录中或者其他页面时不会影响到主体

<template>标签

模板元素

1.标签内容隐藏性: 自带display:none

2.标签位置任意性:类似<script>或者<style>标签,可以在<head>中,也可以在<body>或者<frameset>中

3.childNodes无效性:肉眼看上去是<template>标签里面还有很多子标签,但是template.childNodes无效,可以使用template.innerHTML获取完整的HTML片段。template.content会返回一个文档片段,可以理解为另外一个document,然后,使用document下的一些查询API就可以获得<template>标签里面的“伪子元素”了

<iframe>标签

iframe 元素会创建包含另外一个文档的内联框架(即行内框架)。可以用来在一个网页内引用另一个网页,网页的内容资源用src。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#fh5co-menu {
background: ;
position: absolute; 绝对的
top: 0;
z-index: 999;
width: 100%;
padding-bottom: 20px;
-webkit-transition: 0.5s;
-o-transition: 0.5s;
transition: 0.5s;
-webkit-border-radius: 0px;
-moz-border-radius: 0px;
-ms-border-radius: 0px;
border-radius: 0px;
margin-top: 20px;
}


//控制背景随动
#fh5co-clients {
padding: 2em 0;
background: #4fd2c2;
position: relative;
}


#fh5co-clients1 {
padding: 2em 0;
background: #4fd2c2;
position: relative;

}
@media screen and (max-width: 992px) {
#fh5co-clients1 .fh5co-client {
margin-bottom: 30px;
}
}
@media screen and (max-width: 768px) {
#fh5co-clients1 .fh5co-client {
margin-bottom: 30px;
}
}
@media screen and (max-width: 480px) {
#fh5co-clients1 .fh5co-client img {
max-width: 100%;
}
}

定位

长度单位

px 像素(Pixel),相对长度单位。像素px是相对于显示器屏幕分辨率而言的。
em 是相对长度单位,相对于当前对象内文本的字体尺寸。如当前对行内文本的字体尺寸未被人为设置,则相对于浏览器的默认字体尺寸。

ex 相对长度单位。相对于字符“x”的高度。此高度通常为字体尺寸的一半。如当前对行内文本的字体尺寸未被人为设置,则相对于浏览器的默认字体尺寸。
% 相对长度单位。相对于浏览器窗口的大小

CSS绝对长度单位 说明
in 英寸Inches (1 英寸 = 2.54 厘米)
cm 厘米Centimeters
mm 毫米Millimeters
pt 点Points (1点 = 1/72英寸)
pc 皮卡Picas (1 皮卡 = 12 点)

PX和PT转换的公式: pt=px乘以3/4。比如12px×3/4=9pt大小。
PX和em转换的公式: em=16乘以px,也就是说1.5em=1.5×16=24px

用px来定义字体,就无法用浏览器字体放大的功能。

任意浏览器的默认字体高都是16px。所有未经调整的浏览器都符合:1em=16px。那么12px=0.75em,10px=0.625em。为了简化font-size的换算,需要在css中的body选择器中声明Font-size=62.5%,这就使em值变为16px*62.5%=10px,这样12px=1.2em,10px=1em,也就是说只需要将你的原来的px数值除以10,然后换上em作为单位就行了。
◆CSS中em属性有如下特点

1.em的值并不是固定的;

2.em会继承父级元素的字体大小。

◆所以我们在写CSS的时候,需要注意

1.body选择器中声明Font-size=62.5%;

2.将你的原来的px数值除以10,然后换上em作为单位;

3.重新计算那些被放大的字体的em数值。避免字体大小的重复声明。

也就是避免1.2*1.2=1.44的现象。比如说你在#content中声明了字体大小为1.2em,那么在声明p的字体大小时就只能是1em,而不是1.2em,因为此em非彼em,它因继承#content的字体高而变为了1em=12px。

但是12px汉字例外,就是由以上方法得到的12px(1.2em)大小的汉字在IE中并不等于直接用12px定义的字体大小,而是稍大一点。这个问题Jorux已经解决,只需在body选择器中把62.5%换成63%就能正常显示了。原因可能是IE处理汉字时,对于浮点的取值精确度有限。不知道有没有其他的解释

position: absolute;的元素会相对于第一个设置了position: relative;的祖先元素进行定位,将assistor设置为position: reletive;,滚动条是在parent中的,position: fixed;和parent内的内容滚动就都实现了。

z-index 属性来控制这些框的堆放次序

响应式布局(PC端适配手机端)

只开发一个网页,通过检测视口分辨率,针对不同客户端在客户端做代码处理,来展现不同的布局和内容。可以适配任何设备,兼容多个终端。

响应式开发的原理是使用CSS3中的Media Query(媒体查询)针对不同宽度的设备设置不同的布局和样式,从而适配不同的设备

使用 @media 查询,你可以针对不同的媒体类型定义不同的样式。@media 可以针对不同的屏幕尺寸设置不同的样式,特别是如果你需要设置设计响应式的页面,@media 是非常有用的。当你重置浏览器大小的过程中,页面也会根据浏览器的宽度和高度重新渲染页面

设备的划分情况为:

  • 小于768的为超小屏幕(手机)
  • 768~992之间的为小屏设备(平板)
  • 992~1200的中等屏幕(桌面显示器)
  • 大于1200的宽屏设备(大桌面显示器)

屏幕宽度低于480像素的设备(如iPhone等)

1.在网站HTML文件的开头,增加viewport meta标签告诉浏览器视口宽度等于设备屏幕宽度,且不进行初始缩放:

<meta name="viewport" content="width=device-width, initial-scale=1" />

1
2
3
4
5
6
width:控制 viewport 的大小,可以指定的一个值,如 600,或者特殊的值,如 device-width为设备的宽度(单位为缩放为 100% 时的 CSS 的像素)。 
height:和 width 相对应,指定高度。也可以指定为 window.screen.height
initial-scale:初始缩放比例,也即是当页面第一次 load 的时候缩放比例。
maximum-scale:允许用户缩放到的最大比例。
minimum-scale:允许用户缩放到的最小比例。
user-scalable:用户是否可以手动缩放。

2.在CSS文件尾部增加针对不同屏幕分辨率的规则:

1
2
3
4
5
6
7
8
@media screen and (max-device-width: 720px) {

}

media=”screenand(max-device-width:400px)” href=”tinyScreen.css”/>
如果屏幕宽度小于400像素(max-device-width:400px),就加载tinyScreen.css文件 
media=”screenand(min-width:400px)and(max-device-width:600px)” href=”smallScreen.css”/>
如果屏幕宽度在400像素到600像素之间,则加载smallScreen.css文件  

3.布局宽度使用相对宽度。网页总体框架可以使用绝对宽度,但往下的内容框架、侧栏等最好使用相对宽度,这样针对不同分辨率进行修改就方便。当然也可以不用相对宽度,那就需要在 @media screen and (max-device-width: 480px) 里面增加各个div的针对小屏幕的宽度,实际上更麻烦

4.在HTML页面上不要使用绝对字体(px),而要使用相对字体(em),对于大多数浏览器来说,通常用 em = px/16 换算,例如16px就等于1em

PC站和手机站匹配最常用的几种方法是移动适配、JS跳转、Meta声明、302规则等等

1、移动适配

现在很多搜索引擎都提供移动适配的功能,例如百度的开放适配和360的移动适配,利用百度站长工具提交PC页-手机页对应关系Sitemap,或者利用360站长工具提交PC页-手机页对应关系txt就能实现PC站内容和手机站一一适配。 这样做的优点是只需要在搜索引擎工具提交资料,无需对站点本事做改动,而且网站内容一一对应,并不只局限于网站首页。 缺点就是只能在百度或者360移动搜索中生效,其他没用提交适配文件的搜索中就无效了。

2、JS跳转

JS跳转对于某些行业来说有着重要的作用,尤其是对于用户转换率网站首页较高的网站,只需要在网站的头部加一段JS代码判定是否是移动端访问,然后所有的流量就会全部流向手机站的主页。 JS跳转的优点是适用于所以的移动搜索,并不局限于百度和360,可以提高用户的转换率。 缺点是难以实现每个页面的一一对应,而且容易被某些搜索引擎判定作弊,从而受到惩罚。

3、Meta声明

Meta声明的格式一般为,站长可以将这段代码加在PC站的头部,由Meta信息来指明该PC页对应的手机页的URL,以及该URL对应页面的格式。 Meta声明的优点是代码简单易懂,操作方便。 缺点是只能在百度移动搜索中生效,对于其他搜索引擎无效。

4、302规则

302规则指的是,当判定移动端访问网站时,302临时跳转到一个网址(手机站),例如可以在htaccess文件里加上一段判定手机的代码,做个302重定向。 这样做的好处是,不需要再额外在网站的前端代码中加任何东西,也不需要向搜索引擎提交规则,可是据测试,有可能会导致PC站被降权。 虽然302是暂时性的重定向,但过于频繁的302是很有可能给网站带来不利的影响的,具体的尺度需要站长自己去把握。

以上4种适配方法是最为常见的操作方法,通常PC站做了以上处理后,一段时间以后移动端搜索出来的结果,下面匹配的网址就变成了移动站网址,不过标题还是PC站标题。 至于到底哪种方法更好,更倾向于第一种,毕竟百度移动搜索占据着网站的大部分流量,而且用户体验度也最好。

轮播图

轮播图的样式有很多,可以选择一个框架,然后将代码复制下来,将用到的样式放到自己的文件夹里,这样就可以做成一个轮播图。

在layui中,如果想让轮播图完成在放大缩小的过程中保证图片随动,目前解除了两种方法:

1.写一个响应式布局的css样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#fh5co-clients {
padding: 2em 0;
background: #4fd2c2;
position: relative;
}
#fh5co-clients {
margin-bottom: 0px;
}
@media screen and (max-width: 992px) {
#fh5co-clients {
margin-bottom: 30px;
}
}
@media screen and (max-width: 768px) {
#fh5co-clients{
margin-bottom: 30px;
}
}
@media screen and (max-width: 480px) {
#fh5co-clients img {
max-width: 100%;
}
}

2.写一个js方法,会不断刷新,影响体验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 var b = 1768/881;//这里的图片数值是根据自己的图片指定
//获取浏览器宽度
var W = $(window).width();
var H = $(window).height();
layui.use('carousel', function(){
var carousel = layui.carousel;
//建造实例
carousel.render({
elem: '#test1'
,width: '100%' //设置容器宽度
,height: (W/b).toString()+"px" //按比例和浏览器可视页面宽度来获取高度
// ,arrow: 'always' //始终显示箭头
//,anim: 'updown' //切换动画方式
,indicator: 'none'
});
});
$(window).resize(function () {
// setBanner();
window.location.reload() //每当设置后,进行刷新

})

网页调试技巧

1.F12开发模式,左上角图标有两个:箭头图标可以选择元素,就会指示当前指向的元素,可以注意数值多大,方便知道这个标签管辖的范围。手机图标,点击之后可以模拟手机端查看网页,也可以通过拖动浏览器大小模拟查看。

2.开发者模式下,elements中选择了某一行后,可以在style中查看生效的样式,不生效的样式被划掉了,没有写过的样式不会显示。可以在样式中手动编辑数值模拟调试(比改一处看一处方便一点)

  • HTML

展开全文 >>

nginx配置

2021-03-30

Nginx (“engine x”) 是一个高性能的HTTP和反向代理服务器,也是一个IMAP/POP3/SMTP服务器。Nginx是由Igor Sysoev为俄罗斯访问量第二的Rambler.ru站点开发的,第一个公开版本0.1.0发布于2004年10月4日。其将源代码以类BSD许可证的形式发布,因它的稳定性、丰富的功能集、示例配置文件和低系统资源的消耗而闻名。2011年6月1日,nginx 1.0.4发布。

Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,并在一个BSD-like 协议下发行。由俄罗斯的程序设计师Igor Sysoev所开发,供俄国大型的入口网站及搜索引擎Rambler(俄文:Рамблер)使用。其特点是占有内存少,并发能力强,事实上nginx的并发能力确实在同类型的网页服务器中表现较好,中国大陆使用nginx网站用户有:百度、京东、新浪、网易、腾讯、淘宝等。

Nginx 是开源、高性能、高可靠的 Web 和反向代理服务器,而且支持热部署,几乎可以做到 7 * 24 小时不间断运行,即使运行几个月也不需要重新启动,还能在不间断服务的情况下对软件版本进行热更新。性能是 Nginx 最重要的考量,其占用内存少、并发能力强、能支持高达 5w 个并发连接数,最重要的是, Nginx 是免费的并可以商业化,配置使用也比较简单

优点:Nginx 可以在大多数 UnixLinux OS 上编译运行,并有 Windows 移植版。 Nginx 的1.4.0稳定版已经于2013年4月24日发布,一般情况下,对于新建站点,建议使用最新稳定版作为生产版本,已有站点的升级急迫性不高。Nginx 的源代码使用 2-clause BSD-like license。

Nginx 是一个很强大的高性能Web和反向代理服务器,它具有很多非常优越的特性:

在连接高并发的情况下,Nginx是Apache服务器不错的替代品:Nginx在美国是做虚拟主机生意的老板们经常选择的软件平台之一。能够支持高达 50,000 个并发连接数的响应,感谢Nginx为我们选择了 epoll and kqueue作为开发模型。

  • 高并发、高性能;
  • 模块化架构使得它的扩展性非常好;
  • 异步非阻塞的事件驱动模型这点和 Node.js 相似;
  • 相对于其它服务器来说它可以连续几个月甚至更长而不需要重启服务器使得它具有高可靠性;
  • 热部署、平滑升级;
  • 完全开源,生态繁荣

意义:互联网飞速发展的今天,大用户量高并发已经成为互联网的主体.怎样能让一个网站能够承载几万个或几十万个用户的持续访问呢?这是一些中小网站急需解决的问题。用单机tomcat搭建的网站,在比较理想的状态下能够承受的并发访问量在150到200左右。按照并发访问量占总用户数量的5%到10%这样计算,单点tomcat网站的用户人数在1500到4000左右。对于一个为全国范围提供服务的网站显然是不够用的,为了解决这个问题引入了负载均衡方法。负载均衡就是一个web服务器解决不了的问题可以通过多个web服务器来平均分担压力来解决,并发过来的请求被平均分配到多个后台web服务器来处理,这样压力就被分解开来。

负载均衡服务器分为两种:一种是通过硬件实现的负载均衡服务器,简称硬负载例如:f5。另一种是通过软件来实现的负载均衡,简称软负载:例如apache和nginx。硬负载和软负载相比前者作用的网络层次比较多可以作用到socket接口的数据链路层对发出的请求进行分组转发但是价格成本比较贵,而软负载作用的层次在http协议层之上可以对http请求进行分组转发并且因为是开源的所以几乎是0成本,并且阿里巴巴,京东等电商网站使用的都是Nginx服务器。

应用场景:

1.http服务器,可以做静态网页的http服务器。

2.配置虚拟机。一个域名可以被多个ip绑定。可以根据域名的不同请求转发给运行在不同端口的服务器。

3.反向代理,负载均衡。把请求转发给不同的服务器。

Nginx 的最重要的几个使用场景:

  1. 静态资源服务,通过本地文件系统提供服务;
  2. 反向代理服务,延伸出包括缓存、负载均衡等;
  3. API 服务, OpenResty ;

对于前端来说 Node.js 并不陌生, Nginx 和 Node.js 的很多理念类似, HTTP 服务器、事件驱动、异步非阻塞等,且 Nginx 的大部分功能使用 Node.js 也可以实现,但 Nginx 和Node.js 并不冲突,都有自己擅长的领域。Nginx 擅长于底层服务器端资源的处理(静态资源处理转发、反向代理,负载均衡等), Node.js 更擅长上层具体业务逻辑的处理,两者可以完美组合

正向代理隐藏的是客户端

反向代理隐藏的是服务器端

Nginx安装

官方网站:http://nginx.org/

Nginx在windows下安装:

img

执行exe文件,在浏览器中访问localhost:80

img

Nginx在linux下安装:

1.环境要求:

nginx是C语言开发,建议在linux上运行,本教程使用Centos6.4作为安装环境。

l gcc

​ 安装nginx需要先将官网下载的源码进行编译,编译依赖gcc环境,如果没有gcc环境,需要安装gcc:

*yum install gcc-c++*

l PCRE

​ PCRE(Perl Compatible Regular Expressions)是一个Perl库,包括 perl 兼容的正则表达式库。nginx的http模块使用pcre来解析正则表达式,所以需要在linux上安装pcre库。

*yum install -y pcre pcre-devel*

注:pcre-devel是使用pcre开发的一个二次开发库。nginx也需要此库。

l zlib

​ zlib库提供了很多种压缩和解压缩的方式,nginx使用zlib对http包的内容进行gzip,所以需要在linux上安装zlib库。

*yum install -y zlib zlib-devel*

l openssl

​ OpenSSL 是一个强大的安全套接字层密码库,囊括主要的密码算法、常用的密钥和证书封装管理功能及SSL协议,并提供丰富的应用程序供测试或其它目的使用。

​ nginx不仅支持http协议,还支持https(即在ssl协议上传输http),所以需要在linux安装openssl库。

*yum install -y openssl openssl-devel*

****2.****编译及安装

第一步:把nginx的源码包上传至linux服务器

第二步:解压源码包。 tar -zxf nginx-1.8.0.tar.gz

第三步:进入nginx-1.8.0文件夹。

第四步:使用configure命令创建makefile。参数设置如下:

./configure \

–prefix=/usr/local/nginx \

–pid-path=/var/run/nginx/nginx.pid \

–lock-path=/var/lock/nginx.lock \

–error-log-path=/var/log/nginx/error.log \

–http-log-path=/var/log/nginx/access.log \

–with-http_gzip_static_module \

–http-client-body-temp-path=/var/temp/nginx/client \

–http-proxy-temp-path=/var/temp/nginx/proxy \

–http-fastcgi-temp-path=/var/temp/nginx/fastcgi \

–http-uwsgi-temp-path=/var/temp/nginx/uwsgi \

–http-scgi-temp-path=/var/temp/nginx/scgi

*注意:上边将临时文件目录指定为*****/var/temp/nginx**,需要在**/var**下创建**temp**及**nginx*****目录*

*mkdir -p /var/temp/nginx*

第五步:make

第六步: make install

img

以上操作完成后,进入/usr/local/nginx目录,ll

img

conf它里面装入的是nginx相关的配置文件

html目录 它里面装入的html代码

sbin目录它里面有一个nginx (这个nginx其实就相当于是windows系统的exe)

想要启动nginx只需要执行bin目录下的nginx命令就可以

img

Nginx服务在启动时会启动两个服务

img此时如果想检测访问的话,去浏览器输入:192.168.19.128:80

使用 yum 安装 Nginx :

1
yum install nginx -y

安装完成后,通过 rpm \-ql nginx 命令查看 Nginx 的安装信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Nginx配置文件
/etc/nginx/nginx.conf # nginx 主配置文件
/etc/nginx/nginx.conf.default

# 可执行程序文件
/usr/bin/nginx-upgrade
/usr/sbin/nginx

# nginx库文件
/usr/lib/systemd/system/nginx.service # 用于配置系统守护进程
/usr/lib64/nginx/modules # Nginx模块目录

# 帮助文档
/usr/share/doc/nginx-1.16.1
/usr/share/doc/nginx-1.16.1/CHANGES
/usr/share/doc/nginx-1.16.1/README
/usr/share/doc/nginx-1.16.1/README.dynamic
/usr/share/doc/nginx-1.16.1/UPGRADE-NOTES-1.6-to-1.10

# 静态资源目录
/usr/share/nginx/html/404.html
/usr/share/nginx/html/50x.html
/usr/share/nginx/html/index.html

# 存放Nginx日志文件
/var/log/nginx

主要关注的文件夹有两个:

1. /etc/nginx/conf.d/ 是子配置项存放处, /etc/nginx/nginx.conf 主配置文件会默认把这个文件夹中所有子配置项都引入;

2. /usr/share/nginx/html/ 静态文件都放在这个文件夹,也可以根据你自己的习惯放在其他地方;

Nginx常用命令:

l 关闭nginx需要使用:(暴力)

nginx -s stop 相当于找到nginx进程kill。

l 退出命令:(温和)

nginx -s quit

等程序执行完毕后关闭,建议使用此命令。

l 重新加载配置文件:(重启)

nginx -s reload 可以不关闭nginx的情况下更新配置文件

systemctl 系统命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 开机配置
systemctl enable nginx # 开机自动启动
systemctl disable nginx # 关闭开机自动启动

# 启动Nginx
systemctl start nginx # 启动Nginx成功后,可以直接访问主机IP,此时会展示Nginx默认页面

# 停止Nginx
systemctl stop nginx

# 重启Nginx
systemctl restart nginx

# 重新加载Nginx
systemctl reload nginx

# 查看 Nginx 运行状态
systemctl status nginx

# 查看Nginx进程
ps -ef | grep nginx

# 杀死Nginx进程
kill -9 pid # 根据上面查看到的Nginx进程号,杀死Nginx进程,-9 表示强制结束进程

Nginx 应用程序命令:

1
2
3
4
5
6
nginx -s reload # 向主进程发送信号,重新加载配置文件,热重启
nginx -s reopen # 重启 Nginx
nginx -s stop # 快速关闭
nginx -s quit # 等待工作进程处理完成后关闭
nginx -T # 查看当前 Nginx 最终的配置
nginx -t # 检查配置是否有问题

Nginx配置与应用

1.Nginx虚拟机配置

主要是在描述nginx它是一个http服务器。它是apache的一个替代品。

对于nginx虚拟机配置主要可以从三个方面入手:

\1. ip配置

\2. 域名配置

\3. 端口配置

它的配置主要体现在nginx/conf/nginx.conf,这个文件中#都是注释

img

测试针对ip地址进行配置:复制一个server,将其中的server-name改为自己电脑的ip地址,可以选择性的对location中内容进行修改,实现不同的ip地址和域名访问不同的资源的效果。重启nginx

img

测试针对于域名进行配置

域名作用:可以方便记忆。

问题:为什么通过域名可以访问到网站。

答:DNS服务器—-域名解析服务器。

在windows或linux上都有一个hosts文件,它是一个本地域名解析文件。

windows上在c:\Windows\System32\drivers\etc

linux上在/etc/hosts

linux系统中nginx/conf/nginx.conf,或者windows中config下的nginx.conf文件。修改server-name为一个域名。重启nginx

img

测试针对于端口进行配置

改变listen为8001,就可以根据不同的端口访问不同的资源

img

2.Nginx作反向代理

需要在conf文件中添加反向代理的配置(当访问localhost时,默认端口为80,会跳转到server_list进而访问到tomcat的localhost:8080)如果出错了,可以查看nginx的logs文件,每次改完配置文件后,都需要重启nginx服务器。

img

完成简单的tomcat集群(这个配置有很多错误):在server_list中继续添加tomcat服务器(这里的8081是修改端口号的tomcat文件的副本)

img

3.Nginx负载均衡

所谓的负载均衡简单说就是将一台服务原来承受的压力由多台服务器来分配,可以在nginx中实现tomcat集群,因为不同服务器的性能可能不一样,所以可以通过weight来分配权重,权重越大代表使用的越多

img

4.Nginx+tomcat集群+redis实现session共享

Session共享问题演示:

1)在tomcat中创建项目myweb,这个网页中得到sessionid值

2)分别启动两个tomcat,查看sessionid值

Tomcat_main

img

Tomcat_back

img

Session共享问题解决:

思想:将原来由每一个tomcat管理的session统一存储到redis中管理

1)下载nginx+tomcat集群+redis实现session共享工具jar包

https://github.com/jcoleman/tomcat-redis-session-manager/downloads

其它依赖包

img

最好使用这四个jar包,因为其他版本的jar包可能会存在冲突

将以上四个包copy到tomcat的lib目录下(集群中的所有Tomcat都需要有这四个jar)

2)在tomcat/conf/context.xml文件中添加配置(集群中的所有Tomcat都需要配置)

<Manager className=”com.radiadesign.catalina.session.RedisSessionManager”

​ host=”192.168.19.128”

​ port=”6379”

​ database=”0”

​ maxInactiveInterval=”60”

​

​ password=”admin”

​ />

重启tomcat,nginx,启动redis数据库

3)查看myweb工具中sessionid

img

img

Redis帮助我们存储了session

img

*配置Tomcat的session共享可以有三种解决方案:*

1.以负载均衡服务器本身提供的session共享策略,每种服务器的配置是不一样的并且nginx本身是没有的

2.利用web容器本身的session共享策略来配置共享。针对于weblogic这种方式还是靠谱的。但是针对于tomcat这种方式存在很大的缺陷,主要因为是依靠广播方式来实现的session复制,会浪费很多带宽导致整个网络反映缓慢。官网也建议这种方式最好不要超过4台tomcat,具体的内容可参考/webapps/docs/cluster-howto.html里面有详细的说明

3.*Tomcat集群+redis的Session共享配置方法*

*配置tomcat中的session共享:*

*步骤一:修改server.xml文件,最简单的集群配置只需要将节点中注释掉的下面这句取消注释即可:*

Xml代码:

使用这样方法配置的集群会将Session同步到所在网段上的所有配置了集群属性的实例上(此处讲所在网段可能不准确,是使用Membership 的address和port来区分的。tomcat集群的实例如果在Membership配置中有相同的address和port值的tomcat被分到同一个集群里边。他们的session是相互共享的,同一个session的集群被称为一个cluster。可以配置多个cluster,但是cluster和cluster之间的session是不共享的)。也就是说如果该广播地址下的所有Tomcat实例都会共享Session,那么假如有几个互不相关的集群,就可能造成Session复制浪费,所以为了避免浪费就需要对节点多做点设置了,如下:

Xml代码

​

​ <Membership className=”org.apache.catalina.tribes.membership.McastService”

​ address=”228.0.0.4”

​ port=”45564”

​ frequency=”500”

​ dropTime=”3000”/>

​

加了一个Channel,里面包了个Membership,咱们要关注的就是membership的port属性和address属性,不同的集群设置不同的port值或address值,从目前的使用来看,基本上是隔离开了。

*步骤二:修改项目的web.xml文件:*

web.xml文件的修改很简单:只需要在节点中添加这个节点就可以了。

OK,有了这二步就实现了Tomcat的集群和Session的共享了

nginx的配置

main(全局设置)、server(主机设置)、upstream(负载均衡服务器设置)和 location(URL匹配特定位置的设置)。

main块设置的指令将影响其他所有设置;
server块的指令主要用于指定主机和端口;
upstream指令主要用于负载均衡,设置一系列的后端服务器;
location块用于匹配网页位置。

这四者之间的关系式:server继承main,location继承server,upstream既不会继承其他设置也不会被继承。
在这四个部分当中,每个部分都包含若干指令,这些指令主要包含Nginx的主模块指令、事件模块指令、HTTP核心模块指令,同时每个部分还可以使用其他HTTP模块指令,例如Http SSL模块、HttpGzip Static模块和Http Addition模块等。

全局配置

1
2
3
4
5
6
7
8
user nobody nobody;  #用户及组:用户 组   windows下不指定
worker_processes 2;
#worker_processes是个主模块指令,指定了Nginx要开启的进程数。每个Nginx进程平均耗费10M~12M内存。建议指定和CPU的数量一致即可,根据硬件调整,通常等于CPU数量或者2倍于CPU
error_log logs/error.log notice;
#error_log是个主模块指令,用来定义全局错误日志文件存放路径。日志输出级别有debug、info、notice、warn、error、crit可供选择,其中,debug输出日志最为最详细,而crit输出日志最少
pid logs/nginx.pid; #pid是个主模块指令,pid(进程标识符)用来指定进程pid的存储文件位置
worker_rlimit_nofile 65535;
#worker_rlimit_nofile用于绑定worker进程和CPU, Linux内核2.4以上可用。指定进程可以打开的最大描述符:数目。这个指令是指当一个nginx进程打开的最多文件描述符数目,理论值应该是最多打开文件数(ulimit -n)与nginx进程数相除,但是nginx分配请求并不是那么均匀,所以最好与ulimit -n 的值保持一致。现在在linux 2.6内核下开启文件打开数为65535,worker_rlimit_nofile就相应应该填写65535。这是因为nginx调度时分配请求到进程并不是那么的均衡,所以假如填写10240,总并发量达到3-4万时就有进程可能超过10240了,这时会返回502错误。

events事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#设定Nginx的工作模式及连接数上限:
events{
use epoll;
#use是个事件模块指令,用来指定Nginx的工作模式。Nginx支持的工作模式有select、poll、kqueue、epoll、rtsig和/dev/poll。其中select和poll都是标准的工作模式,kqueue和epoll是高效的工作模式,不同的是epoll用在Linux平台上,而kqueue用在FreeBSD系统中。对于Linux系统,epoll工作模式是首选。window下不指定
worker_connections 65536;
#worker_connections也是个事件模块指令,用于定义Nginx每个进程的最大连接数,默认是1024。最大客户端连接数由worker_processes和worker_connections决定,即Max_client=worker_processes*worker_connections。在作为反向代理时,max_clients变为:max_clients = worker_processes * worker_connections/4。进程的最大连接数受Linux系统进程的最大打开文件数限制,在执行操作系统命令“ulimit -n 65536”后worker_connections的设置才能生效
keepalive_timeout 60; #keepalive超时时间
client_header_buffer_size 4k;
#客户端请求头部的缓冲区大小。这个可以根据你的系统分页大小来设置,一般一个请求头的大小不会超过1k,不过由于一般系统分页都要大于1k,所以这里设置为分页大小。分页大小可以用命令`getconf PAGESIZE` 取得。但也有client_header_buffer_size超过4k的情况,但是client_header_buffer_size该值必须设置为“系统分页大小”的整倍数
open_file_cache max=65535 inactive=60s;
#这个将为打开文件指定缓存,默认是没有启用的,max指定缓存数量,建议和打开文件数一致,inactive是指经过多长时间文件没被请求后删除缓存。
open_file_cache_valid 80s;
#这个是指多长时间检查一次缓存的有效信息。
open_file_cache_min_uses 1;
#open_file_cache指令中的inactive参数时间内文件的最少使用次数,如果超过这个数字,文件描述符一直是在缓存中打开的,如上例,如果有一个文件在inactive时间内一次没被使用,它将被移除。
}

HTTP服务器配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
http{
include conf/mime.types;
#include是个主模块指令,实现对配置文件所包含的文件的设定,可以减少主配置文件的复杂度。类似于Apache中的include方法。设定mime类型,类型由mime.type文件定义
default_type application/octet-stream;
#default_type属于HTTP核心模块指令,这里设定默认类型为二进制流,也就是当文件类型未定义时使用这种方式,例如在没有配置PHP环境时,Nginx是不予解析的,此时,用浏览器访问PHP文件就会出现下载窗口
log_format main '$remote_addr - $remote_user [$time_local] ' 用以记录客户端的ip地址 用来记录客户端用户名称 用来记录访问时间与时区
'"$request" $status $bytes_sent ' 用来记录请求的url与http协议 用来记录请求状态;成功是200 记录发送给客户端文件主体内容大小
'"$http_referer" "$http_user_agent" ' 用来记录从那个页面链接访问过来的 记录客户浏览器的相关信息
'"$gzip_ratio"';
log_format download '$remote_addr - $remote_user [$time_local] '
'"$request" $status $bytes_sent '
'"$http_referer" "$http_user_agent" '
'"$http_range" "$sent_http_content_range"';
##通常web服务器放在反向代理的后面,这样就不能获取到客户的IP地址了,通过$remote_add拿到的IP地址是反向代理服务器的iP地址。反向代理服务器在转发请求的http头信息中,可以增加x_forwarded_for信息,用以记录原有客户端的IP地址和原来客户端的请求的服务器地址
#log_format是Nginx的HttpLog模块指令,用于指定Nginx日志的输出格式。main为此日志输出格式的名称,用了log_format指令设置了日志格式之后,需要用access_log指令指定日志文件的存放路径。
access_log logs/host.access.log main;
access_log logs/host.access.404.log log404;

server_names_hash_bucket_size 128;
#保存服务器名字的hash表是由指令server_names_hash_max_size 和server_names_hash_bucket_size所控制的。参数hash bucket size总是等于hash表的大小,并且是一路处理器缓存大小的倍数。在减少了在内存中的存取次数后,使在处理器中加速查找hash表键值成为可能。如果hash bucket size等于一路处理器缓存的大小,那么在查找键的时候,最坏的情况下在内存中查找的次数为2。第一次是确定存储单元的地址,第二次是在存储单元中查找键值。因此,如果Nginx给出需要增大hash max size 或 hash bucket size的提示,那么首要的是增大前一个参数的大小

client_max_body_size 20m; #client_max_body_size用来设置允许客户端请求的最大的单个文件字节数;设定通过nginx上传文件的大小
client_header_buffer_size 32K; #client_header_buffer_size用于指定来自客户端请求头的headerbuffer大小。对于大多数请求,1K的缓冲区大小已经足够,如果自定义了消息头或有更大的Cookie,可以增加缓冲区大小。这里设置为32K
large_client_header_buffers 4 32k; #large_client_header_buffers用来指定客户端请求中较大的消息头的缓存最大数量和大小, “4”为个数,“128K”为大小,最大缓存量为4个128K
Sendfile on; #sendfile参数用于开启高效文件传输模式。将tcp_nopush和tcp_nodelay两个指令设置为on用于防止网络阻塞
tcp_nopush on; #此选项允许或禁止使用socke的TCP_CORK的选项,此选项仅在使用sendfile的时候使用
tcp_nodelay on;

keepalive_timeout 60; #keepalive_timeout设置客户端连接保持活动的超时时间。在超过这个时间之后,服务器会关闭该连接
client_header_timeout 10; #client_header_timeout设置客户端请求头读取超时时间。如果超过这个时间,客户端还没有发送任何数据,Nginx将返回“Request time out(408)”错误
client_body_timeout 10; #client_body_timeout设置客户端请求主体读取超时时间。如果超过这个时间,客户端还没有发送任何数据,Nginx将返回“Request time out(408)”错误,默认值是60
send_timeout 10; #send_timeout指定响应客户端的超时时间。这个超时仅限于两个连接活动之间的时间,如果超过这个时间,客户端没有任何活动,Nginx将会关闭连接

HttpGzip模块

这个模块支持在线实时压缩输出数据流。通过/opt/nginx/sbin/nginx -V命令可以查看安装Nginx时的编译选项

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@vps ~]# /opt/nginx/sbin/nginx  -V
nginx version: nginx/1.0.14
built by gcc 4.4.6 20110731 (Red Hat 4.4.6-3) (GCC)
configure arguments: --with-http_stub_status_module --with-http_gzip_static_module --prefix=/opt/nginx

gzip on; #gzip用于设置开启或者关闭gzip模块,“gzip on”表示开启GZIP压缩,实时压缩输出数据流
gzip_min_length 1k; #gzip_min_length设置允许压缩的页面最小字节数,页面字节数从header头的Content-Length中获取。默认值是0,不管页面多大都进行压缩。建议设置成大于1K的字节数,小于1K可能会越压越大
gzip_buffers 4 16k; #gzip_buffers表示申请4个单位为16K的内存作为压缩结果流缓存,默认值是申请与原始数据大小相同的内存空间来存储gzip压缩结果
gzip_http_version 1.1; #gzip_http_version用于设置识别HTTP协议版本,默认是1.1,目前大部分浏览器已经支持GZIP解压,使用默认即可
gzip_comp_level 2; #gzip_comp_level用来指定GZIP压缩比,1 压缩比最小,处理速度最快;9 压缩比最大,传输速度快,但处理最慢,也比较消耗cpu资源
gzip_types text/plain application/x-javascript text/css application/xml;
#gzip_types用来指定压缩的类型,无论是否指定,“text/html”类型总是会被压缩的
gzip_vary on; #gzip_vary选项可以让前端的缓存服务器缓存经过GZIP压缩的页面,例如用Squid缓存经过Nginx压缩的数据

负载均衡设置

Nginx的负载均衡模块目前支持4种调度算法:

1.轮询(默认):每个请求按时间顺序逐一分配到不同的后端服务器。指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  #设定负载均衡服务器列表
upstream roundrobin {
#后端服务器访问规则
server 192.168.1.115:8080 weight=1; #server1
server 192.168.1.131:8081 weight=1; #server1
server 192.168.1.94:8090 weight=1; #server3
}

server {
listen 80;
server_name 192.168.1.131;
location / {
proxy_pass http://roundrobin;
}
}
当访问 http://192.168.131 的时候,会把这个请求负载到 192.168.1.115 的 8080 端口、192.168.1.115 的 8080 端口、192.168.1.115 的 8080 端口。负载的权重由 weight 来决定,默认为 1 ,weight 越大,权重就越大

2.ip_hash:每个请求按访问IP的hash结果分配,同一个IP客户端固定访问一个后端服务器。可以保证来自同一ip的请求被打到固定的机器上,可以解决session问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#设定负载均衡服务器列表
upstream roundrobin {
#后端服务器访问规则
ip_hash;
server 192.168.1.134 weight=1 max_fails=2 fail_timeout=2;
server 192.168.1.131 weight=1 max_fails=2 fail_timeout=2;
}

server {
listen 80;
server_name 192.168.1.131;
location / {
proxy_pass http://roundrobin;
}
}
设置后端负载均衡服务器的状态:
down,表示当前的server暂时不参与负载均衡。
backup,预留的备份机器。当其他所有的非backup机器出现故障或者忙的时候,才会请求backup机器,因此这台机器的压力最轻。
max_fails:允许请求失败的次数,默认为1。当超过最大次数时,返回proxy_next_upstream 模块定义的错误。
fail_timeout:在经历了max_fails次失败后,暂停服务的时间。max_fails可以和fail_timeout一起使用。
注意:backup不能和ip_hash同时配置。因为ip_hash只能访问同一台服务器,而backup是在只有所有参与负载均衡的服务器出现故障时,才会请求备份机。当所有负载均衡的服务器出现故障了,ip_hash的将无法请求了
#设定负载均衡服务器列表
upstream roundrobin {
#后端服务器访问规则
server 192.168.1.115:8080 weight=1; #server1
server 192.168.1.131:8080 down; #server2 不参与负载
server 192.168.1.94:8090 backup; #server3 备份机
}

server {
listen 80;
server_name 192.168.1.131;
location / {
proxy_pass http://roundrobin;
}
}

3.url_hash(第三方):访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,可以进一步提高后端缓存服务器的效率。Nginx本身是不支持url_hash的,如果需要使用这种调度算法,必须安装Nginx 的hash软件包

1
2
3
4
5
6
upstream backend {
server squid1:3128;
server squid2:3128;
hash $request_uri;
hash_method crc32;
}

4.fair(第三方):这是比上面两个更加智能的负载均衡算法。此种算法可以依据页面大小和加载时间长短智能地进行负载均衡,也就是根据后端服务器的响应时间来分配请求,响应时间短的优先分配。Nginx本身是不支持 fair的,如果需要使用这种调度算法,必须下载Nginx的 upstream_fair模块

upstream是Nginx的HTTP Upstream模块,这个模块通过一个简单的调度算法来实现客户端IP到后端服务器的负载均衡。

1
2
3
4
5
upstream backend {
server server1;
server server2;
fair;
}

location对URL进行匹配.可以进行重定向或者进行新的代理 负载均衡

server虚拟主机配置

建议将对虚拟主机进行配置的内容写进另外一个文件,然后通过include指令包含进来,这样更便于维护和管理

1
2
3
4
5
6
7
8
9
10
11
12
13
server{    #server标志定义虚拟主机开始
listen 80; #listen用于指定虚拟主机的服务端口
server_name 192.168.8.18 cszhi.com; #server_name用来指定访问的IP地址或者域名,多个域名之间用空格分开
index index.html index.htm index.php; #index用于设定访问的默认首页地址
root /wwwroot/www.cszhi.com #root指令用于指定虚拟主机的网页根目录,这个目录可以是相对路径,也可以是绝对路径
charset gb2312; #charset用于 设置网页的默认编码格式
access_log logs/www.ixdba.net.access.log main; #access_log用来指定此虚拟主机的访问日志存放路径,最后的main用于指定访问日志的输出格式
proxy_pass http://img_relay$request_uri; #设置被代理服务器的端口或套接字,以及URL
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#以上三行,目的是将代理服务器收到的用户的信息传到真实服务器上

location URL匹配配置

URL地址匹配是进行Nginx配置中最灵活的部分。 location支持正则表达式匹配,也支持条件判断匹配,用户可以通过location指令实现Nginx对动、静态网页进行过滤处理。使用location URL匹配配置还可以实现反向代理,用于实现PHP动态解析或者负载负载均衡

1
2
3
4
5
6
7
8
9
10
11
#这段设置是通过location指令来对网页URL进行分析处理,所有扩展名以.gif、.jpg、.jpeg、.png、.bmp、.swf结尾的静态文件都交给nginx进行负载均衡
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ {
root /wwwroot/www.cszhi.com;
expires 30d; #expires用来指定静态文件的过期时间,这里是30天
}

#这段设置中,location是对此虚拟主机下动态网页的过滤处理,也就是将所有以.jsp为后缀的文件都交给本机的8080端口处理
location ~ .*.php$ {
index index.php;
proxy_pass http://localhost:8080;
}

root和alias的区别:

StubStatus模块配置

StubStatus模块能够获取Nginx自上次启动以来的工作状态,此模块非核心模块,需要在Nginx编译安装时手工指定才能使用此功能。

1
2
3
4
5
6
7
#以下指令是指定启用获取Nginx工作状态的功能
location /NginxStatus {
stub_status on; #stub_status设置为“on”表示启用StubStatus的工作状态统计功能
access_log logs/NginxStatus.log; #access_log 用来指定StubStatus模块的访问日志文件
auth_basic "NginxStatus"; #auth_basic是Nginx的一种认证机制
auth_basic_user_file ../htpasswd;
}

auth_basic_user_file用来指定认证的密码文件,由于Nginx的auth_basic认证采用的是与Apache兼容的密码文件,因此需要用Apache的htpasswd命令来生成密码文件,例如要添加一个test用户,可以使用下面方式生成密码文件
/usr/local/apache/bin/htpasswd -c /opt/nginx/conf/htpasswd test
然后输入两次密码后确认之后添加用户成功。

要查看Nginx的运行状态,可以输入http://ip/NginxStatus,输入创建的用户名和密码就可以看到Nginx的运行状态

1
2
3
4
Active connections: 1  #Active connections表示当前活跃的连接数
server accepts handled requests
34561 35731 354399 #表示Nginx当前总共处理了34561个连接, 成功创建次握手, 总共处理了354399个请求
Reading: 0 Writing: 3 Waiting: 0 #Reading表示Nginx读取到客户端Header信息数, Writing表示Nginx返回给客户端的Header信息数,“Waiting”表示Nginx已经处理完,正在等候下一次请求指令时的驻留连接数

错误信息

这段设置中,设置了虚拟主机的错误信息返回页面,通过error_page指令可以定制各种错误信息的返回页面。在默认情况下,Nginx会在主目录的html目录中查找指定的返回页面,特别需要注意的是,这些错误信息的返回页面大小一定要超过512K,否者会被ie浏览器替换为ie默认的错误页面

1
2
3
4
5
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}

Nginx 的典型配置示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# main段配置信息
user nginx; # 运行用户,默认即是nginx,可以不进行设置
worker_processes auto; # Nginx 进程数,一般设置为和 CPU 核数一样
error_log /var/log/nginx/error.log warn; # Nginx 的错误日志存放目录
pid /var/run/nginx.pid; # Nginx 服务启动时的 pid 存放位置

# events段配置信息
events {
use epoll; # 使用epoll的I/O模型(如果你不知道Nginx该使用哪种轮询方法,会自动选择一个最适合你操作系统的)
worker_connections 1024; # 每个进程允许最大并发数
}

# http段配置信息
# 配置使用最频繁的部分,代理、缓存、日志定义等绝大多数功能和第三方模块的配置都在这里设置
http {
# 设置日志模式
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log /var/log/nginx/access.log main; # Nginx访问日志存放位置

sendfile on; # 开启高效传输模式
tcp_nopush on; # 减少网络报文段的数量
tcp_nodelay on;
keepalive_timeout 65; # 保持连接的时间,也叫超时时间,单位秒
types_hash_max_size 2048;

include /etc/nginx/mime.types; # 文件扩展名与类型映射表
default_type application/octet-stream; # 默认文件类型

include /etc/nginx/conf.d/*.conf; # 加载子配置项

# server段配置信息
server {
listen 80; # 配置监听的端口
server_name localhost; # 配置的域名

# location段配置信息
location / {
root /usr/share/nginx/html; # 网站根目录
index index.html index.htm; # 默认首页文件
deny 172.168.22.11; # 禁止访问的ip地址,可以为all
allow 172.168.33.44;# 允许访问的ip地址,可以为all
}

error_page 500 502 503 504 /50x.html; # 默认50x对应的访问页面
error_page 400 404 error.html; # 同上
}
}
  • 全局配置,对全局生效;
  • events 配置影响 Nginx 服务器与用户的网络连接;
  • http 配置代理,缓存,日志定义等绝大多数功能和第三方模块的配置;
  • server 配置虚拟主机的相关参数,一个 http 块中可以有多个 server 块;
  • location 用于配置匹配的 uri ;
  • upstream 配置后端服务器具体地址,负载均衡配置不可或缺的部分;

用一张图清晰的展示它的层级结构:

图片

配置文件 main 段核心参数

user

指定运行 Nginx 的 woker 子进程的属主和属组,其中组可以不指定。

1
2
3
user USERNAME [GROUP]

user nginx lion; # 用户是nginx;组是lion

pid

指定运行 Nginx master 主进程的 pid 文件存放路径。

1
pid /opt/nginx/logs/nginx.pid # master主进程的的pid存放在nginx.pid的文件

worker_rlimit_nofile_number

指定 worker 子进程可以打开的最大文件句柄数。

1
worker_rlimit_nofile 20480; # 可以理解成每个worker子进程的最大连接数量。

worker_rlimit_core

指定 worker 子进程异常终止后的 core 文件,用于记录分析问题。

1
2
worker_rlimit_core 50M; # 存放大小限制
working_directory /opt/nginx/tmp; # 存放目录

worker_processes_number

指定 Nginx 启动的 worker 子进程数量。

1
2
worker_processes 4; # 指定具体子进程数量
worker_processes auto; # 与当前cpu物理核心数一致

worker_cpu_affinity

将每个 worker 子进程与我们的 cpu 物理核心绑定。

1
worker_cpu_affinity 0001 0010 0100 1000; # 4个物理核心,4个worker子进程

图片

将每个 worker 子进程与特定 CPU 物理核心绑定,优势在于,避免同一个 worker 子进程在不同的 CPU 核心上切换,缓存失效,降低性能。但其并不能真正的避免进程切换。

worker_priority

指定 worker 子进程的 nice 值,以调整运行 Nginx 的优先级,通常设定为负值,以优先调用Nginx 。

1
worker_priority -10; # 120-10=110,110就是最终的优先级

Linux 默认进程的优先级值是120,值越小越优先;nice 定范围为 -20 到 +19 。

[备注] 应用的默认优先级值是120加上 nice 值等于它最终的值,这个值越小,优先级越高。

worker_shutdown_timeout

指定 worker 子进程优雅退出时的超时时间。

1
worker_shutdown_timeout 5s;

timer_resolution

worker 子进程内部使用的计时器精度,调整时间间隔越大,系统调用越少,有利于性能提升;反之,系统调用越多,性能下降。

1
timer_resolution 100ms;

在 Linux 系统中,用户需要获取计时器时需要向操作系统内核发送请求,有请求就必然会有开销,因此这个间隔越大开销就越小。

daemon

指定 Nginx 的运行方式,前台还是后台,前台用于调试,后台用于生产。

1
daemon off; # 默认是on,后台运行模式

配置文件 events 段核心参数

use

Nginx 使用何种事件驱动模型。

1
2
3
use method; # 不推荐配置它,让nginx自己选择

method 可选值为:select、poll、kqueue、epoll、/dev/poll、eventport

worker_connections

worker 子进程能够处理的最大并发连接数。

1
worker_connections 1024 # 每个子进程的最大连接数为1024

accept_mutex

是否打开负载均衡互斥锁。

1
accept_mutex on # 默认是off关闭的,这里推荐打开

server_name 指令

指定虚拟主机域名。

1
2
3
4
server_name name1 name2 name3

# 示例:
server_name www.nginx.com;

域名匹配的四种写法:

  • 精确匹配:server_name www.nginx.com ;
  • 左侧通配:server_name *.nginx.com ;
  • 右侧统配:server_name www.nginx.* ;
  • 正则匹配:server_name ~^www\.nginx\.*$ ;

匹配优先级:精确匹配 > 左侧通配符匹配 > 右侧通配符匹配 > 正则表达式匹配

server_name 配置实例:

1、配置本地 DNS 解析 vim /etc/hosts ( macOS 系统)

1
2
3
4
5
6
7
# 添加如下内容,其中 121.42.11.34 是阿里云服务器IP地址
121.42.11.34 www.nginx-test.com
121.42.11.34 mail.nginx-test.com
121.42.11.34 www.nginx-test.org
121.42.11.34 doc.nginx-test.com
121.42.11.34 www.nginx-test.cn
121.42.11.34 fe.nginx-test.club

[注意] 这里使用的是虚拟域名进行测试,因此需要配置本地 DNS 解析,如果使用阿里云上购买的域名,则需要在阿里云上设置好域名解析。

2、配置阿里云 Nginx ,vim /etc/nginx/nginx.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 这里只列举了http端中的sever端配置

# 左匹配
server {
listen 80;
server_name *.nginx-test.com;
root /usr/share/nginx/html/nginx-test/left-match/;
location / {
index index.html;
}
}

# 正则匹配
server {
listen 80;
server_name ~^.*\.nginx-test\..*$;
root /usr/share/nginx/html/nginx-test/reg-match/;
location / {
index index.html;
}
}

# 右匹配
server {
listen 80;
server_name www.nginx-test.*;
root /usr/share/nginx/html/nginx-test/right-match/;
location / {
index index.html;
}
}

# 完全匹配
server {
listen 80;
server_name www.nginx-test.com;
root /usr/share/nginx/html/nginx-test/all-match/;
location / {
index index.html;
}
}

3、访问分析

  • 当访问 www.nginx-test.com 时,都可以被匹配上,因此选择优先级最高的“完全匹配”;
  • 当访问 mail.nginx-test.com 时,会进行“左匹配”;
  • 当访问 www.nginx-test.org 时,会进行“右匹配”;
  • 当访问 doc.nginx-test.com 时,会进行“左匹配”;
  • 当访问 www.nginx-test.cn 时,会进行“右匹配”;
  • 当访问 fe.nginx-test.club 时,会进行“正则匹配”;

root

指定静态资源目录位置,它可以写在 http 、 server 、 location 等配置中。

1
2
3
4
5
6
7
8
root path

例如:
location /image {
root /opt/nginx/static;
}

当用户访问 www.test.com/image/1.png 时,实际在服务器找的路径是 /opt/nginx/static/image/1.png

[注意] root 会将定义路径与 URI 叠加, alias 则只取定义路径。

alias

它也是指定静态资源目录位置,它只能写在 location 中。

1
2
3
4
5
location /image {
alias /opt/nginx/static/image/;
}

当用户访问 www.test.com/image/1.png 时,实际在服务器找的路径是 /opt/nginx/static/image/1.png

[注意] 使用 alias 末尾一定要添加 / ,并且它只能位于 location 中。

location

配置路径。

1
2
3
location [ = | ~ | ~* | ^~ ] uri {
...
}

匹配规则:

  • = 精确匹配;
  • ~ 正则匹配,区分大小写;
  • ~* 正则匹配,不区分大小写;
  • ^~ 匹配到即停止搜索;

匹配优先级:= > ^~ > ~ > ~* > 不带任何字符。

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
server {
listen 80;
server_name www.nginx-test.com;

# 只有当访问 www.nginx-test.com/match_all/ 时才会匹配到/usr/share/nginx/html/match_all/index.html
location = /match_all/ {
root /usr/share/nginx/html
index index.html
}

# 当访问 www.nginx-test.com/1.jpg 等路径时会去 /usr/share/nginx/images/1.jpg 找对应的资源
location ~ \.(jpeg|jpg|png|svg)$ {
root /usr/share/nginx/images;
}

# 当访问 www.nginx-test.com/bbs/ 时会匹配上 /usr/share/nginx/html/bbs/index.html
location ^~ /bbs/ {
root /usr/share/nginx/html;
index index.html index.htm;
}
}

location 中的反斜线

1
2
3
4
5
6
7
location /test {
...
}

location /test/ {
...
}
  • 不带 / 当访问 www.nginx-test.com/test 时, Nginx 先找是否有 test 目录,如果有则找 test 目录下的 index.html ;如果没有 test 目录, nginx 则会找是否有 test 文件。
  • 带 / 当访问 www.nginx-test.com/test 时, Nginx 先找是否有 test 目录,如果有则找test 目录下的 index.html ,如果没有它也不会去找是否存在 test 文件。

return

停止处理请求,直接返回响应码或重定向到其他 URL ;执行 return 指令后, location 中后续指令将不会被执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
return code [text];
return code URL;
return URL;

例如:
location / {
return 404; # 直接返回状态码
}

location / {
return 404 "pages not found"; # 返回状态码 + 一段文本
}

location / {
return 302 /bbs ; # 返回状态码 + 重定向地址
}

location / {
return https://www.baidu.com ; # 返回重定向地址
}

rewrite

根据指定正则表达式匹配规则,重写 URL 。

1
2
3
4
5
语法:rewrite 正则表达式 要替换的内容 [flag];

上下文:server、location、if

示例:rewirte /images/(.*\.jpg)$ /pic/$1; # $1是前面括号(.*\.jpg)的反向引用

flag 可选值的含义:

  • last 重写后的 URL 发起新请求,再次进入 server 段,重试 location 的中的匹配;
  • break 直接使用重写后的 URL ,不再匹配其它 location 中语句;
  • redirect 返回302临时重定向;
  • permanent 返回301永久重定向;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
server{
listen 80;
server_name fe.lion.club; # 要在本地hosts文件进行配置
root html;
location /search {
rewrite ^/(.*) https://www.baidu.com redirect;
}

location /images {
rewrite /images/(.*) /pics/$1;
}

location /pics {
rewrite /pics/(.*) /photos/$1;
}

location /photos {

}
}

按照这个配置我们来分析:

  • 当访问 fe.lion.club/search 时,会自动帮我们重定向到 https://www.baidu.com。
  • 当访问 fe.lion.club/images/1.jpg 时,第一步重写 URL 为 fe.lion.club/pics/1.jpg,找到 pics 的 location ,继续重写 URL 为 fe.lion.club/photos/1.jpg ,找到/photos 的 location 后,去 html/photos 目录下寻找 1.jpg 静态资源。

if 指令

1
2
3
4
5
6
7
8
语法:if (condition) {...}

上下文:server、location

示例:
if($http_user_agent ~ Chrome){
rewrite /(.*)/browser/$1 break;
}

condition 判断条件:

  • $variable 仅为变量时,值为空或以0开头字符串都会被当做 false 处理;
  • = 或 != 相等或不等;
  • ~ 正则匹配;
  • ! ~ 非正则匹配;
  • ~* 正则匹配,不区分大小写;
  • -f 或 ! -f 检测文件存在或不存在;
  • -d 或 ! -d 检测目录存在或不存在;
  • -e 或 ! -e 检测文件、目录、符号链接等存在或不存在;
  • -x 或 ! -x 检测文件可以执行或不可执行;

实例:

1
2
3
4
5
6
7
8
9
10
11
server {
listen 8080;
server_name localhost;
root html;

location / {
if ( $uri = "/images/" ){
rewrite (.*) /pics/ break;
}
}
}

当访问 localhost:8080/images/ 时,会进入 if 判断里面执行 rewrite 命令。

autoindex

用户请求以 / 结尾时,列出目录结构,可以用于快速搭建静态资源下载网站。

autoindex.conf 配置信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
server {
listen 80;
server_name fe.lion-test.club;

location /download/ {
root /opt/source;

autoindex on; # 打开 autoindex,,可选参数有 on | off
autoindex_exact_size on; # 修改为off,以KB、MB、GB显示文件大小,默认为on,以bytes显示出⽂件的确切⼤⼩
autoindex_format html; # 以html的方式进行格式化,可选参数有 html | json | xml
autoindex_localtime off; # 显示的⽂件时间为⽂件的服务器时间。默认为off,显示的⽂件时间为GMT时间
}
}

当访问 fe.lion.com/download/ 时,会把服务器 /opt/source/download/ 路径下的文件展示出来,如下图所示:

图片

变量

Nginx 提供给使用者的变量非常多,但是终究是一个完整的请求过程所产生数据, Nginx 将这些数据以变量的形式提供给使用者。

下面列举些项目中常用的变量:

图片

实例演示 var.conf :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
server{
listen 8081;
server_name var.lion-test.club;
root /usr/share/nginx/html;
location / {
return 200 "
remote_addr: $remote_addr
remote_port: $remote_port
server_addr: $server_addr
server_port: $server_port
server_protocol: $server_protocol
binary_remote_addr: $binary_remote_addr
connection: $connection
uri: $uri
request_uri: $request_uri
scheme: $scheme
request_method: $request_method
request_length: $request_length
args: $args
arg_pid: $arg_pid
is_args: $is_args
query_string: $query_string
host: $host
http_user_agent: $http_user_agent
http_referer: $http_referer
http_via: $http_via
request_time: $request_time
https: $https
request_filename: $request_filename
document_root: $document_root
";
}
}

当我们访问 http://var.lion-test.club:8081/test?pid=121414&cid=sadasd 时,由于Nginx 中写了 return 方法,因此 chrome 浏览器会默认为我们下载一个文件,下面展示的就是下载的文件内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
remote_addr: 27.16.220.84
remote_port: 56838
server_addr: 172.17.0.2
server_port: 8081
server_protocol: HTTP/1.1
binary_remote_addr: 茉
connection: 126
uri: /test/
request_uri: /test/?pid=121414&cid=sadasd
scheme: http
request_method: GET
request_length: 518
args: pid=121414&cid=sadasd
arg_pid: 121414
is_args: ?
query_string: pid=121414&cid=sadasd
host: var.lion-test.club
http_user_agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36
http_referer:
http_via:
request_time: 0.000
https:
request_filename: /usr/share/nginx/html/test/
document_root: /usr/share/nginx/html

Nginx 的配置还有非常多,以上只是罗列了一些常用的配置,在实际项目中还是要学会查阅文档。

Nginx 应用核心概念

代理是在服务器和客户端之间假设的一层服务器,代理将接收客户端的请求并将它转发给服务器,然后将服务端的响应转发给客户端。

不管是正向代理还是反向代理,实现的都是上面的功能。

图片

正向代理

正向代理,意思是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。

正向代理是为我们服务的,即为客户端服务的,客户端可以根据正向代理访问到它本身无法访问到的服务器资源。

正向代理对我们是透明的,对服务端是非透明的,即服务端并不知道自己收到的是来自代理的访问还是来自真实客户端的访问。

反向代理

  • 反向代理*(Reverse Proxy)方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个反向代理服务器。

反向代理是为服务端服务的,反向代理可以帮助服务器接收来自客户端的请求,帮助服务器做请求转发,负载均衡等。

反向代理对服务端是透明的,对我们是非透明的,即我们并不知道自己访问的是代理服务器,而服务器知道反向代理在为他服务。

反向代理的优势:

  • 隐藏真实服务器;
  • 负载均衡便于横向扩充后端动态服务;
  • 动静分离,提升系统健壮性;

那么“动静分离”是什么?负载均衡又是什么?

动静分离

动静分离是指在 web 服务器架构中,将静态页面与动态页面或者静态内容接口和动态内容接口分开不同系统访问的架构设计方法,进而提示整个服务的访问性和可维护性。

图片

一般来说,都需要将动态资源和静态资源分开,由于 Nginx 的高并发和静态资源缓存等特性,经常将静态资源部署在 Nginx 上。如果请求的是静态资源,直接到静态资源目录获取资源,如果是动态资源的请求,则利用反向代理的原理,把请求转发给对应后台应用去处理,从而实现动静分离。

使用前后端分离后,可以很大程度提升静态资源的访问速度,即使动态服务不可用,静态资源的访问也不会受到影响。

负载均衡

一般情况下,客户端发送多个请求到服务器,服务器处理请求,其中一部分可能要操作一些资源比如数据库、静态资源等,服务器处理完毕后,再将结果返回给客户端。

这种模式对于早期的系统来说,功能要求不复杂,且并发请求相对较少的情况下还能胜任,成本也低。随着信息数量不断增长,访问量和数据量飞速增长,以及系统业务复杂度持续增加,这种做法已无法满足要求,并发量特别大时,服务器容易崩。

很明显这是由于服务器性能的瓶颈造成的问题,除了堆机器之外,最重要的做法就是负载均衡。

请求爆发式增长的情况下,单个机器性能再强劲也无法满足要求了,这个时候集群的概念产生了,单个服务器解决不了的问题,可以使用多个服务器,然后将请求分发到各个服务器上,将负载分发到不同的服务器,这就是负载均衡,核心是「分摊压力」。Nginx 实现负载均衡,一般来说指的是将请求转发给服务器集群。

举个具体的例子,晚高峰乘坐地铁的时候,入站口经常会有地铁工作人员大喇叭“请走 B 口,B 口人少车空….”,这个工作人员的作用就是负载均衡。

图片

Nginx 实现负载均衡的策略:

  • 轮询策略:默认情况下采用的策略,将所有客户端请求轮询分配给服务端。这种策略是可以正常工作的,但是如果其中某一台服务器压力太大,出现延迟,会影响所有分配在这台服务器下的用户。
  • 最小连接数策略:将请求优先分配给压力较小的服务器,它可以平衡每个队列的长度,并避免向压力大的服务器添加更多的请求。
  • 最快响应时间策略:优先分配给响应时间最短的服务器。
  • 客户端 ip 绑定策略:来自同一个 ip 的请求永远只分配一台服务器,有效解决了动态网页存在的 session 共享问题。

Nginx 实战配置

在配置反向代理和负载均衡等等功能之前,有两个核心模块是我们必须要掌握的,这两个模块应该说是 Nginx 应用配置中的核心,它们分别是:upstream 、proxy_pass 。

upstream

用于定义上游服务器(指的就是后台提供的应用服务器)的相关信息。

图片

1
2
3
4
5
6
7
8
9
10
语法:upstream name {
...
}

上下文:http

示例:
upstream back_end_server{
server 192.168.100.33:8081
}

在 upstream 内可使用的指令:

  • server 定义上游服务器地址;
  • zone 定义共享内存,用于跨 worker 子进程;
  • keepalive 对上游服务启用长连接;
  • keepalive_requests 一个长连接最多请求 HTTP 的个数;
  • keepalive_timeout 空闲情形下,一个长连接的超时时长;
  • hash 哈希负载均衡算法;
  • ip_hash 依据 IP 进行哈希计算的负载均衡算法;
  • least_conn 最少连接数负载均衡算法;
  • least_time 最短响应时间负载均衡算法;
  • random 随机负载均衡算法;

server

定义上游服务器地址。

1
2
3
语法:server address [parameters]

上下文:upstream

parameters 可选值:

  • weight=number 权重值,默认为1;
  • max_conns=number 上游服务器的最大并发连接数;
  • fail_timeout=time 服务器不可用的判定时间;
  • max_fails=numer 服务器不可用的检查次数;
  • backup 备份服务器,仅当其他服务器都不可用时才会启用;
  • down 标记服务器长期不可用,离线维护;

keepalive

限制每个 worker 子进程与上游服务器空闲长连接的最大数量。

1
2
3
4
5
keepalive connections;

上下文:upstream

示例:keepalive 16;

keepalive_requests

单个长连接可以处理的最多 HTTP 请求个数。

1
2
3
4
5
语法:keepalive_requests number;

默认值:keepalive_requests 100;

上下文:upstream

keepalive_timeout

空闲长连接的最长保持时间。

1
2
3
4
5
语法:keepalive_timeout time;

默认值:keepalive_timeout 60s;

上下文:upstream

配置实例

1
2
3
4
5
6
upstream back_end{
server 127.0.0.1:8081 weight=3 max_conns=1000 fail_timeout=10s max_fails=2;
keepalive 32;
keepalive_requests 50;
keepalive_timeout 30s;
}

proxy_pass

用于配置代理服务器。

1
2
3
4
5
6
7
语法:proxy_pass URL;

上下文:location、if、limit_except

示例:
proxy_pass http://127.0.0.1:8081
proxy_pass http://127.0.0.1:8081/proxy

URL 参数原则

1. URL 必须以 http 或 https 开头;

2. URL 中可以携带变量;

3. URL 中是否带 URI ,会直接影响发往上游请求的 URL ;

接下来让我们来看看两种常见的 URL 用法:

  1. proxy_pass http://192.168.100.33:8081
  2. proxy_pass http://192.168.100.33:8081/

这两种用法的区别就是带 / 和不带 / ,在配置代理时它们的区别可大了:

  • 不带 / 意味着 Nginx 不会修改用户 URL ,而是直接透传给上游的应用服务器;
  • 带 / 意味着 Nginx 会修改用户 URL ,修改方法是将 location 后的 URL 从用户 URL 中删除;

不带 / 的用法:

1
2
3
location /bbs/{
proxy_pass http://127.0.0.1:8080;
}

分析:

\1. 用户请求 URL :/bbs/abc/test.html

\2. 请求到达 Nginx 的 URL :/bbs/abc/test.html

3 .请求到达上游应用服务器的 URL :/bbs/abc/test.html

带 / 的用法:

1
2
3
location /bbs/{
proxy_pass http://127.0.0.1:8080/;
}

分析:

\1. 用户请求 URL :/bbs/abc/test.html

\2. 请求到达 Nginx 的 URL :/bbs/abc/test.html

\3. 请求到达上游应用服务器的 URL :/abc/test.html

并没有拼接上 /bbs ,这点和 root 与 alias 之间的区别是保持一致的。

配置反向代理

这里为了演示更加接近实际,作者准备了两台云服务器,它们的公网 IP 分别是:121.42.11.34与 121.5.180.193 。

我们把 121.42.11.34 服务器作为上游服务器,做如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
# /etc/nginx/conf.d/proxy.conf
server{
listen 8080;
server_name localhost;

location /proxy/ {
root /usr/share/nginx/html/proxy;
index index.html;
}
}

# /usr/share/nginx/html/proxy/index.html
<h1> 121.42.11.34 proxy html </h1>

配置完成后重启 Nginx 服务器 nginx \-s reload 。

把 121.5.180.193 服务器作为代理服务器,做如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# /etc/nginx/conf.d/proxy.conf
upstream back_end {
server 121.42.11.34:8080 weight=2 max_conns=1000 fail_timeout=10s max_fails=3;
keepalive 32;
keepalive_requests 80;
keepalive_timeout 20s;
}

server {
listen 80;
server_name proxy.lion.club;
location /proxy {
proxy_pass http://back_end/proxy;
}
}

本地机器要访问 proxy.lion.club 域名,因此需要配置本地 hosts ,通过命令:vim /etc/hosts 进入配置文件,添加如下内容:

1
121.5.180.193 proxy.lion.club

图片

分析:

当访问 proxy.lion.club/proxy 时通过 upstream 的配置找到 121.42.11.34:8080 ;

因此访问地址变为 http://121.42.11.34:8080/proxy ;

连接到 121.42.11.34 服务器,找到 8080 端口提供的 server ;

通过 server 找到 /usr/share/nginx/html/proxy/index.html 资源,最终展示出来。

配置负载均衡

配置负载均衡主要是要使用 upstream 指令。

我们把 121.42.11.34 服务器作为上游服务器,做如下配置(/etc/nginx/conf.d/balance.conf ):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
server{
listen 8020;
location / {
return 200 'return 8020 \n';
}
}

server{
listen 8030;
location / {
return 200 'return 8030 \n';
}
}

server{
listen 8040;
location / {
return 200 'return 8040 \n';
}
}

配置完成后:

1. nginx -t 检测配置是否正确;

2. nginx -s reload 重启 Nginx 服务器;

\3. 执行 ss -nlt 命令查看端口是否被占用,从而判断 Nginx 服务是否正确启动。

把 121.5.180.193 服务器作为代理服务器,做如下配置( /etc/nginx/conf.d/balance.conf):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
upstream demo_server {
server 121.42.11.34:8020;
server 121.42.11.34:8030;
server 121.42.11.34:8040;
}

server {
listen 80;
server_name balance.lion.club;

location /balance/ {
proxy_pass http://demo_server;
}
}

配置完成后重启 Nginx 服务器。并且在需要访问的客户端配置好 ip 和域名的映射关系。

1
2
3
# /etc/hosts

121.5.180.193 balance.lion.club

在客户端机器执行 curl http://balance.lion.club/balance/ 命令:

图片

不难看出,负载均衡的配置已经生效了,每次给我们分发的上游服务器都不一样。就是通过简单的轮询策略进行上游服务器分发。

接下来,我们再来了解下 Nginx 的其它分发策略。

hash 算法

通过制定关键字作为 hash key ,基于 hash 算法映射到特定的上游服务器中。关键字可以包含有变量、字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
upstream demo_server {
hash $request_uri;
server 121.42.11.34:8020;
server 121.42.11.34:8030;
server 121.42.11.34:8040;
}

server {
listen 80;
server_name balance.lion.club;

location /balance/ {
proxy_pass http://demo_server;
}
}

hash $request_uri 表示使用 request_uri 变量作为 hash 的 key 值,只要访问的 URI 保持不变,就会一直分发给同一台服务器。

ip_hash

根据客户端的请求 ip 进行判断,只要 ip 地址不变就永远分配到同一台主机。它可以有效解决后台服务器 session 保持的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
upstream demo_server {
ip_hash;
server 121.42.11.34:8020;
server 121.42.11.34:8030;
server 121.42.11.34:8040;
}

server {
listen 80;
server_name balance.lion.club;

location /balance/ {
proxy_pass http://demo_server;
}
}

最少连接数算法

各个 worker 子进程通过读取共享内存的数据,来获取后端服务器的信息。来挑选一台当前已建立连接数最少的服务器进行分配请求。

1
2
3
语法:least_conn;

上下文:upstream;

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
upstream demo_server {
zone test 10M; # zone可以设置共享内存空间的名字和大小
least_conn;
server 121.42.11.34:8020;
server 121.42.11.34:8030;
server 121.42.11.34:8040;
}

server {
listen 80;
server_name balance.lion.club;

location /balance/ {
proxy_pass http://demo_server;
}
}

最后你会发现,负载均衡的配置其实一点都不复杂。

配置缓存

缓存可以非常有效的提升性能,因此不论是客户端(浏览器),还是代理服务器( Nginx ),乃至上游服务器都多少会涉及到缓存。可见缓存在每个环节都是非常重要的。下面让我们来学习Nginx 中如何设置缓存策略。

proxy_cache

存储一些之前被访问过、而且可能将要被再次访问的资源,使用户可以直接从代理服务器获得,从而减少上游服务器的压力,加快整个访问速度。

1
2
3
4
5
语法:proxy_cache zone | off ; # zone 是共享内存的名称

默认值:proxy_cache off;

上下文:http、server、location

proxy_cache_path

设置缓存文件的存放路径。

1
2
3
4
5
语法:proxy_cache_path path [level=levels] ...可选参数省略,下面会详细列举

默认值:proxy_cache_path off

上下文:http

参数含义:

  • path 缓存文件的存放路径;
  • level path 的目录层级;
  • keys_zone 设置共享内存;
  • inactive 在指定时间内没有被访问,缓存会被清理,默认10分钟;

proxy_cache_key

设置缓存文件的 key 。

1
2
3
4
5
语法:proxy_cache_key

默认值:proxy_cache_key $scheme$proxy_host$request_uri;

上下文:http、server、location

proxy_cache_valid

配置什么状态码可以被缓存,以及缓存时长。

1
2
3
4
5
语法:proxy_cache_valid [code...] time;

上下文:http、server、location

配置示例:proxy_cache_valid 200 304 2m;; # 说明对于状态为200和304的缓存文件的缓存时间是2分钟

proxy_no_cache

定义相应保存到缓存的条件,如果字符串参数的至少一个值不为空且不等于“ 0”,则将不保存该响应到缓存。

1
2
3
4
5
语法:proxy_no_cache string;

上下文:http、server、location

示例:proxy_no_cache $http_pragma $http_authorization;

proxy_cache_bypass

定义条件,在该条件下将不会从缓存中获取响应。

1
2
3
4
5
语法:proxy_cache_bypass string;

上下文:http、server、location

示例:proxy_cache_bypass $http_pragma $http_authorization;

upstream_cache_status 变量

它存储了缓存是否命中的信息,会设置在响应头信息中,在调试中非常有用。

1
2
3
4
5
6
7
MISS: 未命中缓存
HIT:命中缓存
EXPIRED: 缓存过期
STALE: 命中了陈旧缓存
REVALIDDATED: Nginx验证陈旧缓存依然有效
UPDATING: 内容陈旧,但正在更新
BYPASS: X响应从原始服务器获取

配置实例

我们把 121.42.11.34 服务器作为上游服务器,做如下配置( /etc/nginx/conf.d/cache.conf):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server {
listen 1010;
root /usr/share/nginx/html/1010;
location / {
index index.html;
}
}

server {
listen 1020;
root /usr/share/nginx/html/1020;
location / {
index index.html;
}
}

把 121.5.180.193 服务器作为代理服务器,做如下配置( /etc/nginx/conf.d/cache.conf):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
proxy_cache_path /etc/nginx/cache_temp levels=2:2 keys_zone=cache_zone:30m max_size=2g inactive=60m use_temp_path=off;

upstream cache_server{
server 121.42.11.34:1010;
server 121.42.11.34:1020;
}

server {
listen 80;
server_name cache.lion.club;
location / {
proxy_cache cache_zone; # 设置缓存内存,上面配置中已经定义好的
proxy_cache_valid 200 5m; # 缓存状态为200的请求,缓存时长为5分钟
proxy_cache_key $request_uri; # 缓存文件的key为请求的URI
add_header Nginx-Cache-Status $upstream_cache_status # 把缓存状态设置为头部信息,响应给客户端
proxy_pass http://cache_server; # 代理转发
}
}

缓存就是这样配置,我们可以在 /etc/nginx/cache_temp 路径下找到相应的缓存文件。

对于一些实时性要求非常高的页面或数据来说,就不应该去设置缓存,下面来看看如何配置不缓存的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
...

server {
listen 80;
server_name cache.lion.club;
# URI 中后缀为 .txt 或 .text 的设置变量值为 "no cache"
if ($request_uri ~ \.(txt|text)$) {
set $cache_name "no cache"
}

location / {
proxy_no_cache $cache_name; # 判断该变量是否有值,如果有值则不进行缓存,如果没有值则进行缓存
proxy_cache cache_zone; # 设置缓存内存
proxy_cache_valid 200 5m; # 缓存状态为200的请求,缓存时长为5分钟
proxy_cache_key $request_uri; # 缓存文件的key为请求的URI
add_header Nginx-Cache-Status $upstream_cache_status # 把缓存状态设置为头部信息,响应给客户端
proxy_pass http://cache_server; # 代理转发
}
}

HTTPS**
**

在学习如何配置 HTTPS 之前,我们先来简单回顾下 HTTPS 的工作流程是怎么样的?它是如何进行加密保证安全的?

HTTPS 工作流程

\1. 客户端(浏览器)访问 https://www.baidu.com 百度网站;

\2. 百度服务器返回 HTTPS 使用的 CA 证书;

\3. 浏览器验证 CA 证书是否为合法证书;

\4. 验证通过,证书合法,生成一串随机数并使用公钥(证书中提供的)进行加密;

\5. 发送公钥加密后的随机数给百度服务器;

\6. 百度服务器拿到密文,通过私钥进行解密,获取到随机数(公钥加密,私钥解密,反之也可以);

\7. 百度服务器把要发送给浏览器的内容,使用随机数进行加密后传输给浏览器;

\8. 此时浏览器可以使用随机数进行解密,获取到服务器的真实传输内容;

这就是 HTTPS 的基本运作原理,使用对称加密和非对称机密配合使用,保证传输内容的安全性。

关于HTTPS更多知识,可以查看作者的另外一篇文章《学习 HTTP 协议》。

配置证书

下载证书的压缩文件,里面有个 Nginx 文件夹,把 xxx.crt 和 xxx.key 文件拷贝到服务器目录,再进行如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
server {
listen 443 ssl http2 default_server; # SSL 访问端口号为 443
server_name lion.club; # 填写绑定证书的域名(我这里是随便写的)
ssl_certificate /etc/nginx/https/lion.club_bundle.crt; # 证书地址
ssl_certificate_key /etc/nginx/https/lion.club.key; # 私钥地址
ssl_session_timeout 10m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # 支持ssl协议版本,默认为后三个,主流版本是[TLSv1.2]

location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
}

如此配置后就能正常访问 HTTPS 版的网站了。

配置跨域 CORS

先简单回顾下跨域究竟是怎么回事。

跨域的定义

同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。通常不允许不同源间的读操作。

同源的定义

如果两个页面的协议,端口(如果有指定)和域名都相同,则两个页面具有相同的源。

下面给出了与 URL http://store.company.com/dir/page.html 的源进行对比的示例:

1
2
3
4
http://store.company.com/dir2/other.html 同源
https://store.company.com/secure.html 不同源,协议不同
http://store.company.com:81/dir/etc.html 不同源,端口不同
http://news.company.com/dir/other.html 不同源,主机不同

不同源会有如下限制:

  • Web 数据层面,同源策略限制了不同源的站点读取当前站点的 Cookie 、 IndexDB 、LocalStorage 等数据。
  • DOM 层面,同源策略限制了来自不同源的 JavaScript 脚本对当前 DOM 对象读和写的操作。
  • 网络层面,同源策略限制了通过 XMLHttpRequest 等方式将站点的数据发送给不同源的站点。

Nginx 解决跨域的原理

例如:

  • 前端 server 的域名为:fe.server.com
  • 后端服务的域名为:dev.server.com

现在我在 fe.server.com 对 dev.server.com 发起请求一定会出现跨域。

现在我们只需要启动一个 Nginx 服务器,将 server_name 设置为 fe.server.com 然后设置相应的 location 以拦截前端需要跨域的请求,最后将请求代理回 dev.server.com 。如下面的配置:

1
2
3
4
5
6
7
server {
listen 80;
server_name fe.server.com;
location / {
proxy_pass dev.server.com;
}
}

这样可以完美绕过浏览器的同源策略:fe.server.com 访问 Nginx 的 fe.server.com 属于同源访问,而 Nginx 对服务端转发的请求不会触发浏览器的同源策略。

配置开启 gzip 压缩

GZIP 是规定的三种标准 HTTP 压缩格式之一。目前绝大多数的网站都在使用 GZIP 传输 HTML、CSS 、 JavaScript 等资源文件。

对于文本文件, GZiP 的效果非常明显,开启后传输所需流量大约会降至 1/4~1/3 。

并不是每个浏览器都支持 gzip 的,如何知道客户端是否支持 gzip 呢,请求头中的 Accept-Encoding 来标识对压缩的支持。

图片

启用 gzip 同时需要客户端和服务端的支持,如果客户端支持 gzip 的解析,那么只要服务端能够返回 gzip 的文件就可以启用 gzip 了,我们可以通过 Nginx 的配置来让服务端支持 gzip 。下面的 respone 中 content-encoding:gzip ,指服务端开启了 gzip 的压缩方式。

图片

在 /etc/nginx/conf.d/ 文件夹中新建配置文件 gzip.conf :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# # 默认off,是否开启gzip
gzip on;
# 要采用 gzip 压缩的 MIME 文件类型,其中 text/html 被系统强制启用;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

# ---- 以上两个参数开启就可以支持Gzip压缩了 ---- #

# 默认 off,该模块启用后,Nginx 首先检查是否存在请求静态文件的 gz 结尾的文件,如果有则直接返回该 .gz 文件内容;
gzip_static on;

# 默认 off,nginx做为反向代理时启用,用于设置启用或禁用从代理服务器上收到相应内容 gzip 压缩;
gzip_proxied any;

# 用于在响应消息头中添加 Vary:Accept-Encoding,使代理服务器根据请求头中的 Accept-Encoding 识别是否启用 gzip 压缩;
gzip_vary on;

# gzip 压缩比,压缩级别是 1-9,1 压缩级别最低,9 最高,级别越高压缩率越大,压缩时间越长,建议 4-6;
gzip_comp_level 6;

# 获取多少内存用于缓存压缩结果,16 8k 表示以 8k*16 为单位获得;
gzip_buffers 16 8k;

# 允许压缩的页面最小字节数,页面字节数从header头中的 Content-Length 中进行获取。默认值是 0,不管页面多大都压缩。建议设置成大于 1k 的字节数,小于 1k 可能会越压越大;
# gzip_min_length 1k;

# 默认 1.1,启用 gzip 所需的 HTTP 最低版本;
gzip_http_version 1.1;

其实也可以通过前端构建工具例如 webpack 、rollup 等在打生产包时就做好 Gzip 压缩,然后放到 Nginx 服务器中,这样可以减少服务器的开销,加快访问速度。

关于 Nginx 的实际应用就学习到这里,相信通过掌握了 Nginx 核心配置以及实战配置,之后再遇到什么需求,我们也能轻松应对。接下来,让我们再深入一点学习下 Nginx 的架构。

Nginx 架构

进程结构

多进程结构 Nginx 的进程模型图:

图片

多进程中的 Nginx 进程架构如下图所示,会有一个父进程( Master Process ),它会有很多子进程( Child Processes )。

  • Master Process 用来管理子进程的,其本身并不真正处理用户请求。

    • 某个子进程 down 掉的话,它会向 Master 进程发送一条消息,表明自己不可用了,此时Master 进程会去新起一个子进程。
    • 某个配置文件被修改了 Master 进程会去通知 work 进程获取新的配置信息,这也就是我们所说的热部署。
  • 子进程间是通过共享内存的方式进行通信的。

配置文件重载原理

reload 重载配置文件的流程:

\1. 向 master 进程发送 HUP 信号( reload 命令);

2. master 进程检查配置语法是否正确;

3. master 进程打开监听端口;

4. master 进程使用新的配置文件启动新的 worker 子进程;

5. master 进程向老的 worker 子进程发送 QUIT 信号;

\6. 老的 worker 进程关闭监听句柄,处理完当前连接后关闭进程;

\7. 整个过程 Nginx 始终处于平稳运行中,实现了平滑升级,用户无感知;

Nginx 模块化管理机制

Nginx 的内部结构是由核心部分和一系列的功能模块所组成。这样划分是为了使得每个模块的功能相对简单,便于开发,同时也便于对系统进行功能扩展。Nginx 的模块是互相独立的,低耦合高内聚。

图片

nginx带参数转发链接

https://blog.csdn.net/ixr_wang/article/details/7359825

配置示例

image-20210330153357593

修改root文件下的ngnix,新加一个server()

/default.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
server {
listen 80; #ipv4的端口
listen [::]:80; #ipv6的端口
server_name localhost;

#charset koi8-r; 在Unicode未流行之前,KOI8-R 是最为广泛使用的俄语编码,使用率甚至比ISO 8859-5还高
#access_log /var/log/nginx/host.access.log main;

location ~ .*\.(gif|jpg|jpeg|png)$ {
expires 24h;
root /yxtl/data/;#鎸囧畾鍥剧墖瀛樻斁璺緞
access_log /usr/local/websrv/nginx-1.9.4/logs/images.log;#鏃ュ織瀛樻斁璺緞
proxy_store on;
proxy_store_access user:rw group:rw all:rw;
proxy_temp_path /home/images/;#鍥剧墖璁块棶璺緞
proxy_redirect off;
proxy_set_header Host 127.0.0.1;
client_max_body_size 10m;
client_body_buffer_size 1280k;
proxy_connect_timeout 900;
proxy_send_timeout 900;
proxy_read_timeout 900;
proxy_buffer_size 40k;
proxy_buffers 40 320k;
proxy_busy_buffers_size 640k;
proxy_temp_file_write_size 640k;
if ( !-e $request_filename)
{
proxy_pass http://elinktech.cn;#榛樿80绔彛
}
}
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
location /api {
proxy_pass http://elinktech.cn:9005;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location /daohang.html {
proxy_pass http://elinktech.cn/#/;
proxy_redirect default;
}
location /shop.html {
proxy_pass http://elinktech.cn/#/shophome;
proxy_redirect default;
}

#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}

# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}

# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}

# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}

/nginx/default/conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
server {
listen 80;
listen [::]:80;
server_name elinktech.cn localhost;

#charset koi8-r;
#access_log /var/log/nginx/host.access.log main;

#location ~ .*\.(gif|jpg|jpeg|png)$ {
# expires 24h;
# root /yxtl/data/;
# access_log /usr/local/websrv/nginx-1.9.4/logs/images.log;
# proxy_store on;
# proxy_store_access user:rw group:rw all:rw;
# proxy_temp_path /home/images/
# proxy_redirect off;
# proxy_set_header Host 127.0.0.1;
# client_max_body_size 10m;
# client_body_buffer_size 1280k;
# proxy_connect_timeout 900;
# proxy_send_timeout 900;
# proxy_read_timeout 900;
# proxy_buffer_size 40k;
# proxy_buffers 40 320k;
# proxy_busy_buffers_size 640k;
# proxy_temp_file_write_size 640k;
# if ( !-e $request_filename)
# {
# proxy_pass http://elinktech.cn;
# }
#}
location / {
root /yxtl/data/shop;
index index.html index.htm;
}

location /api/yxtl/data/ {
alias /yxtl/data/;
index index.html index.htm;
}

location /api {
proxy_pass http://elinktech.cn:9005;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}

location /daohang.html {
proxy_pass http://elinktech.cn/#/;
proxy_redirect default;
}
location /shop.html {
proxy_pass http://elinktech.cn/#/shophome;
proxy_redirect default;
}

location /about.html {
proxy_pass http://elinktech.cn/#/auth/index;
proxy_redirect default;
}

#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}

# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}

# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}

# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}

/nginx/guanwang.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server {
listen 80;
listen [::]:80;
server_name www.elinktek.com.cn elinktek.com.cn;

location / {
root /yxtl/data/myindex/index;
index index.html index.htm;
}
location /mystatics/ {
alias /yxtl/data/myindex/mystatics/;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

/root/nginx/conf/nginx.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
user  nginx;
worker_processes 1;

error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
worker_connections 1024;
}

http {
include /etc/nginx/mime.types;
default_type application/octet-stream;

log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log /var/log/nginx/access.log main;

sendfile on;
#tcp_nopush on;

keepalive_timeout 65;

#gzip on;

include /etc/nginx/conf.d/*.conf;
}

/root/nginx/conf/default.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
server {
listen 80;
listen [::]:80;
server_name localhost;

#charset koi8-r;
#access_log /var/log/nginx/host.access.log main;

location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
location /api {
proxy_pass http://elinktech.cn:9005;
proxy_redirect default;
}
location /daohang.html {
proxy_pass http://39.101.193.57/#/;
proxy_redirect default;
}
location /shop/index {
proxy_pass http://39.101.193.57/#/shophome;
proxy_redirect default;
}

#error_page 404 /404.html;

# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}

# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}

# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}

# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}

/etc/nginx/nginx.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# For more information on configuration, see:
# * Official English Documentation: http://nginx.org/en/docs/
# * Official Russian Documentation: http://nginx.org/ru/docs/

user root;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
worker_connections 1024;
use epoll;
}

http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log /var/log/nginx/access.log main;

sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;

include /etc/nginx/mime.types;
default_type application/octet-stream;

# Load modular configuration files from the /etc/nginx/conf.d directory.
# See http://nginx.org/en/docs/ngx_core_module.html#include
# for more information.
include /etc/nginx/conf.d/*.conf;
proxy_next_upstream error;

upstream tornado{
server 127.0.0.1:8081;
}
upstream admin{
server 127.0.0.1:8080;
}

server {
listen 80 default_server;
# listen [::]:80 default_server;
server_name www.elinktech.cn;
root /usr/share/nginx/html;

# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;
location /admin {
proxy_pass http://admin ;
}
location /static/admin {
alias /root/project_helmet/tornado_admin/mystatics;
if ($query_string) {
expires max;
}
}

location /static {
alias /root/project_helmet/weixin_0.1/mystatics;
if ($query_string) {
expires max;
}
}

location / {
proxy_pass http://tornado ;
}

error_page 404 /404.html;
location = /404.html {
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}

# Settings for a TLS enabled server.
#
# server {
# listen 443 ssl http2 default_server;
# listen [::]:443 ssl http2 default_server;
# server_name _;
# root /usr/share/nginx/html;
#
# ssl_certificate "/etc/pki/nginx/server.crt";
# ssl_certificate_key "/etc/pki/nginx/private/server.key";
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 10m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
#
# # Load configuration files for the default server block.
# include /etc/nginx/default.d/*.conf;
#
# location / {
# }
#
# error_page 404 /404.html;
# location = /404.html {
# }
#
# error_page 500 502 503 504 /50x.html;
# location = /50x.html {
# }
# }

}

/etc/nginx/nginx.conf.default

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#user  nobody;
worker_processes 1;

#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;

#pid logs/nginx.pid;


events {
worker_connections 1024;
}


http {
include mime.types;
default_type application/octet-stream;

#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';

#access_log logs/access.log main;

sendfile on;
#tcp_nopush on;

#keepalive_timeout 0;
keepalive_timeout 65;

#gzip on;

server {
listen 80;
server_name localhost;

#charset koi8-r;

#access_log logs/host.access.log main;

location / {
root html;
index index.html index.htm;
}

#error_page 404 /404.html;

# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}

# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}

# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}

# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}


# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;

# location / {
# root html;
# index index.html index.htm;
# }
#}


# HTTPS server
#
#server {
# listen 443 ssl;
# server_name localhost;

# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;

# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;

# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;

# location / {
# root html;
# index index.html index.htm;
# }
#}

}

nginx部署静态网站

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server {
listen 80;
listen [::]:80;
server_name www.elinktek.com.cn;

#charset koi8-r;
#access_log /var/log/nginx/host.access.log main;

location / {
root /usr/share/nginx/mystatics;
index index.html;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;

}

nginx在一个服务器上部署多个网站

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
server {
listen 80 default_server;#只能配一个默认监听
servername aa.com;

server_name_in_redirect off;
proxy_set_header Host $host:$server_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

access_log /home/wwwlogs/access_default.log;

location / {
proxy_pass http://localhost:8080;
}
}

server {
listen 80;
server_name bb.com;

server_name_in_redirect off;
proxy_set_header Host $host:$server_port;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

location / {
proxy_pass http://localhost:8008;
}
}

另:

Nginx 是一个高性能的 HTTP 和反向代理服务器,特点是占用内存少,并发能力强,事实上 Nginx 的并发能力确实在同类型的网页服务器中表现较好。Nginx 专为性能优化而开发,性能是其最重要的要求,十分注重效率,有报告 Nginx 能支持高达 50000 个并发连接数。

01

Nginx 知识网结构图

Nginx 的知识网结构图如下:

图片

02

反向代理

正向代理:局域网中的电脑用户想要直接访问网络是不可行的,只能通过代理服务器来访问,这种代理服务就被称为正向代理。

图片

反向代理:客户端无法感知代理,因为客户端访问网络不需要配置,只要把请求发送到反向代理服务器,由反向代理服务器去选择目标服务器获取数据,然后再返回到客户端。

此时反向代理服务器和目标服务器对外就是一个服务器,暴露的是代理服务器地址,隐藏了真实服务器 IP 地址。

图片

03

负载均衡

客户端发送多个请求到服务器,服务器处理请求,有一些可能要与数据库进行交互,服务器处理完毕之后,再将结果返回给客户端。

普通请求和响应过程如下图:

图片

但是随着信息数量增长,访问量和数据量飞速增长,普通架构无法满足现在的需求。

我们首先想到的是升级服务器配置,可以由于摩尔定律的日益失效,单纯从硬件提升性能已经逐渐不可取了,怎么解决这种需求呢?

我们可以增加服务器的数量,构建集群,将请求分发到各个服务器上,将原来请求集中到单个服务器的情况改为请求分发到多个服务器,也就是我们说的负载均衡。

图解负载均衡:

图片

假设有 15 个请求发送到代理服务器,那么由代理服务器根据服务器数量,平均分配,每个服务器处理 5 个请求,这个过程就叫做负载均衡。

**04
**

动静分离

为了加快网站的解析速度,可以把动态页面和静态页面交给不同的服务器来解析,加快解析的速度,降低由单个服务器的压力。

动静分离之前的状态:

图片

动静分离之后:

图片

**06
**

Nginx安装

Nginx 如何在 Linux 安装

参考链接:

1
https://blog.csdn.net/yujing1314/article/details/97267369

Nginx 常用命令

查看版本:

1
./nginx -v

启动:

1
./nginx

关闭(有两种方式,推荐使用 ./nginx -s quit):

1
2
./nginx -s stop
./nginx -s quit

重新加载 Nginx 配置:

1
./nginx -s reload

Nginx 的配置文件

配置文件分三部分组成:

①全局块

从配置文件开始到 events 块之间,主要是设置一些影响 Nginx 服务器整体运行的配置指令。

并发处理服务的配置,值越大,可以支持的并发处理量越多,但是会受到硬件、软件等设备的制约。

图片

②events 块

影响 Nginx 服务器与用户的网络连接,常用的设置包括是否开启对多 workprocess 下的网络连接进行序列化,是否允许同时接收多个网络连接等等。

支持的最大连接数:

图片

③HTTP 块

诸如反向代理和负载均衡都在此配置。

1
2
3
location[ = | ~ | ~* | ^~] url{

}

location 指令说明,该语法用来匹配 url,语法如上:

  • =:用于不含正则表达式的 url 前,要求字符串与 url 严格匹配,匹配成功就停止向下搜索并处理请求。
  • ~:用于表示 url 包含正则表达式,并且区分大小写。
  • ~*:用于表示 url 包含正则表达式,并且不区分大小写。
  • ^~:用于不含正则表达式的 url 前,要求 Nginx 服务器找到表示 url 和字符串匹配度最高的 location 后,立即使用此 location 处理请求,而不再匹配。
  • 如果有 url 包含正则表达式,不需要有 ~ 开头标识。

07

反向代理实战

①配置反向代理

目的:在浏览器地址栏输入地址 www.123.com 跳转 Linux 系统 Tomcat 主页面。

②具体实现

先配置 Tomcat,因为比较简单,此处不再赘叙,并在 Windows 访问:

图片

具体流程如下图:

图片

修改之前:

图片

配置如下:

图片

再次访问:

图片

③反向代理 2

目标:

  • 访问 http://192.168.25.132:9001/edu/ 直接跳转到 192.168.25.132:8080
  • 访问 http://192.168.25.132:9001/vod/ 直接跳转到 192.168.25.132:8081

准备:配置两个 Tomcat,端口分别为 8080 和 8081,都可以访问,端口修改配置文件即可。

图片

图片

新建文件内容分别添加 8080!!!和 8081!!!

图片

图片

响应如下图:

图片

图片

具体配置如下:

图片

重新加载 Nginx:

1
./nginx -s reload

访问:

图片

图片

实现了同一个端口代理,通过 edu 和 vod 路径的切换显示不同的页面。

**
**

反向代理小结

第一个例子:浏览器访问 www.123.com,由 host 文件解析出服务器 ip 地址
192.168.25.132 www.123.com。

然后默认访问 80 端口,而通过 Nginx 监听 80 端口代理到本地的 8080 端口上,从而实现了访问 www.123.com,最终转发到 tomcat 8080 上去。

第二个例子:

  • 访问 http://192.168.25.132:9001/edu/ 直接跳转到 192.168.25.132:8080
  • 访问 http://192.168.25.132:9001/vod/ 直接跳转到 192.168.25.132:8081

实际上就是通过 Nginx 监听 9001 端口,然后通过正则表达式选择转发到 8080 还是 8081 的 Tomcat 上去。

**08
**

负载均衡实战

①修改 nginx.conf,如下图:

图片

图片

②重启 Nginx:

1
./nginx -s reload

③在 8081 的 Tomcat 的 webapps 文件夹下新建 edu 文件夹和 a.html 文件,填写内容为 8081!!!!

④在地址栏回车,就会分发到不同的 Tomcat 服务器上:

图片

图片

负载均衡方式如下:

  • 轮询(默认)。
  • weight,代表权,权越高优先级越高。
  • fair,按后端服务器的响应时间来分配请求,相应时间短的优先分配。
  • ip_hash,每个请求按照访问 ip 的 hash 结果分配,这样每一个访客固定的访问一个后端服务器,可以解决 Session 的问题。

图片

图片

图片

09

动静分离实战

什么是动静分离?把动态请求和静态请求分开,不是讲动态页面和静态页面物理分离,可以理解为 Nginx 处理静态页面,Tomcat 处理动态页面。

动静分离大致分为两种:

  • 纯粹将静态文件独立成单独域名放在独立的服务器上,也是目前主流方案。
  • 将动态跟静态文件混合在一起发布,通过 Nginx 分开。

动静分离图析:

图片

实战准备,准备静态文件:

图片

图片

配置 Nginx,如下图:

图片

Nginx 高可用

如果 Nginx 出现问题:

图片

解决办法:

图片

前期准备:

  • 两台 Nginx 服务器
  • 安装 Keepalived
  • 虚拟 ip

安装 Keepalived:

1
2
3
[root@192 usr]# yum install keepalived -y
[root@192 usr]# rpm -q -a keepalived
keepalived-1.3.5-16.el7.x86_64

修改配置文件:

1
2
[root@192 keepalived]# cd /etc/keepalived
[root@192 keepalived]# vi keepalived.conf

分别将如下配置文件复制粘贴,覆盖掉 keepalived.conf,虚拟 ip 为 192.168.25.50。

对应主机 ip 需要修改的是:

  • smtp_server 192.168.25.147(主)smtp_server 192.168.25.147(备)
  • state MASTER(主) state BACKUP(备)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
global_defs {
notification_email {
acassen@firewall.loc
failover@firewall.loc
sysadmin@firewall.loc
}
notification_email_from Alexandre.Cassen@firewall.loc
smtp_server 192.168.25.147
smtp_connect_timeout 30
router_id LVS_DEVEL # 访问的主机地址
}

vrrp_script chk_nginx {
script "/usr/local/src/nginx_check.sh" # 检测文件的地址
interval 2 # 检测脚本执行的间隔
weight 2 # 权重
}

vrrp_instance VI_1 {
state BACKUP # 主机MASTER、备机BACKUP
interface ens33 # 网卡
virtual_router_id 51 # 同一组需一致
priority 90 # 访问优先级,主机值较大,备机较小
advert_int 1
authentication {
auth_type PASS
auth_pass 1111
}
virtual_ipaddress {
192.168.25.50 # 虚拟ip
}
}

启动代码如下:

1
[root@192 sbin]# systemctl start keepalived.service

图片

访问虚拟 ip 成功:

图片

关闭主机 147 的 Nginx 和 Keepalived,发现仍然可以访问。

原理解析

图片

如下图,就是启动了一个 master,一个 worker,master 是管理员,worker是具体工作的进程。

图片

worker 如何工作?如下图:

图片

worker 数应该和 CPU 数相等;一个 master 多个 worker 可以使用热部署,同时 worker 是独立的,一个挂了不会影响其他的。

  • ngnix

展开全文 >>

java基础知识

2021-03-29

java的一些基本概念

关键字

关键字:是被java语言赋予特定含义的单词

特点:组成关键字的字母全部小写

goto和const作为保留字存在,目前并不使用(jdk1.7中)

保留字:在jdk新版中可能提升为关键字

关键字

用于定义数据类型的关键字:class interface[接口] byte short int long float double char boolean void

用于定义数据类型值的关键字:true false null

用于定义流程控制的关键字:if else switch case default while do for break continue return

用于定义访问权限修饰符的关键字:public protected private

用于定义类,函数,变量修饰符的关键字:abstract final static synchronized[同步]

用于定义类与类之间关系的关键字:extends implements

用于定义建立实例与引用实例,判断实例的关键字:new this super instanceof [实例]

用于异常处理的关键字:try catch finally throw throws

用于包的关键字:packge import

其他修饰符关键字:native[本地] strictfp[精确浮点] transient[短暂的,用于无需序列化] volatile[易变的,保证单次读/写的原子性] assert[宣称]

标识符

标识符就是给类,接口,方法,变量等起名字时使用的字符序列

组成规则:英文字母,数字字符,美元符$和下划线_(只有这几种,不能出现#¥之类的)

Tips:不能以数字开头,不能是java的关键字,严格区分大小写,不能出现空格。

常见命名规则:见名知意

包:其实就是文件夹,用于把相同的类名进行区分(全部小写)

单级:xxx

多级:cn.xxx

类或者接口:

一个单词:单词的首字母必须大写

多个单词:每个单词的首字母必须大写[驼峰原则]

方法或者变量:

一个单词:单词的首字母必须小写

多个单词:从第二个单词开始,每个单词的首字母大写[驼峰原则]

常量:

一个单词:全部大写

多个单词:每个字母都大写,用_隔开

常量

在程序执行的过程中其值不可以发生改变

分类:

1.字面值常量

字符串常量:用双引号括起来的内容

整数常量:所有整数

小数常量:所有小数

字符常量:用单引号括起来的单个字符

布尔常量:较为特有,只有false和true

空常量:null

2.自定义常量(final int x=10;)

整数常量提供了4种表现形式:

​ 二进制(binary):由0,1组成,以0b开头,或者结尾+B

​ 八进制(octal):由0,1,…7组成,以0开头,或者结尾+O

​ 十进制(decimal):由0,1,…9组成,默认是十进制,或者结尾+D

​ 十六进制(hex):由0,1,…,9,a,b,c,d,e,f(大小写均可),以0x开头,或者结尾+H

1byte=8bit 1个字节=8位

1k=1024byte

1m=1024k

1g=1024m

1t=1024g

进制越大,表现形式越短。

二、八、十、十六进制间转换:

​ 二进制转换为八进制:把二进制的数据,从右开始,每三位一组合,最左边不够的时候补0。然后分别计算出对应的十进制数值,最后把每个十进制的数据组合起来就是一个八进制数据。

​ 二进制转换为十六进制:十六进制就是每4位组合。

​ 二进制转换为十进制:8421码。8421码是中国大陆的叫法,8421码是BCD码中最常用的一种。在这种编码方式中每一位二值代码的1都是代表一个固定数值,把每一位的1代表的十进制数加起来,得到的结果就是它所代表的十进制数码。

变量

在程序执行的过程中,在一定范围内其值可以发生改变的量。本质上来说,变量其实是内存中的一小块区域,使用变量名来访问这块区域。因此,每个变量使用前必须要先申请(声明),然后必须进行赋值(填充内容)才能使用。

变量可以用来不断的存放同一类型的常量,并且可以重复使用。

定义格式(固定的):

​ 数据类型 变量名=初始化值;

Java是强类型语言,对于每一种数据类型都定义了明确的具体数据类型,由内存总分配了不同大小的内存空间。

作用域:变量定义在那一级大括号中,哪个大括号的范围就是这个变量的作用域。相同的作用域中不能定义两个同名变量。

没有初始化值不能直接使用,只要在使用前给值即可,不一定非要在定义的时候给值,推荐在定义的时候给值。

eg: 数据类型 变量名;

​ 变量名=初始化值;

一行建议只定义一个变量,可以定义多个,但是不建议。

数据类型:

1.基本数据类型:4类8种

  • 数值型:

    ⑴整数型

    byte 1字节 -2^7^2^7^ -128127

    short 2字节 -2^15^2^15^-1 -3276832767

    int 4字节 -2^31^2^31^-1 -2,147,483,6482,147,483,647 默认为int

    long 8字节 -2^63^~2^63^-1 19位数 长的整数型变量后缀使用L或l,建议使用L。因为l会和1分不清

    ⑵浮点类型

    float 4字节 -3.403 E^38^3.403E^38^ (2^-127^2^128^) 单精度浮点数用F或f,建议使用F

    double 8字节 -1.798E^308^1.798E^308^ (2^-1023^2^1024^) 默认为 double

  • 字符型:char 2字节 (可以存储中文汉字)

  • 布尔型:boolean 1字节

2.引用数据类型:

  • 类:class

  • 接口:interface

  • 数组:[]

一般来说,我们在运算的时候,要求运算的数据类型必须一致。boolean类型不能转换为其他数据类型

默认转换,隐式转换(从小到大的转换):

byte,short,char→int→long→float→double 出现后面的数据类型,结果必须为最后面的数据类型

byte,short,char相互之间不转换,他们一旦参与运算首先要转换成int数据类型

long是8字节,float是4字节,可以转换是因为它们的底层的存储结构不同;float表示的数据范围比long 的范围要大。

强制转换,显示转换(从大到小的转换):

格式:目标数据类型 变量=(目标数据类型) (被转换的数据);

不要随意使用强制转换,因为它隐含了精度损失的问题。其结果是数据的补码在计算机内被截断后的值。

byte的范围是-128~127,当(byte)128,其实输出的是-128。(byte)129输出的是-127

128:10000000

-128:10000000(这里的1既是符号位,也是数值位)

变量相加,会首先看类型问题,最终把结果赋值的时候也会考虑类型问题。

常量相加,首先做加法,然后看结果是否在赋值的数据类型范围内,如果不是,才会报错。

eg:

1.一个字符,结果就是一个字符。

2.一个字符+一个整数:参考ASCII表,结果为int整数型

‘0’ 48 ‘A’ 65 ‘a’ 97

image-20210323162019525

3.字符串+一个字符+整数:第一个+是连接符。字符串+整数

4.一个字符+整数+字符串:第一个+是加号,ASCII表运算成整数类型。整数+字符串

规则:第一个东西和第二个东西先进行运算并输出,在计算第三个,第四个。

局部变量和成员变量的区别:

1.在类中的位置不同:

局部变量:在方法定义中或者方法声明上。

成员变量:在类中方法外。

2.在内存中的位置不同:

局部变量:在栈内存

成员变量:在堆内存

3.生命周期不同:

局部变量:随着方法的调用而存在,随着方法的调用完毕而消失

成员变量:随着对象的创建而存在,随着对象的消失而消失

4.初始化值不同:

局部变量:没有默认初始化值,必须定义,赋值然后才能使用

成员变量:有默认的初始化值

注意事项:局部变量名称可以和成员变量名称一样,在方法中使用的时候,采用的是就近原则。

运算符

运算:对常量和变量进行操作的过程称为运算

运算符:对常量和变量进行操作的符号称为运算符

操作数:参与运算的数据称为操作数

表达式:用运算符把常量或变量连接起来符合java语法的句子可以称为表达式
1.算术运算符: +,-,*,/,%,++,–

+的用法:a.加法 b.正号 c.字符串连接符

整数相除只能得到整数,若要得到小数,需要把操作的数据中的任意一个数据变为浮点数类型。

/获取的是除法操作的商,%获取的是除法操作的余数。

++自增1,–自减1。就是对变量进行自增或者自减。只能对变量

单独使用:放在操作数前或者操作数后效果一样(常用)

参与运算使用:放在操作数的前面++a,先自增或自减再参与运算(赋值)。放在操作数的后面a++,先参与运算(赋值)再自增或自减。

2.赋值运算符

基本的赋值运算符:=

把=右边的数据赋值给左边(左边必须是变量)

扩展的赋值运算符:+=,-=,*=,/=,%=

+=把左边和右边做加法,然后赋值给左边。(左边必须是变量)

s +=1;不是等价于s = s+1;而是等价于s = (s的数据类型)(s+1); 扩展的赋值运算符其实隐含了一个强制类型转换。

3.比较运算符(关系运算符)

==,!=,<,>,<=,>=,instanceof(检查是否是类的对象)

无论操作是简单还是复杂,比较运算符的结果都是boolean型,要么是true,要么是false。

“==”不能写成”=”

4.逻辑运算符

&与(AND) ()&() 有false则false

|或(OR) ()|() 有true则true

^异或(XOR) ()^() 相同为false,不同为true

!非(NOT) !() 非false则true,非true则false,偶数个不改变本身。

&&短路与(AND) ()&&()

||短路或(OR) ()||()

逻辑运算符用于连接布尔型表达式,不能写3<x<6,而应该是x>3&x<6。

&和&&的区别(|和||同理):最终结果一样

单&时,左边无论真假,右边都会进行运算。

双&时,若左边为真,右边参与运算,若左边为假,右边不参与运算(不执行)。

开发中常用的逻辑运算符:&&,||,!

5.位运算符

&位与运算 有0则0

|位或运算 有1则1

^位异或运算 相同为0,不同为1

~按位取反运算符 0变1,1变0 (在计算机中,所有参与运算的都是补码)

<<左移 左边最高位丢弃,右边补齐0 把<<左边的数据乘以2的移动次幂

>>右移 最高位是0,左边补齐0;最高位是1,左边补齐1 把>>右边的数据除以2的移动次幂

>>>无符号右移 无论最高位是0还是1,左边都补齐0

要做位运算,首先要把数据转换为二进制。

&和|:两边是boolean型,代表逻辑与/或短路 两边是数据类型,代表的是位运算。

^的特点:一个数据对另一个数据位异或两次,该数本身不变。(应用:数据加密,异或后发给你,你在异或一下就得到原数据)

eg:实现两个整数变量的交换:a b

  • 使用第三方变量(开发中用)

    c=a a=b b=c

  • 使用位异或实现(面试用)

    左边aba,右边a^b: a=a^b b=a^b a=a^b

  • 用变量相加的做法

    a=a+b b=a-b a=a-b

  • 一句话搞定

    b=(a+b)-(a=b)

6.三目运算符

(关系表达式)?表达式1:表达式2

关系表达式结果为true,则结果为表达式1,为false,则结果为表达式2.

(单目运算符:~3 双目运算符:3+4 )

数组

存储同一数据类型的多个元素的容器。

定义格式:(2种)

  • 数据类型[] 数组名; 定义一个该数据类型的数组(数组名)变量

  • 数据类型 数组名[]; 定义一个该数据类型的(数组名)数组变量

建议使用第一种,因为其他语言(如C#)正在淘汰第二种。

数组的初始化:java中的数组必须先初始化,然后才能使用。

初始化就是为数组中的每个元素分配内存空间,并为每个元素赋值。

初始化方式:(2种)

  • 动态初始化:初始化时只指定数组的长度,由系统为数组分配初始值。

格式: 数据类型[] 数组名= new 数据类型[数组长度];

eg: int[] arr = new int [4];

​ int:说明数组中元素的数据类型是int类型

​ []:说明这是一个数组

​ arr:数组的名称

​ new:为数组分配内存空间

​ int:说明数组中元素的数据类型是int类型

​ [4]:说明这是一个长度为4的数组,数组中元素的个数为4

这时候直接打印输出arr的值 就是arr的内存地址值。

数组中的每个元素都是有编号的,并且是从0开始,最大编号是数组的长度-1。

用数组名和编号的组合就可以获取数组中的指定编号的元素,这个编号叫索引。

通过数组名访问数据的格式是:数组名 [索引]

arr[0]、arr[1]、arr[2]、arr[3]没有赋值时,均为默认值。

  • 静态初始化:初始化时指定每个数组元素的初始值,由系统决定数组长度。

格式: 数据类型[] 数组名 = new 数据类型[]{元素1,元素2,…};

eg: int[] arr = new int[]{1,2,3};

简化写法int[] arr={1,2,3};

arr的输出值就是arr的地址值。

注意 不要同时动态和静态进行初始化。

数组操作的两个常见小问题:

1.数组索引越界异常(ArrayIndexOutOfBoundsException):你访问了不存在的索引。

2.空指针异常(NullPointerException):数组已经不再指向堆内存了,而你还用数组名去访问元素。引用类型的常量:空常量 null

eg:arr=null;

数组常见方法

  • 数组遍历:依次输出数组中的每一个元素。
1
2
3
4
5
public static void printArray(int[] arr){
for(int i=0;i<arr.length;i++){
System.out.println(arr[i]);
}
}
  • 数组获取最值:获取数组中的最大值、最小值。
1
2
3
4
5
6
7
8
9
public static int getMax(int[] arr) {
int max = arr[0];
for (int x = 1; x < arr.length; x++) {
if (arr[x] > max) {
max = arr[x];
}
}
return max;
}
  • 数组元素逆序:元素对调。
1
2
3
4
5
6
7
public static void reverse(int[] arr) {
for (int x = 0; x < arr.length / 2; x++) {
int temp = arr[x];
arr[x] = arr[arr.length - 1 - x];
arr[arr.length - 1] = temp;
}
}
  • 数组查表法:根据键盘录入索引,查找对应元素。

  • 数组元素查找:查找指定元素第一次在数组中出现的索引。如果找不到数据,我们一般返回一个负数即可,一般是-1。

  • 数组排序和二分查找。

二维数组:元素为一维数组的数组。

格式1:

列是一样的用: 数据类型[] [] 数组名=new 数据类型[m] [n];

​ m表示这个二维数组有多少个一维数组。

​ n表示每一个一维数组的元素个数。

以下两个格式也可以但是不推荐:

数据类型 数组名[] []=new 数据类型[m] [n];

数据类型[] 数组名[]=new 数据类型[m] [n];

int x,y; 定义了一个x,一个y的int型数据。

int[] x,y[]; 定义了一个x的一维数组和一个y的二维数组。

格式2:

列是变化的用: 数据类型[] [] 数组名=new 数据类型[m] [];

m表示这个二维数组有m个一维数组

一维数组的元素个数可以动态给出。

格式3:

静态初始化: 数据类型[][] 数组名=new 数据类型[][]{{ 元素1,元素2,.... }, { 元素1,元素2,.... }, { 元素1,元素2,.... }};

简化版:数据类型[][] 数组名={{元素1,元素2,....}, {元素1,元素2,....}, {元素1,元素2,....}};

二维数组的遍历:

外循环控制的是二维数组的长度,也就是一维数组的个数。

内循环控制的是一维数组的长度。

1
2
3
4
5
6
for(int x = 0;x<arr.length;x++)
{
for (int y = 0; y < arr[x].length; y++) {
System.out.println(arr[x][y]);
}
}

方法

方法就是完成特定功能的代码块。(在很多语言里面都有函数的定义,函数在java中被称为方法)

格式:

1
2
3
4
修饰符  返回值类型  方法名(参数类型 参数名1,参数类型 参数名2,...){
函数体;
return 返回值;
}

修饰符: public static

返回值类型:就是返回结果的数据类型。

方法名:符合命名规则即可,方便我们调用。

参数:

​ 实际参数:就是实际参与运算时填入的参数。

​ 形式参数:就是方法定义上的,用于接收实际参数。

​ 形式参数的类型有2种:基本类型和引用类型。

​ 基本类型(4类8种)中形式参数的改变不影响实际参数。

​ 引用类型(类、接口、数组)中形式参数的改变直接影响实际参数。

如果一个方法的形式参数是一个类类型(引用类型),实际参数需要的是这个类的对象。

​ 参数类型:参数的数据类型

​ 参数名:变量名

​ 方法体语句:完成功能的代码。

​ return:结束方法,用于返回结果。

​ 返回值:就是功能的结果,由return带给调用者。

要写一个方法,必须明确两个东西:

A:返回值类型:结果的数据类型

B:参数列表:要传递的参数及参数类型。

方法的执行特点:不调用,不执行。

调用:

​ A:单独调用,一般来说没有意义,所以不推荐

​ B:输出调用,但是不够好,因为可能需要针对结果进行进一步的操作。

​ C:赋值调用,推荐方案。

方法的注意事项:

​ 1.方法不调用,不执行。

​ 2. 方法与方法之间是平级关系,不能嵌套定义。

​ 3.方法定义的时候参数之间用逗号隔开。

​ 4.方法调用的时候不用再传递数据类型。

​ 5. 如果方法有明确的返回值,一定要有return带回一个值。

void类型返回值的方法调用:无返回结果,只有单独调用一种。

方法的重载:方法的功能相同,参数列表不同的情况,为了见名知意,java允许它们起一样的名字。jvm会根据不同的参数调用不同的功能。

方法重载的特点:与返回值类型无关,只看方法名和参数列表。在调用时,虚拟机通过参数列表的不同(参数个数、类型不同)来区分同名方法。

//一个方法不能访问另一个方法中的局部变量。

类

单独开一个文章总结。

包

其实就是文件夹。

作用:把相同的类名放到不同的包中;对类进行分类管理。

1.可以按照功能分(增加、删除、修改、查找)

2.可以按照模块分(推荐)

包的定义: package 包名;

多级包用.分开即可

注意事项:

package语句必须是程序的第一条可执行(可被虚拟机识别)的代码。package语句在一个java文件中只能有一个。如果没有package,默认表示无包名

带包的编译和运行:

  • 手动式:

    a.编写一个java文件

    b.通过javac命令编译该java文件

    c.在文件夹中的当前目录下手动创建包名

    d.把b步骤的class文件放到c步骤的最底层包

    e.回到和包根目录在同一目录的地方,然后带包运行java cn.xxx.类名

  • 自动式:

    a. javac编译的时候带上-d即可(eg: javac -d . 类名.java)

    b.通过java命令执行。和手动式一样

不同包下类之间的访问,每次都需要加包的全路径,会很麻烦,所以java提供了导包功能。

导包格式:import 包名;

注意:

这种方式导入是到类的名称import cn.xxx.类名;虽然可以写import cn.xxx.代表导入这个cn.xxx包下的所有类,但是不建议。建议我们用谁导入谁。

package(只能有一个)->import(可以有多个)->class(可以有多个,以后建议是一个,使结构清晰)

权限修饰符:

​ public:同一个类中;同一个包下子类,无关类;不同包下该类是那个包的类的子类;不同包下无关类

​ protected:同一个类中;同一个包下子类,无关类;不同包下该类是那个包的类的子类

​ 默认:同一个类中;同一个包下子类,无关类

​ private:同一个类中

总结:如果想都访问,用public;如果想访问不同包的子类,用protected(pretected是用来修饰子类的);如果只想同一个包内访问用默认,不加;如果只想在本类中访问,用private。

修饰符:

​ 权限修饰符:private,默认的,protected,public

​ 状态修饰符:static,final

​ 抽象修饰符:abstract

类及其组成可以用的修饰符:

​ 类:默认,public;final,abstract 经常使用的是public

​ 成员变量:四种权限修饰符均可;final,static 经常使用的是private

​ 构造方法:四种权限修饰符均可;其他不可 经常使用的是public

​ 成员方法:四种权限修饰符均可;final,static,abstract 经常使用的是public

注:自己写项目时的类名不要和常用的API的类名相同。

​ 复制代码的时候,很容易把那个类所在的包也导入过来,容易出现不能理解的问题。

  • 基础概念

展开全文 >>

java中常用类

2021-03-26

API(Application Programming Interface)概述:就是java提供给我们使用的类,这些类将底层的实现封装了起来,我们不需要关心这些类是如何实现的,只需要学习这些类如何使用,我们可以通过查找帮助文档来了解java提供的API如何使用。

Object类

JDK1.0

类层次结构的根类,每个类都直接或者间接继承自Object类。 所有对象(包括数组)都实现了这个类的方法。

构造方法: public Object()

成员方法:

1
public int hashCode() 

public int hashCode() 返回该对象的哈希码值

哈希值是根据哈希算法计算出来的一个值,这个值和地址值有关,但不是实际地址值,可以理解为地址值。

public final Class getClass() 返回此Object的运行时类。

//Class也是一个类,是Class对象建模的类的类型。它其中有个方法:

//public String getName() 以String的形式返回此Class对象所表示的全类名称

public String toString() 返回该对象的字符串表示

toString()方法的值等价于:getClass().getName()+’@’+Integer.toHexString(hashCode())

这个信息是没有任何意义的,所以建议所有子类都重写此方法。

重写:建议重写成把该类的所有成员变量值组成返回。(右键–resources–toString 自动生成,重写的最终版方案就是自动生成)

注意:直接输出一个对象的名称,其实就是调用该对象的toString()方法。

public boolean equals(Object obj) 指示其他某个对象是否与此对象“相等”

这个方法默认情况下比较的是地址值。

比较地址值一般意义不大,所以我们要重写该方法。重写完的方法一般是用来比较对象的成员变量值是否相同。

String的equals()方法是重写自Object类的,比较的是字符串的内容是否相同

重写的代码优化:提高效率,提高代码的健壮性。

最终版其实是自动生成。

==:基本类型:比较的就是值是否相同

引用类型:比较的就是地址值是否相同

equals:

只能比较引用类型:默认情况比较的是地址值,不过我们可以根据自己的情况重写该方法。一般重写都是自动生成,比较对象的成员变量值是否相同。

protected void finalize() 当垃圾回收器不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。永远垃圾回收,但是什么时候回收不确定。

protected Object clone() 创建并返回此对象的一个副本。

因为是prtected,所以需要重写该方法。

Cloneable:此类实现了Cloneable接口,以指示Object.clone()方法可以合法的对该类实力进行按字段复制。这个接口是****标记****接口,告诉我们实现该接口的类就可以实现对象的复制了。(就只是起个标记的作用,只有实现该接口的类才能被clone)

意义:clone之后改动对以前的数据无影响。而直接赋值引用改动会对以前的数据产生影响。(这只是浅克隆)

Scanner类

需要导包:import java.util.Scanner

JDK5以后用于获取用户的键盘输入

构造方法:public Scanner(InputStreamsource)

System类下有一个静态的字段: public static final InputStream in; 标准的输入流,对应着键盘录入。

成员方法:

hasNextXxx() ****判断****是否还有下一个输入项,其中Xxx可以是Int,Double等。如果需要判断的是否包含下一个字符串,可以省略Xxx

nextXxx() 获取下一个输入项。

默认情况下,Scanner使用空格,回车等作为分隔符。

常用的方法:

public int nextInt()

public String nextLine()

先获取一个数值,在获取一个字符串,就会出现问题(因为换行符号被当成是字符串输入)

解决:A.先获取一个数值后,再创建一个新的键盘录入对象获取字符串。

B.把所有的数据都先按照字符串获取,然后要什么在对应转换成什么。

键盘录入数据

1.导包:import java.util.Scanner (import必须出现在所有的class前面)

2.创建对象:Scanner sc=new Scanner(System.in)

3.接收数据:int x=sc.nextInt();

String s=sc.nextLine();

String类/StringBuffer类/StringBuilder类

字符串是由多个字符组成的一串数据(字符序列)

字符串可以看成是字符数组。

字符串字面值”abc”也可以看成是一个字符串对象。

字符串是常量,一旦被赋值,就不能被改变。字符串直接赋值的方式是先到方法区中的字符串常量池里面去找,如果有就直接返回,没有就创建返回。(赋的值不能变,引用可以变)

  1. String s=”hello”; s+=”world”; 则s的输出结果是:helloworld

因为s的赋值hello不能改变,但是+world后在方法区的常量池中会重新生成一个地址值指向栈的s。

  1. String s=new String(“hello”);和String s=”hello”; 的区别?

区别就是前者会生成2个或1个(可能常量池中已经存在hello常量)对象,而后者只会生成1个或0个对象。(因为前者会在堆内存中生成一个地址值,而后者是方法区中的常量池直接指向栈内存)

字符串如果是变量相加,先开空间,在拼接。

字符串如果是常量相加,直接加,然后在常量池中找,如果有就直接返回,否则就创建。

构造方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public String()   //空构造

public String(byte[] bytes) //把字节数组转成字符串

public String(byte[] bytes,int index,int length)//把字节数组的一部分转成字符串

public String(char[] value) //把字符数组转成字符串

public String(char[] value,int index,int count)//把字符数组的一部分转成字符串

public String(String original) //把字符串常量值转成字符串



String s=”hello”; //这个虽然不是构造方法,但结果也是一个字符串对象

方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public int length()  :返回此字符串的长度。

String类的判断功能:

boolean equals(Object obj) 比较字符串的内容是否相同,区分大小写

boolean equalsIgnoreCase(String str)比较字符串的内容是否相同,忽略大小写

boolean contains(String str) 判断大串中是否包含小串(连续的)

boolean startsWith(String str) 判断字符串是否以某个指定的字符串开头

boolean endsWith(String str) 判断字符串是否以某个指定的字符串结尾

boolean isEmpty() 判断字符串是否为空

字符串内容为空(“”)和字符串对象为空(null)是两个概念。

空指针不能调用方法。

String类的获取功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int length()   获取字符串的长度

char charAt(int index) 获取指定索引位置的字符

int indexOf(int ch) 返回指定字符在此字符串中第一次出现的索引

为什么是int类型不是字符类型:因为’a’和97都可以代表’a’,但是如果写字符类型的话就不能写数字了,因为int类型的范围比字符char的类型范围大,要强转,可能会出错。

int indexOf(String str) 返回指定字符串在此字符串中第一次出现的索引

int indexOf(int ch,int fromIndex) 返回指定字符在此字符串中从指定位置后第一次出现的索引

int indexOf(String str,int fromIndex) 返回指定字符串在此字符串中从指定位置后第一次出现的索引

String substring(int start) 从指定位置开始截取字符串,默认到末尾(包括start索引)

String substring(int start,int end) 从指定位置开始到指定位置结束截取字符串(包括start索引但是不包括end索引)

遍历数组:length()和charAt()即可

统计一个字符串中大、小写字母、数字出现的次数:定义统计变量,遍历数组,每个字符判断。(判断就直接判断,因为字符判断时候都会转化成int型在判断)

String类的转换功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
byte[] getBytes()   把字符串转换成字节数组

char[] toCharArray() 把字符串转换成字符数组

static String valueOf(char[] chs) 把字符数组转换成字符串

static String valueOf(int i) 把int类型转换成字符串

String类的valueOf方法可以把任意类型的数据转成字符串(方法的重载)

String toLowerCase() 把字符串转成小写(原字符串不变)

String toUpperCase() 把字符串转成大写

String concat(String str) 把字符串拼接

String format()

String类的其他功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
替换功能:

String replace(char old,char new)

String replace(String old,String new)

去除字符串的两端空格(不去除中间的):

String trim()

按字典顺序比较两个字符串 : (依次比较字符串的int值,再做减法,返回值。如果两个字符串的长度不同,返回长度差。(都是看源码得知))

int compareTo(String str)

int compareToIgnoreCase(String str)

字符串反转:定义一个新的字符串,将给定的字符串倒着遍历,拼接到新的字符串。

统计大串中小串出现的次数:定义一个统计变量,获取小串在大串中第一次出现的索引(用indexOf()方法,不存在的话会返回-1),将索引+小串的长度作为起始位置截取原始大串并赋值给原始大串(用int indexOf(String str,int fromIndex)方法),继续获取小串在大串中第一次出现的索引(用indexOf()方法,不存在的话会返回-1)。

匹配功能:

1
2
3
4
5
6
7
8
9
10
11
1.
public int indexOf( String  str )

2.
public boolean contains( CharSequence  s )

3.通过正则表达式 + matches方法
publicboolean matches( String regex )

4.字符串精确匹配,不考虑大小写
public boolean equalsIgnoreCase( String anotherString )

StringBuffer类

如果对字符串进行拼接操作,每次拼接都会构建一个新的String对象,既耗时又耗空间,所以产生了StringBuffer类。

线程安全的可变字符序列。

安全–同步–数据是安全的

不安全–不同步–效率高一些

StringBuffer和String的区别:前者长度和内容可变,后者不可变。

当字符串拼接时,前者不会浪费太多的资源。

StringBuffer的构造方法:

1
2
3
4
5
public StringBuffer()  无参构造方法

public StringBuffer(int capacity) 指定容量的字符串缓冲区对象 默认为16

public StringBuffer(String str) 指定字符串内容的字符串缓冲区对象

StringBuffer的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public int capacity():返回当前容量。  理论值

public int length():返回长度(字符数)。实际值

StringBuffer添加功能

public StringBuffer append(String str) 可以把任意类型的数据添加到字符串缓冲区里面,并返回字符串缓冲区本身。(所以可以使用链式编程)

public StringBuffer insert(int offset,String str) 在指定位置把任意类型的数据插入到字符串缓冲区里面,并返回字符串缓冲区本身

StringBuffer删除功能

public StringBuffer deleteCharAt(int index) 删除指定位置的字符,并返回本身

public StringBuffer delete(int start,int end) 删除从指定位置开始指定位置结束的内容,并返回本身 [) 包左不包右

public StringBuffer delete(0,x.length())用于初始化清空容器。

StringBuffer替换功能

public StringBuffer replace(int start,int end,String str)

StringBuffer反转功能

public StringBuffer reverse()

StringBuffer截取功能

public String substring(int start)这个索引后的所有字符串输出并返回到String

public String substring(int start,int end) 把这段截取出来输出String

截取功能的返回值类型是String类型,本身String Buffer没有发生改变(前面的都是容器内容发生了变化,所以值都改变了,但是容器本身没有变化)

类之间的转换:

A–B的转换:把A转换成B是为了使用B的功能

B–A的转换:要的结果是A类型,所以需要再转回来。

String和StringBuffer的相互转换:

String转成StringBuffer:

不能把字符串的值直接赋值给StringBuffer

方法1:通过构造方法:StringBuffer sb=new StringBuffer(s);

方法2:通过append方法:StringBuffer sb2=new StringBuffer(); sb2.append(s);

StringBuffer转成String:

方法1:通过构造方法:String s=new String(buffer);

方法2:通过toString方法:String str2 = buffer.toString();

StringBuilder类

一个可变的字符序列,此类提供一个与StringBuffer兼容的API(在单线程中完全可以替代StringBuffer),但不保证同步。在单个线程使用时很普遍,因为不保证安全,但是比StringBuffer类快。

1.String,StringBuffer,String Builder的区别:

String内容是不可变的,而StringBuffer、StringBuilder都是内容可变的。

StringBuffer是同步的,数据安全,效率低;StringBuilder是不同步的,数据不安全,效率高。

2.StringBuffer和数组的区别:

二者都可以看成是容器装其他数据,但是StringBuffer的数据最终是一个字符串数据,而数组可以放置多种数据,但必须是同一种数据类型。

\3. 形式参数问题:

基本类型:形式参数的改变不影响实际参数

引用类型:形式参数的改变直接影响实际参数

String作为参数传递:效果和基本类型作为参数传递是一样的。(因为String是一种特殊的基本类型,常量池)

StringBuffer作为参数传递:只有调用方法时才改变了值,一般的赋值=不改变原来值。

Arrays类和数组高级

Arrays类:需要导包import java.util.Arrays

是针对数组进行操作的工具类,提供了排序,查找等功能。

无构造方法。成员方法全是静态方法。

成员方法:不懂就去看源码。

1
2
3
4
5
public static String toString(int[] a) 把数组转成字符串

public static void sort(int[] a) 对数组进行排序(底层是快速排序)

public static int binarySearch(int[] a,int key) 二分查找(必须是有序的)

数组高级:排序和查找

排序:

\1. 插入排序类:

(1)直接插入排序:(在小规模数据集或是基本有序时,该算法效率较高)每一步将一个待排序的元素,按其排序码的大小,插入到前面已经排好序的一组元素的适当位置上去,直到元素全部插入为止

(2)希尔排序:先对数据进行预处理,使其基本有序,然后再用直接插入排序算法排序。

\2. 选择排序类:

(1)简单选择排序:(每次选择一个最大(小)的,直到所有元素都被输出)从0索引开始,依次和后面元素比较,小的回到0索引;第一次完毕后,最小值出现在了最小索引处;第二次比较,有1个元素不比;比较的次数是array.length-1。

(2)堆排序:根节点是整个堆的最大值,将它移走;将剩余n-1个节点重新构造成一个堆,再将根节点移走;重复执行1,2。直到没有节点可移动,就生成了有序序列。

\3. 交换排序类:

(1)冒泡排序:(该算法比较简单,几乎所有语言涉及到算法时,都会涉及到冒泡算法)相邻元素两两比较,大的往后放;第一次完毕,最大值出现在最大索引处;第二次比较,有1个元素不比;比较的次数是array.length-1。

(2)快速排序:(利用“分而治之”的思想对集合进行排序)

4.归并排序类:

归并排序:归并排序(MERGE-SORT)是利用归并的思想实现的排序方法,该算法采用经典的分治策略(分治法将问题分成一些小的问题然后递归求解,而治的阶段则将分的阶段得到的各答案”修补”在一起,即分而治之)。

5.计数排序:计数排序统计小于等于该元素值的元素的个数i,于是该元素就放在目标数组的索引i位(i≥0)。

计数排序基于一个假设,待排序数列的所有数均为整数,且出现在(0,k)的区间之内。

如果 k(待排数组的最大值) 过大则会引起较大的空间复杂度,一般是用来排序 0 到 100 之间的数字的最好的算法,但是它不适合按字母顺序排序人名。

计数排序不是比较排序,排序的速度快于任何比较排序算法。

6.桶排序:将值为i的元素放入i号桶,最后依次把桶里的元素倒出来。

7.基数排序:取得数组中的最大数,并取得位数;arr为原始数组,从最低位开始取每个位组成radix数组;对radix进行计数排序(利用计数排序适用于小范围数的特点)

*排序算法* *平均时间复杂度* *最差时间复杂度* *空间复杂度* *数据对象稳定性*
冒泡排序 O(n2) O(n2) O(1) 稳定
选择排序 O(n2) O(n2) O(1) 数组不稳定、链表稳定
插入排序 O(n2) O(n2) O(1) 稳定
快速排序 O(n*log2n) O(n2) O(log2n) 不稳定
堆排序 O(n*log2n) O(n*log2n) O(1) 不稳定
归并排序 O(n*log2n) O(n*log2n) O(n) 稳定
希尔排序 O(n*log2n) O(n2) O(1) 不稳定
计数排序 O(n+m) O(n+m) O(n+m) 稳定
桶排序 O(n) O(n) O(m) 稳定
基数排序 O(k*n) O(n2) 稳定

查找:

\1. 基本查找:查找的数组无序(从头找到尾)

\2. 二分(折半)查找:查找的数组有序

只要是无序的就不能使用二分查找,因为二分查找一旦排序就改变了元素在数组中的位置,其索引也就不对了。

*查找算法* *平均时间复杂度* *空间复杂度* *查找条件*
顺序查找 O(n) O(1) 无序或有序
二分查找(折半查找) O(log2n) O(1) 有序
插值查找 O(log2(log2n)) O(1) 有序
斐波那契查找 O(log2n) O(1) 有序
哈希查找 O(1) O(n) 无序或有序
二叉查找树(二叉搜索树查找) O(log2n)
红黑树 O(log2n)
2-3树 O(log2n - log3n)
B树/B+树 O(log2n)

基本类型包装类(Interger,Character)

为了对基本数据类型进行更多的操作,更方便的操作,java就对每一种基本类型提供了对应的类类型,包装类类型。

基本类型 包装类类型
byte Byte
short Short
int Integer
long Long
char Character
boolean Boolean
float Float
double Double

常用于基本数据类型与字符串之间的转换。

Integer类:

Integer 类在对象中包装了一个基本类型 int 的值

该类提供了多个方法,能在 int 类型和 String 类型之间互相转换,还提供了处理 int 类型时非常有用的其他一些常量和方法

构造方法:

public Integer(int value)

public Integer(String s) 这个字符串s必须是由数字字符组成,不然就会报错。

int类型和String类型的相互转换

int – String:

\1. 字符串拼接:String s=””+number

\2. 调用String的方法:String s= String.valueOf(number);

\3. int–Integer–String:: Integer i=new Integer(number); String s= i.toString();

\4. 调用Integer的方法:String s= Integer.toString(number);

String – int:

\1. String–Integer–int: Integer ii= new Integer(s); int x = ii.intValue();

\2. 调用Integer的方法**:int x=Integer.parseInt(s); (这个方法很重要,是将字符串类型转换为基本数据类型,调用的是该基本数据类型的包装类的parseXxx方法)

成员方法:

常用的基本进制转换:

public static String toBinaryString(int i) 十进制到二进制

public static String toOctalString(int i) 十进制到八进制

public static String toHexString(int i) 十进制到十六进制

十进制到其他进制:

public static String toString(int i,int radix) radix代表int型数字(2-36),表示二到三十六进制(0-9,a-z)

其他进制到十进制

public static int parseInt(String s,int radix) 给出的s必须是radix可以转换的,不然会报错。

JDK5新特性:(通过反编译就可以发现)

自动装箱:把基本类型转换为包装类类型。

Integer x = new Integer(4);可以直接写成Integer x = 4;//自动装箱。

自动拆箱:把包装类类型转换为基本类型。

x = x + 5;//自动拆箱。通过intValue方法。

在使用时,Integer x = null;上面的代码就会出现NullPointerException。(所以要先判断对象是否为null)

1.5之后,加了个byte常量池:Integer的数据直接赋值,如果在-128到127之间,会直接从缓冲池里获取数据,而不会new一个新对象。

Character类:

Character 类在对象中包装一个基本类型 char 的值

此外,该类提供了几种方法,以确定字符的类别(小写字母,数字,等等),并将字符从大写转换成小写,小写转成大写

构造方法

public Character(char value)

成员方法:

public static boolean isUpperCase(char ch) 判断给定的字符是否是大写字符

public static boolean isLowerCase(char ch) 判断给定的字符是否是小写字符

public static boolean isDigit(char ch) 判断给定的字符是否是数字字符

public static char toUpperCase(char ch) 把给定的字符转换成大写字符

public static char toLowerCase(char ch) 把给定的字符转换成小写字符

正则表达式regex(Pattern,Matcher)

是指一个用来描述或者匹配一系列符合某个句法规则的字符串的单个字符串。其实就是一种规则。有自己特殊的应用。

组成规则:

规则字符在java.util.regex Pattern类中

常见组成规则:

  1. 字符:

x 字符x。举例:’a’ 表示字符a

\ 反斜线字符。因为一个\代表了转义。

\n 新行(换行)符 (‘\uoooA’)

\r 回车符 (‘\uoooD’)

  1. 字符类

[abc] a、b或c(简单类) abc中的任意一个,但只能是一个

[^abc] 任何字符,除了a、b或c(否定)

[a-zA-Z] a到z或A到Z,两头的字母包括在内(范围)

[0-9] 0到9的字符都包括

[]里不用转义字符,是什么就是什么

  1. 预定义字符类

. 任何字符。如果就是.本身,应该写为 \.

\d 数字:[0-9] 在写的时候都是\d表示 因为要先转义。

\D 非数字:[^ 0-9]

\w 单词字符(小w):[a-zA-Z_0-9] 在正则表达式里面组成单词的东西必须由这些东西组成(可以是A-Z,a-z,0-9,下划线都行)

\W 即(大W)表示除\w单词字符之外的其他所有字符

\S 匹配任何非Unicode空白的字符 [^\s]

\s 空格字符:[\ t\n\x0B\f\r\n]

  1. 边界匹配器

^ 行的开头

$ 行的结尾

\b 单词边界 就是不是单词字符的地方。(单词间隔就用这个字符占)

  1. 数量词

X? X,一次或一次也没有

X* X,零次或多次

X+ X,一次或多次

X{n} X,恰好n次

X{n,} X,至少n次

X{n,m} X,至少n次,但不超过m次

6.POSIX字符类

\p{Punct} 匹配任何标点字符 “#$%&'()* +, - 。/:; <=>?@ [\ _] ^ _> {|}

\p{Lower} 小写字母字符:[az]

\p{Upper} 大写字母字符:[AZ]

\p{ASCII} 所有ASCII:[\ x00-\x7F]

\p{Alpha} 字母字符:[\ p {Lower}\p {Upper}]

\p{Digit} 十进制数字:[0-9]

\p{Alnum} 字母数字字符:[\ p {Alpha}\p {Digit}]

\p{Graph} 一个可见的角色:[\ p {Alnum}\p {Punct}]

\p{Print} 可打印字符:[\ p {Graph}\x20]

\p{Blank} 空格或制表符:[\ t]

\p{XDigit} 十六进制数字:[0-9a-fA-F]

\p{Space} 一个空白字符:[\ t\n\x0B\f\r]

(?:pattern)
非获取匹配,匹配pattern但不获取匹配结果,不进行存储供以后使用。这在使用或字符“(|)”来组合一个模式的各个部分是很有用。例如“industr(?:y|ies)”就是一个比“industry|industries”更简略的表达式。
(?=pattern)
非获取匹配,正向肯定预查,在任何匹配pattern的字符串开始处匹配查找字符串,该匹配不需要获取供以后使用。例如,“Windows(?=95|98|NT|2000)”能匹配“Windows2000”中的“Windows”,但不能匹配“Windows3.1”中的“Windows”。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。
(?!pattern)
非获取匹配,正向否定预查,在任何不匹配pattern的字符串开始处匹配查找字符串,该匹配不需要获取供以后使用。例如“Windows(?!95|98|NT|2000)”能匹配“Windows3.1”中的“Windows”,但不能匹配“Windows2000”中的“Windows”。
(?<=pattern)
非获取匹配,反向肯定预查,与正向肯定预查类似,只是方向相反。例如,“(?<=95|98|NT|2000)Windows”能匹配“2000Windows”中的“Windows”,但不能匹配“3.1Windows”中的“Windows”。
(?<!pattern)
非获取匹配,反向否定预查,与正向否定预查类似,只是方向相反。例如“(?<!95|98|NT|2000)Windows”能匹配“3.1Windows”中的“Windows”,但不能匹配“2000Windows”中的“Windows”。这个地方不正确,有问题

应用:

判断功能

String类的public boolean matches(String regex)方法

分割功能

String类的public String[] split(String regex)方法 根据给定正则表达式的匹配拆分此字符串成字符串数组(相当于是把regex这个字符删除了,剩下的分成小片段)

(硬盘上的路径,我们应该用\\替代\,把它分割是\\\\ 第一个\代表格式,第二个\代表转义,\\代表字符串)

替换功能

String类的public String replaceAll(String regex,String replacement)方法: 使用给定的replacement替换此字符串所有匹配给定的正则表达式的子字符串 (应用:可以屏蔽一些子,密码的*等等)

获取功能

Pattern和Matcher类的使用

模式和匹配器的典型调用顺序:

\1. 把正则表达式编译成模式对象:Pattern p= Pattern.compile(“a*b”);

\2. 通过模式对象得到匹配器对象,这个时候需要的是被匹配的字符串:Matcher m=p.matcher(“aaaaab”);

\3. 调用匹配器对象的功能:

(1) boolean b=m.matches(); 调用的matches方法尝试将整个输入序列与该模式匹配

(2) lookingAt 尝试将输入序列从头开始与该模式匹配

(3) find方法扫描输入序列以查找玉该模式匹配的下一个子序列

以上三种方法都是返回boolean类型。find方法后还有一个group方法用来获取匹配的子字符串。

JAVA时间字符串去空格、冒号和横杠

1
2
3
String date = "2017-09-19 14:40:01";
String response = date.replaceAll("[[\\s-:punct:]]","");
匹配所有空白字符,-,:,标点符号

[[:punct:]]是指所有的符号!#$%&'()*+,-./:;<=>?@[]^ {}~`

\s匹配任何空白字符[\t\n\x0B\f\r],第一个\是转义

Math类/Random类/System类

Math 类包含用于执行基本数学运算的方法,如初等指数、对数、平方根和三角函数。

成员变量:

publi static final double PI

publi static final double E

成员方法(都是静态,不用创建对象)

public static int abs(int a) 绝对值

public static double ceil(double a) 向上取整

public static double floor(double a) 向下取整

public static int max(int a,int b) 最大值

public static double pow(double a,double b) a的b次幂

public static double random() 随机数[0.0,1.0)

public static int round(float a) 四舍五入(+0.5后向下取整)

public static double sqrt(double a) 正平方根

如果想得到任意范围的随机数:

int number=(int) (Math.random()*(end-start+1))+start;

Random类:util包中,此类的实例用于生成伪随机数流。

如果用相同的种子创建两个 Random 实例,则对每个实例进行相同的方法调用序列,它们将生成并返回相同的数字序列。

构造方法

public Random() 没有给种子,用的是默认种子,是当前时间的毫秒值

public Random(long seed) 给出指定的种子。 给定种子后,每次得到的随机数是相同的。

Random类成员方法

public int nextInt() 返回的是int范围内的随机数

public int nextInt(int n) 返回的是[0,n)范围内的随机数

System类:lang包

System 类包含一些有用的类字段和方法。它不能被实例化,全是静态。

成员方法

public static void gc() 运行垃圾回收器 不要一直调用们因为每一次执行垃圾回收,jvm都会强制启动垃圾回收器运行,这回耗费更多的系统资源,会与正常的java程序争夺资源,只有执行大量的对象的释放时,才会调用垃圾回收。

public static void exit(int status) 终止当前正在运行的java虚拟机,参数用作状态码,根据惯例,非0的状态码表示异常终止。(所以一般用System.exit(0);相当于按了x退出,后面的都不运行)

public static long currentTimeMillis() 返回以毫秒为单位的当前时间(可以用来计时)

为什么是1970年1月1日0点:一种说法是java起源于UNIX系统,而UNIX系统认为1970.1.1是时间纪元。因为当时的操作系统是32位,换算下来相当于68.1年就会达到最大值,从而回到起始值,所以将1970.1.1当作起点,让这个时间推迟到2038年,而现在已经出现了64位操作系统,所以时间无线大,解决了这个问题。

public static void arraycopy(Object src,int srcPos,Object dest,int destPos,int length) 从指定原数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。arraycopy中c小写,因为刚开始命名不规范改不回去了。src代表原数组,srcPos代表原数组中的起始位置。dest目标数组,destPos代表目标数组中的起始位置。length代表要复制的数组元素的数量。相当于原数组不变,新数组的长度也不变,只是把原数组中的length长度元素替换到了新数组中。

BigInterger类/BigDecimal类

BigInteger类:

可以让超过Integer范围内的数据进行运算

构造方法

public BigInteger(String val)

BigInteger类的成员方法

public BigInteger add(BigInteger val) +

public BigInteger subtract(BigInteger val) -

public BigInteger multiply(BigInteger val) *

public BigInteger divide(BigInteger val) /

public BigInteger[] divideAndRemainder(BigInteger val) 返回的是商和余数的数组

BigDecimal类:

由于在运算的时候,float类型和double很容易丢失精度,演示案例。所以,为了能精确的表示、计算浮点数,Java提供了BigDecimal(金融方面就要用)

是不可变的、任意精度的有符号十进制数。

构造方法

public BigDecimal(String val)

BigDecimal类的成员方法

public BigDecimal add(BigDecimal augend)

public BigDecimal subtract(BigDecimal subtrahend)

public BigDecimal multiply(BigDecimal multiplicand)

public BigDecimal divide(BigDecimal divisor)

public BigDecimal divide(BigDecimal divisor,int scale, int roundingMode) 商,几位小数,如何取舍

判断 BigDecimal 是否为0:

new BigDecimal(“0.00”).compareTo(你的数据) == 0

Date类/DateFormat类/Calendar类

Date类:

类 Date 表示特定的瞬间,精确到毫秒。 大部分方法已经过时。

构造方法

public Date()

public Date(long date)

成员方法

public long getTime() 获取时间,以毫秒为单位

public void setTime(long time) 设置时间

Syste.ou.println(new Date(0)); 为什么打印的是8.而不是0.呢:因为存在系统时间和本地时间的间隔,系统时间依然是0点,而电脑的时区设置在东8区,所以打印的结果是8点。

DateFormat类:txt包

Date–String 格式化 public final String format(Date date)

String–Date 解析 public Date parse(String source)

DateFormat 是日期/时间格式化子类的抽象类,它以与语言无关的方式格式化并解析日期或时间。

DateFormat可以进行日期和字符串的格式化和解析,但由于是抽象类,所以使用其子类SimpleDateFormat

SimpleDateFormat类的构造方法:

public SimpleDateFormat() 默认模式

public SimpleDateFormat(String pattern) 给定的模式

API中有对应的模式举例:

年 y

月 M

日 d

时 H

分 m

秒 s

2020年11月14日 17:58:20

yyyy年MM月dd日 HH:mm:ss

在把一个字符串解析为日期的时候,请注意格式必须和给定的字符串格式匹配。

成员方法

public final String format(Date date)

public Date parse(String source)

Calendar类

Date类的很多都被Calendar类替代了。

Calendar 类是一个抽象类,它为特定瞬间与一组诸如 YEAR、MONTH、DAY_OF_MONTH、HOUR 等 日历字段之间的转换提供了一些方法,并为操作日历字段(例如获得下星期的日期)提供了一些方法。

成员方法

public static Calendar getInstance() 其实相当于多态,表面上返回的是Calendar,其实返回的是Calendar的一个实现子类。

public int get(int field) 返回给定日历字段的值。日历类中的每个日历字段都是静态的成员变量,并且是int类型。

public void add(int field,int amount) 根据给定的日历字段和对应的时间,来对当前的日历进行操作。 amount可以是 -3 3

public final void set(int year,int month,int date) 设置当前日历的年月日。

month都是0-11 所以适当+1

RunTime类

该类主要代表了应用程序的运行环境。一个RunTime就代表一个运行环境

方法

(1) getRuntime():该方法用于返回当前应用程序的运行环境对象。

(2) exec(String command):该方法用于根据指定的路径执行对应的可执行文件。

(3) freeMemory():该方法用于返回Java虚拟机中的空闲内存量,以字节为单位。
(4) maxMemory():该方法用于返回Java虚拟机试图使用的最大内存量。

(5) totalMemory():该方法用于返回Java虚拟机中的内存总量。

例子:

image-20210809165151605

getRuntime().exec()

在java中,RunTime.getRuntime().exec()实现了调用服务器命令脚本来执行功能需要

用法:

public Process exec(String command)—–在单独的进程中执行指定的字符串命令。

public Process exec(String [] cmdArray)—在单独的进程中执行指定命令和变量

public Process exec(String command, String [] envp)—-在指定环境的独立进程中执行指定命令和变量

public Process exec(String [] cmdArray, String [] envp)—-在指定环境的独立进程中执行指定的命令和变量

public Process exec(String command,String[] envp,File dir)—-在有指定环境和工作目录的独立进程中执行指定的字符串命令

public Process exec(String[] cmdarray,String[] envp,File dir)—-在指定环境和工作目录的独立进程中执行指定的命令和变量

举例:

  1. RunTime.getRuntime().exec(String command);

         在windows下相当于直接调用   /开始/搜索程序和文件  的指令,比如
     
         Process process = Runtime.getRuntime().exec("notepad.exe");  -------打开windows下记事本。
    
  2. public Process exec(String [] cmdArray);

          Linux下:
     
          Process process = Runtime.getRuntime().exec(new String[]{"/bin/sh","-c", ";
     
          Windows下:
     
          Process process = Runtime.getRuntime().exec(new String[]{ "cmd", "/c", cmds});
    

补充:#!/bin/bash和#!/bin/sh的区别

#! 是个指示路径的表示符,/bin/bash和/bin/sh指定了脚本解析器的程序路径

bash是sh的完整版,bash完全兼容sh命令,反之不行

OPTIONS:

  -c string    该选项表明string中包含了一条命令.如 bash -c ls ~

  -i       使Bash以交互式方式运行

  -r       使Bash以受限方式运行

  –login     使Bash以登录Shell方式运行

  –posix     使Bash遵循POSIX标准

  –verbose    使Bash显示所有其读入的输入行

  –help     打印Bash的使用信息

  –version    打印版本信息

Process的几种方法:
1.destroy():杀掉子进程

2.exitValue():返回子进程的出口值,值 0 表示正常终止

3.getErrorStream():获取子进程的错误流

4.getInputStream():获取子进程的输入流

5.getOutputStream():获取子进程的输出流

6.waitFor():导致当前线程等待,如有必要,一直要等到由该 Process 对象表示的进程已经终止。如果已终止该子进程,此方法立即返回。如果没有终止该子进程,调用的线程将被阻塞,直到退出子进程,根据惯例,0 表示正常终止

注意:在java中,调用runtime线程执行脚本是非常消耗资源的,所以切忌不要频繁使用!

在调用runtime去执行脚本的时候,其实就是JVM开了一个子线程去调用JVM所在系统的命令,其中开了三个通道: 输入流、输出流、错误流,其中输出流就是子线程走调用的通道。

waitFor是等待子线程执行命令结束后才执行, 但是在runtime中,打开程序的命令如果不关闭,就不算子线程结束。比如以下代码。

1
2
3
4
5
6
7
private static Process p = null;

p = Runtime.getRuntime().exec("notepad.exe");

p.waitFor();

System.out.println("--------------------------------------------我被执行了");

以上代码中,打开windows中记事本。如果我们不手动关闭记事本,那么输出语句就不会被执行,这点是需要理解的。

process的阻塞:

在runtime执行大点的命令中,输入流和错误流会不断有流进入存储在JVM的缓冲区中,如果缓冲区的流不被读取被填满时,就会造成runtime的阻塞。所以在进行比如:大文件复制等的操作时,我们还需要不断的去读取JVM中的缓冲区的流,来防止Runtime的死锁阻塞。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
在linux 上封装的方法:
/**
* @param daemon true,后台执行,false,直接执行
* @param args 指令行
*/
public static Result sendCommand(boolean daemon,String args) {
logger.info("args={}", args);
StringBuilder builder = new StringBuilder();
// InputStreamReader ir = null;
// LineNumberReader input = null;
try {
String[] envp = { "LANG=UTF-8" };
Process process=null;
if(daemon) {
String[] comands = new String[] { "/bin/sh", "-c", args};
process = Runtime.getRuntime().exec(comands, envp);
process.waitFor();
}else {
process = Runtime.getRuntime().exec(args, envp);
process.waitFor(60,TimeUnit.SECONDS);
}
try (InputStreamReader ir = new InputStreamReader(process.getInputStream(), Charset.forName("UTF-8"));LineNumberReader input = new LineNumberReader(ir)) {
String tmp = "";
while ((tmp = input.readLine()) != null) {
builder.append(tmp).append("\n");
}

}catch (Exception e) {
logger.error(e.getMessage(), e);
return new Result(ErrorCode.EC_FAILED, e.getMessage());
}

try (InputStreamReader ir = new InputStreamReader(process.getErrorStream(), Charset.forName("UTF-8"));LineNumberReader input = new LineNumberReader(ir)) {
String tmp = "";
while ((tmp = input.readLine()) != null) {
builder.append(tmp).append("\n");
}
}catch (Exception e) {
logger.error(e.getMessage(), e);
return new Result(ErrorCode.EC_FAILED, e.getMessage());
}
} catch (Exception e) {
logger.error(e.getMessage(), e);
return new Result(ErrorCode.EC_FAILED, e.getMessage());
}
String result = builder.toString();
if (StringUtils.isNotEmpty(result)&&StringUtils.containsIgnoreCase(result, "ERROR")) {
logger.info("result={}", result);
return new Result(ErrorCode.EC_FAILED, result);
} else {
return new Result(ErrorCode.EC_OK);
}
}

展开全文 >>

项目踩坑

2021-03-26

1.R是com.baomidou.mybatisplus.extension.api中写好的一个统一结果返回集。主要包含code,data,msg

**R(IErrorCode errorCode)**: {data:null,code:errorCode.getCode(),msg:errorCode.getMsg()}

R.ok(T data): 默认情况下:{data:data,code:0,msg:执行成功} 如果data为Boolean类型且就是false:{data:false,code:-1,msg:操作失败}

R.failed(String msg): {data:null,code:-1,msg:msg}

R.failed(IErrorCode errorCode) : {data:null,code:errorCode.getCode(),msg:errorCode.getMsg()}

R.restResult(T data, IErrorCode errorCode) : {data:data,code:errorCode.getCode(),msg:errorCode.getMsg()}

R.restResult(T data, long code, String msg): {data:data,code:code,msg:msg}

可以自己定义枚举类实现IErrorCode接口

遇到的问题:

1
2
3
R.restResult(vo, ApiSuccessCode.YELLOW)
当YELLOW定义为(-1,"yellow")时,前端会toast出yellow。(因为前端代码如果不符合要求Toast(res.data.msg);)
//解决方法:将YELLOW的code定义为其他不常用的特殊数字,前端对这个数字进行判断来满足这个单独的场景

2.多看文档的API,可能有封装好的方法

3.lombok里的@data可以自动装填get/set

4.docker里的时间比写入到mysql的时间少8小时。java程序在本地运行时间(正常,相当于就是mysql的时间)比在docker容器上运行时间迟(多)8小时。(用date命令查看时间是CST还是UTC)

原因 :

一般宿主机用的都是CST时区(China Standard Time UT+8:00 中国标准时间),而docker容器中初始用的都是UTC时区(Coordinated Universal Time 世界协调时间)比CST慢8个小时

解决方案:

查阅相关资料后得知: jre是通过/etc/timezone 配置文件读取本地时间的

需要将挂载宿主机的/etc/localtime 到容器的/etc/localtime,这时输入date命令容器时区显示正常,但是跑在容器中的java项目取到的时间却早了8小时。

接着我修改了/etc/timezone配置命令如下:
echo "Asia/Shanghai" > /etc/timezone

重启了下容器,然后java项目中读取的时区恢复正常了。

但是仅仅是java容器内的正常了,nginx容器内时间还是少8个小时,所以还需要将nginx的容器的时区进行更改。

也可以执行命令更改时区:

1
2
3
root@06057b1eeb48:/usr/share# cd /etc/
root@06057b1eeb48:/etc# mv localtime localtime.bak
root@06057b1eeb48:/etc# cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

输入date查看时间是否是CST

5.程序内微信的acces_token过期,返回值为400001,但是postman正常发送返回值为0。清下服务器上redis的数据库即可。

image-20210410201322948

6.关于vue项目打包的问题:

花了一个下午,差不多搞明白了。

node.js有一个版本号,安装好node.js之后,会自动安装了npm。npm也有一个版本号。

npm可以再安装vue,再安装vue的脚手架:vue -cli,这两个的版本号是同一个。

​ 1.注意这里的版本号。写vue项目用到什么的版本号都会写在vue项目里的package.json。版本过高或者过低都会报错。还要注意这几个软件的安装位置,最好都在一起。

​ 2.特别坑。为什么每次打包都会出错?是因为你运行的VUE的文件外面还包了一个文件。运行的时候一般就是总文件,然后直接打包会成功,如果外面再有一个文件夹(比如你是从git上pull的项目,它外面会自带一个文件夹,你再直接打开这个文件就会出现这种情况),就会报错。

7.String类的int类型hex进制转10进制的时候:00000000会自动转为0,需要转换为00000000再转为string类型,可以用String.format(“%08d”,i)方法

8.vue页面的图片插入问题:require(’相对路径’)

服务器上的/xx/xx/xxx.jpg

9.HttpServletRequest

public void weixin(@RequestParam(name = “openid”, defaultValue = “”) String openid, @RequestBody String xml)

10.将一个xml格式的string类型数据进行解析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
String str= sb.toString();
Document doc = null;
try {
doc = DocumentHelper.parseText(str);
} catch (DocumentException e) {
e.printStackTrace();
}
Element root = doc.getRootElement();// 指向根节点 <root>
try {
//这里的是Element,需要转换为String类型
Element mark=root.element("header").element("mark");
Element second=root.element("body").element("data").element("first").element("second");
//获取xml的节点内容
System.out.println(mark.getTextTrim());
System.out.println(second.getTextTrim());
}catch(Exception e){
e.printStackTrace();
}

//将xml存入map
public static Map<String, String> parseXml(HttpServletRequest request) throws Exception {
// 将解析结果存储在HashMap中
Map<String, String> map = new HashMap<String, String>();
// 从request中取得输入流
InputStream inputStream = request.getInputStream();
// 读取输入流
SAXReader reader = new SAXReader();
Document document = reader.read(inputStream);

// 得到xml根元素
Element root = document.getRootElement();
//获得子元素下的元素
Iterator<Element> iterator = root.elementIterator();
while (iterator.hasNext()) {
Element e = iterator.next();
map.put(e.getName(), e.getText());
@SuppressWarnings("unchecked")
List<Element> sonList = e.elements();
for (Element son : sonList) {
map.put(son.getName(), son.getText());
}
}
// 释放资源
inputStream.close();
inputStream = null;
return map;
}

11.idea里的文件名字变蓝是因为文件里的内容被修改过,没有提交到版本库。变红是新增的文件。

12.微信回复消息的时候,<Content><![CDATA["+text+"]]></Content>中的text文本乱码成?????

是由于spring mvc的@ResponseBody注解返回字符串时默认返回的是“ISO-8859-1”而不是utf-8。

虽然大家的项目里面可能都有字符编码过滤器,但是有一个问题在这里,我们设置response.setContentType(“text/html; charset=utf-8”);时都是在chain.doFilter(request, response);之前设置的,也就是过滤器前处理是设置的响应编码格式,之所以不在后处理时设置是因为后处理时响应内容已经生成此时设置是无效的,必须在响应内容生成之前设置响应编码格式。

问题就出在这个地方,我们虽然设置了响应编码格式,可是spring mvc在有@ResponseBody注解的响应是,篡改了我们的响应编码!
相信大家对spring的Message Converters不会陌生,HttpMessageConverters这个接口是用来把请求信息转化为对象T,把T输出为响应信息的一个接口,在该接口众多的实现类中有一个实现类StringHttpMessageConverter,这个类的作用就是把请求信息转换未字符串,而@ResponseBody注解就是默认调用的这个类,而这个类中默认的编码格式就是ISO-8859-1,所以罪魁祸首找到了

原因:

由于spring默认对String类型的返回的编码采用的是 StringHttpMessageConverter
>>> spring mvc的一个bug,spring MVC有一系列HttpMessageConverter去处理用@ResponseBody注解的返回值,如返回list则使用MappingJacksonHttpMessageConverter。返回string,则使用StringHttpMessageConverter。这个convert使用的是字符集是iso-8859-1,而且是final的:
public static final Charset DEFAULT_CHARSET = Charset.forName(“ISO-8859-1”);

解决:

在RequestMapping里加上 produces = "text/html;charset=UTF-8"//只针对单个方法生效,不全局生效

例:@RequestMapping(value = “/weixin/validation” , method = RequestMethod.POST , produces = “text/html;charset=UTF-8”)

13.程序内是生成6位数字的验证码结果,通过阿里云短信发送出来有时只有5位:因为返回到阿里云里面的验证码,会被识别为数字。然后第一位数字是0就默认去掉了。

提供的解决方法:

​ 1.随机生成数开头为0的话,继续随机:

1
2
3
4
5
6
7
public static int getRandom(){ 	//递归方法
int number = new Random().nextInt(10);
if(0 == number){
return getRandom();
}
return number;
}

​ 2.是在验证码外层拼接一个单引号 :"{code:"+"'"+code+"'"+"}" 为什么加了一个单引号就可以实现?阿里云的接口定义这里应该是一个json格式。

14.记录表和状态表:状态表的主键唯一,unquire。记录表主键为normal,可以重复多条。

15.关于移动端的v-console按钮:

image-20210514122852087

当不需要显示时:在main.js文件中将new Vconsole()注释掉即可。

16.spring MVC接收请求体总是多一个等号

例:我发送的请求体是字符串aaa,spring MVC 接收到的是aaa=

//因为请求的content type不对,应该是:application/json;charset=UTF-8。而我设置的content type是:application/x-www-form-urlencoded;charset=UTF-8

原因是后端接收的是context-type:application/json
而axios的post请求默认是context-type: application/x-www-form-urlencoded
这里在前端发起请求前,在请求拦截器中设置一下context-type为json就好了

因为默认是以键值对形式传递
前端传过来的内容是放在k中,v为空,这时候取值时,内容就变成了k=,也就是为什么后端接收的数据,末尾多了一个=

17.qw条件构造器连续使用的问题

1
2
3
4
5
6
7
QueryWrapper qw = new QueryWrapper();
qw.eq("node_num", "123");
Nodes nodes = nodesService.getOne(qw);

qw = new QueryWrapper();//需要新加这一行代码才能保证上下两个查询语句互不相连。如果没有这条语句,下面的查询语句会有两个限定条件
qw.eq("user_id", "1");
List<Bind> bind = bindService.list(qw);
  1. qs.stringify(this.form)
1
uid=cs11&pwd=000000als&username=cs11&password=000000als

qs是一个npm仓库所管理的包,可通过npm install qs命令进行安装

  1. qs.parse()将URL解析成对象的形式
  2. qs.stringify()将对象 序列化成URL的形式,以&进行拼接

JSON中同样存在stringify方法:

1
{"uid":"cs11","pwd":"000000als","username":"cs11","password":"000000als"}   

当前端页面需要借助url来往后台传递参数的时候我们通常会这样写:

1
var  url="base/exchangeController/1/searchExchanges?code="+code+"&name="+name;

如果你的name传递的是中文的话,在谷歌浏览器中传递到后台的数据是正常的(其他浏览器未测试),但是在IE浏览器中,后台接受到的将会是乱码,这是因为IE浏览器没有将你传递的参数序列化为URL 编码文本字符串,后台在解码你的参数的时候就会形成乱码,解决方法如下:

1
2
var  param={"code":code,"name":name};
var url="base/exchangeController/1/searchExchanges"+"?"+$.param(param);

  借助jquery的param方法将你要携带的参数对象化之后,再序列化一下,这样IE浏览器下即使你传递中文,后台接收到的数据也是正常的中文了

19.前端调试的时候:console.log(A) 输出的数组A显示是Object,而不是包含的值,但只是打印出来的log是Object,在引用时候还是数组的值。需要查看打印的值(将数组A转换为json字符串):console.log(JSON.stringify(A))

20.前端页面动态赋值时不起作用,在属性前面加:绑定后生效。

21.前端页面的css样式复用问题:原页面a.vue用的v-router去跳转到b.vue,然后退回来时a.vue的样式全乱了,我在b.vue中导入了一个css文件,然后页面调试是看到退回后styles是不变的,也就是说a.vue用的不是原来的样式而是b.vue中导入的样式,刷新一下的话就又可以了。

怎么让vue不去使用上个页面的样式,或者说让当前vue只用自己写的当前样式:两个名字不要起一样的,就是两个css样式了。

22.导航下拉菜单被遮住或显示不全:

原因:层叠关系错误

解决:设置z-index,最上面的层级值越大。但是必须有两个前提条件:1.必须是同级。2.二者分别设定了position:relative 或 absolute 或 fixed;

23.后端springboot项目,入口的Application类必须放在最上层,才能读取到所有的其他配置。

24.@Slf4j

如果不想每次都写**private final Logger logger = LoggerFactory.getLogger(当前类名.class); **

可以在类上用注解@Slf4j,然后就可以直接使用log.info(“字符串内容”);

25.在工具类中调用静态方法时,mapper无法注入的问题:一个工具类中的静态方法调用mybatis的mapper接口时,会出现@Autowired无法注入的问题,即使添加了这个注解,spring容器加载完成声明的参数也是空值

在SpringFramework里,不能@Autowired一个静态变量,使之成为一个Spring bean的

解决方案:

首先,在实体类上加上注解@Component,方便spring容器进行加载,然后定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
在静态工具类中注入mapper的方式示例
*/
@Component
public class CodeMapUtils {


private static Logger LOGGER = LoggerFactory.getLogger(DoAllController.class);

private static CodeMapUtils codeMapUtils;

@Autowired
private CodeMapMapper codeMapMapper;

@PostConstruct
public void init() {
codeMapUtils = this;
codeMapUtils.codeMapMapper = this.codeMapMapper;
}

public static String queryFromCodeMap(String key, String colName) {
String value = codeMapUtils.codeMapMapper.getValueByKeyAndColName(key, colName);
// do something...

}
}

另外,spring中Constructor、@Autowired、@PostConstruct的顺序:

其实从依赖注入的字面意思就可以知道,要将对象p注入到对象a,那么首先就必须得生成对象a和对象p,才能执行注入。所以,如果一个类A中有个成员变量p被@Autowried注解,那么@Autowired注入是发生在A的构造方法执行完之后的。

如果想在生成对象时完成某些初始化操作,而偏偏这些初始化操作又依赖于依赖注入,那么就无法在构造函数中实现。为此,可以使用@PostConstruct注解一个方法来完成初始化,@PostConstruct注解的方法将会在依赖注入完成后被自动调用。

加载顺序:Constructor >> @Autowired >> @PostConstruct

另:

spring无法自动注入静态变量、spring注入null 的解决方法:

方式一:使用@Resource注解set方法,将注入的bean对象赋值给静态变量。

/**

  • @description: 自定義公共方法

  • @author: H2103424

  • @createTime: 2021/1/22 下午 02:15
    **/
    @Component
    public class CustomUtil {

    private static UserService userService;
    private static MailSendService mailSendService;

    @Resource
    public void setMailSendService(MailSendService mailSendService) {

    CustomUtil.mailSendService = mailSendService;
    

    }

    @Resource
    public void setUserService(UserService userService) {

    CustomUtil.userService = userService;
    

    }

    /**

    • @description: 根據 用戶編號list 獲取用戶郵箱list
    • @author: H2103424
    • @dateTime: 2021/1/22 下午 03:06
      *
    • @param users 用戶編號集合
    • @return 用戶郵箱集合
      */
      public static List getEmailsByUsers(List users){
      List<Map<String,Object>> userInfos = userService.getUserByUserNo(users);
      List userEmails = new ArrayList<>();
      userInfos.forEach(u -> {
      userEmails.add((String) u.get("user_email"));
      
      });
      return userEmails;
      }

      }

      1
      2
      3

      方法二:使用@Resource自动注入普通变量,然后使用@PostConstruct注解将普通变量赋值给静态变量。

      /**

      • @description: 自定義公共方法

      • @author: H2103424

      • @createTime: 2021/1/22 下午 02:15

      • */
        @Component
        public class CustomUtil {

        @Resource
        private UserService userServiceBean;
        @Resource
        private MailSendService mailSendServiceBean;

        private static UserService userService;
        private static MailSendService mailSendService;

        @PostConstruct
        public void init(){

          userService = userServiceBean;
          mailSendService = mailSendServiceBean;
        

        }

        /**

        • @description: 根據 用戶編號list 獲取用戶郵箱list
        • @author: H2103424
        • @dateTime: 2021/1/22 下午 03:06
        • @param users 用戶編號集合
        • @return 用戶郵箱集合
        • /
          public static List getEmailsByUsers(List users){
          List<Map<String,Object>> userInfos = userService.getUserByUserNo(users);
          List userEmails = new ArrayList<>();
          userInfos.forEach(u -> {
            userEmails.add((String) u.get("user_email"));
          
          });
          return userEmails;
          }
          }
          1
          2
          3
          4
          5

          @PostConstruct该注解被用来修饰一个非静态的void()方法。被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。

          Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)

          package com.example.studySpringBoot.util;

          import com.example.studySpringBoot.service.MyMethorClassService;
          import org.springframework.beans.factory.annotation.Autowired;
          import org.springframework.stereotype.Component;

          import javax.annotation.PostConstruct;

          @Component
          public class MyUtils {

          private static MyUtils staticInstance = new MyUtils();

          @Autowired
          private MyMethorClassService myService;

          @PostConstruct
          public void init(){
          staticInstance.myService = myService;
          }

          public static Integer invokeBean(){
          return staticInstance.myService.add(10,20);
          }

          }

          项目里实际用的这种也可以:(这两个上面加不加@Autowired效果一样的)

          1
          2
          3
          4
          5
          static UserMapper userMapper;

          private UserUtil(UserMapper userMapper) {
          UserUtil.userMapper = userMapper;
          }

          26.vue项目地址里的/#/,#是因为 Vue 的路由使用了 Hash 模式,是单页面应用的经典用法。如果需要去掉的话:可以在路由配置中选择使用 History 模式,但会引发一些问题,需要在后端作出处理。

          27.如果自动安装maven依赖的过程没有执行,可以在 pom.xml 上右键,选择 Maven ->reload project。

          如果右边没有maven菜单栏时,shift+shift 选择actions 点击add maven project

          28.springboot项目报错:(未实操)

          1
          2
          3
          4
          o.apache.coyote.http11.Http11Processor   : Error parsing HTTP request header
          Note: further occurrences of HTTP request parsing errors will be logged at DEBUG level.
          java.lang.IllegalArgumentException: Invalid character found in method name. HTTP method names must be tokens
          .......

          用的是tomcat9.0版本

          解决方法:不推荐降低tomcat版本,这等于掩耳盗铃,绝对得不偿失。Tomcat在 7.0.73, 8.0.39, 8.5.7 版本后,在http解析时做了严格限制。RFC3986文档规定,请求的Url中只允许包含英文字母(a-zA-Z)、数字(0-9)、-_.~4个特殊字符以及所有保留字符

          1)在server.xml中的Connector中添加maxHttpHeaderSize=”8192”

          1
          <Connector port="8080" protocol="HTTP/1.1"   maxHttpHeaderSize="8192"    connectionTimeout="20000"   maxThreads="150"  maxSpareThreads="75"   redirectPort="8443" />  

          2)在在conf/catalina.properties中最后添加2行

          1
          2
          tomcat.util.http.parser.HttpParser.requestTargetAllow=|{}
          org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true

          requestTargetAllow 只能配置|、{、} 允许这三个字符,对于其他的(例如” < > [ \ ] ^ ` { | } .),在请求时,仍然拦截,如果使用了|{}之外的其他字符:

          1
          relaxedPathChars="[\]^`{|}" relaxedQueryChars="[\]^`{|}"

          3)如果是https访问的页面,改为http

          4)将HTTP1.1改成org.apache.coyote.http11.Http11NioProtocol,结果启动tomcat的时候出现了一大堆“地址已使用”的错误

          29.学习项目时,如果跑起来报错jedis,可能是因为本地redis密码没有设置,需要在命令行进行redis登录,再设置密码,退出,再启动项目。

          30.idea里如果get,set方法飘红,是因为lombok插件没有下载,需要在settings里的plugins下载。如果插件半天加载不出来,可以在设置里的HTTP Proxy setting勾选Auto-detect proxy settings,再勾选Automatic proxy configuration URL:填入代理地址url:http://127.0.0.1:1080

          31.代码规范可以下载alibaba java coding guidelines,在项目处右击选择 编码规约扫描

          32.localStorage 是存储在浏览器前端的,后台想获取,就需要前端传过去获取,可以在axios中将head的参数全局设置为localStorage 中的某个值

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          axios.interceptors.request.use(
          (config) => {
          Toast.loading({
          message: "加载中...",
          loadingType: "spinner",
          forbidClick: true,
          duration: 0,
          });
          const openid = localStorage.getItem("openid");
          config.headers["openid"] = openid;
          if( localStorage.getItem('flag')==1){
          config.headers["openid"] = "tangseng";
          }
          return config;
          }

          33.vue页面的轮询:一般轮询都会使用setInterval,但是单独使用它会使页面卡死

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          export default {
          data() {
          return {
          timer: null
          }
          },
          mounted() {
          this.getList();
          this.timer = window.setInterval(() => {
          setTimeout(() => {
          this.getList()
          },0)
          },3000)
          },
          methods: {
          getList() {
          // 发送接口
          }
          },
          destroyed() {
          window.clearInterval(this.timer)
          }

          }

          使用说明:setInterval不会清除定时器队列,每重复执行1次都会导致定时器叠加,会出现网页卡死现象。但是setTimeout是自带清除定时器的,两者结合使用将避免页面卡死。

          页面初始化,待开始轮询后,离开页面,通过生命周期destroyed钩子函数,销毁定时任务。

          如果不进行销毁,在vue页面中,即使离开了有定时器的页面,仍会执行定时任务,但是当关闭页面后,定时任务就没了。

          34.spring方法里写了mapper以后,记得上面加注解@comment将这个类注入到spring中。

          35.vue中:当选择了拒绝或者同意之后,this.radio可以正确取到,但是this.myform.reviewStatus显示为undefined

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
                <van-radio-group v-model="radio" direction="horizontal" style="font-size: 20px;display: flex;align-items: center;justify-content: center">
          <van-radio name="1">拒绝</van-radio>
          <van-radio name="2">同意</van-radio>
          </van-radio-group>

          data() {
          return {
          radio: '1',
          myform: {
          type: "绑定安全帽审核",
          position:this.$route.query.position,
          reviewStatus:this.radio,//2同意,1拒绝
          }
          };
          }

          因为data里的this是指父级作用域的上下文,所以this.radio并拿不到其本身作用域的radio
          需要的话可以通过mounted,将radio赋值给reviewStatus:

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          export default {
          data () {
          return {
          radio: 1,
          reviewStatus: undefined
          }
          },
          moounted: function () {
          this.reviewStatus = this.radio
          console.log(this.reviewStatus) // 1
          }
          }

          36.BigDecimal 进行浮点数精确运算时

          利用 BigDecimal.valueOf 方法构造对象的方法,获得的浮点数发生了精度异常。(当 BigDecimal.valueOf(…) 的入参是 float 类型时,BigDecimal 会把入参强制转换成 double 类型。假如直接使用 new BigDecimal(double val) 构造函数来进行运算,则会发现计算结果发生来精度异常)

          利用 new BigDecimal(String val) 方法,运算正确

          37.LocalDateTime如果秒数是00的话,会自动去掉 比如00:00:00 转为LocalDateTime后变为00:00

          38.java的类不能多继承,但是接口可以多继承,却不能implements任何接口

          39.设计思路:对于订单的延时取消,可以在mysql的数据字段给个过期时间(也可以根据创建时间来判断是否过期),过期了就在查之前更新,将更新后的结果返回。没有过期就是正常查询。

          40.url(前后端接口)的命名规范:

          ​ URL请求采用小写字母,数字,部分特殊符号(非制表符)组成(尽量使用一个单词,如果非要用2个,加_即可 /module/tickets/recently_closed)

          ​ URL请求中不采用大小写混合的驼峰命名方式,尽量采用全小写单词,如果需要连接多个单词,则采用连接符“_”连接单词

          需要分级的话:

          ​ 第一级Pattern为模块,比如组织管理/orgz, 网格化:/grid

          ​ 第二级Pattern为资源分类或者功能请求,优先采用资源分类。

          ​ 如果为资源分类,则按照RESTful的方式定义第三级Pattern,RESTful规范中,资源必须采用资源的名词复数定义。

          例子:/orgz/members/120

          41.redis里的缓存还没到时间就自动消失:是因为redis作为docker容器运行在docker里,而当初创建容器的时候没有设置时间参数,导致使用了docker的默认时间UTC,导致用的是国际时间,而你往redis里存的时候使用的是本地时间即上海地区时间,存入的时候正常,在容器里检测过期了8小时就会自动删除。

          需要设置redis容器的时间为上海地区的时间。

          42.redis莫名其妙会有保存的数据丢失,查看日志:

          1
          Possible SECURITY ATTACK detected. It looks like somebody is sending POST or Host: commands to Redis. This is likely due to an attacker attempting to use Cross Protocol Scripting to compromise your Redis instance. Connection aborted.

          可能是被人攻击了,删除了,需要设置redis的密码

          43.把一个任意类型的值转换为布尔类型:用!! 一个!是取非 再一个!又取非 相当于把这个数据转换为boolen类型了

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          前端转换:
          // 强制转换为Boolean 用 !!
          var bool = !!"c";
          console.log(typeof bool); // boolean

          // 强制转换为Number 用 +
          var num = +"1234";
          console.log(typeof num); // number

          // 强制转换为String 用 ""+
          var str = ""+ 1234;
          console.log(typeof str); // string

          //强制转换为Number,用 - 0

          44.vant框架的list组件:

          页面加载完成后默认会自动加载一次,可以:immediate-check=”false”这样设置一下,页面加载完成后就不会自动加载一次了(此时可以在created方法或mounted方法中手动加载第一页的东西)

          45.手写的一个aop切面的日志注解,加在方法A上可以正常切面打印日志,如果方法A里调用了方法B,但是方法B上的切面日志注解就不会起作用。

          aop切面的执行正常顺序为:@Before ,@Around,@After,@AfterReturning,@AfterThrowing

          从Spring5.2.7开始,在相同@Aspect类中,通知方法将根据其类型按照从高到低的优先级进行执行:@Around,@Before ,@After,@AfterReturning,@AfterThrowing

          46.insert之后返回的返回值实际是增加的数据列数,并不是主键id。要想获取主键id,只需要在insert之后直接get该对象的主键id即可

          1
          2
          3
          4
          5
          6
          7
          //新增银行卡
          BankCard bankCard = new BankCard();
          bankCard.setBankCardRealName(bankCardRequest.getRealName());
          bankCard.setBankCardNum(bankCardRequest.getBankCardNum());
          bankCard.setBankName(bankCardRequest.getBankName());
          bankCardMapper.insert(bankCard);
          Long id = bankCard.getId();

          47.vue页面初始化时声明一个数组的属性,然后axios后端返回的值可以直接赋值。提交数据后将该数组置为{}会导致下一次赋值无法正确赋值,需要采用vue.set或者this.$set。也可以在清空数据的同时把原字段重新初始化

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          adminForm: {
          nodeNum: '',
          helmetColor: ''
          }

          1.用完之后清空 this.adminForm = {};

          再次使用时动态绑定上 this.$set(this.adminForm, 'helmetColor',res.data.data.helmetColor);

          2.清空的时候同时初始化,就可以直接赋值
          this.adminForm: {
          nodeNum: '',
          helmetColor: ''
          }
          this.bindForm.nodeNum = res.data.data.nodeNum

          48.设置数据库对应的entity的属性为null。1.设置字段填充策略,会产生影响。2.写一个sql语句,调用设置字段为null。

          49.

          1
          2
          3
          4
          String S= "1";
          Integer i = (Integer) s;//会报错:String不能强转Integer,因为string强转Integer时不能加括号强转
          需要:
          Integer i = Integer.parseInt(s);

          50.包装类为空的话,不能直接赋值给基础类,不然会报null。因为自动拆箱的时候报空。

          1
          2
          String S= null;
          int i = s;//会报错:string转int涉及到自动装箱和拆箱

          51.不能以名字作为参数判断条件,因为可能出现重名,要以id作为判断条件

          52.微信复制的内容可能会将空格替换。用postman复制别人微信发的post的body格式时踩到的坑。

          53.idea里代码大部分飘红,但是项目可以正常启动。重新载入maven依赖也没起作用。

          是因为IDEA有缓存,只需要刷新一下缓存就好了。解决方案:File -> Invalidate Caches / Restart 然后选择Invalidate and Restart。

          54.spring的事务和redis的“事务”

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          加@Transactional
          1.mysql没有插入,事务生效
          Company company=new Company();
          company.setName("1号公司1");
          companyMapper.insert(company);
          System.out.println(10/0);
          2.mysql没有插入,事务生效
          Company company = new Company();
          company.setName("1号公司2");
          companyMapper.insert(company);
          redisService.test("test");
          3.mysql没有插入,redis数据更改了
          Company company = new Company();
          company.setName("1号公司2");
          companyMapper.insert(company);
          redisService.set("test","aaa");
          System.out.println(10/0);
          4.redis数据更改,mysql没有插入(name设置为不能插入重复字段)
          redisService.set("test","ccc");
          Company company = new Company();
          company.setName("1号公司1");
          companyMapper.insert(company);
          结论:事务只对mysql生效,redis数据自己成功就是成功,失败就是失败

          redis的事务其实不是事务,因为redis操作都是原子性的。如果命令可以执行成功则一定被执行且无法回滚。而spring的事务只要抛出异常,就会将方法内所有内容(除mysql之外的东西是否回滚待测试)回滚,reids不会回滚。

          55.实体类的BigDecimal类型字段,在序列化后或者转换为json后,小数点后的0会被自动去掉

          1
          2
          3
          4
          5
          6
          DecimalFormat df = new DecimalFormat("#0.00");--指定保留两位小数
          p.setPrice(p.getPrice==null?null:df.format(new BigDecimal(p.getPrice)));

          或者在该对象的字段上
          @JsonFormat(shape = JsonFormat.Shape.STRING)
          private BigDecimal amt;

          上述方法会将BigDecimal类型转为String类型,其实没有达到解决的目的。

          56.前端金额显示为xx.00元:

          如果字段为String类型:return (parseInt(price*100)/100).toFixed(2);

          如果字段为Number类型:(price * 100 / 100).toFixed(2)

          57.redis的redisTemplate.boundValueOps(key).set(value);方法中,key为test:::则是在redis分了test一层, 二,三,四层,一共四层

          58.方法A调用异步方法B,如果方法A回滚了,方法B仍会执行,所以最好的办法就是在方法A执行结束确保没问题了,再去通知异步执行。

          59.Vue的async表示该方法是异步方法。await表示等待,表示是同步方法。.then(res => { }); 表示一个同步方法;但是当方法上有async时就是一个异步方法。

          await要放在异步方法里面使用,和promise或者async结合使用。无论是使用箭头函数,还是使用await,最终结果都是获得promise

          60.vue-amap获取地图实例:this.amap = this.$refs.map.$$getInstance();

          61.问题:

          • 后端在序列化json时,在BigDecimal长度大于17位(不包括小数点)会出现精度丢失,在Long长度大于17位时也会出现精度丢失的问题。

          • 前端请求后端接口获取BigDecimal类型字段数值时丢失精度,例如:5999.00变成5999、5999.50变成5999.5

            治标方法:(parseInt(price*100)/100).toFixed(2) (price * 100 / 100).toFixed(2) 将小数点后截断为保留两位

          解决:

          • 类属性直接定义成String类型,处理好之后再返回前端

          在代转化的字段上加上 @JsonFormat(shape = JsonFormat.Shape.STRING)

          在待转化的字段之上加上@JsonSerialize(using=ToStringSerializer.class),但是仍有问题:对BigDecima类型的属性进行转换之后发现,数据后尾会多个0,这是因为这个ToStringSerializer.class类直接对该属性进行了toString()操作

          正常来说对BigDecima类型的属性打印都是用:

          1
          decimalObject.stripTrailingZeros().toPlainString()

          针对BigDecima类型处理重新写一个专门的子类:

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31
          32
          33
          public class BigDecimalToStringSerializer extends ToStringSerializer {
          public static final BigDecimalToStringSerializer instance = new BigDecimalToStringSerializer();

          public BigDecimalToStringSerializer() {
          super(Object.class);
          }

          public BigDecimalToStringSerializer(Class<?> handledType) {
          super(handledType);
          }

          @Override
          public boolean isEmpty(SerializerProvider prov, Object value) {
          if (null == value) {
          return true;
          }
          String str = ((BigDecimal) value).stripTrailingZeros().toPlainString();
          return str.isEmpty();
          }

          @Override
          public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
          gen.writeString(((BigDecimal) value).stripTrailingZeros().toPlainString());
          }

          @Override
          public JsonNode getSchema(SerializerProvider provider, Type typeHint) throws JsonMappingException {
          return this.createSchemaNode("string", true);
          }
          }

          再在需转换字段上加上:
          @JsonSerialize(using = BigDecimalToStringSerializer.class)
          • 自定义消息转换器

          如果系统已经成型,上面两种做法会改动很多地方,所以可以添加拦截器将BigDecimal转为String

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31
          32
          33
          34
          35
          36
          37
          38
          39
          40
          41
          42
          43
          44
          45
          46
          47
          48
          49
          50
          51
          52
          53
          54
          55
          56
          57
          58
          59
          60
          61
          62
          63
          64
          65
          66
          67
          68
          69
          70
          71
          72
          73
          74
          75
          76
          77
          78
          79
          80
          81
          82
          83
          84
          85
          86
          87
          88
          89
          90
          91
          92
          93
          94
          95
          @Configuration
          public class WebMvcConfig extends WebMvcConfigurerAdapter {

          @Autowired
          private TokenInterceptor tokenInterceptor;


          @Override
          public void addInterceptors(InterceptorRegistry registry) {
          /**
          * 添加请求鉴权拦截器
          */
          registry.addInterceptor(tokenInterceptor) //注册拦截器
          .addPathPatterns(PathPattern.API_ENTRY_POINT)//添加已注册拦截器应用于的URL
          .excludePathPatterns(PathPattern.NO_AUTH_API_ENTRY_POINT);//添加注册拦截器不应该应用于的URL
          }

          @Override
          public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
          /**
          *ViewResolver使用所请求的媒体类型的一个实现
          */
          configurer.favorPathExtension(false);//是否应使用URL路径中的路径扩展来确定所请求的媒体类型
          }

          @Override
          public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
          /**
          *自定义转换器
          */
          converters.add(toStringConverter());//增加了序列化long及包装类Long需要使用ToStringSerializer转成String类型
          }

          /**
          * BigDecimal Long 转化为String
          * @return
          */
          @Bean
          public MappingJackson2HttpMessageConverter toStringConverter() {
          MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
          ObjectMapper mapper = new ObjectMapper();
          SimpleModule simpleModule = new SimpleModule();
          simpleModule.addSerializer(BigDecimal.class, BigDecimalToStringSerializer.instance);
          simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
          simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance);
          simpleModule.addSerializer(long.class, ToStringSerializer.instance);
          mapper.registerModule(simpleModule);
          converter.setObjectMapper(mapper);
          return converter;
          }

          @JacksonStdImpl
          static class BigDecimalToStringSerializer extends ToStringSerializer {
          /*
          *因为直接用ToStringSerializer,BigDecimal的值过小就会显示科学计数法,也不能去除末尾无效的零。这边自定义一个BigDecimalToStringSerializer继承自ToStringSerializer,修改里面的serialize、isEmpty方法
          */
          public final static BigDecimalToStringSerializer instance = new BigDecimalToStringSerializer();

          public BigDecimalToStringSerializer() {
          super(Object.class);
          }

          public BigDecimalToStringSerializer(Class<?> handledType) {
          super(handledType);
          }

          @Override
          public boolean isEmpty(SerializerProvider prov, Object value) {
          if (value == null) {
          return true;
          }
          String str = ((BigDecimal) value).stripTrailingZeros().toPlainString();
          return str.isEmpty();
          }

          @Override
          public void serialize(Object value, JsonGenerator gen, SerializerProvider provider)
          throws IOException {
          gen.writeString(((BigDecimal) value).stripTrailingZeros().toPlainString());
          }

          @Override
          public JsonNode getSchema(SerializerProvider provider, Type typeHint) throws JsonMappingException {
          return createSchemaNode("string", true);
          }

          @Override
          public void serializeWithType(Object value, JsonGenerator gen,
          SerializerProvider provider, TypeSerializer typeSer)
          throws IOException {
          // no type info, just regular serialization
          serialize(value, gen, provider);
          }
          }
          }

          62.商城项目里如果历史订单里的商品属性发生变化,已经生成的订单不能更改其属性,所以当时生成的订单需要一个表来当作快照保存

          63.数据库的查询字段不区分大小写:

          在创建数据库的时候,选择了utf8_general_ci排序规则

          创建数据库时,需要同时选择字符集和排序规则,排序规则:是指对指定字符集下不同字符的比较规则

          • 两个不同的字符集不能有相同的排序规则

          • 两个字符集有一个默认的排序规则

          • 有一些常用的命名规则:如 _ci 结尾表示大小写不敏感(case insensitive collation),_cs表示大小写敏感(case sensitive collation),_bin表示二进制的比较(binary case sensitive collation):对于CHAR、VARCHAR和TEXT类型,BINARY属性可以为列分配该列字符集的 校对规则。BINARY属性是指定列字符集的二元 校对规则的简写。排序和比较基于数值字符值。因此也就自然区分了大小写

          5.6版本的mysql是不支持utf8的cs排序规则,如果要想对大小写敏感,可以使用_bin的排序规则

          使用show COLLATION;查询当前版本的数据库支持的所有排序规则。

          使用 show charset like 'utf8%';进一步查看当前字符集的默认排序规则是 什么

          将其变为大小写敏感:

          • 创建数据库时:字符集设置为utf8,排序规则设置为utf8_bin

          • 对于已经建好的表,可以修改表的属性:在default前加binary。也可以写sql语句:

          ALTER TABLE TABLENAME MODIFY COLUMN COLUMNNAME VARCHAR(50) BINARY CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL;

          • 可以将查询条件用binary()括起来:
          1
          select * from TableA where binary columnA ='aaa';

          64.vue可以自动将<style></style>代码块内的样式代码px转换成rem。但是如果是写在html元素里的内联样式style=“ ”里的px就不会自动转换。所以需要将内联样式写成rem或者将样式写在对应的style选择器里才会保证移动端的不同机型不会出现样式错乱。

          65.v-if和v-else的问题:当页面加载时,会先将if和else的内容都加载到页面上,然后根据条件隐藏掉不用显示的,就会导致页面好像跳转了。解决方案:

          1
          2
          3
          4
          5
          6
          7
          网页端在style的代码块里加入:
          [v-cloak] {
          display:none;
          }
          再在if和else的代码块后面加入v-cloak
          但是此方案对移动端不生效,可以考虑加一个过渡状态:
          通过自定义一个变量 isLoading 在data里初始化为 false ,在数据请求成功之后将变量改为 true ,在 template 中通过变量来控制是否显示隐藏。这样会使页面进来时显示的是空,等请求到数据之后才会出现应该出现的页面。

          66.后端自定义打包后的文件名字:

          在pom.xml里:

          1
          2
          3
          <build>  
          <finalName>你的打包后的文件名字,例如:${project.artifactId}-${project.version}-company</finalName>
          </build>

          前端自定义打包后的文件名字:

          1
          2
          3
          module.exports = {
          outputDir: 'shop',//打包后生成的文件夹名字
          }

          67.ios只能识别/,不能识别-

          1
          createTime.replace(/-/g, "/")          /g 代表全局

          68.移动端Vant组件库REM适配:https://blog.csdn.net/jyn15159/article/details/109325998

          Vant 中的样式默认使用px作为单位,如果需要使用rem单位:

          • postcss-pxtorem 是一款 postcss 插件,用于将单位px转化为 rem。
          • lib-flexible 用于设置 rem 基准值,设置 font-size 基准值

          68.前端判断一个数组是否为空不能直接判断this.array。而应该判断this.array.length

          69.后端springboot项目使用ThreadLocal的一个坑:

          ThreadLocal里set值后由于没有清除操作(清除操作写在了Filter的destroy方法,该方法只有在服务器停止时才会执行),导致之前set的值一直在这个线程里。当有一个请求没有set值,但是get取值时,会取到上一个线程的set值,导致出错。

          原因:

          应用程序或(请求线程)容器使用线程池,这意味着线程不会死。如果需要,你需要自己处理线程局部。唯一干净的方法是调用ThreadLocal.remove()方法。

          有两个原因,你可能想清除线程本地线程的线程池中的线程:

          • 以防止记忆(或假设资源)泄漏

          • 以防止意外的信息从一个请求泄漏到另一个通过线程本地的另一个请求。

          线程本地内存泄漏通常不是有界线程池的主要问题,因为任何线程局部变量最终都可能被覆盖;即当线被重复使用时。但是,如果你犯了重复创建一个新的ThreadLocal实例的错误(而不是使用静态变量来保存单例实例),线程本地值将不会被覆盖,并将累积在每个线程的threadlocals映射。这可能导致严重的泄漏。

          假设你正在谈论在webapp处理HTTP请求时创建/使用的线程局部变量,那么避免线程局部泄漏的一种方法是使用webapp的ServletContext注册ServletRequestListener,并实现监听器的requestDestroyed方法来清除线程当前线程的本地化。

          70.项目的前端商城的图片是从数据库取图片在服务器上的地址字符串,页面拼接域名成为一个完整的url后请求图片。当我把服务器上的图片更新覆盖后,图片还是之前的图片:刷新网页、resin和tomcat都是这种情况。和浏览器缓存没有关系。路径也绝对正确

          因为浏览器首次读取服务端的图片之后,再次读取同名图片,会直接从临时文件中读取,不再请求服务端。如果清除浏览器缓存,则图片更新。

          解决方法:1.修改图片名,给图片加上时间戳。让图片名不一样。2.url上可以加版本号,用随机数、计数器或时间戳作为参数,对于浏览器来说就是一个新的uri,不会访问缓存,而是加载新的东西

          71.前端项目的依赖可以在node_modules文件夹下查找,这里面实际上是所依赖的具体项目所有内容。如果想查看官方文档而github打开又很慢的话,也可以在这里查找。

          72.修改mysql密码:%和localhost是两个东西,一个是远程链接,一个是本地链接

          73.关于mapper更新:如果一个实体类实例是new出来的,set一些字段,update时其他字段保持不动。而如果是查出来的实体类实例,set一些字段,update时其他字段也是保持不动,但是是当时查出来的字段。如果set某字段为null,那么update时会忽略这个字段,不用当时查出来的字段。(解决的问题:在查出一个用户的属性时,他有公司属性,但是通过mapper的自定义方法让他公司属性变为null之后,set值再update发现公司属性又被写上了。原因:update时的实例是当时查出来带有公司属性的字段,必须将其设置为null才可。)

          74.

          1
          2
          3
          4
          5
          6
          7
          8
          9
          int 在C和C++的占用2个字节,在java中4个字节
          char在C和C+中占一个字节,在Java中无论是汉字还是英文字母都是用Unicode编码来表示的,一个Unicode码是16位,每字节是8位,所以一个Unicode码占两字节。但是英文字母比较特殊,源自于8位(1字节)的ASCII吗,于是在Unicode码仅使用了低8位(1字节)就可以表示,高8位的话不使用也无所谓。所以

          char c='a';
          System.out.println(c.getBytes().lenth()),得到的是1(字节)

          但汉字就完整地使用了16位(2字节)的Unicode,所以
          char c='中';
          System.out.println(c.getBytes().lenth()),得到的是2(字节)

          75.工具类中不要打印日志,第三方日志,为了降低依赖:被人用你的工具类不需要再引入其他依赖。

          展开全文 >>

          初识java

          2021-03-26

          java概述

          特性

          1.简单

          语法规则和c++类似,是对C++的简化和提高,使用接口取代了多重继承,并取消了指针,java还有垃圾自动收集。

          提供了丰富的类库,API文档和第三方开发包,还有大量基于java 的开源项目。

          2.面向对象(Object Oriented)

          是对现实世界的一种抽象,面向对象会把相关的数据和方法组织为一个整体来看待。所有的元素都要通过类和对象来访问。

          3.分布性

          Java语言支持Internet应用的开发,java中有net.api,他提供了用于网络应用编程的类库,包括URL、URLConnection、Socket、ServerSocket等。Java的RMI(远程方法激活)机制也是开发分布式应用的重要手段。

          包括操作分布和数据分布。操作分布是指在多个不同的主机上布置相关操作,而数据分布是将数据分别存放在多个不同的主机上。

          4.平台独立性和可移植性(write once ,run anywhere)

          核心是jvm。

          在应用中编写java代码,用Eclipse或javac把java代码编译为.class文件,然后把.class文件打成.jar文件,.jar文件可以在windows、MacOS、Linux系统下运行。

          5.解释性

          运行java程序需要解释器,任何移植了java解释器的计算机或其他设备都可以用java字节码进行解释运行。

          6. 安全性

          删除了类C语言中的指针和内存释放等语法,有效避免了用户对内存的非法操作。Java程序代码要经过代码校验,指针校验等很多测试步骤才能运行。

          7. 健壮性

          Java的强类型机制,异常处理,垃圾的自动收集等。开发工具如:Eclipse、NetBeans。

          8.多线程

          Java在用户空间实现的多线程(实现多线程的方式有:在用户空间、在内核空间、在用户和内核空间中混合实现)应用程序在同一时间并行执行多项任务,而且相应的同步机制可以保证不同的线程可以正确的共享数据。

          9.高性能

          Hotspot JVM提供了JIT(just in time)编译器即动态编译器,能够在运行时将代码编译为机器码,运行效率比较高。

          10.动态

          可以动态调整库内方法和增加变量,而客户端不需要作任何更改。

          11.开源

          java的这个特性决定了它的广泛应用。

          运行和工作原理

          Java源代码(HelloWorld.java) –> java字节码文件(HelloWorld.class)–> 机器码 –> 运行结果

          1.首先编写java源代码程序,扩展名为.java

          2.在命令行模式中,输入javac命令:javac 源文件名.java对源代码进行编译,生成字节码文件,扩展名为.class

          3.编译完成后,如果没有报错信息,输入java命令:java 类名对class字节码文件进行解释运行,执行时不需要添加.class扩展名

          这里是我最早接触java时,还没有用到开发工具,在cmd命令行进行的HelloWorld输出。当时踩得坑如下:

          • 文件隐藏了扩展名

          • javac+文件名,java+类名 (所以当时需要文件名和类名一致)

          • 严格区分大小写

          • 非法字符:/65307确是中文问题,要保证全部英文状态

          • 括号成对

          • 最后贴一个梦开始的main方法:

            public static void main(String[] args){

            ​ System.out.println(“HelloWorld”);

            }

          版本

          版本名称发行日期
          JDK 1.0Oak(橡树)1996-01-23
          JDK 1.1none(无)1997-02-19
          JDK 1.1.4Sparkler(宝石)1997-09-12
          JDK 1.1.5Pumpkin(南瓜)1997-12-13
          JDK 1.1.6Abigail(阿比盖尔–女子名)1998-04-24
          JDK 1.1.7Brutus(布鲁图–古罗马政治家和将军)1998-09-28
          JDK 1.1.8Chelsea(切尔西–城市名)1999-04-08
          J2SE 1.2Playground(运动场)1998-12-04
          J2SE 1.2.1none(无)1999-03-30
          J2SE 1.2.2Cricket(蟋蟀)1999-07-08
          J2SE 1.3Kestrel(美洲红隼)2000-05-08
          J2SE 1.3.1Ladybird(瓢虫)2001-05-17
          J2SE 1.4.0Merlin(灰背隼)2002-02-13
          J2SE 1.4.1grasshopper(蚱蜢)2002-09-16
          J2SE 1.4.2Mantis(螳螂)2003-06-26
          Java SE 5.0 (1.5.0)Tiger(老虎)2004-09-30
          Java SE 6.0 (1.6.0)Mustang(野马)2006-04
          Java SE 7.0 (1.7.0)Dolphin(海豚)2011-07-28
          Java SE 8.0 (1.8.0)Spider(蜘蛛)2014-03-18
          Java SE 9.0none(无)2017-09-21
          Java SE 10.0none(无)2018-03-21
          Java SE 11.0none(无)2018-09-25

          这里的JAVA就是指JDK开发工具

          Java2

          1998年12月8日,Sun公司发布了第二代Java平台(简称为Java2)的3个版本

          J2ME(Java2 Micro Edition,Java2平台的微型版):主要用于嵌入式系统的开发,应用于移动、无线及有限资源的环境

          J2SE(Java 2 Standard Edition,Java 2平台的标准版):应用于桌面应用软件的编程

          J2EE(Java 2 Enterprise Edition,Java 2平台的企业版):应用于基于Java的应用服务器

          Java5

          2004年9月30日

          1.自动拆装箱

          2.泛型

          3.增强for

          说明:是for循环的一种,简化数组和Collection集合的遍历(增强for就是来替代迭代器的)

          格式:

          for(元素数据类型 变量 : 数组或者Collection集合) {

          ​ 使用变量即可,该变量就是元素

          ​ }

          注意事项:增强for的目标要先进行不为null的判断,然后再使用

          4.静态导入

          说明:可以直接导入到方法级别

          格式:import static 包名….类名.方法名;

          注意事项:

          ​ 方法必须是静态的

          ​ 如果有多个同名的静态方法,容易不知道使用谁?这个时候要使用的话,就必须在调用前面加静态的包,类名前缀。由此可见,意义不大,所以一般不用。

          5.可变参数

          说明:定义方法的时候不知道该定义多少个参数,当调用的时候才知道需要几个参数。

          格式:

          public static int sunm(int… a){}

          ​ 修饰符 返回值类型 方法名(数据类型… 变量名){

          }

          注意事项:

          1.这里的变量(a)其实是一个数组

          2.如果一个方法有可变参数,并且有多个参数,那么,可变参数肯定是最后一个(如果不是最后一个,相当于前面的都包含了,最后单独的多出来了,所以就会报错。只有当可变参数是最后一个才是正常的)

          3.如果要调用的方法可以和两个可变参数匹配,则出现错误

          4.在调用方法的时候,如果能够和固定参数的方法匹配,也能够与可变长参数的方法匹配,则选择固定参数的方法

          Arrays工具类中的一个方法:把数组转成集合

          public static <T> List<T> asList(T... a)

          注意事项:集合的长度不能改变,因为其本质还是一个数组。(所以对集合进行操作时,只要改变了其长度就会报错)

          6.枚举

          Java7

          1.二进制字面量

          JDK7开始,可以用二进制来表示整数(byte,short,int和long)。

          好处:可以使代码更容易被理解。语法非常简单,只要在二进制数值前面加 0b或者0B

          2.数字字面量可以出现下划线

          为了增强对数值的阅读性,如我们经常把数据用逗号分隔一样。JDK7提供了_对数据进行分隔。

          注意事项:

          ​ 不能出现在进制标识和数值之间

          ​ 不能出现在数值开头和结尾

          ​ 不能出现在小数点旁边

          3.switch 语句可以用字符串

          4.泛型简化(泛型推断)

          5.异常的多个catch合并

          出现了一个新的异常处理方案:

          try {

          } catch (异常名1 | 异常名2 | 异常名3 … 变量 ) {

          ​ }

          这个方式虽然简洁,但是也不够好:

          A:处理方式是一致的。(实际开发中,好多时候可能就是针对同类型的问题,给出同一个处理)

          B:多个异常间必须是平级关系

          6.try-with-resources 语句

          try ( 必须是java.lang.AutoCloseable的子类对象 ) { … }

          好处:

          ​ 资源自动释放,不需要close()了

          ​ 把需要关闭资源的部分都定义在()中就ok了

          这个接口的子类主要是流体系的对象(JDK7的API中AutoCloseable的子类)

          Java8

          2014年3月18日,普遍使用的版本,是java5以来最具革命性的版本。支持32位

          1.允许接口中有默认方法,静态方法实现

          2.Lambda表达式

          一、引言

          java8最大的特性就是引入Lambda表达式,即函数式编程,可以将行为进行传递。总结就是:使用不可变值与函数,函数对不可变值进行处理,映射成另一个值。

          二、Java重要的函数式接口

          1、什么是函数式接口

          函数接口是只有一个抽象方法的接口,用作 Lambda 表达式的类型。使用@FunctionalInterface注解修饰的类,编译器会检测该类是否只有一个抽象方法或接口,否则,会报错。可以有多个默认方法,静态方法。

          1.1 java8自带的常用函数式接口。

          图片

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31
          32
          33
          34
          35
          36
          37
          38
          39
          40
          41
          42
          43
          44
          45
          46
          47
          48
          49
          50
          public class Test {
          public static void main(String[] args) {
          Predicate<Integer> predicate = x -> x > 185;
          Student student = new Student("9龙", 23, 175);
          System.out.println(
          "9龙的身高高于185吗?:" + predicate.test(student.getStature()));

          Consumer<String> consumer = System.out::println;
          consumer.accept("命运由我不由天");

          Function<Student, String> function = Student::getName;
          String name = function.apply(student);
          System.out.println(name);

          Supplier<Integer> supplier =
          () -> Integer.valueOf(BigDecimal.TEN.toString());
          System.out.println(supplier.get());

          UnaryOperator<Boolean> unaryOperator = uglily -> !uglily;
          Boolean apply2 = unaryOperator.apply(true);
          System.out.println(apply2);

          BinaryOperator<Integer> operator = (x, y) -> x * y;
          Integer integer = operator.apply(2, 3);
          System.out.println(integer);

          test(() -> "我是一个演示的函数式接口");
          }

          /**
          * 演示自定义函数式接口使用
          *
          * @param worker
          */
          public static void test(Worker worker) {
          String work = worker.work();
          System.out.println(work);
          }

          public interface Worker {
          String work();
          }
          }
          //9龙的身高高于185吗?:false
          //命运由我不由天
          //9龙
          //10
          //false
          //6
          //我是一个演示的函数式接口

          以上演示了lambda接口的使用及自定义一个函数式接口并使用。下面,我们看看java8将函数式接口封装到流中如何高效的帮助我们处理集合。

          注意:Student::getName例子中这种编写lambda表达式的方式称为方法引用。格式为ClassNmae::methodName。是不是很神奇,java8就是这么迷人。

          示例:本篇所有示例都基于以下三个类。OutstandingClass:班级;Student:学生;SpecialityEnum:特长。

          图片

          1.2 惰性求值与及早求值

          惰性求值:只描述Stream,操作的结果也是Stream,这样的操作称为惰性求值。惰性求值可以像建造者模式一样链式使用,最后再使用及早求值得到最终结果。

          及早求值:得到最终的结果而不是Stream,这样的操作称为及早求值。

          2、常用的流

          2.1 collect(Collectors.toList())

          将流转换为list。还有toSet(),toMap()等。及早求值。

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          public class TestCase {
          public static void main(String[] args) {
          List<Student> studentList = Stream.of(new Student("路飞", 22, 175),
          new Student("红发", 40, 180),
          new Student("白胡子", 50, 185)).collect(Collectors.toList());
          System.out.println(studentList);
          }
          }
          //输出结果
          //[Student{name='路飞', age=22, stature=175, specialities=null},
          //Student{name='红发', age=40, stature=180, specialities=null},
          //Student{name='白胡子', age=50, stature=185, specialities=null}]

          2.2 filter

          顾名思义,起过滤筛选的作用。内部就是Predicate接口。惰性求值。

          图片

          比如我们筛选出出身高小于180的同学。

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          public class TestCase {
          public static void main(String[] args) {
          List<Student> students = new ArrayList<>(3);
          students.add(new Student("路飞", 22, 175));
          students.add(new Student("红发", 40, 180));
          students.add(new Student("白胡子", 50, 185));

          List<Student> list = students.stream()
          .filter(stu -> stu.getStature() < 180)
          .collect(Collectors.toList());
          System.out.println(list);
          }
          }
          //输出结果
          //[Student{name='路飞', age=22, stature=175, specialities=null}]

          2.3 map

          转换功能,内部就是Function接口。惰性求值

          图片

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          public class TestCase {
          public static void main(String[] args) {
          List<Student> students = new ArrayList<>(3);
          students.add(new Student("路飞", 22, 175));
          students.add(new Student("红发", 40, 180));
          students.add(new Student("白胡子", 50, 185));

          List<String> names = students.stream().map(student -> student.getName())
          .collect(Collectors.toList());
          System.out.println(names);
          }
          }
          //输出结果
          //[路飞, 红发, 白胡子]

          例子中将student对象转换为String对象,获取student的名字。

          2.4 flatMap

          将多个Stream合并为一个Stream。惰性求值

          图片

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          public class TestCase {
          public static void main(String[] args) {
          List<Student> students = new ArrayList<>(3);
          students.add(new Student("路飞", 22, 175));
          students.add(new Student("红发", 40, 180));
          students.add(new Student("白胡子", 50, 185));

          List<Student> studentList = Stream.of(students,
          asList(new Student("艾斯", 25, 183),
          new Student("雷利", 48, 176)))
          .flatMap(students1 -> students1.stream()).collect(Collectors.toList());
          System.out.println(studentList);
          }
          }
          //输出结果
          //[Student{name='路飞', age=22, stature=175, specialities=null},
          //Student{name='红发', age=40, stature=180, specialities=null},
          //Student{name='白胡子', age=50, stature=185, specialities=null},
          //Student{name='艾斯', age=25, stature=183, specialities=null},
          //Student{name='雷利', age=48, stature=176, specialities=null}]

          调用Stream.of的静态方法将两个list转换为Stream,再通过flatMap将两个流合并为一个。

          2.5 max和min

          我们经常会在集合中求最大或最小值,使用流就很方便。及早求值。

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          public class TestCase {
          public static void main(String[] args) {
          List<Student> students = new ArrayList<>(3);
          students.add(new Student("路飞", 22, 175));
          students.add(new Student("红发", 40, 180));
          students.add(new Student("白胡子", 50, 185));

          Optional<Student> max = students.stream()
          .max(Comparator.comparing(stu -> stu.getAge()));
          Optional<Student> min = students.stream()
          .min(Comparator.comparing(stu -> stu.getAge()));
          //判断是否有值
          if (max.isPresent()) {
          System.out.println(max.get());
          }
          if (min.isPresent()) {
          System.out.println(min.get());
          }
          }
          }
          //输出结果
          //Student{name='白胡子', age=50, stature=185, specialities=null}
          //Student{name='路飞', age=22, stature=175, specialities=null}

          max、min接收一个Comparator(例子中使用java8自带的静态函数,只需要传进需要比较值即可。)并且返回一个Optional对象,该对象是java8新增的类,专门为了防止null引发的空指针异常。

          可以使用max.isPresent()判断是否有值;可以使用max.orElse(new Student()),当值为null时就使用给定值;也可以使用max.orElseGet(() -> new Student());这需要传入一个Supplier的lambda表达式。

          2.6 count

          统计功能,一般都是结合filter使用,因为先筛选出我们需要的再统计即可。及早求值

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          public class TestCase {
          public static void main(String[] args) {
          List<Student> students = new ArrayList<>(3);
          students.add(new Student("路飞", 22, 175));
          students.add(new Student("红发", 40, 180));
          students.add(new Student("白胡子", 50, 185));

          long count = students.stream().filter(s1 -> s1.getAge() < 45).count();
          System.out.println("年龄小于45岁的人数是:" + count);
          }
          }
          //输出结果
          //年龄小于45岁的人数是:2

          2.7 reduce

          reduce 操作可以实现从一组值中生成一个值。在上述例子中用到的 count 、 min 和 max 方法,因为常用而被纳入标准库中。事实上,这些方法都是 reduce 操作。及早求值。

          图片

          1
          2
          3
          4
          5
          6
          7
          8
          public class TestCase {
          public static void main(String[] args) {
          Integer reduce = Stream.of(1, 2, 3, 4).reduce(0, (acc, x) -> acc+ x);
          System.out.println(reduce);
          }
          }
          //输出结果
          //10

          我们看得reduce接收了一个初始值为0的累加器,依次取出值与累加器相加,最后累加器的值就是最终的结果。

          三、高级集合类及收集器

          3.1 转换成值

          收集器,一种通用的、从流生成复杂值的结构。只要将它传给 collect 方法,所有的流就都可以使用它了。标准类库已经提供了一些有用的收集器,以下示例代码中的收集器都是从 java.util.stream.Collectors 类中静态导入的。

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31
          32
          33
          34
          35
          36
          37
          38
          39
          public class CollectorsTest {
          public static void main(String[] args) {
          List<Student> students1 = new ArrayList<>(3);
          students1.add(new Student("路飞", 23, 175));
          students1.add(new Student("红发", 40, 180));
          students1.add(new Student("白胡子", 50, 185));

          OutstandingClass ostClass1 = new OutstandingClass("一班", students1);
          //复制students1,并移除一个学生
          List<Student> students2 = new ArrayList<>(students1);
          students2.remove(1);
          OutstandingClass ostClass2 = new OutstandingClass("二班", students2);
          //将ostClass1、ostClass2转换为Stream
          Stream<OutstandingClass> classStream = Stream.of(ostClass1, ostClass2);
          OutstandingClass outstandingClass = biggestGroup(classStream);
          System.out.println("人数最多的班级是:" + outstandingClass.getName());

          System.out.println("一班平均年龄是:" + averageNumberOfStudent(students1));
          }

          /**
          * 获取人数最多的班级
          */
          private static OutstandingClass biggestGroup(Stream<OutstandingClass> outstandingClasses) {
          return outstandingClasses.collect(
          maxBy(comparing(ostClass -> ostClass.getStudents().size())))
          .orElseGet(OutstandingClass::new);
          }

          /**
          * 计算平均年龄
          */
          private static double averageNumberOfStudent(List<Student> students) {
          return students.stream().collect(averagingInt(Student::getAge));
          }
          }
          //输出结果
          //人数最多的班级是:一班
          //一班平均年龄是:37.666666666666664

          maxBy或者minBy就是求最大值与最小值。

          3.2 转换成块

          常用的流操作是将其分解成两个集合,Collectors.partitioningBy帮我们实现了,接收一个Predicate函数式接口。

          图片

          将示例学生分为会唱歌与不会唱歌的两个集合。

          1
          2
          3
          4
          5
          6
          7
          8
          public class PartitioningByTest {
          public static void main(String[] args) {
          //省略List<student> students的初始化
          Map<Boolean, List<Student>> listMap = students.stream().collect(
          Collectors.partitioningBy(student -> student.getSpecialities().
          contains(SpecialityEnum.SING)));
          }
          }

          3.3 数据分组

          数据分组是一种更自然的分割数据操作,与将数据分成 ture 和 false 两部分不同,可以使用任意值对数据分组。Collectors.groupingBy接收一个Function做转换。

          图片

          如图,我们使用groupingBy将根据进行分组为圆形一组,三角形一组,正方形一组。

          例子:根据学生第一个特长进行分组

          1
          2
          3
          4
          5
          6
          7
          8
          public class GroupingByTest {
          public static void main(String[] args) {
          //省略List<student> students的初始化
          Map<SpecialityEnum, List<Student>> listMap =
          students.stream().collect(
          Collectors.groupingBy(student -> student.getSpecialities().get(0)));
          }
          }

          Collectors.groupingBy与SQL 中的 group by 操作是一样的。

          3.4 字符串拼接

          如果将所有学生的名字拼接起来,怎么做呢?通常只能创建一个StringBuilder,循环拼接。使用Stream,使用Collectors.joining()简单容易。

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          public class JoiningTest {
          public static void main(String[] args) {
          List<Student> students = new ArrayList<>(3);
          students.add(new Student("路飞", 22, 175));
          students.add(new Student("红发", 40, 180));
          students.add(new Student("白胡子", 50, 185));

          String names = students.stream()
          .map(Student::getName).collect(Collectors.joining(",","[","]"));
          System.out.println(names);
          }
          }
          //输出结果
          //[路飞,红发,白胡子]

          joining接收三个参数,第一个是分界符,第二个是前缀符,第三个是结束符。也可以不传入参数Collectors.joining(),这样就是直接拼接。

          3.函数式接口

          4.内置函数式接口

          5.Streams

          得益于Lambda所带来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端

          传统集合在于使用循环遍历。for循环的语法就是“怎么做”,for循环的循环体才是“做什么”。

          Java 8的Lambda让我们可以更加专注于做什么(What),而不是怎么做(How)

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          import java.util.ArrayList;
          import java.util.List;

          /**
          * @author tanglei
          * @date 2020/6/10 10:28 下午
          */
          public class StreamFilter {
          public static void main(String[] args) {
          List<String> list = new ArrayList<>();
          list.add("张无忌");
          list.add("周芷若");
          list.add("赵敏");
          list.add("张强");
          list.add("张三丰");

          list.stream()
          .filter(s -> s.startsWith("张"))
          .filter(s -> s.length() == 3)
          .forEach(System.out::println);
          }
          }

          “Stream流”其实是一个集合元素的函数模型,它并不是集合,也不是数据结构,其本身并不存储任何 元素(或其地址值

          获取流:

          所有的 Collection 集合都可以通过 stream 默认方法获取流;

          Stream 接口的静态方法 of 可以获取数组对应的流

          根据Collection获取流:

          1
          2
          3
          4
          5
          6
          7
          public static void main(String[] args) {
          List<String> list = new ArrayList<>();
          Stream<String> stream1 = list.stream();
          Set<String> set = new HashSet<>();
          Stream<String> stream2 = set.stream();
          Vector<String> vector = new Vector<>();
          }

          根据Map获取流

          1
          2
          3
          4
          5
          6
          public static void main(String[] args) {
          Map<String, String> map = new HashMap<>();
          Stream<String> keyStream = map.keySet().stream();
          Stream<String> valueStream = map.values().stream();
          Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();
          }

          根据数组获取流

          1
          2
          3
          4
          public static void main(String[] args) {
          String[] array = { "张无忌", "张翠山", "张三丰", "张一元" };
          Stream<String> stream = Stream.of(array);
          }

          常用方法

          逐一处理:forEach 会将每一个流元素交给该函数进行处理

          1
          2
          3
          4
          public static void main(String[] args) {
          Stream<String> stream = Stream.of("张无忌", "张三丰", "周芷若");
          stream.forEach(name‐> System.out.println(name));
          }

          过滤:filter 可以通过 filter 方法将一个流转换成另一个子集流

          1
          2
          3
          4
          public static void main(String[] args) {
          Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
          Stream<String> result = original.filter(s ‐> s.startsWith("张"));
          }

          映射:map 如果需要将流中的元素映射到另一个流中

          1
          2
          3
          4
          public static void main(String[] args) {
          Stream<String> original = Stream.of("10", "12", "18");
          Stream<Integer> result = original.map(str‐>Integer.parseInt(str));
          }

          统计个数:count 数一数其中的元素个数

          1
          2
          3
          4
          5
          public static void main(String[] args) {
          Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
          Stream<String> result = original.filter(s ‐> s.startsWith("张"));
          System.out.println(result.count()); // 2
          }

          取用前几个:limit 可以对流进行截取,只取用前n个

          1
          2
          3
          4
          5
          public static void main(String[] args) {
          Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
          Stream<String> result = original.limit(2);
          System.out.println(result.count()); // 2
          }

          跳过前几个:skip 如果希望跳过前几个元素

          1
          2
          3
          4
          5
          public static void main(String[] args) {
          Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
          Stream<String> result = original.skip(2);
          System.out.println(result.count()); // 1
          }

          组合:concat 如果有两个流,希望合并成为一个流

          1
          2
          3
          4
          5
          public static void main(String[] args) {
          Stream<String> streamA = Stream.of("张无忌");
          Stream<String> streamB = Stream.of("张翠山");
          Stream<String> result = Stream.concat(streamA, streamB);
          }

          一个List泛型根据另一个List泛型的属性进行过滤

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31
          32
          33
          34
          35
          36
          37
          38
          39
          40
          41
          42
          43
          List<User> list1 = new ArrayList<>();

          User u1 = new User();
          u1.setName("u1");
          u1.setAge(12)

          User u2 = new User();
          u2.setName("u2");
          u2.setAge(13)

          User u3 = new User();
          u3.setName("u3");
          u3.setAge(13)

          list1.add(u1);
          list1.add(u2);
          list1.add(u3);

          List<User> list2 = new ArrayList<>();

          User u4 = new User();
          u4.setName("u4");
          u4.setAge(13)

          list2.add(u3);
          list2.add(u4);

          //resList 过滤后的结果
          List<User> resList = list1 .stream().filter(new Predicate<User>() {
          @Override
          public boolean test(User u) {
          //如根据name过滤
          for (User user : list2) {
          if(u.getName().equals(user.getName())){
          return false;
          }
          }
          return true;
          }
          }).collect(Collectors.toList());


          //resList 输出为 u1 u2 (去掉了重复的u3)

          6.Map

          7. 时间日期API

          Java8以前,我们一直长期使用Date和Calendar来处理时间,而在使用Date处理日期时间问题上会存在一定的隐患,产生线程不安全的问题,最典型的就是在一定负载并发量的情况下使用SimpleDateFormat引发的线程安全性问题。如今Java8提供了LocalDate、LocalTime、LocalDateTime三个日期时间类,在安全性和操作性上对比Date和Calendar非常可观

          1. 使用Date输出的日期可读性差(在不进行日期格式化的情况下)

          Tue Sep 10 09:34:04 CST 2019

          1. 在对Date使用SimpleDateFormat进行日期时间格式化时我们需要明确的知道,SimpleDateFormat是线程不安全的,在高并发高负载的情况下使用,极容易引发线程安全性问题

          calendar是共享变量,并且这个共享变量没有做线程安全控制。当多个线程同时使用相同的SimpleDateFormat对象【如用static修饰的SimpleDateFormat】调用format方法时,多个线程会同时调用calendar.setTime方法,可能一个线程刚设置好time值另外的一个线程马上把设置的time值给修改了导致返回的格式化时间可能是错误的。SimpleDateFormat除了format是线程不安全以外,parse方法也是线程不安全的。parse方法实际调用alb.establish(calendar).getTime()方法来解析,alb.establish(calendar)方法里主要完成了以下操作(这三步不是原子性操作):

          重置日期对象cal的属性值
          使用calb中中属性设置cal
          返回设置好的cal对象

          多线程并发如何保证线程安全

          避免线程之间共享一个SimpleDateFormat对象,每个线程使用时都创建一次SimpleDateFormat对象(问题:创建和销毁对象时大量内存资源开销)
          对使用format和parse方法的地方进行加锁(问题:线程阻塞性能差)
          使用ThreadLocal保证每个线程最多只创建一次SimpleDateFormat对象,属于较好的方法。
          3. Date对时间处理比较麻烦

          比如想获取某年、某月、某星期,以及n天以后的时间,使用Date来处理的话会特别麻烦,你可能会说Date类不是有getYear、getMonth这些方法吗,获取年月日很Easy,但都被JDK弃用了。

          LocalDate:年月日

          LocalTime:时分秒

          LocalDateTime:年月日时分秒

          1. LocalDate

          //获取当前年月日
          LocalDate localDate = LocalDate.now();
          //构造指定的年月日
          LocalDate localDate1 = LocalDate.of(2019, 9, 10);
          //获取年、月、日、星期几
          int year = localDate.getYear();
          int year1 = localDate.get(ChronoField.YEAR);
          Month month = localDate.getMonth();
          int month1 = localDate.get(ChronoField.MONTH_OF_YEAR);
          int day = localDate.getDayOfMonth();
          int day1 = localDate.get(ChronoField.DAY_OF_MONTH);
          DayOfWeek dayOfWeek = localDate.getDayOfWeek();
          int dayOfWeek1 = localDate.get(ChronoField.DAY_OF_WEEK);
          2. LocalTime

          // 获取当前时间
          LocalTime localTime1 = LocalTime.now();
          // 获取指定时间
          LocalTime localTime = LocalTime.of(13, 51, 10);
          //获取小时
          int hour = localTime.getHour();
          int hour1 = localTime.get(ChronoField.HOUR_OF_DAY);
          //获取分
          int minute = localTime.getMinute();
          int minute1 = localTime.get(ChronoField.MINUTE_OF_HOUR);
          //获取秒
          int second = localTime.getMinute();
          int second1 = localTime.get(ChronoField.SECOND_OF_MINUTE);

          1. LocalDateTime

          等同于LocalDate+LocalTime

          // 获取当前日期 年月日时分秒
          LocalDateTime localDateTime = LocalDateTime.now();
          // 设置指定日期时间 年月日时分秒
          LocalDateTime localDateTime1 = LocalDateTime.of(2019, Month.SEPTEMBER, 10, 14, 46, 56);
          LocalDateTime localDateTime2 = LocalDateTime.of(localDate, localTime);
          LocalDateTime localDateTime3 = localDate.atTime(localTime);
          LocalDateTime localDateTime4 = localTime.atDate(localDate);
          LocalDate localDate2 = localDateTime.toLocalDate();
          LocalTime localTime2 = localDateTime.toLocalTime();
          4. Instant(获取秒数)

          Instant instant = Instant.now();
          long currentSecond = instant.getEpochSecond();
          long currentMilli = instant.toEpochMilli();
          注:个人觉得如果只是为了获取秒数或者毫秒数,使用System.currentTimeMillis()来得更为方便

          LocalDate、LocalTime、LocalDateTime、Instant为不可变对象,修改这些对象对象会返回一个副本。比如增加、减少年数、月数、天数等,以LocalDateTime为例:

          LocalDateTime localDateTime = LocalDateTime.of(2019, Month.SEPTEMBER, 10,
          14, 46, 56);
          //增加一年
          localDateTime = localDateTime.plusYears(1);
          localDateTime = localDateTime.plus(1, ChronoUnit.YEARS);
          //减少一个月
          localDateTime = localDateTime.minusMonths(1);
          localDateTime = localDateTime.minus(1, ChronoUnit.MONTHS);
          通过with修改某些值

          //修改年为2019
          localDateTime = localDateTime.withYear(2020);
          //修改为2022
          localDateTime = localDateTime.with(ChronoField.YEAR, 2022);
          还可以修改月、日,比如有些时候想知道这个月的最后一天是几号、下个周末是几号

          LocalDate localDate = LocalDate.now();
          //比如通过firstDayOfYear()返回了当前日期的第一天日期,还有很多方法这里不在举例说明
          LocalDate localDate1 = localDate.with(firstDayOfYear());

          格式化时间

          LocalDate localDate = LocalDate.of(2019, 9, 10);
          String s1 = localDate.format(DateTimeFormatter.BASIC_ISO_DATE);
          String s2 = localDate.format(DateTimeFormatter.ISO_LOCAL_DATE);
          //自定义格式化
          // DateTimeFormatter默认提供了多种格式化方式,如果默认提供的不能满足要求,可以通过DateTimeFormatter的ofPattern方法创建自定义格式化方式
          DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(“dd/MM/yyyy”);
          String s3 = localDate.format(dateTimeFormatter);

          解析时间

          LocalDate localDate1 = LocalDate.parse(“20190910”, DateTimeFormatter.BASIC_ISO_DATE);
          LocalDate localDate2 = LocalDate.parse(“2019-09-10”, DateTimeFormatter.ISO_LOCAL_DATE);

          不能单纯认为localdatetime是calender类的替代品,它和date一样是一种类型,是数据库支持的类型,并且提出更加简易的方法

          8.Annotations

          9.Optional类型

          它是作为某个指定类型的对象的包装器或者用于那些不存在对象(null)的场景

          简单来说,它是处理空值的一个更好的替代品

          使用下它的三个静态方法就可以了:

          public static Optional stringOptional(String input) {

          return Optional.of(input);

          }

          简单明了——创建一个包含这个值的Optional包装器。记住——如果这个值是null的话,它会抛出NPE!public static Optional stringNullableOptional(String input) {

          if (!new Random().nextBoolean()) {

          input = null;

          }

          return Optional.ofNullable(input);

          }

          我个人认为是要更好一点。这样就不会有NPE的风险了——如果输入为null的话,会返回一个空的Optionalpublic static Optional emptyOptional() {

          return Optional.empty();

          }

          如果你真的就是希望返回一个”空”值的话。“空”值并不意味着null。

          好吧,那如何去消费/使用Optional呢?public static void consumingOptional() {

          Optional wrapped = Optional.of(“aString”);

          if (wrapped.isPresent()) {

          System.out.println(“Got string - “ + wrapped.get());

          }

          else {

          System.out.println(“Gotcha !”);

          }

          }

          简单的方法就是检查Optional包装器是否真的有值(使用isPresent方法)——你会怀疑这和使用if(myObj != null)相比有什么好处。别担心,这个我会解释清楚的。public static void consumingNullableOptional() {

          String input = null;

          if (new Random().nextBoolean()) {

          input = “iCanBeNull”;

          }

          Optional wrapped = Optional.ofNullable(input);

          System.out.println(wrapped.orElse(“default”));

          }

          你可以使用orElse方法,这样万一封装的确实是一个null值的话可以用它来返回一个默认值——它的好处显而易见。在提取出真实值的时候可以避免调用ifPresent方法这样明显多余的方式了。public static void consumingEmptyOptional() {

          String input = null;

          if (new Random().nextBoolean()) {

          input = “iCanBeNull”;

          }

          Optional wrapped = Optional.ofNullable(input);

          System.out.println(wrapped.orElseGet(

          () -> {

          return “defaultBySupplier”;

          }

          ));

          }

          这个我就有点搞不清楚了。为什么有两个同样目的的不同方法?orElse和orElseGet明明可以重载的(同名但不同参数)。

          不论如何,这两个方法明显的区别就在于它们的参数——你可以选择使用lambda表达式而不是Supplier的实例来完成这个(一个函数式接口)

          为什么使用Optional要比常见的null检查强?

          1.使用Optional最大的好处就是可以更明白地表述你的意图——返回null值的话会让消费者感到疑惑(当真的出现NPE的时候)这是不是故意返回的,因此还得查看javadoc来进一步定位。而使用Optional就相当明了了。

          2.有了Optional你就可以彻底避免NPE了——如上所提,使用Optional.ofNullable,orElse以及orElseGet可以让我们远离NPE。

          Java11

          2018年9月26日

          安装

          明确概念

          1.JDK(java development kit):java开发工具包JRE+tools

          ​ 是提供给java开发人员使用的,其中包含了java的开发工具,也包括了JRE

          ​ Java的开发工具:编译工具(javac.exe)和打包工具(jar.exe)

          2.JRE(java runtime environment):java运行环境 JVM+class library

          ​ 包括java虚拟机和java程序所需的核心类库等

          3.JVM(java virture machine):java虚拟机

          jvm是用C和汇编语言写的,jdk的开发工具包都是用java写的。

          可以去网上查找jdk并下载安装,这里推荐使用JDK1.8

          配置JDK环境变量

          要使java文件写在其他文件下面,仍可以运行,就需要配置环境变量。

          我的电脑→属性→高级系统设置→环境变量→系统变量:以下三个环境变量 已存在则点击“编辑”,不存在则点击“新建”

          1.新建JAVA_HOME(变量名)指向的是JDK的安装路径(变量值),在此路径下能找到bin、lib等目录。

          2.编辑PATH环境变量,目的是为了指向JDK的bin目录,这里面放的是各种编译执行命令。目的是使javac指令可以在任意目录下运行。

          配置方法一:

          ​ 将javac指令所在目录也就是JDK安装目录下的bin目录配置到path变量下,即可使javac指令在任意目录下运行。系统中本来有path环境变量,直接放后面就行,中间用分号间隔。path环境变量具有先后顺序

          具体方法:

          ​ 将JDK安装目录下的bin目录添加到最左边并添加分号

          配置方法二(因为jdk可能有多个,主要用这个):

          ​ 新建变量名为:JAVA_HOME 变量值为:JDK安装目录 将path环境变量中的JDK目录修改为:%JAVA_HOME%\bin;(%%相当于引用)

          ​ 切换JDK时只需修改变量值

          Tips:操作系统因不明原因导致%JAVA_HOME%环境变量失效,即使正确配置“Path”中的内容,也无法验证通过。此时可以采用“弃用JAVA_HOME”的办法,在“Path”环境变量中,直接添加JDK下bin文件夹的完整地址(即配置方法一)。

          如果“弃用JAVA_HOME”之后依然无法通过验证,则要打开JDK的bin文件夹,检查此文件夹下是否有“java.exe”和“javac.exe”这两个文件,这两个可执行文件实际上就是java命令和javac命令的“本体”,如果缺失了这两个文件,JDK就无法正常使用,环境也就永远无法验证通过。

          3.CLASSPATH(class文件的环境变量)一般不用配置,仅了解,不推荐配置在系统的环境变量中

          ​ 创建新的变量名称:classpath 变量值设定为指定的含有class文件的目录,多个目录之间使用分号(;)分割。

          ​ 作用:使classpath目录中的.class文件可以在任意目录运行

          classpath目录中的配置存在先后顺序,先在第一个目录下查找,没有的话;.再继续查找第二个,依次类推

          ​ 区别:path环境变量里面记录的是可执行性文件,如:.exe文件。

          ​ classpath环境变量里面记录的是java类的运行文件所在的目录如:.class文件

          验证结果

          cmd中输入java -version 或者输入javac

          总结

          ​ 这里的下载安装很简单,最主要的是JDK的环境变量配置。原理就是在path的环境变量中加入java的bin文件夹(这个bin文件夹 中会有javac.exe和java.exe),这样在运行java时,就会找到javac命令和java命令并运行。设置JAVA_HOME的目的是为了防止有多个java时,在切换版本时需要不断更改操作path,为了方便操作,当path中运行到%JAVA_HOME%.bin时,会去再访问JAVA_HOME这个变量下的java文件夹,最终目的是一样的。classpath就是一组目录的集合,它设置了一个搜索路径,当运行某个类时,会依次查找这个目录下的这个类,找到了就会运行,如果找不到就会报错。所以一般情况下,不用配置classpath,用默认的配置在当前文件下查找这个类即可。

          ​ 这里的配置环境变量是接触的第一个环境变量。环境变量这个东西一通百通,以后的环境变量配置也可以参照这个原理,其根本目的都是要使系统的path路径中可以找到你的软件的bin目录。教程只是一方面,背后的原理才是以后会独立配置的关键。

          • java概述
          • 历史版本
          • 安装

          展开全文 >>

          java开发工具之IDEA

          2021-03-25

          IDEA介绍

          IDEA 全称 IntelliJ IDEA,是java编程语言开发的集成环境(集成开发环境IDE(Integrated Development Environment):把开发,运行,编译集成在一起)。IntelliJ在业界被公认为最好的java开发工具,尤其在智能代码助手、代码自动提示、重构、JavaEE支持、各类版本工具(git、svn等)、JUnit、CVS整合、代码分析、 创新的GUI设计等方面的功能可以说是超常的。IDEA属于JetBrains这个公司,旗下的知名软件还有:PyCharm-智能 Python 集成开发工具,WebStorm - 最智能的 JavaScript IDE。

          ​ 在刚接触java的时候,跟着视频下载了eclipse,白色的界面,很有教科书的感觉。当时看到一些博主,微信公众号的推送的代码示例截图,编写代码的工具都是黑色的,感觉很像命令行的风格,以为很高大上,就很羡慕(其实只是界面皮肤的问题)。后来才接触到了idea,默认的一种黑色的界面给了我逼格很高的感觉,想着终于拥有了大佬一样的开发工具,我也算是大佬了,hhhh。就像是长江后浪推前浪,有的人认为eclipse也是在不断完善,认为新兴的idea始终无法替代eclipse;也有的人自从接触了idea就爱不释手,毫不犹豫的抛弃了eclipse。说到底这也只是个工具而已,能够帮助我们更高效、简洁、方便的开发代码,达到我们的目的,使用什么工具又有什么所谓呢。

          常用快捷键

          目前常用

          快捷键 介绍
          Ctrl + D 复制光标所在行 或 复制选择内容,并把复制内容插入光标位置下面
          Ctrl + / 注释光标所在行代码,会根据当前不同文件类型使用不同的注释符号
          ctrl+shif+/ 注释一段代码
          Ctrl + F 在当前文件进行文本查找
          ctrl+shift+f 在全局搜索
          Ctrl + Z 撤销
          Ctrl + Shift + Z 取消撤销
          Alt + F7 查找光标所在的方法 / 变量 / 类被调用的地方
          Ctrl + Alt + 鼠标左键 在某个调用的方法名上使用会跳到具体的实现处,可以跳过接口
          Ctrl + Alt + ← 跳到上次浏览的位置
          alt+shift+上下键 将某一行或选中的区域整体上下移动
          ctrl + alt + u 显示类之间的关系
          sout System.out.println();
          fori for(int i=0;i< ;i++)
          itar Iterate elements of array
          Shift + F6 对文件 / 文件夹 重命名(变量,类名,方法名都可以)
          ctrl+r 替换本页内容
          ctrl+shift+r 在多个文件中替换内容

          ctrl

          快捷键 介绍
          Ctrl + R 在当前文件进行文本替换 (必备)
          Ctrl + O 选择可重写的方法
          Ctrl + W 递进式选择代码块。可选中光标所在的单词或段落,连续按会在原有选中的基础上再扩展选中范围 (必备)
          Ctrl + Y 删除光标所在行 或 删除选中的行 (必备)
          Ctrl + E 显示最近打开的文件记录列表
          Ctrl + U 前往当前光标所在的方法的父类的方法 / 接口定义
          Ctrl + N 根据输入的 类名 查找类文件
          Ctrl + H 显示当前类的层次结构
          Ctrl + I 选择可继承的方法
          Ctrl + G 在当前文件跳转到指定行处
          Ctrl + J 插入自定义动态代码模板
          Ctrl + P 方法参数提示显示
          Ctrl + Q 光标所在的变量 / 类名 / 方法名等上面(也可以在提示补充的时候按),显示文档内容
          Ctrl + B 进入光标所在的方法/变量的接口或是定义出,等效于 Ctrl + 左键单击
          Ctrl + K 版本控制提交项目,需要此项目有加入到版本控制才可用
          Ctrl + T 版本控制更新项目,需要此项目有加入到版本控制才可用
          Ctrl + + 展开代码
          Ctrl + - 折叠代码
          Ctrl + [ 移动光标到当前所在代码的花括号开始位置
          Ctrl + ] 移动光标到当前所在代码的花括号结束位置
          Ctrl + Space 基础代码补全,默认在 Windows 系统上被输入法占用,需要进行修改,建议修改为 Ctrl + 逗号 (必备)
          Ctrl + Delete 删除光标后面的单词或是中文句
          Ctrl + BackSpace 删除光标前面的单词或是中文句
          Ctrl + F1 在光标所在的错误代码出显示错误信息
          Ctrl + F3 调转到所选中的词的下一个引用位置
          Ctrl + F4 关闭当前编辑文件
          Ctrl + F8 在 Debug 模式下,设置光标当前行为断点,如果当前已经是断点则去掉断点
          Ctrl + F9 执行 Make Project 操作
          Ctrl + F11 选中文件 / 文件夹,使用助记符设定 / 取消书签
          Ctrl + F12 弹出当前文件结构层,可以在弹出的层上直接输入,进行筛选
          Ctrl + Tab 编辑窗口切换,如果在切换的过程又加按上delete,则是关闭对应选中的窗口
          Ctrl + Enter 智能分隔行
          Ctrl + End 跳到文件尾
          Ctrl + Home 跳到文件头
          Ctrl + 1,2,3…9 定位到对应数值的书签位置
          Ctrl + 左键单击 在打开的文件标题上,弹出该文件路径
          Ctrl + 光标定位 按 Ctrl 不要松开,会显示光标所在的类信息摘要
          Ctrl + 左方向键 光标跳转到当前单词 / 中文句的左侧开头位置
          Ctrl + 右方向键 光标跳转到当前单词 / 中文句的右侧开头位置
          Ctrl + 前方向键 等效于鼠标滚轮向前效果
          Ctrl + 后方向键 等效于鼠标滚轮向后效果

          alt

          快捷键 介绍
          Alt + Enter IntelliJ IDEA 根据光标所在问题,提供快速修复选择,光标放在的位置不同提示的结果也不同 (必备)
          Alt + Insert 代码自动生成,如生成对象的 set / get 方法,构造函数,toString() 等
          Alt + Q 弹出一个提示,显示当前类的声明 / 上下文信息
          Alt + ` 显示版本控制常用操作菜单弹出层
          Alt + F1 显示当前文件选择目标弹出层,弹出层中有很多目标可以进行选择
          Alt + F2 对于前面页面,显示各类浏览器打开目标选择弹出层
          Alt + F3 选中文本,逐个往下查找相同文本,并高亮显示
          Alt + F8 在 Debug 的状态下,选中对象,弹出可输入计算表达式调试框,查看该输入内容的调试结果
          Alt + Home 定位 / 显示到当前文件的 Navigation Bar
          Alt + 左方向键 按左方向切换当前已打开的文件视图
          Alt + 右方向键 按右方向切换当前已打开的文件视图
          Alt + 前方向键 当前光标跳转到当前文件的前一个方法名位置
          Alt + 后方向键 当前光标跳转到当前文件的后一个方法名位置
          Alt + 1,2,3…9 显示对应数值的选项卡,其中 1 是 Project 用得最多

          shift

          快捷键 介绍
          Shift + 滚轮前后滚动 当前文件的横向滚动轴滚动
          Shift + F1 如果有外部文档可以连接外部文档
          Shift + F2 跳转到上一个高亮错误 或 警告位置
          Shift + F3 在查找模式下,查找匹配上一个
          Shift + F4 对当前打开的文件,使用新Windows窗口打开,旧窗口保留
          Shift + F7 在 Debug 模式下,智能步入。断点所在行上有多个方法调用,会弹出进入哪个方法
          Shift + F8 在 Debug 模式下,跳出,表现出来的效果跟 F9 一样
          Shift + F9 等效于点击工具栏的 Debug 按钮
          Shift + F10 等效于点击工具栏的 Run 按钮
          Shift + F11 弹出书签显示层
          Shift + Tab 取消缩进
          Shift + ESC 隐藏当前 或 最后一个激活的工具窗口
          Shift + End 选中光标到当前行尾位置
          Shift + Home 选中光标到当前行头位置
          Shift + Enter 开始新一行。光标所在行下空出一行,光标定位到新行位置
          Shift + 左键单击 在打开的文件名上按此快捷键,可以关闭当前打开文件

          ctrl+alt

          快捷键 介绍
          Ctrl + Alt + L 格式化代码,可以对当前文件和整个包目录使用 (必备)
          Ctrl + Alt + T 对选中的代码弹出环绕选项弹出层
          Ctrl + Alt + V 快速引进变量
          Ctrl + Alt + O 优化导入的类,可以对当前文件和整个包目录使用 (必备)
          Ctrl + Alt + I 光标所在行 或 选中部分进行自动代码缩进,有点类似格式化
          Ctrl + Alt + J 弹出模板选择窗口,讲选定的代码加入动态模板中
          Ctrl + Alt + H 调用层次
          Ctrl + Alt + Y 同步、刷新
          Ctrl + Alt + S 打开 IntelliJ IDEA 系统设置
          Ctrl + Alt + F7 显示使用的地方。寻找被该类或是变量被调用的地方,用弹出框的方式找出来
          Ctrl + Alt + F11 切换全屏模式
          Ctrl + Alt + Enter 光标所在行上空出一行,光标定位到新行
          Ctrl + Alt + Home 弹出跟当前文件有关联的文件弹出层
          Ctrl + Alt + Space 类名自动完成

          Ctrl + Shift

          快捷键 介绍
          Ctrl + Shift + U 对选中的代码进行大 / 小写轮流转换 (必备)
          Ctrl + Shift + F 根据输入内容查找整个项目 或 指定目录内文件 (必备)
          Ctrl + Shift + / 代码块注释 (必备)
          Ctrl + Shift + Space 智能代码提示
          Ctrl + Shift + Enter 自动结束代码,行末自动添加分号 (必备)
          Ctrl + Shift + Backspace 退回到上次修改的地方
          Ctrl + Shift + J 自动将下一行合并到当前行末尾 (必备)
          Ctrl + Shift + R 根据输入内容替换对应内容,范围为整个项目 或 指定目录内文件 (必备)
          Ctrl + Shift + W 递进式取消选择代码块。可选中光标所在的单词或段落,连续按会在原有选中的基础上再扩展取消选中范围 (必备)
          Ctrl + Shift + N 通过文件名定位 / 打开文件 / 目录,打开目录需要在输入的内容后面多加一个正斜杠 (必备)
          Ctrl + Shift + I 快速查看光标所在的方法 或 类的定义
          Ctrl + Shift + E 显示最近修改的文件列表的弹出层
          Ctrl + Shift + T 对当前类生成单元测试类,如果已经存在的单元测试类则可以进行选择
          Ctrl + Shift + C 复制当前文件磁盘路径到剪贴板
          Ctrl + Shift + V 弹出缓存的最近拷贝的内容管理器弹出层
          Ctrl + Shift + H 显示方法层次结构
          Ctrl + Shift + B 跳转到类型声明处
          Ctrl + Shift + A 查找动作 / 设置
          Ctrl + Shift + [ 选中从光标所在位置到它的顶部中括号位置
          Ctrl + Shift + ] 选中从光标所在位置到它的底部中括号位置
          Ctrl + Shift + + 展开所有代码
          Ctrl + Shift + - 折叠所有代码
          Ctrl + Shift + F7 高亮显示所有该选中文本,按Esc高亮消失
          Ctrl + Shift + F8 在 Debug 模式下,指定断点进入条件
          Ctrl + Shift + F9 编译选中的文件 / 包 / Module
          Ctrl + Shift + F12 编辑器最大化
          Ctrl + Shift + 1,2,3…9 快速添加指定数值的书签
          Ctrl + Shift + 左方向键 在代码文件上,光标跳转到当前单词 / 中文句的左侧开头位置,同时选中该单词 / 中文句
          Ctrl + Shift + 右方向键 在代码文件上,光标跳转到当前单词 / 中文句的右侧开头位置,同时选中该单词 / 中文句
          Ctrl + Shift + 左方向键 在光标焦点是在工具选项卡上,缩小选项卡区域
          Ctrl + Shift + 右方向键 在光标焦点是在工具选项卡上,扩大选项卡区域
          Ctrl + Shift + 前方向键 光标放在方法名上,将方法移动到上一个方法前面,调整方法排序
          Ctrl + Shift + 后方向键 光标放在方法名上,将方法移动到下一个方法前面,调整方法排序

          Alt + Shift

          快捷键 介绍
          Alt + Shift + F7 在 Debug 模式下,下一步,进入当前方法体内,如果方法体还有方法,则会进入该内嵌的方法中,依此循环进入
          Alt + Shift + N 选择 / 添加 task
          Alt + Shift + F 显示添加到收藏夹弹出层
          Alt + Shift + C 查看最近操作项目的变化情况列表
          Alt + Shift + F 添加到收藏夹
          Alt + Shift + I 查看项目当前文件
          Alt + Shift + F9 弹出 Debug 的可选择菜单
          Alt + Shift + F10 弹出 Run 的可选择菜单
          Alt + Shift + 左键双击 选择被双击的单词 / 中文句,按住不放,可以同时选择其他单词 / 中文句
          Alt + Shift + 前方向键 移动光标所在行向上移动
          Alt + Shift + 后方向键 移动光标所在行向下移动

          Ctrl + Shift + Alt

          快捷键 介绍
          Ctrl + Shift + Alt + V 无格式黏贴
          Ctrl + Shift + Alt + N 前往指定的变量 / 方法
          Ctrl + Shift + Alt + S 打开当前项目设置
          Ctrl + Shift + Alt + C 复制参考信息

          其他

          快捷键 介绍
          F2 跳转到下一个高亮错误 或 警告位置 (必备)
          F3 在查找模式下,定位到下一个匹配处
          F4 编辑源
          F7 在 Debug 模式下,进入下一步,如果当前行断点是一个方法,则进入当前方法体内,如果该方法体还有方法,则不会进入该内嵌的方法中
          F8 在 Debug 模式下,进入下一步,如果当前行断点是一个方法,则不进入当前方法体内
          F9 在 Debug 模式下,恢复程序运行,但是如果该断点下面代码还有断点则停在下一个断点上
          F11 添加书签
          F12 回到前一个工具窗口
          Tab 缩进
          ESC 从工具窗口进入代码文件窗口
          连按两次Shift 弹出 Search Everywhere 弹出层

          使用IDEA读取底层源码的快捷键集合
          自己常用的两种方式:
          如果是看接口的底层,就用ctrl+alt
          如果是看其他的底层,就双击锁定,然后两下shift即可.
          如果看继承树,ctrl+h

          感觉通常都是ctrl+键是主要功能,shift是全选,alt

          快捷键 说明
          Alt+回车 导入包,自动修正
          Ctrl+Alt+L 格式化代码
          Alt+Insert 生成代码(如get,set方法,构造函数等)
          Ctrl+F 查找文本
          Ctrl+N 查找类
          Ctrl+Shift+N 查找文件
          Ctrl+Shift+Alt+N 查找类中的方法或变量
          Ctrl+R 在当前文件进行文本替换
          Ctrl+Y 删除光标所在行 或 删除选中的行
          Ctrl+Space 代码提示
          Ctrl+Shift+Space 自动补全代码(常用)
          Ctrl+P 方法参数提示
          Ctrl+/或Ctrl+Shift+/ 注释(//或者/…./)
          Ctrl+D 复制光标所在行 或 复制选择内容,并把复制内容插入光标位置下面
          Ctrl+Shift+Up/Down 代码向上/下移动
          Alt+ Up/Down 在方法间快速移动定位
          Ctrl + U 前往当前光标所在的方法的父类的方法 / 接口定义
          Ctrl+Alt+left/right 返回至上次浏览的位置
          F2 或Shift+F2 高亮错误或警告快速定位
          选中文本,按Alt+F3 逐个往下查找相同文本,并高亮显示

          IDEA出现的问题

          1.idea查看源码时会显示/*compiled code*/,一般新版的idea会自带反编译功能,如果出现了表示反编译没有成功。

          解决方案:

          File->setting->Plugins,然后下拉,找到Java Bytecode Decompiler 打钩 应用保存 重启

          如果发现已经打过勾仍出现这种情况,可能是因为用了 java decompiler Intellij Plugin这个插件,取消打勾之后重启也可。

          IDEA个人配置

          1.Java文件统一添加固定Header,通过IDE统一配置(code templates)  File and Code Templates

          1
          2
          3
          4
          5
          6
          /**
          * @author swx
          * @site
          * @company
          * @create ${YEAR}-${MONTH}-${DAY}-${TIME}
          */

          2.Auto Import

          可以帮助我们自动删除无用的包Import(未被引用),以及自动Import填充尚未导入的包。完全智能化地帮助我们在开发程序时,省略了导包的操作,大大优化了开发的效率。并且,当你移动某个类改变其路径的时候,这个功能会相应的改变关联的文件中包的路径

          Settings→Editor→General→Auto Import
          然后勾选Add unambiguous imports on the fly以及Optimize imports on the fly

          Add unambiguous imports on the fly:快速添加明确的导入。

          Optimize imports on the fly:快速优化导入,优化的意思即自动帮助删除无用的导入。

          IDEA插件

          JRebel

          是一款JVM插件,它使得Java代码修改后不用重启系统,立即生效

          1.搜索并安装插件jrebel

          2.在线GUID地址:在线生成GUID

          https://www.guidgen.com/

          3.ctrl+shift+s 打开settings设置,找到jrebel 面板,选择Connect to online licensing service

          image-20211107125210013

          https://jrebel.qekang.com/+生成的GUID

          自己的邮箱地址

          4.打开jrebel面板,把JRebel设置为offline模式 点一下work offline

          5.设置项目自动编译

          settings下找到build,Execution,deployment下的compiler,勾选build project automaticlly

          6.设置 compiler.automake.allow.when.app.running

          ctrl+shift+A 或者 help->find action…打开并搜索registry或者按快捷键 Ctrl+Shift+Alt+/ ,选择 Registry
          找到 compiler.automake.allow.when.app.running 并✔

          开始使用:

          左下角有一个Jrebel,点击打开的窗口,选择你所需要进行热部署的项目。勾选后会在resource下生成一个rebel.xml文件,此文件可以忽略

          这样设置之后就可以通过JRebel的图标启动项目。这样修改完Java代码后,就可以通过快捷键 Ctrl+shift+F9(更多的用ctrl+F9编译) 而不再需要重启站点这样繁琐浪费时间的操作了

          修改快捷键,可以利用宏设置:

          edit—>macros—>start macro recording

          右下角会出现一个提示

          在这里插入图片描述

          然后点击ctrl+F9,此时右下角会有提示,点击红色停止

          在这里插入图片描述

          会弹出一个窗口,在窗口中输入宏的名字build

          打开设置,找到kaymap,找到宏下面会出现刚刚设置的名字,可以设置快捷键,以后就可以用新的快捷键

          Jrebel不适用于resources包下面的文件,修改此包下面的必须重启,否则不生效

          创建项目

          创建新项目

          1. 启动 IntelliJ IDEA。

          如果打开欢迎屏幕,请单击“新建项目”。否则,从主菜单中选择 File | New | Project。

          1. 在 New Project 向导中,从左侧的列表中选择 Java。

          2. 要在 intellijidea 中开发 Java 应用程序,您需要 javasdk (JDK)。

          如果所需的 JDK 已经在 IntelliJ IDEA 中定义,那么从 Project SDK 列表中选择它。

          如果 JDK 安装在您的计算机上,但是没有在 IDE 中定义,那么选择 Add JDK 并指定 JDK home 目录的路径(例如,/library/java/javavialmachines/JDK-13.0.1)。

          图片

          如果您的计算机上没有必要的 JDK,选择 Download JDK。在下一个对话框中,指定 JDK 供应商(例如 OpenJDK)、版本,如果需要则更改安装路径,然后单击 Download。

          图片

          在本教程中,我们不打算使用任何其他库或框架,因此请单击“下一步”。不要从模板创建项目。

          为项目命名,例如:HelloWorld。 如有必要,请更改默认项目位置并单击“完成”。

          创建 package 和 class

          包用于将属于同一类别或提供类似功能的类组合在一起,用于构造和组织具有数百个类的大型应用程序。

          1. 在 Project 工具窗口中,选择 src 文件夹,按 Alt + Insert,然后选择 Java Class

          2. 在 Name 字段中,键入 com.example.HelloWorld. HelloWorld 并单击 OK. IntelliJ IDEA 创建 com.example.HelloWorld 包和 HelloWorld 类。

          图片

          与该文件一起,IntelliJ IDEA 自动为您的类生成了一些内容。在这种情况下,IDE 插入了包语句和类声明。这是通过文件模板来完成的。根据您创建的文件的类型,IDE 插入初始代码和预期在该类型的所有文件中存在的格式。有关如何使用和配置模板的详细信息,请参阅文件模板。

          开始编码

          通过动态模板增加 main 方法

          图片

          输出 print 方法

          图片

          运行方法

          图片

          将工程打包为 Jar 包

          图片

          图片

          如果你能看到 out/artifacts 文件夹,那么你将能看到 Jar 包

          图片

          运行打包的应用程序,执行 Jar

          基础操作

          在相同内容后生成光标,一个一个选择 :

          具体什么意思可以看下面这个动图就行了,需要配合快捷键使用,可以在相同的关键字后面生成光标,这样删除、修改添加新的代码不就方便了吗?

          • Windows:Alt + J
          • Mac: Ctrl + G

          图片

          在所有的相同的内容后添加光标,一步到位:

          使用快捷键就能在所有的 word 后生成光标

          • Windows:Ctrl + Shift + Alt + J
          • Mac: Ctrl + Command + G

          图片

          数列光标:

          方法:alt + 按住鼠标左键拖动就能达到下面动图的效果了,效率 max!

          图片

          行尾添加光标

          alt + 按住鼠标左键拖动,从左侧拖动到右侧。

          图片

          在指定位置添加光标操作

          图片

          格式批量调整

          图片

          将上方左侧的代码批量变为右侧代码

          alt+shift+鼠标选中多个光标,ctrl+w扩大选中范围,ctrl+c复制,ctrl + alt + enter 将光标移动到当前行的上一行

          图片

          批量添加 Swagger 属性注释

          图片

          将上图左侧只有注释的类,添加上 swagger 信息

          ctrl+shift+鼠标点击多个选中,复制,回车,输入,粘贴

          图片

          在多个相同结构 Json 中提取某字段信息

          图片

          提取左侧 Json 中所有的 role 字段

          图片

          IDEA常用快捷键

          上面介绍了一些配合鼠标使用的快捷键,还有一些常用快捷键。

          方法参数提示
          ctrl + p

          非常实用的快捷键, 有的时候我们自己写的方法, 或者在看一些源码时, 都非常有用

          文本大小写转换

          ctrl + shift + U

          折叠代码/展开代码

          ctrl + - / ctrl + +

          快速查找和打开最近使用过的文件码

          ctrl + E

          自动代码片

          ctrl + j

          实现接口方法

          ctrl + i

          查看当前类的子类

          ctrl + h

          将当前行和下一行进行合并

          ctrl + shfit + j

          将光标跳到当前行的上一行

          有时候在写完一行代码的时候需要添加注释, 或者为类属性添加注释的时候需要跳到当前行的上一行, 这个快捷键就非常方便

          ctrl + alt + enter

          idea git 提交

          ctrl + k

          删除当前行

          ctrl + y

          重写 或者 实现接口或父类方法

          ctrl + o

          删除类中没有用到的 package

          ctrl + alt + o

          进入设置界面

          ctrl + alt + s

          在当前光标在的这样一行的下一行添加一行

          ctrl + shfit + enter

          弹出, 当前类中的方法集合

          ctrl + F12

          最常用的快捷键之一, 快速的查找方法

          添加书签

          ctrl + F11

          快速生成 try, if 等语句

          alt + shift + t

          当你试用了之后, 你会爱上这个快捷键的

          抽取局部变量

          ctrl + alt + v

          将当前选中的代码抽取为一个局部变量

          进入到实现子类中

          ctrl + alt + b

          在使用mvc框架的时候, 往往我们只有一个接口的实例 这个快捷键可以直接到实现类中

          格式化代码

          让代码变得优美, 是每个程序员都应该注意的事, 方便自己和他人阅读, 利人利己

          idea 多光标选择

          按下滚轮上下拖动鼠标即可

          ctrl + alt + L

          idea 批量修改相同内容

          ctrl + alt + shift + j

          有的时候数据需要批量处理, 比如, 正常来说我们的实体类, 在使用mybatis 等逆向工程进行生成的时候, 一般属性是有注释的, 但是在针对如果我们使用了swagger 等插件需要来显示传递实体所代表的含义的时候, 就需要我们自己一个个的去写, 就会显得异常麻烦

          运行当前类

          ctrl + shift + F10

          在写一些测试代码的时候 这个快捷键就显得特别方便

          从多项目中启动一个 debug 模式

          alt + shfit + F9

          在微服务中 多个工程在一个项目中的时候, 这个方法就比较的好用, 这样就不用自己一个一个的去点省去很多没必要的操作

          从多项目中启动一个 正常模式

          alt + shfit + F10

          重新编译当前项目

          ctrl + shift + F9

          当你发现有的问题 特别的奇怪, 命名表面上没问题, 但就是项目运行不了的时候, 重新编译一下获取就好了

          查看当前类在哪些地方被使用过

          快速的查看选中类, 选中方法的定义

          有的时候我们不想进入方法内部, 或者进入类的内部查看细节, 想要在外面就探查清楚, 就可以使用此种方法

          ctrl + shift + i

          图片

          比较强大的几个快捷键之一 Ctrl + ~ (感叹号旁边的按键)

          共有五种可供选择的操作

          Switch Code Formatter (切换代码格式化程序)

          1. Color Scheme (配色方案)

          可以设置一些常用的配色, 字体样式, 可以一键切换

          图片2. Code Style Scheme

          3. Keymap (快捷键列表)

          图片

          4. View Mode (显示模式)

          图片

          5. Look and Feel (设置软件主题)

          idea 调出版本控制操作

          alt + ~

          \4. IDEA常用设置

          IDEA 以新窗口的形式打开多个项目

          File - Settings - Appearance & Behavior - System Settings

          图片

          修改 IDEA 默认编码 -> UTF-8

          File - Settings - Editor - File Encodings

          图片

          设置统一编译器和编译版本

          File - Setting - Build - Compiler - Java Compiler

          图片

          设置类注释

          File - Editor- File and Code Templates

          图片

          $$end$$ 可以设置光标结束的位置

          自动导包

          File - Editor- General - Auto Import

          图片

          内存使用量展示

          由于日常开发时都是在公司的办公电脑上进行的,所以内存总是不够用,但是又不清楚 IDEA 具体实时的占用了多少内存。这个时候对于一些内存并不是太够的开发人员来说能看到实时的内存使用量还是比较好的

          File - Settings - Appearance & Behavior

          图片

          开启悬浮提示

          有时候在看代码的时候,不清楚一个类具体是干什么的,就会点进去看这个类的注释,但是强大的 IDEA 是支持不用点进去就可以看到注释的以及类的相关信息的。

          File - Settings - Editor - General

          图片

          Ctrl+鼠标滚轴修改字体大小

          IDEA 也支持向浏览器那样按住 Ctrl+鼠标滚轴来改变编辑区的字体的大小

          File–>Settings–>Editor–>General。

          图片

          显示多行 Tab

          当我们打开的标签页多了的时候,默认的会隐藏在右侧,当我们需要的时候在右侧找到后再打开。IDEA 是支持多行显示的,这样在大屏幕的显示器上也不用总去点击右侧的去找刚才打开过的文件了

          File - Settings - Editor - General - Editor Tabs

          图片

          设置字体, 行距 让代码看着更舒服

          File - Settings - Editor - Font

          图片

          IDEA GIT 配置

          File - Settings - Version Control - Git

          图片

          IDEA MAVEN 配置

          File - Settings - Build - Build Tools - Maven

          图片

          maven 阿里镜像配置

          1
          2
          3
          4
          5
          <mirror>
          <id>nexus</id>
          <mirrorOf>*</mirrorOf>
          <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
          </mirror>

          自动编译

          具体步骤:顶部工具栏 File ->Other Settings -> Default Settings -> Auto Import

          说明:开启自动编译之后,结合Ctrl+Shift+F9 会有热更新效果。

          图片

          具体步骤:敲击 Ctrl + Shift + Alt + / 然后双击Shift搜索进入Registry ,找到compiler.automake.allow.when.app.running ,然后勾选上。

          图片

          图片

          最后 如果想知道SpringBoot如何具体实现热部署的请点击SpringBoot+IDEA实现热部署教程:

          https://www.jianshu.com/p/f658fed35786

          取消大小写敏感

          具体步骤:

          File | Settings | Editor | General | Code Completion Case | Sensitive Completion = None

          取消大小敏感,在编写代码的时候,代码的自动提示将更加全面和丰富。

          图片

          调整字体类型和字体大小

          默认的白色背景和细小的字体会影响大家的编码体验,这里特意提供了调整代码窗的快捷配置。打开配置,搜索Font,然后再Font可以调整字体类型,Size可以调整字体大小,如图:

          图片

          将快捷键设置为跟Eclipse一样

          很多人可能并不习惯IDEA的快捷键,为了方便,这里我们将快捷键设置为跟 Eclipse一样。

          具体步骤: File -> Settings -> Keymap - > 选择Eclipse .

          图片

          从Eclipse转过来的小伙伴 可以放心使用

          打开常用工具栏

          具体步骤:顶部导航栏 - View -> 勾选 Toolbar & Tool Buttons

          \5. IDEA 常用插件

          AlibabaCloudToolkit自动部署

          这个插件更加适用于小型项目,或者在测试环境开发。生产环境 个人感觉不太适用。

          个人经常会有这样的需求,每次自己更新完测试环境之后, 就需要 Maven 打包clean install, 然后copy jar 包, 利用ftp工具上传jar包到测试服务器, 然后kill 服务, 在启动服务 java -jar , 有时更新频繁 这就是一件非常麻烦的事

          Cloud Toolkit 是本地 IDE 插件,帮助开发者更高效地开发、测试、诊断并部署应用。通过插件,您可以将本地应用一键部署到云端(ECS、EDAS 和 Kubernetes 等)和任意服务器;并且它还内置了 Arthas 程序诊断、Dubbo工具、Terminal Shell 终端和 MySQL 执行器等工具。

          官方链接:https://www.aliyun.com/product/cloudtoolkit

          简单的说, 安装了这个插件之后,Idea 就具备了一些 Jenkins 的自动部署的功能

          1 安装

          在 Idea 工具中 Plugins 直接搜索安装

          2 使用

          在安装完成之后, 在工具栏中就会出现阿里云的按钮, 点击按钮

          图片

          然后点击 Deploy to Host, 然后下方就会出现添加主机页面

          图片

          点击 Add Host

          图片

          以我自己的博客为例, 输入完配置之后, 点击 Test Connection, 出现 Succeeded, 点击 add, 代表添加成功

          图片

          然后再点击 Deploy to Host

          图片

          点击Run, idea 便会, 先使用maven打包, 后发送到服务器的指定位置

          图片

          图片

          后续还可以 监听启动日志, 很简单, 就是 Advanced 里面, 大家看看就知道了,

          IDEA 插件 Git Flow

          插件作用:集成 Git Flow 让我们更加专注在 开发 这件事上。

          Git Flow:

          https://medium.com/@rafavinnce/gitflow-branch-guide-8a523360c053

          安装

          图片

          使用

          最开始还没有初始化的时候,点击右下脚 GitFlow init

          图片

          直接 默认 设置就好,点击 Ok 之后,就可以开始使用了。

          图片

          按照最规范的流程走,可以避免在未来某个阶段掉坑里。

          Git 版本管理规范:

          master:永远处于production-ready状态

          • 主分支,产品的功能全部实现后,最终在master分支对外发布;
          • 只读分支,只能从release或hotfix分支合并,不能修改;
          • 所有在master分支的推送应该做标签记录,方便追溯。

          develop:最新的下次发布的开发状态

          • 主开发分支,基于master分支克隆,发布到下一个release;
          • 只读分支,feature功能分支完成,合并到develop(不推送);
          • develop拉取release分支,提测;
          • release/hotfix分支上线完毕,合并到develop并推送。

          feature:开发新功能都从develop分支出来,完成后merge回develop

          • 功能开发分支,基于develop分支克隆,用于新需求的开发;
          • 功能开发完毕后合并到develop分支(未正式上线之前不能推送到远程中央仓库)
          • feature可以同时存在多个,用于团队多功能同步开发,属于临时分支,开发完毕后可以删除。

          release:准备要release的版本,只修bug。从develop出来,完成后merge回master和develop

          • 测试分支,feature分支合并到develop分支之后,从develop分支克隆;
          • 只要用于提交给测试人员进行功能测试,测试过程中如果发现BUG在release分支修复,修复完成上线后合并到
          • develop/master分支并推送完成,做标签记录;
          • 临时分支,上线后可删除。

          hotfix:等不及release版本就必须马上修复master上线。从master出来,完成后merge回master和develop

          • 补丁分支,基于master分支克隆,主要用于对线上的版本进行BUG修复;
          • 修复完毕后合并到develop/master分支并推送,做标签记录;
          • 所有hotfix分支的修改会进入到下一个release;
          • 临时分支,补丁修复上线后可以删除;

          IDEA 插件 PlantUML

          1. 背景

          之前介绍过 使用 Vscode 画 UML,实际上 idea 中也有类似的插件 可以画 UML 图。

          PlantUML 语法:https://plantuml.com/zh/component-diagram

          安装插件安装地址:

          https://plugins.jetbrains.com/plugin/7017-plantuml-integration

          在安装完官方的插件之后,还需要额外安装一个 graphviz ,不然有的复杂的用例图就没办法展示了。

          graphviz:https://graphviz.org/download/

          2. windows 安装

          choco install graphviz

          使用

          新建文件 - PlantUML File - 选择想要创建的类型

          图片

          流程图

          图片

          流程图 CODE

          图片

          用例图

          图片

          用例图 Code

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28
          29
          30
          31
          32
          33
          34
          35
          36
          37
          38
          39
          40
          41
          42
          @startuml
          left to right direction

          actor 普通 as User
          actor 审核 as Aduitor
          actor 编辑 as Editor
          actor 营销 as Marketing
          actor 运营 as Operator
          actor 管理员 as Admin


          Admin --|> User

          rectangle 社区 {
          User -- (查看文章)
          User -- (发布文章/帖子)

          (帖子管理) <|-- (置顶)
          (帖子管理) <|-- (审核)
          (帖子管理) <|-- (推荐)
          (帖子管理) <|-- (删除)

          (社区运营) <|-- (帖子管理)
          Operator -- (社区运营)
          Aduitor -- (社区运营)
          Editor -- (社区运营)
          Marketing -- (社区运营)

          (App 配置) <|-- (商品图片配置)
          (App 配置) <|-- (启动页配置)
          (App 配置) <|-- (活动 Banner)
          (App 配置) <|-- (互动 Banner)
          (App 配置) <|-- (商城配置)
          (App 配置) <|-- (功能配置)
          (App 配置) <|-- (产品配置)
          (App 配置) <|-- (系统配置)
          (App 配置) <|-- (新人礼包)
          (App 配置) <|-- (个人中心)
          (App 配置) <|-- (渠道配置)
          Admin -- (App 配置)
          }
          @enduml

          类图

          图片

          彩虹括号 🌈 Rainbow Brackets

          由于很多人没有养成好的编码风格,没有随手 format 代码的习惯,甚至有些同事会写代码超过几百行,阅读起来将非常痛苦。

          痛苦的原因之一就是找到上下文,由于括号太多,不确定当前代码行是否属于某个代码块,此时这个插件就会帮上大忙.

          界面效果

          图片动图

          图片

          阿里巴巴编码规范:Alibaba Java Coding Guidelines

          实时监测代码的规范性, 从代码层面减少空指针等异常的出现,阿里巴巴出品的Java代码规范插件, 可以扫描整个项目找到不规范的地方 并且大部分可以自动修复。

          虽说检测功能没有 findbugs 强大,但是可以自动修复, 阿里巴巴 Java 编码指南插件支持。

          让代码变得更规范, 是我们每一位程序员都应该记在心中的事。

          变量驼峰命名规范

          图片

          当鼠标移到变量上时, 就会自动提示 不符合 lowerCamelCase命名风格

          字符串比较提示 equals

          1
          2
          3
          4
          5
          6
          7
          public static void main(String[] args) {
          String str = null;

          if (str.equals("test")) {
          System.out.println("success");
          }
          }

          WARNING

          当我们初学 Java时, 很有可能会犯这样的错误, 这里很明显会报空指针异常, 而在编辑器中

          图片

          安装插件之后, 编辑器已经给出详细提示,"test“ 应该做为 equals方法的调用方, 并给出了原因, 应为这样很容易导致空指针异常, 并给出了例子

          当你的类命名不规范时

          比如我创建了一个测试类 test.java,就会出现如下提示

          图片

          类名,首字母需要大写。缺少作者注释。

          mybatis 插件:Free Mybatis plugin

          安装此插件后可以节约很多的开发时间, 在 mapper 层接口可以直接进入 xml文件中

          IntelliJ Lombok plugin

          Lombok pom.xml 文件配置

          1
          2
          3
          4
          5
          6
          <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <version>1.16.18</version>
          <scope>provided</scope>
          </dependency>

          生命很宝贵, 没有必要浪费在这个重复的工作上, 尤其是如果我们使用传统的 get set 方法, 在实体类进行变更的时候, 或多添加了列, 或减少了列, 又要重新生成对应的 get set 这难道不就是浪费时间浪费生命吗?

          还不熟悉使用的可以看这篇文章,Java 开发之 Lombok 必知必会:

          juejin.im/post/5cf3edf7e51d454f71439c79

          GenAllSetter

          在 Java 方法中, 根据 new 关键词, 为 Java Bean 生成所有Setter方法。按 GenAllSetter 键两次, 会为 Setter 方法生成默认值。

          GenDaoCode

          一键生成 dao xml service

          CodeGlance

          在右侧生成代码地图

          RestfulToolkit

          一套 Restful 服务开发辅助工具

          • 1.根据 URL 直接跳转到对应的方法定义 ( Ctrl \ or Ctrl Alt N );
          • 2.提供了一个 Services tree 的显示窗口;
          • 3.一个简单的 http 请求工具;
          • 4.在请求方法上添加了有用功能: 复制生成 URL;,复制方法参数…
          • 5.其他功能: java 类上添加 Convert to JSON 功能,格式化 json 数据 ( Windows: Ctrl + Enter; Mac: Command + Enter )。

          Grep Console

          高亮log不同级别日志,看日志的时候一目了然。

          MyBatis Log Plugin

          把 Mybatis 输出的sql日志还原成完整的sql语句,看起来更直观。

          GsonFormat

          快速的讲一个 json转换为一个实体 安装完插件后 alt + s 放入正确的 json格式

          图片

          VisualVm Launcher

          运行java程序的时候启动visualvm,方便查看jvm的情况 比如堆内存大小的分配

          某个对象占用了多大的内存,jvm调优必备工具

          jclasslib bytecode viewer

          一款可视化的字节码查看插件

          Codota

          支持智能代码自动提示,该功能可以增强 IDEA 的代码提示功能。

          支持 JDK 和知名第三方库的函数的使用方法搜索,可以看到其他知名开源项目对该函数的用法。当我们第一次使用某个类,对某个函数不够熟悉时,可以通过该插件搜索相关用法,快速模仿学习。

          Auto filling Java call arguments

          开发中,我们通常会调用其它已经编写好的函数,调用后需要填充参数,但是绝大多数情况下,传入的变量名称和该函数的参数名一致,当参数较多时,手动单个填充参数非常浪费时间。

          该插件就可以帮你解决这个问题。安装完该插件以后,调用一个函数,使用 Alt+Enter 组合键,调出 “Auto fill call parameters” 自动使用该函数定义的参数名填充。

          Rainbow Brackets

          由于很多人没有养成好的编码风格,没有随手 format 代码的习惯,甚至有些同事会写代码超过几百行,阅读起来将非常痛苦。

          痛苦的原因之一就是找到上下文,由于括号太多,不确定当前代码行是否属于某个代码块,此时这个插件就会帮上大忙。

          SequenceDiagram

          SequenceDiagram 可以根据代码调用链路自动生成时序图,超级赞,超级推荐!

          这对研究源码,梳理工作中的业务代码有极大的帮助,堪称神器。

          安装完成后,在某个类的某个函数中,右键 –> Sequence Diagaram 即可调出。

          Java Stream Debugger

          Stream 非常好用,可以灵活对数据进行操作,但是对很多刚接触的人来说,不好理解。

          那么 Java Stream Debugger 这款神器的 IDEA 就可以帮到你。它可以将 Stream 的操作步骤可视化,非常有助于我们的学习。

          下面是刚刚介绍的这些插件的名字。

          • AlibabaCloudToolkit
          • Git Flow
          • PlantUML
          • Rainbow Brackets
          • Alibaba Java Coding Guidelines
          • 翻译插件
          • mybatis 插件
          • IntelliJ Lombok plugin
          • GenAllSetter
          • GenDaoCode
          • CodeGlance
          • RestfulToolkit
          • Grep Console
          • MyBatis Log Plugin
          • GsonFormat
          • VisualVm Launcher
          • jclasslib bytecode viewer
          • Codota
          • Auto filling Java call arguments
          • Rainbow Brackets
          • SequenceDiagram
          • Java Stream Debugger
          • Ace Jump

          \6. 好看字体

          最近JetBrains公司推出了一款优雅美观的字体:JetBrainsMono。

          对比 Consolas

          对比之前一直在使用 Consolas 字体

          图片图片

          下载字体

          官网下载:https://www.jetbrains.com/lp/mono/

          坚果云下载:jianguoyun.com/p/DRPh-GkQ_7eJCBiv2uMC

          Windows 安装

          图片

          • 下载后,解压文件
          • 直接双击扩展名为 tff 后缀的文件
          • 重启 IDEA
          • Perferences/Setting -> Editor -> Font,选择 JetBrains Mono 确认即可

          \7. 版本控制

          这里主要盘点一下在 IDEA 中进行版本控制时,大部分人没注意到的一些细节吧,主要包括下面这写。

          • 查看每一行代码的条件人, 提交时间(大部分人不知道)
          • 克隆远程代码
          • 拉取远程代码
          • 将暂存区代码提交到本地库
          • 将本地库 提交到远程库
          • 切换分支, 或拉取远程分支
          • 查看当前打开类 历史记录
          • Stash
          • Check Out

          查看每一行代码的条件人, 提交时间(大部分人不知道)

          图片

          选择后入下图所示

          图片

          鼠标移动上去还能看到提交详细信息

          图片

          克隆远程代码

          git clone origin url

          常规操作

          图片

          装逼操作

          图片

          拉取远程代码

          git pull

          图片

          快捷方式

          ctrl + t

          将暂存区代码提交到本地库

          git commit -m ‘message’

          图片 图片

          将本地库 提交到远程库

          git push

          图片

          快捷键

          ctrl + shif 或 alt + 1 + 8

          1
          切换分支, 或拉取远程分支

          图片

          以下提供几种快捷方式

          1
          ctrl + shift + ` 或 alt + ~ + 7 或

          图片

          查看当前打开类 历史记录

          1
          alt + ~ + 4查看项目工程历史记录

          选中工程后

          1
          alt + ~ + 4

          或 alt + 9 切换到 Version Control 面板 选择log

          图片

          Stash

          应用场景

          我在本地修改好后,发现远程分支已经被改动了,此时我本地也被改动了就造成了冲突,无法push或者pull。此时可以使用git stash

          1
          2
          3
          git stash //把本地的改动暂存起来
          git pull //拉取远端分支(此时本地分支会回滚到上次commit的情况,新的改动都存在了stash中)
          git stash pop // 将栈顶改动重新加回本地分支,就可以继续修改了,当然,如果改好了就是add,commit,push啥的。。

          不小心改动了其他分支,例如忘记切换,直接在master分支上做改动,这里假设我的分支是test分支

          1
          2
          3
          git stash //把本地当前改动暂存起来,此时master分支就恢复到了上次拉取时的状态
          git checkout test //切换到需要改动的分支
          git stash pop //将改动pop到自己当前的分支

          Idea 将现在本地的修改存储在 Stash 中alt + ~加 9

          图片

          将 Stash 中的代码还原

          图片

          Check Out

          将本地修改的代码还原对应命令 git checkout <file>

          \8. Terminal 配置

          将 Idea 的 Terminal 改为 Git Bash。使用体验翻倍

          图片

          以前用着这么恶心的 CMD Terminal 我居然没有想法去修改。设置看图

          图片

          调试功能

          1.设置断点

          图片

          选定要设置断点的代码行,在行号的区域后面单击鼠标左键即可。

          2.开启调试会话

          图片

          点击红色箭头指向的小虫子,开始进入调试。

          图片

          IDE下方出现 Debug 视图,红色的箭头指向的是现在调试程序停留的代码行,方法 f2() 中,程序的第11行。红色箭头悬停的区域是程序的方法调用栈区。在这个区域中显示了程序执行到断点处所调用过的所用方法,越下面的方法被调用的越早。

          3.单步调试

          3.1 step over

          图片

          点击红色箭头指向的按钮,程序向下执行一行(如果当前行有方法调用,这个方法将被执行完毕返回,然后到下一行)

          3.2 step into

          图片

          点击红色箭头指向的按钮,程序向下执行一行。如果该行有自定义方法,则运行进入自定义方法(不会进入官方类库的方法)。具体步骤如下:

          在自定义方法发f1()处设置断点,执行调试

          图片

          点击

          图片

          图片

          3.3 Force step into

          图片

          该按钮在调试的时候能进入任何方法。

          3.4 step out

          图片

          如果在调试的时候你进入了一个方法(如f2()),并觉得该方法没有问题,你就可以使用stepout跳出该方法,返回到该方法被调用处的下一行语句。值得注意的是,该方法已执行完毕。

          3.5 Drop frame

          图片

          点击该按钮后,你将返回到当前方法的调用处(如上图,程序会回到main()中)重新执行,并且所有上下文变量的值也回到那个时候。只要调用链中还有上级方法,可以跳到其中的任何一个方法。

          3.6 run to cursor

          运行到光标处

          4. 高级调试

          4.1 跨断点调试

          设置多个断点,开启调试。

          图片

          想移动到下一个断点,点击如下图:

          图片

          程序将运行一个断点到下一个断点之间需要执行的代码。如果后面代码没有断点,再次点击该按钮将会执行完程序。

          4.2 查看断点

          图片

          点击箭头指向的按钮,可以查看你曾经设置过的断点并可设置断点的一些属性。

          图片

          箭头1指向的是你曾经设置过的断点,箭头2可以设置条件断点(满足某个条件的时候,暂停程序的执行,如 c==97)。结束调试后,应该在箭头1处把所设的断点删除(选择要删除的断点后,点击上方的红色减号)。

          4.3 设置变量值

          图片

          调试开始后,在红箭头指向的区域可以给指定的变量赋值(鼠标左键选择变量,右键弹出菜单选择setValue…)。这个功能可以更加快速的检测你的条件语句和循环语句。

          idea插件将jar包反编译的命令

          1
          java -cp "D:\swx\IntelliJ IDEA 2020.1.2\plugins\java-decompiler\lib" org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler -dgs=true yxtl1.jar data D:\yxtl
          • 快捷键
          • IDEA
          • 插件

          展开全文 >>

          开发常用软件

          2021-03-24

          开发中常接触的软件

          编辑工具

          工具 描述
          txt文本文档 备忘录放桌面,随时记灵感思路
          notepad++ 看配置文件
          typora 编辑文档
          xmind 画思维导图,屡关系,发散思维(快捷键高效)
          ProcessOn 免费在线作图工具,画原型图、思维导图、UML图(有网页版方便)
          Sublime Text 代码草稿本。对python友好
          Microsoft To Do 待办事项
          腾讯文档 在线文档,功能不如金山文档多
          金山文档 在线文档,wps旗下,会有崩溃现象

          浏览器

          工具 描述
          谷歌浏览器 chrome,各种插件脚本丰富,大多网页都可以拿谷歌访问
          火狐浏览器 F12开发者模式是中文,易懂一些。可以在本地存储里手动设定key value值
          Microsoft Edge 微软自带的win 10浏览器

          解压工具

          工具 描述
          7-zip 推荐使用,免费,无广告
          winrar 广告多,但也凑合用
          2345 极不推荐,会安装流氓软件

          Java后端

          工具 描述
          jdk java版本,常用为1.8
          NetBeans java开发工具,可以用于Java、C/C++,PHP等语言的开发
          eclipse java开发工具,最早接触,前期常用
          MyEclipse 在Eclipse基础上追加的功能性插件,对插件收费。在WEB开发中提供强大的系统架构平台
          idea java开发工具,目前常用,插件丰富
          XJad 反编译java的class文件为java文件

          Java技术栈

          工具 描述
          spring java框架,核心是IOC和AOP
          spring MVC
          spring boot
          spring cloud
          mybatis
          mybatis plus

          Java常用的第三方工具包

          工具 描述
          lombok 自动生成get/set等方法
          swagger 自动生成api文档
          Hutool 常用的工具类聚合

          分布式框架

          工具 描述
          RocketMQ
          RabbitMQ
          ActiveMQ
          Kafka
          ZeroMQ
          Zookeeper
          ``

          IDEA的一些插件

          工具 描述
          IDE Eval Reset idea重置插件。(需添加第三方插件仓库https://plugins.zhile.io)
          Gitee idea集合的码云
          Free Mybatis plugin mapper接口跳转到xml文件
          Translation 在线翻译的插件
          lombok 可以将lombok的相关代码消除飘红,可以进行代码提示
          Alibaba Java Coding Guidelines 阿里巴巴代码规范

          数据库

          种类 描述
          mysql 5.7
          SQLite
          redis
          Minio

          数据库工具

          工具 描述
          SQLyog 前期使用,已转战
          navicat 设计表好用,会出下长时间不点击的下一次点击特别慢
          DataGrip jetbrains旗下的数据库软件,反应快
          Redis Desktop Manager redis的管理工具(普遍用这个,但是需要收费)
          AnotherRedisDesktopManager 免费,所以比上面那个好用

          搜索引擎

          工具 描述
          ElasticSearch
          ``

          项目管理工具

          工具 描述
          maven 3.6.1,pom.xml文件中放依赖
          gradle 安卓的依赖

          代码管理工具

          工具 描述
          svn
          git

          代码托管平台

          工具 描述
          gitee 码云,国内常用
          git hub 国外全英文,最火最棒;速度慢,经常打不开

          原型图

          工具 描述
          Axure RP 快速创建应用软件或Web网站的线框图、流程图、原型和规格说明文档
          墨刀 移动端原型工具,推荐这个
          Figma 项目团队合作的页面设计,素材库

          前端

          工具 描述
          HBuilder HTML开发的工具。快、绿柔护眼、国产
          webstorm jetbrains旗下的前端开发工具
          node.js
          Vue

          前端样式框架

          工具 描述
          bootstrap
          layui 简约实用,2021.10.13官网下线,但是git上还有
          element ui
          iview
          vant 移动端商城组件

          linux系统

          工具 描述
          VMware
          Ubuntn
          Centos 7版本常见,服务器用的多
          XenServer

          服务器

          工具 描述
          Tomcat
          Ngnix

          windows和linux的交互

          工具 描述
          Xftp windows上将linux服务器内文件可视化界面
          WinSCP windows上访问linux,配合putty可以打开命令行
          MobaXterm_Personal

          部署项目

          工具 描述
          Docker 原生的docker容器
          portainer.io 可视化的docker可视化工具,支持中文
          ``

          抓包工具

          工具 描述
          wireshark 可以将某段时间内的所有数据抓包,过滤自己需要的信息

          测试

          工具 描述
          postman 接口测试工具,英文
          apipost 接口测试工具,中文
          jmeter 接口测试工具,可选择语言,支持并发测试,功能强大
          微信开发者工具 开发微信,可以模拟微信浏览器

          远程连接服务器

          工具 描述
          XShell 命令行访问
          PuTTY 配合winscp使用,打开服务器端的命令行
          FileZilla FTP客户端工具,可以共享文件,本地局域网内

          远程办公

          工具 描述
          VNC
          向日葵 分两个版本,完成远程控制
          ``

          Python

          工具 描述
          Python
          VS code
          pycharm jetbrains旗下的python开发工具
          ``

          爬虫

          工具 描述
          Selenium 自动化测试工具

          总结

          ​ 工欲善其事必先利其器,开发中,工具是必不可少的东西,它能帮助我们减轻很多的工作量,让开发变得更容易。工具没有好坏之分,只有适合自己与不适合。以上就是总结的目前所接触过的工具,随着时代的不断发展,工具层出不穷,有新的就会有淘汰旧的,正如逆水行舟,不进则退,只有不断完善自己,开发新功能,才不至于被淘汰。人也是如此,只有不断学习,不断充实自己,才不至于被社会淘汰。

          • 新环境
          • 软件
          • 软件说明

          展开全文 >>

          java基础知识附录

          2021-03-23

          原码、反码和补码

          在计算机内,有符号数有三种表示法:原码,反码,补码。计算机中,所有数据的运算都是采用补码进行的。

          原码:二进制定点表示法,即最高位为符号位。0表示正,1表示负,其余位表示数值的大小。

          反码:正数的反码与其原码相同。负数的反码是对其原码逐位取反,但符号位除外。

          补码:正数的补码与其原码相同。负数的补码是在其反码的末位+1。

          浮点数

          浮点数:浮点数是属于有理数中某特定子集的数的数字表示,在计算机中用以近似表示任意某个实数。具体的说,这个实数由一个整数或定点数(即尾数)乘以某个基数(计算机中通常是2)的整数次幂得到,这种表示方法类似于基数为10的科学计数法。

          一个浮点数a由两个数m和e来表示:a = m × b^e^。(计算机中b一般为2) 尾数就是小数 二进制的小数点和十进制的小数点是不同的。二进制小数点后是2的负幂,十进制是10的负幂。

          float类型数字在计算机中用4个字节存储。遵循IEEE-754格式标准:一个浮点数有2部分组成:底数m和指数e

          底数部分m: 使用二进制数来表示此浮点数的实际值

          指数部分e: 占用8bit的二进制数,可表示数值范围为0-255

          ​ 但是指数可正可负,所以,IEEE规定,此处算出的次方必须减去127才是真正的指数。所以,float类型的指数可从-126到128

          ​ 底数部分实际是占用24bit的一个值,但是最高位始终为1,所以,最高位省去不存储,在存储中占23bit

          格式:

          SEEE EEEE EMMM MMMM MMMM MMMM MMMM MMMM

          S表示浮点数正负

          E指数加上127后的值得二进制数据,指数位决定了大小范围

          M底数,尾数位决定了计算精度

          举例:

          17.625在内存中的存储

          首先要把17.625换算成二进制:10001.101

          ​ 整数部分,除以2,直到商为0,余数反转。

          ​ 小数部分,乘以2,直到乘位0,进位顺序取。

          在将10001.101右移,直到小数点前只剩1位: 1.0001101 * 2^4 因为右移动了四位

          这个时候,我们的底数和指数就出来了

          底数:因为小数点前必为1,所以IEEE规定只记录小数点后的就好。所以,此处的底数为:0001101

          指数:实际为4,必须加上127(转出的时候,减去127),所以为131。也就是10000011

          符号部分是正数,所以是0

          综上所述,17.625在内存中的存储格式是:

          01000001 10001101 00000000 00000000

          float 符号位1bit 指数位e 8bit 尾数位m 23bit

          double 符号位1bit 指数位e 11bit 尾数位m 52bit

          boolean占几个字节

          虽然boolean表现出非0即1的“位”特性,但是存储空间的基本计量单位是字节,不是位。所以boolean至少占一个字节。
          JVM规范中,boolean变量作为int处理,也就是4字节(声明一个基本变量类型时,占4个字节);boolean数组当做byte数组处理(声明一个数组类型时,数组中的每个元素占1个字节)

          运算符的优先级

          优先级 描述 运算符
          1 括号 ()、[]
          2 正负号 +、-
          3 自增自减,非 ++、–、!
          4 乘除,取余 *、/、%
          5 加减 +、-
          6 移位运算 <<、>>、>>>
          7 大小关系 >、>=、<、<=
          8 相等关系 ==、!=
          9 按位与 &
          10 按位异或 ^
          11 按位或 |
          12 逻辑与 &&
          13 逻辑或 ||
          14 条件运算 ?:
          15 赋值运算 =、+=、-=、*=、/=、%=
          16 位赋值运算 &=、|=、<<=、>>=、>>>=

          如果在程序中,要改变运算顺序,可以使用()括起来

          编码问题

          java语言使用的是Unicode编码。

          一般软件中的ANSI指的是对应当前系统 locale 的遗留(legacy)编码。

          建议用 Notepad++ 等正常的专业文本编辑器保存为不带 BOM 的 UTF-8。另外,如果文本中所有字符都在 ASCII 范围内,那么其实,记事本保存的所谓的「ANSI」文件,和 ASCII 或无 BOM 的 UTF-8 是一样的。

          ASCII码表

          image-20210323162019525

          ASCII码规定最高位必须为0,因此只能有128个,即0~127。

          于是需要Unicode(统一码,万国码,单一码)这个东西,就是要把地球上所有的语言的符号,都用统一的字符集来表示,一个编码真正做到了唯一,在表示Unicode字符时,通常会用“U+”然后紧跟着一组十六进制的数字来表示。

          UTF-8是针对Unicode的一种可变长度字符编码,可以用来表示Unicode标准中的任何字符,而且编码中的第一个字节与ASCII相容。UTF-8使用一至四个字节为每个字符进行编码,汉字一般采用3个字节。

          中文网站不需要其他国家的文字出现,用UTF-8进行编码,大多数占用3个字节甚至更多,会造成存储浪费,所以采用一套GB系列的编码,最常用的是GBK。GBK编码英文采用单字节编码,完全兼容ASCII码表,汉字采用2个字节进行编码,范围为0x8140到0xFEFE(剔除xx7F,因为对应的ASCII码表是DEL,意味着要向后删除一个字符)

          Unicode定义一个特殊字符�,对应编码为0xFFFD。假如A支持一个特殊字符,但B不支持,在B中就会用�代替。这个字符用UTF-8编码后,十六进制表示为0xEF 0XBF 0XBD,如果连续出现两个符号,即为0xEF 0XBF 0XBD 0xEF 0XBF 0XBD,再转换成GBK码(2个字节一个字符)就成了锟(0xEFBF),斤(0xBDEF),拷(0xBFBD)。出现锟斤拷的原因就是UTF-8转码GBK时出现了问题,至少需要两个字符出现乱码才会出现锟斤拷。

          Unicode编码:指的是带有 BOM 的小端序 UTF-16(BOM(byte order mark)也是Unicode标准的一部分,有它特定的适用范围。通常BOM是用来标示Unicode纯文本字节流的,用来提供一种方便的方法让文本处理程序识别读入的.txt文件是哪个Unicode编码(UTF-8,UTF-16BE,UTF-16LE),Linux系统不会识别BOM,直接读取;而windows会先识别,从而会使一些代码加载出问题)

          UTF-8编码:指的是带 BOM 的 UTF-8。(对于微软而言)linux系统的UTF-8是不带BOM的,因为他要运行老版本。而windows系统之前是不带BOM的,但是为了考虑兼容性问题,所以以后的UTF-8编码都是带BOM的。(这里推荐使用不带的)

          java中的参数传递问题

          基本类型:形式参数的改变对实际参数没有影响。(值传递)

          引用类型:形式参数的改变直接影响实际参数。(地址值传递)

          展开全文 >>

          hexo搭建博客

          2021-03-23

          用hexo来搭建一个自己的博客

          Hexo是一款基于Node.js的静态博客框架,依赖少易于安装使用,可以方便的生成静态网页托管在GitHub和Coding上,是搭建博客的首选框架。

          ​ 个人理解:GitHub上自己申请一个公共的仓库,这个仓库的地址对应的是你的博客地址。通过hexo这个工具将你本地的一些md文档上传到你的github仓库里,这样别人访问你的网站时,相当于是访问了你的仓库,在你的仓库里get到了你写的这些md文档并可以阅读。

          hexo的搭建

          1.安装Git

          ​ git属于版本管理工具的一种,是一种分布式的系统,功能确实很强大,在协作开发中会经常性用到,所以很有必要了解。

          windows:到git官网上下载,Download git,按照安装步骤安装到本地。下载后会有一个Git Bash的命令行工具,以后就用这个工具来使用git。

          linux:最早的git就是在linux上编写的,只需要一行代码:

          1
          sudo apt-get install git

          安装好后,用git --version 来查看一下版本
          image-20210319151950597

          2.安装Node.js

          ​ Hexo是基于node.JS编写的,所以需要安装一下node.Js和里面的npm工具。这个属于前端一个常用工具,也有必要了解。

          windows:nodejs选择LTS版本就行了

          linux:

          1
          2
          sudo apt-get install nodejs
          sudo apt-get install npm

          安装完后,打开命令行查看版本

          1
          2
          node -v
          npm -v

          windows在安装完git后,可以使用git bash命令来代替cmd。

          image-20210319152742280

          3.安装Hexo

          ​ 安装完git和node后就可以继续安装hexo。hexo就是我们用来管理博客的工具,之后博客的相关命令都是与hexo相关的。

          先创建一个文件夹blog,然后cd到这个文件夹下(或者在这个文件夹下直接右键git bash打开),输入命令(cli是命令行界面):

          1
          npm install -g hexo-cli

          用hexo -v查看一下版本

          image-20210319153759391

          至此安装完成:git,node,hexo。

          接下来初始化一下hexo

          1
          2
          3
          hexo init myblog#这个myblog可以自己取什么名字都行,然后
          cd myblog #进入这个myblog文件夹
          npm install

          新建完成后,指定文件夹目录下有:

          node_modules: 依赖包
          public:存放生成的页面
          scaffolds:生成文章的一些模板
          source:用来存放你的文章
          themes:主题
          _config.yml: 博客的配置文件

          1
          2
          hexo g
          hexo server

          打开hexo的服务,在浏览器输入localhost:4000就可以看到我们博客的预览图

          按CTRL+C就可以把这个server关闭

          4.GitHub创建个人仓库

          ​ github是一个将来很常用的网站,虽然全是英文,但是不影响会有很多优秀的项目,软件需要从GitHub上下载和借鉴,所以掌握并熟用是必须的。注册一个属于自己的GitHub账号是很必需的。

          登录GitHub账户后点击你的头像下有一个Your repositories,点击后可以看到你的所有仓库,点击new创建一个和你用户名相同的仓库,后面加.github.io(这里必须要和你用户名相同,只有这样,将来要部署到GitHub page的时候,才会被识别,这是一个大坑)也就是xxxx.github.io,其中xxx就是你注册GitHub的用户名。

          5.生成SSH添加到GitHub

          ​ SSH是一种安全协议,我记得好像在https,服务器连接好多地方看到过。有用户名/密码,也有密钥。

          在git bash中:

          1
          2
          git config --global user.name "yourname"    #yourname输入你的GitHub用户名
          git config --global user.email "youremail" #youremail输入你GitHub的邮箱

          这样GitHub才能知道你是不是对应它的账户。可以在检查一下有没有输对:

          1
          2
          3
          4
          5
          git config user.name
          git config user.email
          #创建ssh
          ssh-keygen -t rsa -C "youremail"
          #一路回车,最后提示生成了.ssh的文件夹

          在电脑中找到.ssh的文件夹:

          id_rsa是你这台电脑的私人秘钥,不能给别人看的

          id_rsa.pub是公共秘钥,可以随便给别人看。把这个公钥放在GitHub上,这样当你链接GitHub自己的账户时,它就会根据公钥匹配你的私钥,当能够相互匹配时,才能够顺利的通过git上传你的文件到GitHub上。

          known_hosts是记录你连接过的ip文件

          将公钥部署到你的仓库里:在GitHub的头像下点击setting,找到SSH keys的设置选项,点击New SSH key
          把你的id_rsa.pub里面的信息复制进去

          在git bash中输入:

          1
          ssh -T git@github.com

          6.将hexo部署到GitHub

          ​ 将hexo和github关联起来主要是修改 _config.yml这个文件的相关内容。

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          title: swx                #网站的标题名
          subtitle: 'blog' #网站的副标题
          description: '我的博客' #网站描述。主要用于SEO,告诉搜索引擎一个关于站点的简单描述,通常建议在其中包含网站的关键词。
          keywords:
          author: swx #你的名字。用于主题显示文章的作者
          language: zh-CN #网站使用的语言
          timezone: '' #网站的时区。Hexo 默认使用自己电脑的时区、时区列表。比如说:America/New_York, Japan, 和 UTC 。

          url: https://s-qwer.github.io/ #你的网站域名
          permalink: :year/:month/:day/:title/ #文章的永久链接格式。生成某个文章时的那个链接格式:比如我新建一个文章叫temp.md,那么这个时候他自动生成的地址就是https://s-qwer.github.io/2021/03/18/hexo/
          permalink_defaults: #永久链接中各部分的默认值

          theme: landscape #设置主题在这里,默认的是landscape。当你需要更换主题时,在官网上下载,把主题的文件放在theme文件夹下,再修改这个参数为theme文件夹下的文件名就可以

          deploy: #这个在后面进行双平台部署的时候会再次用到deploy
          type: git
          repo: https://github.com/YourgithubName/YourgithubName.github.io.git #YourgithubName就是你的GitHub账户
          branch: master #分支

          接下来需要先安装deploy-git ,也就是部署的命令,这样你才能用命令部署到GitHub

          1
          2
          3
          4
          5
          6
          npm install hexo-deployer-git --save

          hexo clean #清除了之前生成的东西,也可以不加,相当于把public这个文件夹删除了
          hexo generate #生成静态文章,可以用 hexo g缩写
          hexo deploy #部署文章,可以用hexo d缩写
          #注意deploy时可能要你输入username和password,这是一个很痛苦的过程,每次都需要输入用户名/密码,可以在网上找解决方式。最后我找了一种用git而不是https的方式。repo:git@github.com:s-qwer/s-qwer.github.io.git

          现在就可以在http://yourname.github.io 这个网站看到自己的博客

          7.设置个人域名

          ​ 域名这个东西相当于是一个网址的后缀名。域名是从后往前算的,依次是一级二级。。。常见的一级域名有.com,.cn,.org你可以在域名查询系统里查询这个域名是否可用,像一些比较火的域名,可能被人先注册的,你如果需要使用就需要花钱。也可以在网上找一些免费的域名。这个东西也只是作为一个标识,本质上还是指向了你的GitHub的仓库里的那个项目的文件。

          现在你的个人网站的地址是 yourname.github.io,如果觉得这个网址不太彳亍,就需要你设置个人域名了。

          在你的域名控制台中点解析,添加解析。192.30.252.153 和 192.30.252.154 是GitHub的服务器地址。注意,解析线路选择默认。

          登录GitHub,进入之前创建的仓库,点击settings,设置Custom domain,输入你的域名

          然后在你的博客文件source中创建一个名为CNAME文件,不要后缀。写上你的域名。

          最后,在git bash中,输入

          1
          2
          3
          hexo clean
          hexo g
          hexo d #后两个命令也可以合并为 hexo d -g hexo g -d 效果是一样的

          8.发布文章

          接下来就可以正式开始写文章并发布。

          1
          2
          3
          4
          5
          6
          hexo new newpapername      #这里的newpapername就是你要写的文章的标题名,可以是中文,英文
          #然后在source/_post中就会生成一个md文件 打开这个markdown文件,就可以开始编辑写自己的文章了。当你写完的时候,再
          hexo clean
          hexo g
          hexo d
          #接下来就可以在你设置好的域名访问到你的博客了

          9.Front-matter

          Front-matter 是文件最上方以 --- 分隔的区域,用于指定个别文件的变量:

          参数 描述
          layout 布局
          title 标题
          date 建立日期
          updated 更新日期
          comments 开启文章的评论功能
          tags 标签(不适用于分页)
          categories 分类(不适用于分页)
          permalink 覆盖文章网址

          分类和标签需要区别一下,分类具有顺序性和层次性,也就是说 Foo, Bar 不等于 Bar, Foo;而标签没有顺序和层次。

          1
          2
          3
          4
          5
          categories:
          - Diary
          tags:
          - PS3
          - Games
          layout

          当你每一次使用代码 hexo new paper 它其实默认使用的是post这个布局,也就是在source文件夹下的_post里面。

          Hexo 有三种默认布局:post、page 和 draft,它们分别对应不同的路径。而自定义的其他布局和 post 相同,都将储存到 source/_posts 文件夹。

          布局 路径
          post source/_posts
          page source
          draft source/_drafts

          而new这个命令其实是:hexo new [layout] <title> 只不过这个layout默认是post罢了。

          page
          如果你想另起一页,那么可以使用hexo new page board 系统会自动给你在source文件夹下创建一个board文件夹,以及board文件夹中的index.md,这样你访问的board对应的链接就是http://xxx.xxx/board

          draft
          draft是草稿的意思,也就是你如果想写文章,又不希望被看到,那么可以hexo new draft newpage 这样会在source/_draft中新建一个newpage.md文件,如果你的草稿文件写的过程中,想要预览一下,那么可以使用hexo server --draft 在本地端口中开启服务预览。

          如果你的草稿文件写完了,想要发表到post中,hexo publish draft newpage 就会自动把newpage.md发送到post中。

          10.更换主题

          可以在主题网站上探索你喜欢的主题,然后点击主题的名字,会直接跳转到GitHub上,直接下载下来,然后放到theme文件夹下就行了,(也可以使用git bash命令: git clone 跳转的网址地址,这样会在theme文件夹下自动生成相应的文件夹)然后再在刚才说的配置文件中把theme换成那个主题文件夹的名字,它就会自动在theme文件夹中搜索你配置的主题。

          而后进入你下载好的主题文件夹,可以看到里面也有一个配置文件config.xml,貌似它默认是config.xml.example,把它复制一份,重命名为_config.xml就可以了。这个配置文件是修改你整个主题的配置文件。

          11.遇到的坑

          ​ 严格按照步骤进行的操作,可以直接访问到了自己的博客网址。记录下自己遇到的坑:

          ​ 1.因为没有按照说明输入自己的用户名作为博客的网址,从而一直报错,所以按照教程步骤一步一步来复现还是很有必要的。

          ​ 2.上传的第一篇文章的标题格式没有搞对,于是删除了本地的文件,重新加了title:+文章名字 之后上传就开始一直报错,这期间我不断进行:hexo clean 、hexo d -g的循环操作,而且需要一直输入用户名密码,被折磨了很多次之后,查了相关教程,发现hexo s可以本地预生成,访问localhost:4000可以访问,然后继续进行clean和g -d的循环操作,还是一直出错。最后看了一篇关于简化不用输入那么多次密码的方法教学,把rep里的https改为了git@之后,提交,部署一气呵成。nice!所以有的教程里面推荐rep写https也有的是直接写git格式,原理都是一样的,用的协议不一样,个人感觉git格式更方便一些,同样也是可以连接到仓库的地址。

          repo:git@github.com:s-qwer/s-qwer.github.io.git

          repo: git@github.com:s-qwer/s-qwer.github.io.git

          ​ 3.因为修改时,有些配置没有起作用,这里还有个坑,就是相关的配置:后面必须有一个空格!

          ​ 4. 数据类型[][] 数组名=new 数据类型[][]{{ 元素1,元素2,.... }, { 元素1,元素2,.... }, { 元素1,元素2,.... }}; 这个字段hexo d -g 的时候一直报错:expected name as lookup value, got .

          改为:数据类型[][] 数组名=new 数据类型[][]{{ 元素1,元素2... }, { 元素1,元素2... }, { 元素1,元素2... }}; 仍然报错: expected variable end (应为变量结尾)

          改为:数据类型[] [] 数组名=new 数据类型[] []{{ 元素1,元素2...元素n }, { 元素1,元素2... 元素n}, { 元素1,元素2...元素n } } ;

          放弃了,加了个引用就不报错了。

          ​ 5.卸载了node,重新安装npm之后,hexo命令找不到。重新安装了一下hexo,可以正常提交了。

          ​ 6.提交报错:

          image-20210601181054370

          错误说明:YAMLException: unidentified alias “title*:”

          解决方法:将*去掉不再报错

          ​ 7.提交报错:

          image-20210601181222745

          错误说明:Error [Nunjucks Error]: _posts/8.6.VUE.md [Line 520, Column 159] expected name as lookup value, got .

          原因猜测:是正文里面出现了多次{% %}这样的语句,网上解释说是Nunjucks引擎会把它解释为其它意思

          解决方法:在大括号前加上\转义字符即可。我的解决方法是将内容变为代码块。

          8.提交显示INFO Deploy done: git,但是访问网页没有更新的内容

          hexo的完善

          图片显示问题

          ​ 将文章hexo d -g之后去网站上查看,发现图片都无法显示。

          在md文件中插入图片的语法为![]()。

          其中方括号是图片描述,圆括号是图片路径。

          一般来说有三种图片路径,分别是相对路径,绝对路径和网络路径。

          网络路径就是直接引用网上的图片,直接复制图片地址,放在圆括号中就完事了。

          这种方式十分的方便,但是也存在一定的问题:

          • 图片失效导致无法加载;
          • 打开网页后要再请求加载图片;
          • 原网站限制,如微信公众号的图片会变得不可见等。

          这种方式算是有利有弊。

          绝对路径是图片在计算机中的绝对位置,相对路径是相对于当前文件的路径。

          由于我们的博客是要部署在网站上,部署后会生成新的文件目录,所以我们选择使用相对路径的方式。

          在hexo中使用文章资源文件夹需要在config.yaml文件中更改一下配置:

          1
          post_asset_folder: true

          当该配置被应用后,使用hexo new命令创建新文章时,会生成相同名字的文件夹,也就是文章资源文件夹。

          由于项目会生成新的文件目录,同时会解析Markdown中的图片路径,会导致一个问题。
          如在一个文件目录下,博客名为1.md,相应的存在一个1文件夹存放图片image.jpg。
          在Typora编辑器中,普通的md文件使用![](1/image.jpg)能在编辑器中正常显示图片。
          在hexo中,按理说应该是使用![](image.jpg),但网页中却无法正常显示。
          此时应该使用这样的方式来引入图片:{% asset_img image.jpg 这是一张图片 %}

          虽然可以正常引用图片了,但是这种引用图片的方式只有一句话能形容,wtf。

          图片插件

          插件hexo-renderer-marked解决了这个问题。可以只用npm install hexo-renderer-marked命令直接安装,之后在config.yaml中更改配置如下:

          1
          2
          3
          4
          post_asset_folder: true
          marked:
          prependRoot: true
          postAsset: true

          之后就可以在使用![](image.jpg)的方式愉快的插入图片了。

          我们做了这么多都是为了方便,那么为什么不再方便一点呢。

          hexo与Typora的完美结合

          上述是从文章资源文件夹中引用图片,前提是先将图片放入到文章资源文件夹,如果图片数量众多的话,一张一张的放很影响效率。但是不用怕,我们有很方便的解决方法。

          Typora是我非常喜欢的Markdown文本编辑器,在之前的文章中也介绍过一点。

          Typora对于插入图片的支持做得非常好,在文件->偏好设置或者直接<C-,>进入设置。

          img

          使用该配置后,可以直接复制网页中的图片地址,粘贴到Typora中后,会直接复制该图片到文章资源文件夹,同时自动更改路径。

          如复制网络路径的图片https://...../image.jpg粘贴到Typora中叫文章名的文章后,图片会自动变为![](文章名/image.jpg)。

          但我们知道部署后,文件路径是不同的,所以当我们插入完所有的图片后,我们还需要删除每个图片路径中的文件名/。不慌,也很简单。

          在Typora编辑器中,使用<C-f>快捷键,将所有的文章名/替换为空即可删除。

          img

          然后再将博客上传,图片就会随着文章一起打包。在网页中就可以看到正常显示的图片,大功告成

          多台电脑使用

          使用场景:希望公司、家里电脑都能正常使用,电脑升级更新环境

          • 使用网盘或其他方式同步hexo源文件; — git很容易搞定
          • 创建两个repo分别管理站点和源文件;— 没有必要,可以利用git分支

          这里使用git分支来管理站点、源文件的方式

          Hexo生产的静态博客的特点,首先它是一个静态博客生成工具,可以根据你的配置和md文件生成一系列的html、css、js等文件,组成一个站点,部署到github pages,这样网站就可以访问了。

          完成hexo的部署命令,其实hexo帮助我们做的事情就是:

          1. 生成站点有关文件到 .deploy_git
          2. 把它初始化为git目录,并根据你的配置指定remote和branch(一般是master)
          3. 进行git commit,并把修改push到指定的remote branch
          4. 命令执行完成后,到github仓库,发现master分支上的内容其实就和’.deploy_git’中一样

          按照教程建站完成部署后,你的本地源文件其实都没有同步到github,所以只需要想办法把源文件同步即可。最简单的方式就是在你的xx.github.io repo中创建一个source分支,管理源文件,对源文件的修改注意commit/push即可

          步骤如下:

          1. 给源文件目录初始化git,并增加remote

            1
            2
            git init
            git remote add origin https://github.com/xx/xx.github.io // 填入你的repo地址
          2. 创建分支source,commit/push

            1
            2
            3
            4
            git checkout -b source
            git add .
            git commit -m 'add source'
            git push origin source
          3. ok,可以在github上把source设置为主分支。其他电脑上只需clone你的.io repo,切换到source分支操作即可。

          4. 初次clone需要切换到source分支,并执行npm install,初始化hexo有关的依赖。

          因为服务器上的项目无法创建分支,所以出现了错误,暂时不能跑通。

          换电脑/重装系统

          ​ 如果准备换电脑或者重装系统,那么我们的md文件和相关配置怎么保存或者重新设置呢。

          ​ 以上,就是搭建的全过程了,借鉴了很多文章,很多内容都是复制粘贴下来的,为了给自己以后再次搭建时一个步骤说明。这些只是一些基础的功能,保证了自己写的文章可以被保存在服务器上。hexo还有很多功能我目前没有用到,包括一些互动功能,git分支,国内外分流,SEO等。以后用到了再去查查相关资料完善吧。

          • git
          • hexo
          • node.js

          展开全文 >>

          &laquo; Prev1…9101112Next &raquo;
          © 2021 swx
          Hexo Theme Yilia by Litten
          • 所有文章
          • 关于我

          tag:

          • 杂谈
          • 经历
          • 与人沟通
          • 项目合作
          • 搜索技巧
          • 百度
          • 谷歌
          • 书
          • 收藏
          • 打包
          • swagger
          • springfox
          • mybatis
          • Portainer.io
          • Typora
          • 快捷键
          • 压缩算法
          • PBOOTCMS
          • Eclipse
          • 新环境
          • 软件
          • 软件说明
          • WebRTC
          • 类型转换
          • JVM
          • 接口
          • 抽象类
          • 内部类
          • if
          • 逻辑判断
          • for循环
          • Tomcat
          • http
          • vite
          • WEB
          • servlet
          • JSP
          • JPA
          • json
          • 数据库
          • SQL
          • 小技巧
          • 小科普
          • git
          • Gitee
          • hexo
          • node.js
          • ElasticSearch
          • 基础概念
          • 面向对象
          • mysql
          • 索引
          • 锁
          • 事务
          • Navicat
          • JQuery
          • mqtt
          • 代理
          • 工具类代码
          • 轮子
          • 消息中间件
          • 设计规范
          • 编程规范
          • RESTful
          • HATEOAS
          • java概述
          • 历史版本
          • 安装
          • MySQL基础概念
          • HTML
          • 日志
          • PO-BO-VO,DTO-DAO与POJO
          • spring
          • maven
          • C
          • IDEA
          • 插件
          • 微信
          • Redis
          • ngnix
          • MyBatis-Plus
          • npm
          • vue
          • SpringBoot

            缺失模块。
            1、请确保node版本大于6.2
            2、在博客根目录(注意不是yilia根目录)执行以下命令:
            npm i hexo-generator-json-content --save

            3、在根目录_config.yml里添加配置:

              jsonContent:
                meta: false
                pages: false
                posts:
                  title: true
                  date: true
                  path: true
                  text: false
                  raw: false
                  content: false
                  slug: false
                  updated: false
                  comments: false
                  link: false
                  permalink: false
                  excerpt: false
                  categories: false
                  tags: true
            

          一起加油吧!