<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Wang-nine的小站</title><description>技术笔记与学习记录</description><link>https://wang-nine.github.io/</link><language>zh_CN</language><item><title>01 前向预测与 MSE：从 batch 输入到标量损失</title><link>https://wang-nine.github.io/posts/llm-math-numpy-01/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/llm-math-numpy-01/</guid><description>用一个 batch 的矩阵乘法理解线性预测，并把逐样本误差压成标量 MSE；附 shape 链、手算示例与 broadcasting 易错点。</description><pubDate>Mon, 26 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;复习提纲，对应 M2.1 形状与 M2.5 broadcasting 基础；不是掌握记录，也不是练习答案。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;1. 本课位置&lt;/h2&gt;
&lt;p&gt;把一批样本的线性预测写成矩阵乘法，再把预测与目标的差压成一个标量 loss。只算数值与 shape，不推导梯度。&lt;/p&gt;
&lt;h2&gt;2. 术语与三个身份&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;feature&lt;/strong&gt;：一列输入，共 &lt;code&gt;D&lt;/code&gt; 个。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;sample&lt;/strong&gt;：一行输入，一个样本。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;batch&lt;/strong&gt;：一次算的 &lt;code&gt;N&lt;/code&gt; 个样本，矩阵 &lt;code&gt;X&lt;/code&gt; 形状 &lt;code&gt;(N, D)&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;角色&lt;/th&gt;
&lt;th&gt;是否随样本变化&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;X (N, D)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;输入 input&lt;/td&gt;
&lt;td&gt;是&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;w (D, 1)&lt;/code&gt;、&lt;code&gt;b ()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;模型参数 parameters&lt;/td&gt;
&lt;td&gt;否，全样本共用&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;y (N, 1)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;目标 target&lt;/td&gt;
&lt;td&gt;是，只在 loss 里出现&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;b&lt;/code&gt; 是显式 bias，不占 &lt;code&gt;X&lt;/code&gt; 的列，只给所有样本同一个偏移。&lt;/p&gt;
&lt;h2&gt;3. 形状链&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;X (N, D) @ w (D, 1) -&amp;gt; z (N, 1)
z + b               -&amp;gt; p (N, 1)      b 广播到每行
p - y               -&amp;gt; residual (N, 1)
residual ** 2       -&amp;gt; squared (N, 1)
mean(squared)       -&amp;gt; MSE ()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;p = X @ w + b&lt;/code&gt;、&lt;code&gt;residual = p - y&lt;/code&gt;、&lt;code&gt;MSE = mean(residual ** 2)&lt;/code&gt;。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;形状&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;X&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(N, D)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;batch 输入&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;w&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(D, 1)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;每个 feature 一个权重&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;标量 bias，广播到每行&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;p&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(N, 1)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;每个样本一个预测&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;y&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(N, 1)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;目标，须与 &lt;code&gt;p&lt;/code&gt; 对齐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;residual&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;(N, 1)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;p - y&lt;/code&gt;，逐样本误差&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MSE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;所有平方误差的平均，标量&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;(N, D) @ (D, 1) -&amp;gt; (N, 1)&lt;/code&gt; 不是维度抵消：&lt;code&gt;D&lt;/code&gt; 是每个输出内部的求和长度，&lt;code&gt;p_i = sum_j X[i, j] * w[j, 0] + b&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;4. 一个 batch 的手算&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;X = [[2, 1], [4, 2]]    w = [[2], [3]]    b = 5    y = [[13], [17]]
p = [[12], [19]]         # 2*2 + 1*3 + 5, 4*2 + 2*3 + 5
residual = p - y = [[-1], [2]]
MSE = mean([1, 4]) = 2.5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;y&lt;/code&gt; 必须写成 &lt;code&gt;(2, 1)&lt;/code&gt;；写成 &lt;code&gt;[13, 17]&lt;/code&gt; 就是 &lt;code&gt;(2,)&lt;/code&gt;，减法会进另一套 broadcasting。residual 为正表示预测高于目标，为负表示低于目标，MSE 会把符号丢掉。&lt;/p&gt;
&lt;h2&gt;5. 易错点&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;MSE 的单位与平均抵消。&lt;/strong&gt; &lt;code&gt;MSE = (1 / M) * sum residual_i ** 2&lt;/code&gt;，&lt;code&gt;M&lt;/code&gt; 是参与平均的元素个数（当前 &lt;code&gt;p (N, 1)&lt;/code&gt; 时 &lt;code&gt;M = N&lt;/code&gt;）。若 &lt;code&gt;p&lt;/code&gt;、&lt;code&gt;y&lt;/code&gt; 以元计，residual 是元，&lt;code&gt;residual ** 2&lt;/code&gt; 与 MSE 是元²，不能说&quot;MSE 是 2.5 元&quot;。&lt;code&gt;residual = [+3, -3]&lt;/code&gt; 时 &lt;code&gt;mean(residual) = 0&lt;/code&gt;，但 &lt;code&gt;MSE = (9 + 9) / 2 = 9&lt;/code&gt;：正负抵消不代表预测准，比较模型看 MSE。MSE 为 0 当且仅当所有 residual 都为 0。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;size 与 shape[0]。&lt;/strong&gt; &lt;code&gt;X.size = N * D&lt;/code&gt; 是元素总数，&lt;code&gt;shape[0] = N&lt;/code&gt; 只是第一轴长度，第一轴是否表示样本取决于任务约定。&lt;code&gt;p (N, 1)&lt;/code&gt; 的 &lt;code&gt;size = N&lt;/code&gt; 只因输出宽度为 1；若 &lt;code&gt;p (N, K)&lt;/code&gt;，全元素平均的分母是 &lt;code&gt;N * K&lt;/code&gt;，不是 &lt;code&gt;N&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;三种 size 1。&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;np.array([5])    -&amp;gt; (1,)    ndim 1
np.array([[5]])  -&amp;gt; (1, 1)  ndim 2
np.array(5)      -&amp;gt; ()      ndim 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;元素个数相同，但轴的划分与能参与的运算不同。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;broadcasting。&lt;/strong&gt; 从最右轴对齐，逐轴长度相等或其中一个为 1，缺的左侧轴当作 1。最大的坑是 &lt;code&gt;p (N, 1) - y (N,) -&amp;gt; (N, N)&lt;/code&gt;：&lt;code&gt;y&lt;/code&gt; 被当作 &lt;code&gt;(1, N)&lt;/code&gt;，每个预测都去减每个目标。&lt;code&gt;N = 1&lt;/code&gt; 时结果可能仍是 &lt;code&gt;(1, 1)&lt;/code&gt;，错误被藏起来，所以单样本测试不能替代多样本检查。shape 相等也不等于样本对齐，还要核对样本顺序与含义。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;reshape 与 guard。&lt;/strong&gt; &lt;code&gt;reshape&lt;/code&gt; 只改轴划分、元素总数不变；broadcasting 只扩展运算视角、不改输入 shape。任务要求先确认 &lt;code&gt;p.shape == y.shape&lt;/code&gt; 再相减：打印一行警告不是 guard，residual 与 MSE 必须放在合法分支里，形状不匹配时整个 loss 都不执行。&lt;/p&gt;
&lt;h2&gt;6. 速查与入口&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;X (N, D)&lt;/code&gt;、&lt;code&gt;w (D, 1)&lt;/code&gt;、&lt;code&gt;b ()&lt;/code&gt;、&lt;code&gt;p&lt;/code&gt; 与 &lt;code&gt;y (N, 1)&lt;/code&gt;、MSE &lt;code&gt;()&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;p&lt;/code&gt; 与 &lt;code&gt;y&lt;/code&gt; 表示同样的 &lt;code&gt;N&lt;/code&gt; 个样本，顺序一一对应。&lt;/li&gt;
&lt;li&gt;不是只打印警告：mismatch 时下游 loss 完全不执行，不留下半成品结果。&lt;/li&gt;
&lt;li&gt;MSE 是有限的标量；&lt;code&gt;np.mean(residual ** 2)&lt;/code&gt; 的分母是元素总数。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;N = 1&lt;/code&gt; 通过不代表多样本形状正确。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;来源：&lt;a href=&quot;https://numpy.org/doc/stable/user/absolute_beginners.html&quot;&gt;NumPy: the absolute basics for beginners&lt;/a&gt;、&lt;a href=&quot;https://numpy.org/doc/stable/reference/generated/numpy.mean.html&quot;&gt;numpy.mean&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;本文于2026-10-26整理公开，日期为导入日期。&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>02 bias 与损失：局部变化率与导数入门</title><link>https://wang-nine.github.io/posts/llm-math-numpy-02/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/llm-math-numpy-02/</guid><description>固定 X、w、y，只改变量 bias，观察 MSE 如何变化；用差商认识导数，并区分局部方向与全局最小。</description><pubDate>Mon, 26 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;复习提纲，对应 M2.4 局部变化率入门，以及 M2.1/M2.5 的预测与 MSE 应用；不是掌握记录，也不是练习答案。正式 chain rule 尚未讲，本课不等同 M3 通过。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;1. 本课位置&lt;/h2&gt;
&lt;p&gt;固定 &lt;code&gt;X&lt;/code&gt;、&lt;code&gt;w&lt;/code&gt;、&lt;code&gt;y&lt;/code&gt;，只动一个标量参数 &lt;code&gt;b&lt;/code&gt;，观察 MSE 怎样随它变化：先扫点看趋势，再用差商逼近斜率，最后用导数判断该往哪边挪一小步。&lt;/p&gt;
&lt;h2&gt;2. 术语&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;bias&lt;/strong&gt; &lt;code&gt;b&lt;/code&gt;：本课固定数据中单独加减的标量参数，形状 &lt;code&gt;()&lt;/code&gt;，给所有样本加同一个偏移。这里说的是本课的显式 bias，不是对所有模型中 bias 是否独立于 feature 的普遍断言。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;shared shift&lt;/strong&gt;：改 &lt;code&gt;b&lt;/code&gt; 时每个预测一起平移，不是只改某一行。&lt;/li&gt;
&lt;li&gt;控制变量：&lt;code&gt;X&lt;/code&gt;、&lt;code&gt;w&lt;/code&gt;、&lt;code&gt;y&lt;/code&gt; 全部固定，只让 &lt;code&gt;b&lt;/code&gt; 变。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;p = X @ w + b        # (N, 1)
r = p - y            # residual
b -&amp;gt; b + h   =&amp;gt;  每个 r_i 都增加 h
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以 residual 整体平移，MSE 是它们平方的平均。&lt;/p&gt;
&lt;h2&gt;3. 扫点：共享指标与单样本&lt;/h2&gt;
&lt;p&gt;用本课固定数据扫 &lt;code&gt;b&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;b          4    5    6    7    8
L = MSE    6    3    2    3    6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;测过的候选 &lt;code&gt;4..8&lt;/code&gt; 中 &lt;code&gt;b = 6&lt;/code&gt; 的 MSE 最低，两侧对称上升；全局最小见第 5 节。&lt;code&gt;b = 5&lt;/code&gt; 时 residual 是 &lt;code&gt;[1, -2, -2]&lt;/code&gt;，&lt;code&gt;b = 6&lt;/code&gt; 时是 &lt;code&gt;[2, -1, -1]&lt;/code&gt;：MSE 从 3 降到 2，但第一个样本的 &lt;code&gt;|r|&lt;/code&gt; 从 1 升到 2。共享指标变好，不代表每个样本都变好。&lt;/p&gt;
&lt;h2&gt;4. 差商与导数&lt;/h2&gt;
&lt;p&gt;对这份固定数据，MSE 恰好是下面这个二次函数：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;L(b) = 2 + (b - 6)^2
L&apos;(b) = 2(b - 6)
[L(b + h) - L(b)] / h = 2(b - 6) + h,   h != 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;h -&amp;gt; 0&lt;/code&gt; 时差商趋于导数。不能直接代 &lt;code&gt;h = 0&lt;/code&gt;，那是除以 0；差商只是有限 &lt;code&gt;h&lt;/code&gt; 上可算的近似。局部有 &lt;code&gt;ΔL ≈ L&apos;(b) Δb&lt;/code&gt;，只在小步长附近成立，不是全局恒等式。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;b     h=0.1    h=0.01    h=0.001    差商极限
5     -1.9     -1.99     -1.999     -2
6      0.1      0.01      0.001      0
7      2.1      2.01      2.001      2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以上展开由教师给出，不是学生证明。符号规则：&lt;code&gt;L&apos;(b) &amp;gt; 0&lt;/code&gt; 时 &lt;code&gt;b&lt;/code&gt; 略减可降 &lt;code&gt;L&lt;/code&gt;，&lt;code&gt;L&apos;(b) &amp;lt; 0&lt;/code&gt; 时 &lt;code&gt;b&lt;/code&gt; 略增可降 &lt;code&gt;L&lt;/code&gt;。前提是足够局部且可导，步长不能任意大；本课不教正式 gradient descent 算法。&lt;/p&gt;
&lt;h2&gt;5. 导数为零不等于最小&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;b = 6&lt;/code&gt; 左侧取 &lt;code&gt;Δb = -0.1&lt;/code&gt; 得 &lt;code&gt;ΔL = +0.01&lt;/code&gt;、差商 &lt;code&gt;-0.1&lt;/code&gt;；右侧取 &lt;code&gt;Δb = +0.1&lt;/code&gt; 也得 &lt;code&gt;ΔL = +0.01&lt;/code&gt;、差商 &lt;code&gt;+0.1&lt;/code&gt;。两边 &lt;code&gt;L&lt;/code&gt; 都升高，不是两边变化率都为正；导数 0 时 &lt;code&gt;L&lt;/code&gt; 仍可按 &lt;code&gt;h^2&lt;/code&gt; 变化。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;L&apos;(6) = 0&lt;/code&gt; 只说明该点切线水平，不保证最小值。教师给过的反例是 &lt;code&gt;f(b) = -b^2&lt;/code&gt; 在 &lt;code&gt;b = 0&lt;/code&gt; 取最大值。对这份固定 &lt;code&gt;X&lt;/code&gt;、&lt;code&gt;w&lt;/code&gt;、&lt;code&gt;y&lt;/code&gt;，直接配方即可判定：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;L(6 + h) = 2 + h^2 &amp;gt;= 2 = L(6)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以 &lt;code&gt;b = 6&lt;/code&gt; 是全局最小。但 &lt;code&gt;L = 2&lt;/code&gt; 不是 0：residual 为 &lt;code&gt;[2, -1, -1]&lt;/code&gt;。完美预测当且仅当所有 &lt;code&gt;r_i = 0&lt;/code&gt;，不是导数等于 0。扫点只说明测过的候选里谁最好，不证明所有实数上都最好；有限 &lt;code&gt;h&lt;/code&gt; 的差商是近似，不等于精确导数。&lt;/p&gt;
&lt;h2&gt;6. 实现易错点&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;h = 0&lt;/code&gt; 那行只作基线，不能拿去除以 &lt;code&gt;offset&lt;/code&gt;；基线必须配对同一个 &lt;code&gt;b&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;res[(bias - 4) * 4]&lt;/code&gt; 这种索引依赖起始值、循环顺序和每组 4 个 offset，条件一改就会取错基线。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;7. 速查&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;b&lt;/code&gt; 是共享偏移，增加 &lt;code&gt;h&lt;/code&gt; 就给每个 residual 加 &lt;code&gt;h&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;差商要求 &lt;code&gt;h != 0&lt;/code&gt;，导数取 &lt;code&gt;h -&amp;gt; 0&lt;/code&gt;；&lt;code&gt;L&apos;(b)&lt;/code&gt; 的符号只指示局部挪动方向。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;L&apos;(b) = 0&lt;/code&gt; 不保证最小，也不保证预测完美；判定最小值要看函数值比较。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;来源：数组与广播基础沿用 &lt;a href=&quot;https://numpy.org/doc/stable/user/absolute_beginners.html&quot;&gt;NumPy: the absolute basics for beginners&lt;/a&gt;；本课的变化率、导数与二次函数推导为教师补充，未新增外部微积分来源。&lt;/p&gt;
&lt;p&gt;上一课：&lt;a href=&quot;/posts/llm-math-numpy-01/&quot;&gt;01 前向预测与 MSE&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;本文于2026-10-26整理公开，日期为导入日期。&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>03 驻点与局部极值：导数为零说明什么</title><link>https://wang-nine.github.io/posts/llm-math-numpy-03/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/llm-math-numpy-03/</guid><description>从一元可导函数出发，说明 f&apos;(a)=0 只代表驻点；用三个函数与反例区分局部极值、全局最小和 MSE 为零。</description><pubDate>Mon, 26 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;复习提纲，对应 M2.4 的驻点与局部极值；不是掌握记录，也不是练习答案。正式 chain rule 与 gradient descent 尚未讲。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;1. 本课位置&lt;/h2&gt;
&lt;p&gt;只在一元函数、可导点的范围内讨论：导数为零的点和局部极值是什么关系。结论不能直接套到多变量，也不处理不可导点。&lt;/p&gt;
&lt;h2&gt;2. 术语与定义&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;stationary point（驻点）&lt;/strong&gt;：定义域内点 &lt;code&gt;a&lt;/code&gt;，且 &lt;code&gt;f&apos;(a) = 0&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;local minimum（局部最小）&lt;/strong&gt;：若存在 &lt;code&gt;δ &amp;gt; 0&lt;/code&gt;，使定义域内所有满足 &lt;code&gt;|x - a| &amp;lt; δ&lt;/code&gt; 的 &lt;code&gt;x&lt;/code&gt; 都有 &lt;code&gt;f(x) &amp;gt;= f(a)&lt;/code&gt;，则 &lt;code&gt;a&lt;/code&gt; 是 local minimum。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;local maximum（局部最大）&lt;/strong&gt;：同样条件下 &lt;code&gt;f(x) &amp;lt;= f(a)&lt;/code&gt;。strict 版本要求 &lt;code&gt;0 &amp;lt; |x - a| &amp;lt; δ&lt;/code&gt;，即排除 &lt;code&gt;x = a&lt;/code&gt; 自身；只把 &lt;code&gt;&amp;gt;=&lt;/code&gt; 换成 &lt;code&gt;&amp;gt;&lt;/code&gt; 且仍包含中心点，会在 &lt;code&gt;x = a&lt;/code&gt; 处出现 &lt;code&gt;f(a) &amp;gt; f(a)&lt;/code&gt; 的矛盾。本课不强制 strict。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;f&apos;(a) = 0&lt;/code&gt; 的含义是局部变化率为 0、切线水平。它不表示函数在 &lt;code&gt;a&lt;/code&gt; 附近是常数，也不直接说明 &lt;code&gt;a&lt;/code&gt; 是最小还是最大。&lt;/p&gt;
&lt;h2&gt;3. 三个函数在 0 的差商&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;function     f(h)     [f(h)-f(0)]/h, h!=0   极限 h-&amp;gt;0   0 处身份
A(t)=t^2     h^2      h                      0           local min
B(t)=-t^2    -h^2     -h                     0           local max
C(t)=t^3     h^3      h^2                    0           都不是
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;三者导数在 0 都为 0，身份却不同，所以 &lt;code&gt;f&apos;(0) = 0&lt;/code&gt; 不能单独判定极值。反过来，可导 + 定义域内点 + 局部极值 &lt;code&gt;=&amp;gt; f&apos;(a) = 0&lt;/code&gt; 成立；逆命题不成立。&lt;/p&gt;
&lt;p&gt;不可导的尖角与定义域端点不在本课结论范围内：它们可能取到极值，但不能用 &lt;code&gt;f&apos; = 0&lt;/code&gt; 来分析。&lt;/p&gt;
&lt;h2&gt;4. 平均变化率&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;Δf/Δt = (f(t0 + Δt) - f(t0)) / Δt,   Δt != 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;分子分母都是变化量，都按“新值减旧值”计算，不是 &lt;code&gt;f/Δt&lt;/code&gt;。以 &lt;code&gt;C(t) = t^3&lt;/code&gt;、&lt;code&gt;t0 = 0&lt;/code&gt; 向左走 &lt;code&gt;Δt = -0.1&lt;/code&gt; 为例：&lt;code&gt;Δf = -0.001&lt;/code&gt;，差商 &lt;code&gt;(-0.001)/(-0.1) = +0.01&lt;/code&gt;。两个负数相除得正，所以只看 &lt;code&gt;Δf &amp;lt; 0&lt;/code&gt; 不能断定差商为负，还要看 &lt;code&gt;Δt&lt;/code&gt; 的符号。&lt;/p&gt;
&lt;h2&gt;5. 导数为零不等于全局最小：一个反例&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;F(t) = (t^2 - 1)^2 &amp;gt;= 0&lt;/code&gt;，且 &lt;code&gt;F(0) = 1&lt;/code&gt;、&lt;code&gt;F(±0.1) = 0.9801&lt;/code&gt;。存在更小的函数值，所以 0 不是全局最小。教师补充的判定：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;F(h) - F(0) = h^2 (h^2 - 2) &amp;lt; 0,   0 &amp;lt; |h| &amp;lt; 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;邻域内 &lt;code&gt;F(h) &amp;lt; F(0)&lt;/code&gt;，故 0 是 strict local maximum，不是 minimum。函数值非负、导数又为 0，仍可能是局部最大。&lt;/p&gt;
&lt;p&gt;就本例而言，&lt;code&gt;F(0)&lt;/code&gt; 与 &lt;code&gt;F(±0.1)&lt;/code&gt; 的有限采样足以反驳“0 是全局最小”，但不能只靠这两个点证明 0 是局部最大；局部结论需要邻域内任意接近的 &lt;code&gt;h&lt;/code&gt; 都满足的不等式，本课已由上面的因式分解给出。&lt;/p&gt;
&lt;h2&gt;6. 导数为零与 MSE 为零&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;有限实数、精确算术、非空 batch 下，&lt;code&gt;MSE = 0&lt;/code&gt; 当且仅当所有 residual 都为 0；而驻点只保证 &lt;code&gt;dMSE/db = 0&lt;/code&gt;，不保证预测完美，它可能对应非零的极小值。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;7. 速查&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;驻点 &lt;code&gt;f&apos;(a) = 0&lt;/code&gt;：局部变化率为 0，切线水平，不等于附近常数。&lt;/li&gt;
&lt;li&gt;可导内点取局部极值 &lt;code&gt;=&amp;gt; f&apos;(a) = 0&lt;/code&gt;；反过来不成立。&lt;/li&gt;
&lt;li&gt;差商是 &lt;code&gt;Δf/Δt&lt;/code&gt;，分子分母都按“新减旧”计算。&lt;/li&gt;
&lt;li&gt;有限点比较能反驳某个全局断言，不足以证明局部结论；局部结论要靠邻域不等式。&lt;/li&gt;
&lt;li&gt;导数 0 与 MSE 0 是两件事。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;8. 自测&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;[f(0.2) - f(0)] / 0.2&lt;/code&gt; 与 &lt;code&gt;f(0.2) / 0.2&lt;/code&gt; 差在哪？&lt;/li&gt;
&lt;li&gt;可导点 &lt;code&gt;f&apos;(a) = 0&lt;/code&gt; 能否推出 &lt;code&gt;a&lt;/code&gt; 是局部最小？举一个反例。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;F(t) = (t^2 - 1)^2&lt;/code&gt; 在 &lt;code&gt;t = 0&lt;/code&gt; 为什么不是全局最小？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;来源：本课数学与反例为教师补充，未新增外部微积分来源。&lt;/p&gt;
&lt;p&gt;上一课：&lt;a href=&quot;/posts/llm-math-numpy-02/&quot;&gt;02 bias 与损失&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;本文于2026-10-26整理公开，日期为导入日期。&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>SpringCloud09-MQ</title><link>https://wang-nine.github.io/posts/10609/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10609/</guid><description>简单的介绍了SpringCloud中的MQ。</description><pubDate>Sun, 24 Dec 2023 09:00:00 GMT</pubDate><content:encoded>&lt;p&gt;简单的介绍了SpringCloud中的MQ。&lt;/p&gt;
&lt;h1&gt;初识MQ&lt;/h1&gt;
&lt;h2&gt;同步调用&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/springcloud-tools/mq-sync-async.svg&quot; alt=&quot;同步调用等待处理结果，与通过消息队列异步消费的对比&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：同步调用的调用方需要等待本次请求的处理结果，才能继续依赖该结果的流程。若多个调用串行发生，端到端延迟会累积；一处变慢或失败还可能沿依赖链传播，因此需要明确超时、错误处理与隔离边界，不能仅凭同步方式就推断系统一定发生级联故障。&lt;/p&gt;
&lt;h3&gt;优点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;时效性较强，可以立即得到结果&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;缺点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;耦合度高&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;每次加入新的需求，都要修改原来的代码&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;性能下降&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;调用者需要等待服务提供者响应，如果调用链过长则晌应时间等于每次调用的时间之和&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;资源浪费&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;调用链中的每个服务在等待响应过程中，不能释故请求占用的资源，高并发场录下会极度浪费系统资源&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;级联失败&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;如果服务提供者出现问题,所有调用方都会跟着出问题,如同多米诺骨牌一样，迅速导致整个微服务群故障&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;异步调用&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/springcloud-tools/mq-sync-async.svg&quot; alt=&quot;生产者经 Broker 发布消息、消费者独立处理的异步关系&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：异步调用把消息提交与后续业务处理分开，消费者按自己的处理能力消费队列中的消息。生产者收到发布确认，并不等于消费者已经完成业务；故障隔离和流量削峰也依赖 Broker 容量、消息持久化、失败重试及消费者幂等。需要最终结果时，可另行设计结果事件或状态查询。&lt;/p&gt;
&lt;h3&gt;优点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;耦合度低&lt;/li&gt;
&lt;li&gt;吞吐量提升&lt;/li&gt;
&lt;li&gt;故障隔离&lt;/li&gt;
&lt;li&gt;流量削峰&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;缺点&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;依赖于Broker的可靠性、安全性、吞吐能力&lt;/li&gt;
&lt;li&gt;架构复杂了，业务没有明显的流程线，不好追踪管理&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;MQ&lt;/h2&gt;
&lt;p&gt;MQ（MessageQueue），中文是消息队列，字面来看就是存放消息的队列。也就是事件驱动架构中的Broker。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;RabbitMQ&lt;/th&gt;
&lt;th&gt;ActiveMQ&lt;/th&gt;
&lt;th&gt;RocketMQ&lt;/th&gt;
&lt;th&gt;Kafka&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;公司/社区&lt;/td&gt;
&lt;td&gt;Rabbit&lt;/td&gt;
&lt;td&gt;Apache&lt;/td&gt;
&lt;td&gt;阿里&lt;/td&gt;
&lt;td&gt;Apache&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;开发语言&lt;/td&gt;
&lt;td&gt;Erlang&lt;/td&gt;
&lt;td&gt;Java&lt;/td&gt;
&lt;td&gt;Java&lt;/td&gt;
&lt;td&gt;Scala&amp;amp;Java&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;协议支持&lt;/td&gt;
&lt;td&gt;AMQP,XMPP,SMTP,STOMP&lt;/td&gt;
&lt;td&gt;Openwire,STOMP,REST,XMPP,AMQP&lt;/td&gt;
&lt;td&gt;自定义协议&lt;/td&gt;
&lt;td&gt;自定义协议&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;可用性&lt;/td&gt;
&lt;td&gt;高&lt;/td&gt;
&lt;td&gt;一般&lt;/td&gt;
&lt;td&gt;高&lt;/td&gt;
&lt;td&gt;高&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;单机吞吐量&lt;/td&gt;
&lt;td&gt;一般&lt;/td&gt;
&lt;td&gt;差&lt;/td&gt;
&lt;td&gt;高&lt;/td&gt;
&lt;td&gt;非常高&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;消息延迟&lt;/td&gt;
&lt;td&gt;微秒级&lt;/td&gt;
&lt;td&gt;毫秒级&lt;/td&gt;
&lt;td&gt;毫秒级&lt;/td&gt;
&lt;td&gt;毫秒以内&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;消息可靠性&lt;/td&gt;
&lt;td&gt;高&lt;/td&gt;
&lt;td&gt;一般&lt;/td&gt;
&lt;td&gt;高&lt;/td&gt;
&lt;td&gt;一般&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;RabbitMQ快速入门&lt;/h1&gt;
&lt;h2&gt;RabbitMQ概述和安装&lt;/h2&gt;
&lt;h2&gt;常见消息模型&lt;/h2&gt;
&lt;h2&gt;快速入门&lt;/h2&gt;
&lt;h1&gt;SpringAMQP&lt;/h1&gt;
</content:encoded></item><item><title>SpringCloud08-Docker</title><link>https://wang-nine.github.io/posts/10608/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10608/</guid><description>简单的介绍了SpringCloud中的Docker。</description><pubDate>Wed, 20 Dec 2023 06:30:00 GMT</pubDate><content:encoded>&lt;p&gt;简单的介绍了SpringCloud中的Docker。&lt;/p&gt;
&lt;h1&gt;初识Docker&lt;/h1&gt;
&lt;h2&gt;Docker如何解决大型项目依赖关系复杂，不同组件依赖的兼容性问题?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Docker允许开发中将应用、依赖、函数库、配置一起&lt;strong&gt;打包&lt;/strong&gt;，形成可移植镜像&lt;/li&gt;
&lt;li&gt;Docker应用运行在容器中，使用沙箱机制，相互&lt;strong&gt;隔离&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Docker如何解决开发、测试、生产环境有差异的问题&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Docker镜像中包含完整运行环境，包括系统函数库，仅依赖系统的Linux内公核，因此可以在任意Linux操作系统上运行&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Docker与虚拟机&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/springcloud-tools/docker-vm.svg&quot; alt=&quot;Linux 容器共享宿主内核，虚拟机各有客户机系统与内核&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：Linux 容器隔离应用进程与资源，但共享其运行宿主的 Linux 内核；虚拟机则在虚拟化层上运行各自的客户机系统。这里比较的是隔离层次，不是性能测试：启动耗时、占用空间和性能仍取决于镜像、负载与平台，Docker Desktop 运行 Linux 容器时通常还包含 Linux 虚拟机这一层。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;特性&lt;/th&gt;
&lt;th&gt;Docker&lt;/th&gt;
&lt;th&gt;虚拟机&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;性能&lt;/td&gt;
&lt;td&gt;接近原生&lt;/td&gt;
&lt;td&gt;性能较差&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;硬盘占用&lt;/td&gt;
&lt;td&gt;一般为 MB&lt;/td&gt;
&lt;td&gt;一般为 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;启动&lt;/td&gt;
&lt;td&gt;秒级&lt;/td&gt;
&lt;td&gt;分钟级&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;镜像与容器&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;镜像（lmage）&lt;/strong&gt;：Docker将应用程序及其所需的依赖、函数库、环境、配置等文件打包在一起，称为镜像。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;容器（Container）&lt;/strong&gt;：镜像中的应用程序运行后形成的进程就是容器，只是Docker会给容器做隔离，对外不可见。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Docker和DockerHub&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DockerHub&lt;/strong&gt;：DockerHub是一个Docker镜像的托管平台。这样的平台称为Docker Registry。&lt;/li&gt;
&lt;li&gt;国内也有类似于DockerHub的公开服务，比如网易云镜像服务、阿里云镜像库等。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Docker架构&lt;/h2&gt;
&lt;p&gt;Docker是一个CS架构的程序，由两部分组成&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;服务端（server）&lt;/strong&gt;：Docker守护进程，负责处理Docker指令，管理镜像、容器等&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;客户端（client）&lt;/strong&gt;：通过命令或RestAPI向Docker服务端发送指令。可以在本地或远程向服务端发送指令。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/springcloud-tools/docker-architecture.svg&quot; alt=&quot;Docker CLI 经 API 连接守护进程，由守护进程管理镜像容器并连接仓库&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：客户端把命令交给 Docker daemon，实际的镜像拉取、容器创建和资源管理发生在守护进程所在主机。Registry 保存可分发的镜像，&lt;code&gt;pull&lt;/code&gt; 与 &lt;code&gt;push&lt;/code&gt; 分别拉取与推送镜像内容；镜像到达本机后，还需要创建并启动容器才能运行应用。&lt;/p&gt;
&lt;h1&gt;Docker的基本操作&lt;/h1&gt;
&lt;h2&gt;Docker,DockerCompose,Docker镜像仓库的安装&lt;/h2&gt;
&lt;h3&gt;0.安装Docker&lt;/h3&gt;
&lt;p&gt;Docker 分为 CE 和 EE 两大版本。CE 即社区版（免费，支持周期 7 个月），EE 即企业版，强调安全，付费使用，支持周期 24 个月。&lt;/p&gt;
&lt;p&gt;Docker CE 分为 &lt;code&gt;stable&lt;/code&gt; &lt;code&gt;test&lt;/code&gt; 和 &lt;code&gt;nightly&lt;/code&gt; 三个更新频道。&lt;/p&gt;
&lt;p&gt;官方网站上有各种环境下的 &lt;a href=&quot;https://docs.docker.com/install/&quot;&gt;安装指南&lt;/a&gt;，这里主要介绍 Docker CE 在 CentOS上的安装。&lt;/p&gt;
&lt;h4&gt;1.CentOS安装Docker&lt;/h4&gt;
&lt;p&gt;Docker CE 支持 64 位版本 CentOS 7，并且要求内核版本不低于 3.10， CentOS 7 满足最低内核的要求，所以我们在CentOS 7安装Docker。&lt;/p&gt;
&lt;h4&gt;1.1.卸载（可选）&lt;/h4&gt;
&lt;p&gt;如果之前安装过旧版本的Docker，可以使用下面命令卸载：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-selinux \
                  docker-engine-selinux \
                  docker-engine \
                  docker-ce
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;1.2.安装docker&lt;/h4&gt;
&lt;p&gt;首先需要大家虚拟机联网，安装yum工具&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yum install -y yum-utils \
           device-mapper-persistent-data \
           lvm2 --skip-broken
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后更新本地镜像源：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 设置docker镜像源
yum-config-manager \
    --add-repo \
    https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
    
sed -i &apos;s/download.docker.com/mirrors.aliyun.com\/docker-ce/g&apos; /etc/yum.repos.d/docker-ce.repo

yum makecache fast
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后输入命令：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yum install -y docker-ce
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;docker-ce为社区免费版本。稍等片刻，docker即可安装成功。&lt;/p&gt;
&lt;h4&gt;1.3.启动docker&lt;/h4&gt;
&lt;p&gt;Docker应用需要用到各种端口，逐一去修改防火墙设置。非常麻烦，因此建议大家直接关闭防火墙！&lt;/p&gt;
&lt;p&gt;启动docker前，一定要关闭防火墙后！！&lt;/p&gt;
&lt;p&gt;启动docker前，一定要关闭防火墙后！！&lt;/p&gt;
&lt;p&gt;启动docker前，一定要关闭防火墙后！！&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 关闭
systemctl stop firewalld
# 禁止开机启动防火墙
systemctl disable firewalld
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过命令启动docker：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl start docker  # 启动docker服务

systemctl stop docker  # 停止docker服务

systemctl restart docker  # 重启docker服务
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后输入命令，可以查看docker版本：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker -v
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;1.4.配置镜像加速&lt;/h4&gt;
&lt;p&gt;docker官方镜像仓库网速较差，我们需要设置国内镜像服务：&lt;/p&gt;
&lt;p&gt;参考阿里云的镜像加速文档：&lt;a href=&quot;https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors&quot;&gt;https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;2.CentOS7安装DockerCompose&lt;/h3&gt;
&lt;h4&gt;2.1.下载&lt;/h4&gt;
&lt;p&gt;Linux下需要通过命令下载：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 安装
curl -L https://github.com/docker/compose/releases/download/1.23.1/docker-compose-`uname -s`-`uname -m` &amp;gt; /usr/local/bin/docker-compose
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2.2.修改文件权限&lt;/h4&gt;
&lt;p&gt;修改文件权限：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 修改权限
chmod +x /usr/local/bin/docker-compose
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;2.3.Base自动补全命令：&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;# 补全命令
curl -L https://raw.githubusercontent.com/docker/compose/1.29.1/contrib/completion/bash/docker-compose &amp;gt; /etc/bash_completion.d/docker-compose
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果这里出现错误，需要修改自己的hosts文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;echo &quot;199.232.68.133 raw.githubusercontent.com&quot; &amp;gt;&amp;gt; /etc/hosts
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.Docker镜像仓库&lt;/h3&gt;
&lt;p&gt;搭建镜像仓库可以基于Docker官方提供的DockerRegistry来实现。&lt;/p&gt;
&lt;p&gt;官网地址：&lt;a href=&quot;https://hub.docker.com//registry&quot;&gt;https://hub.docker.com//registry&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;3.1.简化版镜像仓库&lt;/h4&gt;
&lt;p&gt;Docker官方的Docker Registry是一个基础版本的Docker镜像仓库，具备仓库管理的完整功能，但是没有图形化界面。&lt;/p&gt;
&lt;p&gt;搭建方式比较简单，命令如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker run -d \
    --restart=always \
    --name registry	\
    -p 5000:5000 \
    -v registry-data:/var/lib/registry \
    registry
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;命令中挂载了一个数据卷registry-data到容器内的/var/lib/registry 目录，这是私有镜像库存放数据的目录。&lt;/p&gt;
&lt;p&gt;访问&lt;a href=&quot;http://YourIp:5000/v2/_catalog&quot;&gt;http://YourIp:5000/v2/_catalog&lt;/a&gt; 可以查看当前私有镜像服务中包含的镜像&lt;/p&gt;
&lt;h4&gt;3.2.带有图形化界面版本&lt;/h4&gt;
&lt;p&gt;使用DockerCompose部署带有图象界面的DockerRegistry，命令如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;version: &apos;3.0&apos;
services:
  registry:
    image: registry
    volumes:
      - ./registry-data:/var/lib/registry
  ui:
    image: joxit/docker-registry-ui:static
    ports:
      - 8080:80
    environment:
      - REGISTRY_TITLE=传智教育私有仓库
      - REGISTRY_URL=http://registry:5000
    depends_on:
      - registry
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3.3.配置Docker信任地址&lt;/h4&gt;
&lt;p&gt;我们的私服采用的是http协议，默认不被Docker信任，所以需要做一个配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 打开要修改的文件
vi /etc/docker/daemon.json
# 添加内容：
&quot;insecure-registries&quot;:[&quot;http://192.168.150.101:8080&quot;]
# 重加载
systemctl daemon-reload
# 重启docker
systemctl restart docker
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;镜像相关命令&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;镜像名称一般分两部分组成：[repository]:[tag]。&lt;/li&gt;
&lt;li&gt;在没有指定tag时，默认是latest，代表最新版本的镜像&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;镜像操作按目的区分：&lt;code&gt;docker pull&lt;/code&gt; 从仓库拉取镜像，&lt;code&gt;docker images&lt;/code&gt; 查看本地镜像，&lt;code&gt;docker image inspect&lt;/code&gt; 查看元数据，&lt;code&gt;docker tag&lt;/code&gt; 为已有镜像增加引用，&lt;code&gt;docker push&lt;/code&gt; 推送到指定仓库，&lt;code&gt;docker rmi&lt;/code&gt; 删除本地镜像引用。归档迁移使用下面的 &lt;code&gt;save&lt;/code&gt; 与 &lt;code&gt;load&lt;/code&gt;，它们不是创建或启动容器的命令。省略标签通常使用 &lt;code&gt;latest&lt;/code&gt;，但这个标签只是约定名称，不保证始终对应时间上最新或兼容的版本。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker --help
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;主要还是查看帮助文档进行&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 例子
docker save -o nginx.tar nginx:latest
docker load -i nginx.tar
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;容器相关命令&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/springcloud-tools/docker-lifecycle.svg&quot; alt=&quot;Docker 镜像创建容器、停止重启及删除的生命周期&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：&lt;code&gt;run&lt;/code&gt; 基于镜像创建并启动新容器，&lt;code&gt;stop&lt;/code&gt; 停止已有容器，&lt;code&gt;start&lt;/code&gt; 重新启动同一个容器，&lt;code&gt;rm&lt;/code&gt; 删除容器。镜像、容器可写层与外部数据卷是不同对象；再次执行 &lt;code&gt;run&lt;/code&gt; 不等于恢复原容器，删除容器也不等于删除镜像或命名数据卷。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;常用指令：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;以 Nginx 为例，可使用 &lt;code&gt;docker run --name web -p 8080:80 -d nginx:latest&lt;/code&gt; 创建一个后台容器：&lt;code&gt;web&lt;/code&gt; 是容器名称，&lt;code&gt;8080:80&lt;/code&gt; 把宿主机端口映射到容器内端口，&lt;code&gt;-d&lt;/code&gt; 表示后台运行。运行前确认宿主端口未被占用；随后可用 &lt;code&gt;docker ps&lt;/code&gt; 查看状态，用 &lt;code&gt;docker logs web&lt;/code&gt; 排查启动问题。需要保留同一个容器时使用 &lt;code&gt;stop&lt;/code&gt; 与 &lt;code&gt;start&lt;/code&gt;，不要重复创建同名容器。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;docker run命令的常见参数：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;—name：指定容器名称&lt;/li&gt;
&lt;li&gt;-p：指定端口映射&lt;/li&gt;
&lt;li&gt;-d：让容器后台运行&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;查看容器日志的命令：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;docker logs&lt;/li&gt;
&lt;li&gt;添加-f参数可以持续查看日志&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;查看容器状态：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;docker ps&lt;/li&gt;
&lt;li&gt;添加-a参数查看所有状态的容器&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;删除容器：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;docker rm&lt;/li&gt;
&lt;li&gt;不能删除运行中的容器，除非添加-f参数&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;进入容器：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;命令是docker exec -it[容器名][要执行的命令]&lt;/li&gt;
&lt;li&gt;exec命令可以进入容器修改文件，但是在容器内修改文件是不推荐的&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;数据卷相关命令&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;容器与数据耦合的问题&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不便于修改
&lt;ul&gt;
&lt;li&gt;当我们要修改Nginx的html内容时，需要进入容器内部修改，很不方便。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;数据不可复用
&lt;ul&gt;
&lt;li&gt;在容器内的修改对外是不可见的。所有修改对新创建的容器是不可复用的。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;升级维护困难
&lt;ul&gt;
&lt;li&gt;数据在容器内，如果要升级容器必然删除旧容器，所有数据都跟着删除了。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/springcloud-tools/docker-storage.svg&quot; alt=&quot;命名数据卷和宿主目录在容器可写层之外提供存储&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：把网页、数据库文件等需要保留的数据挂载到容器外部，使容器升级与数据生命周期分离。命名卷由 Docker 管理，绑定挂载指向指定宿主路径；删除容器通常不删除这些外部数据，但主动删除卷或宿主文件仍会造成数据丢失，挂载不能替代备份。&lt;/p&gt;
&lt;p&gt;两种挂载方式可通过来源区分：&lt;code&gt;-v html:/usr/share/nginx/html&lt;/code&gt; 使用名为 &lt;code&gt;html&lt;/code&gt; 的数据卷，&lt;code&gt;-v /srv/site:/usr/share/nginx/html&lt;/code&gt; 则直接绑定宿主目录。前者由 Docker 管理存储位置，后者依赖宿主目录结构与权限，更适合直接编辑网站文件。冒号右侧始终是容器路径；只需读取的内容可增加 &lt;code&gt;:ro&lt;/code&gt;。挂载会遮蔽目标目录原有内容，部署前应确认来源数据和访问权限。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;数据卷操作常见命令：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;docker volume create 创建一个volume&lt;/li&gt;
&lt;li&gt;docker volume ls 列出所有的volume&lt;/li&gt;
&lt;li&gt;docker volume inspect 显示一个或多个volume的信息&lt;/li&gt;
&lt;li&gt;docker volume rm 删除一个或多个指定的volume&lt;/li&gt;
&lt;li&gt;docker volume prune 删除未使用的volume&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;数据卷挂载方式：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;-v volumeName: /targetContainerPath&lt;/li&gt;
&lt;li&gt;如果容器运行时volume不存在，会自动被创建出来&lt;/li&gt;
&lt;li&gt;```shell&lt;br /&gt;
docker run —name mn -p 80:80 -v htm1:/usr/share/nginx/html -d nginx&lt;pre&gt;&lt;code&gt;
**docker run的命令中通过-v参数挂载文件或目录到容器中：**

+ -v volume名称:容器内目录
+ -宿主机文件:容器内文件
+ -v宿主机目录:容器内目录

**数据卷挂载与目录直接挂载的**

+ 数据卷挂载耦合度低，由docker来管理目录。但是目录较深，不好找

# Dockerfile自定义镜像

**镜像是将应用程序及其需要的系统函数库、环境、配置、依赖打包而成。**

![image-20231224140745581](https://wang-nine.cn//images/SpringCloud08-06%E9%95%9C%E5%83%8F%E7%9A%84%E7%BB%93%E6%9E%84.png)

**镜像是分层结构，每一层称为一个Lager**

+ Baselmage层:包含基本的系统函数库、环境变量、文件系统
+ Entrypoint: 入口，是镜像中应用启动的命令
+ 其它:在Baselmage基础上添加依赖、安装程序、完成整个应用的安装和配置

**Dockerfile就是一个文本文件，其中包含一个个的指令(Instruction)，用指令来说明要执行什么操作来构建镜像。每一个指令都会形成一层Layer。**

|    指令    |                     说明                     |            示例             |
| :--------: | :------------------------------------------: | :-------------------------: |
|    FROM    |                 指定基础镜像                 |        FROM centos:6        |
|    ENV     |        设置环境变量，可在后面指令使用        |        ENV key value        |
|    COPY    |         拷贝本地文件到镜像的指定目录         |  COPY ./mysql-5.7.rpm /tmp  |
|    RUN     |  执行Linux的shell命令，一般是安装过程的命令  |     RUN yum install gcc     |
|   EXPOSE   | 指定容器运行时监听的端口，是给镜像使用者看的 |         EXPOSE 8080         |
| ENTRYPOINT |     镜像中应用的启动命令，容器运行时调用     | ENTRYPOINT java -jar xx.jar |

+ Dockerfile的本质是一个文件，通过指令描述镜像的构建过程
+ Dockerfile的第一行必须是FROM，从一个基础镜像来构建
+ 基础镜像可以是基本操作系统，如Ubuntu。也可以是其他人制作好的镜像，例如: java:8-alpine

# Docker-Compose

+ Docker Compose可以基于Compose文件帮我们快速的部署分布式应用，而无需手动一个个创建和运行容器!
+ Compose文件是一个文本文件，通过指令定义集群中的每个容器如何运行。
+ 指令写法

```shell
# MySQL容器
docker run \
--name mysql \
-e MYSQL_ROOT_PASSwORD=123 \
-p 3306:3306 \
-v /tmp/mysql/conf/hmy.cnf:/etc/mysql/conf.d/hmy.cnf \
-v /tmp/mysql/data:/var/lib/mysql \
-d \
mysql:5.7.25

# 构建镜像
docker build -t web:1.0 . # 注意，这个&quot;.&quot; 表示是在当前目录下运行
# 运行容器
docker run --name web -p 8090:8090 -d web: 1.0
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;compose写法&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;version: &quot;3.8&quot;

services:
  mysql:
	image: mysql:5.7.25
	environment:
	  MYSQL_ROOT_PASSWORD:123
	volumes:
	  - /tmp/mysql/data:/var/lib/mysql
	  - /tmp/mysql/conf/hmy.cnf:/etc/mysql/conf.d/hmy.cnf
  web:
	build: .
	ports:
	  - 8090: 8090
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Docker镜像服务&lt;/h1&gt;
&lt;p&gt;镜像仓库（Docker Registry ）有公共的和私有的两种形式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;公共仓库：例如Docker官方的Docker Hub，国内也有一些云服务商提供类似于Docker Hub的公开服务，比如网易云镜像服务、DaoCloud镜像服务、阿里云镜像服务等。&lt;/li&gt;
&lt;li&gt;除了使用公开仓库外，用户还可以在本地搭建私有Docker Registry。企业自己的镜像最好是采用私有DockerRegistry来实现。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;私有 Registry 的工作流程是：先为本地镜像设置包含仓库地址的名称，再按仓库要求完成认证并推送；其他主机使用同一地址拉取镜像，再创建容器。前文的 &lt;code&gt;registry-data:/var/lib/registry&lt;/code&gt; 用来持久化仓库内容，图形管理页面只是访问仓库的入口，不是镜像本身的存储位置。生产部署应配置 TLS、访问控制与备份，不应把学习环境中的 HTTP 信任配置直接当作生产方案。&lt;/p&gt;
</content:encoded></item><item><title>SpringCloud07-Gateway</title><link>https://wang-nine.github.io/posts/10607/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10607/</guid><description>简单的介绍了SpringCloud中的Gateway。</description><pubDate>Mon, 18 Dec 2023 05:00:00 GMT</pubDate><content:encoded>&lt;p&gt;简单的介绍了SpringCloud中的Gateway。&lt;/p&gt;
&lt;h1&gt;为什么需要网关？&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/springcloud-tools/gateway-routing.svg&quot; alt=&quot;客户端经网关完成入口校验、路由匹配和服务转发&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：网关集中承接外部流量，把认证、权限校验、限流与路由等入口职责组织起来。本文的 &lt;code&gt;/user/**&lt;/code&gt; 匹配用户服务，&lt;code&gt;lb://userservice&lt;/code&gt; 表示通过服务发现与负载均衡选择实例；这些策略需要相应配置或实现，不会仅因引入网关依赖便全部自动生效。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;网关的技术实现&lt;/strong&gt;&lt;br /&gt;
在SpringCloud中网关的实现包括两种：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;gateway&lt;/li&gt;
&lt;li&gt;Zuul
&lt;ul&gt;
&lt;li&gt;Zuul是基于Servlet的实现，属于阻塞式编程。而SpringCloudGateway则是基于Spring5中提供的WebFlux，属于响应式编程的实现，具备更好的性能。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;网关的作用&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对用户请求做身份认证、权限校验&lt;/li&gt;
&lt;li&gt;将用户请求路由到微服务，并实现负载均衡&lt;/li&gt;
&lt;li&gt;对用户请求做限流&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;搭建网关的步骤&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;创建新的module，引入SpringCloudGateway的依赖和nacos的服务发现依赖:&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!--nacos服务注册发现依赖--&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;com.alibaba.cloud&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-cloud-starter-alibaba-nacos-discovery&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;!--网关gateway依赖--&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.springframework.cloud&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-cloud-starter-gateway&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;编写路由配置及nacos地址&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;server:
  port: 10010 # 网关端口
spring:
  application:
	name: gateway # 服务名称
  cloud:
	nacos:
	  server-addr: localhost:8848 # nacos地址
	gateway:
	  routes: # 网关路由配置
		- id: user-service # 路由id，自定义，只要唯一即可
		  # uri: http://127.0.0.1:8081 #路由的目标地址http就是固定地址
		  uri: lb://userservice # 路由的目标地址lb就是负载均衡，后面跟服务名称
		  predicates: # 路由断言，也就是判断请求是否符合路由规则的条件
			- Path=/user/** # 这个是按照路径匹配，只要以/user/开头就符合要求
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;路由配置&lt;/h2&gt;
&lt;p&gt;网关路由可以配置的内容包括&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;路由id：路由唯一标示&lt;/li&gt;
&lt;li&gt;uri：路由目的地，支持lb和http两种&lt;/li&gt;
&lt;li&gt;predicates：路由断言，判断请求是否符合要求，符合则转发到路由目的地&lt;/li&gt;
&lt;li&gt;filters：路由过滤器，处理请求或响应&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;路由断言工厂Route Predicate Factory&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;th&gt;示例&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;After&lt;/td&gt;
&lt;td&gt;是某个时间点后的请求&lt;/td&gt;
&lt;td&gt;- After=2037-01-20T17:42:47.789-07:00[America/Denver]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Before&lt;/td&gt;
&lt;td&gt;是某个时间点之前的请求&lt;/td&gt;
&lt;td&gt;- Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Between&lt;/td&gt;
&lt;td&gt;是某两个时间点之前的请求&lt;/td&gt;
&lt;td&gt;- Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cookie&lt;/td&gt;
&lt;td&gt;请求必须包含某些cookie&lt;/td&gt;
&lt;td&gt;- Cookie=chocolate, ch.p&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Header&lt;/td&gt;
&lt;td&gt;请求必须包含某些header&lt;/td&gt;
&lt;td&gt;- Header=x-Request-ld,\d+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Host&lt;/td&gt;
&lt;td&gt;请求必须是访问某个host（域名)&lt;/td&gt;
&lt;td&gt;- Host=*&lt;em&gt;.somehost.org,*&lt;/em&gt;.anotherhost.org&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Method&lt;/td&gt;
&lt;td&gt;请求方式必须是指定方式&lt;/td&gt;
&lt;td&gt;- Method=GET,POST&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Path&lt;/td&gt;
&lt;td&gt;请求路径必须符合指定规则&lt;/td&gt;
&lt;td&gt;- Path=/red/{segment},/blue/**&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query&lt;/td&gt;
&lt;td&gt;请求参数必须包含指定参数&lt;/td&gt;
&lt;td&gt;- Query=name, Jack或者-Query=name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RemoteAddr&lt;/td&gt;
&lt;td&gt;请求者的ip必须是指定范围&lt;/td&gt;
&lt;td&gt;- RemoteAddr=192.168.1.1/24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;weight&lt;/td&gt;
&lt;td&gt;权重处理&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;路由过滤器GatewayFilter&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;GatewayFilter是网关中提供的一种过滤器，可以对进入网关的请求和微服务返回的响应做处理，共计三十一中，具体配置官网查看&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;路由过滤器配置在某条路由的 &lt;code&gt;filters&lt;/code&gt; 下，只处理匹配该路由的请求。理解时分清三个步骤：&lt;code&gt;predicates&lt;/code&gt; 判断请求是否匹配，&lt;code&gt;uri&lt;/code&gt; 指定转发目标，&lt;code&gt;filters&lt;/code&gt; 在转发前后处理请求或响应。例如统一添加请求头、调整路径或补充响应头，属于过滤器职责而不是新的业务接口。配置语法与可用工厂以当前 Gateway 版本为准，不把过滤器数量当作固定常量。&lt;/p&gt;
&lt;p&gt;当多条路由需要相同的过滤规则时，可以把公共规则放到 &lt;code&gt;spring.cloud.gateway.default-filters&lt;/code&gt;，避免在每条路由中重复声明。它表示默认应用的路由过滤器配置，并不是替请求选择目标的“默认路由器”。单条路由仍可声明自己的 &lt;code&gt;filters&lt;/code&gt;；需要自定义通用逻辑时，再考虑下面介绍的 &lt;code&gt;GlobalFilter&lt;/code&gt;，并检查公共规则与局部规则是否产生重复处理。&lt;/p&gt;
&lt;h2&gt;全局过滤器&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;全局过滤器的作用也是处理一切进入网关的请求和微服务响应，与GatewayFilter的作用一样。区别在于GatewayFilter通过配置定义，处理逻辑是固定的。而GlobalFilter的逻辑需要自己写代码实现。定义方式是实现GlobalFilter接口。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;举例如下：&lt;/p&gt;
&lt;p&gt;需求：&lt;/p&gt;
&lt;p&gt;定义全局过滤器，拦截请求，判断请求的参数是否满足下面条件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;参数中是否有authorization&lt;/li&gt;
&lt;li&gt;authorization参数值是否为admin&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果同时满足则放行，否则拦截&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//@Order(-1) //过滤器顺序，越小越优先
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {
    @Override
    public Mono&amp;lt;Void&amp;gt; filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1.获取请求参数
        ServerHttpRequest request = exchange.getRequest();
        MultiValueMap&amp;lt;String, String&amp;gt; params = request.getQueryParams();
        // 2.获取参数中的 authorization 参数
        String auth = params.getFirst(&quot;authorization&quot;);
        // 3.判断参数值是否等于 admin
        if (&quot;admin&quot;.equals(auth)) {
            // 4.是，放行
            return chain.filter(exchange);
        }
        // 5.否，拦截
        // 5.1.设置状态码
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        // 5.2.拦截请求
        return exchange.getResponse().setComplete();
    }
    
    @Override
    public int getOrder() {
        return -1;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;过滤器链执行顺序&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/springcloud-tools/gateway-filter-chain.svg&quot; alt=&quot;Gateway 前置处理按 order 升序进入、后置处理沿嵌套链反向返回&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：正常放行时，前置处理按 &lt;code&gt;order&lt;/code&gt; 从小到大进入过滤器链，响应的后置处理沿已建立的嵌套顺序返回。直接结束响应会短路后续处理；同一 &lt;code&gt;order&lt;/code&gt; 下的类型顺序需结合版本和实际注册方式核对，不能仅依赖类型名称判断。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;过滤器执行顺序&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每一个过滤器都必须指定一个int类型的order值,order值越小，优先级越高，执行顺序越靠前。&lt;/li&gt;
&lt;li&gt;GlobalFilter通过实现Ordered接口，或者添加@Order注解来指定order值，由我们自己指定&lt;/li&gt;
&lt;li&gt;路由过滤器和defaultFilter的order由Spring指定，默认是按照声明顺序从1递增。&lt;/li&gt;
&lt;li&gt;当过滤器的order值一样时，会按照 defaultFilter &amp;gt; 路由过滤器 &amp;gt; GlobalFilter的顺序执行。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;跨域问题处理&lt;/h2&gt;
&lt;p&gt;跨域:域名不一致就是跨域，主要包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;域名不同：www.taobao.com 和 www.taobao.org 和 www.jd.com 和 miaosha.jd.com&lt;/li&gt;
&lt;li&gt;域名相同，端口不同：localhost:8080和localhost8081&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;跨域问题：浏览器禁止请求的发起者与服务端发生跨域ajax请求，请求被浏览器拦截的问题&lt;/p&gt;
&lt;p&gt;解决方案：CORS&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;spring:
  cloud:
    gateway:
	  #。。。
	  globalcors: # 全局的跨域处理
		add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
		corsConfigurations:
		  &apos;[/**]&apos;:
			allowed0rigins: # 允许哪些网站的跨域请求
			  - &quot;http://localhost:8090&quot;
			  - &quot;http://www.leyou.com&quot;
			allowedMethods: # 允许的跨域ajax的请求方式
			  - &quot;GET&quot;
			  - &quot;POST&quot;
			  - &quot;DELETE&quot;
			  - &quot;PUT&quot;
			  - &quot;OPTIONS&quot;
			allowedHeaders: &quot;*” # 允许在请求中携带的头信息
			allowCredentials: true # 是否允许携带
			cookiemaxAge: 360000 # 这次跨域检测的有效期
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>SpringCloud06-Feign</title><link>https://wang-nine.github.io/posts/10606/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10606/</guid><description>简单的介绍了SpringCloud中的Feign。</description><pubDate>Sat, 16 Dec 2023 09:00:00 GMT</pubDate><content:encoded>&lt;p&gt;简单的介绍了SpringCloud中的Feign。&lt;/p&gt;
&lt;h1&gt;http客户端Feign&lt;/h1&gt;
&lt;h2&gt;restTemplate方式调用存在的问题&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;String url = &quot;http://userservice/user/&quot; + order.getUserId();
User user = restTemplate.getForObject(url, User.class);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;存在以下的问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;代码可读性差，编程体验不统一&lt;/li&gt;
&lt;li&gt;参数复杂URL难以维护&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Feign的使用方法&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;引入依赖&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!--feign客户端依赖--&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.springframework.cloud&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-cloud-starter-openfeign&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;在order-service的启动类添加注解开启Feign的功能&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;@MapperScan(&quot;cn.itcast.order.mapper&quot;)
@SpringBootApplication
@EnableFeignClients(clients = UserClient.class,defaultConfiguration = DefaultFeignConfiguration.class)
public class OrderApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;编写Feign客户端&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;@FeignClient(&quot;userservice&quot;)
public interface UserClient {
    @GetMapping(&quot;/user/{id}&quot;)
    User findById(@Pathvariable(&quot;id&quot;) Long id);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;主要是基于SpringMVC的注解来声明远程调用的信息，比如：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;服务名称：userservice&lt;/li&gt;
&lt;li&gt;请求方式：GET&lt;/li&gt;
&lt;li&gt;请求路径：/user/{id}&lt;/li&gt;
&lt;li&gt;请求参数：Long id&lt;/li&gt;
&lt;li&gt;返回值类型：User&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;自定义Feign的参数配置&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;feign.Logger.Level&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;修改日志级别&lt;/td&gt;
&lt;td&gt;包含四种不同的级别：NONE、BASIC、HEADERS、FULL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;feign.codec.Decoder&lt;/td&gt;
&lt;td&gt;响应结果的解析器&lt;/td&gt;
&lt;td&gt;http远程调用的结果做解析，例如解析json字符串为java对象&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;feign.codec.Encoder&lt;/td&gt;
&lt;td&gt;请求参数编码&lt;/td&gt;
&lt;td&gt;将请求参数编码，便于通过http请求发送&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;feign.Contract&lt;/td&gt;
&lt;td&gt;支持的注解格式&lt;/td&gt;
&lt;td&gt;默认是SpringMVC的注解&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;feign.Retryer&lt;/td&gt;
&lt;td&gt;失败重试机制&lt;/td&gt;
&lt;td&gt;请求失败的重试机制，默认是没有，不过会使用Ribbon的重试&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;一般我们需要配置的就是日志级别，有两种配置方法&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;方式一：配置文件方式&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;全局生效&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;feign:
	client:
		config:
			default: #这里用default就是全局配置，如果是写服务名称，则是针对某个微服务的配置
				loggerLevel: FULL #日志级别
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;局部生效&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;feign:
	client:
		config:
			userservice: #这里用default就是全局配置，如果是写服务名称，则是针对某个微服务的配置
				loggerLevel: FULL #日志级别
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;方式二：java代码方式&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;需要先声明一个Bean&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class FeignclientConfiguration {
	@Bean
	public Logger.Level feignLogLevel() {
		return Logger.Level.BASIC;
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;如果是全局配置,则把它放到@EnableFeignclients这个注解中&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;@EnableFeignclients(defaultConfiguration = FeignclientConfiguration.class)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;如果是局部配置，则把它放到@Feignclient这个注解中&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;@FeignClient(value = &quot;userservice&quot;, configuration = FeignClientConfiguration.class)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;总结&lt;/h3&gt;
&lt;p&gt;Feign的日志配置：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;方式一是配置文件，feign.client.config.xxx.loggerLevel&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;如果xxx是default则代表全局&lt;/li&gt;
&lt;li&gt;如果xxx是服务名称，例如userservice则代表某服务&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;方式二是java代码配置Logger.Level这个Bean&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;如果在@EnableFeignClients注解声明则代表全局&lt;/li&gt;
&lt;li&gt;如果在@FeignClient注解中声明则代表某服务&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Feign的性能优化&lt;/h2&gt;
&lt;p&gt;Feign底层的客户端实现：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;URLConnection:：默认实现，不支持连接池&lt;/li&gt;
&lt;li&gt;Apache HttpClient：支持连接池&lt;/li&gt;
&lt;li&gt;OKHttp：支持连接池&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此优化Feign的性能主要包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用连接池代替默认的URLConnection&lt;/li&gt;
&lt;li&gt;日志级别，最好用basic或none&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Feign的性能优化-连接池配置&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;引入依赖&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!--引入HttpClient依赖--&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;io.github.openfeign&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;feign-httpclient&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;配置连接池&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;feign:
  client:
	config:
	  default: #default全局的配置
		loggerLevel: BASIC #日志级别，BASIC就是基本的请求和响应信息
  httpclient:
    enabled: true # 支持HttpClient的开关
    max-connections: 200 # 最大连接数
    max-connections-per-route: 50 # 单个路径的最大连接数
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;实践分析&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;方式一（继承）：给消费者的Feignclient和提供者的controller定义统一的父接口作为标准。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/springcloud-tools/feign-inheritance.svg&quot; alt=&quot;公共父接口由 Feign 客户端继承、服务端 Controller 实现&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：公共父接口统一方法契约，两端分别继承或实现，但运行时仍是跨进程的 HTTP 调用。接口复用会把契约变更传递到两端；尤其要核对当前版本对映射注解继承的支持，不能只凭 Java 接口继承就认定远程路由已经正确配置。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;方式二（抽取）：将FeignClient抽取为独立模块，并且把接口有关的PO]O、默认的Feign配置都放到这个模块中，提供给所有消费者使用&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/springcloud-tools/feign-module.svg&quot; alt=&quot;多个消费者依赖包含客户端接口与传输对象的独立 Feign 模块&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：把客户端接口、相关传输对象和默认配置抽成独立模块供消费者依赖，服务端保留自己的 Controller 实现。模块依赖只解决代码可见性；还要让 &lt;code&gt;@EnableFeignClients&lt;/code&gt; 的扫描范围或显式客户端列表覆盖该接口，才能注册并注入调用代理。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;当定义的FeignClient不在SpringBootApplication的扫描包范围时，这些FeignClient无法使用。有两种方式解决:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;方式一：指定FeignClient所在包&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;@EnableFeignclients(basePackages = &quot;cn.itcast.feign.clients&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;方式二：指定FeignClient字节码&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;@EnableFeignclients(clients = {Userclient.class})
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>HTML5+CSS305-移动端Web</title><link>https://wang-nine.github.io/posts/10505/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10505/</guid><description>第十天笔记</description><pubDate>Thu, 07 Dec 2023 05:30:00 GMT</pubDate><content:encoded>&lt;p&gt;第十天笔记&lt;/p&gt;
&lt;h1&gt;移动端Web&lt;/h1&gt;
</content:encoded></item><item><title>JavaScript总览</title><link>https://wang-nine.github.io/posts/10800/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10800/</guid><pubDate>Fri, 01 Dec 2023 15:00:00 GMT</pubDate><content:encoded/></item><item><title>JavaScript01-基础</title><link>https://wang-nine.github.io/posts/10801/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10801/</guid><description>1.JavaScript 是什么（是什么？）</description><pubDate>Fri, 01 Dec 2023 15:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;JavaScript基础&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;1.JavaScript 是什么（是什么？）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;是一种运行在客户端（浏览器）的编程语言，实现人机交互效果。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2.作用（做什么?）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;网页特效（监听用户的一些行为让网页作出对应的反馈）&lt;/li&gt;
&lt;li&gt;表单验证（针对表单数据的合法性进行判断）&lt;/li&gt;
&lt;li&gt;数据交互（获取后台的数据,渲染到前端）&lt;/li&gt;
&lt;li&gt;服务端编程（node.js）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3.JavaScript的组成（有什么?）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ECMAScript：规定了 js 基础语法核心知识。&lt;/li&gt;
&lt;li&gt;Web APIs
&lt;ul&gt;
&lt;li&gt;DOM：操作文档，比如对页面元素进行移动、大小、添加删除等操作&lt;/li&gt;
&lt;li&gt;BOM：操作浏览器，比如页面弹窗，检测窗口宽度、存储数据到浏览器等等&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;JavaScript书写位置&lt;/h2&gt;
&lt;p&gt;与 CSS 相同&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CSS&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;行内样式表&lt;/li&gt;
&lt;li&gt;内部样式表&lt;/li&gt;
&lt;li&gt;外部样式表&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;JavaScript&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;行内样式表&lt;/li&gt;
&lt;li&gt;内部样式表&lt;/li&gt;
&lt;li&gt;外部样式表&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;内部 JavaScript&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;直接写在html文件里，用script标签包住&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;规范：script标签写在\&amp;lt;/body&amp;gt;上面&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;拓展：alert(‘你好，js’) 页面弹出警告对话框&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意事项：&lt;strong&gt;我们将\&amp;lt;script&amp;gt;放在HTML文件的&lt;/strong&gt;底部附近&lt;/strong&gt;的原因是浏览器会按照代码在文件中的&lt;strong&gt;顺序加载&lt;/strong&gt; HTML。如果先加载的 JavaScript 期望修改其下方的 HTML，那么它可能由于 HTML 尚未被加载而失效。因此，将 JavaScript 代码放在 HTML 页面的底部附近通常是最好的策略。&lt;/p&gt;
&lt;h3&gt;外部 JavaScript&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;代码写在以.js结尾的文件里&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;语法：通过script标签，引入到html页面中。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意事项：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;script 标签中间无需写代码，否则会被忽略!&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;外部 JavaScript 会使代码更加有序，更易于复用，且没有了脚本的混合，HTML也会更加易读，因此这是个好的习惯。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;内联 JavaScript&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;代码写在标签内部&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;语法：先不做了解&lt;/p&gt;
&lt;p&gt;注意：此处作为了解即可，但是后面vue框架会用这种模式&lt;/p&gt;
&lt;h2&gt;JavaScript 注释&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;单行注释
&lt;ul&gt;
&lt;li&gt;符号：//&lt;/li&gt;
&lt;li&gt;作用：右边这一行的代码会被忽略&lt;/li&gt;
&lt;li&gt;快捷键：ctrl + /&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;块注释
&lt;ul&gt;
&lt;li&gt;符号：/* */&lt;/li&gt;
&lt;li&gt;作用：在/*和*/之间的所有内容都会被忽略&lt;/li&gt;
&lt;li&gt;快捷键：shift + alt + a&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;JavaScript 结束符&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;结束符
&lt;ul&gt;
&lt;li&gt;作用：使用英文的;代表语句结束&lt;/li&gt;
&lt;li&gt;实际情况：实际开发中，可写可不写，浏览器（JavaScript引擎）可以自动推断语句的结束位置&lt;/li&gt;
&lt;li&gt;现状：在实际开发中，越来越多的人主张，书写 JavaScript 代码时省略结束符&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;约定：为了风格统一，结束符要么每句都写，要么每句都不写（按照团队要求）&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;JavaScript 输入与输出语法&lt;/h2&gt;
&lt;h3&gt;输出语法&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;语法1&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;document.write(&apos;输出的内容&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;作用：向 body 内输出内容&lt;/p&gt;
&lt;p&gt;注意：如果输出的内容写的是标签，也会被解析成网页元素&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;语法2&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;alert(&apos;输出的内容&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;作用：页面弹出警告框&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;语法3&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;console.log(&apos;控制台打印&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;输入语法&lt;/h3&gt;
&lt;p&gt;语法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;prompt(&apos;请输入您的姓名：&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;作用：显示一个对话框，对话框中包含一条文字信息，用来提示用户输入文字&lt;/p&gt;
&lt;h3&gt;代码执行顺序&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;按 HTML 文档流顺序执行 JavaScript 代码&lt;/li&gt;
&lt;li&gt;alert() 和 prompt() 它们会跳过页面渲染先被执行（目前作为了解，后期讲解详细执行过程）&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;变量&lt;/h2&gt;
&lt;h3&gt;声明&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;let 变量名
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;赋值&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;变量名 = 值
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;更新&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;变量名 = 值
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意：let 不允许多次声明一个变量&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;声明多个变量（不推荐）&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;let age = 18, uname = &apos;pink&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;let 和 var 的区别&lt;/h3&gt;
&lt;p&gt;在较旧的JavaScript，使用关键字var来声明变量，而不是let。&lt;/p&gt;
&lt;p&gt;var现在开发中一般不再使用它，只是我们可能再老版程序中看到它。&lt;/p&gt;
&lt;p&gt;let为了解决var 的一些问题。&lt;/p&gt;
&lt;p&gt;var声明：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;可以先使用在声明(不合理)&lt;/li&gt;
&lt;li&gt;var声明过的变量可以重复声明(不合理)&lt;/li&gt;
&lt;li&gt;比如变量提升、全局变量、没有块级作用域等等&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;常量&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;const 变量名 = 值
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意：声明时必须初始化&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;数组&lt;/h2&gt;
&lt;h3&gt;声明语法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;let 数组名 = [数据1, 数据2, ..., 数据n]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;取值语法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;数组名[索引号]
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;和其他语言一样从零开始&lt;/li&gt;
&lt;li&gt;数组里面可以存储任意类型的数据&lt;/li&gt;
&lt;li&gt;数组的长度可以使用其属性获得，和 java 一样，数组名.length&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;数据类型&lt;/h2&gt;
&lt;h3&gt;基本数据类型&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;number 数字型&lt;/li&gt;
&lt;li&gt;string 字符串型&lt;/li&gt;
&lt;li&gt;boolean 布尔型&lt;/li&gt;
&lt;li&gt;undefined 未定义型&lt;/li&gt;
&lt;li&gt;null 空类型&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;number - 数字型&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;有关于 NaN（Not a Number） 的注意：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;NaN代表一个计算错误，它是一个不正确的或者一个未定义的数学操作所得到的结果&lt;/li&gt;
&lt;li&gt;NaN是粘性的，任何对NaN的操作都会返回NaN&lt;/li&gt;
&lt;li&gt;NaN != NaN&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;string - 字符串型&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;通过单引号(‘’)、双引号(“”)或反引号(`)包裹的数据都叫字符串，单引号和双引号没有本质上的区别，推荐使用单引号&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;字符串 + 变量写法&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;写法1（较为麻烦）&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;let name = wang
let age = 21
document.write(&apos;大家好，我叫’+ name + &apos;，今年&apos; +age +&apos;岁&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;写法2（较为简单）&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;let name = wang
let age = 21
document.write(`大家好，我叫${name}，今年${age}岁`)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;写法2注意事项：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;``反引号包裹&lt;/li&gt;
&lt;li&gt;拼接变量时，用 ${}包裹变量&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;boolean - 布尔型&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;true&lt;/li&gt;
&lt;li&gt;false&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;undefined - 未定义型&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;只声明变量，不赋值的情况下，变量的默认值为undefined，一般很少直接为某个变量赋值为undefined。&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;null - 空类型&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;如果一个变量里面确定存放的是对象，如果还没准备好对象，可以放个null&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Javascript 中的 null 仅仅是一个代表“无”、“空”或“值未知”的特殊值&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;null 和 undefined 区别：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;undefined 表示没有赋值&lt;/li&gt;
&lt;li&gt;null 表示赋值了，但是内容为空&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;typeof - 检测数据类型&lt;/h4&gt;
&lt;p&gt;typeof运算符可以返回被检测的数据类型。它支持两种语法形式&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;作为运算符：typeof x（常用的写法）&lt;/li&gt;
&lt;li&gt;函数形式：typeof(x)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;换言之，有括号和没有括号，得到的结果是一样的，所以我们直接&lt;strong&gt;使用运算符&lt;/strong&gt;的写法。&lt;/p&gt;
&lt;h4&gt;类型转换&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;1.隐式转换&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;某些运算符被执行时，系统内部自动将数据类型进行转换，这种转换称为隐式转换。&lt;/p&gt;
&lt;p&gt;规则:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;ul&gt;
&lt;li&gt;号两边只要有一个是字符串，都会把另外一个转成字符串&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;除了 + 以外的算术运算符比如 - * / 等都会把数据转成数字类型&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;缺点:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;转换类型不明确，靠经验才能总结&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;小技巧:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;+号作为正号解析可以转换成数字型&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;任何数据和字符串相加结果都是字符串&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2.显示转换&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;转换为数字型
&lt;ul&gt;
&lt;li&gt;Number(数据)&lt;/li&gt;
&lt;li&gt;parseInt(数据)&lt;/li&gt;
&lt;li&gt;parseFloat(数据)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;引用数据类型&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;object 对象&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>HTML5+CSS304-前端项目学习</title><link>https://wang-nine.github.io/posts/10504/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10504/</guid><description>第九天笔记</description><pubDate>Fri, 01 Dec 2023 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;第九天笔记&lt;/p&gt;
&lt;h1&gt;前端项目学习&lt;/h1&gt;
&lt;h2&gt;项目目录&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;xtx-pc
&lt;ul&gt;
&lt;li&gt;images文件夹：存放固定使用的图片素材，例如：logo、样式修饰图等等&lt;/li&gt;
&lt;li&gt;uploads文件夹：存放非固定使用的图片素材，例如：商品图、宣传图、需要上传的图片&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;iconfont文件夹：字体图标素材&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;css 文件夹：存放CSS文件(link标签引入)
&lt;ul&gt;
&lt;li&gt;base.css：基础公共样式(&lt;strong&gt;最先引入&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;common.css：各个网页相同模块的重复样式，例如：头部、底部&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;index.css：首页CSS样式&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;index.html：首页HTML文件&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;SEO（搜索引擎优化）&lt;/h2&gt;
&lt;p&gt;提升 SEO 的常见方法：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;竞价排名&lt;/li&gt;
&lt;li&gt;将网页制作成 html 后缀&lt;/li&gt;
&lt;li&gt;标签语义化&lt;/li&gt;
&lt;li&gt;……&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;网页头部 SEO 标签&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;title&lt;/strong&gt;：网页标题标签&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;description&lt;/strong&gt;：网页描述&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;keywords&lt;/strong&gt;：网页关键词&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;meta name=&quot;description&quot; content=&quot;xxx&quot;&amp;gt;
&amp;lt;meta name=&quot;keywords&quot; content=&quot;xxx&quot;&amp;gt;
&amp;lt;title&amp;gt;xxx&amp;lt;/title&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Favicon 图标&lt;/h2&gt;
&lt;p&gt;Favicon图标：网页图标，出现在浏览器标题栏，增加网站辨识度。&lt;/p&gt;
&lt;p&gt;图标：favition.ico 一般存放到网站的根目录里面&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;link rel=&quot;shortcut icon&quot; href=&quot;favicon.ico&quot; type=&quot;image/x-icon&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;版心&lt;/h2&gt;
&lt;p&gt;wrapper 版心宽度：1240px&lt;/p&gt;
&lt;p&gt;common.css 设置即可&lt;/p&gt;
&lt;h2&gt;导航&lt;/h2&gt;
&lt;p&gt;结构：通栏 &amp;gt; 版心 &amp;gt; 导航 ul&lt;/p&gt;
&lt;p&gt;布局：flex-end&lt;/p&gt;
&lt;h2&gt;头部&lt;/h2&gt;
&lt;p&gt;结构：.header &amp;gt; &lt;strong&gt;logo&lt;/strong&gt; + &lt;strong&gt;导航&lt;/strong&gt;（nav） + &lt;strong&gt;搜索&lt;/strong&gt;（search） + &lt;strong&gt;购物车&lt;/strong&gt;（cart）&lt;/p&gt;
&lt;h3&gt;注意&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;logo：使用 h1 &amp;gt; a，a放关键字并设置字号 0px 来提升搜索引擎优化&lt;/li&gt;
&lt;li&gt;导航：下边界线加给 a 而不是 hover，确保悬停时从下间距开始显示边界线&lt;/li&gt;
&lt;li&gt;搜索：浏览器解析 input 优先生效自己的默认宽度，宽度设置为 0 重置宽度使 flex: 1生效&lt;/li&gt;
&lt;li&gt;购物车：子绝父相中 left 默认向右撑开，right默认向左撑开&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;底部&lt;/h2&gt;
&lt;h2&gt;index 区域&lt;/h2&gt;
&lt;h3&gt;新鲜好物（goods）&lt;/h3&gt;
&lt;p&gt;结构：&lt;strong&gt;标题&lt;/strong&gt;（title） + &lt;strong&gt;内容&lt;/strong&gt;（bd）&lt;/p&gt;
&lt;p&gt;提示：多区域样式共用&lt;/p&gt;
&lt;h3&gt;人气推荐（recommend）&lt;/h3&gt;
&lt;p&gt;同上&lt;/p&gt;
&lt;h3&gt;热门品牌（brand）&lt;/h3&gt;
&lt;p&gt;结构：左侧（left） + 右侧箭头（显示在标题外部：定位）&lt;/p&gt;
&lt;h3&gt;生鲜（fresh）&lt;/h3&gt;
&lt;p&gt;标题结构：右侧（right） &amp;gt; &lt;strong&gt;菜单（ul）&lt;/strong&gt; ＋ 查看全部&lt;/p&gt;
&lt;h3&gt;最新专题（topic）&lt;/h3&gt;
&lt;h2&gt;代码&lt;/h2&gt;
&lt;h3&gt;base.css&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;/* 去除常见标签默认的 margin 和 padding */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

/* 设置网页统一的字体大小、行高、字体系列相关属性 */
body {
  font: 16px/1.5  &quot;Microsoft Yahei&quot;,
    &quot;Hiragino Sans GB&quot;, &quot;Heiti SC&quot;, &quot;WenQuanYi Micro Hei&quot;, sans-serif;
  color: #333;
}

/* 去除列表默认样式 */
ul,
ol {
  list-style: none;
}

/* 去除默认的倾斜效果 */
em,
i {
  font-style: normal;
}

/* 去除a标签默认下划线，并设置默认文字颜色 */
a {
  text-decoration: none;
  color: #333;
}

/* 设置img的垂直对齐方式为居中对齐，去除img默认下间隙 */
img {
  width: 100%;
  height: 100%;
  vertical-align: middle;
}

/* 去除input默认样式 */
input {
  border: none;
  outline: none;
  color: #333;
}

h1,
h2,
h3,
h4,
h5,
h6 {
  font-weight: 400;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;common.css&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;/* 头尾各个页面相同的样式 */
/* 版心 */
.wrapper {
  margin: 0 auto;
  width: 1240px;
}

/* 快捷导航 */
.shortcut {
  height: 52px;
  background-color: #333;
}

.shortcut .wrapper {
  display: flex;
  justify-content: end;

  height: 52px;
  /* background-color: pink; */
}

.shortcut ul {
  display: flex;
  line-height: 52px;
}


.shortcut li a {
  padding: 0 15px;
  border-right: 1px solid #999;
  font-size: 14px;
  color: #fff;
}

.shortcut li:last-child a {
  border-right: none;
}

.shortcut li .iconfont {
  margin-right: 4px;
  vertical-align: middle;
}

.shortcut li .login {
  color: #5Eb69c;
}


/* 头部区域 */
.header {
  display: flex;
  margin-top: 22px;
  margin-bottom: 22px;
  height: 88px;
  /* background-color: pink; */
}

/* logo区域 */
.logo {
  margin-right: 40px;
  width: 200px;
  height: 88px;
  /* background-color: blue; */
}

.logo a {
  display: block;
  width: 200px;
  height: 88px;
  font-size: 0px;
  background-image: url(../images/logo.png);
}

/* 导航区域 */
.nav {
  margin-top: 33px;
  margin-right: 28px;
}

.nav ul {
  display: flex;
}

.nav li {
  margin-right: 47px;
}

.nav li a {
  padding-bottom: 10px;
}

.nav li a:hover {
  border-bottom: 2px solid #5EB69C;
  color: #5EB69C;
}

/* 搜索区域 */
.search {
  display: flex;
  margin-top: 33px;
  margin-right: 45px;
  width: 170px;
  height: 34px;
  border-bottom: 2px solid #F4F4F4;
}

.search .iconfont {
  margin-right: 8px;
  width: 18px;
  height: 18px;
  color: #CCC;
}

.search input {
  flex: 1;
  width: 0;
}

.search input::placeholder {
  font-size: 16px;
  color: #CCC;
}


/* 购物车区域 */
.cart {
  position: relative;
  margin-top: 32px;
}

.cart .iconfont {
  font-size: 24px;
}

.cart i {
  position: absolute;
  top: 1px;
  left: 15px;
  padding: 0 6px;
  height: 15px;
  background-color: #E26237;
  border-radius: 8px;
  font-size: 14px;
  color: #FFFEFE;
  line-height: 15px;
}


/* 底部区域 */
.footer {
  height: 580px;
  background-color: #F5F5F5;
}

/* 服务区域 */
.service {
  padding: 60px 0;
  height: 178px;
  border-bottom: 1px solid #E8E8E8;
}

.service ul {
  display: flex;
  justify-content: space-evenly;
}

.service li {
  display: flex;
  width: 190px;
  height: 58px;
  /* background-color: pink; */
}

.service li h5 {
  margin-right: 20px;
  width: 58px;
  height: 58px;
  background-image: url(../images/sprite.png);
}

.service li p {
  font-size: 28px;
  line-height: 58px;
}

.service li:nth-child(2) h5 {
  background-position: 0 -58px;
}

.service li:nth-child(3) h5 {
  background-position: 0 -116px;
}

.service li:nth-child(4) h5 {
  background-position: 0 -174px;
}

/* 帮助中心区域 */
.help {
  padding-top: 60px;
  display: flex;
  justify-content: space-between;
  padding: 60px 0;
  height: 300px;
  /* background-color: pink; */
}

.help .left {
  display: flex;
}

.help .left dl {
  margin-right: 84px;
}

.help .left dl:last-child {
  margin-right: 0;
}

.help .left dt {
  margin-bottom: 30px;
  font-size: 18px;
}

.help .left dd {
  margin-bottom: 10px;
}

.help .left a {
  color: #969696
}

.help .left a:hover {
  color: #5Eb69c
}

.help .left .iconfont {
  color: #5Eb69c;
}

.help .right ul {
  display: flex;
}

.help .right li:first-child {
  margin-right: 55px;
}

.help .right .pic {
  margin-bottom: 10px;
  width: 120px;
  height: 120px;
}

.help .right p {
  color: #969696;
  text-align: center;
}

/* 版权区域 */
.copywrite {
  text-align: center;
}

.copywrite p {
  margin-bottom: 10px;
  color: #969696;
}

.copywrite p a {
  margin: 0 10px;
  color: #969696;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;index.css&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;/* 首页内容的样式 */

/* banner 区域 */
.banner {
  height: 500px;
  background-color: #f5f5f5;
}

.banner .wrapper {
  position: relative;
  height: 500px;
  /* background-color: pink; */
  /* 为了避免三张图片都显示，父级可以设置溢出隐藏属性 */
  overflow: hidden;
}

.banner .pic {
  display: flex;
  /* flex布局的关系。子级图片会被挤小，想要原图的效果则加宽 */
  width: 3720px;
}

/* 侧导航区域 */
.subnav {
  position: absolute;
  left: 0;
  top: 0;
  width: 250px;
  height: 500px;
  background-color: rgba(0, 0, 0, 0.42);
}

.subnav li {
  display: flex;
  justify-content: space-between;
  padding-left: 30px;
  padding-right: 12px;
  height: 50px;
  /* background-color: pink; */
  line-height: 50px;
  color: #FFF;

  cursor: pointer;
}

.subnav li a {
  margin-right: 5px;
  font-size: 14px;
  color: #fff;    
}

.subnav li .classify {
  margin-right: 14px;
  font-size: 16px;
}

.subnav li .iconfont {
  font-size: 14px;
}

.subnav li:hover {
  background-color: #00be9a;
}

/* 圆点指示器 */
.banner ol {
  display: flex;
  position: absolute;
  bottom: 17px;
  right: 16px;
}

.banner ol li {
  margin-left: 8px;
  width: 22px;
  height: 22px;
  /* background-color: pink; */
  border-radius: 50%;
  cursor: pointer;
}

.banner ol li i {
  display: block;
  margin: 4px;
  width: 14px;
  height: 14px;
  background-color: rgba(255, 255, 255, 0.5);
  border-radius: 50%;
}

/* 选中转态 li 半透明，i 白色 */
.banner ol .current {
  background-color: rgba(255, 255, 255, 0.5);
}

.banner ol .current i {
  background-color: rgba(255, 255, 255);
}


/* 公共样式 */
/* 新鲜好物区域 */
.title {
  display: flex;
  justify-content: space-between;
  margin-top: 40px;
  margin-bottom: 30px;
  height: 42px;
  /* background-color: pink; */
}

.title .left {
  display: flex;
}

.title .left h3 {
  margin-right: 35px;
  font-size: 30px;
}

.title .left p {
  align-self: flex-end;
  color: #A1A1A1;
}

.title .right .more {
  line-height: 42px;
  color: #A1A1A1;
}

.title .right .more .iconfont {
  margin-left: 10px;
}

/* 好物内容 */
.bd ul {
  display: flex;
  justify-content: space-between;
}

.bd li {
  width: 304px;
  height: 404px;
  background-color: #EEF9F4;
}

.bd li pic {
  width: 304px;
  height: 304px;
}

.bd li .txt {
  text-align: center;
}

.bd li h4 {
  margin-top: 18px;
  margin-bottom: 8px;
  font-size: 20px;
}

.goods .bd p {
  font-size: 18px;
  color: #AA2113;
}

.goods .bd p span {
  margin-left: 3px;
  font-size: 22px;
}

/* 人气推荐区域 */
.recommend bd li {
  background-color: #fff;
}

.recommend bd p {
  color: #A1A1A1;
}

/* 热门品牌区域 */
.brand {
  margin-top: 60px;
  height: 468px;
  background-color: #F5F5F5;
}

.brand .wrapper {
  overflow: hidden;
  height: 468px;
  /* background-color: pink; */
}

.brand .title {
  position: relative;
  margin-bottom: 40px;
}

.brand .button {
  display: flex;
  position: absolute;
  bottom: -25px;
  right: 0;
}

.brand .button a {
  margin-left: 12px;
  width: 20px;
  height: 20px;
  text-align: center;
  line-height: 20px;
  color: #fff;
}

.brand .button .prev {
  background-color: #ddd;
}

.brand .button .next {
  background-color: #00BE9A;
}

.brand .bd li {
  width: 244px;
  height: 306px;
}

/* 生鲜区域 */
.fresh .title {
  margin-top: 60px;
  margin-bottom: 20px;
}

.title .right {
  display: flex;
}

.title .right ul {
  display: flex;
  margin-right: 58px;
  margin-top: 10px;
}

.title .right a {
  display: block;
  margin-left: 6px;
  padding: 0 7px;
  height: 20px;
  line-height: 20px;
}

.title .right ul .active {
  background-color: #00BE9A;
  color: #fff;
}

/* 生鲜内容区域 */
.content {
  display: flex;
  justify-content: space-between;
}

.content .left {
  width: 248px;
  height: 610px;
}

.content .right {
  width: 968px;
  height: 610px;
}

.content .right ul {
  display: flex;
  flex-wrap: wrap;
}

.content .right li {
  position: relative;
  padding: 10px 21px 0;
  width: 242px;
  height: 305px;
  border: 2px solid #fff;
  
  /* 为了隐藏绿色的cover */
  overflow: hidden;
}

.content .pic {
  width: 200px;
  height: 180px;
}

.content .info {
  margin-top: 14px;
  margin-bottom: 5px;
  height: 60px;
  line-height: 19px;
}

.content .price {
  color: #AF2F22;
}

.content .price span {
  font-size: 22px;
}

/* 产品底部绿色区域 */
.content li .cover {
  position: absolute;
  left: 0;
  bottom: -86px;
  padding-top: 15px; 
  width: 242px;
  height: 84px;
  background-color: #00BE9A;
  text-align: center;
  color: #fff;
  transition: all 0.5s;
}

.content .cover p:nth-child(1) {
  font-size: 18px;
}

.content .cover p:nth-child(2) {
  margin: 3px auto 6px;
  width: 120px;
  height: 1px;
  background-color: rgba(255, 255, 255, 0.11);
}

.content .cover p:nth-child(3) {
  font-size: 13px;
}

.content .cover p:nth-child(3) .iconfont {
  font-size: 14px;
}

/* 默认鼠标悬停到 li 显示cover */
.content .right li:hover .cover {
  bottom: 0;
}

.content .right li:hover {
  border: 2px solid #00BE9A;
}


/* 最新专题区域 */
.topic {
  margin-bottom: 40px;
}

.topic .title {
  margin-top: 100px;
}

.topic-bd ul {
  display: flex;
  justify-content: space-between;
}

.topic-bd li {
  width: 405px;
  height: 355px;
  /* background-color: pink; */
}

.topic-bd .pic {
  position: relative;
  width: 405px;
  height: 288px;
}

.topic-bd .txt {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 15px;
  width: 405px;
  height: 67px;
  font-size: 14px;
  color: #666;
}

.topic-bd .txt .left {
  display: flex;
}

.topic-bd .txt .left p {
  margin-right: 20px;
}

.topic-bd .txt .left p:nth-child(1) {
  color: #AF2F22;
}

/* 定位区域 - 文字 */
.topic-bd .cover {
  position: absolute;
  left: 0;
  bottom: 0;

  display: flex;
  justify-content: space-between;
  align-items: center;

  padding: 0 15px;
  width: 405px;
  height: 90px;
  background-image: linear-gradient(180deg, rgba(137,137,137,0.00) 0%, rgba(0,0,0,0.90) 100%);
}

.topic-bd .cover .left {
  color: #fff;
}

.topic-bd .cover .left h4 {
  margin-bottom: 4px;
  font-size: 20px;
}

.topic-bd .cover .right {
  padding: 0 7px;
  height: 25px;
  background-color: #fff;
  color: #AA2113;
  font-size: 15px;
}

.topic-bd .cover .right span {
  font-size: 18px;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;index.html&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
  &amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&amp;gt;
  &amp;lt;meta name=&quot;description&quot; content=&quot;小兔鲜儿官网，致力于打造全球最大的食品、生鲜电商购物平台。&quot;&amp;gt;
  &amp;lt;meta name=&quot;keywords&quot; content=&quot;小兔鲜儿,食品,生鲜,服装,家电,电商,购物&quot;&amp;gt;
  &amp;lt;title&amp;gt;小兔鲜儿-新鲜、惠民、快捷！&amp;lt;/title&amp;gt;
  &amp;lt;link rel=&quot;shortcut icon&quot; href=&quot;favicon.ico&quot; type=&quot;image/x-icon&quot;&amp;gt;
  &amp;lt;link rel=&quot;stylesheet&quot; href=&quot;./iconfont/iconfont.css&quot;&amp;gt;
  &amp;lt;link rel=&quot;stylesheet&quot; href=&quot;./css/base.css&quot;&amp;gt;
  &amp;lt;link rel=&quot;stylesheet&quot; href=&quot;./css/common.css&quot;&amp;gt;
  &amp;lt;link rel=&quot;stylesheet&quot; href=&quot;./css/index.css&quot;&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;!-- 导航区域 --&amp;gt;
  &amp;lt;div class=&quot;shortcut&quot;&amp;gt;
    &amp;lt;div class=&quot;wrapper&quot;&amp;gt;
      &amp;lt;ul&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot; class=&quot;login&quot;&amp;gt;请先登录&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;免费注册&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;我的订单&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;会员中心&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;购物中心&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;在线客服&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;&amp;lt;span class=&quot;iconfont icon-mobile-phone&quot;&amp;gt;&amp;lt;/span&amp;gt;请先登录&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;!-- 头部区域 --&amp;gt;
  &amp;lt;div class=&quot;header wrapper&quot;&amp;gt;
    &amp;lt;!-- logo区域 --&amp;gt;
    &amp;lt;div class=&quot;logo&quot;&amp;gt;
      &amp;lt;h1&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;小兔鲜&amp;lt;/a&amp;gt;&amp;lt;/h1&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;!-- 导航区域 --&amp;gt;
    &amp;lt;div class=&quot;nav&quot;&amp;gt;
      &amp;lt;ul&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;首页&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;生鲜&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;美食&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;餐厨&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;电器&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;居家&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;洗护&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;孕婴&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;服装&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;!-- 搜索区域 --&amp;gt;
    &amp;lt;div class=&quot;search&quot;&amp;gt;
      &amp;lt;span class=&quot;iconfont icon-search&quot;&amp;gt;&amp;lt;/span&amp;gt;
      &amp;lt;input type=&quot;text&quot; placeholder=&quot;搜一搜&quot;&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;!-- 购物车区域 --&amp;gt;
    &amp;lt;div class=&quot;cart&quot;&amp;gt;
      &amp;lt;span class=&quot;iconfont icon-cart-full&quot;&amp;gt;&amp;lt;/span&amp;gt;
      &amp;lt;i&amp;gt;2&amp;lt;/i&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;!-- banner 区域 --&amp;gt;
  &amp;lt;div class=&quot;banner&quot;&amp;gt;
    &amp;lt;div class=&quot;wrapper&quot;&amp;gt;
      &amp;lt;!-- 图片 --&amp;gt; 
      &amp;lt;ul class=&quot;pic&quot;&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/banner1.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/banner1.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/banner1.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;/ul&amp;gt;
      &amp;lt;!-- 侧导航 --&amp;gt;
      &amp;lt;div class=&quot;subnav&quot;&amp;gt;
        &amp;lt;ul&amp;gt;
          &amp;lt;li&amp;gt;
            &amp;lt;div&amp;gt;&amp;lt;a href=&quot;#&quot; class=&quot;classify&quot;&amp;gt;生鲜&amp;lt;/a&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;水果&amp;lt;/a&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;蔬菜&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;span class=&quot;iconfont icon-arrow-right-bold&quot;&amp;gt;&amp;lt;/span&amp;gt;
          &amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;
            &amp;lt;div&amp;gt;&amp;lt;a href=&quot;#&quot; class=&quot;classify&quot;&amp;gt;没事&amp;lt;/a&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;面点&amp;lt;/a&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;干果&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;span class=&quot;iconfont icon-arrow-right-bold&quot;&amp;gt;&amp;lt;/span&amp;gt;
          &amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;
            &amp;lt;div&amp;gt;&amp;lt;a href=&quot;#&quot; class=&quot;classify&quot;&amp;gt;餐厨&amp;lt;/a&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;数码产品&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;span class=&quot;iconfont icon-arrow-right-bold&quot;&amp;gt;&amp;lt;/span&amp;gt;
          &amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;
            &amp;lt;div&amp;gt;&amp;lt;a href=&quot;#&quot; class=&quot;classify&quot;&amp;gt;电器&amp;lt;/a&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;床品&amp;lt;/a&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;四件套&amp;lt;/a&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;被枕&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;span class=&quot;iconfont icon-arrow-right-bold&quot;&amp;gt;&amp;lt;/span&amp;gt;
          &amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;
            &amp;lt;div&amp;gt;&amp;lt;a href=&quot;#&quot; class=&quot;classify&quot;&amp;gt;居家&amp;lt;/a&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;玩具&amp;lt;/a&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;奶粉&amp;lt;/a&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;辅食&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;span class=&quot;iconfont icon-arrow-right-bold&quot;&amp;gt;&amp;lt;/span&amp;gt;
          &amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;
            &amp;lt;div&amp;gt;&amp;lt;a href=&quot;#&quot; class=&quot;classify&quot;&amp;gt;洗护&amp;lt;/a&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;洗发&amp;lt;/a&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;洗护&amp;lt;/a&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;美妆&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;span class=&quot;iconfont icon-arrow-right-bold&quot;&amp;gt;&amp;lt;/span&amp;gt;
          &amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;
            &amp;lt;div&amp;gt;&amp;lt;a href=&quot;#&quot; class=&quot;classify&quot;&amp;gt;孕婴&amp;lt;/a&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;奶粉&amp;lt;/a&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;玩具&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;span class=&quot;iconfont icon-arrow-right-bold&quot;&amp;gt;&amp;lt;/span&amp;gt;
          &amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;
            &amp;lt;div&amp;gt;&amp;lt;a href=&quot;#&quot; class=&quot;classify&quot;&amp;gt;服饰&amp;lt;/a&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;女装&amp;lt;/a&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;男装&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;span class=&quot;iconfont icon-arrow-right-bold&quot;&amp;gt;&amp;lt;/span&amp;gt;
          &amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;
            &amp;lt;div&amp;gt;&amp;lt;a href=&quot;#&quot; class=&quot;classify&quot;&amp;gt;杂货&amp;lt;/a&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;户外&amp;lt;/a&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;图书&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;span class=&quot;iconfont icon-arrow-right-bold&quot;&amp;gt;&amp;lt;/span&amp;gt;
          &amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;
            &amp;lt;div&amp;gt;&amp;lt;a href=&quot;#&quot; class=&quot;classify&quot;&amp;gt;品牌&amp;lt;/a&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;品牌制造&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;span class=&quot;iconfont icon-arrow-right-bold&quot;&amp;gt;&amp;lt;/span&amp;gt;
          &amp;lt;/li&amp;gt;
        &amp;lt;/ul&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;!-- 圆点指示器 --&amp;gt;
      &amp;lt;ol&amp;gt;
        &amp;lt;li class=&quot;current&quot;&amp;gt;&amp;lt;i&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;i&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;&amp;lt;i&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;/ol&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;!-- 新鲜好物区域 --&amp;gt;
  &amp;lt;div class=&quot;goods wrapper&quot;&amp;gt;
    &amp;lt;!-- 标题 --&amp;gt;
    &amp;lt;div class=&quot;title&quot;&amp;gt;
      &amp;lt;div class=&quot;left&quot;&amp;gt;
        &amp;lt;h3&amp;gt;新鲜好物&amp;lt;/h3&amp;gt;
        &amp;lt;p&amp;gt;新鲜出炉 品质靠谱&amp;lt;/p&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div class=&quot;right&quot;&amp;gt;
        &amp;lt;a href=&quot;#&quot; class=&quot;more&quot;&amp;gt;查看全部&amp;lt;span class=&quot;iconfont icon-arrow-right-bold&quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;!-- 内容 --&amp;gt;
    &amp;lt;div class=&quot;bd&quot;&amp;gt;
      &amp;lt;ul&amp;gt;
        &amp;lt;li&amp;gt;
          &amp;lt;a href=&quot;#&quot;&amp;gt;
            &amp;lt;div class=&quot;pic&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/goods1.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;div class=&quot;txt&quot;&amp;gt;
              &amp;lt;h4&amp;gt;KN95级莫兰迪色防护口罩&amp;lt;/h4&amp;gt;
              &amp;lt;p&amp;gt;￥&amp;lt;span&amp;gt;79&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/a&amp;gt;
        &amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;
          &amp;lt;a href=&quot;#&quot;&amp;gt;
            &amp;lt;div class=&quot;pic&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/goods2.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;div class=&quot;txt&quot;&amp;gt;
              &amp;lt;h4&amp;gt;紫檀外独板三层普洱茶盒&amp;lt;/h4&amp;gt;
              &amp;lt;p&amp;gt;￥&amp;lt;span&amp;gt;566&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/a&amp;gt;
        &amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;
          &amp;lt;a href=&quot;#&quot;&amp;gt;
            &amp;lt;div class=&quot;pic&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/goods1.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;div class=&quot;txt&quot;&amp;gt;
              &amp;lt;h4&amp;gt;法拉蒙高颜值记事本可定制&amp;lt;/h4&amp;gt;
              &amp;lt;p&amp;gt;￥&amp;lt;span&amp;gt;58&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/a&amp;gt;
        &amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;
          &amp;lt;a href=&quot;#&quot;&amp;gt;
            &amp;lt;div class=&quot;pic&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/goods1.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;div class=&quot;txt&quot;&amp;gt;
              &amp;lt;h4&amp;gt;科技布布艺沙发&amp;lt;/h4&amp;gt;
              &amp;lt;p&amp;gt;￥&amp;lt;span&amp;gt;3579&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/a&amp;gt;
        &amp;lt;/li&amp;gt;
      &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;!-- 人气推荐区域 --&amp;gt;
  &amp;lt;div class=&quot;recommend wrapper&quot;&amp;gt;
    &amp;lt;!-- 标题 --&amp;gt;
    &amp;lt;div class=&quot;title&quot;&amp;gt;
      &amp;lt;div class=&quot;left&quot;&amp;gt;
        &amp;lt;h3&amp;gt;人气推荐&amp;lt;/h3&amp;gt;
        &amp;lt;p&amp;gt;人气爆款 不容错过&amp;lt;/p&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;!-- 内容 --&amp;gt;
    &amp;lt;div class=&quot;bd&quot;&amp;gt;
      &amp;lt;ul&amp;gt;
        &amp;lt;li&amp;gt;
          &amp;lt;a href=&quot;#&quot;&amp;gt;
            &amp;lt;div class=&quot;pic&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/recommend1.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;div class=&quot;txt&quot;&amp;gt;
              &amp;lt;h4&amp;gt;特惠推荐&amp;lt;/h4&amp;gt;
              &amp;lt;p&amp;gt;￥&amp;lt;span&amp;gt;我猜得到 你的想要&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/a&amp;gt;
        &amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;
          &amp;lt;a href=&quot;#&quot;&amp;gt;
            &amp;lt;div class=&quot;pic&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/recommend2.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;div class=&quot;txt&quot;&amp;gt;
              &amp;lt;h4&amp;gt;爆款推荐&amp;lt;/h4&amp;gt;
              &amp;lt;p&amp;gt;￥&amp;lt;span&amp;gt;人气好物推荐&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/a&amp;gt;
        &amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;
          &amp;lt;a href=&quot;#&quot;&amp;gt;
            &amp;lt;div class=&quot;pic&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/recommend3.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;div class=&quot;txt&quot;&amp;gt;
              &amp;lt;h4&amp;gt;节日礼品一站买全&amp;lt;/h4&amp;gt;
              &amp;lt;p&amp;gt;￥&amp;lt;span&amp;gt;编辑尽心整理推荐&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/a&amp;gt;
        &amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;
          &amp;lt;a href=&quot;#&quot;&amp;gt;
            &amp;lt;div class=&quot;pic&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/recommend4.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;div class=&quot;txt&quot;&amp;gt;
              &amp;lt;h4&amp;gt;鲜花园艺&amp;lt;/h4&amp;gt;
              &amp;lt;p&amp;gt;￥&amp;lt;span&amp;gt;给生活增加仪式感&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/a&amp;gt;
        &amp;lt;/li&amp;gt;
      &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;!-- 热门品牌区域 --&amp;gt;
  &amp;lt;div class=&quot;brand&quot;&amp;gt;
    &amp;lt;div class=&quot;wrapper&quot;&amp;gt;
      &amp;lt;!-- 标题 --&amp;gt;
      &amp;lt;div class=&quot;title&quot;&amp;gt;
        &amp;lt;div class=&quot;left&quot;&amp;gt;
          &amp;lt;h3&amp;gt;热门品牌&amp;lt;/h3&amp;gt;
          &amp;lt;p&amp;gt;国际经典 品质认证&amp;lt;/p&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div&amp;gt;
          &amp;lt;div class=&quot;button&quot;&amp;gt;
            &amp;lt;a href=&quot;#&quot; class=&quot;prev&quot;&amp;gt;
              &amp;lt;i class=&quot;iconfont icon-arrow-left-bold&quot;&amp;gt;&amp;lt;/i&amp;gt;
            &amp;lt;/a&amp;gt;
            &amp;lt;a href=&quot;#&quot; class=&quot;next&quot;&amp;gt;
              &amp;lt;i class=&quot;iconfont icon-arrow-right-bold&quot;&amp;gt;&amp;lt;/i&amp;gt;
            &amp;lt;/a&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;!-- 内容 --&amp;gt;
       &amp;lt;div class=&quot;bd&quot;&amp;gt;
        &amp;lt;ul&amp;gt;
          &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/hot1.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/hot2.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/hot3.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/hot4.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/hot5.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;/ul&amp;gt;
       &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;!-- 生鲜区域 --&amp;gt;
  &amp;lt;div class=&quot;fresh wrapper&quot;&amp;gt;
    &amp;lt;!-- 标题 --&amp;gt;
    &amp;lt;div class=&quot;title&quot;&amp;gt;
      &amp;lt;div class=&quot;left&quot;&amp;gt;
        &amp;lt;h3&amp;gt;生鲜&amp;lt;/h3&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div class=&quot;right&quot;&amp;gt;
        &amp;lt;ul&amp;gt;
          &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot; class=&quot;active&quot;&amp;gt;热门&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;蔬菜&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;肉禽蛋&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;水果&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;海鲜&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;零食&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;饮料&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;/ul&amp;gt;
        &amp;lt;a href=&quot;#&quot; class=&quot;more&quot;&amp;gt;查看全部&amp;lt;span class=&quot;iconfont icon-arrow-right-bold&quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;!-- 内容 --&amp;gt;
    &amp;lt;div class=&quot;content&quot;&amp;gt;
      &amp;lt;div class=&quot;left&quot;&amp;gt;
        &amp;lt;a href=&quot;#&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/fresh_left.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/a&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div class=&quot;right&quot;&amp;gt;
        &amp;lt;ul&amp;gt;
          &amp;lt;li&amp;gt;
            &amp;lt;a href=&quot;#&quot;&amp;gt;
              &amp;lt;div class=&quot;pic&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/fresh1.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/div&amp;gt;
              &amp;lt;div class=&quot;txt&quot;&amp;gt;
                &amp;lt;div class=&quot;info&quot;&amp;gt;
                  &amp;lt;h4&amp;gt;双味千层，手抓饼烤肉组合&amp;lt;/h4&amp;gt;
                  &amp;lt;p&amp;gt;240g/袋 4片装&amp;lt;/p&amp;gt;
                  &amp;lt;p&amp;gt;加热即食&amp;lt;/p&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;p class=&quot;price&quot;&amp;gt;￥&amp;lt;span&amp;gt;89.99&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
              &amp;lt;/div&amp;gt;
            &amp;lt;/a&amp;gt;
            &amp;lt;div class=&quot;cover&quot;&amp;gt;
              &amp;lt;p&amp;gt;找相似&amp;lt;/p&amp;gt;
              &amp;lt;p&amp;gt;&amp;lt;/p&amp;gt;
              &amp;lt;p&amp;gt;发现更多宝贝&amp;lt;span class=&quot;iconfont icon-arrow-right-bold&quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;
            &amp;lt;a href=&quot;#&quot;&amp;gt;
              &amp;lt;div class=&quot;pic&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/fresh2.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/div&amp;gt;
              &amp;lt;div class=&quot;txt&quot;&amp;gt;
                &amp;lt;div class=&quot;info&quot;&amp;gt;
                  &amp;lt;h4&amp;gt;云南甘蔗慢熬红糖馒头&amp;lt;/h4&amp;gt;
                  &amp;lt;p&amp;gt;220g/袋 5个装&amp;lt;/p&amp;gt;
                  &amp;lt;p&amp;gt;加热即食&amp;lt;/p&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;p class=&quot;price&quot;&amp;gt;￥&amp;lt;span&amp;gt;9.00&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
              &amp;lt;/div&amp;gt;
            &amp;lt;/a&amp;gt;
            &amp;lt;div class=&quot;cover&quot;&amp;gt;
              &amp;lt;p&amp;gt;找相似&amp;lt;/p&amp;gt;
              &amp;lt;p&amp;gt;&amp;lt;/p&amp;gt;
              &amp;lt;p&amp;gt;发现更多宝贝&amp;lt;span class=&quot;iconfont icon-arrow-right-bold&quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;
            &amp;lt;a href=&quot;#&quot;&amp;gt;
              &amp;lt;div class=&quot;pic&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/fresh3.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/div&amp;gt;
              &amp;lt;div class=&quot;txt&quot;&amp;gt;
                &amp;lt;div class=&quot;info&quot;&amp;gt;
                  &amp;lt;h4&amp;gt;日式风味小圆饼&amp;lt;/h4&amp;gt;
                  &amp;lt;p&amp;gt;圆形【海盐味】&amp;lt;/p&amp;gt;
                  &amp;lt;p&amp;gt;糖果零食&amp;lt;/p&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;p class=&quot;price&quot;&amp;gt;￥&amp;lt;span&amp;gt;588.00&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
              &amp;lt;/div&amp;gt;
            &amp;lt;/a&amp;gt;
            &amp;lt;div class=&quot;cover&quot;&amp;gt;
              &amp;lt;p&amp;gt;找相似&amp;lt;/p&amp;gt;
              &amp;lt;p&amp;gt;&amp;lt;/p&amp;gt;
              &amp;lt;p&amp;gt;发现更多宝贝&amp;lt;span class=&quot;iconfont icon-arrow-right-bold&quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;
            &amp;lt;a href=&quot;#&quot;&amp;gt;
              &amp;lt;div class=&quot;pic&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/fresh4.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/div&amp;gt;
              &amp;lt;div class=&quot;txt&quot;&amp;gt;
                &amp;lt;div class=&quot;info&quot;&amp;gt;
                  &amp;lt;h4&amp;gt;全麦奶油浓香小面包&amp;lt;/h4&amp;gt;
                  &amp;lt;p&amp;gt;50g*12袋&amp;lt;/p&amp;gt;
                  &amp;lt;p&amp;gt;美味西点&amp;lt;/p&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;p class=&quot;price&quot;&amp;gt;￥&amp;lt;span&amp;gt;69.00&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
              &amp;lt;/div&amp;gt;
            &amp;lt;/a&amp;gt;
            &amp;lt;div class=&quot;cover&quot;&amp;gt;
              &amp;lt;p&amp;gt;找相似&amp;lt;/p&amp;gt;
              &amp;lt;p&amp;gt;&amp;lt;/p&amp;gt;
              &amp;lt;p&amp;gt;发现更多宝贝&amp;lt;span class=&quot;iconfont icon-arrow-right-bold&quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;
            &amp;lt;a href=&quot;#&quot;&amp;gt;
              &amp;lt;div class=&quot;pic&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/fresh5.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/div&amp;gt;
              &amp;lt;div class=&quot;txt&quot;&amp;gt;
                &amp;lt;div class=&quot;info&quot;&amp;gt;
                  &amp;lt;h4&amp;gt;秘制外皮五福摩提大福点心&amp;lt;/h4&amp;gt;
                  &amp;lt;p&amp;gt;150g/盒&amp;lt;/p&amp;gt;
                  &amp;lt;p&amp;gt;美味西点&amp;lt;/p&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;p class=&quot;price&quot;&amp;gt;￥&amp;lt;span&amp;gt;39.99&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
              &amp;lt;/div&amp;gt;
            &amp;lt;/a&amp;gt;
            &amp;lt;div class=&quot;cover&quot;&amp;gt;
              &amp;lt;p&amp;gt;找相似&amp;lt;/p&amp;gt;
              &amp;lt;p&amp;gt;&amp;lt;/p&amp;gt;
              &amp;lt;p&amp;gt;发现更多宝贝&amp;lt;span class=&quot;iconfont icon-arrow-right-bold&quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;
            &amp;lt;a href=&quot;#&quot;&amp;gt;
              &amp;lt;div class=&quot;pic&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/fresh6.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/div&amp;gt;
              &amp;lt;div class=&quot;txt&quot;&amp;gt;
                &amp;lt;div class=&quot;info&quot;&amp;gt;
                  &amp;lt;h4&amp;gt;水果面膜韩国蜂蜜柚子茶&amp;lt;/h4&amp;gt;
                  &amp;lt;p&amp;gt;560g/瓶&amp;lt;/p&amp;gt;
                  &amp;lt;p&amp;gt;冲调饮品&amp;lt;/p&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;p class=&quot;price&quot;&amp;gt;￥&amp;lt;span&amp;gt;89.99&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
              &amp;lt;/div&amp;gt;
            &amp;lt;/a&amp;gt;
            &amp;lt;div class=&quot;cover&quot;&amp;gt;
              &amp;lt;p&amp;gt;找相似&amp;lt;/p&amp;gt;
              &amp;lt;p&amp;gt;&amp;lt;/p&amp;gt;
              &amp;lt;p&amp;gt;发现更多宝贝&amp;lt;span class=&quot;iconfont icon-arrow-right-bold&quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;
            &amp;lt;a href=&quot;#&quot;&amp;gt;
              &amp;lt;div class=&quot;pic&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/fresh7.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/div&amp;gt;
              &amp;lt;div class=&quot;txt&quot;&amp;gt;
                &amp;lt;div class=&quot;info&quot;&amp;gt;
                  &amp;lt;h4&amp;gt;浓情比利时巧克力礼盒装&amp;lt;/h4&amp;gt;
                  &amp;lt;p&amp;gt;2406g/盒&amp;lt;/p&amp;gt;
                  &amp;lt;p&amp;gt;糖果零食&amp;lt;/p&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;p class=&quot;price&quot;&amp;gt;￥&amp;lt;span&amp;gt;120.00&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
              &amp;lt;/div&amp;gt;
            &amp;lt;/a&amp;gt;
            &amp;lt;div class=&quot;cover&quot;&amp;gt;
              &amp;lt;p&amp;gt;找相似&amp;lt;/p&amp;gt;
              &amp;lt;p&amp;gt;&amp;lt;/p&amp;gt;
              &amp;lt;p&amp;gt;发现更多宝贝&amp;lt;span class=&quot;iconfont icon-arrow-right-bold&quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;
            &amp;lt;a href=&quot;#&quot;&amp;gt;
              &amp;lt;div class=&quot;pic&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/fresh8.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/div&amp;gt;
              &amp;lt;div class=&quot;txt&quot;&amp;gt;
                &amp;lt;div class=&quot;info&quot;&amp;gt;
                  &amp;lt;h4&amp;gt;抹茶奶油小蛋糕礼盒装&amp;lt;/h4&amp;gt;
                  &amp;lt;p&amp;gt;220g/盒&amp;lt;/p&amp;gt;
                  &amp;lt;p&amp;gt;加热即食&amp;lt;/p&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;p class=&quot;price&quot;&amp;gt;￥&amp;lt;span&amp;gt;60.00&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
              &amp;lt;/div&amp;gt;
            &amp;lt;/a&amp;gt;
            &amp;lt;div class=&quot;cover&quot;&amp;gt;
              &amp;lt;p&amp;gt;找相似&amp;lt;/p&amp;gt;
              &amp;lt;p&amp;gt;&amp;lt;/p&amp;gt;
              &amp;lt;p&amp;gt;发现更多宝贝&amp;lt;span class=&quot;iconfont icon-arrow-right-bold&quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/li&amp;gt;
        &amp;lt;/ul&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;!-- 最新专题区域 --&amp;gt;
  &amp;lt;div class=&quot;topic wrapper&quot;&amp;gt;
    &amp;lt;!-- 标题 --&amp;gt;
    &amp;lt;div class=&quot;title&quot;&amp;gt;
      &amp;lt;div class=&quot;left&quot;&amp;gt;
        &amp;lt;h3&amp;gt;最新专题&amp;lt;/h3&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div class=&quot;right&quot;&amp;gt;
        &amp;lt;a href=&quot;#&quot; class=&quot;more&quot;&amp;gt;查看全部&amp;lt;span class=&quot;iconfont icon-arrow-right-bold&quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;!-- 内容 --&amp;gt;
    &amp;lt;div class=&quot;topic-bd&quot;&amp;gt;
      &amp;lt;ul&amp;gt;
        &amp;lt;li&amp;gt;
          &amp;lt;div class=&quot;pic&quot;&amp;gt;
            &amp;lt;img src=&quot;./uploads/topic1.png&quot; alt=&quot;&quot;&amp;gt;
            &amp;lt;!-- 定位区域 --&amp;gt;
            &amp;lt;div class=&quot;cover&quot;&amp;gt;
              &amp;lt;div class=&quot;left&quot;&amp;gt;
                &amp;lt;h4&amp;gt;吃这些美食才不算辜负自己&amp;lt;/h4&amp;gt;
                &amp;lt;p&amp;gt;餐厨起居洗护好物&amp;lt;/p&amp;gt;
              &amp;lt;/div&amp;gt;
              &amp;lt;div class=&quot;right&quot;&amp;gt;￥&amp;lt;span&amp;gt;29.9&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;起&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/div&amp;gt;
          &amp;lt;div class=&quot;txt&quot;&amp;gt;
            &amp;lt;div class=&quot;left&quot;&amp;gt;
              &amp;lt;p&amp;gt;
                &amp;lt;i class=&quot;iconfont icon-favorites-fill&quot;&amp;gt;&amp;lt;/i&amp;gt;
                &amp;lt;span&amp;gt;1220&amp;lt;/span&amp;gt;
              &amp;lt;/p&amp;gt;
              &amp;lt;p&amp;gt;
                &amp;lt;i class=&quot;iconfont icon-browse&quot;&amp;gt;&amp;lt;/i&amp;gt;
                &amp;lt;span&amp;gt;1800&amp;lt;/span&amp;gt;
              &amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class=&quot;right&quot;&amp;gt;
              &amp;lt;p&amp;gt;
                &amp;lt;i class=&quot;iconfont icon-comment&quot;&amp;gt;&amp;lt;/i&amp;gt;
                &amp;lt;span&amp;gt;246&amp;lt;/span&amp;gt;
              &amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;
          &amp;lt;div class=&quot;pic&quot;&amp;gt;
            &amp;lt;img src=&quot;./uploads/topic2.png&quot; alt=&quot;&quot;&amp;gt;
            &amp;lt;!-- 定位区域 --&amp;gt;
            &amp;lt;div class=&quot;cover&quot;&amp;gt;
              &amp;lt;div class=&quot;left&quot;&amp;gt;
                &amp;lt;h4&amp;gt;吃这些美食才不算辜负自己&amp;lt;/h4&amp;gt;
                &amp;lt;p&amp;gt;餐厨起居洗护好物&amp;lt;/p&amp;gt;
              &amp;lt;/div&amp;gt;
              &amp;lt;div class=&quot;right&quot;&amp;gt;￥&amp;lt;span&amp;gt;29.9&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;起&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/div&amp;gt;
          &amp;lt;div class=&quot;txt&quot;&amp;gt;
            &amp;lt;div class=&quot;left&quot;&amp;gt;
              &amp;lt;p&amp;gt;
                &amp;lt;i class=&quot;iconfont icon-favorites-fill&quot;&amp;gt;&amp;lt;/i&amp;gt;
                &amp;lt;span&amp;gt;1220&amp;lt;/span&amp;gt;
              &amp;lt;/p&amp;gt;
              &amp;lt;p&amp;gt;
                &amp;lt;i class=&quot;iconfont icon-browse&quot;&amp;gt;&amp;lt;/i&amp;gt;
                &amp;lt;span&amp;gt;1800&amp;lt;/span&amp;gt;
              &amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class=&quot;right&quot;&amp;gt;
              &amp;lt;p&amp;gt;
                &amp;lt;i class=&quot;iconfont icon-comment&quot;&amp;gt;&amp;lt;/i&amp;gt;
                &amp;lt;span&amp;gt;246&amp;lt;/span&amp;gt;
              &amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;
          &amp;lt;div class=&quot;pic&quot;&amp;gt;
            &amp;lt;img src=&quot;./uploads/topic3.png&quot; alt=&quot;&quot;&amp;gt;
            &amp;lt;!-- 定位区域 --&amp;gt;
            &amp;lt;div class=&quot;cover&quot;&amp;gt;
              &amp;lt;div class=&quot;left&quot;&amp;gt;
                &amp;lt;h4&amp;gt;吃这些美食才不算辜负自己&amp;lt;/h4&amp;gt;
                &amp;lt;p&amp;gt;餐厨起居洗护好物&amp;lt;/p&amp;gt;
              &amp;lt;/div&amp;gt;
              &amp;lt;div class=&quot;right&quot;&amp;gt;￥&amp;lt;span&amp;gt;29.9&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;起&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/div&amp;gt;
          &amp;lt;div class=&quot;txt&quot;&amp;gt;
            &amp;lt;div class=&quot;left&quot;&amp;gt;
              &amp;lt;p&amp;gt;
                &amp;lt;i class=&quot;iconfont icon-favorites-fill&quot;&amp;gt;&amp;lt;/i&amp;gt;
                &amp;lt;span&amp;gt;1220&amp;lt;/span&amp;gt;
              &amp;lt;/p&amp;gt;
              &amp;lt;p&amp;gt;
                &amp;lt;i class=&quot;iconfont icon-browse&quot;&amp;gt;&amp;lt;/i&amp;gt;
                &amp;lt;span&amp;gt;1800&amp;lt;/span&amp;gt;
              &amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class=&quot;right&quot;&amp;gt;
              &amp;lt;p&amp;gt;
                &amp;lt;i class=&quot;iconfont icon-comment&quot;&amp;gt;&amp;lt;/i&amp;gt;
                &amp;lt;span&amp;gt;246&amp;lt;/span&amp;gt;
              &amp;lt;/p&amp;gt;
            &amp;lt;/div&amp;gt;
          &amp;lt;/div&amp;gt;
        &amp;lt;/li&amp;gt;
      &amp;lt;/ul&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;

  &amp;lt;!-- 底部区域 --&amp;gt;
  &amp;lt;div class=&quot;footer&quot;&amp;gt;
    &amp;lt;div class=&quot;wrapper&quot;&amp;gt;
      &amp;lt;!-- 服务区域 --&amp;gt;
      &amp;lt;div class=&quot;service&quot;&amp;gt;
        &amp;lt;ul&amp;gt;
          &amp;lt;li&amp;gt;
            &amp;lt;h5&amp;gt;&amp;lt;/h5&amp;gt;
            &amp;lt;p&amp;gt;价格亲民&amp;lt;/p&amp;gt;
          &amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;
            &amp;lt;h5&amp;gt;&amp;lt;/h5&amp;gt;
            &amp;lt;p&amp;gt;物流快捷&amp;lt;/p&amp;gt;
          &amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;
            &amp;lt;h5&amp;gt;&amp;lt;/h5&amp;gt;
            &amp;lt;p&amp;gt;品质新鲜&amp;lt;/p&amp;gt;
          &amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;
            &amp;lt;h5&amp;gt;&amp;lt;/h5&amp;gt;
            &amp;lt;p&amp;gt;售后无忧&amp;lt;/p&amp;gt;
          &amp;lt;/li&amp;gt;
        &amp;lt;/ul&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;!-- 帮助中心区域 --&amp;gt;
      &amp;lt;div class=&quot;help&quot;&amp;gt;
        &amp;lt;div class=&quot;left&quot;&amp;gt;
          &amp;lt;dl&amp;gt;
            &amp;lt;dt&amp;gt;购物指南&amp;lt;/dt&amp;gt;
            &amp;lt;dd&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;购物流程&amp;lt;/a&amp;gt;&amp;lt;/dd&amp;gt;
            &amp;lt;dd&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;支付方式&amp;lt;/a&amp;gt;&amp;lt;/dd&amp;gt;
            &amp;lt;dd&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;售后规则&amp;lt;/a&amp;gt;&amp;lt;/dd&amp;gt;
          &amp;lt;/dl&amp;gt;
          &amp;lt;dl&amp;gt;
            &amp;lt;dt&amp;gt;配送方式&amp;lt;/dt&amp;gt;
            &amp;lt;dd&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;配送运费&amp;lt;/a&amp;gt;&amp;lt;/dd&amp;gt;
            &amp;lt;dd&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;配送范围&amp;lt;/a&amp;gt;&amp;lt;/dd&amp;gt;
            &amp;lt;dd&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;配送时间&amp;lt;/a&amp;gt;&amp;lt;/dd&amp;gt;
          &amp;lt;/dl&amp;gt;
          &amp;lt;dl&amp;gt;
            &amp;lt;dt&amp;gt;关于我们&amp;lt;/dt&amp;gt;
            &amp;lt;dd&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;平台规则&amp;lt;/a&amp;gt;&amp;lt;/dd&amp;gt;
            &amp;lt;dd&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;联系我们&amp;lt;/a&amp;gt;&amp;lt;/dd&amp;gt;
            &amp;lt;dd&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;问题反馈&amp;lt;/a&amp;gt;&amp;lt;/dd&amp;gt;
          &amp;lt;/dl&amp;gt;
          &amp;lt;dl&amp;gt;
            &amp;lt;dt&amp;gt;售后服务&amp;lt;/dt&amp;gt;
            &amp;lt;dd&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;售后政策&amp;lt;/a&amp;gt;&amp;lt;/dd&amp;gt;
            &amp;lt;dd&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;退款说明&amp;lt;/a&amp;gt;&amp;lt;/dd&amp;gt;
            &amp;lt;dd&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;取消订单&amp;lt;/a&amp;gt;&amp;lt;/dd&amp;gt;
          &amp;lt;/dl&amp;gt;
          &amp;lt;dl&amp;gt;
            &amp;lt;dt&amp;gt;服务热线&amp;lt;/dt&amp;gt;
            &amp;lt;dd&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;在线客服&amp;lt;span class=&quot;iconfont icon-customer-service&quot;&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&amp;lt;/dd&amp;gt;
            &amp;lt;dd&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;客服电话 400-0000-000&amp;lt;/a&amp;gt;&amp;lt;/dd&amp;gt;
            &amp;lt;dd&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;工作时间 周一至周日 8:00-18:00&amp;lt;/a&amp;gt;&amp;lt;/dd&amp;gt;
          &amp;lt;/dl&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div class=&quot;right&quot;&amp;gt;
          &amp;lt;ul&amp;gt;
            &amp;lt;li&amp;gt;
              &amp;lt;div class=&quot;pic&quot;&amp;gt;&amp;lt;img src=&quot;./images/wechat.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/div&amp;gt;
              &amp;lt;p&amp;gt;微信公众号&amp;lt;/p&amp;gt;
            &amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;
              &amp;lt;div class=&quot;pic&quot;&amp;gt;&amp;lt;img src=&quot;./images/app.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/div&amp;gt;
              &amp;lt;p&amp;gt;APP下载二维码&amp;lt;/p&amp;gt;
            &amp;lt;/li&amp;gt;
          &amp;lt;/ul&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;!-- 版权区域 --&amp;gt;
      &amp;lt;div class=&quot;copywrite&quot;&amp;gt;
        &amp;lt;p&amp;gt;
          &amp;lt;a href=&quot;#&quot;&amp;gt;关于我们&amp;lt;/a&amp;gt;|
          &amp;lt;a href=&quot;#&quot;&amp;gt;帮助中心&amp;lt;/a&amp;gt;|
          &amp;lt;a href=&quot;#&quot;&amp;gt;售后服务&amp;lt;/a&amp;gt;|
          &amp;lt;a href=&quot;#&quot;&amp;gt;配送与验收&amp;lt;/a&amp;gt;|
          &amp;lt;a href=&quot;#&quot;&amp;gt;商务合作&amp;lt;/a&amp;gt;|
          &amp;lt;a href=&quot;#&quot;&amp;gt;搜索推荐&amp;lt;/a&amp;gt;|
          &amp;lt;a href=&quot;#&quot;&amp;gt;友情链接&amp;lt;/a&amp;gt;
        &amp;lt;/p&amp;gt;
        &amp;lt;p&amp;gt;CopyRight © 小兔鲜&amp;lt;/p&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;如代码中注释&lt;/p&gt;
</content:encoded></item><item><title>HTML5+CSS303-综合页面设计</title><link>https://wang-nine.github.io/posts/10503/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10503/</guid><description>第七天笔记</description><pubDate>Tue, 28 Nov 2023 05:30:00 GMT</pubDate><content:encoded>&lt;p&gt;第七天笔记&lt;/p&gt;
&lt;h1&gt;综合页面设计&lt;/h1&gt;
&lt;h2&gt;项目目录&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;网站根目录&lt;/strong&gt;是指存放网站的&lt;strong&gt;第一层&lt;/strong&gt;文件夹，内部包含当前网站的&lt;strong&gt;所有素材&lt;/strong&gt;，包含HTML、cSS、图片、JavaScript等等。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;study
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;images&lt;/strong&gt; 文件夹：存放&lt;strong&gt;固定使用&lt;/strong&gt;的图片素材，例如：logo、样式修饰图等等&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;uploads&lt;/strong&gt; 文件夹：存放&lt;strong&gt;非固定使用&lt;/strong&gt;的图片素材，例如：商品图、宣传图需要上传的图片&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;css&lt;/strong&gt; 文件夹：存放 CSS 文件（ link 标签引入）
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;base.css&lt;/strong&gt;：基础公共样式，例如：清除默认样式、设置网页基本样式&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;index.css&lt;/strong&gt;：首页 CSS 样式&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;index.html&lt;/strong&gt;：首页 HTML 文件&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;网页制作思路&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;布局思路：先整体再局部，从外到内，从上到下，从左到右&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;css 实现思路&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;画盒子，调整盒子范围 → 宽高背景色&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;调整盒子位置 → flex 布局、内外边距&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;控制图片、文字内容样式&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;网页预览&lt;/h2&gt;
&lt;p&gt;页面从上到下由页头、横幅、精品推荐、课程列表和页脚组成。页头版心内依次安排 logo、导航、搜索与用户信息；横幅左侧放分类导航，右侧放课程表；下方课程区用可换行的 Flex 列表组织卡片。阅读后面的代码时，先核对各区域的嵌套关系，再检查宽度、间距和文字样式。示例 CSS 使用固定 &lt;code&gt;1200px&lt;/code&gt; 版心，窄屏时需要另行设计适配，不能把桌面布局直接视为响应式布局。&lt;/p&gt;
&lt;h2&gt;布局设计&lt;/h2&gt;
&lt;h3&gt;header 区域 - 布局与功能&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;通栏：宽度与浏览器窗口相同的盒子&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;标签结构：通栏 &amp;gt; 版心 &amp;gt; logo ＋ 导航 ＋ 搜索 ＋ 用户&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;logo 区域 - 布局与功能&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;logo功能&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;单击跳转到首页&lt;/li&gt;
&lt;li&gt;搜索引擎优化：提升网站百度搜索排名&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;实现方法&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;标签结构：h1 &amp;gt; a &amp;gt; 网站名称（搜索关键字）&lt;/li&gt;
&lt;li&gt;css 样式&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;导航（nav）区域 - 布局与功能&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;导航功能&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;单击跳转页面&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;实现方法&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;标签结构：ul &amp;gt; li * 3 &amp;gt; a&lt;/li&gt;
&lt;li&gt;优势：避免&lt;strong&gt;堆砌 a 标签&lt;/strong&gt;，网站搜索排名&lt;strong&gt;降级&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;布局思路
&lt;ul&gt;
&lt;li&gt;li 设置 右侧 margin&lt;/li&gt;
&lt;li&gt;a 设置 左右 padding&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;搜索（search）区域 - 布局与功能&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;实现方法&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;标签结构：.search &amp;gt; input + a / button&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;用户（user）区域 - 布局与功能&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;实现方法&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;标签结构：.user &amp;gt; a &amp;gt; img + span&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;banner 区域 - 布局与功能&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;实现方法&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;标签结构：通栏 banner &amp;gt; 版心 &amp;gt; .left + .right&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;左侧导航栏（left）&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;实现方法&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;标签结构：.left &amp;gt; ul &amp;gt; li * 9 &amp;gt; a&lt;/li&gt;
&lt;li&gt;布局思路：a 默认状态：背景图为白色右箭头&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;右侧课程表（right）&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;实现方法&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;标签结构：.right &amp;gt; h3 + .content&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;精品推荐（recommend）区域 - 布局和功能&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;实现方法&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;标签结构：.recommend &amp;gt; h3 + ul + a.modify&lt;/li&gt;
&lt;li&gt;布局思路：flex 布局&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;精品课程（course）区域 - 布局和功能&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;实现方法&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;标签结构：.hd + .bd&lt;/li&gt;
&lt;li&gt;布局思路：盒子模型&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;base.css&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

li {
    list-style: none;
}

body {
    font: 14px/1.5 &quot;Microsoft Yahei&quot;, &quot;Hiragino Sans GB&quot;, &quot;Heiti SC&quot;, &quot;WenQuanYi Micro Hei&quot;, sans-serif;
    color: #333;
}

a {
    color: #333;
    text-decoration: none;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;index.css&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;/* 首页样式 */

/* 版心样式 */
.wrapper {
    margin: 0 auto;
    width: 1200px;
}

body {
    background-color: #f3f5f7;
}


/* 头部区域样式 */
.header {
    height: 100px;
    background-color: #fff;
}

.header .wrapper {
    padding-top: 29px;
    display: flex;
}


/* logo 区域 */
.logo a {
    display: block;

    width: 195px;
    height: 41px;
    background-image: url(../images/logo.png);

    /* 隐藏文字 */
    font-size: 0;
}


/* 导航区域 */
.nav {
    margin-left: 102px;
}

.nav ul {
    display: flex;
}

.nav ul li {
    margin-right: 24px;
}

.nav ul a {
    display: block;
    padding: 6px 8px;

    line-height: 27px;
    font-size: 19px;
}

/* active 类选择器，表示默认选中的 a */
.nav li .active,
.nav li a:hover {
    border-bottom: 2px solid #00a4ff;
}


/* 搜索区域 */
.search {
    display: flex;
    margin-left: 64px;
    padding-left: 19px;
    padding-right: 12px;
    width: 412px;
    height: 40px;
    background-color: #f3f5f7;
    border-radius: 20px;
}

.search input {
    flex: 1;
    border: 0;
    background-color: transparent;
    outline: none;
}

.search::placeholder {
    font-size: 14px;
    color: #999;
}

/* 父级 flex 布局，子集的宽高生效 */
.search a {
    align-self: center;

    width: 16px;
    height: 16px;
    background-image: url(../images/search.png);
}


/* 用户区域 */
.user {
    margin-left: 32px;
    margin-top: 4px;
}

.user img {
    margin-right: 7px;
    /* vertical-align 行内块和行内垂直方向对齐方式 */
    vertical-align: middle;
}

.user span {
    font-size: 16px;
    color: #666
}


/* banner区域 */
.banner {
    height: 420px;
    background-color: #0092cd;
}

.banner .wrapper {
    display: flex;
    justify-content: space-between;
    height: 420px;
    background-image: url(../uploads/banner.png);
}


/* 侧导航区域 */
.banner .left {
    padding: 3px 20px;

    width: 191px;
    height: 420px;
    background-color: rgba(0, 0, 0, 0.42);
}

.banner .left a {
    /* 块级：宽度是父级的 100% */
    display: block;

    height: 46px;
    background: url(../images/right.png) no-repeat right center;

    line-height: 46px;

    font-size: 16px;
    color: #fff;
}

.banner .left a:hover {
    background-image: url(../images/right-hover.png);
    color: #00a4ff;
}


/* 课程表区域 */
.banner .right {
    margin-top: 60px;
    width: 218px;
    height: 305px;
    background-color: #209dd5;
    border-radius: 10px;
}

.banner .right h3 {
    margin-left: 14px;
    height: 48px;
    line-height: 48px;
    font-size: 15px;
    color: #fff;
    font-weight: 400;
}

.right .content {
    padding: 14px;
    height: 257px;
    background-color: #fff;
    border-radius: 10px;
}

.banner .right dl {
    margin-bottom: 12px;
    border-bottom: 1px solid #e0e0e0;
}

.banner .right dt {
    margin-bottom: 8px;
    font-size: 14px;
    line-height: 20px;
    font-weight: 700;
}

.banner .right dd {
    margin-bottom: 8px;
    font-size: 12px;
    line-height: 16px;
}

.banner .right dd span {
    color: #00a4ff;
}

.banner .right dd strong {
    color: #7d7d7d;
    font-weight: 400;
}

.banner .right a {
    display: block;
    height: 32px;
    background-color: #00a4ff;
    text-align: center;
    line-height: 32px;
    font-size: 14px;
    color: #fff;
    border-radius: 15px;
}


/* 精品推荐区域 */
.recommend {
    display: flex;
    margin-top: 11px;
    padding: 0 20px;
    height: 60px;
    background-color: #fff;
    box-shadow: 0px 1px 2px 0px rgba(211, 211, 211, 0.5);
    line-height: 60px;
}

.recommend h3 {
    font-size: 18px;
    color: #00a4ff;
    font-weight: 400;
}

.recommend ul {
    display: flex;
    /* 除去标题和修改兴趣的尺寸，父级剩余尺寸都给ul，实现把修改兴趣挤到最右侧 */
    flex: 1;
}

.recommend ul li a {
    padding: 0 24px;
    border-right: 1px solid #e0e0e0;;
    font-size: 18px;
}

.recommend ul li:last-child a {
    border-right: none;
}

.recommend .modify {
    font-size: 16px;
    color: #00a4ff;
}


/* 精品推荐课程区域 */
.course {
    margin-top: 15px;
}

/* 标题，公共类，与其他的标题共用 */
.hd {
    display: flex;
    justify-content: space-between;
    height: 60px;
    line-height: 60px;
}

.hd h3 {
    font-size: 21px;
    font-weight: 400;
}

.hd .more {
    padding-right: 20px;
    background: url(../images/more.png) no-repeat right center;
    font-size: 14px;
    color: #999;
}

/* 课程内容，公共类 */
.bd ul {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
}

.bd li {
    margin-bottom: 14px;
    width: 228px;
    height: 271px;
    background-color: pink;
}

.bd li .pic {
    height: 156px;
}

.bd li .text {
    padding: 20px;
    height: 115px;
    background-color: #fff;
}

.bd li .text h4 {
    margin-bottom: 13px;
    height: 40px;
    font-size: 14px;
    line-height: 20px;
    font-weight: 400;
}

.bd li .text p {
    font-size: 14px;
    line-height: 20px;
    color: #999;
}

.bd li .text p span {
    color: #fa6400;
}

.bd li .text p i {
    font-style: normal;
}


/* 前端开发工程师 */
.hd ul {
    display: flex;
}

.hd li {
    margin-right: 60px;
    font-size: 16px;
}

.hd li .active {
    color: #00a4ff;
}

.hd li a:hover {
    color: #00a4ff;
}

.bd {
    display: flex;
    justify-content: space-between;
}

.bd .left {
    width: 228px;
}

.bd .right {
    width: 957px;
}

.bd .right .top {
    margin-bottom: 15px;
    height: 100px;
}


/* 版权区域 */
.footer {
    margin-top: 60px;
    padding-top: 60px;
    height: 273px;
    background-color: #fff;
}

.footer .wrapper {
    display: flex;
    justify-content: space-between;
}

.footer .left {
    width: 440px;
}

.footer .left p {
    margin-top: 24px;
    margin-bottom: 14px;
    font-size: 12px;
    line-height: 17px;
    color: #666;
}

.footer .left .download {
    display: block;
    width: 120px;
    height: 36px;
    border: 1px solid #00a4ff;
    text-align: center;
    line-height: 34px;
    font-size: 16px;
    color: #00a4ff;
}

.footer .right {
    display: flex;
}

.footer .right dl {
    margin-left: 130px;
}

.footer .right dt {
    margin-bottom: 12px;
    font-size: 16px;
    line-height: 23px;
}

.footer .right a {
    font-size: 14px;
    color: #666;
    line-height: 24px;
}

.footer .right a:hover {
    color: #00a4ff;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;index.html&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
    &amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&amp;gt;
    &amp;lt;title&amp;gt;学成在线&amp;lt;/title&amp;gt;
    &amp;lt;!-- 注意！！！顺序要求：先清除，再设置 --&amp;gt;
    &amp;lt;link rel=&quot;stylesheet&quot; href=&quot;./css/base.css&quot;&amp;gt;
    &amp;lt;link rel=&quot;stylesheet&quot; href=&quot;./css/index.css&quot;&amp;gt;
&amp;lt;/head&amp;gt; 
&amp;lt;body&amp;gt;
    &amp;lt;!-- 头部区域 --&amp;gt;
    &amp;lt;div class=&quot;header&quot;&amp;gt;
        &amp;lt;div class=&quot;wrapper&quot;&amp;gt;
            &amp;lt;!-- logo --&amp;gt;
            &amp;lt;div class=&quot;logo&quot;&amp;gt;
                &amp;lt;h1&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;学成在线&amp;lt;/a&amp;gt;&amp;lt;/h1&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;!-- 导航 --&amp;gt;
            &amp;lt;div class=&quot;nav&quot;&amp;gt;
                &amp;lt;ul&amp;gt;
                    &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot; class=&quot;active&quot;&amp;gt;首页&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
                    &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;课程&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
                    &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;职业规划&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
                &amp;lt;/ul&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;!-- 搜索 --&amp;gt;
            &amp;lt;div class=&quot;search&quot;&amp;gt;
                &amp;lt;input type=&quot;text&quot; placeholder=&quot;请输入关键字词&quot;&amp;gt;
                &amp;lt;a href=&quot;#&quot;&amp;gt;&amp;lt;/a&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;!-- 用户 --&amp;gt;
            &amp;lt;div class=&quot;user&quot;&amp;gt;
                &amp;lt;a href=&quot;#&quot;&amp;gt;
                    &amp;lt;img src=&quot;./uploads/user.png&quot; alt=&quot;&quot;&amp;gt;
                    &amp;lt;span&amp;gt;前端用户&amp;lt;/span&amp;gt;
                &amp;lt;/a&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;

    &amp;lt;!-- banner 区域 --&amp;gt;
    &amp;lt;div class=&quot;banner&quot;&amp;gt;
        &amp;lt;div class=&quot;wrapper&quot;&amp;gt;
            &amp;lt;div class=&quot;left&quot;&amp;gt;
                &amp;lt;ul&amp;gt;
                    &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;前端开发&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
                    &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;后端开发&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
                    &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;移动开发&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
                    &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;人工智能&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
                    &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;商业预测&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
                    &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;云计算&amp;amp;大数据&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
                    &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;运维&amp;amp;测试&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
                    &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;UI设计&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
                    &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;产品&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
                &amp;lt;/ul&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class=&quot;right&quot;&amp;gt;
                &amp;lt;h3&amp;gt;我的课程表&amp;lt;/h3&amp;gt;
                &amp;lt;div class=&quot;content&quot;&amp;gt;
                    &amp;lt;dl&amp;gt;
                        &amp;lt;dt&amp;gt;数据可视化课程&amp;lt;/dt&amp;gt;
                        &amp;lt;dd&amp;gt;&amp;lt;span&amp;gt;正在学习&amp;lt;/span&amp;gt;-&amp;lt;strong&amp;gt;echarts使用步骤&amp;lt;/strong&amp;gt;&amp;lt;/dd&amp;gt;
                    &amp;lt;/dl&amp;gt;
                    &amp;lt;dl&amp;gt;
                        &amp;lt;dt&amp;gt;Vue3医疗项目课程&amp;lt;/dt&amp;gt;
                        &amp;lt;dd&amp;gt;&amp;lt;span&amp;gt;正在学习&amp;lt;/span&amp;gt;-&amp;lt;strong&amp;gt;认识组合式API&amp;lt;/strong&amp;gt;&amp;lt;/dd&amp;gt;
                    &amp;lt;/dl&amp;gt;
                    &amp;lt;dl&amp;gt;
                        &amp;lt;dt&amp;gt;React核心技术课程&amp;lt;/dt&amp;gt;
                        &amp;lt;dd&amp;gt;&amp;lt;span&amp;gt;正在学习&amp;lt;/span&amp;gt;-&amp;lt;strong&amp;gt;rudex配合TS使用&amp;lt;/strong&amp;gt;&amp;lt;/dd&amp;gt;
                    &amp;lt;/dl&amp;gt;
                    &amp;lt;a href=&quot;#&quot;&amp;gt;全部课程&amp;lt;/a&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;

    &amp;lt;!-- 精品推荐区域 --&amp;gt;
    &amp;lt;div class=&quot;recommend wrapper&quot;&amp;gt;
        &amp;lt;h3&amp;gt;精品推荐&amp;lt;/h3&amp;gt;
        &amp;lt;ul&amp;gt;
            &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;HTML&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;CSS&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;JavaScript&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;Node.js&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;Ajax&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;Vue2.0&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;Vue3.0&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;TypeScript&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;React&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;/ul&amp;gt;
        &amp;lt;a href=&quot;#&quot; class=&quot;modify&quot;&amp;gt;修改兴趣&amp;lt;/a&amp;gt;
    &amp;lt;/div&amp;gt;

    &amp;lt;!-- 精品课程推荐区域 --&amp;gt;
    &amp;lt;div class=&quot;course wrapper&quot;&amp;gt;
        &amp;lt;!-- 标题 --&amp;gt;
        &amp;lt;div class=&quot;hd&quot;&amp;gt;
            &amp;lt;h3&amp;gt;精品推荐&amp;lt;/h3&amp;gt;
            &amp;lt;a href=&quot;#&quot; class=&quot;more&quot;&amp;gt;查看全部&amp;lt;/a&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;!-- 课程 --&amp;gt;
        &amp;lt;div class=&quot;bd&quot;&amp;gt;
            &amp;lt;ul&amp;gt;
                &amp;lt;li&amp;gt;
                    &amp;lt;a href=&quot;#&quot;&amp;gt;
                        &amp;lt;div class=&quot;pic&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/course01.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/div&amp;gt;
                        &amp;lt;div class=&quot;text&quot;&amp;gt;
                            &amp;lt;h4&amp;gt;JavaScript数据看板项目实战&amp;lt;/h4&amp;gt;
                            &amp;lt;p&amp;gt;&amp;lt;span&amp;gt;高级&amp;lt;/span&amp;gt; - &amp;lt;i&amp;gt;1126&amp;lt;/i&amp;gt;人在学习&amp;lt;/p&amp;gt;
                        &amp;lt;/div&amp;gt;
                    &amp;lt;/a&amp;gt;
                &amp;lt;/li&amp;gt;
                &amp;lt;li&amp;gt;
                    &amp;lt;a href=&quot;#&quot;&amp;gt;
                        &amp;lt;div class=&quot;pic&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/course01.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/div&amp;gt;
                        &amp;lt;div class=&quot;text&quot;&amp;gt;
                            &amp;lt;h4&amp;gt;JavaScript数据看板项目实战&amp;lt;/h4&amp;gt;
                            &amp;lt;p&amp;gt;&amp;lt;span&amp;gt;高级&amp;lt;/span&amp;gt; - &amp;lt;i&amp;gt;1126&amp;lt;/i&amp;gt;人在学习&amp;lt;/p&amp;gt;
                        &amp;lt;/div&amp;gt;
                    &amp;lt;/a&amp;gt;
                &amp;lt;/li&amp;gt;
                &amp;lt;li&amp;gt;
                    &amp;lt;a href=&quot;#&quot;&amp;gt;
                        &amp;lt;div class=&quot;pic&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/course01.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/div&amp;gt;
                        &amp;lt;div class=&quot;text&quot;&amp;gt;
                            &amp;lt;h4&amp;gt;JavaScript数据看板项目实战&amp;lt;/h4&amp;gt;
                            &amp;lt;p&amp;gt;&amp;lt;span&amp;gt;高级&amp;lt;/span&amp;gt; - &amp;lt;i&amp;gt;1126&amp;lt;/i&amp;gt;人在学习&amp;lt;/p&amp;gt;
                        &amp;lt;/div&amp;gt;
                    &amp;lt;/a&amp;gt;
                &amp;lt;/li&amp;gt;
                &amp;lt;li&amp;gt;
                    &amp;lt;a href=&quot;#&quot;&amp;gt;
                        &amp;lt;div class=&quot;pic&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/course01.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/div&amp;gt;
                        &amp;lt;div class=&quot;text&quot;&amp;gt;
                            &amp;lt;h4&amp;gt;JavaScript数据看板项目实战&amp;lt;/h4&amp;gt;
                            &amp;lt;p&amp;gt;&amp;lt;span&amp;gt;高级&amp;lt;/span&amp;gt; - &amp;lt;i&amp;gt;1126&amp;lt;/i&amp;gt;人在学习&amp;lt;/p&amp;gt;
                        &amp;lt;/div&amp;gt;
                    &amp;lt;/a&amp;gt;
                &amp;lt;/li&amp;gt;
                &amp;lt;li&amp;gt;
                    &amp;lt;a href=&quot;#&quot;&amp;gt;
                        &amp;lt;div class=&quot;pic&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/course01.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/div&amp;gt;
                        &amp;lt;div class=&quot;text&quot;&amp;gt;
                            &amp;lt;h4&amp;gt;JavaScript数据看板项目实战&amp;lt;/h4&amp;gt;
                            &amp;lt;p&amp;gt;&amp;lt;span&amp;gt;高级&amp;lt;/span&amp;gt; - &amp;lt;i&amp;gt;1126&amp;lt;/i&amp;gt;人在学习&amp;lt;/p&amp;gt;
                        &amp;lt;/div&amp;gt;
                    &amp;lt;/a&amp;gt;
                &amp;lt;/li&amp;gt;
                &amp;lt;li&amp;gt;
                    &amp;lt;a href=&quot;#&quot;&amp;gt;
                        &amp;lt;div class=&quot;pic&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/course01.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/div&amp;gt;
                        &amp;lt;div class=&quot;text&quot;&amp;gt;
                            &amp;lt;h4&amp;gt;JavaScript数据看板项目实战&amp;lt;/h4&amp;gt;
                            &amp;lt;p&amp;gt;&amp;lt;span&amp;gt;高级&amp;lt;/span&amp;gt; - &amp;lt;i&amp;gt;1126&amp;lt;/i&amp;gt;人在学习&amp;lt;/p&amp;gt;
                        &amp;lt;/div&amp;gt;
                    &amp;lt;/a&amp;gt;
                &amp;lt;/li&amp;gt;
                &amp;lt;li&amp;gt;
                    &amp;lt;a href=&quot;#&quot;&amp;gt;
                        &amp;lt;div class=&quot;pic&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/course01.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/div&amp;gt;
                        &amp;lt;div class=&quot;text&quot;&amp;gt;
                            &amp;lt;h4&amp;gt;JavaScript数据看板项目实战&amp;lt;/h4&amp;gt;
                            &amp;lt;p&amp;gt;&amp;lt;span&amp;gt;高级&amp;lt;/span&amp;gt; - &amp;lt;i&amp;gt;1126&amp;lt;/i&amp;gt;人在学习&amp;lt;/p&amp;gt;
                        &amp;lt;/div&amp;gt;
                    &amp;lt;/a&amp;gt;
                &amp;lt;/li&amp;gt;
                &amp;lt;li&amp;gt;
                    &amp;lt;a href=&quot;#&quot;&amp;gt;
                        &amp;lt;div class=&quot;pic&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/course01.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/div&amp;gt;
                        &amp;lt;div class=&quot;text&quot;&amp;gt;
                            &amp;lt;h4&amp;gt;JavaScript数据看板项目实战&amp;lt;/h4&amp;gt;
                            &amp;lt;p&amp;gt;&amp;lt;span&amp;gt;高级&amp;lt;/span&amp;gt; - &amp;lt;i&amp;gt;1126&amp;lt;/i&amp;gt;人在学习&amp;lt;/p&amp;gt;
                        &amp;lt;/div&amp;gt;
                    &amp;lt;/a&amp;gt;
                &amp;lt;/li&amp;gt;
                &amp;lt;li&amp;gt;
                    &amp;lt;a href=&quot;#&quot;&amp;gt;
                        &amp;lt;div class=&quot;pic&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/course01.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/div&amp;gt;
                        &amp;lt;div class=&quot;text&quot;&amp;gt;
                            &amp;lt;h4&amp;gt;JavaScript数据看板项目实战&amp;lt;/h4&amp;gt;
                            &amp;lt;p&amp;gt;&amp;lt;span&amp;gt;高级&amp;lt;/span&amp;gt; - &amp;lt;i&amp;gt;1126&amp;lt;/i&amp;gt;人在学习&amp;lt;/p&amp;gt;
                        &amp;lt;/div&amp;gt;
                    &amp;lt;/a&amp;gt;
                &amp;lt;/li&amp;gt;
                &amp;lt;li&amp;gt;
                    &amp;lt;a href=&quot;#&quot;&amp;gt;
                        &amp;lt;div class=&quot;pic&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/course01.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/div&amp;gt;
                        &amp;lt;div class=&quot;text&quot;&amp;gt;
                            &amp;lt;h4&amp;gt;JavaScript数据看板项目实战&amp;lt;/h4&amp;gt;
                            &amp;lt;p&amp;gt;&amp;lt;span&amp;gt;高级&amp;lt;/span&amp;gt; - &amp;lt;i&amp;gt;1126&amp;lt;/i&amp;gt;人在学习&amp;lt;/p&amp;gt;
                        &amp;lt;/div&amp;gt;
                    &amp;lt;/a&amp;gt;
                &amp;lt;/li&amp;gt;
            &amp;lt;/ul&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;

    &amp;lt;!-- 前端开发工程师 --&amp;gt;
    &amp;lt;div class=&quot;wrapper&quot;&amp;gt;
        &amp;lt;div class=&quot;hd&quot;&amp;gt;
            &amp;lt;h3&amp;gt;前端开发工程师&amp;lt;/h3&amp;gt;
            &amp;lt;ul&amp;gt;
                &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot; class=&quot;active&quot;&amp;gt;热门&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
                &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;初级课程&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
                &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;中级课程&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
                &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;高级课程&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
            &amp;lt;/ul&amp;gt;
            &amp;lt;a href=&quot;#&quot; class=&quot;more&quot;&amp;gt;查看全部&amp;lt;/a&amp;gt;
        &amp;lt;/div&amp;gt;
        &amp;lt;div class=&quot;bd&quot;&amp;gt;
            &amp;lt;div class=&quot;left&quot;&amp;gt;
                &amp;lt;img src=&quot;./uploads/web_left.png&quot; alt=&quot;&quot;&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class=&quot;right&quot;&amp;gt;
                &amp;lt;div class=&quot;top&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/web_top.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/div&amp;gt;
                &amp;lt;div class=&quot;bottom&quot;&amp;gt;
                    &amp;lt;ul&amp;gt;
                        &amp;lt;li&amp;gt;
                            &amp;lt;a href=&quot;#&quot;&amp;gt;
                                &amp;lt;div class=&quot;pic&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/course01.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/div&amp;gt;
                                &amp;lt;div class=&quot;text&quot;&amp;gt;
                                    &amp;lt;h4&amp;gt;JavaScript数据看板项目实战&amp;lt;/h4&amp;gt;
                                    &amp;lt;p&amp;gt;&amp;lt;span&amp;gt;高级&amp;lt;/span&amp;gt; - &amp;lt;i&amp;gt;1126&amp;lt;/i&amp;gt;人在学习&amp;lt;/p&amp;gt;
                                &amp;lt;/div&amp;gt;
                            &amp;lt;/a&amp;gt;
                        &amp;lt;/li&amp;gt;
                        &amp;lt;li&amp;gt;
                            &amp;lt;a href=&quot;#&quot;&amp;gt;
                                &amp;lt;div class=&quot;pic&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/course01.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/div&amp;gt;
                                &amp;lt;div class=&quot;text&quot;&amp;gt;
                                    &amp;lt;h4&amp;gt;JavaScript数据看板项目实战&amp;lt;/h4&amp;gt;
                                    &amp;lt;p&amp;gt;&amp;lt;span&amp;gt;高级&amp;lt;/span&amp;gt; - &amp;lt;i&amp;gt;1126&amp;lt;/i&amp;gt;人在学习&amp;lt;/p&amp;gt;
                                &amp;lt;/div&amp;gt;
                            &amp;lt;/a&amp;gt;
                        &amp;lt;/li&amp;gt;
                        &amp;lt;li&amp;gt;
                            &amp;lt;a href=&quot;#&quot;&amp;gt;
                                &amp;lt;div class=&quot;pic&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/course01.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/div&amp;gt;
                                &amp;lt;div class=&quot;text&quot;&amp;gt;
                                    &amp;lt;h4&amp;gt;JavaScript数据看板项目实战&amp;lt;/h4&amp;gt;
                                    &amp;lt;p&amp;gt;&amp;lt;span&amp;gt;高级&amp;lt;/span&amp;gt; - &amp;lt;i&amp;gt;1126&amp;lt;/i&amp;gt;人在学习&amp;lt;/p&amp;gt;
                                &amp;lt;/div&amp;gt;
                            &amp;lt;/a&amp;gt;
                        &amp;lt;/li&amp;gt;
                        &amp;lt;li&amp;gt;
                            &amp;lt;a href=&quot;#&quot;&amp;gt;
                                &amp;lt;div class=&quot;pic&quot;&amp;gt;&amp;lt;img src=&quot;./uploads/course01.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/div&amp;gt;
                                &amp;lt;div class=&quot;text&quot;&amp;gt;
                                    &amp;lt;h4&amp;gt;JavaScript数据看板项目实战&amp;lt;/h4&amp;gt;
                                    &amp;lt;p&amp;gt;&amp;lt;span&amp;gt;高级&amp;lt;/span&amp;gt; - &amp;lt;i&amp;gt;1126&amp;lt;/i&amp;gt;人在学习&amp;lt;/p&amp;gt;
                                &amp;lt;/div&amp;gt;
                            &amp;lt;/a&amp;gt;
                        &amp;lt;/li&amp;gt;
                    &amp;lt;/ul&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;

    &amp;lt;!-- 版权区域 --&amp;gt;
    &amp;lt;div class=&quot;footer&quot;&amp;gt;
        &amp;lt;div class=&quot;wrapper&quot;&amp;gt;
            &amp;lt;div class=&quot;left&quot;&amp;gt;
                &amp;lt;a href=&quot;#&quot;&amp;gt;&amp;lt;img src=&quot;./images/logo.png&quot; alt=&quot;&quot;&amp;gt;&amp;lt;/a&amp;gt;
                &amp;lt;p&amp;gt;学成在线致力于普及中国最好的教育它与中国一流大学和机构合作提供在线课程。
                    © 2017年XTCG Inc.保留所有权利。-沪ICP备15025210号&amp;lt;/p&amp;gt;
                &amp;lt;a href=&quot;#&quot; class=&quot;download&quot;&amp;gt;下载APP&amp;lt;/a&amp;gt;
            &amp;lt;/div&amp;gt;
            &amp;lt;div class=&quot;right&quot;&amp;gt;
                &amp;lt;dl&amp;gt;
                    &amp;lt;dt&amp;gt;合作伙伴&amp;lt;/dt&amp;gt;
                    &amp;lt;dd&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;合作机构&amp;lt;/a&amp;gt;&amp;lt;/dd&amp;gt;
                    &amp;lt;dd&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;合作导师&amp;lt;/a&amp;gt;&amp;lt;/dd&amp;gt;
                &amp;lt;/dl&amp;gt;
                &amp;lt;dl&amp;gt;
                    &amp;lt;dt&amp;gt;合作伙伴&amp;lt;/dt&amp;gt;
                    &amp;lt;dd&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;合作机构&amp;lt;/a&amp;gt;&amp;lt;/dd&amp;gt;
                    &amp;lt;dd&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;合作导师&amp;lt;/a&amp;gt;&amp;lt;/dd&amp;gt;
                &amp;lt;/dl&amp;gt;
                &amp;lt;dl&amp;gt;
                    &amp;lt;dt&amp;gt;合作伙伴&amp;lt;/dt&amp;gt;
                    &amp;lt;dd&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;合作机构&amp;lt;/a&amp;gt;&amp;lt;/dd&amp;gt;
                    &amp;lt;dd&amp;gt;&amp;lt;a href=&quot;#&quot;&amp;gt;合作导师&amp;lt;/a&amp;gt;&amp;lt;/dd&amp;gt;
                &amp;lt;/dl&amp;gt;
            &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt; 
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;如代码中注释&lt;/p&gt;
</content:encoded></item><item><title>HTML5+CSS302-CSS</title><link>https://wang-nine.github.io/posts/10502/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10502/</guid><description>第三天笔记</description><pubDate>Thu, 23 Nov 2023 11:30:00 GMT</pubDate><content:encoded>&lt;p&gt;第三天笔记&lt;/p&gt;
&lt;h1&gt;CSS&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;层叠样式表&lt;/strong&gt;(Cascading Style Sheets，缩写为CSS)，是一种样式表语言，用来描述HTML文档的呈现(美化内容)&lt;/p&gt;
&lt;p&gt;书写位置：title标签下方添加style双标签，style标签里面书写CSS代码。&lt;/p&gt;
&lt;p&gt;示例代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
    &amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&amp;gt;
    &amp;lt;title&amp;gt;CSS&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;style&amp;gt;
    /* CSS 代码 */
    /* 选择器 { CSS 属性 } */
    /* 属性名和属性值成对出现 -&amp;gt; 键值对 */
    p {
        color: red; /* 修改字体颜色 */
        font-size: 30px; /* 修改字体大小 */
    }
&amp;lt;/style&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;p&amp;gt;体验css&amp;lt;/p&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;CSS 引入方式&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;内部样式表：&lt;strong&gt;学习使用&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;CSS代码写在style标签里面&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;外部样式表：开发使用
&lt;ul&gt;
&lt;li&gt;CSS代码写在单独的CSS文件中（.css）&lt;/li&gt;
&lt;li&gt;在 .html 文件中使用 &lt;strong&gt;link&lt;/strong&gt; 标签引入&lt;pre&gt;&lt;code&gt;&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;./my.css&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;行内样式：&lt;strong&gt;配合JavaScript使用&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;CSS代码写在style属性里面&lt;pre&gt;&lt;code&gt;&amp;lt;div style=&quot;color: red; font-size: 30px&quot;&amp;gt;
    xxx
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;CSS选择器&lt;/h2&gt;
&lt;p&gt;作用：查找标签，设置样式&lt;/p&gt;
&lt;p&gt;基础选择器：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;标签选择器&lt;/li&gt;
&lt;li&gt;类选择器&lt;/li&gt;
&lt;li&gt;id选择器&lt;/li&gt;
&lt;li&gt;通配符选择器&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;标签选择器&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;作用：使用标签名作为选择器 → 选中同名标签设置相同的样式。因此无法差异化同名标签的样式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;例如：p h1 div a&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;类选择器&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;作用：查找标签，差异化设置标签的显示效果。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;步骤：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;定义类选择器 —&amp;gt; .类名&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;使用类选择器 —&amp;gt; 标签加 class=“类名”&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;示例代码：&lt;strong&gt;（一个类选择器可以个多个标签使用，一个标签也可以使用多个类名）&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;style&amp;gt;
	/* 定义类选择器 */
    .red {
        color: red;
    }
    .size {
        font-size: 30px;
    }
&amp;lt;/style&amp;gt;

&amp;lt;!-- 使用类选择器 --&amp;gt;
&amp;lt;div class=&quot;red size&quot;&amp;gt;
    芝士div标签
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;开发习惯：类名见名知意，多个单词可以用–连接，例如:news-hd（新闻头部）&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;id选择器&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;作用：查找标签，差异化设置标签的显示效果。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;场景：id选择器一般配合JavaScript使用，很少用来设置CSS样式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;步骤：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;定义 id 选择器 —&amp;gt; #id名&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;使用 id 选择器 —&amp;gt; 标签加 id=“id名”&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;规则：同一个id选择器在一个页面只能使用一次&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;示例代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
    &amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&amp;gt;
    &amp;lt;title&amp;gt;Document&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;style&amp;gt;
    #red {
        color: red; /* 修改字体颜色 */
        font-size: 30px; /* 修改字体大小 */
    }
&amp;lt;/style&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;div id=&quot;red&quot;&amp;gt;芝士div标签&amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;通配符选择器&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;作用：查找页面所有标签，设置相同样式。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;通配符选择器：*，不需要调用，浏览器自动查找页面所有标签，设置相同的样式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;示例代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
    &amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&amp;gt;
    &amp;lt;title&amp;gt;Document&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;style&amp;gt;
    * {
        color: red; /* 修改字体颜色 */
        font-size: 30px; /* 修改字体大小 */
    }
&amp;lt;/style&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;div id=&quot;red&quot;&amp;gt;芝士div标签&amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;画盒子&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;目标：使用合适的选择器画盒子&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;新属性：&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;width&lt;/td&gt;
&lt;td&gt;宽度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;height&lt;/td&gt;
&lt;td&gt;高度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;background-color&lt;/td&gt;
&lt;td&gt;背景色&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;字体修饰属性&lt;/h2&gt;
&lt;p&gt;字体修饰可以按职责分开记忆：&lt;code&gt;font-size&lt;/code&gt; 控制字号，&lt;code&gt;font-weight&lt;/code&gt; 控制粗细，&lt;code&gt;font-style&lt;/code&gt; 控制倾斜，&lt;code&gt;line-height&lt;/code&gt; 控制行高，&lt;code&gt;font-family&lt;/code&gt; 指定字体与回退顺序。先分别调整这些属性观察差异，再使用 &lt;code&gt;font&lt;/code&gt; 简写；简写中的字号与字体族不可省略，行高紧随字号并用斜杠连接。多字体列表从左向右匹配，最后保留通用字体族作为回退。&lt;/p&gt;
&lt;h3&gt;字体大小&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;属性名：font-size&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;属性值：文字尺寸，PC端网页最常用的单位px（注意，必须有单位）&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;谷歌浏览器默认字体大小为16px&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;字体粗细&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;属性名：font-weight&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;属性值：&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;数字（开发使用）&lt;/strong&gt;
| 大小 | 数值 |&lt;br /&gt;
| — | —- |&lt;br /&gt;
| 正常 | 400 |&lt;br /&gt;
| 加粗 | 700 |&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;关键字&lt;/strong&gt;
| 大小 | 关键字 |&lt;br /&gt;
| — | ——— |&lt;br /&gt;
| 正常 | normal |&lt;br /&gt;
| 加粗 | bold |&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;字体倾斜&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;作用：清除文字默认的倾斜效果&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;属性名：font-style&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;属性值：&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;正常（不倾斜）：normal&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;倾斜：italic&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;行高&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;作用：调整多行文本间的间距&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;属性名：line-height&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;属性值：&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;数字+px&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数字（当前标签 font-size 属性值的倍数）&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;行高的垂直居中技巧：行高属性值等于盒子的高度属性值&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意：只适用于单行区域&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;字体族&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;属性名：font-family&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;属性值：字体名&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;font-family: 楷体;&lt;/p&gt;
&lt;p&gt;font-family: Microsoft YaHlei, Heiti SC, tahoma, arial, Hiragino Sans GB, “\5B8B\4F53”, &lt;strong&gt;sans-serif&lt;/strong&gt;;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;拓展（了解）：font-family属性值可以书写多个字体名，各个字体名用逗号隔开，执行顺序是从左向右依次查找&lt;/li&gt;
&lt;li&gt;font-family 属性最后设置一个字体族名，网页开发建议使用无衬线字体&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;font 复合属性&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;使用场景：设置网页文字公共样式&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;复合属性：属性的简写方式，一个属性对应多个值的写法，各个属性值之间用空格隔开。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;font：是否倾斜 是否加粗 字号/行高 字体族（必须按顺序书写)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;div{&lt;br /&gt;
​ font: italic 700 30px/2 楷体;&lt;br /&gt;
}&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;字号和字体值必须书写，否则font属性不生效&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;文本缩进&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;属性名：text-indent&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;属性值&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;数字+px&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数字+em（推荐：1 em = 当前标签的字号大小）&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;文本对齐&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;作用：控制内容水平对齐方式&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;属性名：text-align&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;属性值&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性值&lt;/th&gt;
&lt;th&gt;效果&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;left&lt;/td&gt;
&lt;td&gt;左对齐（默认）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;center&lt;/td&gt;
&lt;td&gt;居中对齐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;right&lt;/td&gt;
&lt;td&gt;右对齐&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;修饰线&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;属性名：text-decoration&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;属性值&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性值&lt;/th&gt;
&lt;th&gt;效果&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;none&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;无&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;underline&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;下划线&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;line-through&lt;/td&gt;
&lt;td&gt;删除线&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;overline&lt;/td&gt;
&lt;td&gt;上划线&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;颜色&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;属性名：color&lt;/li&gt;
&lt;li&gt;属性值&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;颜色表示方式&lt;/th&gt;
&lt;th&gt;属性值&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;th&gt;使用场景&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;颜色关键字&lt;/td&gt;
&lt;td&gt;颜色英文单词&lt;/td&gt;
&lt;td&gt;red、green、blue…&lt;/td&gt;
&lt;td&gt;学习测试&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rgb表示法&lt;/td&gt;
&lt;td&gt;rgb(r, g, b)&lt;/td&gt;
&lt;td&gt;r，g，b表示红绿蓝三原色，取值：0~255&lt;/td&gt;
&lt;td&gt;了解&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rgba表示法&lt;/td&gt;
&lt;td&gt;rgba(r, g, b, a)&lt;/td&gt;
&lt;td&gt;a表示透明度，取值：0~1&lt;/td&gt;
&lt;td&gt;开发使用，实现透明色&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;十六进制表示法&lt;/td&gt;
&lt;td&gt;#RRGGBB&lt;/td&gt;
&lt;td&gt;#000000，#ffcc00，简写：#000，#fc0&lt;/td&gt;
&lt;td&gt;开发使用（从设计稿复制）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;复合选择器&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;定义：由两个或多个基础选择器，通过不同的方式组合而成。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;作用：更准确、更高效的选择目标元素（标签）。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;后代选择器&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;后代选择器：选中某元素的后代元素。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;选择器写法：父选择器 子选择器 {CSS属性} 父子选择器之间用空格隔开&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;style&amp;gt;
	div span {
		color: red;
	}
&amp;lt;style&amp;gt;

&amp;lt;span&amp;gt;span标签&amp;lt;/span&amp;gt;
&amp;lt;div&amp;gt;
	&amp;lt;span&amp;gt;这是div的儿子&amp;lt;/span&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;子代选择器&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;子代选择器：选中某元素的子代元素。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;选择器写法：父选择器 &amp;gt; 子选择器 {CSS属性} 父子选择器之间用 &amp;gt; 隔开&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;style&amp;gt;
	div &amp;gt; span {
		color: red;
	}
&amp;lt;style&amp;gt;

&amp;lt;div&amp;gt;
	&amp;lt;span&amp;gt;这是div里面的&amp;lt;/span&amp;gt;
	&amp;lt;p&amp;gt;
		&amp;lt;span&amp;gt;这是div里面的p里面的&amp;lt;/span&amp;gt;
	&amp;lt;/p&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;并集选择器&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;并集选择器：选中多组标签设置相同的样式。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;选择器写法：选择器1，选择器2，…，选择器n {CSS属性}，选择器之间用逗号隔开&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;style&amp;gt;
	div,
	p,
	span {
		color: red;
	}
&amp;lt;style&amp;gt;

&amp;lt;div&amp;gt;div标签&amp;lt;/div&amp;gt;
&amp;lt;p&amp;gt;p标签&amp;lt;/p&amp;gt;
&amp;lt;span&amp;gt;span标签&amp;lt;/span&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;交集选择器—-了解&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;交集选择器：选中同时满足多个条件的元素。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;选择器写法：选择器1选择器2 {CSS属性}，选择器之间连写，没有任何符号&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意：如果交集选择器中有标签选择器，标签选择器必须书写在最前面&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;style&amp;gt;
	p.box {
		color: red;
	}
&amp;lt;style&amp;gt;

&amp;lt;p class=&quot;box&quot;&amp;gt;p标签，使用了类选择器 box&amp;lt;/p&amp;gt;
&amp;lt;p&amp;gt;p标签&amp;lt;/p&amp;gt;
&amp;lt;div class=&quot;box&quot;&amp;gt;div标签，使用了类选择器 box&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;伪类选择器&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;伪类选择器：伪类表示元素状态，选中元素的某个状态设置样式。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;鼠标悬停状态：选择器:hover {SSS属性}&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;style&amp;gt;
	a:hover {
		color: red;
	}

	.box:hover {
		color: green;
	}
&amp;lt;/style&amp;gt;

&amp;lt;a href=&quot;#&quot;&amp;gt;a标签&amp;lt;/a&amp;gt;
&amp;lt;div&amp;gt; class=&quot;box&quot;&amp;gt;div 标签&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;伪类：超链接&lt;/h3&gt;
&lt;p&gt;超链接一共有四个状态&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;选择器&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;:link&lt;/td&gt;
&lt;td&gt;访问前&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;:visited&lt;/td&gt;
&lt;td&gt;访问后&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;:hover&lt;/td&gt;
&lt;td&gt;鼠标悬停&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;:active&lt;/td&gt;
&lt;td&gt;点击时: 激活&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;提示：如果要给超链接设置以上四个状态，需要按 LVHA 的顺序书写。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;CSS 特性&lt;/h2&gt;
&lt;p&gt;CSS 特性：化简代码 / 定位问题，并解决问题&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;继承性&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;层叠性&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优先级&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;继承性&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;继承性：子级标签默认继承父级的文字控制属性&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意：若标签自己有属性则不继承&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;层叠性&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;特点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;相同的属性会覆盖：后面的 CSS 属性覆盖前面的 CSS 属性&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不同的属性会叠加：不同的 CSS 属性都生效&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;优先级&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;优先级：也叫权重，当一个标签使用了多种选择器时，基于不同种类的选择器的匹配规则。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;规则：选择器优先级高的样式生效。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;公式：通配符选择器 &amp;lt; 标签选择器 &amp;lt; 类选择器 &amp;lt; id 选择器 &amp;lt; 行内样式 &amp;lt; !Important&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;(选中标签的范围越大，优先级越低)&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;优先级 - 叠加计算原则&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;叠加计算：如果是复合选择器，则需要权重叠加计算。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;公式：（每一级之间不存在进位）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;（行内样式,，id 选择器个数，类选择器个数，标签选择器个数）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;规则：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;从左向右依次比较选个数，同一级个数多的优先级高，如果个数相同，则向后比较&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;!Important 权重最高&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;继承权重最低&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Emmet 写法&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Emmet 写法: 代码的简写方式，输入缩写 VS code 会自动生成对应的代码。&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HTML&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;table&amp;gt;
&amp;lt;thead&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;th&amp;gt;说明&amp;lt;/th&amp;gt;
&amp;lt;th&amp;gt;标签结构&amp;lt;/th&amp;gt;
&amp;lt;th&amp;gt;Emmet&amp;lt;/th&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/thead&amp;gt;
&amp;lt;tbody&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td&amp;gt;类选择器&amp;lt;/td&amp;gt;
&amp;lt;td&amp;gt;&amp;lt;div class=&quot;box&quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td&amp;gt;标签名.类名&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td&amp;gt;id 选择器&amp;lt;/td&amp;gt;
&amp;lt;td&amp;gt;&amp;lt;div id=&quot;box&quot;&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td&amp;gt;标签名#id名&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td&amp;gt;同级标签&amp;lt;/td&amp;gt;
&amp;lt;td&amp;gt;&amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;p&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td&amp;gt;div+p&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td&amp;gt;父子级标签&amp;lt;/td&amp;gt;
&amp;lt;td&amp;gt;&amp;lt;div&amp;gt;&amp;lt;p&amp;gt;&amp;lt;/p&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td&amp;gt;div&amp;gt;p&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td&amp;gt;多个相同标签&amp;lt;/td&amp;gt;
&amp;lt;td&amp;gt;&amp;lt;span&amp;gt;1&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;2&amp;lt;/span&amp;gt;&amp;lt;span&amp;gt;3&amp;lt;/span&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td&amp;gt;span*3&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td&amp;gt;有内容的标签&amp;lt;/td&amp;gt;
&amp;lt;td&amp;gt;&amp;lt;div&amp;gt;内容&amp;lt;/div&amp;gt;&amp;lt;/td&amp;gt;
&amp;lt;td&amp;gt;div{内容}&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/tbody&amp;gt;
&amp;lt;/table&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CSS&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;首字母写法&lt;/p&gt;
&lt;h2&gt;背景属性&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;th&gt;属性&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;背景色&lt;/td&gt;
&lt;td&gt;background-color&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;背景图&lt;/td&gt;
&lt;td&gt;background-image&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;背景图平铺方式&lt;/td&gt;
&lt;td&gt;background-repeat&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;背景图位置&lt;/td&gt;
&lt;td&gt;background-position&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;背景图缩放&lt;/td&gt;
&lt;td&gt;background-size&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;背景图固定&lt;/td&gt;
&lt;td&gt;background-attachment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;背景复合属性&lt;/td&gt;
&lt;td&gt;background&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;背景图&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;网页中，使用背景图实现装饰性的图片效果。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性名： background-image (bgi)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性值：url (背景图 URL)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意：背景图默认平铺效果&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;背景图平铺方式&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;属性名：background-repeat (bgr)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性值：&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性值&lt;/th&gt;
&lt;th&gt;效果&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;no-repeat&lt;/td&gt;
&lt;td&gt;不平铺，盒子左上角显示一张背景图&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;repeat&lt;/td&gt;
&lt;td&gt;平铺，默认效果&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;repeat-x&lt;/td&gt;
&lt;td&gt;水平方向平铺&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;repeat-y&lt;/td&gt;
&lt;td&gt;垂直方向平铺&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;背景图位置&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;属性名：background-position (bgp)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性值：水平方向位置 （正数右移，负数左移），垂直方向位置（正数下移，负数上移）&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;关键字&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;关键字&lt;/th&gt;
&lt;th&gt;位置&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;left&lt;/td&gt;
&lt;td&gt;左侧&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;right&lt;/td&gt;
&lt;td&gt;右侧&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;center&lt;/td&gt;
&lt;td&gt;居中&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;top&lt;/td&gt;
&lt;td&gt;顶部&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bottom&lt;/td&gt;
&lt;td&gt;底部&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;坐标（数字+px，正负都可以）&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;示例代码：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;div{
	width: 400px;
	height: 400px;
	background-color: pink;
	background-image: url(./image/pic.jpg);
	background-repeat: no-repeat;

	background-position: center bottom;
	background-position: 50px -100px
	background-position: 50px center;

	background-attachedment: fixed;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;提示：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;关键字取值方式写法，可以颠倒取值顺序&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;可以只写一个关键字，另一个方向默认为居中数字只写一个值表示水平方向，垂直方向为居中&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;背景图缩放&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;属性名：background-size (bgz)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;常用属性值：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;关键字：&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;cover：等比例缩放背景图片以完全覆盖背景区，可能背景图片部分看不见&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;contain：等比例缩放背景图片以完全装入背景区，可能背景区部分空白&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;百分比：根据盒子尺寸计算图片大小（100%图片的宽度跟盒子宽度一样，图片的高度按照图片比例等比缩放）&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数字+单位（例如：px）（较少用）&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;背景图固定&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;属性名：background-attachment (bga)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性值：fixed&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;背景复合属性&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;属性名：background（bg）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性值：背景色 背景图 背景图平铺方式 背景图位置/背景图缩放 背景图固定&lt;br /&gt;
（空格隔开各个属性值，不区分顺序)&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;显示模式&lt;/h2&gt;
&lt;p&gt;显示模式：标签（元素）的显示方式。&lt;/p&gt;
&lt;p&gt;作用：布局网页的时候，根据标签的显示模式选择合适的标签摆放内容。&lt;/p&gt;
&lt;h3&gt;块级元素（例如 div）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;独占&lt;/strong&gt;一行&lt;/li&gt;
&lt;li&gt;宽度默认是父级的 &lt;strong&gt;100%&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;添加&lt;strong&gt;宽高&lt;/strong&gt;属性生效&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;行内元素（例如 span）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;一行&lt;/strong&gt;可以显示&lt;strong&gt;多个&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;设置&lt;strong&gt;宽高&lt;/strong&gt;属性&lt;strong&gt;不生效&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;宽高尺寸由&lt;strong&gt;内容撑开&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;vertical-align 行内块和行内垂直方向对齐方式&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;行内块元素（例如 img）&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;一行&lt;/strong&gt;可以显示&lt;strong&gt;多个&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;设置&lt;strong&gt;宽高&lt;/strong&gt;尺寸&lt;strong&gt;生效&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;宽高尺寸默认由&lt;strong&gt;内容撑开&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;vertical-align 行内块和行内垂直方向对齐方式&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;转换显示模式&lt;/h3&gt;
&lt;p&gt;属性名：display&lt;/p&gt;
&lt;p&gt;属性值：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性值&lt;/th&gt;
&lt;th&gt;效果&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;block&lt;/td&gt;
&lt;td&gt;块级&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;inline-block&lt;/td&gt;
&lt;td&gt;行内块&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;inline&lt;/td&gt;
&lt;td&gt;行内&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;选择器&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;E: first-child&lt;/td&gt;
&lt;td&gt;查找第一个 E 元素&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E: last-child&lt;/td&gt;
&lt;td&gt;查找最后一个 E 元素&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E: nth-child (N)&lt;/td&gt;
&lt;td&gt;查找第 N 个 E 元素（第一个元素的 N 值为 1）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;: nth-child (公式)&lt;/h3&gt;
&lt;p&gt;作用：根据元素结构关系查找多个元素&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;功能&lt;/th&gt;
&lt;th&gt;公式&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;偶数标签&lt;/td&gt;
&lt;td&gt;2n&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;奇数标签&lt;/td&gt;
&lt;td&gt;2n+1; 2n-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;找到 5 的倍数的标签&lt;/td&gt;
&lt;td&gt;5n&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;找到第 5 个以后的标签&lt;/td&gt;
&lt;td&gt;n+5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;找到第 5 个以前的标签&lt;/td&gt;
&lt;td&gt;-n+5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;伪元素选择器&lt;/h3&gt;
&lt;p&gt;作用：创建&lt;strong&gt;虚拟&lt;/strong&gt;元素（伪元素），用来摆放&lt;strong&gt;装饰性&lt;/strong&gt;的内容。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;选择器&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;E::before&lt;/td&gt;
&lt;td&gt;在 E 元素&lt;strong&gt;里面最前面&lt;/strong&gt;添加一个伪元素&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E::after&lt;/td&gt;
&lt;td&gt;在 E 元素&lt;strong&gt;里面最后面&lt;/strong&gt;添加一个伪元素&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;注意点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;必须&lt;/strong&gt;设置 &lt;strong&gt;content: “ “&lt;/strong&gt; 属性用来&lt;strong&gt;设置伪元素的内容&lt;/strong&gt;，如果没有内容，则引号&lt;strong&gt;留空&lt;/strong&gt;即可&lt;/li&gt;
&lt;li&gt;伪元素默认是&lt;strong&gt;行内&lt;/strong&gt;显示模式&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;权重&lt;/strong&gt;和&lt;strong&gt;标签选择器&lt;/strong&gt;相同&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;PxCook 软件&lt;/h2&gt;
&lt;p&gt;PxCook（&lt;strong&gt;像素大厨&lt;/strong&gt;）是一款切图设计工具&lt;strong&gt;软件&lt;/strong&gt;。支持 &lt;strong&gt;PSD&lt;/strong&gt; 文件的文字、颜色、距离&lt;strong&gt;自动智能识别&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;开发面板&lt;/strong&gt;（自动智能识别）&lt;/li&gt;
&lt;li&gt;设计面板（手动测量尺寸和颜色）&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;盒子模型&lt;/h2&gt;
&lt;p&gt;作用：布局网页，摆放盒子和内容&lt;/p&gt;
&lt;h3&gt;盒子模型 - 组成&lt;/h3&gt;
&lt;p&gt;盒子模型的重要组成部分：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;内容区域 - wedth &amp;amp; height&lt;/li&gt;
&lt;li&gt;内边距 - padding （出现在内容和盒子边距之间）&lt;/li&gt;
&lt;li&gt;边框线 - border&lt;/li&gt;
&lt;li&gt;外边距 - margin （出现在盒子外面，常用来拉开两个盒子之间的距离）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;盒子模型 - 边框线&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;属性名：border（bd）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性值：边框线粗细 线条样式 颜色 （不区分顺序）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;常用线条样式：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性值&lt;/th&gt;
&lt;th&gt;线条样式&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;solid&lt;/td&gt;
&lt;td&gt;实线&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dashed&lt;/td&gt;
&lt;td&gt;虚线&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dotted&lt;/td&gt;
&lt;td&gt;点线&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;盒子模型 - 设置单方向边框线&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;属性名：border-方位名词 (bd+方位首字母，例如 bdl)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性值：边框线粗细 线条样式 颜色 （不区分顺序）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;样例代码&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;div{
	border-top: 2px solid red;
	border-right: 3px dashed green;
	border-bottom: 4px dotted blue;
	border-left: 5px solid orange;

	width: 200px;
	height: 200px;
	background-color: pink;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;盒子模型 - 内边距&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;作用：设置 内容 与 盒子边缘 之间的距离&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性名：padding / padding+方位名词（p+方位首字母）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;样例代码：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;div{
	padding-top: 2px solid red;
	padding-right: 3px dashed green;
	padding-bottom: 4px dotted blue;
	padding-left: 5px solid orange;

	width: 200px;
	height: 200px;
	background-color: pink;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;盒子模型 - 内边距 - 多值写法&lt;/h3&gt;
&lt;p&gt;Padding 多值写法&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;取值个数&lt;/th&gt;
&lt;th&gt;示例&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;一个值&lt;/td&gt;
&lt;td&gt;padding: 10px&lt;/td&gt;
&lt;td&gt;四个方向内边距均为 10 px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;四个值&lt;/td&gt;
&lt;td&gt;padding: 10px 20px 30px 40px&lt;/td&gt;
&lt;td&gt;上：10 px；右：20 px；下：30 px；左：40 px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;三个值&lt;/td&gt;
&lt;td&gt;padding: 10px 20px 30px&lt;/td&gt;
&lt;td&gt;上：10 px；左右：20 px；下：30 px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;两个值&lt;/td&gt;
&lt;td&gt;padding: 10px 20px&lt;/td&gt;
&lt;td&gt;上下：10 px；左右：20 px&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;==记忆方法：上右下左，没数看对面==&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;盒子模型 - 尺寸计算&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;默认情况&lt;/strong&gt;：&lt;strong&gt;盒子尺寸 = 内容尺寸 + border 尺寸 + 内边距尺寸&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;结论：给盒子加 border / padding 会撑大盒子&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;手动做减法，减掉 border / padding 的尺寸&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;內减模式：box-sizing: border-box&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;盒子模型 - 外边距&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;作用：拉开两个盒子之间的距离&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性名：margin&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;提示：与 padding 的属性值、含义相同&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意：外边距不会撑大盒子&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;版心居中&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;div {
	margin: 0px auto;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;浏览器计算宽度先减去盒子宽度，此时左右都是 auto，即为水平居中，因此做到了自适应版心居中&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;清除默认样式&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;清除标签默认的样式，比如：默认的内外边距&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;两种做法：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;京东&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;* {
	margin: 0;
	padding: 0;
	box-sizing: border-box;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;淘宝&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;blockquote, body, button, dd, dl, dt, fieldset, form, h1, h2, h3, h4, h5, h6, hr, input, legend, li, ol, p, pre, td,, extarea, th, ul {
	margin: 0;
	padding: 0;
	box-sizing: border-box;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意：去掉列表的项目符号属性 list-style: none;&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;盒子模型 - 元素溢出&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;作用：控制溢出元素的内容的显示方式。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性名：overflow&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性值：&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性值&lt;/th&gt;
&lt;th&gt;效果&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;hidden&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;溢出隐藏&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;scroll&lt;/td&gt;
&lt;td&gt;溢出滚动（无论是否溢出，都显示滚动条位置）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;auto&lt;/td&gt;
&lt;td&gt;溢出滚动（溢出才显示滚动条位置）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;外边距问题 - 合并现象&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;场景：垂直排列的兄弟元素，上下 margin 会合并&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;现象：取两个 margin 中的较大值生效&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;外边距问题 - 塌陷问题&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;场景：父子级的标签，子级的添加 上外边距 会产生塌陷问题&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;现象：导致父级一起向下移动&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决方法:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;取消子级 margin，父级设置 padding（推荐）&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;父级设置 overflow: hidden&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;父级设置 border-top&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;行内元素 - 内外边距问题&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;场景：行内元素添加 margin 和 padding，无法改变元素垂直位置&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决方法：给行内元素添加 line-height 可以改变垂直位置&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;盒子模型 - 圆角&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;作用：设置元素的外边框为圆角。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性名：border-radius&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性值：数字+px / 百分比&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;提示：属性值是圆角半径&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意：也存在多值写法，记忆方法：从左上角顺时针赋值，没有取值的角与对角取值相同&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;常用的圆角形状&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;常见形状 - 正圆形状&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;给正方形盒子设置圆角属性值为宽高的一半 / 50%（超过 50 没有效果）&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;常见形状 - 胶囊形状&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;给长方形盒子设置圆角属性值为盒子高度的一半&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;盒子模型 - 阴影（拓展）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;作用：给元素设置阴影效果&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性名：box-shadow&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性值：X 轴偏移量 Y 轴偏移量 模糊半径 扩散半径 颜色 内外阴影&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;X 轴偏移量和 Y 轴偏移量必须书写&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;默认是外阴影，内阴影需要添加 inset&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;设计顺序&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;书写顺序&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;1. 盒子模型属性&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2. 文字样式&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;3. 圆角，阴影等属性&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;浮动&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;作用：让块元素水平排列。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性名：float&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性值&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;left：左对齐&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;right：右对齐&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;浮动后的盒子特点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;顶对齐&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;具备行内块特点&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;脱标&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;注意：一般放在 css 属性的最前面&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;清除浮动&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;场景：浮动元素会脱标，如果父级没有高度，子级无法撑开父级高度（可能导致页面布局错乱）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解决方法：清除浮动（清除浮动带来的影响）&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;方法一 ：额外标签法&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;在父元素内容的最后忝加一个块级元素（一般类名为 clearfix），设置 CSS 属性 clear: both&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;方法二：单伪元素法&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;.clearfix::after {
	content: &quot;&quot;;
	display: block;
	clear: both;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;方法三：双伪元素法 （推荐）&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!-- brfore 伪元素是为了解决塌陷的 after是清除浮动影响的 --&amp;gt;
.clearfix::before,
.clearfix::after {
	content: &quot;&quot;;
	display: table;
}

.clearfix::after {
	clear: both;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;方法四：overflow&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;父元素添加 CSS 属性 overflow: hidden&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;总结&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;浮动属性 float，left 表示左浮动，right 表示右浮动&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特点：&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;浮动后的盒子&lt;strong&gt;顶对齐&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;浮动后的盒子具备&lt;strong&gt;行内块&lt;/strong&gt;特点&lt;/li&gt;
&lt;li&gt;父级&lt;strong&gt;宽度不够&lt;/strong&gt;，浮动的子级会&lt;strong&gt;换行&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;浮动后的盒子&lt;strong&gt;脱标&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;清除浮动：子级浮动，父级没有高度，子级无法撑开父级高度，影响布局效果&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;双伪元素法&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;拓展：浮动本质作用是实现图文混排效果&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Flex 布局&lt;/h2&gt;
&lt;h3&gt;Flex 布局 - 简介&lt;/h3&gt;
&lt;p&gt;Flex布局也叫弹性布局，是&lt;strong&gt;浏览器提倡&lt;/strong&gt;的布局模型，非常适合结构化布局，提供了强大的&lt;strong&gt;空间分布&lt;/strong&gt;和&lt;strong&gt;对齐&lt;/strong&gt;能力。&lt;/p&gt;
&lt;p&gt;Flex模型&lt;strong&gt;不会&lt;/strong&gt;产生浮动布局中&lt;strong&gt;脱标&lt;/strong&gt;现象，布局网页更简单、更灵活。&lt;/p&gt;
&lt;h3&gt;Flex - 组成&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;设置方式&lt;/strong&gt;：给&lt;strong&gt;父元素&lt;/strong&gt;设置 &lt;strong&gt;display: flex&lt;/strong&gt; &lt;strong&gt;子元素&lt;/strong&gt;可以&lt;strong&gt;自动挤压或拉伸&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;组成部分&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;弹性容器&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;弹性盒子&lt;/strong&gt;：沿着主轴方向排列&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;主轴&lt;/strong&gt;：默认在&lt;strong&gt;水平&lt;/strong&gt;方向&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;侧轴&lt;/strong&gt; / 交叉轴：默认在&lt;strong&gt;垂直&lt;/strong&gt;方向&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;th&gt;属性&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;创建 flex 容器&lt;/td&gt;
&lt;td&gt;display: flex&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;主轴对齐方式&lt;/td&gt;
&lt;td&gt;justify-content&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;侧轴对齐方式&lt;/td&gt;
&lt;td&gt;align-items&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;某个弹性盒子侧轴对齐方式&lt;/td&gt;
&lt;td&gt;align-self&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;修改主轴方向&lt;/td&gt;
&lt;td&gt;flex-decoration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;弹性伸缩比&lt;/td&gt;
&lt;td&gt;flex&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;弹性盒子换行&lt;/td&gt;
&lt;td&gt;flex-wrap&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;行对齐方式&lt;/td&gt;
&lt;td&gt;align-content&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Flex - 主轴对齐方式&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;属性名：justify-content&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性值&lt;/th&gt;
&lt;th&gt;效果&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;flex-start&lt;/td&gt;
&lt;td&gt;默认值，弹性盒子从&lt;strong&gt;起点&lt;/strong&gt;开始依次排列&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;flex-end&lt;/td&gt;
&lt;td&gt;弹性盒子从&lt;strong&gt;终点&lt;/strong&gt;开始依次排列&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;center&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;弹性盒子沿主轴&lt;strong&gt;居中排列&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;space-between&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;弹性盒子沿主轴均匀排列，&lt;strong&gt;空白间距均分&lt;/strong&gt;在弹性盒子&lt;strong&gt;之间&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;space-around&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;弹性盒子沿主轴均匀排列，&lt;strong&gt;空白间距均分&lt;/strong&gt;在弹性盒子&lt;strong&gt;两侧&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;space-evenly&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;弹性盒子沿主轴均匀排列，&lt;strong&gt;弹性盒子与容器之间间距相等&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Flex - 侧轴对齐方式&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;属性名：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Align-items&lt;/strong&gt;：当前弹性容器内&lt;strong&gt;所有&lt;/strong&gt;弹性盒子的侧轴对齐方式（给弹性容器设置）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Align-self&lt;/strong&gt;：单独控制&lt;strong&gt;某个&lt;/strong&gt;弹性盒子的侧轴对齐方式（给弹性盒子设置）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;属性值：&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性值&lt;/th&gt;
&lt;th&gt;效果&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;stretch&lt;/td&gt;
&lt;td&gt;弹性盒子沿着恻轴线被&lt;strong&gt;拉伸至铺满容器&lt;/strong&gt;（弹性盒子没有设置侧轴方向尺寸则默认拉伸）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;center&lt;/td&gt;
&lt;td&gt;弹性盒子沿侧轴&lt;strong&gt;居中&lt;/strong&gt;排列&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;flex-start&lt;/td&gt;
&lt;td&gt;弹性盒子从&lt;strong&gt;起点&lt;/strong&gt;开始依次排列&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;flex-end&lt;/td&gt;
&lt;td&gt;弹性盒子从&lt;strong&gt;终点&lt;/strong&gt;开始依次排列&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Flex - 修改主轴方向&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;主轴默认在水平方向，侧轴默认在垂直方向。主轴方向变换，侧轴自动变换。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性名：flex-decoration&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性值：&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性值&lt;/th&gt;
&lt;th&gt;效果&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;row&lt;/td&gt;
&lt;td&gt;水平方向，从左向右（默认）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;column&lt;/td&gt;
&lt;td&gt;垂直方向，从上向下&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;row-reverse&lt;/td&gt;
&lt;td&gt;水平方向，从右向左&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;column-reverse&lt;/td&gt;
&lt;td&gt;垂直方向，从下向上&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Flex - 弹性伸缩比&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;作用：控制弹性盒子在主轴方向的尺寸。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性名：flex&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性值：整数数字，表示占用父级剩余尺寸的份数。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;Flex - 弹性盒子换行&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;弹性盒子可以自动挤压或拉伸，默认情况下，所有弹性盒子都在一行显示。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性名：flex-wrap&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性值：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Wrap：换行&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nowrap：不换行（默认）&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Flex - 行对齐方式&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;属性名：align-content&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性值：&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性值&lt;/th&gt;
&lt;th&gt;效果&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;flex-start&lt;/td&gt;
&lt;td&gt;默认值，弹性盒子从&lt;strong&gt;起点&lt;/strong&gt;开始依次排列&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;flex-end&lt;/td&gt;
&lt;td&gt;弹性盒子从&lt;strong&gt;终点&lt;/strong&gt;开始依次排列&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;center&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;弹性盒子沿主轴&lt;strong&gt;居中排列&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;space-between&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;弹性盒子沿主轴均匀排列，&lt;strong&gt;空白间距均分&lt;/strong&gt;在弹性盒子&lt;strong&gt;之间&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;space-around&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;弹性盒子沿主轴均匀排列，&lt;strong&gt;空白间距均分&lt;/strong&gt;在弹性盒子&lt;strong&gt;两侧&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;space-evenly&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;弹性盒子沿主轴均匀排列，&lt;strong&gt;弹性盒子与容器之间间距相等&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;注意：该属性对单行弹性盒子不生效&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;注意：&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;默认情况下，主轴方向尺寸是靠内容撑开，侧轴默认拉伸&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ul&amp;gt;li{$}*4可以快速生成&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;定位&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;作用：灵活的改变盒子在网页中的位置&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;实现：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;定位模式：position&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;边偏移：设置盒子的位置&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;left&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;right&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;top&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bottom&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;定位模式：相对定位&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;属性值：position: relative&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;特点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;改变位置的参照物是自己原来的位置&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不脱标，占位&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;标签的显示模式特点：不变&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;定位模式：绝对定位&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;属性值：position: absolute&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;使用场景&lt;/strong&gt;：&lt;strong&gt;子&lt;/strong&gt;级&lt;strong&gt;绝&lt;/strong&gt;对定位，&lt;strong&gt;父&lt;/strong&gt;级&lt;strong&gt;相&lt;/strong&gt;对定位（&lt;strong&gt;子绝父相&lt;/strong&gt;）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;特点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;参照物：先找最近的已经定位的祖先元素，如果所有祖先元素都没有定位，参照浏览器可视区改位置&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;脱标，不占位&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;标签的显示模式特点：改变，宽高生效（具备了行内块特点）&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;定位居中&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;实现步骤：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;绝对定位&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;水平、垂直边偏移为 &lt;strong&gt;50%&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;子&lt;/strong&gt;级向左、上移动&lt;strong&gt;自身尺寸&lt;/strong&gt;的一半&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;左、上的外边距为 - 尺寸的一半&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;transtform: translate(-)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;固定定位&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;属性值：position: fixed&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;场景：元素的位置在网页滚动时不会改变&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;特点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;参照物：浏览器窗口&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;脱标，不占位&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;标签的显示模式：改变，宽高生效（具备了行内块特点）&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;堆叠层级&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;默认效果：按照标签书写顺序，后来者居上&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;作用：设置定位元素的层级顺序，改变定位元素的显示顺序，取值越大显示顺序越靠上&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;总结&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;定位模式&lt;/th&gt;
&lt;th&gt;属性值&lt;/th&gt;
&lt;th&gt;是否脱标&lt;/th&gt;
&lt;th&gt;显示模式&lt;/th&gt;
&lt;th&gt;参照物&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;相对定位&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;relative&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;否&lt;/td&gt;
&lt;td&gt;保持标签&lt;strong&gt;原有&lt;/strong&gt;显示模式&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;自己原来&lt;/strong&gt;位置&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;绝对定位&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;absolute&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;是&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;行内块&lt;/strong&gt;特点&lt;/td&gt;
&lt;td&gt;1.&lt;strong&gt;已经定位&lt;/strong&gt;的&lt;strong&gt;祖先&lt;/strong&gt;元素  &amp;lt;br&amp;gt;2.浏览器可视区&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;固定定位&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;fixed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;是&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;行内块&lt;/strong&gt;特点&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;浏览器窗口&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;CSS 精灵&lt;/h2&gt;
&lt;p&gt;CSS 精灵，也叫 &lt;strong&gt;CSS Sprites&lt;/strong&gt;，是一种网页&lt;strong&gt;图片&lt;/strong&gt;应用处理方式。把网页中一些&lt;strong&gt;背景图片整合到一张图片&lt;/strong&gt;文件，再 &lt;strong&gt;background-position&lt;/strong&gt; 精确的定位出背景图片的&lt;strong&gt;位置&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优点&lt;/strong&gt;：&lt;strong&gt;减少&lt;/strong&gt;服务器被请求&lt;strong&gt;次数&lt;/strong&gt;，&lt;strong&gt;减轻&lt;/strong&gt;服务器的&lt;strong&gt;压力&lt;/strong&gt;，&lt;strong&gt;提高&lt;/strong&gt;页面加载&lt;strong&gt;速度&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;实现步骤：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;创建盒子，&lt;strong&gt;盒子尺寸&lt;/strong&gt;与&lt;strong&gt;小图尺寸相同&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;设置盒子&lt;strong&gt;背景图&lt;/strong&gt;为精灵图&lt;/li&gt;
&lt;li&gt;添加 &lt;strong&gt;background-position&lt;/strong&gt; 属性，改变背景图位置
3.1 是用 PxCook 测量
3.2 取&lt;strong&gt;负数&lt;/strong&gt;坐标为 &lt;strong&gt;background-position&lt;/strong&gt; 属性值（向左上移动图片位置）&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;字体图标&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;字体图标&lt;/strong&gt;：&lt;strong&gt;展示的是图标，本质是字体&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;作用&lt;/strong&gt;：在网页中添加&lt;strong&gt;简单的&lt;/strong&gt;、颜色单一的小图标&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;灵活性&lt;/strong&gt;：灵活地修改样式，例如：尺寸、颜色等&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;轻量级&lt;/strong&gt;：体积小、渲染快、降低服务器请求次数&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;兼容性&lt;/strong&gt;：几乎兼容所有主流浏览器&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;使用方便&lt;/strong&gt;：先下载再使用&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;字体图标 - 下载字体&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;iconfont 图标库：&lt;a href=&quot;https://www.iconfont.cn/&quot;&gt;https://www.iconfont.cn/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;下载字体：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;登录 → 素材库 → 官方图标库 → 进入图标库 → 选图标，加入购物车 → 购物车，添加至项目，确定 → 下载至本地&lt;/p&gt;
&lt;h3&gt;字体图标 - 使用字体&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;引入字体样式表（iconfont.css）&lt;/li&gt;
&lt;li&gt;标签使用字体图标类名&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;iconfont&lt;/strong&gt;：字体图标基本样式（字体名，大小等等）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;icon-xxx&lt;/strong&gt;：图标对应的类名&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;样例代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
    &amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&amp;gt;
    &amp;lt;title&amp;gt;test&amp;lt;/title&amp;gt;
    &amp;lt;link rel=&quot;stylesheet&quot; href=&quot;./iconfont/iconfont.css&quot;&amp;gt;
    &amp;lt;style&amp;gt;
        /* 字体样式 */
    &amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
    &amp;lt;span class=&quot;iconfont icon-xxx&quot;&amp;gt;&amp;lt;/span&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：如果要调整字体大小，注意选择器的优先级要高于iconfont类&lt;/p&gt;
&lt;h3&gt;字体图标 - 上传矢量库&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;作用&lt;/strong&gt;：项目特有的图标上传到iconfont图标库，生成字体&lt;/p&gt;
&lt;h2&gt;垂直对齐方式 - vertical-align&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;属性名：vertical-align&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性值：&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;屬性值&lt;/th&gt;
&lt;th&gt;效果&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;baseline&lt;/td&gt;
&lt;td&gt;基线对齐（默认）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;top&lt;/td&gt;
&lt;td&gt;顶部对齐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;middle&lt;/td&gt;
&lt;td&gt;居中对齐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bottom&lt;/td&gt;
&lt;td&gt;底部对齐&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;注意：行内块和行内标签当成文字处理，默认基线对齐导致基线下有空白，除去 baseline 其余三种对其方式无空白，行内块转为块显示模式也无空白&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;过渡属性 - transition&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;作用：可以为一个元素在不同状态之间切换的时候添加过渡效果&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性名：transition（复合属性）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性值：过渡的属性 花费时间（s）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;提示：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;过渡的属性可以是&lt;strong&gt;具体的 CSS 属性&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;也可以为 &lt;strong&gt;all&lt;/strong&gt;（两个状态&lt;strong&gt;属性值不同&lt;/strong&gt;的所有属性，都产生过渡效果）&lt;/li&gt;
&lt;li&gt;transition 设置给&lt;strong&gt;元素本身&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;透明度 - opacity&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;作用&lt;/strong&gt;：设置&lt;strong&gt;整个元素的透明度&lt;/strong&gt;（包含背景和内容）&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性名：opacity&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性值&lt;/strong&gt;：0 - 1&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;0：&lt;strong&gt;完全透明&lt;/strong&gt;（元素不可见）&lt;/li&gt;
&lt;li&gt;1：&lt;strong&gt;不透明&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;0 - 1 之间小数：&lt;strong&gt;半透明&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;光标类型 - cursor&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;作用：鼠标悬停在元素上时指针显示样式&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性名：cursor&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性值：&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性值&lt;/th&gt;
&lt;th&gt;效果&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;default&lt;/td&gt;
&lt;td&gt;默认值，通常是箭头&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pointer&lt;/td&gt;
&lt;td&gt;小手效果，提示用户可以点击&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;工字型，提示用户可以选择文字&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;move&lt;/td&gt;
&lt;td&gt;十字光标，提示用户可以移动&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;平面转换 transform&lt;/h2&gt;
&lt;p&gt;作用：为元素&lt;strong&gt;添加动态效果&lt;/strong&gt;，一般与&lt;strong&gt;过渡&lt;/strong&gt;配合使用&lt;/p&gt;
&lt;p&gt;概念：改变盒子在&lt;strong&gt;平面&lt;/strong&gt;内的&lt;strong&gt;形态&lt;/strong&gt;（位移、旋转、缩放、倾斜）&lt;/p&gt;
&lt;p&gt;平面转换又叫做 &lt;strong&gt;2D转换&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;平面转换 - 平移&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;属性值：transform: translate(X轴移动距离, Y轴移动距离）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;取值：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;像素单位数值&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;百分比（按照盒子自身尺寸计算结果）&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;正负均可&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;技巧：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;translate（只写一个值，表示沿着 X 轴移动）&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;单独设置 X 或 Y 轴移动距离：translateX() 或 translateY()&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;居中写法：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;写法1&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;position: absolute;
left: 50%;
top: 50%;

/* margin */
margin-left: -100px;
margin-top: -50px;

width: 200px;
height: 100px;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;写法2（参考自身尺寸，方便）&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;osition: absolute;
left: 50%;
top: 50%;
translate: transform(-50%, -50%);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;宽高可以使用百分比，以父级元素为基准&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;平面转换 - 旋转&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;属性值：transform: rotate(旋转角度）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;旋转角度：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;单位：deg&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;有正负（正顺负逆）&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;平面转换 - 改变转换原点&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;默认情况下，转换原点是盒子中心点&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性值：transform-origin: 水平原点位置 垂直原点位置;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;取值&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;方位名词（left、top、right、bottom、center)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;像素单位数值&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;百分比&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;多重转换&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;先平移再旋转&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;旋转会改变坐标轴向&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;多重转换:以第一种转换形态的坐标轴为准&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;拆开写不行，有层叠性，生效最后一个&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;平面转换 - 缩放&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;属性值：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;transform: scale(缩放倍数);&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;transform: scale(X轴缩放倍效, Y轴缩放倍数);&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;技巧&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;通常，只为 scale 设置一个值，表示 X 轴和 Y 轴等比例缩放&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;取值大于1表示放大，取值小于1表示缩小&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;平面转换 - 倾斜&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;属性值：transform: skew();&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;取值：角度度数deg&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;渐变&lt;/h2&gt;
&lt;h3&gt;线性渐变&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;属性值：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;background-image: linear-gradient(
	渐变方向,
	颜色1 终点位置,
	颜色2 终点位置,
	......
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;取值&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;渐变方向（可选）&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;to 方位名词&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;角度度数&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;终点位置（可选）&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;百分比&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;硬线：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;例子1：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;background: linear-gradient(
	to bottom left, 
	cyan 50%, 
	palegoldenrod 50%
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例子2（多条纹硬线）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;background: linear-gradient(
    to left,
    lime 25%,
    red 25%,
    red 50%,
    cyan 50%,
    cyan 75%,
    yellow 75%
);
background: linear-gradient(
    to left,
    lime 25%,
    red 25% 50%,
    cyan 50% 75%,
    yellow 75%
);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;径向渐变&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;属性值：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;background-image: radial-gradient(
	半径 at 圆心位置,
	颜色1 终点位置,
	颜色2 终点位置,
	......
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;取值&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;半径可以是2条，则为椭圆&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;圆心位置取值：像素单位数值 / 百分比 / 方位名词&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;锥形渐变&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;属性值：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;background: conic-gradient(
	from 起始角度 at 变换中心,
	颜色1 终点位置,
	颜色2 终点位置,
	......
);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;空间转换&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;空间：是从坐标轴角度定义的X、Y和Z三条坐标轴构成了一个立体空间，&lt;strong&gt;Z轴位置与视线方向相同&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;空间转换也叫3D转换&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;属性：transform&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;空间转换 - 平移&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;属性值：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;transform: translate3d(x, y, z);
transform: translatex();
transform: translateY();
transform: translatez();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;取值（&lt;strong&gt;正负&lt;/strong&gt;均可）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;像素单位数值&lt;/li&gt;
&lt;li&gt;百分比（&lt;strong&gt;参照盒子自身尺寸&lt;/strong&gt;计算结果）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;注意：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;电脑是平面，默认无法观察z轴平移效果&lt;/li&gt;
&lt;li&gt;translate3d 必须要有三个数字&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;视距&lt;/h3&gt;
&lt;p&gt;作用：指定了&lt;strong&gt;观察者&lt;/strong&gt;与 &lt;strong&gt;Z = 0 平面&lt;/strong&gt;的距离，为元素&lt;strong&gt;添加透视&lt;/strong&gt;效果&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;透视效果：近大远小、近实远虚&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性值&lt;/strong&gt;：(&lt;strong&gt;添加给父级&lt;/strong&gt;，&lt;strong&gt;取值范围800-1200&lt;/strong&gt;）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;perspective: 视距;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;空间转换 - 旋转&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;属性值：transform: rotateZ(值)；&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;属性值：transform: rotateX(值)；&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;属性值：transform: rotateY(值)；&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;左手法则 – 根据旋转方向确定取值正负：左手握住旋转轴，拇指指向正值方向，其他四个手指弯曲方向为旋转正值方向&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;拓展&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;rotate3d(x, y, z, 角度度数)：用来设置自定义旋转轴的位置及旋转的角度&lt;/li&gt;
&lt;li&gt;x， y，z取值为0-1之间的数字&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;立体呈现&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;作用&lt;/strong&gt;：设置元素的子元素是位于 &lt;strong&gt;3d空间&lt;/strong&gt; 中还是 &lt;strong&gt;平面&lt;/strong&gt; 中&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性名：transform-style&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性值：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;flat：子级处于平面中&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;preserve-3d：子级处于3D空间&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;呈现立体图形步骤&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;父元素添加transform-style: preserve-3d;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;子级定位&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;调整子盒子的位置（位移或旋转）&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;空间转换 - 缩放&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;属性值：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;transform: scale3d(x, y, z);
transform: scaleX();
transform: scaleY();
transform: scaleZ();
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;动画 - animation&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;过渡：实现&lt;strong&gt;两个状态&lt;/strong&gt;间的变化过程&lt;/li&gt;
&lt;li&gt;动画：实现&lt;strong&gt;多个状态&lt;/strong&gt;间的变化过程，&lt;strong&gt;动画过程可控&lt;/strong&gt;（重复播放、最终画面、是否暂停）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;实现步骤：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;定义动画&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;@keyframes 动画名称{
    from {}
    to {}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;@keyframes 动画名称{
    0% {}
    10% {}
    ......
    100% {}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;使用动画&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;animation: 动画名称 动画时长 速度曲线 延迟时间 重复次数 动画方向 执行完毕时状态;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;提示：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;动画名称&lt;/strong&gt;和&lt;strong&gt;动画时长&lt;/strong&gt;必须赋值&lt;/li&gt;
&lt;li&gt;取值&lt;strong&gt;不&lt;/strong&gt;分先后顺序&lt;/li&gt;
&lt;li&gt;如果有&lt;strong&gt;两个时间&lt;/strong&gt;值，第一个时间表示&lt;strong&gt;动画时长&lt;/strong&gt;，第二个时间表示&lt;strong&gt;延迟时间&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;属性值：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;th&gt;取值&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;animation-name&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;动画名称&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;animation-duration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;动画时长&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;animation-delay&lt;/td&gt;
&lt;td&gt;延迟时间&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;animation-fill-mode&lt;/td&gt;
&lt;td&gt;执行完毕时状态&lt;/td&gt;
&lt;td&gt;forwards：最后一帧转态  &amp;lt;br&amp;gt;backwards：第一帧状态&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;animation-timing-function&lt;/td&gt;
&lt;td&gt;速度曲线&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;linear：匀速运动  &amp;lt;br&amp;gt;steps(数字)：逐帧动画&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;animation-iteration-count&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;重复次数&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;infinite为无限循环&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;animation-direction&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;动画方向&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;alternate为反向&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;animation-play-state&lt;/td&gt;
&lt;td&gt;暂停动画&lt;/td&gt;
&lt;td&gt;paused为暂停，通常配合 &lt;strong&gt;:hover&lt;/strong&gt; 使用&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;无缝动画原理：复制开头图片到结尾位置（图片累加宽度=区域宽度）&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;多组动画&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;animation:
	动画1,
	动画2,
	动画N
;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意：当动画的开始状态样式跟盒子默认样式相同，可以省略动画开始状态的代码&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;屏幕分辨率&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;硬件分辨率→物理分辨率（出厂设置）&lt;/li&gt;
&lt;li&gt;缩放调节的分辨率→逻辑分辨率（软件/驱动设置）&lt;/li&gt;
&lt;li&gt;通过视口标签规定html尺寸，让html宽度等于设备宽度（默认生成）&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;适配方案&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;宽度适配：宽度自适应
&lt;ul&gt;
&lt;li&gt;百分比布局&lt;/li&gt;
&lt;li&gt;Flex布局&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;等比适配：宽高等比缩放
&lt;ul&gt;
&lt;li&gt;rem&lt;/li&gt;
&lt;li&gt;vw&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;rem适配方案&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;rem单位，是&lt;strong&gt;相对单位&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;rem单位是相对于&lt;strong&gt;HTML标签的字号&lt;/strong&gt;计算结果&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1rem = 1HTML字号大小&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;手机屏幕大小不同，分辨率不同，如何设置不同的HTML标签字号?
&lt;ul&gt;
&lt;li&gt;媒体查询&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;媒体查询能够检测视口的宽度，然后编写差异化的CSS样式当&lt;/li&gt;
&lt;li&gt;某个条件成立，执行对应的CSS样式&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;@media (媒体特性) {
  选择器 {
	css属性
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;目前rem布局方案中，将网页等分成10份，HTML标签的字号为视口宽度的1/10&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;rem - flexible.js&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;flexible.js是手淘开发出的一个用来适配移动端的js库。&lt;/li&gt;
&lt;li&gt;核心原理就是根据不同的视口宽度给网页中html根节点设置不同的 font-size。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;less&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Less是一个&lt;strong&gt;CSS预处理器&lt;/strong&gt;，Less文件后缀是**.less**。扩充了CSS语言,使CSS具备一定的逻辑性、计算能力&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;注意：浏览器不识别Less 代码，目前阶段，网页要引入对应的CSS文件&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;VS Code插件：&lt;strong&gt;Easy LESS&lt;/strong&gt;，保存less文件后&lt;strong&gt;自动生成对应的CSS文件&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;less - 注释&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;单行注释
&lt;ul&gt;
&lt;li&gt;语法：&lt;strong&gt;// 注释内容&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;快捷键：&lt;strong&gt;ctrl + /&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;块注释
&lt;ul&gt;
&lt;li&gt;语法：&lt;strong&gt;/*注释内容*/&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;快捷键：&lt;strong&gt;Shift + Alt + A&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;less - 运算&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;加、减、乘直接书写计算表达式&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;除法需要添加小括号 或 .&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;如果表达式有多个单位，最终css里面以第一个单位为准&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;less - 嵌套&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;作用：快速生成后代选择器&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&amp;amp; 表示的是当前选择器，代码写到谁的大括号里面就表示谁&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;不会生成后代选择器&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;应用：配合hover伪类或nth-child结构伪类使用&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;less - 变量&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;语法&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;定义变量：@变量名:数据;&lt;/li&gt;
&lt;li&gt;使用变量：CSS属性:@变量名;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;less - 导入&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;作用：导入 less 公共样式文件&lt;/li&gt;
&lt;li&gt;语法：导入：&lt;strong&gt;@import “文件路径”;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;提示：如果是&lt;strong&gt;less文件可以省略后缀&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;import &quot;./base.less&apos;;
import &quot;./common&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;less - 导出&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;写法：在less文件的第一行添加 //out:存储URL&lt;/li&gt;
&lt;li&gt;提示：文件夹名称后面添加 /&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// out: ./index.css
// out: ./css/
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;less - 禁止导出&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;写法：在 less文件第一行添加：// out:false&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;vw适配方案&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;相对单位&lt;/li&gt;
&lt;li&gt;相对视口的尺寸计算结果&lt;/li&gt;
&lt;li&gt;vw：viewport width
&lt;ul&gt;
&lt;li&gt;1vw = 1/100 视口宽度&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;vh：viewport height
&lt;ul&gt;
&lt;li&gt;1vh = 1/100 视口高度&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;注意：vw和vh不要混用&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Bootstrap&lt;/h3&gt;
&lt;p&gt;Bootstrap 是由 Twitter 公司开发维护的&lt;strong&gt;前端 UI 框架&lt;/strong&gt;，它提供了大量&lt;strong&gt;编写好的 CSS 样式&lt;/strong&gt;，允许开发者结合一定HTML结构及JavaScript，&lt;strong&gt;快速编写功能完善的网页及常见交互效果&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;中文官网：&lt;a href=&quot;https://www.bootcss.coml&quot;&gt;https://www.bootcss.coml&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;Bootstrap - 使用&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;下载：Bootstrap V5中文文档 → 进入中文文档 → 下载 → 下载 Bootstrap 生产文件&lt;/li&gt;
&lt;li&gt;使用
&lt;ul&gt;
&lt;li&gt;1．引入Bootstrap.css文件&lt;pre&gt;&lt;code&gt;&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;./Bootstrap/css/bootstrap.min.css&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;2.调用类名：container：响应式布局版心类&lt;pre&gt;&lt;code&gt;&amp;lt;div class=&quot;container&quot;&amp;gt;测试&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Bootstrap - 栅格系统&lt;/h4&gt;
&lt;p&gt;栅格化是指将整个网页的宽度分成12等份，每个盒子占用的对应的份数&lt;/p&gt;
&lt;p&gt;例如：一行排4个盒子，则每个盒子占3份即可（12 / 4= 3）&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Breakpoint&lt;/th&gt;
&lt;th&gt;Class infix&lt;/th&gt;
&lt;th&gt;Dimensions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Extra small&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;&amp;lt;576px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Small&lt;/td&gt;
&lt;td&gt;sm&lt;/td&gt;
&lt;td&gt;&amp;gt;=576px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;md&lt;/td&gt;
&lt;td&gt;&amp;gt;=768px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Large&lt;/td&gt;
&lt;td&gt;lg&lt;/td&gt;
&lt;td&gt;&amp;gt;=992px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extra large&lt;/td&gt;
&lt;td&gt;xl&lt;/td&gt;
&lt;td&gt;&amp;gt;=1200px&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extra extra large&lt;/td&gt;
&lt;td&gt;xxl&lt;/td&gt;
&lt;td&gt;&amp;gt;=1400px&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;常用布局类&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;col-*-*：列（例如：col-xxl-3）&lt;/li&gt;
&lt;li&gt;row：行&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Bootstrap - 全局样式&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Button类&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;btn：默认样式&lt;/li&gt;
&lt;li&gt;btn-success：成功&lt;/li&gt;
&lt;li&gt;btn-warning：警告&lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;li&gt;按钮尺寸：btn-lg / btn-sm&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;剩余的看文档&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;表格类&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;table：默认样式&lt;/li&gt;
&lt;li&gt;table-striped：隔行变色&lt;/li&gt;
&lt;li&gt;table-success：表格颜色&lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;剩余的看文档&lt;/p&gt;
&lt;h4&gt;Bootstrap - 组件(Components)&lt;/h4&gt;
&lt;p&gt;1.引入样式表&lt;/p&gt;
&lt;p&gt;2.引入 js 文件&lt;/p&gt;
&lt;p&gt;3.复制结构，修改内容&lt;/p&gt;
&lt;h4&gt;Bootstrap - 字体图标&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;下载
&lt;ul&gt;
&lt;li&gt;导航 / Extend：图标库 → 安装 → 下载安装包 → bootstrap-icons-1.X.X.zip&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;使用
&lt;ul&gt;
&lt;li&gt;复制fonts文件夹到项目目录&lt;/li&gt;
&lt;li&gt;网页引入 bootstrap-icons.css 文件&lt;/li&gt;
&lt;li&gt;调用CSS类名（图标对应的类名）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>无题</title><link>https://wang-nine.github.io/posts/5/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/5/</guid><description>事务隔离</description><pubDate>Mon, 13 Nov 2023 14:06:06 GMT</pubDate><content:encoded>&lt;p&gt;事务隔离&lt;/p&gt;
&lt;p&gt;索引&lt;/p&gt;
&lt;p&gt;jwt&lt;/p&gt;
&lt;p&gt;微服务&lt;/p&gt;
&lt;p&gt;多线程&lt;/p&gt;
&lt;p&gt;乐观/悲观&lt;/p&gt;
&lt;p&gt;limit 1000000&lt;/p&gt;
&lt;p&gt;aqs&lt;/p&gt;
&lt;p&gt;g1 cms&lt;/p&gt;
&lt;p&gt;redis&lt;/p&gt;
&lt;p&gt;tcp拥塞控制&lt;/p&gt;
&lt;p&gt;jmm&lt;/p&gt;
&lt;p&gt;jvm&lt;/p&gt;
&lt;p&gt;可达性分析&lt;/p&gt;
&lt;p&gt;接雨水&lt;/p&gt;
&lt;p&gt;协程&lt;/p&gt;
&lt;p&gt;Spring SpringMVC SpringBoot&lt;/p&gt;
&lt;p&gt;cas 和 volatile&lt;/p&gt;
&lt;p&gt;3握4挥&lt;/p&gt;
&lt;p&gt;拦截与过滤区别&lt;/p&gt;
&lt;p&gt;Session&lt;/p&gt;
&lt;p&gt;循环队列&lt;/p&gt;
</content:encoded></item><item><title>分析</title><link>https://wang-nine.github.io/posts/20230717/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/20230717/</guid><description>使用场景：在实体类上边使用，标记类时swagger的解析类</description><pubDate>Sun, 16 Jul 2023 16:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;注解&lt;/h1&gt;
&lt;h2&gt;@ApiModel&lt;/h2&gt;
&lt;p&gt;使用场景：在实体类上边使用，标记类时swagger的解析类&lt;/p&gt;
&lt;p&gt;什么是Swagger&lt;br /&gt;
OpenAPI规范（OpenAPI Specification 简称OAS）是Linux基金会的一个项目，试图通过定义一种用来描述API格式或API定义的语言，来规范RESTful服务开发过程，目前版本是V3.0，并且已经发布并开源在github上。&lt;/p&gt;
&lt;p&gt;Swagger是符合OpenAPI规范的接口开发工具，支持从设计和文档到测试和部署的整个API生命周期的开发&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;作用范围&lt;/th&gt;
&lt;th&gt;API&lt;/th&gt;
&lt;th&gt;使用位置&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;对象属性&lt;/td&gt;
&lt;td&gt;@ApiModelProperty&lt;/td&gt;
&lt;td&gt;用在出入参数对象的字段上&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;协议集描述&lt;/td&gt;
&lt;td&gt;@Api&lt;/td&gt;
&lt;td&gt;用于controller类上&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;协议描述&lt;/td&gt;
&lt;td&gt;@ApiOperation&lt;/td&gt;
&lt;td&gt;用在controller的方法上&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Response集&lt;/td&gt;
&lt;td&gt;@ApiResponses&lt;/td&gt;
&lt;td&gt;用在controller的方法上&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Response&lt;/td&gt;
&lt;td&gt;@ApiResponse&lt;/td&gt;
&lt;td&gt;用在 @ApiResponses里边&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;非对象参数集&lt;/td&gt;
&lt;td&gt;@ApiImplicitParams&lt;/td&gt;
&lt;td&gt;用在controller的方法上&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;非对象参数描述&lt;/td&gt;
&lt;td&gt;@ApiImplicitParam&lt;/td&gt;
&lt;td&gt;用在@ApiImplicitParams的方法里边&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;描述返回对象的意义&lt;/td&gt;
&lt;td&gt;@ApiModel&lt;/td&gt;
&lt;td&gt;用在返回对象类上&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;@JsonNaming&lt;/h2&gt;
&lt;p&gt;@JsonNaming批注用于为序列化中的属性选择命名策略，覆盖默认设置。 使用value元素，我们可以指定任何策略，包括自定义策略。&lt;/p&gt;
&lt;p&gt;除了默认值LOWER_CAMEL_CASE（例如lowerCamelCase）外，Jackson库还为我们提供了另外四种内置的属性命名策略，以方便使用：&lt;/p&gt;
&lt;p&gt;KEBAB_CASE：名称元素之间用连字符分隔，例如 kebab-case。&lt;/p&gt;
&lt;p&gt;LOWER_CASE：所有字母均为小写字母，没有分隔符，例如 lowercase。&lt;/p&gt;
&lt;p&gt;LOWER_DOT_CASE：所有字母均为小写字母，用点连接字符，例如 lower.case。&lt;/p&gt;
&lt;p&gt;SNAKE_CASE：所有字母均为小写，并在名称元素之间使用下划线作为分隔符，例如 snake_case。&lt;/p&gt;
&lt;p&gt;UPPER_CAMEL_CASE：所有名称元素，包括第一个，都以大写字母开头，后跟小写字母，并且没有分隔符，例如 UpperCamelCase。&lt;/p&gt;
&lt;p&gt;此示例将说明使用蛇形案例名称序列化属性的方法，其中将名为beanName的属性序列化为bean_name。&lt;/p&gt;
&lt;h2&gt;@ConfigurationProperties( prefix = ”xxxx” )&lt;/h2&gt;
&lt;p&gt;在 SpringBoot 中，当想需要获取到配置文件数据时，除了可以用 Spring 自带的 @Value 注解外，SpringBoot 还提供了一种更加方便的方式：@ConfigurationProperties。只要在 Bean 上添加上了这个注解，指定好配置文件的前缀，那么对应的配置文件数据就会自动填充到 Bean 中。&lt;/p&gt;
&lt;p&gt;比如在application.properties文件中有如下配置文件&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;config.username=jay.zhou&lt;br /&gt;
config.password=3333&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>Redis09-消息队列</title><link>https://wang-nine.github.io/posts/10709/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10709/</guid><description>消息队列(Message Queue)，字面意思就是存放消息的队列。最简单的消息队列模型包括3个角色:</description><pubDate>Sat, 08 Jul 2023 11:30:00 GMT</pubDate><content:encoded>&lt;h1&gt;Redis消息队列&lt;/h1&gt;
&lt;h2&gt;基于List的消息队列&lt;/h2&gt;
&lt;p&gt;消息队列(Message Queue)，字面意思就是存放消息的队列。最简单的消息队列模型包括3个角色:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;消息队列:存储和管理消息，也被称为消息代理(Message Broker)&lt;/li&gt;
&lt;li&gt;生产者:发送消息到消息队列&lt;/li&gt;
&lt;li&gt;消费者:从消息队列获取消息并处理消息&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Redis提供了三种不同的方式来实现消息队列:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;list结构:基于List结构模拟消息队列&lt;/li&gt;
&lt;li&gt;PubSub:基本的点对点消息模型&lt;/li&gt;
&lt;li&gt;stream: 比较完善的消息队列模型&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;基于List结构模拟消息队列&lt;br /&gt;
消息队列(Message Queue)，字面意思就是存放消息的队列。而Redis的list数据结构是一个双向链表，很容易模拟出队列效果。&lt;br /&gt;
队列是入口和出口不在一边，因此我们可以利用:LPUSH结合RPOP、或者RPUSH结合LPOP来实现。&lt;br /&gt;
不过要注意的是，当队列中没有消息时RPOP或LPOP操作会返回null，并不像JVM的阻塞队列那样会阻塞并等待消息。因此这里应该使用BRPOP或者BLPOP来实现阻塞效果。&lt;/p&gt;
&lt;p&gt;基于List的消息队列有哪些优缺点?&lt;/p&gt;
&lt;p&gt;优点:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;利用Redis存储，不受限于JVM内存上限&lt;/li&gt;
&lt;li&gt;基于Redis的持久化机制，数据安全性有保证&lt;/li&gt;
&lt;li&gt;可以满足消息有序性&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;缺点:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;无法避免消息丢失·只支持单消费者&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;基于PubSub的消息队列&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/redis/pubsub.svg&quot; alt=&quot;Redis PubSub 向在线订阅者分发同一消息&quot; /&gt;&lt;/p&gt;
&lt;p&gt;发布者把消息发送到频道，当前在线且订阅该频道的多个订阅者各自接收一份，这与把任务分摊给单个消费者不同。频道不保存供离线订阅者补读的历史，也没有消费组的确认与待处理记录；断开连接期间错过的消息，不能仅靠重新订阅自动恢复。&lt;/p&gt;
&lt;p&gt;基于PubSub的消息队列有哪些优缺点?&lt;/p&gt;
&lt;p&gt;优点:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;采用发布订阅模型，支持多生产、多消费&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;缺点:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不支持数据持久化&lt;/li&gt;
&lt;li&gt;无法避免消息丢失&lt;/li&gt;
&lt;li&gt;消息堆积有上限，超出时数据丢失&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;基于Stream的消息队列&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/redis/stream-groups.svg&quot; alt=&quot;Redis Stream 的消费组分摊、pending 与确认流程&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Stream 保存带标识的消息记录，不同消费组维护各自进度，同组消费者可以分摊新消息。组内消息交付后进入待确认记录，业务处理成功再确认；确认移除的是待确认状态，不是流中的消息正文。失败消息需要显式恢复或认领，重复交付仍可能发生，因此处理逻辑必须考虑幂等。&lt;/p&gt;
&lt;p&gt;消费组并不会替应用判断业务是否真正完成。消费者应在处理成功后确认，处理中断时由恢复逻辑检查待确认记录并安排重试或认领。消息保留、裁剪与 Redis 持久化配置还会影响可恢复范围；确认机制不自动提供端到端恰好一次处理，并发消费者的完成顺序也不必等于消息写入顺序。&lt;/p&gt;
</content:encoded></item><item><title>Redis08-分布式锁</title><link>https://wang-nine.github.io/posts/10708/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10708/</guid><description>分布式锁的核心是实现多进程之间互斥，而满足这一点的方式有很多，常见的有三种:</description><pubDate>Wed, 05 Jul 2023 13:30:00 GMT</pubDate><content:encoded>&lt;p&gt;分布式锁要协调的是不同进程对同一资源的访问，而不是只锁住单个应用实例内的线程。竞争者需要使用一致的资源标识，通过共享的协调服务决定谁可进入临界区；获取失败时不得继续执行受保护操作。互斥、释放策略和故障边界需要分别设计，不能因为使用了 Redis 就默认全部具备。&lt;/p&gt;
&lt;p&gt;分布式锁的核心是实现多进程之间互斥，而满足这一点的方式有很多，常见的有三种:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;MySQL&lt;/th&gt;
&lt;th&gt;Redis&lt;/th&gt;
&lt;th&gt;Zookeeper&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;互斥&lt;/td&gt;
&lt;td&gt;利用mysql本身的互斥锁机制&lt;/td&gt;
&lt;td&gt;利用setnx这样的互斥命令&lt;/td&gt;
&lt;td&gt;利用节点的唯一性和有序性实现互斥&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;高可用&lt;/td&gt;
&lt;td&gt;好&lt;/td&gt;
&lt;td&gt;好&lt;/td&gt;
&lt;td&gt;好&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;高性能&lt;/td&gt;
&lt;td&gt;一般&lt;/td&gt;
&lt;td&gt;好&lt;/td&gt;
&lt;td&gt;一般&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;安全性&lt;/td&gt;
&lt;td&gt;断开连接，自动释放锁&lt;/td&gt;
&lt;td&gt;利用锁超时时间，到期释放&lt;/td&gt;
&lt;td&gt;临时节点，断开连接自动释放&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;注意redis的超时释放&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/redis/lock-expiry.svg&quot; alt=&quot;锁租期结束后旧持有者误删新锁的时序&quot; /&gt;&lt;/p&gt;
&lt;p&gt;过期时间限制的是 Redis 锁键的存活，并不会中断持锁线程。旧线程停顿期间锁可能到期，另一个线程随后获得同名锁；旧线程恢复后若直接删除该键，就删除了别人的锁。解锁需要核对本次加锁标识，并且将核对与删除合并执行，避免新的竞争窗口。&lt;/p&gt;
&lt;p&gt;租期结束后，同名锁可能已经属于新的请求。旧持有者恢复执行时，如果不检查当前锁值便直接删除，就会把新持有者的锁一并释放，使后续请求也进入临界区。超时机制有助于故障后的自动释放，却不会停止仍在执行的旧业务，因此不能把键过期理解为业务已经结束。&lt;/p&gt;
&lt;p&gt;解决方法：释放锁的时候判断一下锁标识&lt;/p&gt;
&lt;p&gt;锁值应保存本次加锁生成的唯一标识，释放前只接受与该标识一致的所有者。仅记录一个容易被不同进程重复使用的线程编号并不充分；不同持有者必须能够区分。检查归属只能确定应该删除谁的锁，若检查与删除分成两次命令，仍存在中间过期、换主人的竞争窗口。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/redis/unlock-atomic.svg&quot; alt=&quot;非原子检查删除的竞争窗口与 Lua 原子解锁&quot; /&gt;&lt;/p&gt;
&lt;p&gt;先 GET 再 DEL 即使检查过所有者，也可能在两条命令之间发生过期和重新加锁。把比较与条件删除放进同一个 Lua 脚本，只有锁值等于预期标识时才删除同一把锁，其他客户端无法在这两个步骤之间插入命令。这个保证限于脚本内的 Redis 操作，不覆盖数据库事务。&lt;/p&gt;
&lt;p&gt;==使用Redis事务（Redis只保证了原子性，不能保证一致性，要不然再加上乐观锁进行判断，复杂==&lt;/p&gt;
&lt;p&gt;==使用简单的Lua脚本，在一个脚本中编写多条Redis命令，确保多条命令执行时的原子性。==&lt;/p&gt;
&lt;p&gt;样例：&lt;/p&gt;
&lt;p&gt;调用解锁脚本时，要把实际锁键作为键参数传入，再把期望的所有者标识作为普通参数传入。脚本中的 KEYS[1] 与 ARGV[1] 分别读取这两类输入，不应把用户输入直接拼进脚本文本。调用方还要解释返回值，区分本次确实删除了锁与锁已不存在、已不属于自己等未释放情况。&lt;/p&gt;
&lt;p&gt;这段 Lua 的核心是先读取目标锁的当前值，再与调用者提供的标识比较，匹配时删除的必须仍是同一个键。这里键参数应统一使用 KEYS[1]，不能换成未定义的 key 变量。返回值也应与删除结果对应，否则调用者无法仅凭固定返回值知道是否真正完成了释放。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-- 比较标识的一致性
if(redis.call(&apos;get&apos;, KEYS[1]) == ARGV[1]) then
    -- 释放锁
    redis.call(&apos;del&apos;, key)
end
return 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;完整的锁生命周期需要两处不可分割的操作：获取时用一条带 NX 与过期参数的 SET 同时建立互斥和租期，避免先加锁后设置过期之间中断；释放时用脚本原子比较标识并条件删除。租期仍应覆盖业务风险，自动过期本身不能保证旧执行者已停止访问受保护资源。&lt;/p&gt;
&lt;p&gt;基于Redis的分布式锁实现思路:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;利用set nx ex获取锁，并设置过期时间，保存线程标示&lt;/li&gt;
&lt;li&gt;释放锁时先判断线程标示是否与自己一致，一致则删除锁&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;特性:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;利用set nx满足互斥性&lt;/li&gt;
&lt;li&gt;利用set ex保证故障时锁依然能释放，避免死锁，提高安全性·利用Redis集群保证高可用和高并发特性&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Redsson可重入锁&lt;/h1&gt;
&lt;p&gt;==基于setnx存在以下问题==&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不可重入：同一个线程无法多次获取同一把锁&lt;/li&gt;
&lt;li&gt;不可重试：获取锁只尝试一次就返回false，没有重试机制&lt;/li&gt;
&lt;li&gt;锁超时释放虽然可以避免死锁，但如果是业务执行耗时较长，也会导致锁释放，存在安全隐患&lt;/li&gt;
&lt;li&gt;如果Redis提供了主从集群，主从同步存在延迟，当主宕机时，如果从并同步主中的锁数据，则会出现锁实现&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;使用可重入锁时，先按业务资源取得同名锁对象，再尝试获取；只有获取成功才执行受保护逻辑，并在退出时确保释放。若获取过程被中断或未成功，不能当作已经持锁继续处理，也不能无条件解锁。等待时长与租期属于不同配置概念，需要根据所选调用方式确认续期行为。&lt;/p&gt;
&lt;h2&gt;可重入原理&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/redis/redisson-lifecycle.svg&quot; alt=&quot;Redisson 所有者、重入计数与释放通知的生命周期&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可重入允许同一所有者重复进入同一把锁，而不是允许其他线程共享持有。锁状态记录可区分客户端与线程的标识及重入计数，每次正确解锁减少计数，归零才释放并通知等待者。等待者仍需重新获取锁，通知不等于授权；续期是否启用也要结合固定租期或 watchdog 模式判断。&lt;/p&gt;
&lt;p&gt;加锁时需要区分锁不存在、锁属于当前线程、锁属于其他线程三种状态。不存在时建立所有者与计数，属于当前所有者时增加重入次数，属于别人时不能直接覆盖，而应依据等待策略重试或返回。被通知唤醒只是获得再次竞争的机会，并不等于已经自动取得锁。&lt;/p&gt;
&lt;p&gt;解锁先确认当前执行者有权操作这把锁，再减少自己的重入计数。仍有未退出的重入层级时锁应继续保留，只有最后一层退出才删除锁并发布释放通知。其他线程收到通知后还要重新竞争，不能将一次通知当作锁已转交，也不能由非持有者随意清空计数。&lt;/p&gt;
&lt;p&gt;Redisson 将重入计数、等待唤醒和适用模式下的租期续约组合起来：同一所有者重入增加计数，其他线程等待通知后再尝试竞争，最终释放才唤醒等待者。watchdog 不是所有调用方式下都启用的无限续期，也不能消除进程长时间停顿、网络中断或主从切换的全部互斥风险。&lt;/p&gt;
&lt;p&gt;Redisson分布式锁原理:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;可重入:利用hash结构记录线程id和重入次数&lt;/li&gt;
&lt;li&gt;可重试:利用信号量和PubSub功能实现等待、唤醒，获取锁失败的重试机制&lt;/li&gt;
&lt;li&gt;超时续约:利用watchDog，每隔一段时间( releaseTime / 3)，重置超时时间&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Redis07-秒杀</title><link>https://wang-nine.github.io/posts/10707/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10707/</guid><description>一般的订单会出什么问题呢？</description><pubDate>Tue, 04 Jul 2023 13:30:00 GMT</pubDate><content:encoded>&lt;h1&gt;全局唯一ID&lt;/h1&gt;
&lt;p&gt;一般的订单会出什么问题呢？&lt;/p&gt;
&lt;p&gt;第一点：数据量大&lt;/p&gt;
&lt;p&gt;第二点：要具备唯一性（数据量过大时分表，可能出现重复id的问题）&lt;/p&gt;
&lt;p&gt;因此要使用==全局唯一ID==在分布式系统下用来生成全局唯一ID的工具&lt;/p&gt;
&lt;p&gt;需要满足：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;唯一性&lt;/li&gt;
&lt;li&gt;高可用&lt;/li&gt;
&lt;li&gt;高性能&lt;/li&gt;
&lt;li&gt;递增性（有利于索引和插入）Redis的incr&lt;/li&gt;
&lt;li&gt;安全性（自增不能规律过于明显）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;为了增加ID的安全性，我们可以不直接使用Redis自增的数值，而是拼接一些其它信息:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/redis/distributed-id.svg&quot; alt=&quot;时间信息与 Redis 自增序列组合全局 ID&quot; /&gt;&lt;/p&gt;
&lt;p&gt;同一业务与日期命名空间中的应用实例，共用 Redis 计数器获取自增序列，再与约定的时间信息组成 ID。编码需要明确字段边界、日期切换和序列溢出的处理，不能随意拼接便认为绝不会重复。这是 Redis 自增组合方案，与通常包含机器标识的 Snowflake 应区别理解，也不代表 ID 已不可猜测。&lt;/p&gt;
&lt;p&gt;全局唯一ID生成策略:.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;UUID&lt;/li&gt;
&lt;li&gt;Redis自增snowflake算法&lt;/li&gt;
&lt;li&gt;数据库自增&lt;/li&gt;
&lt;li&gt;Redis自增ID策略:
&lt;ul&gt;
&lt;li&gt;每天一个key，方便统计订单量.&lt;/li&gt;
&lt;li&gt;ID构造是时间戳＋计数器&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下单时需要判断两点:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;秒杀是否开始或结束，如果尚未开始或已经结束则无法下单&lt;/li&gt;
&lt;li&gt;库存是否充足，不足则无法下单&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/redis/seckill-stock.svg&quot; alt=&quot;秒杀活动时间校验与条件扣库存流程&quot; /&gt;&lt;/p&gt;
&lt;p&gt;活动时间不符合条件时直接拒绝下单；活动进行中也不能仅凭先前读到的库存继续扣减。应在数据库更新时同时约束库存大于零，并依据更新结果决定是否创建订单。库存扣减与订单写入需要事务边界，而同一用户同一优惠券只能购买一次，还需要额外的并发约束。&lt;/p&gt;
&lt;p&gt;==高并发情况下会出现超卖==&lt;/p&gt;
&lt;p&gt;超卖的风险来自检查与修改之间的并发窗口：多个请求可能先后读到同一个可售库存，再各自继续下单。若扣减没有同步约束，就可能卖出超过可用数量的订单，或者发生更新覆盖。把库存条件放进更新语句，并检查影响行数，才能让过时的读取结果不再直接决定是否售出。&lt;/p&gt;
&lt;p&gt;悲观锁把冲突操作放进互斥区，让其他执行者等待；乐观方案则允许先读取，在提交更新时用版本号或条件比较拒绝过期修改。前者需要评估锁粒度与等待成本，后者需要处理冲突失败和重试，不能只用“更快”概括选择。进程内同步锁也不会自动约束另一台应用实例。&lt;/p&gt;
&lt;p&gt;乐观锁的实现方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;版本号法&lt;/li&gt;
&lt;li&gt;CAS&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;1．悲观锁:添加同步锁，让线程串行执行&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;优点:简单粗暴&lt;/li&gt;
&lt;li&gt;缺点:性能一般&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;2.乐观锁:不加锁，在更新时判断是否有其它线程在修改&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;优点∶性能好&lt;/li&gt;
&lt;li&gt;缺点∶存在成功率低的问题&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;修改秒杀业务，要求同一个优惠券，一个用户只能下一单&lt;/p&gt;
&lt;p&gt;p54&lt;/p&gt;
&lt;p&gt;集群下的并发安全问题&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/redis/cluster-lock.svg&quot; alt=&quot;进程内锁与共享 Redis 锁的作用边界&quot; /&gt;&lt;/p&gt;
&lt;p&gt;同一用户的请求分散到不同应用进程后，各进程自己的同步锁无法相互排斥。需要让相同业务资源竞争同一个共享锁键，只有获锁者进入受保护业务，未获锁者等待、重试或返回。锁键粒度应匹配优惠券与用户的业务约束，数据库唯一约束也可作为防止重复订单的最终保护。&lt;/p&gt;
</content:encoded></item><item><title>Redis06-缓存</title><link>https://wang-nine.github.io/posts/10706/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10706/</guid><description>缓存就是数据交换的缓冲区（称作Cache [ kae]] )，是存贮数据的临时地方，一般读写性能较高。</description><pubDate>Mon, 03 Jul 2023 14:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;缓存&lt;/h1&gt;
&lt;p&gt;缓存就是数据交换的缓冲区（称作Cache [ kae]] )，是存贮数据的临时地方，一般读写性能较高。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;优点
&lt;ul&gt;
&lt;li&gt;降低后端的负载&lt;/li&gt;
&lt;li&gt;提高读写效率，降低响应的时间&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;成本
&lt;ul&gt;
&lt;li&gt;数据一致性成本&lt;/li&gt;
&lt;li&gt;代码维护成本&lt;/li&gt;
&lt;li&gt;运维成本&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/redis/cache-aside.svg&quot; alt=&quot;Cache Aside 读取回填与写后删除缓存流程&quot; /&gt;&lt;/p&gt;
&lt;p&gt;应用先查询 Redis，命中时直接返回，未命中时查询数据库并回填带有效期的缓存。修改数据时先提交数据库更新，再让对应缓存失效，后续读取重新加载。缓存减少重复查询，但数据库与缓存之间的失败窗口仍需处理，不能仅靠设置有效期宣称两者始终一致。&lt;/p&gt;
&lt;h1&gt;缓存更新策略&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;内存淘汰&lt;/th&gt;
&lt;th&gt;超时剔除&lt;/th&gt;
&lt;th&gt;主动更新&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;说明&lt;/td&gt;
&lt;td&gt;不用自己维护，利用Redis的内存淘汰机制，当内存不足时自动淘汰部分数据。下次查询时更新缓存。&lt;/td&gt;
&lt;td&gt;给缓存数据添加TTL时间，到期后自动删除缓存。下次查询时更新缓存。&lt;/td&gt;
&lt;td&gt;编写业务逻辑，在修改数据库的同时，更新缓存。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;一致性&lt;/td&gt;
&lt;td&gt;差&lt;/td&gt;
&lt;td&gt;一般&lt;/td&gt;
&lt;td&gt;好&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;维护成本&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;低&lt;/td&gt;
&lt;td&gt;高&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;业务场景:&lt;br /&gt;
低一致性需求:使用内存淘汰机制。例如店铺类型的查询缓存&lt;br /&gt;
高一致性需求:主动更新，并以超时剔除作为兜底方案。例如店铺详情查询的缓存&lt;/p&gt;
&lt;p&gt;主动更新&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;由缓存的调用者，在更新数据库的同时更新缓存&lt;/li&gt;
&lt;li&gt;缓存与数据库整合为一个服务，由服务来维护一致性。调用者调用该服务，无需关心缓存一致性问题。&lt;/li&gt;
&lt;li&gt;调用者只操作缓存，由其它线程异步的将缓存数据持久化到数据库，保证最终一致。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;缓存更新策略的最佳实践方案:&lt;br /&gt;
1.低一致性需求:使用Redis自带的内存淘汰机制&lt;br /&gt;
2.高一致性需求:主动更新，并以超时剔除作为兜底方案&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;读操作︰
&lt;ul&gt;
&lt;li&gt;缓存命中则直接返回&lt;/li&gt;
&lt;li&gt;缓存未命中则查询数据库，并写入缓存，设定超时时间&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;写操作∶
&lt;ul&gt;
&lt;li&gt;先写数据库，然后再删除缓存&lt;/li&gt;
&lt;li&gt;要确保数据库与缓存操作的原子性&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;先写数据库再删除缓存，是让下一次读取重新加载最新数据，而不是给旧缓存逐字段打补丁。数据库提交与缓存删除属于两个操作，删除失败需要重试或补偿，超时剔除只能限制陈旧数据存续的窗口，不能自动提供跨存储的原子性，也不能消除所有并发回填竞态。&lt;/p&gt;
&lt;h1&gt;*缓存穿透&lt;/h1&gt;
&lt;p&gt;==缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在，这样缓存永远不会生效，这些请求都会打到数据库。==&lt;/p&gt;
&lt;p&gt;恶意用户在发送请求后发现为空，而查询每次都要到数据库，长此以往会导致数据库压力过大。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;缓存空对象
&lt;ul&gt;
&lt;li&gt;优点:实现简单，维护方便&lt;/li&gt;
&lt;li&gt;缺点:
&lt;ul&gt;
&lt;li&gt;额外的内存消耗&lt;/li&gt;
&lt;li&gt;可能造成短期的不一致&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;布隆过滤器（存伪不失真）存在不一定存在，不存在一定不存在
&lt;ul&gt;
&lt;li&gt;优点:内存占用较少，没多余key&lt;/li&gt;
&lt;li&gt;缺点:
&lt;ul&gt;
&lt;li&gt;实现复杂&lt;/li&gt;
&lt;li&gt;存在误判的可能&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/redis/penetration-guards.svg&quot; alt=&quot;缓存空对象与布隆过滤器的两条穿透保护路径&quot; /&gt;&lt;/p&gt;
&lt;p&gt;数据库查空后，可以短期缓存明确的空值标记，使后续同类请求直接返回不存在；需要区分空标记和真正没有缓存。布隆过滤器能够排除确定不存在的查询，但“可能存在”仍须继续查询，不能当作数据存在的证明。两种方式都需要考虑新增数据和失效维护。&lt;/p&gt;
&lt;p&gt;缓存穿透产生的原因是什么?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用户请求的数据在缓存中和数据库中都不存在，不断发起这样的请求给数据库带来巨大压力&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;缓存穿透的解决方案有哪些?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;缓存null值&lt;/li&gt;
&lt;li&gt;布隆过滤&lt;/li&gt;
&lt;li&gt;增强id的复杂度，避免被猜测id规律&lt;/li&gt;
&lt;li&gt;做好数据的基础格式校验&lt;/li&gt;
&lt;li&gt;加强用户权限校验&lt;/li&gt;
&lt;li&gt;做好热点参数的限流&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;*缓存雪崩&lt;/h1&gt;
&lt;p&gt;缓存雪崩是指在同一时段大量的缓存kev同时失效或者Redis服务宕机，导致大量请求到达数据库，带来巨大压力。&lt;/p&gt;
&lt;p&gt;解决方案:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;给不同的Key的TTL添加随机值&lt;/li&gt;
&lt;li&gt;利用Redis集群提高服务的可用性&lt;/li&gt;
&lt;li&gt;给缓存业务添加降级限流策略&lt;/li&gt;
&lt;li&gt;给业务添加多级缓存&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;*缓存击穿&lt;/h1&gt;
&lt;p&gt;缓存击穿问题也叫热点Key问题，就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了，无数的请求访问会在瞬间给数据库带来巨大的冲击。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/redis/cache-failures.svg&quot; alt=&quot;缓存穿透、热点键击穿和大范围雪崩的区别&quot; /&gt;&lt;/p&gt;
&lt;p&gt;击穿的对象是原本存在、访问集中的热点键，缓存失效后大量请求同时回源；穿透则是缓存和数据库都没有对应数据，雪崩涉及大量键失效或整个缓存服务不可用。先定位触发范围，再决定使用空值缓存、互斥重建、错开有效期或业务降级，避免把不同故障当作同一问题处理。&lt;/p&gt;
&lt;p&gt;==常见的解决方案有两种:==&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;互斥锁&lt;/li&gt;
&lt;li&gt;逻辑过期&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/redis/hotspot-rebuild.svg&quot; alt=&quot;互斥重建与逻辑过期的缓存击穿处理对照&quot; /&gt;&lt;/p&gt;
&lt;p&gt;互斥方案让获锁请求再次检查缓存，再按需回源重建，其他请求等待或重试；逻辑过期方案保留旧值，发现过期时先返回旧数据，仅让获锁任务后台更新。后者以暂时陈旧换取读取不等待，但缓存真实缺失时没有旧值可用，仍需冷启动或回源策略。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;解决方案&lt;/th&gt;
&lt;th&gt;优点&lt;/th&gt;
&lt;th&gt;缺点&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;互斥锁&lt;/td&gt;
&lt;td&gt;没有额外的内存消耗  &amp;lt;br&amp;gt;保证一致性  &amp;lt;br&amp;gt;实现简单&lt;/td&gt;
&lt;td&gt;线程需要等待，性能受影响  &amp;lt;br&amp;gt;可能有死锁风险&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;逻辑过期&lt;/td&gt;
&lt;td&gt;线程无需等待，性能较好&lt;/td&gt;
&lt;td&gt;不保证一致性  &amp;lt;br&amp;gt;有额外内存消耗  &amp;lt;br&amp;gt;实现复杂&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</content:encoded></item><item><title>Redis05-短信登陆</title><link>https://wang-nine.github.io/posts/10705/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10705/</guid><description>1.怎么基于session</description><pubDate>Mon, 03 Jul 2023 13:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;短信登陆&lt;/h1&gt;
&lt;h2&gt;导入的表格&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;tb_user:用户表&lt;/li&gt;
&lt;li&gt;tb_userLinfo:用户详情表&lt;/li&gt;
&lt;li&gt;tb_shop:商户信息表&lt;/li&gt;
&lt;li&gt;tb_shop_type:商户类型表&lt;/li&gt;
&lt;li&gt;tb_blog:用户日记表(达人探店日记)&lt;/li&gt;
&lt;li&gt;tb_follow:用户关注表&lt;/li&gt;
&lt;li&gt;tb_voucher:优惠券表&lt;/li&gt;
&lt;li&gt;tb_voucher_order:优惠券的订单表&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;短信登录的关键不在页面外观，而在验证码与用户身份的衔接：服务端保存与手机号对应的验证码，登录请求提交后进行比对，通过后再查找或创建用户并建立登录态。验证码只是一次登录校验材料，不应作为长期凭证，也不应把用户表中的全部字段交给浏览器。&lt;/p&gt;
&lt;h1&gt;为什么使用Redis&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/redis/shared-session.svg&quot; alt=&quot;实例内 Session 与 Redis 共享登录态对照&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Session 保存在各自应用实例内存时，一个实例不能直接读到另一个实例的登录记录。改用 Redis 保存登录态后，请求携带随机 token，各实例都能通过它查找同一份用户信息；这改变的是状态存储位置，并不意味着浏览器会自动保存或发送新的凭证。&lt;/p&gt;
&lt;p&gt;1.怎么基于session&lt;/p&gt;
&lt;p&gt;2.验证码如何比对&lt;/p&gt;
&lt;p&gt;3.是否要将所有的数据存入session？&lt;/p&gt;
&lt;p&gt;==集群的session共享问题==&lt;/p&gt;
&lt;p&gt;session共享问题:多台Tomcat并不共享session存储空间，当请求切换到不同tomcat服务时导致数据丢失的问题。&lt;/p&gt;
&lt;p&gt;session的替代方案应该满足:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数据共享&lt;/li&gt;
&lt;li&gt;内存存储&lt;/li&gt;
&lt;li&gt;kev、value结构(和session一样)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;使用Redis！&lt;/p&gt;
&lt;p&gt;首先每个Tomcat可以去访问Redis，Redis是内存存储的，且Redis就是key，value结构&lt;/p&gt;
&lt;p&gt;问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;保存验证码&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;存储在Redis中类型：String，那么key是什么？手机号（确保每个手机号有自己的验证码，不仅可以&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;保存用户信息&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;存储在Redis中类型：Hash，那么key是什么？==手机号（不推荐，放在请求头中，有暴露的风险） 建议随机token==&lt;/p&gt;
&lt;p&gt;（保存登录的用户信息，可以使用String结构，以SON字符串来保存，比较直观，&lt;/p&gt;
&lt;p&gt;Hash结构可以将对象中的每个字段独立存储，可以针对单个字段做CRUD，并且内存占用更少）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;登陆校验&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;不再是sessionid，token才是登录凭着，但是token不是自动存储在浏览器，需要手动返回到浏览器。&lt;/p&gt;
&lt;p&gt;验证码可以按手机号建立独立的 String 键，登录用户信息则以随机 token 为索引保存为 Hash，或按需要序列化为 String。登录成功后把 token 返回客户端，由后续请求显式携带；验证码和登录态分别设置有效期，既限制凭证使用窗口，也避免过期记录长期占用内存。&lt;/p&gt;
&lt;p&gt;==可以给验证码设置有效期，放在内存满==&lt;/p&gt;
&lt;p&gt;==注意像session（默认30min）一样给登陆时间做一个有效期，要不然登陆信息越来越多，最后会把redis装满==&lt;/p&gt;
&lt;p&gt;==另一个问题，有效期30min，从登陆开始算起，只要登陆了30min后就会踢出，那么如何访问刷新有效期？拦截器！==&lt;/p&gt;
&lt;p&gt;==上个问题可以在prehandle中修改，获取请求头的token，&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;preHandle

调用时间：Controller方法处理之前

执行顺序：链式Intercepter情况下，Intercepter按照声明的顺序一个接一个执行

若返回false，则中断执行，注意：不会进入afterCompletion

 

postHandle

调用前提：preHandle返回true

调用时间：Controller方法处理完之后，DispatcherServlet进行视图的渲染之前，也就是说在这个方法中你可以对ModelAndView进行操作

执行顺序：链式Intercepter情况下，Intercepter按照声明的顺序倒着执行。

备注：postHandle虽然post打头，但post、get方法都能处理



afterCompletion

调用前提：preHandle返回true

调用时间：DispatcherServlet进行视图的渲染之后
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Redis代替session需要考虑的问题:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;选择合适的数据结构&lt;/li&gt;
&lt;li&gt;选择合适的key&lt;/li&gt;
&lt;li&gt;选择合适的存储粒度&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;==拦截器问题==&lt;/p&gt;
&lt;p&gt;拦截器拦住了需要登录的路径，若一直访问不需要登陆的网页则不会刷新，怎么办？&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/redis/interceptor-order.svg&quot; alt=&quot;刷新登录态与路径登录校验的拦截器顺序&quot; /&gt;&lt;/p&gt;
&lt;p&gt;将刷新拦截器放在前面并覆盖所有路径，有有效 token 才恢复用户信息、刷新登录态有效期；随后由登录拦截器检查受保护路径。公开路径无需登录，但有效登录用户访问这些路径时仍能完成续期。缺少或失效的凭证不能通过刷新重新获得登录身份。&lt;/p&gt;
&lt;p&gt;如何控制拦截器顺序？&lt;/p&gt;
&lt;p&gt;.order属性&lt;/p&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Override
public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate))
            .addPathPatterns(
                    &quot;/**&quot;
            ).order(0);
    registry.addInterceptor(new LoginInterceptor())
            .excludePathPatterns(
                    &quot;/shop/**&quot;,
                    &quot;/blog/hot&quot;,
                    &quot;/shop/type/**&quot;,
                    &quot;/upload/**&quot;,
                    &quot;/vocher/**&quot;,
                    &quot;/user/code&quot;,
                    &quot;/user/login&quot;
            ).order(1);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;order越小越先执行&lt;/p&gt;
</content:encoded></item><item><title>后端</title><link>https://wang-nine.github.io/posts/999999999/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/999999999/</guid><description>Java语言的特点是：一次编译，到处运行，即平台无关性；是纯粹的面向对象的语言。</description><pubDate>Sun, 02 Jul 2023 15:00:00 GMT</pubDate><content:encoded>&lt;p&gt;[toc]&lt;/p&gt;
&lt;h4&gt;1.请你说说Java的特点和优点,为什么要选择Java&lt;/h4&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Java语言的特点是：一次编译，到处运行，即平台无关性；是纯粹的面向对象的语言。&lt;/li&gt;
&lt;li&gt;JAVA语言的优点有：内置的类库简化了开发人员的设计工作；具有较好的安全性和健壮性；开发人员不需要对内存的使用进行管理。&lt;/li&gt;
&lt;li&gt;选择JAVA的原因是：使用范围很广，安卓操作系统的应用软件目前大部分还是使用JAVA语言编写。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h4&gt;2.请你说说Java基本数据类型和引用类型&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;Java的数据类型分为基本数据类型和引用数据类型两大类。 基本数据类型共有八大类,这八大数据类型又可分为四小类,分别是整数类型（byte/short/int/long）、浮点类型（float、double）、字符类型（char）和布尔类型（boolean）。其中,int是最常用的整数类型,double是最为常用的浮点类型,除了布尔类型之外的其他7个类型,都可以看做是数字类型,它们相互之间可以进行类型转换。 引用类型包括数组、类、接口类型,还有一种特殊的null类型,所谓引用数据类型就是对一个对象的引用,对象包括实例和数组两种。&lt;/p&gt;
&lt;p&gt;8种基本数据类型的变量的值存放在栈内存，有char(16位),byte(8位),short(16位),int(32位)，long(64位)float(32位),double(64位),boolean。 引用类型的变量存放引用地址，堆内存才是真正的值。类，接口，数组,string等为引用类型。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;3.请你说一下抽象类和接口的区别&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;相同点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1、两者都不能实例化；&lt;/li&gt;
&lt;li&gt;2、可以拥有抽象方法。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;区别：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1、抽象类定义的关键字是abstract class，接口定义的关键字是interface；&lt;/li&gt;
&lt;li&gt;2、属性上，抽象类可以有静态变量、常量和成员变量，接口只能有常量；&lt;/li&gt;
&lt;li&gt;3、抽象方法可以有普通方法，而接口在 JDK1.8 之前只能有抽像方法（1.8之后，增加了静态方法和默认方法）；&lt;/li&gt;
&lt;li&gt;4、抽象方法可以有构造方法，接口不可以有构造方法。&lt;/li&gt;
&lt;li&gt;5、一个类只能单继承一个父类，而一个接口可以继承多个父接口，同时，一个类可以实现多个接口却没有实现多个父类这一说法；&lt;/li&gt;
&lt;li&gt;6、抽象方法在业务编程上更像一个模板，有自己的功能，同时也可以有优化补充的多种形式，而接口更像是一种规范和要求，实现就要按照要求来进行。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h4&gt;4.请你说一下final关键字&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;final可以修饰类，方法，变量。 final修饰类，该类不可被继承。 final修饰方法，该方法不能被重写。 final修饰变量，如果是基本变量则值不能再改变，如果是引用变量则引用地址不能改变，但值可以改变。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;5.说说static修饰符的用法&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;Java类中包含了成员变量、方法、构造器、初始化块和内部类（包括接口、枚举）5种成员,static关键字可以修饰除了构造器外的其他4种成员。static关键字修饰的成员被称为类成员。类成员属于整个类,不属于单个对象。 static关键字有一条非常重要的规则,即类成员不能访问实例成员,因为类成员属于类的,类成员的作用域比实例成员的作用域更大,很容易出现类成员初始化完成时,但实例成员还没被初始化,这时如果类成员访问实力成员就会引起大量错误。 加分回答 static修饰的部分会和类同时被加载。被static修饰的成员先于对象存在,因此,当一个类加载完毕,即使没有创建对象也可以去访问被static修饰的部分。 静态方法中没有this关键词,因为静态方法是和类同时被加载的,而this是随着对象的创建存在的。静态比对象优先存在。也就是说,静态可以访问静态,但静态不能访问非静态而非静态可以访问静态。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;6.请你说说String类,以及new string和使用字符串直接量&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;String类被final修饰，所以不能被继承。创建String对象时可以使用字符串直接量，如String str=”1abc”, 另一种String str=new String(“1abc”),前者使用常量池来管理，后者先判断常量池中是否已存在此字符串，不存在就也在常量池创建，再在堆内存创建一个新的对象，因此后者开销更大。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;7.String、StringBuffer、Stringbuilder有什么区别&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;StringBuilder和StringBuffer非常类似，均代表可变的字符序列，而且方法也一样&lt;/p&gt;
&lt;p&gt;String:不可变字符序列，效率低，但是复用率高。&lt;/p&gt;
&lt;p&gt;StringBuffer:可变字符序列、效率较高(增删)、线程安全&lt;/p&gt;
&lt;p&gt;StringBuilder:可变字符序列、效率最高、线程不安全&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;8.请你说说\==与 equals()的区别&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;== 比较基本数据类型时，比较的是两个数值是否相等；比较引用类型是，比较的是对象的内存地址是否相等。 equals() 没有重写时，Object 默认以\==来实现，即比较两个对象的内存地址是否相等；重写以后，按照对象的内容进行比较&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;9.请你说说hashCode()和equals()的区别,为什么重写equals()就要重写hashcod()&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;得分点 hashCode()用途,equals()用途,hashCode()、equals()约定&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1、hashCode()：获取哈希码，equals()：比较两个对象是否相等。&lt;/li&gt;
&lt;li&gt;2、二者两个约定：如果两个对象相等，它们必须有相同的哈希码；若两个对象的哈希码相同，他们却不一定相等。也就是说,equals()比较两个对象相等时hashCode()一定相等，hashCode()相等的两个对象equqls()不一定相等。&lt;/li&gt;
&lt;li&gt;3、加分回答：由于hashCode()与equals()具有联动关系，equals()重写时，hashCode()进行重写，使得这两个方法始终满足相关的约定。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h4&gt;10.请你讲一下Java 8的新特性&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;得分点 Lambda表达式、Java8对接口的改进&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1、Lambda表达式：可将功能视为方法参数，或者将代码视为数据。使用 Lambda 表达式，可以更简洁地表示单方法接口（称为功能接口）的实例。&lt;/li&gt;
&lt;li&gt;2、方法引用：提供了非常有用的语法，可直接引用已有Java类或对象（实例）的方法或构造器。与Lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。&lt;/li&gt;
&lt;li&gt;3、对接口进行了改进：允许在接口中定义默认方法，默认方法必须使用default修饰。&lt;/li&gt;
&lt;li&gt;4、Stream API：新添加的Stream API（java.util.stream）支持对元素流进行函数式操作。Stream API 集成在 Collections API 中,可以对集合进行批量操作，例如顺序或并行的 map-reduce 转换。&lt;/li&gt;
&lt;li&gt;5、Date Time API：加强对日期与时间的处理。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h4&gt;11.介绍一下包装类的自动拆装箱与自动装箱&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;得分点 包装类的作用,应用场景&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1、自动装箱、自动拆箱是JDK1.5提供的功能。&lt;/li&gt;
&lt;li&gt;2、自动装箱：把一个基本类型的数据直接赋值给对应的包装类型；&lt;/li&gt;
&lt;li&gt;3、自动拆箱是指把一个包装类型的对象直接赋值给对应的基本类型；&lt;/li&gt;
&lt;li&gt;4、通过自动装箱、自动拆箱功能，简化基本类型变量和包装类对象之间的转换过程&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h4&gt;12.请你说说Java的异常处理机制&lt;/h4&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;1、异常处理机制让程序具有容错性和健壮性,程序运行出现状况时,系统会生成一个Exception对象来通知程序&lt;/li&gt;
&lt;li&gt;2、处理异常的语句由try、catch、finally三部分组成。try块用于包裹业务代码,catch块用于捕获并处理某个类型的异常,finally块则用于回收资源。&lt;/li&gt;
&lt;li&gt;3、如果业务代码发生异常,系统创建一个异常对象,并将其提交给JVM,由JVM寻找可以处理这个异常的catch块,并将异常对象交给这个catch块处理。如果JVM没有找到，运行环境终止,Java程序退出。&lt;/li&gt;
&lt;li&gt;4、Java也允许程序主动抛出异常。当业务代码中,判断某项错误的条件成立时,可以使用throw关键字向外抛出异常&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h4&gt;13.说说你对面向对象的理解&lt;/h4&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;1、面向对象三大基本特征：封装、继承、多态。&lt;/li&gt;
&lt;li&gt;2、封装：将对象的状态信息隐藏在对象内部，不允许外部程序直接访问对象内部信息，让外部程序通过该类提供的方法来实现对内部信息的操作和访问,提高了代码的可维护性；&lt;/li&gt;
&lt;li&gt;3、继承：实现代码复用的重要手段,通过extends实现类的继承，实现继承的类被称为子类，被继承的类称为父类；&lt;/li&gt;
&lt;li&gt;4、多态的实现离不开继承,在设计程序时,我们可以将参数的类型定义为父类型。在调用程序时根据实际情况,传入该父类型的某个子类型的实例,这样就实现了多态。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;多态实现的三个条件：1.需要有继承关系的存在。 2. 需要有方法的重写。 3. 需要有父类的引用指向子类对象。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;14.请你说说重载和重写的区别,构造方法能不能重写&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;重写和重载的区别：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.重载发生在同一类中，而重写发生在子类中。&lt;/li&gt;
&lt;li&gt;2.重载要求方法名相同，参数列表，返回值，访问修饰符都可以不同。重写要求方法名相同，参数列表相同，返回值类型要小于等于父类的方法，抛出的异常要小于等于父类方法抛出的异常，访问修饰符权限大于等于父类方法的访问修饰符权限。&lt;/li&gt;
&lt;li&gt;3.final，private修饰的方法不能重写，构造方法也不能重写。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;构造方法不能重写。因为构造方法需要和类保持同名,而重写的要求是子类方法要和父类方法保持同名。如果允许重写构造方法的话,那么子类中将会存在与类名不同的构造方法,这与构造方法的要求是矛盾的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;15.请介绍一下访问修饰符&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;Java中的访问修饰符有四种，分别为private,default,protected,public。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;private：类中被private修饰的只能在被当前类的内部访问&lt;/li&gt;
&lt;li&gt;default：类中被default修饰的只能在当前类和当前类所在包的其他类访问&lt;/li&gt;
&lt;li&gt;protected：类中被protected修饰的可以被当前类和当前类所在的包的其他类以及子类访问&lt;/li&gt;
&lt;li&gt;public：类中被public修饰的能被当前项目下的所有类访问。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h4&gt;16.请你说说泛型、泛型擦除&lt;/h4&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;1.泛型：Java在jdk1.5引入了泛型，在没有泛型之前，每次从集合中读取的对象都必须进行类型转换，如果在插入对象时，类型出错，那么在运行时转换处理的阶段就出错；在提出泛型之后就可以明确的指定集合接受哪些对象类型，编译器就能知晓并且自动为插入的代码进行泛化，在编译阶段告知是否插入类型错误的对象，程序会变得更加安全清晰。&lt;/li&gt;
&lt;li&gt;2.泛型擦除：Java泛型是伪泛型，因为Java代码在编译阶段，所有的泛型信息会被擦除，Java的泛型基本上都是在编辑器这个层次上实现的，在生成的字节码文件中是不包含泛型信息的，使用泛型的时候加上的类型，在编译阶段会被擦除掉，这个过程称为泛型擦除。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h4&gt;17.请说说你对反射的了解&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;在程序运行期间动态的获取对象的属性和方法的功能叫做反射。&lt;/p&gt;
&lt;p&gt;它能够在程序运行期间，对于任意一个类，都能知道它所有的方法和属性，对于任意一个对象，都能知道他的属性和方法。&lt;/p&gt;
&lt;p&gt;获取Class对象的三种方式：getClass(); xx.class; Class.forName(“xxx”);&lt;/p&gt;
&lt;p&gt;反射的优缺点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;优点：运行期间能够动态的获取类，提高代码的灵活性。&lt;/li&gt;
&lt;li&gt;缺点：性能比直接的Java代码要慢很多。&lt;/li&gt;
&lt;li&gt;应用场景：spring的xml配置模式，以及动态代理模式都用到了反射。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h4&gt;18.请你说说多线程&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;进程是操作系统资源调度的基本单位，线程是处理器任务调度和执行的基本单位，一个进程可以创建多个线程，每个线程有自己独立的程序计数器，本地方法栈和虚拟机栈，线程之间共享进程的堆和方法区。线程之间是通过时间片算法来争夺CPU的执行权的。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;多线程的优点：当一个线程进入阻塞或者等待状态时，其他的线程可以获取CPU的执行权，提高了CPU的利用率。&lt;/li&gt;
&lt;li&gt;多线程的缺点：可能产生死锁；频繁的上下文切换可能会造成资源的浪费；在并发编程中如果因为资源的限制，多线程串行执行，可能速度会比单线程更慢。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;死锁四个条件：&lt;/p&gt;
&lt;p&gt;（1） 互斥条件：一个资源每次只能被一个进程使用。&lt;/p&gt;
&lt;p&gt;（2） 请求与保持条件：一个进程因请求资源而阻塞时，对已获得的资源保持不放。&lt;/p&gt;
&lt;p&gt;（3） 不剥夺条件：进程已获得的资源，在末使用完之前，不能强行剥夺。&lt;/p&gt;
&lt;p&gt;（4） 循环等待条件：若干进程之间形成一种头尾相接的循环等待资源关系。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;19.说说线程的创建方式&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;创建线程有三种方式，分别是继承Thread类，实现Runnable接口，实现Callable接口&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;继承Thread类之后我们需要重写run()方法，方法中是我们希望这个线程执行什么操作，再创建对象的实例，通过实例对象的start())方法开启这个线程&lt;/li&gt;
&lt;li&gt;实现runnable接口之后，我们需要实现run()方法，方法中同样写我们需要执行的操作，然后将实现了接口的类作为参数创建一个Thread对象，通过这个对象的start方法开启线程&lt;/li&gt;
&lt;li&gt;实现Callable之后，需要实现call()方法，方法中写我们需要的操作，然后创建实现接口类的对象，将对象作为参数创建FurtureTask对象，再将task对象作为参数创建thread对象，调用start方法开启线程，还可以使用task对象的get方法获取返回值。他们的区别是前二者不能获取返回值，callable接口可以获得返回值，一般在实际使用中，更多使用实现接口的方式开启线程，因为接口不会占用类的继承位置&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h4&gt;20.说说线程的状态&lt;/h4&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;1：新建态(NEW)：当一个线程被创建成功后，但并没有执行它的start方时处于该状态&lt;/li&gt;
&lt;li&gt;2：就绪态(RUNNABLE)：一个线程执行了start方法进入就绪态开始竞争cpu调度权但还没有竞争到以完成它的任务&lt;/li&gt;
&lt;li&gt;3：运行态：一个线程对象获取到了cpu的资源调度权，并进入允许态开始完成它的任务&lt;/li&gt;
&lt;li&gt;4：阻塞态(BLOCKED)：若一个运行中的线程存在同步操作，此时锁被其他线程占用，该线程就会进入阻塞态等待获取锁&lt;/li&gt;
&lt;li&gt;5：限期等待(TIMED WAITING)：正在运行的线程执行了Thread.spleep()方法或者设置了timeout的wait()方法，join方法等进入一定时间的等待，系统自动唤醒。&lt;/li&gt;
&lt;li&gt;6：不限期等待(WAITING)：正在运行的线程执行了未设置timeout的wait方法或join方法进入等待，只有通过其他线程使用interrupt()或notify方法对其进行唤醒。&lt;/li&gt;
&lt;li&gt;7：死亡态(TEMINATED)：线程成功执行完毕或执行中抛出异常中断了线程会进入死亡态。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h4&gt;21.说说wait()和sleep()的区别&lt;/h4&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;1.所属的类型不同 - wait()是Object类的实例方法,调用该方法的线程将进入WAITING状态。 - sleep()是Thread类的静态方法,调用该方法的线程将进入TIMED_WTING状态。&lt;/li&gt;
&lt;li&gt;2.对锁的依赖不同 - wait()依赖于synchronized锁,通过监视器进行调用,调用后线程会释放锁。 - sleep()不依赖于任何锁,所以在调用后它也不会释放锁&lt;/li&gt;
&lt;li&gt;3.返回的条件不同 - 调用wait()进入等待状态的线程,需要由notify()/notifyAll()唤醒,从而返回。 - 调用sleep()进入超时等待的线程,需要在超时时间到达后自动返回。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h4&gt;22.说说怎么保证线程安全&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;线程的安全主要是原子性、可见性、有序性三个问题。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1、可见性：可见性问题是由处理器核心的缓存导致的,每个核心均有各自的缓存,而这些缓存均要与内存进行同步，volatile在多处理器开发中保证了共享变量的“可见性”&lt;/li&gt;
&lt;li&gt;2、原子性：一个或者多个操作在CPU执行的过程中不被中断的特性。线程切换可能造成原子性问题。synchronized关键字可以解决原子性、有序性和可见性问题，同时lock和Atomic开头的类也可以解决原子性问题&lt;/li&gt;
&lt;li&gt;3、有序性：程序执行的顺序按照代码的顺序执行，编译优化可能造成这个问题。volatile关键字通过禁止指令重排来解决有序性问题&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h4&gt;23.说说你了解的线程同步方式&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;java主要通过加锁的方式实现线程同步：主要有两种方式：synchronized关键字和lock接口。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;synchronized可以加在三个不同的位置，对应着三种不同范围，区别是锁对象的不同：
&lt;ul&gt;
&lt;li&gt;1.加在实例方法上，锁的就是当前的实例。&lt;/li&gt;
&lt;li&gt;2.加在静态方法和类上，锁的就是当前整个类。&lt;/li&gt;
&lt;li&gt;3.加在代码块上，锁的就是代码块里面的内容。我们应该合理的选择锁的对象。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;lock锁接口除了支持上述功能外，还支持了响应中断，超时机制，阻塞队列等。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h4&gt;24.说说你了解的线程通信方式&lt;/h4&gt;
&lt;p&gt;&amp;gt;&lt;/p&gt;
&lt;h4&gt;25.说说Java中常用的锁及原理&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;synchronized关键字和lock锁接口：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;synchronized关键字底层采用java对象头来存储锁信息的。&lt;/li&gt;
&lt;li&gt;lock锁接口是基于AQS实现的。AQS内部定义一个先进先出的队列实现锁的同步，同时还定义了同步状态来记录锁信息。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h4&gt;26.synchronized和Lock有什么区别&lt;/h4&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;synchronized是同步锁，可以修饰静态方法、普通方法和代码块。修饰静态方法时锁住的是类对象，修饰普通方法时锁住的是实例对象。当一个线程获取锁时，其他线程想要访问当前资源只能等当前线程释放锁。&lt;/li&gt;
&lt;li&gt;synchronized是java的关键字，Lock是一个接口。&lt;/li&gt;
&lt;li&gt;synchronized可以作用在代码块和方法上，Lock只能用在代码里。&lt;/li&gt;
&lt;li&gt;synchronized在代码执行完或出现异常时会自动释放锁，Lock不会自动释放，需要在finally中释放。&lt;/li&gt;
&lt;li&gt;synchronized会导致线程拿不到锁一直等待，Lock可以设置获取锁失败的超时时间。&lt;/li&gt;
&lt;li&gt;synchronized无法获知是否获取锁成功，Lock则可以通过tryLock判断是否加锁成功。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h4&gt;27.说说synchronize的用法及原理&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;synchronized可以修饰静态方法、普通方法、代码块。 能够保证同一个时刻只有一个线程执行该段代码，保证线程安全。 在执行完或者出现异常时自动释放锁。 synchronized作用在代码块时，它的底层是通过monitorenter、monitorexit指令来实现的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;28.说说你对AQS的理解&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;1、AQS队列同步器,用来构建锁的基础框架,Lock实现类都是基于AQS实现的。&lt;/p&gt;
&lt;p&gt;2、AQS是基于模板方法模式进行设计的,所以锁的实现需要继承AQS并重写它指定的方法。&lt;/p&gt;
&lt;p&gt;3、AQS内部定义了一个FIFO的队列来实现线程的同步,同时还定义了同步状态来记录锁的信息。&lt;/p&gt;
&lt;p&gt;4、AQS的模板方法,将管理同步状态的逻辑提炼出来形成标准流程,这些方法主要包括：独占式获取同步状态、独占式释放同步状态、共享式获取同步状态、共享式释放同步状态&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;29.说说你对线程池的理解&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;ThreadPoolExecutor构造器：&lt;br /&gt;
public ThreadPoolExecutor（int corePoolSize&lt;br /&gt;
int maximumPoolSize,&lt;br /&gt;
long keepAliveTIme,&lt;br /&gt;
TimeUnit unit,&lt;br /&gt;
BlockingQueue workQueue,&lt;br /&gt;
ThreadFactory threadFactory,&lt;br /&gt;
RejectedExecutionHandler handler)&lt;br /&gt;
参数一：指定线程池的线程数量（核心线程）：corePoolsize ——-&amp;gt; 不能小于0&lt;br /&gt;
参数二：指定线程池可支持的最大线程数：maximumPoolSize ———&amp;gt; 最大数量&amp;gt;=核心线程数量&lt;br /&gt;
参数三：指定临时线程的最大存活时间：keepAliveTime ———&amp;gt; 不能小于0&lt;br /&gt;
参数四：存活时间的单位（秒，分，时，天）：unit ———&amp;gt; 时间单位&lt;br /&gt;
参数五：指定任务队列：workQueue ———&amp;gt; 不能为null&lt;br /&gt;
参数六：指定用那个线程工厂创建线程：threadFactory ———&amp;gt; 不能为null&lt;br /&gt;
参数七：指定线程忙，任务忙的时候，新任务来了怎么办，即拒绝策略：handler ———&amp;gt; 不能为null&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;（重要！！！）注意两点：&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;临时线程什么时候创建：新任务提交时发现核心线程都在忙，任务队列满，还可以创建临时线程时才会创建。&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;什么时候开始拒绝任务：核心线程和临时线程都在忙，任务队列都满，新任务过来会拒绝。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;拒绝策略：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;策略&lt;/th&gt;
&lt;th&gt;详解&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ThreadPoolExecutor.AbortPolicy&lt;/td&gt;
&lt;td&gt;丢弃任务抛出RejectedExecutionException异常&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ThreadPoolExecutor.DiscardPolicy&lt;/td&gt;
&lt;td&gt;丢弃任务不抛出异常（不推荐）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ThreadPoolExecutor.DiscardOldestPolicy&lt;/td&gt;
&lt;td&gt;抛弃等待最久的任务并把当前任务加入队列&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ThreadPoolExecutor.CallerRunsPolicy&lt;/td&gt;
&lt;td&gt;由主线程负责调用的run()方法绕过线程池直接执行，即来新任务主线程亲自服务&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/blockquote&gt;
&lt;h4&gt;30.说说volatile的用法及原理&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;1.修饰被不同线程访问和修改的变量&lt;/p&gt;
&lt;p&gt;2.被其修饰的变量，系统每次用到它时都是直接从对应的内存中提取，而不会利用缓存（如寄存器），所有线程在任何时候所看到变量的值都是相同的&lt;/p&gt;
&lt;p&gt;3.volatile不能保证操作的原子性，因此不能代替synchronized&lt;/p&gt;
&lt;p&gt;4.能不使用就尽量不要使用&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;*31.说说你对ThreadLocal的理解&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;ThreadLocal即线程变量，它用于共享变量在多线程中的隔绝，即每个线程都有一个该变量的副本彼此互不影响也就不需要同步机制了，&lt;/p&gt;
&lt;p&gt;实现原理：每个Thread对象中都有一个ThreadLocal类的内部类ThreadLocalMap对象，他是一个键值形式的容器，以ThreadLocal对象的get和set方法来存取共享变量值，原理时：以ThreadLocal对象作为key来存取共享变量值。一个ThreadLocal用完后必须remove，否则会造成内存泄漏。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;*32.请你说说JUC&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;JUC是java.util.concurrent的缩写，这个包中包含了支持并发操作的各种工具。&lt;/p&gt;
&lt;p&gt;1.原子类：遵循比较和替换原则。可以用于解决单个变量的线程安全问题。&lt;/p&gt;
&lt;p&gt;2.锁：与Synchronized类似，在包含synchronized所有功能的基础上，还支持超时机制，响应中断机制，主要用于解决多个变量的线程安全问题。&lt;/p&gt;
&lt;p&gt;3.线程池：可以更方便的管理线程，同时避免重复开线程和杀线程带来的消耗，效率高。&lt;/p&gt;
&lt;p&gt;4.并发容器：例如ConcurrentHashMap，支持多线程操作的并发集合，效率更快。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;?33.Java哪些地方使用了CAS&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;1、CAS 比较并交换,比较典型的使用场景有原子类、AQS、并发容器。&lt;/p&gt;
&lt;p&gt;2、AQS：在向同步队列的尾部追加节点时,它首先会以CAS的方式尝试一次,如果失败则进入自旋状态,并反复以CAS的方式进行尝试。&lt;/p&gt;
&lt;p&gt;3、并发容器：以ConcurrentHashMap为例,它的内部多次使用了CAS操作。在初始化数组时,以CAS的方式修改初始化状态,避免多个线程同时进行初始化。在执行put方法初始化头节点时,它会以CAS的方式将初始化好的头节点设置到指定槽的首位,避免多个线程同时设置头节点。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;34.请说说你对Java集合的了解&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;java中的集合类主要都有Collection和Map这两个接口派生而出，其中Collection又派生出List,Set,Queue。所有的集合类都是List,set,queue,map这四个接口的实现类。其中，list代表有序的，可重复的数据集合；set代表无序的，不可重复的数据集合，queue代表先进先出的队列；map是具有映射关系的集合。最常用的实现类又ArrayList,LinkedList,HashMap,TreeMap,HashSet,TreeSet,ArrayQueue。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;35.你知道哪些线程安全的集合？&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;java.util包中的集合类大部分都是非线程安全的，例如：ArrayList/LinkedList/HashMap等等，但也有少部分是线程安全的，像是Vector和Hashtable，它们属于很古老的API了，是基于Synchronized实现的，性能很差，在实际的开发中不常用。一般可以使用collections工具类中的syncheronizedXxx()方法将非线程安全的集合包装成线程安全的类。在java5之后可以使用concurrent包提供的大量的支持并发访问的集合类，例如ConcurrentHashMap/CopyOnWriteArrayList等&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;36.请你说说HashMap底层原理&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;在1.8之前，HashMap的底层是数组加链表，在1.8之后是数组+链表+红黑树； 它的put流程是：基于哈希算法来确定元素位置，当我们向集合存入数据时，他会计算传入的key的哈希值，并利用哈希值取绝对值再根据集合长度取余来确定元素的位置，如果这个位置已经存在其他元素了，就会发生哈希碰撞，则hashmap就会通过链表将这些元素组织起来，如果链表的长度达到8时，就会转化为红黑树，从而提高查询速度。 扩容机制：HashMap中数组的默认初始容量为16，当达到默认负载因子0.75时，会以2的指数倍进行扩容。 Hashmap时非线程安全的，在多线程环境下回产生循环死链，因此在多线程环境下建议使用ConcurrentHashMap。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;37.请你说说HashMap和Hashtable的区别&lt;/h4&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;1.Hashtable在实现Map接口时保证了线程安全性，而HashMap则是非线程安全的。所以Hashtable的性能不如HashMap,因为为了保证线程它牺牲了一些性能。&lt;/li&gt;
&lt;li&gt;2.Hashtable不允许存入null，无论是以null作为key或value,都会引发异常但，HashMap是允许的。Hashtable是很古老的API，性能不好，不推荐使用，要在多线程下使用ConcurrrntHashMap,它不但保证了线程安全，也通过降低锁的粒度提高了并发访问时的性能&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h4&gt;38.HashMap是线程安全的吗？如果不是该如何解决&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;HashMap不是线程安全的，在添加数据的时候，会根据key和value计算出在底层数组中的位置，然后封装成entrey对象插入，但由于HashMap并没有做对应的线程安全处理，所以如果恰好两个线程同时操作的话，就有点会将其中一个数据覆盖掉，这不符合要求。 那么解决方法就是说，你可以在操作这个HashMap的时候手动的上锁，可以通过Lock也可以通过synchronized关键字。当然我更推荐直接使用java已经提供了的ConcurrentHashMap，其内部使用了Lock来解决线程安全问题，并且底层结构也进行了一些变动，上锁的时候只会锁对应下标的元素，不会对其他位置造成影响，即保证了线程安全，又保证了性能。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;*39.请你说说ConcurrentHashMap&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;ConcurrentHashMap是一个线程安全的集合，它的底层是数组+链表/红黑树构成的。 在1.7的时候采用segment数组+hashEntry的方式实现的，lock加在Segment的上面，在size计算的时候，首先是不加锁的，最多计算三次，前后两次的结果是相同的话那么结果就是准确的，如果不一样的话，那么就加锁，重新计算。 在1.8的时候废弃了这种算法，采用Synchronized+CAS+Node来保证并发安全的进行，使用一个volatile类型的变量baseCount来记录元素的个数，集合每次增加或者删除的时候，basecount就会通过addCount()来对baseCunt产生相应的变化，最后得到元素的个数。 初始值为16,每次扩容都是之前的二倍，不支持null值和null为key&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;40.说说你对ArrayList的理解&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;arraylist在jdk7.0的时候，创建容器的时候会在底层创建一个长度为10的object数组，在jdk8.0的时候，在创建容器的时候底层并不会立刻创建，只有在第一次调用add方法的时候才会创建一个长度为10的数组，默认情况下，扩容为原来容量的1.5倍，同时将原有数组中的值复制到新的数组中，并且arraylist属于有序的，可重复的集合，提供了iterator方法，增强了迭代能力。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;41.请你说说ArrayList和LinkedList的区别&lt;/h4&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;1.ArrayList的实现是基于数组,LinkedList的实现是基于双向链表。&lt;/li&gt;
&lt;li&gt;2.对于随机访问ArrayList要优于LinkedList,ArrayList可以根据下标以O(1)时间复杂度对元素进行随机访问,而LinkedList的每一个元素都依靠地址指针和它后一个元素连接在一起,查找某个元素的时间复杂度是O(N)。&lt;/li&gt;
&lt;li&gt;3.对于插入和删除操作,LinkedList要优于ArrayList,因为当元素被添加到LinkedList任意位置的时候,不需要像ArrayList那样重新计算大小或者是更新索引&lt;/li&gt;
&lt;li&gt;4.LinkedList比ArrayList更占内存,因为LinkedList的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h4&gt;42.请你说说List与Set的区别&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;list和set都是接口collection的子接口，list代表有序的可重复的集合，每个元素都有对应的顺序索引，可以通过索引来访问指定位置的集合元素。而set表示无序，不可重复的集合元素。但是它有支持排序的实现类treeset，treeset可以确保元素处于排序状态，并支持自然排序和定制排序两种方式，treeset是非线程安全的，内部元素的值不能为null&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;?43.请你说说BIO、NIO、AIO&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;BIO是阻塞IO，当用户线程发送请求后会一直阻塞知道内核将数据准备；NIO是非阻塞IO，用户线程发送请求后，可以做其他工作，并不断询问内核数据，但在数据复制阶段，用户线程依然属于阻塞状态。BIO和NIO都属于同步IO。AIO是异步IO，当用户线程发送请求后，内核会返回一个回调函数，但该回调函数不包含数据，之后用户线程可以去处理其他操作，当数据准备好后，内核会将数据发送给用户线程，而不必像同步IO中用户线程自己去读取。BIO只能处理一个请求，NIO可以处理多个请求。IO多路复用在NIO的基础上加入了事件机制，将用户请求注册到多路复用器上，然后监视是否有IO事件发生，如果有，会通知用户线程，IO多路复用的方式主要有select、poll、epoll。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;?44.请你说说IO多路复用&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;IO多路复用指的是单个线程能够同时完成对多个IO事件的监听处理。linux提供了select、poll和epoll三种多路复用方式。本质上是利用内核缓存fd描述文件，并内核完成对文件描述符的监听操作。selec是将所用文件描述符的集合从用户态拷贝到内核空间，底层采用的是数组。poll和select相似，主要区别是poll底层使用的是链表，所以其能够监听的文件描述符不受限制。但是这两种方法都需要多次的内核与用户空间的复制拷贝，并且用户空间还需要在O(N)的时间复杂度下对描述符进行遍历才具体知道哪一个文件描述符发生了事件。epoll在内核开辟空间底层采用红黑树，用户可以直接在内核创建需要需要关注的文件描述的节点，当事件发送内核将对应文件描述符直接存入队列并将其返回到用户空间。epoll这种方式可以减少每次调用时从用户空间复制到内核的操作，并且因为内核返回的发送事件描述符的队列，可以减少每次轮询的操作，使得在O(1)的时间复杂度就能找到发送事件的描述符。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;?45.请你讲一下Java NIO&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;NIO弥补了原来同步阻塞IO的不足，它在标准Java代码中提供了高速的、面向块的IO。通过定义包含数据的类，以及通过以块的形式处理这些数据。NIO包含三个核心的组件：Buffer（缓冲区）、Channel（通道）、Selector（多路复用器）。Buffer是一个对象，它包含一些写入或者要读出的数据。在读取数据时，它是直接读到缓冲区中的。在写入数据时，写入到缓冲区中，任何时候访问NIO中的数据，都是通过缓冲区进行操作。Channel是一个通道，可以通过它的读取和写入数据，它就像自来水管一样，网络数据通过Channel读取和写入。通道和流的不同之处在于通道是双向的，流只是在一个方向上移动而且通道可以用于读、写或者同时读写。Selector会不断地轮询注册在其上的Channel，如果某个Channel上面有心的TCP连接接入、读和写事件，这个Channel就处于就绪状态，会被Selector轮询出来，然后通过SelectionKey可以获取就绪Channel的集合，进行后续的IO操作&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;46.简单说下你对JVM的了解&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;JVM是Java语言跨平台的关键，Java在虚拟机层面隐藏了底层技术的复杂性以及机器与操作系统的差异性。运行程序的物理机千差万别，而JVM则在千差万别的物理机上面简历了统一的运行平台，实现了在任意一台JVM上编译的程序，都能在任何其他JVM正常运行。 JVM由三部分组成：类加载子系统、执行引擎和执行时数据区。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1）类加载子系统：可以根据指定的全限定名来载入类或接口。&lt;/li&gt;
&lt;li&gt;2）执行引擎：负责执行那些包含在被载入类的方法中的指令。&lt;/li&gt;
&lt;li&gt;3）当程序运行时，JVM需要内存来存储许多内容，例如：字节码、对象、参数、返回值、局部变量、运算的中间结果等，JVM会把这些东西都存储到运行时数据区中，以便于管理。而运行时数据区又可以分为方法区、堆、虚拟机栈、本地方法栈、程序计数器。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h4&gt;47.说说你了解的JVM内存模型&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;JVM由三部分组成：类加载子系统、执行引擎、运行时数据区 1、类加载子系统：可以根据指定的全限定名来载入类或接口。 2、执行引擎：负责执行那些包含在被载入类的方法中的指令。 3、运行时数据区：分为方法区、堆、虚拟机栈、本地方法栈、程序计数器。当程序运行时,JVM需要内存来存储许多内容,例如：字节码、对象、参数、返回值、局部变量、运算的中间结果等,把这些东西都存储到运行时数据区中,以便于管理。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;48.说说Java运行时数据区&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;Java运行时数据区由五部分组成：程序计数器、Java栈、本地方法栈、Java堆和方法区。程序计数器控制着程序下一步的执行，如循环、分值判断等，Java栈保存着方法运行时的数据结构，方法的调用和结束对应着一个帧栈的入栈和出栈，本地方法栈与Java栈作用类似，其作用对象是本地方法，Java堆中存储new的对象，也是垃圾回收的重要管理区域，方法区中保存着全局变量、静态变量等。方法区和Java堆是线程共享的，而程序计数器和Java栈是线程私有的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;49.说说JVM的垃圾回收机制&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;JVM的垃圾回收机制是遵循分代收集理论进行设计的，主要分为四种收集方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.新生代收集，目标为新生代的垃圾收集。&lt;/li&gt;
&lt;li&gt;2.老年代收集：目标为老年代的垃圾收集，目前只有CMS收集器会有这种行为。&lt;/li&gt;
&lt;li&gt;3.混合收集：目标为整个新生代及部分老年代的垃圾收集，目前只有G1收集器会有这种行为。&lt;/li&gt;
&lt;li&gt;4.整堆收集：目标为整个方法区和堆的垃圾收集。&lt;/li&gt;
&lt;li&gt;常见的垃圾回收算法包括：标记清除算法：缺点：内存碎片化，优点：速度快。&lt;/li&gt;
&lt;li&gt;标记复制算法：缺点占用内存大，优点：内存连续。&lt;/li&gt;
&lt;li&gt;标记整理算法：优点：内存连续，缺点：整理效率低。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h4&gt;50.说说JVM的垃圾回收算法&lt;/h4&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;1.引用计数法，每次赋值时均要维护引用计数器且计数器本身也有一定的消耗，较难处理循环引用，一般不采用这种方式&lt;/li&gt;
&lt;li&gt;2.复制算法，将内存分为两块，每次只使用其中一块，当这块内存用完，就将还活着的对象复制到另外一块上面，效率高且没有碎片，但是需要双倍的空间，年轻代中使用复制算法&lt;/li&gt;
&lt;li&gt;3.标记-清除，先标记要清除的对象，然后统一回收这些对象，不需要额外的空间，但是需要两次扫描耗时严重并且会产生内存碎片&lt;/li&gt;
&lt;li&gt;4.标记-整理，标记存活对象，然后将标记的存活对象按内存地址依次排序，清除边界外未标记的对象，没有内存碎片，但是需要移动对象。老年代一般用标记-清除和标记-整理的混合实现&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h4&gt;51.说说GC的可达性分析&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;可达性分析算法用于判断对象是否可以被回收，程序通过GC Roots中的对象为起点，以类之间的引用关系简历引用链，最终形成一个类似于数据结构中森林的一个结果，不存在与森林中的对象便是需要被回收的对象。这里的GC Roots主要包括线程栈中引用的变量，本地方法栈中引用的变量，方法区中的静态引用对象，常量池中的常量引用对象和被锁引用的对象。对一个对象真正的宣告回收需要经历两次标记过程，如果一个对象不再引用链上就会对它进行第一次标记，并判断它是否重新了finalize方法，若未重新或finalize方法已经被执行过了则会直接回收对象，否则会创建一个F-queue队列来存储这些对象，并启动一个低优先级的Finalizer线程去执行它们的finalize方法。第二次标记，稍后收集器会对队列中的对象进行可达性分析并标记，若仍然存在标记则表明该对象没有通过finalize方法实现自救则直接回收，否则对象复活。任何对象的finalize方法都只能被调用一次。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;52.请你说说Java的四种引用方式&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;java中的四种引用方式分别是：1，强引用，以new关键字创建的引用都是强引用，被强引用引用的对象永远都不会被回收。2，软引用：以SoftRererenc引用对象，被弱引用引用的对象只有在内存空间不足时会被垃圾回收。3，弱引用，以WeakReference引用对象，被弱引用引用的对象一定会被回收，它只能存活到下一次垃圾回收。4，虚引用：以PhantomReference引用对象，一个对象被引用引用后不会有任何影响，也无法通过该引用来获取该对象，只是其再被垃圾回收时会收到一个系统通知。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;53.请你讲下CMS垃圾回收器&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;CMS垃圾收集器采用标记清除算法，使用多线程实现，所以它的应用场景一般为服务端系统的老年代。它是一个以达到在垃圾回收期间用户线程低停顿为目标的垃圾收集器。CMS垃圾收集器垃圾回收分为四个阶段：1，初始标记：只对与GCRoots有直接关键的对象进行可达性分析的标记。2，并发标记：标记整个GCRoots引用链中的对象，与用户线程并发执行。3，重新标记：用于更新在并发标记过程中被复活的对象。4，并发清除：清除标记阶段判断的已死亡的对象。该流程与用户线程并发执行。缺点：1，它使用标记清除算***导致内存碎片化，2，会产生在并发清除阶段的浮动垃圾，只有到下一次垃圾回收时才会被清除。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;54.请你讲下G1垃圾回收器&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;G1回收器是一个多线程，可以同时收集新生代和老年代的一个垃圾收集器。G1将整个堆内存区域划分为多个大小相等的region，追踪每个Region可以回收对象的大小和预估时间，并以region为单位进行垃圾收集并获取每个region的收集效率和收集收益，通过一张优先级表对其进行维护。 G1可以设置垃圾回收的预期停顿时间（STW）。 G1的年轻代和老年代空间并不是固定的，当现有年轻代分区占满时，JVM会分配新的空闲Region加入到年轻代空间，老年代也是如此。 G1 GC的回收过程中有内存整理，理论上不会产生内存碎片！ G1垃圾收集器垃圾回收主要包括四个流程：1，初始标记 2，并发标记（类似于CMS） 3，最终标记 4，并发筛选回收：根据优先级表选择分区进行垃圾回收，用户线程不停顿。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;55.说说类加载机制&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;类加载的过程中首先判断这个是否被加载过，如果没有被加载过，那么调用类加载器进行加载，判读这个类是否符合规范，如果不符合就抛出异常，加载成功就会生成class对象。 接下来是链接过程，分为三步：准备，验证，准备，解析。 验证：确保文件符合规范，不会危害虚拟机自身的安全，对文件格式，字节码，元数据，符号引用进行验证。 准备:为类变量分配初始空间以及默认初始值，即零值。这里不会为实例变量分配，类变量分配在方法区中，实例变量跟随对象分配在堆中，final修饰的在编译期间就分配了，在准备阶段会显式的初始化。 解析：将常量池内的直接引用转为直接引用的过程。 链接过程完成之后开始初始化的过程： 初始化阶段就是执行类构造器方法的过程。此方法不需要定义，一个类只会被加载一次，虚拟机必须保证在多线程条件下类的构造方法是被加锁的。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;56.说说JVM的双亲委派模型&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;双亲委派模型的工作过程是,如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去完成加载&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;57.说说类的实例化过程&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;类实例化的过程。类加载，分配内存，初始化零值，状态设置，构造函数。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;58.请你说说内存溢出&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;内存溢出：指程序运行过程中申请的内存大于系统能够提供的内存，导致无法申请到足够的内存，于是就发生了内存溢出。引起内存溢出的原因：1.内存加载的数据量过于庞大。如一次从数据库取出的过多的数据。2.代码中存在死循环或者死循环中产生大量的对象实体。3.启动内存值设定过小。解决内存溢出的方案：修改JVM启动参数，直接增加内存。2.检查错误日志，查看“OutOfMemory”错误之前是否存在异常。3.对代码进行debug分析。4.使用内存工具动态查看内存使用情况。常见的内存溢出出现在：1.堆。对象创建过多2.栈溢出3.方法区和运行时常量池；创建大量动态类。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;59.请你说说内存泄漏&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;内存泄露：指不再使用的对象仍然被引用，导致垃圾收集器无法回收它们的内存，最终导致OOM。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;60.请你说说进程间的通信方式&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;1.管道：管道本质是内核中维护的一块内存缓冲区。2.命名管道：因为无名管道只适用于具有亲缘关系的线程，所以就衍生出来命名管道，这样就可以实现了非亲缘关系进程之间的通信。3.信号：一种通知机制。4.消息队列：是一个消息链表，既可以读消息，也可以写消息。5.共享内存：多个线程共享一片内存区域。6.内存映射：就是将磁盘文件数据映射到内存，通过修改内存就能修改磁盘文件。7socket接口，socket接口一般用于不同主机上进程之间的通信&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;61.请你说说线程和协程的区别&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;协程与线程的区别: 1) 一个线程可以多个协程，一个进程也可以单独拥有多个协程。 2) 线程进程都是同步机制，而协程则是异步。 3) 协程能保留上一次调用时的状态，每次过程重入时，就相当于进入上一次调用的状态。 4）线程是抢占式，而协程是非抢占式的，所以需要用户自己释放使用权来切换到其他协程，因此同一时间其实只有一个协程拥有运行权，相当于单线程的能力。 5）协程并不是取代线程, 而且抽象于线程之上, 线程是被分割的CPU资源, 协程是组织好的代码流程, 协程需要线程来承载运行, 线程是协程的资源, 但协程不会直接使用线程, 协程直接利用的是执行器(Interceptor), 执行器可以关联任意线程或线程池, 可以使当前线程, UI线程, 或新建新程.。 6）线程是协程的资源。协程通过Interceptor来间接使用线程这个资源。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;62.请你说说线程和协程的区别&lt;/h4&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;线程是操作系统的资源,线程的创建、切换、停止等都非常消耗资源,而创建协程不需要调用操作系统的功能,编程语言自身就能完成,所以协程也被称为用户态线程,协程比线程轻量很多；&lt;/li&gt;
&lt;li&gt;线程在多核环境下是能做到真正意义上的并行,而协程是为并发而产生的；&lt;/li&gt;
&lt;li&gt;一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作的运行；&lt;/li&gt;
&lt;li&gt;线程进程都是同步机制,而协程则是异步；&lt;/li&gt;
&lt;li&gt;线程是抢占式,而协程是非抢占式的,所以需要用户自己释放使用权来切换到其他协程,因此同一时间其实只有一个协程拥有运行权,相当于单线程的能力；&lt;/li&gt;
&lt;li&gt;操作系统对于线程开辟数量限制在千的级别,而协程可以达到上万的级别。&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h4&gt;63.请你说说死锁定义及发生的条件&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;死锁：两个或者两个以上的线程互相争夺对方的资源而不释放自己的资源，从而导致死锁的产生。 死锁产生的条件： 互斥：一个资源在同一个时刻只能由一个线程执行 请求与保持：一个线程在请求被占用资源时，对已经获得的资源保持不放。 循环等待：发生死锁时所有的线程都会形成一个死循环，一直阻塞。 不可剥夺条件：线程对所获得的资源在未使用完时不能被其他线程剥夺，只能自己释放。 避免死锁的方法就是破坏死锁产生的条件。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;64.请你说说内存管理&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;linux操作系统采用段页式内存管理方式：页式存储管理可以有效的提高内存利用率，段式内存管理能反映程序的逻辑结构并有利于段的共享。将这两种方法结合起来就形成了段页式储存管理方式。段页式储存管理方式就是先建好用户程序分成若干个段，再把每个段分成若干页，并为每个段赋予一个段名&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;65.请你说说虚拟内存和物理内存的区别&lt;/h4&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;物理内存 以前,还没有虚拟内存概念的时候,程序寻址用的都是物理地址。程序能寻址的范围是有限的,这取决于 CPU 的地址线条数。比如在 32 位平台下,寻址的范围是 2^32 也就是 4G。并且这是固定的,如果没有虚拟内存,且每次开启一个进程都给 4G 物理内存,就可能会出现很多问题： - 因为物理内存是有限的,当有多个进程要执行的时候,都要给 4G 内存,很显然内存不够,这很快就分配完了,于是没有得到分配资源的进程就只能等待。当一个进程执行完了以后,再将等待的进程装入内存。这种频繁的装入内存的操作效率很低 - 由于指令都是直接访问物理内存的,那么任何进程都可以修改其他进程的数据,甚至会修改内核地址空间的数据,这是不安全的&lt;/li&gt;
&lt;li&gt;虚拟内存 由于物理内存有很多问题,所以出现了虚拟内存。虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存（一个连续完整的地址空间）,而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h4&gt;66.请你说说IO多路复用（select、poll、epoll）&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;IO多路复用指的是单个进程或者线程能同时处理多个IO请求，select，epoll，poll是LinuxAPI提供的复用方式。本质上由操作系统内核缓冲IO数据，使得单个进程线程能监视多个文件描述符。select是将装有文件描述符的集合从用户空间拷贝到内核空间，底层是数组，poll和select差距不大，但是底层是链表，这就代表没有上限，而select有数量限制。epoll则是回调的形式，底层是红黑树，避免轮询，时间复杂度从O（n）变为O（1）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;67.epoll原理&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;epoll是一种高效地IO多路复用技术。调用epoll_create()会创建一个结构体数据，里面包含一个用于遍历扫描文件描述符状态的红黑树和一个就绪列表。调用epoll_ctr()可以进行增删改要监听的文件描述符及事件。调用epoll_wt()就会让内核检测就绪事件，将就绪事件到该表列表返回。epoll有两种触发机制。水平触发：当文件描述符状态改变时就立即进行IO操作，如果不进行处理将继续通知。边沿触发：是高速工作方式。该机制默认你已经知道了状态描述符改变，再你改变IO状态后描述状态改变后不会通知。该种方式减少了epoll的重复触发次数，提升了效率。必须使用非阻塞接口防止因一个文件描述符阻塞读写其他任务饿死&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;68.请你说说MySQL的事务隔离级别&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;事务隔离级别是为了解决脏读、不可重复读、幻读 脏读：一个事务读取了另一个事务未提交的数据 不可重复读：事务A两次读取的数据不一致，读第二次之前可能有其他事务修改了这个数据并提交了 幻读：事务A两次读取数据库，两次查询结果的条数不同，称为幻读。行数变了即为幻读，数据变了即为不可重复度 事务隔离级别如下： 读未提交：以上三个问题都解决不了 读已提交：只能解决脏读 可重复读：mysql的默认隔离级别，能解决脏读和不可重复读，包含了间隙锁，可以防止幻读 串行化：都可以解决。（为每个读取操作加一个共享锁）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;69.请你说说innodb和myisam的区别&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;1.innodb支持事务，myisam不支持。2.Innodb支持行级锁；myisam支持表级锁。3.Innodb的增删改性能更优；Myisam的查询性能更优。4.Innodb不支持全文索引，myisam默认支持。5.Innodb默认支持外键，而myisam不支持。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;70.请你说说MySQL索引,以及它们的好处和坏处&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;MySQL索引是一种帮助快速查找数据的数据结构，可以把它理解为书的目录，通过索引能够快速找到数据所在位置。场景的索引数据结构有：Hash表（通过hash算法快速定位数据，但不适合范围查询，因为需要每个key都进行一次hash）、二叉树（查找和修改效率都比较高），但是在InnoDB引擎中使用的索引是B+Tree，相较于二叉树，B+Tree这种多叉树，更加矮宽，更适合存储在磁盘中。使用索引增加了数据查找的效率，但是相对的由于索引也需要存储到磁盘，所以增加了存储的压力，并且新增数据时需要同步维护索引。但是合理的使用索引能够极大提高我们的效率！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;71.请你讲讲B树和B+树&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;B树和B+树都是多路平衡查找树。B树中所有节点都存放数据。B+树只有叶子结点存放数据，其他节点存放key。B树中的叶子结点是独立的，B+书中的叶子结点通过链与相邻叶子结点连接。B树查找使用的是二分查找，没有查找到叶子结点就可能结束，而B+树必须从根节点进行查找，查询效率更稳定。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;72.MySQL主从同步是如何实现的&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;复制（replication）是MySQL数据库提供的一种高可用高性能的解决方案,一般用来建立大型的应用。总体来说,replication的工作原理分为以下3个步骤： 1. 主服务器（master）把数据更改记录到二进制日志（binlog）中。 2. 从服务器（slave）把主服务器的二进制日志复制到自己的中继日志（relay log）中。 3. 从服务器重做中继日志中的日志,把更改应用到自己的数据库上,以达到数据的最终一致性&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;73.请你说说数据库索引的底层数据结构&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;数据库索引用的是B+树，它的叶子节点存储所有的数据，并且叶子节点通过指针进行链接，树的查找效率与其高度有关，树的高度越高，查找效率越低，B+树的高度一般在2-4层，这意味着查找到某一键值只需要2-4次IO操作，而且现在的数据库1s中至少可以做100次IO操作，2-4次的IO操作意味着查询时间只需要0.02-0.04s，所以其查找效率很高。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;74.请你说说聚簇索引和非聚簇索引&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;它们两个的最大区别就是索引和数据是否存放在一起。 聚簇索引：索引和数据存放在一起，叶子节点保留数据行。 非聚簇索引：索引和数据分开存放，叶子节点存放的是指向数据行的地址。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;75.请你说说数据库引擎有哪些,各自有什么区别&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;1.InnoDB引擎支持MySQL事务，具有提交，回滚和崩溃恢复功能能够更加安全的保护用户数据；支持行级锁，提高多用户并发和性能；支持外键，维护数据完整性。 2.MyISAM引擎，占用空间较小，支持表级锁，能够限制读写工作的负载的性能，查询效率较高，常用于只读场景。 3.Memory引擎，将所有数据存储在RAM（主存）中，在非关键字查询时，查询效率较高。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;76.数据库为什么不用红黑树而用B+树&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;索引的数据结构会被存储在磁盘中，每次查询都需要到磁盘中访问，对于红黑树，树的高度可能会非常的高，会进行很多次的磁盘IO，效率会非常低，B+树的高度一般为2-4，也就是说在最坏的条件下，也最多进行2到4次磁盘IO，这在实际中性能时非常不错的&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;77.请你介绍一下数据库的ACID&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;原子性&lt;/p&gt;
&lt;p&gt;事务被视为不可分割的最小单元，事务的所有操作要么全部提交成功，要么全部失败回滚。&lt;/p&gt;
&lt;p&gt;回滚可以用回滚日志（Undo Log）来实现，回滚日志记录着事务所执行的修改操作，在回滚时反向执行这些修改操作即可。&lt;/p&gt;
&lt;p&gt;一致性&lt;/p&gt;
&lt;p&gt;数据库在事务执行前后都保持一致性状态。在一致性状态下，所有事务对同一个数据的读取结果都是相同的。&lt;/p&gt;
&lt;p&gt;隔离性&lt;/p&gt;
&lt;p&gt;一个事务所做的修改在最终提交以前，对其他事务是不可见的。&lt;/p&gt;
&lt;p&gt;持久性&lt;/p&gt;
&lt;p&gt;一旦事务提交，则其所做的修改将会永远保存到数据库中。即使系统发生崩溃，事务执行的结果也不能丢失。&lt;/p&gt;
&lt;p&gt;系统发生崩溃可以用重做日志（Redo Log）进行恢复，从而实现持久性。与回滚日志记录数据的逻辑修改不同，重做日志记录的是数据页的物理修改。&lt;/p&gt;
&lt;p&gt;事务的 ACID 特性概念简单，但不是很好理解，主要是因为这几个特性不是一种平级关系：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;只有满足一致性，事务的执行结果才是正确的。&lt;/li&gt;
&lt;li&gt;在无并发的情况下，事务串行执行，隔离性一定能够满足。此时只要能满足原子性，就一定能满足一致性。&lt;/li&gt;
&lt;li&gt;在并发的情况下，多个事务并行执行，事务不仅要满足原子性，还需要满足隔离性，才能满足一致性。&lt;/li&gt;
&lt;li&gt;事务满足持久化是为了能应对系统崩溃的情况。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h4&gt;78.请你说说数据库的索引是什么结构,为什么不用哈希表&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;MySQL中的索引是采用B+树的，哈希表的查询效率很高，但是哈希表最大的问题就是不支持范围和顺序查找，还会产生哈希冲突问题&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;79.请你说说InnoDB的MVCC&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;多版本并发控制，最大的优点是无锁并发，读不加锁，因此读写不冲突，并发性好，它为每个数据都根据事务维护了多个版本，使其在并发事务中解决了读写冲突，同时使用快照读为MVCC提供了非阻塞读功能，所以它是一个用户解决读写冲突的无锁并发控制机制，它通过数据表的三个隐藏字段分别为 db_trx_id(最长操作事务id)，roll_point(undolog指针)，db_row_id(唯一性自增张列，可能没有)，undolog和readview实现。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;80.请你说说索引怎么实现的B+树,为什么选这个数据结构&lt;/h4&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;如何实现： - 索引的本质上就是通过预排序+树型结构来加快检索效率，而MySQL中使用InnoDB和MyISAM引擎时都使用了B+树实现索引 - 为什么选取： - 在二叉查找树上查找一个数据时，当出现海量信数据时，查找效率将大大折扣 - B+树是一种一棵平衡多路查找树，可以有效减少磁盘IO同时B+树增加了叶子节点间的连接，能保证范围查找时找到起点和终点后能快速取出需要的数据&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h4&gt;81.请你说说乐观锁和悲观锁&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;乐观锁：乐观锁总是假设最好的情况，每次去拿数据的时候默认别人不会修改，所以不会上锁，只有当更新的时候会判断一下在此期间有没有人更新了这个数据。适用于多读，可以使用版本号机制进行控制 悲观锁：悲观锁总是假设最坏的情况，每次去拿数据是都认为别人会修改，所以每次在拿数据时都会上锁，这样别人想拿这个数据时会阻塞直到拿到锁。mysql数据库的共享锁和排他锁都是悲观锁的实现。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;82.说说你对Spring Boot的理解,以及它和Spring的区别&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;（1）1、从本质上来说,Spring Boot就是Spring,它帮你完成了一些Spring Bean配置。 2、Spring Boot使用“习惯优于配置”的理念让你的项目快速地运行起来 3、但Spring Boot本身不提供Spring的核心功能,而是作为Spring的脚手架框架,达到快速构建项目的目的&lt;/p&gt;
&lt;p&gt;（2）Spring Boot优点, 可以快速构建项目 - 可以对主流开发框架的无配置集成 - 项目可独立运行,无需外部依赖Servlet容器 - 提供运行时的应用监控 - 可以极大地提高开发、部署效率 - 可以与云计算天然集成&lt;/p&gt;
&lt;p&gt;（3）核心功能： 1. 自动配置 针对很多Spring应用程序常见的应用功能,Spring Boot能自动提供相关配置。 2. 起步依赖 Spring Boot通过起步依赖为项目的依赖管理提供帮助。起步依赖其实就是特殊的Maven依赖和Gradle依赖,利用了传递依赖解析,把常用库聚合在一起,组成了几个为特定功能而定制的依赖。 3. 端点监控 Spring Boot 可以对正在运行的项目提供监控。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;83.说说Spring Boot常用的注解&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;@SpringBootApplication:它是SpringBoot的核心注解，用于开启自动配置，准确的说是通过该注解内的@EnablAutoConfiguration注解实现的自动配置。 @EnableAutoConfiguration:自动配置注解，在启动Spring应用程序上下文时进行自动配置，自动配置通常是基于项目classpath中引入的类和已定义的bean来实现的。 @Import:@EnableAutoConfiguration的关键功能是通过@Import注解导入的ImportSelector来完成的。 @Congiguration：配置类注解，根据一些特定条件来控制bean的实例化的行为。 @ComponentScan：位置在SpringBoot的启动类上，Spring包扫描。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;84.说说Soring Boot的起步依赖&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;SpringBoot将日常企业应用研发中的各种常见都抽取出来，做成一个个的starter（启动器），starter中整合了该场景下各种可能用到的依赖，用户只需要在Maven中引入starter依赖，SpringBoot就能自动扫描到要加载的信息并启动相应的默认配置。starter提供了大量的自动配置，让用户摆脱了处理各种依赖和配置的困扰。所有这些starter都遵循这约定俗成的默认配置，并允许用户调整这些配置，即遵循“约定大于配置”的原子&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;85.说说Spring Boot的启动流程&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;调用run方法，run方法执行流程 =》获取监听器参数配置-打印Banner信息-创建并初始化容器-监听器发送通知&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;86.说说Spring Boot的自动装配&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;靠EnableAutoConfigurations注解，首先会从spring.factories中寻找有没有AutoConfiguration类满足Conditional注解的生效条件，有的话，就是实例化该AutoConfiguration类，然后加载到spring容器就实现了spring的自动装配&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;87.简单介绍Spring&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;Spring是一个轻量级的免费框架，它有两大核心功能，分别是ioc和aop，ioc控制反转是将创建对象的权限交给spring容器来进行管理，可以很好的起到解耦和的作用，aop是一种编程思想，底层使用的是动态代理，可以在程序原有的功能上进行增强，常用的地方有日志记录，权限验证等&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;88.说说你对IoC的理解&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;IoC：控制反转。控制：对象的创建的控制权限；反转：将对象的控制权限交给spring。之前我们创建对象时用new，现在直接从spring容器中取，维护对象之间的依赖关系，降低对象之间的耦合度。 实现方式为DI,依赖注入，有三种注入方式：构造器、setter、接口注入&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;89.说说你对AOP的理解&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;AOP面向切面编程。是spring两大核心之一，它是一种编程思想，是对OOP的一种补充。它可以对业务逻辑的各个部分进行隔离，降低耦合，提高代码的可重用性。它的底层是通过动态代理实现的。它的应用场景有事务、日志管理等。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;90.说说Bean的生命周期&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;创建，初始化，调用，销毁； bean的创建方式有四种，构造器，静态工厂，实例工厂，setter注入的方式。 spring在调用bean的时候因为作用域的不同，不同的bean初始化和创建的时间也不相同。 在作用域为singleton的时候，bean是随着容器一起被创建好并且实例化的， 在作用域为pritotype的时候，bean是随着它被调用的时候才创建和实例化完成。 然后程序就可以使用bean了，当程序完成销毁的时候，bean也被销毁&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;91.说说@Autowired和@Resource注解的区别&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;@Autowied是Spring提供的注解，@Resource是JDK提供的注解。@Autowied是只能按类型注入，@Resource默认按名称注入，也支持按类型注入。@Autowired按类型装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它required属性为false,如果我们想使用按名称装配,可以结合@Qualifier注解一起使用&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;92.说说Spring事务管理&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;spring支持编程式事务管理和声明式事务管理两种方式： ①编程式事务管理使用TransactionTemplate。 ②声明式事务管理建立在AOP之上的。其本质是通过AOP功能，对方法前后进行拦截，将事务处理的功能编织到拦截的方法中，也就是在目标方法开始之前启动一个事务，在执行完目标方法之后根据执行情况提交或者回滚事务。 声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码，只需在配置文件中做相关的事务规则声明或通过@Transactional注解的方式，便可以将事务规则应用到业务逻辑中，减少业务代码的污染。唯一不足地方是，最细粒度只能作用到方法级别，无法做到像编程式事务那样可以作用到代码块级别。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;93.说说Bean的作用域,以及默认的作用域&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;作用域分为singleton、prototype、request、session、globalsession。singleton表示在spring容器中仅存在一个实例，以单例的方式存在，prototype：每次调用都会返回一个新的实例，request：每次HTTP请求都会创建一个新的bean，session：同一个HTTP session共享一个bean，不同的HTTP请求使用不同的session，global session：同一个全局session共享一个bean。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;94.说说BeanFactory和FactoryBean的区别&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;BeanFactory是最基础的IOC容器，给Spring 的容器定义一套规范，给IOC容器提供了一套完整的规范； FactoryBean只是SpringIOC容器创建Bean的一种形式；&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;95.说说你对MVC的理解&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;Spring mvc 是一个基于java的实现了mvc设计模式的轻量级web框架，在这种模式下软件被分为三层，即model、view、Controller。将软件分层的好处是 可 以将对象之间的耦合度降低，便于代码的维护。model封装了数据和对数据的操作，是实际进行数据处理的地方，view负责进行模型的展示，一般就是我们见 到的用户界面 Controller控制器负责视图和模型之间的交互主要负责两方面的动作，一是把用户的请求分发到相应的模型，二是把模型的改变及时地响应到 视图上。Spring mvc框架已经成为了mvc模式地最主流实现，前端控制器是DispatcherServlet接口实现类，映射处理器是HandlerMapping接口实现类，视图 解析器是ViewResolver接口实现类，页面控制器是Controller接口实现类&lt;/p&gt;
&lt;p&gt;MVC是一种设计模式，将软件分为三层，分别是模型层，视图层，控制器层。其中模型层代表的是数据，视图层代表的是界面，控制器层代表的是逻辑处理，是连接视图与模型之前的桥梁。降低耦合，便于代码的维护&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;96.介绍一下Spring MVC的执行流程&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;SpringMVC 的执行流程如下。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用户点击某个请求路径，发起一个 HTTP request 请求，该请求会被提交到 DispatcherServlet（前端控制器）；&lt;/li&gt;
&lt;li&gt;由 DispatcherServlet 请求一个或多个 HandlerMapping（处理器映射器），并返回一个执行链（HandlerExecutionChain）。&lt;/li&gt;
&lt;li&gt;DispatcherServlet 将执行链返回的 Handler 信息发送给 HandlerAdapter（处理器适配器）；&lt;/li&gt;
&lt;li&gt;HandlerAdapter 根据 Handler 信息找到并执行相应的 Handler（常称为 Controller）；&lt;/li&gt;
&lt;li&gt;Handler 执行完毕后会返回给 HandlerAdapter 一个 ModelAndView 对象（Spring MVC的底层对象，包括 Model 数据模型和 View 视图信息）；&lt;/li&gt;
&lt;li&gt;HandlerAdapter 接收到 ModelAndView 对象后，将其返回给 DispatcherServlet ；&lt;/li&gt;
&lt;li&gt;DispatcherServlet 接收到 ModelAndView 对象后，会请求 ViewResolver（视图解析器）对视图进行解析；&lt;/li&gt;
&lt;li&gt;ViewResolver 根据 View 信息匹配到相应的视图结果，并返回给 DispatcherServlet；&lt;/li&gt;
&lt;li&gt;DispatcherServlet 接收到具体的 View 视图后，进行视图渲染，将 Model 中的模型数据填充到 View 视图中的 request 域，生成最终的 View（视图）；&lt;/li&gt;
&lt;li&gt;视图负责将结果显示到浏览器（客户端）。&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h4&gt;97.在MyBatis中$和#有什么区别&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;$设置参数时,MyBatis会创建普通的SQL语句,然后在执行SQL 语句时将参数拼入SQL #设置参数时,MyBatis会创建预编译的SQL语句,然后在执行SQL时MyBatis会为预编译SQL中的占位符赋值,预编译的SQL语句执行效率高,并且可以防止注入攻击,效率和安全性都大大优于前者&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;98.介绍一下MyBatis的缓存机制&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;MyBatis的缓存机制，一级缓存也称为本地缓存，它默认启用且不能关闭。一级缓存存在于SqlSession的生命周期中，即它是SqlSession级别的缓存，在同一个SqlSession中查询时，MyBatis会把执行的方法和参数通过算法生成缓存的键值，将键值和查询结果存入一个Map对象中，如果同一个SqlSession中执行的方法和参数完全一致，则会将缓存的对象返回；二级缓存则为SqlSessionFactory,mybaits的全局配置setting有一个参数cacheEnabled,这个参数是二级缓存的全局开关，默认值是true,初始状态为启用状态，映射语句文件中的所有SELECT 语句将会被缓存。 - 映射语句文件中的所有时INSERT 、UPDATE 、DELETE 语句会刷新缓存。 - 缓存会使用Least Recently Used ( LRU ,最近最少使用的）算法来收回&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;99.说说你对Redis的了解&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;Redis是一款基于键值对的NoSQL数据库，Redis中拥有string(字符串)，hash(哈希)、list(列表)、set（集合）等多种数据结构，redis将数据写进内存的性能很快，不仅如此，如遇到系统崩溃，内存中的数据不会丢失；redis访问速度快、支持的数据类型丰富，很适合用来储存热点数据、 而且适用业务广，如可以运用expire命令来做限时业务，设置一个键的生存时间，到时间后redis会自动删除它，，如排行榜可以借住redis的SortedSet进行热点数据的排序，还有分页查询，模糊查询，点赞好友等&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;100.请你说说Redis的数据类型&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;Redis拥有五种基本数据类型和四种特殊的数据类型。 五种基本数据类型： 1）String：String是Redis中最基本的数据类型，可以存储任何数据，包括二进制数据、序列化的数据、JSON化的对象甚至是图片。 2）List：List是字符串列表，按照插入的顺序排序，元素可以重复，你可以添加一个元素到哦列表的头部或者尾部，底层是一个链表结构。 3）Set：Set是一个无序不重复的集合。 4）Hash：Hash是String类型的filed和value的集合，适合用于存储对象。 5）Zset：Zset和set一样也是String类型元素的集合，且不允许有重复的元素，但不同的是Zset的每个元素都会关联一个分数，分数可以重复，Redis通过分数来为集合汇总的成员进行从小到大的排序。 四种特殊数据类型 1）bitmap 2）hyperloglog 3）geo 4）stream&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;101.请你说说Redis数据类型中的zset,它和set有什么区别？底层是怎么实现的？&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;zset是有序的，而set是无序的。 zset底层使用的是压缩列表以及跳跃表，当元素数量小于128个，所有member的长度都小于64字节，是使用压缩列表。不满足这两个条件时使用跳跃表。 set底层是hashtable和inset.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;102.详细的说说Redis的数据类型&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;redis中常用的五种数据结构：string、list、set、zset、hash。String结构底层是一个简单动态字符串，支持扩容，存储字符串。list存储线性有序且可重复的元素，底层数据结构可以是双向链表/压缩列表。set存储不可重复的元素，一般用于求交集、差集等，底层数据结构可以是hash和整数数组，zset存储的是有序不可重复的元素，zset为每个元素添加了一个score属性作为排序依据，底层数据结构可以是ziplist和跳表，hash类型存储的是键值对，底层数据结构是ziplist和hash。redis会在性能以及节省内存间考虑，选择最适合当前状态的底层数据结构实现&lt;/p&gt;
&lt;p&gt;list（有序可重）的底层数据结构是双向链表/压缩列表&lt;/p&gt;
&lt;p&gt;set（不可重）=hash+整数数组，&lt;/p&gt;
&lt;p&gt;zset（有序不重）=ziplist+跳表&lt;/p&gt;
&lt;p&gt;hash（存键值对）=ziplist+hash&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;103.说说Redis的单线程架构&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;redis采用的是单线程+IO多路复用技术。这里单线程指的是redis中读写操作和网络IO使用的是是有一个线程来完成，但是其他操作是有其他线程完成，例如持久化操作。单线程既可以简化数据结构和算法的实现，同时也消除了线程切换和锁竞争所带来的消耗。redis中采用的IO多路复用技术实现了单线程下同时处理多个IO请求。redis为什么这么快：1.单线程进行读写操作，避免线程切换和锁竞争带来的消耗。2：redis操作是在内存中进行的。3.最重要的就是：采用了IO多路复用技术，实现了在网络IO中能够处理大量并发请求，实现高吞吐率。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;104.说说Redis的持久化策略&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;1.RDB: redis database 在指定的时间间隔内，将内存中的数据集的快照写入磁盘，文件名dump.rdb 适合大规模的数据恢复，对数据库的完整性和一致性要求不是很高 一定时间间隔备份一次，如果数据库意外down掉，就会失去最后一次快照的所有修改 2.AOF: append only file 以日志的形式记录每个写操作，只允许追加文件，不允许改写文件，redis启动时会读取这个文件，并从头到尾执行一遍，以此来恢复数据，文件名appendonly.aof 在最恶劣的环境下，也丢失不会超过2秒的数据，完整性较高，但是会对磁盘持续的进行IO，代价太大。企业级最少需要5G才能支持 如果.aof文件大小超过原来的一倍，会进行重写压缩，保留最小的指令集合 3.优先级 aof&amp;gt;rdb&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;105.说说Redis的主从同步机制&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;主从同步分为全量同步和增量同步，从机第一次连接主机时不会携带主机id和数据偏移量，主机会对从机的主机id进行校验，如果不是则说明是第一次连接需要进行全量同步，原理就是将当前数据写到RDB文件发送给从机，从机接收到文件之后将数据读取到从机的内存中，增量同步是第二次和之后连接才会发生，当从机第一次同步完成之后，主机在这期间数据发生变化，会将命令存储在缓冲区，当校验到从机的id正确时会获取从机的偏移量，主机从偏移量记录的命令开始将从机没同步的数据的操作命令发送给从机执行，执行完成后即完成了数据同步&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;106.说说Redis的缓存淘汰策略&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;惰性删除： 当你访问一个key的时候，redis检查它的过期时间，如果过期就会删除。 定期删除：redis会将设置了过期事件的key全都放入一个字典中，然后进行扫描，过期了的key就会被删除。但是扫描不是全局扫描，而是一种简单的贪心算法，从字典中随机选择20个key，将过期的key删除，当过期的key超过一定的比例时，将会再次随机选择，知道随机选择的key小于一定比例。 当写入数据超过最大内存限制时，redis也有8种淘汰方案： 1. 直接报错 2. 删除拥有过期时间的key中快要过期的 3. 在所有key中随机删除 4. 在具有过期时间的key中随机删除 5. lru 从所有key中删除使用时间离现在最久的 6. lru 从拥有过期时间的key中删除使用时间离现在最久的 7. lfu 从所有key中删除使用次数最少的 8. lfu 从拥有过期时间的key中删除使用次数最少的&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;107.说说缓存穿透、击穿、雪崩的区别&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;缓存穿透：客户端访问不存在的数据，使得请求直达存储层，导致负载过大，直至宕机。原因可能是业务层误删了缓存和库中的数据，或是有人恶意访问不存在的数据。解决方式：1.存储层未命中后，返回空值存入缓存层，客户端再次访问时，缓存层直接返回空值。2.将数据存入布隆过滤器，访问缓存之前经过滤器拦截，若请求的数据不存在则直接返回空值。 缓存击穿：一份热点数据，它的访问量非常大，在它缓存失效的瞬间，大量请求直达存储层，导致服务崩溃。解决方案：1.永不过期：对热点数据不设置过期时间。2.加互斥锁，当一个线程访问该数据时，另一个线程只能等待，这个线程访问之后，缓存中的数据将被重建，届时其他线程就可以从缓存中取值。 缓存雪崩：大量数据同时过期、或是redis节点故障导致服务不可用，缓存层无法提供服务，所有的请求直达存储层，造成数据库宕机。解决方案：1.避免数据同时过期，设置随机过期时间。2.启用降级和熔断措施。3.设置热点数据永不过期。4.采用redis集群，一个宕机，另外的还能用&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;108.Redis如何与数据库保持双写一致性&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;共有四种同步策略：1.先更新数据库再更新缓存。缺点：多线程并发下会存在数据库中数据和缓存不一致的的现象。可能出现2.先更新缓存在更新数据库，优点就是每次数据变化都可以及时的更新缓存，但是消耗很大，影响服务器性能。3.先删除缓存在更新数据库。缺点：也会导致缓存和数据库数据不一致。4.先更新数据库再删除缓存。缺点仍然可能存在缓存和数据库中数据不一致的情况，但是，我们可以使用重试机制进行操作。，所以说这是效果最好的解决方案。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;109.如何实现Redis高可用&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;主从复制：写一定是在主服务器上，然后主服务器同步给从服务器。缺点：当主服务器挂掉的时候，不能自动切换到从服务器上。主从服务器存储数据一样，内存可用性差。优点：在一定程度上分担主服务器读的压力。哨兵模式：构建多个哨兵节点监视主从服务器，当主服务器挂掉的时候，自动将对应的从服务器切换成主服务器。优点：实现自动切换，可用性高。缺点：主从服务器存储数据一致，内存可用性差。还要额外维护一套哨兵系统，较为麻烦。集群模式：采用无中心节点的方式实现。多个主服务器相连，一个主服务器可以有多个从服务器，不同的主服务器存储不同的数据。优点：可用性更高，内存可用性高。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;？110.如何利用Redis实现一个分布式锁&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;最简单redis分布式锁的实现方式：加锁：setnx（key,1），解锁：del(key),问题：如果客户忘记解锁，将会出现死锁。第二种分布式锁的实现方式：setnx(key,1)+expire(key,30),解锁：del(key).问题：，由于setnx和expire的非原子性，当第二步挂掉，仍然会出现死锁。第三种方式：加锁：将setnx和expire变成原子性操作，set(key,1,30,NX),解锁：del（key）。同时考虑到线程A还在执行，但是锁已经到期，当线程A执行结束时去释放锁时，可能就会释放别的线程锁，所以在解锁时要先判断一下value值，看是不是该锁，如果是，再进行删除&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;111.设计模式了解么&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;常用的设计模式有单例模式、工厂模式、代理模式、适配器模式、装饰器模式、模板方法模式等等。像sping中的定义的bean默认为单例模式，spring中的BeanFactory用来创建对象的实例，他是工厂模式的体现。AOP面向切面编程时代理模式的体现，它的底层就是基于动态代理实现的。适配器模式在springMVC中有体现，它的处理器适配器会根据处理器规则适配相应的处理器执行，模板方法模式用来解决代码重复的问题等&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;112.请你讲讲单例模式、请你手写一下单例模式&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;单例模式是一个类只能创建一个对象，单例模式又分为饿汉式和懒汉式，饿汉式就是在类加载的的时候不管后边会不会用到都创建一个对象并且做初始化，浪费资源。懒汉式是在类加载的的时候创建对象，但不进行初始化，只有在用的时候，先判断对象是否为空，如果为空，对对象进行初始化，然后返回初始化后的值。为了解决线程安全问题，又分为懒汉式线程安全、懒汉式双重检验锁、静态内部类等&lt;/p&gt;
&lt;p&gt;public class Singleton{&lt;/p&gt;
&lt;p&gt;​ private static Singleton instance;&lt;/p&gt;
&lt;p&gt;​ private Singleton(){}&lt;/p&gt;
&lt;p&gt;​ private static synchronized Singleton getInstance(){&lt;/p&gt;
&lt;p&gt;​ if(instance == null){&lt;/p&gt;
&lt;p&gt;​ instance = new Singleton();&lt;/p&gt;
&lt;p&gt;​ }&lt;/p&gt;
&lt;p&gt;​ return instance;&lt;/p&gt;
&lt;p&gt;​ }&lt;/p&gt;
&lt;p&gt;}&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;113.请你讲讲工厂模式,手写实现工厂模式&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;工厂模式其用意是定义一个创建产品的接口将具体创建推迟到子类中去。工厂模式可以分为简单工厂、工厂方法、抽象工厂。简单工厂就是定义一个工厂类根据传入的参数返回不同的实例，被创建的实例具有共同的父类或接口。工厂方法工厂方法是定义一个工厂接口，但创建过程让子类去实现，去决定哪一个产品被实例化。抽象工厂模式是对工厂方法的进一步深化，在工厂类中可以创建一组对象。实现方式是提供一个创建一系列相关或相互依赖对象的接口而无需指定具体的类&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;114.说说垃圾收集器&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;Serial（新生代）、Serial Old（老年代）：适用于单核小CPU，单核工作，回收时会暂停其他工作stop the word。&lt;/p&gt;
&lt;p&gt;PawNew（新生代）、CMS（老年代）：适用于多核CPU，最求短暂停时间，多核工作，使用标记清除算法，最短的暂停时间。&lt;/p&gt;
&lt;p&gt;Parallel Scavenge（新生代-标记复制算法）、Parallel Old（老年代-标记整理算法）：1.7,1.8默认的组合，适用于多核CPU，追求最大吞吐量&lt;/p&gt;
&lt;p&gt;G1 jdk1.9默认，适用于大内存多核CPU服务器，它不按整个新生代或老年代去回收，而是开辟了面向局部收集，实现了较小的收集暂停时间和高吞吐量。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;Java中的error和exception有什么区别&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;Error（错误）:程序无法处理，通常指程序中出现的严重问题。&lt;/p&gt;
&lt;p&gt;例如java.lang.VirtualMachineError（Java虚拟机运行错误）：当 Java虚拟机崩溃或用尽了它继续操作所需的资源时，抛出该错误&lt;br /&gt;
例如java.lang.StackOverflowError（栈溢出错误）：当应用程序递归太深而发生堆栈溢出时，抛出该错误。&lt;br /&gt;
例如java.lang.OutOfMemoryError（内存溢出）：内存溢出或没有可用的内存提供给垃圾回收器时，产生这个错误。&lt;br /&gt;
Error（错误）是不可查的，而且也常常在应用程序的控制和处理能力之外，因此当Error（错误）出现时，程序会立即奔溃，Java虚拟机立即停止运行，&lt;br /&gt;
Exception（异常）:是指程序本身可以处理的异常（可以向上抛出或者捕获处理）。&lt;/p&gt;
&lt;p&gt;Java处理异常的默认方式是中断处理。&lt;/p&gt;
&lt;p&gt;以java.lang.NullPointerException为例，当程序出现空指针异常时，会创建一个空指针异常对象，并向外抛出，并被虚拟机捕获，从而导致程序中断执行。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;进程间通信的方式有几种&lt;/h4&gt;
&lt;blockquote&gt;
&lt;h2&gt;&lt;strong&gt;一、进程间的通信方式&lt;/strong&gt;&lt;/h2&gt;
&lt;h3&gt;管道( pipe )：&lt;/h3&gt;
&lt;p&gt;管道是一种半双工的通信方式，数据只能单向流动，而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。&lt;/p&gt;
&lt;h3&gt;有名管道 (namedpipe) ：&lt;/h3&gt;
&lt;p&gt;有名管道也是半双工的通信方式，但是它允许无亲缘关系进程间的通信。&lt;/p&gt;
&lt;h3&gt;信号量(semophore ) ：&lt;/h3&gt;
&lt;p&gt;信号量是一个计数器，可以用来控制多个进程对共享资源的访问。它常作为一种锁机制，防止某进程正在访问共享资源时，其他进程也访问该资源。因此，主要作为进程间以及同一进程内不同线程之间的同步手段。&lt;/p&gt;
&lt;h3&gt;消息队列( messagequeue ) ：&lt;/h3&gt;
&lt;p&gt;消息队列是由消息的链表，存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。&lt;/p&gt;
&lt;h3&gt;信号 (sinal ) ：&lt;/h3&gt;
&lt;p&gt;信号是一种比较复杂的通信方式，用于通知接收进程某个事件已经发生。&lt;/p&gt;
&lt;h3&gt;共享内存(shared memory ) ：&lt;/h3&gt;
&lt;p&gt;共享内存就是映射一段能被其他进程所访问的内存，这段共享内存由一个进程创建，但多个进程都可以访问。共享内存是最快的 IPC 方式，它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制，如信号两，配合使用，来实现进程间的同步和通信。&lt;/p&gt;
&lt;h3&gt;套接字(socket ) ：&lt;/h3&gt;
&lt;p&gt;套接口也是一种进程间通信机制，与其他通信机制不同的是，它可用于不同设备及其间的进程通信。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;DNS域名解析过程&lt;/h4&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;浏览器先检查自身缓存中有没有被解析过的这个域名对应的ip地址，如果有，解析结束。同时域名被缓存的时间也可通过TTL属性来设置。&lt;/li&gt;
&lt;li&gt;如果浏览器缓存中没有（专业点叫还没命中），浏览器会检查操作系统缓存中有没有对应的已解析过的结果。而操作系统也有一个域名解析的过程。在windows中可通过c盘里一个叫hosts的文件来设置，如果你在这里指定了一个域名对应的ip地址，那浏览器会首先使用这个ip地址。&lt;/li&gt;
&lt;li&gt;如果至此还没有命中域名，才会真正的请求本地域名服务器（LDNS）来解析这个域名，这台服务器一般在你的城市的某个角落，距离你不会很远，并且这台服务器的性能都很好，一般都会缓存域名解析结果，大约80%的域名解析到这里就完成了。&lt;/li&gt;
&lt;li&gt;如果LDNS仍然没有命中，就直接跳到Root Server 域名服务器请求解析&lt;/li&gt;
&lt;li&gt;根域名服务器返回给LDNS一个所查询域的主域名服务器（gTLD Server，国际顶尖域名服务器，如.com .cn .org等）地址&lt;/li&gt;
&lt;li&gt;此时LDNS再发送请求给上一步返回的gTLD&lt;/li&gt;
&lt;li&gt;接受请求的gTLD查找并返回这个域名对应的Name Server的地址，这个Name Server就是网站注册的域名服务器&lt;/li&gt;
&lt;li&gt;Name Server根据映射关系表找到目标ip，返回给LDNS&lt;/li&gt;
&lt;li&gt;LDNS缓存这个域名和对应的ip&lt;/li&gt;
&lt;li&gt;LDNS把解析的结果返回给用户，用户根据TTL值缓存到本地系统缓存中，域名解析过程至此结束&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h4&gt;浏览器输入一个网址后发生了什么&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;1.在客户端浏览器中输入URL.&lt;br /&gt;
2.发送到DNS(域名服务器)获得域名对应的web服务器的lP地址。&lt;br /&gt;
3.客户端浏览器与web服务器建立tcp连接.&lt;br /&gt;
4.客户端浏览器向对应IP地址的WEB服务器发送相应的http或https请求。&lt;br /&gt;
5.WEB服务器响应请求，返回指定的URL数据或错误信息。如果设定重定向，则重定向到新的URL地址。&lt;br /&gt;
6.客户端浏览器下载数据，解析HTML源文件，解析的过程中实现对页面的排版，解析完成后，在浏览器中显示基础界面。&lt;br /&gt;
7.分析页面中的超链接，显示在当前页面，重复以上过程直至没有超链接需要发送，完成页面的全部显示。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;Jvm内存划分&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/java-ssm/jvm-memory.svg&quot; alt=&quot;JVM 线程私有区域、共享区域及直接内存&quot; /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;原理示意：每个线程独有程序计数器、虚拟机栈与本地方法栈，堆和方法区则由线程共享。栈帧中的引用可以指向堆对象；运行时常量池属于方法区。直接内存位于规范定义的运行时数据区之外，可由堆中的 DirectByteBuffer 对象关联。这里区分逻辑职责，不表示实际物理地址或内存大小比例。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;第一，是程序计数器（Program Counter Register)，在JVM规范中，每个线程都有自己的程序计数器。这是一块比较小的内存空间，存储当前线程正在执行的Java方法的JVM指令地址，即字节码的行号。如果正在执行Native方法，则这个计数器为空。&lt;strong&gt;该内存区域是唯一一个在Java虚拟机规范中没有规定任何OOM情况的内存区域。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第二，Java虚拟机栈(Java Virtal Machine Stack)，同样也是属于线程私有区域，每个线程在创建的时候都会创建一个虚拟机栈，生命周期与线程一致，线程退出时，线程的虚拟机栈也回收。虚拟机栈内部保持一个个的栈帧，每次方法调用都会进行压栈，JVM对栈帧的操作只有出栈和压栈两种，方法调用结束时会进行出栈操作。&lt;/p&gt;
&lt;p&gt;该区域存储着局部变量表，编译时期可知的各种基本类型数据、对象引用、方法出口等信息。&lt;/p&gt;
&lt;p&gt;第三，本地方法栈（Native Method Stack）与虚拟机栈类似，本地方法栈是在调用本地方法时使用的栈，每个线程都有一个本地方法栈。&lt;/p&gt;
&lt;p&gt;第四，堆（Heap）,几乎所有创建的Java对象实例，都是被直接分配到堆上的。堆被所有的线程所共享，在堆上的区域，会被垃圾回收器做进一步划分，例如新生代、老年代的划分。Java虚拟机在启动的时候，可以使用“Xmx”之类的参数指定堆区域的大小。&lt;/p&gt;
&lt;p&gt;第五，方法区（Method Area)。方法区与堆一样，也是所有的线程所共享，存储被虚拟机加载的元（Meta）数据，包括类信息、常量、静态变量、即时编译器编译后的代码等数据。这里需要注意的是运行时常量池也在方法区中。&lt;/p&gt;
&lt;p&gt;根据Java虚拟机规范的规定，当方法区无法满足内存分配需求时，将抛出OutOfMemoryError异常。由于早期HotSpot JVM的实现，将CG分代收集拓展到了方法区，因此很多人会将方法区称为永久代。Oracle JDK8中已永久代移除永久代，同时增加了元数据区（Metaspace）。&lt;/p&gt;
&lt;p&gt;第六，运行时常量池（Run-Time Constant Pool)，这是方法区的一部分，受到方法区内存的限制，当常量池无法再申请到内存时，会抛出OutOfMemoryError异常。&lt;/p&gt;
&lt;p&gt;第七，直接内存（Direct Memory），直接内存并不属于Java规范规定的属于Java虚拟机运行时数据区的一部分。Java的NIO可以使用Native方法直接在java堆外分配内存，使用DirectByteBuffer对象作为这个堆外内存的引用。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;可重入锁&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;什么是“可重入”，可重入就是说某个线程已经获得某个锁，可以再次获取锁而不会出现死锁。例如&lt;/p&gt;
&lt;p&gt;使用ReentrantLock的注意点&lt;br /&gt;
ReentrantLock和synchronized不一样，需要手动释放锁，所以使用ReentrantLock的时候一定要手动释放锁，并且加锁次数和释放次数要—样&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;Java锁升级&lt;/h4&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Java中的锁有几种状态：无锁 → 偏向锁 → 轻量级锁 → 重量级锁&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;无锁状态&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;程序不会有锁的竞争。那么这种情况我们不需要加锁，所以这种情况下对象锁状态为无锁。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;偏向锁&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;偏向锁，顾名思义，它会偏向于第一个访问锁的线程&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果在运行过程中，同步锁只有一个线程访问，不存在多线程争用的情况，则线程是不需要触发同步的，这种情况下，就会给线程加一个偏向锁。线程第二次到达同步代码块时，会判断此时持有锁的线程是否就是自己，如果是则正常往下执行。由于之前没有释放锁，这里也就不需要重新加锁。如果自始至终使用锁的线程只有一个，很明显偏向锁几乎没有额外开销，性能极高。&lt;br /&gt;
如果在运行过程中，遇到了其他线程抢占锁，则持有偏向锁的线程会被挂起，JVM会消除它身上的偏向锁，将锁恢复到标准的轻量级锁。偏向锁通过消除资源无竞争情况下的同步原语，进一步提高了程序的运行性能。一旦有第二个线程加入锁竞争，偏向锁就升级为轻量级锁（自旋锁）。升级为轻量级锁的时候需要撤销偏向锁，撤销偏向锁的时候会导致STW(stop the word)操作；&lt;/p&gt;
&lt;h4&gt;轻量级锁（自旋锁）&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;自旋锁：自旋锁原理非常简单，如果持有锁的线程能在很短时间内释放锁资源，那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态，它们只需要等一等（自旋），等持有锁的线程释放锁后即可立即获取锁，这样就避免用户线程和内核的切换的消耗。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在轻量级锁状态下继续锁竞争，如果成功就成功获取轻量级锁。否则进入锁膨胀阶段，没有抢到锁的线程将自旋，即不停地循环判断锁是否能够被成功获取。长时间的自旋操作是非常消耗资源的，一个线程持有锁，其他线程就只能在原地空耗CPU，执行不了任何有效的任务，这种现象叫做忙等（busy-waiting）。如果锁竞争情况严重，某个达到最大自旋次数的线程，会将轻量级锁升级为重量级锁。&lt;br /&gt;
自旋锁:竞争锁失败的线程，并不会真实的在操作系统层面挂起等待，而是JVM会让线程做几个空循环(基于预测在不久的将来就能获得)，在经过若干次循环后，如果可以获得锁，那么进入临界区，如果还不能获得锁，才会真实的将线程在操作系统层面进行挂起。这样的好处就是快，坏处就是消耗cpu资源。&lt;/p&gt;
&lt;h4&gt;重量级锁&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;当后续线程尝试获取锁时，发现被占用的锁是重量级锁，则直接将自己挂起，等待将来被唤醒。在JDK1.6之前，synchronized直接加重量级锁，很明显现在得到了很好的优化。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;重量级锁的特点：其他线程试图获取锁时，都会被阻塞，只有持有锁的线程释放锁之后才会唤醒这些线程。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;锁&lt;/th&gt;
&lt;th&gt;优点&lt;/th&gt;
&lt;th&gt;缺点&lt;/th&gt;
&lt;th&gt;适用场景&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;偏向锁&lt;/td&gt;
&lt;td&gt;加锁和解锁不需要额外的消耗，和执行非同步方法比仅存在纳秒级的差距。&lt;/td&gt;
&lt;td&gt;如果线程间存在锁竞争，会带来额外的锁撤销的消耗。&lt;/td&gt;
&lt;td&gt;适用于只有一个线程访问同步块场景。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;轻量级锁&lt;/td&gt;
&lt;td&gt;竞争的线程不会阻塞，提高了程序的响应速度。&lt;/td&gt;
&lt;td&gt;如果始终得不到锁竞争的线程使用自旋会消耗CPU。&lt;/td&gt;
&lt;td&gt;追求响应时间。同步块执行速度非常快。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;重量级锁&lt;/td&gt;
&lt;td&gt;线程竞争不使用自旋，不会消耗CPU。&lt;/td&gt;
&lt;td&gt;线程阻塞，响应时间缓慢。&lt;/td&gt;
&lt;td&gt;追求吞吐量。同步块执行速度较长。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4&gt;锁升级场景&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;场景1： 经常只有某一个线程来加锁。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;加锁过程：也许获取锁的经常为同一个线程，这种情况下为了避免加锁造成的性能开销，加偏向锁。&lt;br /&gt;
偏向锁的执行流程如下：&lt;br /&gt;
1、线程首先检查该对象头的线程ID是否为当前线程；&lt;br /&gt;
2、A：如果对象头的线程ID和当前线程ID一致，则直接执行代码；B：如果不是当前线程ID则使用CAS方式替换对象头中的线程ID，如果使用CAS替换不成功则说明有线程正在执行，存在锁的竞争，这时需要撤销偏向锁，升级为轻量级锁。&lt;br /&gt;
3、如果CAS替换成功，则把对象头的线程ID改为自己的线程ID，然后执行代码。&lt;br /&gt;
4、执行代码完成之后释放锁，把对象头的线程ID修改为空。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;场景2： 有线程来参与锁的竞争，但是获取锁的冲突时间很短。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当开始有锁的竞争了，那么偏向锁就会升级到轻量级锁；&lt;br /&gt;
线程获取锁出现冲突时，线程必须做出决定是继续在这里等，还是先去做其他事情，等会再来看看，而轻量级锁的采用了继续在这里等的方式。当发现有锁竞争，线程首先会使用自旋的方式循环在这里获取锁，因为使用自旋的方式非常消耗CPU。当一定时间内通过自旋的方式无法获取到锁的话，那么锁就开始升级为重量级锁了。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;场景3： 有大量的线程参与锁的竞争，冲突性很高。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当获取锁冲突多，时间越长的时候，线程肯定无法继续在这里死等了，所以只好先挂起，然后等前面获取锁的线程释放了锁之后，再开启下一轮的锁竞争，而这种形式就是我们的重量级锁。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;为什么jdk9要将String的底层由char[]改为byte[]&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;JDK 9并没有将String的底层实现由char[]改成byte[]，而是在JDK 9中，引入了一个CompactStrings的优化,优化的目的是减少String对象的内存消耗，&lt;strong&gt;在大多数Java程序的堆里，String占用的空间最大&lt;/strong&gt;，并且绝大多数String只有&lt;a href=&quot;https://www.zhihu.com/search?q=Latin-1%E5%AD%97%E7%AC%A6&amp;amp;search_source=Entity&amp;amp;hybrid_search_source=Entity&amp;amp;hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A1759986003%7D&quot;&gt;Latin-1字符&lt;/a&gt;，这些Latin-1字符只需要1个字节就够了。JDK9之前，JVM因为String使用&lt;a href=&quot;https://www.zhihu.com/search?q=char%E6%95%B0%E7%BB%84&amp;amp;search_source=Entity&amp;amp;hybrid_search_source=Entity&amp;amp;hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A1759986003%7D&quot;&gt;char数组&lt;/a&gt;存储，每个char占2个字节，所以即使字符串只需要1字节/字符，它也要按照2字节/字符进行分配，浪费了一半的内存空间。&lt;/p&gt;
&lt;p&gt;JDK9是怎么解决这个问题的呢？一个字符串出来的时候判断，它是不是只有Latin-1字符，如果是，就按照1字节/字符的规格进行分配内存，如果不是，就按照2字节/字符的规格进行分配，提高了内存使用率。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;单点登录&lt;/h4&gt;
&lt;p&gt;&amp;gt;&lt;/p&gt;
&lt;h4&gt;手写单例模式、工厂模式&lt;/h4&gt;
&lt;h5&gt;&lt;strong&gt;一、单例模式的定义&lt;/strong&gt;&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;定义：&lt;/strong&gt; 确保一个类只有一个实例，并提供该实例的全局访问点。&lt;/p&gt;
&lt;p&gt;这样做的好处是：有些实例，全局只需要一个就够了，使用单例模式就可以避免一个全局使用的类，频繁的创建与销毁，耗费系统资源。&lt;/p&gt;
&lt;h5&gt;&lt;strong&gt;二、单例模式的设计要素&lt;/strong&gt;&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;一个私有构造函数 （确保只能单例类自己创建实例）&lt;/li&gt;
&lt;li&gt;一个私有静态变量 （确保只有一个实例）&lt;/li&gt;
&lt;li&gt;一个公有静态函数 （给使用者提供调用方法）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;简单来说就是，单例类的构造方法不让其他人修改和使用；并且单例类自己只创建一个实例，这个实例，其他人也无法修改和直接使用；然后单例类提供一个调用方法，想用这个实例，只能调用。这样就确保了全局只创建了一次实例。&lt;/p&gt;
&lt;h5&gt;&lt;strong&gt;三、单例模式的6种实现及各实现的优缺点&lt;/strong&gt;&lt;/h5&gt;
&lt;h6&gt;&lt;strong&gt;（一）懒汉式（线程不安全）&lt;/strong&gt;&lt;/h6&gt;
&lt;p&gt;&lt;strong&gt;实现：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class Singleton {
     private static Singleton uniqueInstance;
   
     private Singleton() {
        
    }
    
    public static Singleton getUniqueInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;说明：&lt;/strong&gt; 先不创建实例，当第一次被调用时，再创建实例，所以被称为懒汉式。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优点：&lt;/strong&gt; 延迟了实例化，如果不需要使用该类，就不会被实例化，节约了系统资源。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;缺点：&lt;/strong&gt; 线程不安全，多线程环境下，如果多个线程同时进入了 if (uniqueInstance == null) ，若此时还未实例化，也就是uniqueInstance == null，那么就会有多个线程执行 uniqueInstance = new Singleton(); ，就会实例化多个实例；&lt;/p&gt;
&lt;h6&gt;&lt;strong&gt;（二）饿汉式（线程安全）&lt;/strong&gt;&lt;/h6&gt;
&lt;p&gt;&lt;strong&gt;实现：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class Singleton {
    
    private static Singleton uniqueInstance = new Singleton();
    
    private Singleton() {
    }
    
    public static Singleton getUniqueInstance() {
        return uniqueInstance;
    }
    
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;说明：&lt;/strong&gt; 先不管需不需要使用这个实例，直接先实例化好实例 (饿死鬼一样，所以称为饿汉式)，然后当需要使用的时候，直接调方法就可以使用了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优点：&lt;/strong&gt; 提前实例化好了一个实例，避免了线程不安全问题的出现。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;缺点：&lt;/strong&gt; 直接实例化好了实例，不再延迟实例化；若系统没有使用这个实例，或者系统运行很久之后才需要使用这个实例，都会操作系统的资源浪费。&lt;/p&gt;
&lt;h6&gt;&lt;strong&gt;（三）懒汉式（线程安全）&lt;/strong&gt;&lt;/h6&gt;
&lt;p&gt;&lt;strong&gt;实现：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class Singleton {
    private static Singleton uniqueInstance;
    
    private static singleton() {
    }
   
    private static synchronized Singleton getUinqueInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
    
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;说明：&lt;/strong&gt; 实现和 线程不安全的懒汉式 几乎一样，唯一不同的点是，在get方法上 加了一把 锁。如此一来，多个线程访问，每次只有拿到锁的的线程能够进入该方法，避免了多线程不安全问题的出现。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优点：&lt;/strong&gt; 延迟实例化，节约了资源，并且是线程安全的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;缺点：&lt;/strong&gt; 虽然解决了线程安全问题，但是性能降低了。因为，即使实例已经实例化了，既后续不会再出现线程安全问题了，但是锁还在，每次还是只能拿到锁的线程进入该方法，会使线程阻塞，等待时间过长。&lt;/p&gt;
&lt;h6&gt;&lt;strong&gt;（四）双重检查锁实现（线程安全）&lt;/strong&gt;&lt;/h6&gt;
&lt;p&gt;&lt;strong&gt;实现：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class Singleton {
    
    private volatile static Singleton uniqueInstance;
    
    private Singleton() {
    }
    
    public static Singleton getUniqueInstance() {
        if (uniqueInstance == null) {
            synchronized (Singleton.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }  
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;说明:&lt;/strong&gt; 双重检查数相当于是改进了 线程安全的懒汉式。线程安全的懒汉式 的缺点是性能降低了，造成的原因是因为即使实例已经实例化，依然每次都会有锁。而现在，我们将锁的位置变了，并且多加了一个检查。 也就是，先判断实例是否已经存在，若已经存在了，则不会执行判断方法内的有锁方法了。 而如果，还没有实例化的时候，多个线程进去了，也没有事，因为里面的方法有锁，只会让一个线程进入最内层方法并实例化实例。如此一来，最多最多，也就是第一次实例化的时候，会有线程阻塞的情况，后续便不会再有线程阻塞的问题。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么使用 volatile 关键字修饰了 uniqueInstance 实例变量 ？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;uniqueInstance = new Singleton(); 这段代码执行时分为三步：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;为 uniqueInstance 分配内存空间&lt;/li&gt;
&lt;li&gt;初始化 uniqueInstance&lt;/li&gt;
&lt;li&gt;将 uniqueInstance 指向分配的内存地址&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;正常的执行顺序当然是 1&amp;gt;2&amp;gt;3 ，但是由于 JVM 具有指令重排的特性，执行顺序有可能变成 1&amp;gt;3&amp;gt;2。 单线程环境时，指令重排并没有什么问题；多线程环境时，会导致有些线程可能会获取到还没初始化的实例。 例如：线程A 只执行了 1 和 3 ，此时线程B来调用 getUniqueInstance()，发现 uniqueInstance 不为空，便获取 uniqueInstance 实例，但是其实此时的 uniqueInstance 还没有初始化。&lt;/p&gt;
&lt;p&gt;解决办法就是加一个 volatile 关键字修饰 uniqueInstance ，volatile 会禁止 JVM 的指令重排，就可以保证多线程环境下的安全运行。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优点：&lt;/strong&gt; 延迟实例化，节约了资源；线程安全；并且相对于 线程安全的懒汉式，性能提高了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;缺点：&lt;/strong&gt; volatile 关键字，对性能也有一些影响。&lt;/p&gt;
&lt;h6&gt;&lt;strong&gt;（五）静态内部类实现（线程安全）&lt;/strong&gt;&lt;/h6&gt;
&lt;p&gt;&lt;strong&gt;实现：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class Singleton {
    
    private Singleton() {
    }
    
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    
    public static Singleton getUniqueInstance() {
        return SingletonHolder.INSTANCE;
    }
     
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;说明：&lt;/strong&gt; 首先，当外部类 Singleton 被加载时，静态内部类 SingletonHolder 并没有被加载进内存。当调用 getUniqueInstance() 方法时，会运行 return SingletonHolder.INSTANCE; ，触发了 SingletonHolder.INSTANCE ，此时静态内部类 SingletonHolder 才会被加载进内存，并且初始化 INSTANCE 实例，而且 JVM 会确保 INSTANCE 只被实例化一次。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优点：&lt;/strong&gt; 延迟实例化，节约了资源；且线程安全；性能也提高了。&lt;/p&gt;
&lt;h6&gt;&lt;strong&gt;（六）枚举类实现（线程安全）&lt;/strong&gt;&lt;/h6&gt;
&lt;p&gt;&lt;strong&gt;实现：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public enum Singleton {
    
    INSTANCE;

    //添加自己需要的操作
    public void doSomeThing() {
    
    }
    
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;说明：&lt;/strong&gt; 默认枚举实例的创建就是线程安全的，且在任何情况下都是单例。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优点：&lt;/strong&gt; 写法简单，线程安全，天然防止反射和反序列化调用。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;**防止反序列化****序列化：**把java对象转换为字节序列的过程；**反序列化：**通过这些字节序列在内存中新建java对象的过程；**说明：**反序列化 将一个单例实例对象写到磁盘再读回来，从而获得了一个新的实例。 我们要防止反序列化，避免得到多个实例。**枚举类天然防止反序列化。** 其他单例模式 可以通过 重写 readResolve() 方法，从而防止反序列化，使实例唯一重写 readResolve() :&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;private Object readResolve() throws ObjectStreamException{
        return singleton;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;&lt;strong&gt;四、单例模式的应用场景&lt;/strong&gt;&lt;/h5&gt;
&lt;p&gt;&lt;strong&gt;应用场景举例：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;网站计数器。&lt;/li&gt;
&lt;li&gt;应用程序的日志应用。&lt;/li&gt;
&lt;li&gt;Web项目中的配置对象的读取。&lt;/li&gt;
&lt;li&gt;数据库连接池。&lt;/li&gt;
&lt;li&gt;多线程池。&lt;/li&gt;
&lt;li&gt;……&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;使用场景总结：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;频繁实例化然后又销毁的对象&lt;/strong&gt;，使用单例模式可以提高性能。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;经常使用的对象，但实例化时耗费时间或者资源多&lt;/strong&gt;，如数据库连接池，使用单例模式，可以提高性能，降低资源损坏。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;使用线程池之类的控制资源时&lt;/strong&gt;，使用单例模式，可以方便资源之间的通信。&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;&lt;strong&gt;1. 简单工厂模式（静态工厂）&lt;/strong&gt;&lt;/h5&gt;
&lt;p&gt;某种程度上不符合设计原则，但实际使用最多。&lt;/p&gt;
&lt;h6&gt;&lt;strong&gt;原始方法&lt;/strong&gt;&lt;/h6&gt;
&lt;pre&gt;&lt;code&gt;public interface Car {   
    public void name();
}
public class Tesla implements Car{
    public void name(){   
        System.out.println(&quot;特斯拉！&quot;);    
    }}
public class WuLing implements Car{ 
    public void name(){  
        System.out.println(&quot;五菱宏光！&quot;);  
    }}
public class Consumer { 
    public static void main(String[] args) { 
        // 接口，所有实现类   
        Car car=new WuLing();
        Car car2=new Tesla(); 
        car.name();     
        car2.name();
        // Car car = CarFactory.getCar(&quot;五菱&quot;);  
        // car.name(); 
    }}
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;&lt;strong&gt;静态工厂方法&lt;/strong&gt;&lt;/h5&gt;
&lt;p&gt;因为CarFactory类中的所有方法都是静态的，需要向其中添加参数来返回不同的对象实例。&lt;/p&gt;
&lt;p&gt;增加一个新的产品，如果不修改CarFactory代码就做不到。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class CarFactory {  
    // 方法一   
    public static Car getCar(String car){ 
        if(car.equals(&quot;五菱&quot;)){    
            return new WuLing();  
        }else if(car.equals(&quot;特斯拉&quot;)){  
            return new Tesla();     
        }else { 
            return null; 
        } 
    } 
    // 方法二 
    public static Car getWuLing(){
        return new WuLing(); 
    }
    public static Car getTesla(){ 
        return new Tesla();   
    }}
public class Consumer { 
    public static void main(String[] args) {     
        // 接口，所有实现类    
        // Car car=new WuLing();
        // Car car2=new Tesla();  
        // car.name();
        // car2.name();   
        Car car = CarFactory.getCar(&quot;五菱&quot;);    
        Car car2 = CarFactory.getCar(&quot;特斯拉&quot;);    
        car.name(); 
        car2.name();
    }}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;strong&gt;静态工厂方法&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;因为CarFactory类中的所有方法都是静态的，需要向其中添加参数来返回不同的对象实例。&lt;/p&gt;
&lt;p&gt;增加一个新的产品，如果不修改CarFactory代码就做不到。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class CarFactory {  
    // 方法一   
    public static Car getCar(String car){ 
        if(car.equals(&quot;五菱&quot;)){    
            return new WuLing();  
        }else if(car.equals(&quot;特斯拉&quot;)){  
            return new Tesla();     
        }else { 
            return null; 
        } 
    } 
    // 方法二 
    public static Car getWuLing(){
        return new WuLing(); 
    }
    public static Car getTesla(){ 
        return new Tesla();   
    }}
public class Consumer { 
    public static void main(String[] args) {     
        // 接口，所有实现类    
        // Car car=new WuLing();
        // Car car2=new Tesla();  
        // car.name();
        // car2.name();   
        Car car = CarFactory.getCar(&quot;五菱&quot;);    
        Car car2 = CarFactory.getCar(&quot;特斯拉&quot;);    
        car.name(); 
        car2.name();
    }}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;新增一个产品&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class DaZhong implements Car{ 
    @Override  
    public void name() {    
        System.out.println(&quot;大众！&quot;);   
    }}
public class  DaZhongFactory implements CarFactory {
    @Override
    public Car getCar() {
        return new DaZhong();   
    }}
public class Consumer {
    public static void main(String[] args) {
        Car car = new WuLingFactory().getCar();  
        Car car1 = new TeslaFactory().getCar();
        car.name();
        car1.name();  
        Car car2 = new DaZhongFactory().getCar(); 
        car2.name(); 
    }}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;strong&gt;对比&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;结构复杂度：simple更简单&lt;/li&gt;
&lt;li&gt;代码复杂度：simple更简单&lt;/li&gt;
&lt;li&gt;编程复杂度：simple更简单&lt;/li&gt;
&lt;li&gt;管理上的复杂度：simple更简单&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;根据设计原则：工厂方法模式&lt;/p&gt;
&lt;p&gt;根据实际业务：简单工厂模式&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;3、抽象工厂模式&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;定义:抽象工厂模式提供了一个创建一系列相关或者相互依赖对象的接口，无需指定它们具体的类&lt;/li&gt;
&lt;li&gt;适用场景:&lt;/li&gt;
&lt;li&gt;
&lt;ul&gt;
&lt;li&gt;客户端(应用层)不依赖于产品类实例如何被创建、实现等细节&lt;/li&gt;
&lt;li&gt;强调一系列相关的产品对象(属于同一产品族)&lt;strong&gt;一起使用&lt;/strong&gt;创建对象需要大量的重复代码（一次一起全部创建，很少更改）&lt;/li&gt;
&lt;li&gt;提供一个产品类的库，所有的产品以同样的接口出现，从而使得客户端不依赖于具体的实现&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;优点:&lt;/li&gt;
&lt;li&gt;
&lt;ul&gt;
&lt;li&gt;具体产品在应用层的&lt;strong&gt;代码隔离&lt;/strong&gt;，&lt;strong&gt;无需关心创建的细节&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;将一个系列的产品统一到一起创建&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;缺点:&lt;/li&gt;
&lt;li&gt;
&lt;ul&gt;
&lt;li&gt;规定了所有可能被创建的产品集合，&lt;strong&gt;产品簇中扩展新的产品困难&lt;/strong&gt;;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;IO多路复用&lt;/h4&gt;
&lt;h4&gt;LRU手写&lt;/h4&gt;
&lt;h4&gt;设计模式的原则&lt;/h4&gt;
&lt;h4&gt;setnx底层实现&lt;/h4&gt;
&lt;h4&gt;可见性问题？volatile，那么volatile底层如何实现的&lt;/h4&gt;
&lt;h4&gt;迭代器失效&lt;/h4&gt;
&lt;h4&gt;dns基于什么协议&lt;/h4&gt;
&lt;h4&gt;osi和tcp/ip，有什么主要的协议&lt;/h4&gt;
&lt;h4&gt;java跨平台如何实现&lt;/h4&gt;
&lt;h4&gt;https加密通信的过程&lt;/h4&gt;
&lt;h4&gt;流量控制和拥塞控制&lt;/h4&gt;
&lt;h4&gt;topk&lt;/h4&gt;
</content:encoded></item><item><title>Redis04-SpringDataRedis</title><link>https://wang-nine.github.io/posts/10704/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10704/</guid><description>SpringData是Spring中数据操作的模块，包含对各种数据库的集成，其中对Redis的集成模块就叫做SpringDataRedis</description><pubDate>Sun, 02 Jul 2023 13:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;SpringDataRedis&lt;/h1&gt;
&lt;p&gt;SpringData是Spring中数据操作的模块，包含对各种数据库的集成，其中对Redis的集成模块就叫做SpringDataRedis&lt;/p&gt;
&lt;p&gt;提供了对不同Redis客户端的整合(Lettuce和Jedis)&lt;br /&gt;
提供了RedisTemplate统一API来操作Redis&lt;br /&gt;
支持Redis的发布订阅模型&lt;br /&gt;
支持Redis哨兵和Redis集群&lt;br /&gt;
支持基于Lettuce的响应式编程&lt;br /&gt;
支持基于JDK、JSON、字符串、Spring对象的数据序列化及反序列化&lt;br /&gt;
支持基于Redis的JDKCollection实现&lt;/p&gt;
&lt;h2&gt;SpringDataRedis快速入门&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;API&lt;/th&gt;
&lt;th&gt;返回值类型&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;redisTemplate.opsForValue()&lt;/td&gt;
&lt;td&gt;ValueOperations&lt;/td&gt;
&lt;td&gt;操作String数据类型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;redisTemplate.opsForHash()&lt;/td&gt;
&lt;td&gt;HashOperations&lt;/td&gt;
&lt;td&gt;操作Hash类型数据&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;redisTemplate.opsForList()&lt;/td&gt;
&lt;td&gt;ListOperations&lt;/td&gt;
&lt;td&gt;操作List类型数据&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;redisTemplate.opsForSet()&lt;/td&gt;
&lt;td&gt;SetOperations&lt;/td&gt;
&lt;td&gt;操作Set类型数据&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;redisTemplate.opsForZSet()&lt;/td&gt;
&lt;td&gt;ZSetOperations&lt;/td&gt;
&lt;td&gt;操作SortedSet类型数据&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;redisTemplate&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;通用的命令&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;使用(基于SpringBoot)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;1.引入依赖&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!-- Redis依赖 --&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-boot-starter-data-redis&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;!-- 连接池依赖 --&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.apache.commons&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;commons-pool2&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;2.配置文件&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;spring:
  redis:
    host: 192.168.72.128
    port: 6379
    password: 123456
    lettuce:
      pool:
        max-active: 8 # 最大连接
        max-idle: 8 # 最大空闲连接
        min-idle: 0 # 最小空闲连接
        max-wait: 100 # 连接等待时间
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;3.注入RedisTemplate&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;@Autowired
private RedisTemplate redisTemplate;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;4.编写测试&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;@SpringBootTest
public class RedisTest {
    @Autowired
    private RedisTemplate redisTemplate;
    
    @Test
    void testString() {
        //插入一条String类型数据
        redisTemplate.opsForValue().set(&quot;name&quot;, &quot;李四&quot;);
        //读取一条String数据
        Object name = redisTemplate.opsForValue().get(&quot;name&quot;);
        System.out.println(&quot;name = &quot; + name);
    }
    
    @Test
    void testSaveUser() {
        //写入数据
        redisTemplate.opsForValue().set(&quot;user:100&quot;, new User(&quot;Wang&quot;, 21));
        //获取数据
        User o = (User) redisTemplate.opsForValue().get(&quot;user:100&quot;);
        System.out.println(&quot;o = &quot; + o);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;插入数据出现了问题，序列化出现了问题，变成了字节&lt;/p&gt;
&lt;p&gt;缺点：可读性差且占用内存大&lt;/p&gt;
&lt;p&gt;改进(自己定义序列化和反序列化)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class RedisConfig {

    @Bean
    public RedisTemplate&amp;lt;String, Object&amp;gt; redisTemplate(RedisConnectionFactory connectionFactory) {
        //创建RedisTemplate对象
        RedisTemplate&amp;lt;String, Object&amp;gt; template = new RedisTemplate&amp;lt;&amp;gt;();
        //设置连接工厂
        template.setConnectionFactory(connectionFactory);
        //创建JSON序列化工具
        GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        //设置Key的序列化
        template.setKeySerializer(RedisSerializer.string());
        template.setHashKeySerializer(RedisSerializer.string());
        //设置Value的序列化
        template.setValueSerializer(jsonRedisSerializer);
        template.setHashValueSerializer(jsonRedisSerializer);
        //返回
        return template;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为了在反序列化时知道对象的类型，JSON序列化器会将类的class类型写入json结果中，存入Redis，会带来额外的内存开销。&lt;/p&gt;
&lt;p&gt;为了节省内存空间，我们并不会使用JSON序列化器来处理value，而是统一使用String序列化器，要求只能存储String类型的key和value。当需要存储Java对象时，手动完成对象的序列化和反序列化。&lt;/p&gt;
&lt;p&gt;Spring默认提供了一个StringRedisTemplate类，它的key和value的序列化方式默认就是String方式。省去了我们自定义RedisTemplate的过程:&lt;/p&gt;
&lt;p&gt;使用StringRedisTemplate和手动序列化和反序列化&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@SpringBootTest
class RedisStringTests {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Test
    void testString() {
        //插入一条String类型数据
        stringRedisTemplate.opsForValue().set(&quot;name&quot;, &quot;string测试&quot;);
        //读取一条String数据
        Object name = stringRedisTemplate.opsForValue().get(&quot;name&quot;);
        System.out.println(&quot;name = &quot; + name);//插入一条String类型数据
    }

    private static final ObjectMapper mapper = new ObjectMapper();

    @Test
    void testSaveUser() throws JsonProcessingException {
        //创建对象
        User user = new User(&quot;Wang-nine&quot;, 9);
        //手动序列化
        String json = mapper.writeValueAsString(user);
        //写入数据
        stringRedisTemplate.opsForValue().set(&quot;user:200&quot;, json);

        //获取数据
        String jsonUser = stringRedisTemplate.opsForValue().get(&quot;user:200&quot;);
        //手动反序列化
        User user1 = mapper.readValue(jsonUser, User.class);
        System.out.println(&quot;user1 = &quot; + user1);
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;==RedisTemplate的两种序列化实践方案:==&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;方案一:
1.自定义RedisTemplate
2.修改RedisTemplate的序列化器为GenericJackson2JsonRedisSerializer&lt;/li&gt;
&lt;li&gt;方案二:
1.使用StringRedisTemplate
2.写入Redis时，手动把对象序列化为JSON
3.读取Redis时，手动把读取到的JSON反序列化为对象&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;注意：有关于Hash数据结构的存取&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Test
void TestHash() {
    stringRedisTemplate.opsForHash().put(&quot;user:400&quot;, &quot;测试300&quot;, &quot;300&quot;);
    stringRedisTemplate.opsForHash().put(&quot;user:400&quot;, &quot;测试400&quot;, &quot;400&quot;);

    Map&amp;lt;Object, Object&amp;gt; entries = stringRedisTemplate.opsForHash().entries(&quot;user:400&quot;);
    System.out.println(entries);
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Redis03-Jedis</title><link>https://wang-nine.github.io/posts/10703/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10703/</guid><description>Jedis本身是线程不安全的，并且频繁的创建和销毁连接会有性能损耗，因此我们推荐大家使用Jedis连接池代替Jedis的直连方式</description><pubDate>Sun, 02 Jul 2023 11:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Jedis&lt;/h1&gt;
&lt;h2&gt;样例程序&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;1.引入依赖&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
	&amp;lt;groupId&amp;gt;redis.clients&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;jedis&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;3.7.0&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;2.建立连接&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;private Jedis jedis;

@BeforeEach
void setUp() {
	//建立连接
	jedis = new Jedis(&quot;192.168.150.101&quot;，6379);
    //设置密码
	jedis.auth(&quot;123456&quot;);
    //选择库
	jedis.select(0);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;测试String&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;@Test
void testString() {
	//插入数据，方法名称就是redis命令名称，非常简单
    String result = jedis.set(&quot;name&quot;, &quot;张三&quot;); 
    System.out.println(&quot;result = &quot; + result);
    //获取数据
	String name = jedis.get(&quot;name&quot;) ;
	System.out.println(&quot;name = &quot; + name) ;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;释放资源&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;@AfterEach
void tearDown() {
	//释放资源
	if (jedis != null) {
		jedis.close();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Jedis连接池&lt;/h2&gt;
&lt;p&gt;Jedis本身是线程不安全的，并且频繁的创建和销毁连接会有性能损耗，因此我们推荐大家使用Jedis连接池代替Jedis的直连方式&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class JedisConnectionFactory {
    private static final JedisPool jedisPool;
    
    static {
        //配置连接池
        JedisPoolConfig poolConfig = new JedisPoolConfig();
        poolConfig.setMaxTotal(8);  //最大连接数
        poolConfig.setMaxIdle(8);   //最大空闲连接
        poolConfig.setMinIdle(0);   //最小空闲连接
        poolConfig.setMaxWaitMillis(1000);  //没有连接可用的等待时长，默认-1，持续等待
        //创建连接池对象
        jedisPool = new JedisPool(poolConfig, &quot;192.168.72.128&quot;, 6379, 1000, &quot;123456&quot;);
    }
    
    public static Jedis getJedis() {
        return jedisPool.getResource();
    }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Redis02-命令</title><link>https://wang-nine.github.io/posts/10702/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10702/</guid><description>Redis是一个key-value的数据库，key一般是String类型，不过value的类型多种多样:</description><pubDate>Sun, 02 Jul 2023 09:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Redis数据结构介绍&lt;/h1&gt;
&lt;p&gt;Redis是一个key-value的数据库，key一般是String类型，不过value的类型多种多样:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;数据类型&lt;/th&gt;
&lt;th&gt;样例&lt;/th&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;String&lt;/td&gt;
&lt;td&gt;hello world&lt;/td&gt;
&lt;td&gt;基本类型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hash&lt;/td&gt;
&lt;td&gt;{name: “Jack”,age:21}&lt;/td&gt;
&lt;td&gt;基本类型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;List&lt;/td&gt;
&lt;td&gt;[A -&amp;gt;B -&amp;gt;C -&amp;gt;C]&lt;/td&gt;
&lt;td&gt;基本类型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;set&lt;/td&gt;
&lt;td&gt;{A，B，C}&lt;/td&gt;
&lt;td&gt;基本类型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SortedSet&lt;/td&gt;
&lt;td&gt;{A: 1，B:2，C:3}&lt;/td&gt;
&lt;td&gt;基本类型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GEO&lt;/td&gt;
&lt;td&gt;{A:( 120.3, 30.5)}&lt;/td&gt;
&lt;td&gt;特殊类型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BitMap&lt;/td&gt;
&lt;td&gt;0110110101110101011&lt;/td&gt;
&lt;td&gt;特殊类型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HyperLog&lt;/td&gt;
&lt;td&gt;0110110101110101011&lt;/td&gt;
&lt;td&gt;特殊类型&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;命令文档&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://redis.io/commands&quot;&gt;Redis官网命令文档&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;help @xxxx&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;通用命令&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;help @generic
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;常用命令&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;KEYS pattern&lt;/td&gt;
&lt;td&gt;查询符和模版的所有key（通常使用通配符模糊匹配）  &amp;lt;br&amp;gt;不建议在生产环境上使用（redis单线程，执行此命令则会等待）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DEL key [key …]&lt;/td&gt;
&lt;td&gt;删除一个/多个指定的key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EXISTS key [key …]&lt;/td&gt;
&lt;td&gt;判断key是否存在&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EXPIRE key seconds&lt;/td&gt;
&lt;td&gt;设置一个key的存活时间&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TTL key&lt;/td&gt;
&lt;td&gt;查看key的剩余有效期（-1永久有效）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;String&lt;/h1&gt;
&lt;p&gt;String类型，也就是字符串类型，是Redis中最简单的存储类型。&lt;/p&gt;
&lt;p&gt;其value是字符串，不过根据字符串的格式不同，又可以分为3类:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;string:普通字符串&lt;/li&gt;
&lt;li&gt;int:整数类型，可以做自增、自减操作&lt;/li&gt;
&lt;li&gt;float:浮点类型，可以做自增、自减操作&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;不管是哪种格式，底层都是字节数组形式存储，只不过是编码方式不同。字符串类型的最大空间不能超过512m&lt;/p&gt;
&lt;p&gt;常见命令&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SET key value&lt;/td&gt;
&lt;td&gt;添加或者修改已经存在的一个String类型的键值对&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;td&gt;根据key获取String类型的value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MSET&lt;/td&gt;
&lt;td&gt;批量添加多个String类型的键值对&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MGET&lt;/td&gt;
&lt;td&gt;根据多个key获取多个String类型的value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;INCR&lt;/td&gt;
&lt;td&gt;让一个整型的key自增1（自减，DECR，一般不用，自增负数即可）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;INCRBY&lt;/td&gt;
&lt;td&gt;让一个整型的key自增并指定步长，例如: incrby num 2 让num值自增2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;INCRBYFLOAT&lt;/td&gt;
&lt;td&gt;让一个浮点类型的数字自增并指定步长&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SETNX（set not exist）&lt;/td&gt;
&lt;td&gt;添加一个String类型的键值对，前提是这个key不存在，否则不执行&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SETEX（set expire）&lt;/td&gt;
&lt;td&gt;添加一个String类型的键值对，并且指定有效期，例如 setex name 10 jack 或者 set name jack ex 10&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;Key的层级结构&lt;/h1&gt;
&lt;p&gt;Redis没有类似MySQL中的Table的概念，我们该如何区&lt;br /&gt;
分不同类型的key呢?&lt;br /&gt;
例如，需要存储用户、商品信息到redis，有一个用户id是1，有一个商品id恰好也是1&lt;/p&gt;
&lt;p&gt;Redis的key允许有多个单词形成层级结构，多个单词之间用’:’隔开，格式如下:&lt;/p&gt;
&lt;p&gt;项目名:业务名:类型:id&lt;/p&gt;
&lt;p&gt;例如 如果Value是一个Java对象，例如一个User对象，则可以将对象序列化为JSON字符串后存储:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;KEY&lt;/th&gt;
&lt;th&gt;VALUE&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;wang:user:1&lt;/td&gt;
&lt;td&gt;{“id”:1，”name”:”Jack” , “age”:21}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;wang:product:1&lt;/td&gt;
&lt;td&gt;{“id”:1，”name”:”小米11”，”price”: 4999}&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;Hash类型&lt;/h1&gt;
&lt;p&gt;Hash类型，也叫散列，其value是一个无序字典，类似于Java中的HashMap结构&lt;/p&gt;
&lt;p&gt;String结构是将对象序列化为JSON字符串后存储，当需要修改对象某个字段时很不方便:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;KEY&lt;/th&gt;
&lt;th&gt;VALUE&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;wang:user:1&lt;/td&gt;
&lt;td&gt;{name:”Jack”, age:21}&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;wang:user:2&lt;/td&gt;
&lt;td&gt;{name:”Rose”, age:18}&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Hash结构可以将对象中的每个字段独立存储，可以针对单个字段做CRUD:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;KEY&lt;/th&gt;
&lt;th&gt;VALUE&lt;/th&gt;
&lt;th&gt;VALUE&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;field&lt;/td&gt;
&lt;td&gt;value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;wang:user:1&lt;/td&gt;
&lt;td&gt;name&lt;/td&gt;
&lt;td&gt;Jack&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;age&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;wang:user:2&lt;/td&gt;
&lt;td&gt;name&lt;/td&gt;
&lt;td&gt;Rose&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;age&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;常见命令&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HSET key field value&lt;/td&gt;
&lt;td&gt;添加或者修改hash类型key的field的值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HGET key field&lt;/td&gt;
&lt;td&gt;获取一个hash类型key的field的值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HMSET&lt;/td&gt;
&lt;td&gt;批量添加多个hash类型key的field的值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HMGET&lt;/td&gt;
&lt;td&gt;批量获取多个hash类型key的field的值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HGETALL&lt;/td&gt;
&lt;td&gt;获取一个hash类型的key中的所有的field和value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HKEYS&lt;/td&gt;
&lt;td&gt;获取一个hash类型的key中的所有的field&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HVALS&lt;/td&gt;
&lt;td&gt;获取一个hash类型的key中的所有的value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HINCRBY&lt;/td&gt;
&lt;td&gt;让一个hash类型key的字段值自增并指定步长&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HSETNX&lt;/td&gt;
&lt;td&gt;添加一个hash类型的key的field值，前提是这个field不存在，否则不执行&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;List类型&lt;/h1&gt;
&lt;p&gt;Redis中的List类型与Java中的LinkedList类似，可以看做是一个双向链表结构。既可以支持正向检索和也可以支持反向检索。&lt;/p&gt;
&lt;p&gt;特征也与LinkedList类似:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;有序&lt;/li&gt;
&lt;li&gt;元素可以重复&lt;/li&gt;
&lt;li&gt;插入和删除快&lt;/li&gt;
&lt;li&gt;查询速度一般&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;常见命令&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;LPUSH key element …&lt;/td&gt;
&lt;td&gt;向列表左侧插入一个或多个元素&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LPOP key&lt;/td&gt;
&lt;td&gt;移除并返回列表左侧的第一个元素，没有则返回nil&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RPUSH key element …&lt;/td&gt;
&lt;td&gt;向列表右侧插入一个或多个元素&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RPOP key&lt;/td&gt;
&lt;td&gt;移除并返回列表右侧的第一个元素&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LRANGE key star end&lt;/td&gt;
&lt;td&gt;返回一段角标范围内的所有元素（-1为队尾）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BLPOP和BRPOP&lt;/td&gt;
&lt;td&gt;与LPOP和RPOP类似，只不过在没有元素时等待指定时间，而不是直接返回nil&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;如何利用List结构模拟一个栈?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;入口和出口在同一边&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如何利用List结构模拟一个队列?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;入口和出口在不同边&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如何利用List结构模拟一个阻塞队列?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;入口和出口在不同边&lt;/li&gt;
&lt;li&gt;出队时采用BLPOP或BRPOP&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Set类型&lt;/h1&gt;
&lt;p&gt;Redis的Set结构与Java中的HashSet类似，可以看做是一个value为null的HashMap。因为也是一个hash表，&lt;/p&gt;
&lt;p&gt;因此具备与HashSet类似的特征:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;无序&lt;/li&gt;
&lt;li&gt;元素不可重复&lt;/li&gt;
&lt;li&gt;查找快&lt;/li&gt;
&lt;li&gt;支持交集、并集、差集等功能&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;常见命令&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SADD key member …&lt;/td&gt;
&lt;td&gt;向set中添加一个或多个元素&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SREM key member …&lt;/td&gt;
&lt;td&gt;移除set中的指定元素&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SCARD key&lt;/td&gt;
&lt;td&gt;返回set中元素的个数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SISMEMBER key member&lt;/td&gt;
&lt;td&gt;判断一个元素是否存在于set中&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SMEMBERS&lt;/td&gt;
&lt;td&gt;获取set中的所有元素&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SINTER key1 key2 …&lt;/td&gt;
&lt;td&gt;求key1和key2的交集&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SDIFF key1 key2 …&lt;/td&gt;
&lt;td&gt;求key1和key2的差集&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SUNION key1 key2 …&lt;/td&gt;
&lt;td&gt;求key1和key2的并集&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;SortedSet类型&lt;/h1&gt;
&lt;p&gt;Redis的SortedSet是一个可排序的set集合，与Java中的TreeSet(红黑树)有些类似，但底层数据结构却差别很大。SortedSet中的每一个元素都带有一个score属性，可以基于score属性对元素排序，底层的实现是一个跳表(SkipList)加 hash表。&lt;/p&gt;
&lt;p&gt;SortedSet具备下列特性:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;可排序&lt;/li&gt;
&lt;li&gt;元素不重复&lt;/li&gt;
&lt;li&gt;查询速度快&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因为SortedSet的可排序特性，经常被用来实现排行榜这样的功能。&lt;/p&gt;
&lt;p&gt;常见命令&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;命令&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ZADD key score member&lt;/td&gt;
&lt;td&gt;添加一个或多个元素到sorted set，如果已经存在则更新其score值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ZREM key member&lt;/td&gt;
&lt;td&gt;删除sorted set中的一个指定元素&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ZSCORE key member&lt;/td&gt;
&lt;td&gt;获取sorted set中的指定元素的score值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ZRANK key member&lt;/td&gt;
&lt;td&gt;获取sorted set中的指定元素的排名&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ZCARD key&lt;/td&gt;
&lt;td&gt;获取sorted set中的元素个数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ZCOUNT key min max&lt;/td&gt;
&lt;td&gt;统计score值在给定范围内的所有元素的个数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ZINCRBY key increment member&lt;/td&gt;
&lt;td&gt;让sorted set中的指定元素自增，步长为指定的increment值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ZRANGE key min max&lt;/td&gt;
&lt;td&gt;按照score排序后，获取指定排名范围内的元素&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ZRANGEBYSCORE key min max&lt;/td&gt;
&lt;td&gt;按照score排序后，获取指定score范围内的元素&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ZDIFF、ZINTER、ZUNION&lt;/td&gt;
&lt;td&gt;求差集、交集、并集&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;注意:所有的排名默认都是升序，如果要降序则在命令的Z后面添加REV即可&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Redis00-基础</title><link>https://wang-nine.github.io/posts/10701/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10701/</guid><description>Redis诞生于2009年全称是Remote Dictionary Server，远程词典服务器，是一个基于内存的键值型NoSQL数据库。</description><pubDate>Sat, 01 Jul 2023 15:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;NoSQL&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;SQL&lt;/th&gt;
&lt;th&gt;NoSQL&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;数据结构&lt;/td&gt;
&lt;td&gt;结构化(Structured)&lt;/td&gt;
&lt;td&gt;非结构化&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;数据关联&lt;/td&gt;
&lt;td&gt;关联的(Relational)&lt;/td&gt;
&lt;td&gt;无关联的&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;查询方式&lt;/td&gt;
&lt;td&gt;SQL查询&lt;/td&gt;
&lt;td&gt;非SQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;事务特性&lt;/td&gt;
&lt;td&gt;事务ACID&lt;/td&gt;
&lt;td&gt;BASE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;存储方式&lt;/td&gt;
&lt;td&gt;磁盘&lt;/td&gt;
&lt;td&gt;内存&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;扩展性&lt;/td&gt;
&lt;td&gt;垂直&lt;/td&gt;
&lt;td&gt;水平&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;使用场景&lt;/td&gt;
&lt;td&gt;1）数据结构固定  &amp;lt;br&amp;gt;2）相关业务对数据安全性、—致性要求较高&lt;/td&gt;
&lt;td&gt;1）数据结构不固定  &amp;lt;br&amp;gt;2）对—致性、安全性要求不高  &amp;lt;br&amp;gt;3）对性能要求高&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;Redis&lt;/h1&gt;
&lt;p&gt;Redis诞生于2009年全称是Remote Dictionary Server，远程词典服务器，是一个基于内存的键值型NoSQL数据库。&lt;/p&gt;
&lt;p&gt;特征:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;键值（ key-value）型，value支持多种不同数据结构，功能丰富&lt;/li&gt;
&lt;li&gt;单线程，每个命令具备原子性&lt;/li&gt;
&lt;li&gt;低延迟，速度快（基于内存、IO多路复用、良好的编码）&lt;/li&gt;
&lt;li&gt;支持数据持久化&lt;/li&gt;
&lt;li&gt;支持主从集群、分片集群&lt;/li&gt;
&lt;li&gt;支持多语言客户端&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;安装与启动&lt;/h1&gt;
&lt;p&gt;启动&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;redis-server redis.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ps -ef | grep redis
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;开机自启&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vi /etc/systemd/system/redis.service
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;内容如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Unit]
Description=redis-server
After=network.target

[Service]
Type=forking
ExecStart=/usr/loca/bin/redis-server /usr/local/src/redis-6.2.6/redis.conf
PrivateTmp=true

[Install]
WantedBy=multi-user.target
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重载服务器&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl daemon-reload
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动redis&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl start redis
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看redis状态&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl status redis
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;关闭redis&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl stop redis
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;设置开机自启&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl enable redis
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Redis客户端&lt;/h1&gt;
&lt;p&gt;包括:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;命令行客户端&lt;/li&gt;
&lt;li&gt;图形化桌面客户端&lt;/li&gt;
&lt;li&gt;编程客户端&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;命令行客户端&lt;/h2&gt;
&lt;p&gt;Redis安装完成后就自带了命令行客户端: redis-cli，使用方式如下:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;redis-cli [options] [commonds]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中常见的options有:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;-h 127.0.0.1:指定要连接的redis节点的IP地址，默认是127.0.0.1&lt;/li&gt;
&lt;li&gt;-p 6379:指定要连接的redis节点的端口，默认是6379&lt;/li&gt;
&lt;li&gt;-a 123321:指定redis的访问密码&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其中的commonds就是Redis的操作命令，例如:&lt;br /&gt;
ping : 与redis服务端做心跳测试，服务端正常会返回 pong&lt;br /&gt;
不指定commond时，会进入 redis-cli 的交互控制台:&lt;/p&gt;
</content:encoded></item><item><title>Redis00-基础</title><link>https://wang-nine.github.io/posts/10700/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10700/</guid><description>基于Redis以及SpringBoot，实现了较为完整的业务流程</description><pubDate>Fri, 30 Jun 2023 15:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;基于Redis以及SpringBoot，实现了较为完整的业务流程&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;使用Redis解决了集群模式下的Session共享问题。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;使用拦截器实现了用户的登陆校验功能和登录时间刷新功能。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;使用Redis进行缓存，降低了数据库查询压力，解决了缓存穿透，雪崩，击穿的问题。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;使用Redis与简单的Lua脚本实现了Redis操作的原子化，实现高可用，解决了用户秒杀资格的检测，同时使用了乐观锁解决了超卖问题。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;==使用Redis解决了集群模式下的Session共享问题。==&lt;/p&gt;
&lt;p&gt;Redis 在本组业务中首先承担共享存储的角色：验证码和登录信息不再只保存在某个应用实例的内存里，各实例访问同一份 Redis 数据后，才能在请求切换实例时继续识别用户。验证码与登录态应分别设计键和有效期，不能混为一条永久记录。&lt;/p&gt;
&lt;p&gt;登录校验和登录有效期刷新是两项不同职责。请求携带有效凭证时可以恢复用户身份并续期，而是否允许访问某个业务接口，还要根据该路径是否要求登录来判断；公开页面也可能需要刷新已经存在的有效登录态。&lt;/p&gt;
&lt;p&gt;缓存把可复用的查询结果放在数据库之前，但未命中仍可能回到数据库。处理风险时应区分三种原因：请求的数据本来不存在、单个热点键失效，以及大量键同时失效或缓存服务不可用，它们分别对应穿透、击穿与雪崩，保护策略不能互相替代。&lt;/p&gt;
&lt;p&gt;秒杀流程需要把资格判断与并发约束分别落实：Redis 内相互依赖的判断和修改可以放入同一段 Lua 脚本，数据库库存则应在更新时检查条件。脚本的原子执行范围限于 Redis，不会自动把数据库更新、订单写入和服务高可用一并变成原子保证。&lt;/p&gt;
</content:encoded></item><item><title>SpringCloud05-Nacos</title><link>https://wang-nine.github.io/posts/10605/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10605/</guid><description>简单的介绍了SpringCloud中的Nacos。</description><pubDate>Fri, 30 Jun 2023 14:00:00 GMT</pubDate><content:encoded>&lt;p&gt;简单的介绍了SpringCloud中的Nacos。&lt;/p&gt;
&lt;h1&gt;Nacos注册中心&lt;/h1&gt;
&lt;h2&gt;启动&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;startup.cmd -m standalone
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;注册与发现&lt;/h2&gt;
&lt;p&gt;由于SpringCloud Commons定义了统一的接口规范&lt;/p&gt;
&lt;p&gt;所以只需要改变引入的依赖和地址即可&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;在cloud-demo父工程中添加spring-cloud-alilbaba的管理依赖:&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;com.alibaba.cloud&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-cloud-alibaba-dependencies&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;2.2.5.RELEASE&amp;lt;/version&amp;gt;
    &amp;lt;type&amp;gt;pom&amp;lt;/type&amp;gt;
    &amp;lt;scope&amp;gt;import&amp;lt;/scope&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;注释掉order-service和user-service中原有的eureka依赖。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;添加nacos的客户端依赖&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!-- nacos客户端依赖 --&amp;gt;
&amp;lt;dependency&amp;gt;
	&amp;lt;groupId&amp;gt;com.alibaba.cloud&amp;lt;/groupId&amp;gt;
	&amp;lt;arifactId&amp;gt;spring-cloud-starter-alibaba-nacos-discovery&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;修改user-service&amp;amp;order-service中的application.yml文件，注释eureka地址，添加nacos地址&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;spring:
	cloud:
		discovery:
			nacos:
				server-addr: localhost:8848 # nacos服务端地址
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;启动并测试&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;总结&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Nacos服务搭建
- 下载安装包
- 解压
- 在bin目录下运行指令: startup.cmd -m standalone&lt;/li&gt;
&lt;li&gt;Nacos服务注册或发现
- 引入nacos.discovery依赖
- 配置nacos地址spring.cloud.discovery.nacos.server-addr&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Nacos服务分级存储模型&lt;/h2&gt;
&lt;p&gt;xxxxxxxxxx6 1ribbon:2 eager-load:3 enabled: true # 开启饥饿加载4 clients: userservice # 指定对userservice这个服务饥饿加载5 - userservice16 - userservice2yaml&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;spring:
	cloud:
		nacos:
			server-addr: localhost:8848 # nacos服务端地址
			discovery:
				cluster-name: HZ #配置集群名称，也就是机房位置，例如:HZ，杭州
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;总结&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Nacos服务分级存储模型&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;一级是服务，例如userservice&lt;/li&gt;
&lt;li&gt;二级是集群，例如杭州或上海&lt;/li&gt;
&lt;li&gt;三级是实例，例如杭州机房的某台部署了userservice的服务器&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;如何设置实例的集群属性&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;修改application.yml文件，添加spring.cloud.nacos.discovery.cluster-name属性即可
心&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;那么如何设置orderservice也进行集群属性配置使得远程调用可以优先调用本地的集群?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;1.修改order-service中的application.yml，设置集群&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;spring:
	cloud:
		nacos:
			server-addr: localhost:8848 # nacos服务端地址
			discovery:
				cluster-name: HZ # 配置集群名称，也就是机房位置，例如:HZ，杭州
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;2.在order-service中设置负载均衡的IRule为NacosRule，这个规则会优先寻找与自己同集群的服务&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;userservice:
	ribbon:
		NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;3.注意将user-service的权重都设置为1&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Nacos负载均衡的策略：先访问本地的集群，在本地的集群内随机负载均衡&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;那么如何进行根据权重负载均衡呢?&lt;/h3&gt;
&lt;p&gt;Nacos提供了权重配置来控制访问的频次&lt;/p&gt;
&lt;p&gt;在Nacos页面进行权重配置即可&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;权重为0即为停止访问，可以用来停机进行版本升级，再小量的提升权重，放入小部分用户进行测试，若无问题则调整群众，进行一个平滑的升级&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Nacos的环境隔离-namespace&lt;/h2&gt;
&lt;p&gt;环境隔离先从 namespace 入手：在 Nacos 的命名空间管理区域为开发、测试等环境分别建立空间，并记录各自的 ID。微服务通过 &lt;code&gt;spring.cloud.nacos.discovery.namespace&lt;/code&gt; 指向目标空间，消费者与提供者必须处在相同的发现范围内才能互相发现。下方配置填写的是命名空间 ID，不是显示名称；&lt;code&gt;cluster-name&lt;/code&gt; 表示服务实例的集群属性，不能替代 namespace 的环境隔离职责。&lt;/p&gt;
&lt;p&gt;修改命名空间：修改order-service的application.yml，添加namespace:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;spring:
	cloud:
		nacos:
			server-addr: localhost:8848 # nacos服务端地址
			discovery:
				cluster-name: HZ # 配置集群名称，也就是机房位置，例如:HZ，杭州
				namespace: 492a7d5d-237b-46a1-a99a-fa8e98e4b0f9 #命名空间，填ID
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Nacos环境隔离&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;namespace用来做环境隔离&lt;/li&gt;
&lt;li&gt;每个namespace都有唯一id&lt;/li&gt;
&lt;li&gt;不同namespace下的服务不可见&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Nacos注册中心细节&lt;/h1&gt;
&lt;p&gt;服务注册与发现可以拆成四件事：提供者登记实例，客户端维护实例健康信息，消费者订阅或获取服务列表，消费者再按规则选择实例调用。本文采用的 Nacos 客户端配置通过 &lt;code&gt;discovery.ephemeral&lt;/code&gt; 区分临时与持久实例；两者的健康检测和失联后的处理方式应结合服务端版本与协议理解。排查时先核对 namespace、服务名、集群名和实例健康状态，再判断是否是发现或负载均衡问题，不要只看到实例记录仍在就认定它可接收业务流量。&lt;/p&gt;
&lt;p&gt;非临时实例：yml中的discovery:ephemeral: false&lt;/p&gt;
&lt;p&gt;1.Nacos与eureka的共同点&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;都支持服务注册和服务拉取&lt;/li&gt;
&lt;li&gt;都支持服务提供者心跳方式做健康检测&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;2.Nacos与Eureka的区别&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式，非临时实例采用主动检测模式&lt;/li&gt;
&lt;li&gt;临时实例心跳不正常会被剔除，非临时实例则不会被剔除&lt;/li&gt;
&lt;li&gt;Nacos支持服务列表变更的消息推送模式，服务列表更新更及时&lt;/li&gt;
&lt;li&gt;Nacos集群默认采用AP方式，当集群中存在非临时实例时，采用CP模式;Eureka采用AP方式&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Nacos配置管理&lt;/h1&gt;
&lt;h2&gt;统一配置管理&lt;/h2&gt;
&lt;p&gt;需求：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;配置更改热更新&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如何管理：Nacos表单填写配置名称&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Data ID: 配置文件的id:[服务名称]-[profile].[后缀名]&lt;/li&gt;
&lt;li&gt;Group：分组：默认即可&lt;/li&gt;
&lt;li&gt;格式：建议yaml&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;有个问题：Nacos的地址存于application.yml ，如何先读取到naocs地址呢？&lt;/p&gt;
&lt;p&gt;有个bootstrap.yml优先级高于本地的配置文件，写在其中即可&lt;/p&gt;
&lt;p&gt;本文的启动顺序是先读取引导配置，再连接 Nacos 获取远程配置，随后建立应用环境并创建业务对象。&lt;code&gt;bootstrap.yml&lt;/code&gt; 提供连接地址、服务名、激活环境和后缀，客户端据此定位 Data ID；按文中的 &lt;code&gt;userservice&lt;/code&gt;、&lt;code&gt;dev&lt;/code&gt;、&lt;code&gt;yaml&lt;/code&gt; 组合，对应的环境配置名为 &lt;code&gt;userservice-dev.yaml&lt;/code&gt;。还需核对配置所属的 namespace 与 Group。该说明针对文中启用 bootstrap 的旧版集成方式，升级时应先核对配置加载机制，不能假设仅新增同名文件就必然生效。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;配置步骤如下&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.引入Nacos的配置管理客户端依赖:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!-- nacos配置管理依赖 --&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;com.alibaba.cloud&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-cloud-starter-alibaba-nacos-config&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;2.在userservice中的resource目录添加一个bootstrap.yml文件，这个文件是引导文件，优先级高于application.yml:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;spring:
	application:
		name : userservice #服务名称
	profiles:
		active: dev #开发环境，这里是dev
	cloud:
		nacos:
			server-addr: localhost:8848 #Nacos地址
			config:
				file-extension: yaml #文件后缀名
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;中间的三项配置即为Data ID&lt;/p&gt;
&lt;p&gt;==将配置交给Nacos管理的步骤==&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.在Nacos中添加配置文件&lt;/li&gt;
&lt;li&gt;在微服务中引入nacos的config依赖&lt;/li&gt;
&lt;li&gt;在微服务中添加bootstrap.yml，配置nacos地址、当前环境、服务名称、文件后缀名。这些决定了程序启动时去nacos读取哪个文件&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;==如何进行热更新==&lt;/p&gt;
&lt;p&gt;热更新先确认配置发布到了应用实际读取的 namespace、Group 与 Data ID，再检查客户端是否收到配置变更。对于通过 &lt;code&gt;@Value&lt;/code&gt; 注入的运行参数，本文配合 &lt;code&gt;@RefreshScope&lt;/code&gt; 让相关对象在刷新后重新取值；发布配置与业务对象实际使用新值是两个环节。验证时应读取应用暴露的相关行为或参数，不要仅凭配置中心内容已保存就认定所有对象都已经刷新。&lt;/p&gt;
&lt;p&gt;另一种方式是用 &lt;code&gt;@ConfigurationProperties&lt;/code&gt; 把同一前缀下的配置绑定到配置对象，并让业务代码读取该对象。能否动态重新绑定还取决于该对象是否正确注册、客户端刷新机制和具体版本，不能把注解本身视为热更新开关。可以只调整一个可观察的业务参数，再检查后续请求是否读取新值；连接池、启动结构等配置不一定支持在线变更，应单独确认其生命周期与重建要求。&lt;/p&gt;
&lt;p&gt;Nacos配置更改后，微服务可以实现热更新，方式:&lt;/p&gt;
&lt;p&gt;通过@Value注解注入，结合@RefreshScope来刷新&lt;/p&gt;
&lt;p&gt;通过@ConfigurationProperties注入&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;自动刷新注意事项：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不是所有的配置都适合放到配置中心，维护起来比较麻烦&lt;/li&gt;
&lt;li&gt;建议将一些关键参数，需要运行时调整的参数放到nacos配置中心，一般都是自定义配置&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;共享配置&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;微服务会从nacos读取的配置文件：
&lt;ul&gt;
&lt;li&gt;[服务名]-[spring.profile.active].yaml，环境配置&lt;/li&gt;
&lt;li&gt;[服务名].yaml，默认配置，多环境共享&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;优先级：
&lt;ul&gt;
&lt;li&gt;[服务名]-[环境].yaml &amp;gt;[服务名].yaml &amp;gt;本地配置&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Nacos集群&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/springcloud-tools/nacos-cluster.svg&quot; alt=&quot;Nacos 集群的代理入口、多个节点与配置存储关系&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：多个 Nacos 节点组成集群，按部署方案通过代理提供入口，配置数据使用共享的 MySQL 持久化存储。数据库连线只表示配置存储，不表示所有服务注册信息都写入 MySQL；节点互联、客户端协议和代理端口还需按实际版本配置。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;集群搭建步骤：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;搭建MySQL集群并初始化数据库表&lt;/li&gt;
&lt;li&gt;下载解压nacos&lt;/li&gt;
&lt;li&gt;修改集群配置（节点信息）、数据库配置&lt;/li&gt;
&lt;li&gt;分别启动多个nacos节点&lt;/li&gt;
&lt;li&gt;nginx反向代理&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>MySQL12-MVCC多版本并发控制</title><link>https://wang-nine.github.io/posts/10415/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10415/</guid><description>读取的是记录的最新版本，读取时还要保证其他并发事务不能修改当前记录，会对读取的记录进行加锁。对于我们日常的操作，如:select … lock in share mode(共享锁)，select … for update、update、in…</description><pubDate>Fri, 30 Jun 2023 07:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;基本概念&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;当前读&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;读取的是记录的最新版本，读取时还要保证其他并发事务不能修改当前记录，会对读取的记录进行加锁。对于我们日常的操作，如:select … lock in share mode(共享锁)，select … for update、update、insert、delete(排他锁)都是一种当前读。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;快照读&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;简单的select(不加锁）就是快照读，快照读，读取的是记录数据的可见版本，有可能是历史数据，不加锁，是非阻塞读。&lt;/p&gt;
&lt;p&gt;1.Read Committed:每次select，都生成一个快照读。&lt;/p&gt;
&lt;p&gt;2.Repeatable Read:开启事务后第一个select语句才是快照读的地方。&lt;/p&gt;
&lt;p&gt;3.erializable:快照读会退化为当前读。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MVCC&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;全称Multi-Version Concurrency Control，多版本并发控制。指维护一个数据的多个版本，使得读写操作没有冲突，快照读为My5QL实现MVCC提供了一个非阻塞读功能。MVCC的具体实现，还需要依赖于数据库记录中的三个隐式字段、undo log日志、readview.&lt;/p&gt;
&lt;h1&gt;实现原理&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;记录中的隐式字段&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;隐式字段&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DB_TRX_ID(database transaction id)&lt;/td&gt;
&lt;td&gt;最近修改事务ID，记录插入这条记录或最后一次修改该记录的事务ID。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DB_ROLL_PTR(database rollback pointer)&lt;/td&gt;
&lt;td&gt;回滚指针，指向这条记录的上一个版本，用于配合undo log，指向上一个版本。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DB_ROW_ID(dataabse row id)&lt;/td&gt;
&lt;td&gt;隐藏主键，如果表结构没有指定主键，将会生成该隐藏字段。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;可以使用以下命令查看表空间信息&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ibd2sdi xxx.ibd
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;undo log&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;回滚日志，在insert、update、delete的时候产生的便于数据回滚的日志。&lt;/p&gt;
&lt;p&gt;当insert的时候，产生的undo log日志只在回滚时需要，在事务提交后，可被立即删除。&lt;/p&gt;
&lt;p&gt;而update、delete的时候，产生的undo log日志不仅在回滚时需要，在快照读时也需要，不会立即被删除。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/mysql/mvcc-version-chain.svg&quot; alt=&quot;通过回滚指针和 undo 重建更早版本，再判断快照可见性&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：记录上的修改事务标识和回滚指针把当前状态与撤销信息联系起来。快照读若发现当前版本不可见，就沿撤销信息重建更早版本，直到找到可见记录或确认不存在。图中的事务号与状态值都只是示例；更新、删除的撤销信息可能在提交后仍被旧快照需要，不能立即全部清除。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;readview&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ReadView（读视图）是快照读SQL执行时MVCC提取数据的依据，记录并维护系统当前活跃的事务（未提交的) id。&lt;/p&gt;
&lt;p&gt;ReadView中包含了四个核心字段:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;字段&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;m_ids&lt;/td&gt;
&lt;td&gt;当前活跃的事务ID集合&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;min_trx_id&lt;/td&gt;
&lt;td&gt;最小活跃事务ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;max_trx_id&lt;/td&gt;
&lt;td&gt;预分配事务ID,当前最大事务ID+1（因为事务ID是自增的）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;creator_trx_id&lt;/td&gt;
&lt;td&gt;ReadView创建者的事务ID&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/mysql/readview-visibility.svg&quot; alt=&quot;ReadView 的自身事务、事务上界与活跃集合可见性规则&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：先判断是否为自身修改，再比较版本修改事务号与快照边界。处于中间区间的版本，还要检查修改事务是否在创建快照时的活跃集合中，不能只按事务号大小判断。读已提交级别每次一致性读建立新视图，可重复读通常首次建立后复用；不可见时继续沿撤销链查找，而锁定读不按旧快照选版本。&lt;/p&gt;
&lt;p&gt;==不同的隔离级别，生成ReadView的时机不同:&lt;br /&gt;
READ COMMITTED:在事务中每一次执行快照读时生成ReadView,&lt;br /&gt;
REPEATABLE READ:仅在事务中第一次执行快照读时生成ReadView，后续复用该ReadView.==&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;RC&lt;/li&gt;
&lt;li&gt;RR&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>SpringCloud03-Eureka</title><link>https://wang-nine.github.io/posts/10603/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10603/</guid><description>简单的介绍了SpringCloud中的注册中心Eureka。</description><pubDate>Thu, 29 Jun 2023 15:00:00 GMT</pubDate><content:encoded>&lt;p&gt;简单的介绍了SpringCloud中的注册中心Eureka。&lt;/p&gt;
&lt;h1&gt;提供者与消费者&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;服务提供者:一次业务中，被其它微服务调用的服务。（提供接口给其它微服务）&lt;/li&gt;
&lt;li&gt;服务消费者:一次业务中，调用其它微服务的服务。（调用其它微服务提供的接口）&lt;/li&gt;
&lt;li&gt;提供者与消费者角色其实是&lt;strong&gt;相对&lt;/strong&gt;的&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一个服务可以同时是服务提供者和服务消费者&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Eureka注册中心&lt;/h1&gt;
&lt;p&gt;服务调用出现的问题&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;服务消费者该如何获取服务提供者的地址信息?
&lt;blockquote&gt;
&lt;p&gt;服务提供者启动时向eureka注册自己的信息&lt;/p&gt;
&lt;p&gt;eureka保存这些信息&lt;/p&gt;
&lt;p&gt;消费者根据服务名称向eureka拉取提供者信息&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;如果有多个服务提供者，消费者该如何选择?
&lt;blockquote&gt;
&lt;p&gt;服务消费者利用负载均衡算法，从服务列表中挑选一个&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;消费者如何感知服务提供者健康状态?
&lt;blockquote&gt;
&lt;p&gt;服务提供者会每隔30秒向EurekaServer发送心跳请求，报告健康状态eureka会更新记录服务列表信息，心跳不正常会被剔除消费者就可以拉取到最新的信息&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;解决URL的硬编码问题&lt;/p&gt;
&lt;h2&gt;Eureka的作用&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/springcloud-tools/eureka-discovery.svg&quot; alt=&quot;Eureka 中实例注册、服务发现与直接 HTTP 调用的关系&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：提供者注册地址并发送心跳，消费者获取实例列表并在客户端选择目标。业务请求直接发给目标实例，不通过 Eureka Server 中转；服务发现的缓存与更新也意味着异常感知并非瞬时完成。&lt;/p&gt;
&lt;p&gt;在Eureka架构中，微服务角色有两类:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;EurekaServer:服务端，注册中心
&lt;ul&gt;
&lt;li&gt;记录服务信息&lt;/li&gt;
&lt;li&gt;心跳监控&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;EurekaClient:客户端
&lt;ul&gt;
&lt;li&gt;Provider:服务提供者，例如案例中的user-service
&lt;ul&gt;
&lt;li&gt;注册自己的信息到EurekaServer&lt;/li&gt;
&lt;li&gt;每隔30秒向EurekaServer发送心跳&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;consumer:服务消费者，例如案例中的order-service
&lt;ul&gt;
&lt;li&gt;根据服务名称从EurekaServer拉取服务列表&lt;/li&gt;
&lt;li&gt;基于服务列表做负载均衡，选中一个微服务后发起远程调用&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Eureka远程调用&lt;/h2&gt;
&lt;h3&gt;搭建EurekaServer&lt;/h3&gt;
&lt;p&gt;搭建EurekaServer服务步骤如下:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.创建项目，引入spring-cloud-starter-netflix-eureka-server的依赖&lt;pre&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
	&amp;lt;groupId&amp;gt;org.springframework.cloud&amp;lt;/groupId&amp;gt;
	&amp;lt;artifactId&amp;gt;spring-cloud-starter-netflix-eureka-server&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;2.编写启动类，添加@EnableEarekaServer注解&lt;/li&gt;
&lt;li&gt;3.添加application.yml文件，编写下面的配置:&lt;pre&gt;&lt;code&gt;server:
	port: 10086 # 服务端口
spring:
	application:
		name: eurekaserver # Eureka服务名称
eureka:
	client:
		service-url: # Eureka的地址信息
			defaultzone: http://127.0.0.1:10086/eureka/
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;注册微服务&lt;/h3&gt;
&lt;p&gt;将user-service服务注册到EurekaServer步骤如下:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.在user-service项目引入spring-cloud-starter-netflix-eureka-client的依赖&lt;pre&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.springframework.cloud&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-cloud-starter-netflix-eureka-client&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;2.在application.yml文件，编写下面的配置:&lt;pre&gt;&lt;code&gt;spring:
	application:
		name: userservice
eureka:
	client:
		service-url:
			defaultZone: http:/ /127.0.0.1:10086/eureka/
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;在order-service完成服务拉取&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;1.修改OrderService的代码，修改访问的url路径，用服务名代替ip、端口：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;- String url = &quot;http://localhost:8081/user/&quot; + order.getUserId();
+ String url = &quot;http://userservice/user/&quot; + order.getUserId();
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;2.在order-service项目的启动类OrderApplication的RestTemplate添加&lt;strong&gt;负载均衡&lt;/strong&gt;注解：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;@Bean
@LoadBalanced
public RestTemplate restTemplate(){
	return new RestTemplate();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;总结&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;1.搭建EurekaServer
&lt;ul&gt;
&lt;li&gt;引入eureka-server依赖&lt;/li&gt;
&lt;li&gt;添加@EnableEurekaServer注解&lt;/li&gt;
&lt;li&gt;在application.yml中配置eureka地址&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2.服务注册
&lt;ul&gt;
&lt;li&gt;引入eureka-client依赖&lt;/li&gt;
&lt;li&gt;在application.yml中配置eureka地址&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;3.服务发现
&lt;ul&gt;
&lt;li&gt;引入eureka-client依赖&lt;/li&gt;
&lt;li&gt;在application.yml中配置eureka地址&lt;/li&gt;
&lt;li&gt;给RestTemplate添加@LoadBalanced注解&lt;/li&gt;
&lt;li&gt;用服务提供者的服务名称远程调用&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>SpringCloud04-Ribbon负载均衡</title><link>https://wang-nine.github.io/posts/10604/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10604/</guid><description>简单的介绍了SpringCloud中的Ribbon负载均衡。</description><pubDate>Thu, 29 Jun 2023 14:30:00 GMT</pubDate><content:encoded>&lt;p&gt;简单的介绍了SpringCloud中的Ribbon负载均衡。&lt;/p&gt;
&lt;h1&gt;负载均衡原理&lt;/h1&gt;
&lt;h2&gt;负载均衡流程&lt;/h2&gt;
&lt;p&gt;Ribbon拦截请求寻找Eureka返回服务列表，进行负载均衡&lt;/p&gt;
&lt;p&gt;在本文的 Ribbon 调用链中，&lt;code&gt;@LoadBalanced&lt;/code&gt; 的 &lt;code&gt;RestTemplate&lt;/code&gt; 先由拦截器识别 URL 中的服务名，再通过服务发现获得可用实例列表，交给 &lt;code&gt;IRule&lt;/code&gt; 选择目标实例，最后用选中实例的主机和端口发出 HTTP 请求。负载均衡发生在消费者一侧，Eureka 并不代理业务流量；实例列表通常还涉及客户端缓存与更新，不应理解为每次业务请求都同步查询一次注册中心。&lt;/p&gt;
&lt;h2&gt;负载均衡策略&lt;/h2&gt;
&lt;p&gt;IRule&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;内置负载均衡规则类&lt;/th&gt;
&lt;th&gt;规则描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;RoundRobinRule&lt;/td&gt;
&lt;td&gt;简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AvailabilityFilteringRule&lt;/td&gt;
&lt;td&gt;对以下两种服务器进行忽略: (1）在默认情况下，这台服务器如果3次连接失败，这台服务器就会被设置为“短路”状态。短路状态将持续30秒，如果再次连接失败，短路的持续时间就会几何级地增加。(2）并发数过高的服务器。如果一个服务器的并发连接数过高，配置了AvailabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上限，可以由客户端的\.\.ActiveConnectionsLimit属性进行配置。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;weightedResponseTimeRule&lt;/td&gt;
&lt;td&gt;为每一个服务器赋予一个权重值。服务器响应时间越长，这个服务器的权重就越小。这个规则会随机选择服务器，这个权重值会影响服务器的选择。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ZoneAvoidanceRule&lt;/td&gt;
&lt;td&gt;以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类，这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BestAvailableRule&lt;/td&gt;
&lt;td&gt;忽略哪些短路的服务器，并选择并发数较低的服务器。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RandomRule&lt;/td&gt;
&lt;td&gt;随机选择一个可用的服务器。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RetryRule&lt;/td&gt;
&lt;td&gt;重试机制的选择逻辑。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;调整负载均衡策略&lt;/h2&gt;
&lt;p&gt;通过定义IRule实现可以修改负载均衡规则，有两种方式:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.代码方式:在order-service中的OrderApplication类中，定义一个新的IRule:&lt;strong&gt;全局生效&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;@Bean
public IRule randomRule(){
	return new RandomRule();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;2 配置文件方式:在order-service的application.yml文件中，添加新的配置也可以修改规则: &lt;strong&gt;某个微服务范围生效&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;userservice:
	ribbon:
		NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #负载均衡规则
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;饥饿加载&lt;/h2&gt;
&lt;p&gt;Ribbon默认是采用懒加载，即第一次访问时才会去创建LoadBalanceClient，请求时间会很长。而饥饿加载则会在项目启动时创建，降低第一次访问的耗时，通过下面配置开启饥饿加载:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ribbon:
	eager-load:
		enabled: true # 开启饥饿加载
		clients: userservice # 指定对userservice这个服务饥饿加载
			- userservice1
			- userservice2
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>SpringCloud02-服务拆分</title><link>https://wang-nine.github.io/posts/10602/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10602/</guid><description>简单的介绍了SpringCloud中的服务拆分。</description><pubDate>Thu, 29 Jun 2023 14:00:00 GMT</pubDate><content:encoded>&lt;p&gt;简单的介绍了SpringCloud中的服务拆分。&lt;/p&gt;
&lt;h1&gt;服务拆分&lt;/h1&gt;
&lt;p&gt;注意事项：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.不同微服务，不要重复开发相同业务&lt;/li&gt;
&lt;li&gt;2.微服务数据独立，不要访问其它微服务的数据库&lt;/li&gt;
&lt;li&gt;3.微服务可以将自己的业务暴露为接口，供其它微服务调用&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;远程调用&lt;/h1&gt;
&lt;p&gt;如何进行远程调用呢，让其中一个微服务发起http请求，返回需要的数据即可&lt;/p&gt;
&lt;p&gt;步骤：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.注册RestTemplate&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;/**
 * 创建一个RestTemplate并且注入Spring容器
 * @return
 */
@Bean
public RestTemplate restTemplate(){
    return new RestTemplate();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;2.利用RestTemplate发起http请求&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;restTemplate.getForObject(url, User.class);	//自动反序列化为json数据
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>JavaSE03-Java高级</title><link>https://wang-nine.github.io/posts/10103/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10103/</guid><pubDate>Wed, 28 Jun 2023 17:00:00 GMT</pubDate><content:encoded/></item><item><title>SpringCloud00-总览</title><link>https://wang-nine.github.io/posts/10600/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10600/</guid><description>SpringCloud01-微服务简介</description><pubDate>Wed, 28 Jun 2023 11:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;SpringCloud01-微服务简介&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10601/&quot;&gt;SpringCloud01-微服务简介&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;SpringCloud02-服务拆分&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10602/&quot;&gt;SpringCloud02-服务拆分&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;SpringCloud03-Eureka&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10603/&quot;&gt;SpringCloud03-Eureka&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;SpringCloud04-Ribbon负载均衡&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10604/&quot;&gt;SpringCloud04-Ribbon负载均衡&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;SpringCloud05-Nacos&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10605/&quot;&gt;SpringCloud05-Nacos&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;SpringCloud06-Feign&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10606/&quot;&gt;SpringCloud06-Feign&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;SpringCloud07-Gateway&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10607/&quot;&gt;SpringCloud07-Gateway&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;SpringCloud08-Docker&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10608/&quot;&gt;SpringCloud08-Docker&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;SpringCloud09MQ&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10608/&quot;&gt;SpringCloud09-MQ&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>SpringCloud01-微服务简介</title><link>https://wang-nine.github.io/posts/10601/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10601/</guid><description>简单的介绍了SpringCloud中的微服务技术栈。</description><pubDate>Wed, 28 Jun 2023 11:00:00 GMT</pubDate><content:encoded>&lt;p&gt;简单的介绍了SpringCloud中的微服务技术栈。&lt;/p&gt;
&lt;h1&gt;微服务技术栈&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/springcloud-tools/microservice-stack.svg&quot; alt=&quot;微服务请求入口、服务治理、业务集群与运行支撑的分层关系&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：业务服务负责各自能力，网关处理统一入口，注册与配置中心提供治理信息，缓存、搜索与消息队列提供运行能力。日志、监控和部署贯穿这些服务的生命周期；图按职责归类，不表示所有组件都属于 Spring Cloud，也不要求小型系统一次性引入全部组件。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;微服务拆分，形成微服务集群&lt;/li&gt;
&lt;li&gt;注册中心：拉取活注册服务信息（记录微服务的ip端口等，微服务调用关系访问注册中心获得对方信息）&lt;/li&gt;
&lt;li&gt;配置中心：拉取配置信息（统一的管理所有微服务的配置，实现配置的热更新）&lt;/li&gt;
&lt;li&gt;服务网关组件：身份校验，将请求路由到服务，可以进行负载均衡&lt;/li&gt;
&lt;li&gt;分布式缓存：实现高并发&lt;/li&gt;
&lt;li&gt;分布式搜索：进行复杂的搜索&lt;/li&gt;
&lt;li&gt;消息队列：异步通信，提高服务并发&lt;/li&gt;
&lt;li&gt;分布式日志服务：统计整个集群的日志，统一存储，统计进行分析&lt;/li&gt;
&lt;li&gt;系统监控链路追踪：实时监控整个集群中每个节点的运行状态，一旦出现问题可以快速定位异常所在&lt;/li&gt;
&lt;li&gt;自动化部署（Jenkins）&lt;/li&gt;
&lt;li&gt;打包（Docker） kuebernetes RANCHER&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;微服务简介&lt;/h1&gt;
&lt;h2&gt;单体架构&lt;/h2&gt;
&lt;p&gt;将业务的所有功能集中在一个项目中开发，打成一个包部署。&lt;/p&gt;
&lt;p&gt;优点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;架构简单&lt;/li&gt;
&lt;li&gt;部署成本低&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;缺点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;耦合度高&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;分布式架构&lt;/h2&gt;
&lt;p&gt;根据业务功能对系统进行拆分，每个业务模块作为独立项目开发，称为一个服务。&lt;/p&gt;
&lt;p&gt;优点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;降低服务耦合&lt;/li&gt;
&lt;li&gt;有利于服务升级拓展&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;缺点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;服务治理
&lt;ul&gt;
&lt;li&gt;服务拆分粒度？&lt;/li&gt;
&lt;li&gt;服务集群地址如何维护？&lt;/li&gt;
&lt;li&gt;服务之间如何实现远程调用？&lt;/li&gt;
&lt;li&gt;服务健康状态如何感知？&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;微服务架构特征&lt;/h2&gt;
&lt;p&gt;微服务是一种经过良好架构设计的分布式架构方案，微服务架构特征：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;单一职责：微服务拆分粒度更小，每一个服务都对应唯一的业务能力，做到单一职责，避免重复业务开发&lt;/li&gt;
&lt;li&gt;面向服务：微服务对外暴露业务接口&lt;/li&gt;
&lt;li&gt;自治：团队独立、技术独立、数据独立、部署独立&lt;/li&gt;
&lt;li&gt;隔离性强：服务调用做好隔离、容错、降级，避免出现级联问题&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;微服务结构&lt;/h2&gt;
&lt;p&gt;技术框架&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SpringCloud&lt;/li&gt;
&lt;li&gt;Dubbo&lt;/li&gt;
&lt;li&gt;SpringCloudAlibaba&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;差异对比&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Dubbo&lt;/th&gt;
&lt;th&gt;SpringCloud&lt;/th&gt;
&lt;th&gt;SpringCloudAlibaba&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;注册中心&lt;/td&gt;
&lt;td&gt;zookeeper、Redis&lt;/td&gt;
&lt;td&gt;Eureka、Consul&lt;/td&gt;
&lt;td&gt;Nacos、Eureka&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;服务远程调用&lt;/td&gt;
&lt;td&gt;Dubbo协议&lt;/td&gt;
&lt;td&gt;Feign(http协议)&lt;/td&gt;
&lt;td&gt;Dubbo、Feign&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;配置中心&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;SpringCloudConfig&lt;/td&gt;
&lt;td&gt;SpringCloudConfig、Nacos&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;服务网关&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;SpringCloudGateway、Zuul&lt;/td&gt;
&lt;td&gt;SpringCloudGateway、Zuul&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;服务监控和保护&lt;/td&gt;
&lt;td&gt;Dubbo-admin，功能弱&lt;/td&gt;
&lt;td&gt;Hystrix&lt;/td&gt;
&lt;td&gt;Sentinel&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;SpringCloud&lt;/h2&gt;
&lt;p&gt;SpringCloud是目前国内使用最广泛的微服务框架。&lt;/p&gt;
&lt;p&gt;SpringCloud基于SpringBoot&lt;/p&gt;
&lt;p&gt;Spring Boot 负责单个服务的应用组织与启动，Spring Cloud 则围绕多个服务协作提供治理组件。可以沿一条调用链理解它们：服务先注册实例信息，消费者按服务名发现实例，再通过 HTTP 客户端发起调用；外部请求由网关统一路由，跨服务的配置、监控与容错由相应组件配合。前面的组件对照表属于本文学习时的技术背景，实际选型还应核对 Spring Boot、Spring Cloud 与 Alibaba 组件的版本兼容关系。&lt;/p&gt;
</content:encoded></item><item><title>SSM12-MybatisPlus</title><link>https://wang-nine.github.io/posts/10312/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10312/</guid><description>==极其精简化了dao层的书写==</description><pubDate>Wed, 28 Jun 2023 10:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;基础案例&lt;/h1&gt;
&lt;p&gt;==极其精简化了dao层的书写==&lt;/p&gt;
&lt;p&gt;==先引入mp起步依赖==&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.mybatis.spring.boot&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;mybatis-spring-boot-starter&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;3.0.2&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;==导入实体类和表==&lt;/p&gt;
&lt;p&gt;==定义数据接口继承BaseMapper&amp;lt;泛型&amp;gt;即可==&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Mapper
public interface UserDao extends BaseMapper&amp;lt;User&amp;gt; {
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;CURD和分页&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;功能&lt;/th&gt;
&lt;th&gt;自定义接口&lt;/th&gt;
&lt;th&gt;MP接口&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;新增&lt;/td&gt;
&lt;td&gt;boolean save(T t)&lt;/td&gt;
&lt;td&gt;int insert(T t)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;删除&lt;/td&gt;
&lt;td&gt;boolean delete(int id)&lt;/td&gt;
&lt;td&gt;int deleteById(Serializable id)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;修改&lt;/td&gt;
&lt;td&gt;boolean update(T t)&lt;/td&gt;
&lt;td&gt;int updateById(T t)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;根据id查询&lt;/td&gt;
&lt;td&gt;T getById(int id)&lt;/td&gt;
&lt;td&gt;T selectById (Serializable id)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;查询全部&lt;/td&gt;
&lt;td&gt;List getAll()&lt;/td&gt;
&lt;td&gt;List selectList()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;分页查询&lt;/td&gt;
&lt;td&gt;PageInfo getAll(int page, int size)&lt;/td&gt;
&lt;td&gt;IPage selectPage(IPage page)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;条件查询&lt;/td&gt;
&lt;td&gt;List getAll(Condition condition)&lt;/td&gt;
&lt;td&gt;IPage selectPage(wrapper querywrapper)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;==自动动态SQL==&lt;/p&gt;
&lt;p&gt;样例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@SpringBootTest
class MybatisplusApplicationTests {

    @Autowired
    private UserDao userDao;

    //添加
    @Test
    void testSave() {
        User user = new User();
        user.setName(&quot;Wang&quot;);
        user.setPassword(&quot;123456&quot;);
        user.setAge(9);
        user.setTel(&quot;116483477&quot;);
        userDao.insert(user);
    }

    //删除
    @Test
    void testDelete() {
        userDao.deleteById(1L);
    }

    //修改
    @Test
    void testUpdate() {
        User user = new User();
        user.setId(1L);
        user.setName(&quot;Wang-nine&quot;);
        userDao.updateById(user);
    }

    //查找
    @Test
    void testGetById() {
        User user = userDao.selectById(1L);
        System.out.println(user);
    }

    //查找全部
    @Test
    void testGetAll() {
        List&amp;lt;User&amp;gt; users = userDao.selectList(null);
        System.out.println(users);
    }

    //分页查询
    @Test
    void testGetByPage() {
        IPage page = new Page(1, 2);
        userDao.selectPage(page, null);
        System.out.println(&quot;当前页码值：&quot; + page.getCurrent());
        System.out.println(&quot;每页页码值：&quot; + page.getSize());
        System.out.println(&quot;一共多少页：&quot; + page.getPages());
        System.out.println(&quot;一共多少条：&quot; + page.getTotal());
        System.out.println(&quot;数据：&quot; + page.getRecords());
    }
    
    //

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;==对于分页查询，开启拦截器和输出日志==&lt;/p&gt;
&lt;p&gt;拦截器&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Configuration
public class MpConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        //1.定义mp拦截器
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        //2.添加具体的拦截器
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return mybatisPlusInterceptor;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配置日志输出&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;==条件查询==&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//按条件查询
@Test
void testGetByCondition() {
    //第一种方法
    QueryWrapper qw = new QueryWrapper();
    //查询小于18岁的
    qw.lt(&quot;age&quot;, 18);
    List&amp;lt;User&amp;gt; users = userDao.selectList(qw);
    System.out.println(users);

    //第二种方法，lambda表达式
    QueryWrapper&amp;lt;User&amp;gt; qw1 = new QueryWrapper();
    qw1.lambda().lt(User::getAge, 18);
    List&amp;lt;User&amp;gt; users1 = userDao.selectList(qw);
    System.out.println(users1);

    //第三种方法，lambda表达式
    LambdaQueryWrapper&amp;lt;User&amp;gt; lqw = new LambdaQueryWrapper&amp;lt;User&amp;gt;();
    lqw.lt(User::getAge, 18);
    List&amp;lt;User&amp;gt; users2 = userDao.selectList(lqw);
    System.out.println(users2);

    //链式编程
    LambdaQueryWrapper&amp;lt;User&amp;gt; lqw1 = new LambdaQueryWrapper&amp;lt;&amp;gt;();
    lqw1.lt(User::getAge, 30).or().gt(User::getAge, 10);
    
    //判断为空
    //其弊端，判断年龄上下限，需要新来一个类继承一下user同时自己扩展一个age2
    //模拟页面传递的数据，万一age2为空之类的情况
    UserQuery uq = new UserQuery();
    uq.setAge(10);
    uq.setAge2(30);
    lqw.lt(null != uq.getAge2(), User::getAge, uq.getAge2());
    lqw.gt(null != uq.getAge(), User::getAge, uq.getAge());
   	...查询语句...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;==查询投影，非常麻烦，不如自己写==&lt;/p&gt;
&lt;p&gt;查询投影解决的是“返回哪些列”，与前面年龄上下限解决的“保留哪些行”不同。先确定调用方真正需要的字段，再让查询只选择这些列；若用实体承接部分列，未查询属性通常不会被填入数据，不能把它们当成完整记录直接覆盖更新。涉及统计或分组时，结果形状也可能不再对应完整 User，应选择合适的结果对象或映射方式，并核对实际生成的 SELECT 列表。&lt;/p&gt;
&lt;p&gt;==对于其余的复杂查询，例如&amp;gt; = between like null in group order等，强烈建议自己写mysql语句，不要再去学习复杂的API了==&lt;/p&gt;
&lt;p&gt;==实在想学，可以查看它的官方文档==&lt;/p&gt;
&lt;p&gt;==字段名称映射(均为熟悉注解)==&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;名称不一致&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;@TableField(value = &quot;数据库字段名&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;存在数据库未定义属性&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;@TableField(exist = false)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;设定不可查询的字段权限，设定不参与查询&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;@TableField(select = false)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;表名与实体类名不一致&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;@TableName(&quot;表名&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;全局配置表名前缀&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mybatis-plus:
	global-config:
		db-config:
			table-prefix: tbl_
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;==id生成策略(均为属性注解)==&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@TableId(value = 数据库主键名称, type = 策略)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;策略：IdType.AUTO 自增 NONE无策略 INPUT自己输入 ASSIGN_ID雪花算法 ASSIGN_UUIDuuid&lt;/p&gt;
&lt;p&gt;主键策略首先要区分由谁提供标识：AUTO 依赖数据库自增，INPUT 要求应用在插入前设置主键，ASSIGN_ID 通常由框架的标识生成器分配，默认使用雪花算法，ASSIGN_UUID 面向字符串主键生成标识。NONE 表示没有单独指定策略，可跟随全局配置，并不等于数据库一定自增。应同时核对实体主键类型、数据库列定义、字段级 @TableId 与全局 id-type，避免只改一处配置就假定所有新增记录都会采用同一种方式。&lt;/p&gt;
&lt;p&gt;也可在yaml中全局配置&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mybatis-plus:
	global-config:
		db-config:
			id-type: assign_id
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;==多数据操作==&lt;/p&gt;
&lt;p&gt;样例&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;List&amp;lt;Long&amp;gt; ids = Arrays.asList(new Long[]{2, 3});
userDao.deleteBatchIds(ids);	//删除
userDao.selectBatchIds(ids);	//查询
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;==逻辑删除==&lt;/p&gt;
&lt;p&gt;通用配置&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mybatis-plus:
	global-config:
		db-config:
			logic-delete-field: deleted
			logic-not-delete-value: 0
			logic-delete-value: 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;==乐观锁：比如秒杀（可以解决2000个一下的并发问题）==&lt;/p&gt;
&lt;p&gt;新增一个字段并加上下述注解&lt;/p&gt;
&lt;p&gt;属性注解&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Version
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;@Configuration
public class MpConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        //1.定义mp拦截器
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        //2.添加具体的拦截器
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        //3.添加乐观锁拦截器
        mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return mybatisPlusInterceptor;
    }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>SSM10-SSM整合深入</title><link>https://wang-nine.github.io/posts/10310/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10310/</guid><description>详细的描述了Spring中的SSM整合深入</description><pubDate>Tue, 27 Jun 2023 10:00:00 GMT</pubDate><content:encoded>&lt;p&gt;详细的描述了Spring中的SSM整合深入&lt;/p&gt;
&lt;h1&gt;SSM整合深入&lt;/h1&gt;
&lt;h2&gt;表现层与前端数据协议&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;前端接收数据格式—创建结果模型类，封装数据到data属性中（操作的结果）&lt;/li&gt;
&lt;li&gt;前端接收数据格式—封装操作结果到code属性中（操作的结果：true还是false）&lt;/li&gt;
&lt;li&gt;前端接收数据格式—封装特殊消息到message(msg)属性中（给用户看的）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;综上，设计一个统一数据返回结果类&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class Result {
	private Object data;
    private Integer code;
    private String msg;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Result类中的字段并不是固定的，可以根据需要自行增减提供若干个构造方法，方便操作&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;针对Code也可以设计一个返回结果编码类&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package com.wang.controller;

public class Code {
    public static final Integer SAVE_OK = 20011;
    public static final Integer DELETE_OK = 20021;
    public static final Integer UPDATE_OK = 20031;
    public static final Integer GET_OK = 20041;

    public static final Integer SAVE_ERR = 20010;
    public static final Integer DELETE_ERR = 20020;
    public static final Integer UPDATE_ERR = 20030;
    public static final Integer GET_ERR = 20040;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;SSM异常处理器&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;出现异常现象的常见位置与常见诱因如下:
&lt;ul&gt;
&lt;li&gt;框架内部抛出的异常:因使用不合规导致&lt;/li&gt;
&lt;li&gt;数据层抛出的异常:因外部服务器故障导致（例如:服务器访问超时）&lt;/li&gt;
&lt;li&gt;业务层抛出的异常:因业务逻辑书写错误导致（例如:遍历业务书写操作，导致索引异常等）&lt;/li&gt;
&lt;li&gt;表现层抛出的异常:因数据收集、校验等规则导致（例如:不匹配的数据类型间导致异常）&lt;/li&gt;
&lt;li&gt;工具类抛出的异常:因工具类书写不严谨不够健壮导致（例如:必要释放的连接长期未释放等）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;问题如下&lt;/p&gt;
&lt;p&gt;1.各个层级均出现异常，异常处理代码书写在哪一层？&lt;/p&gt;
&lt;p&gt;==所有的异常均抛出到表现层进行处理== 向上抛到表现层&lt;/p&gt;
&lt;p&gt;2.表现层处理异常，每个方法中单独书写，代码书写量巨大且意义不强，如何解决?&lt;/p&gt;
&lt;p&gt;AOP思想&lt;/p&gt;
&lt;p&gt;实现代码与杂项4-项目中的全局异常处理器相同：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/**
 * 全局异常处理器
 */

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)      //捕获所有的异常
    public Result ex(Exception ex) {
        ex.printStackTrace();
        return Result.error(&quot;对不起，操作失败，请联系管理员&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>SSM09-SSM整合</title><link>https://wang-nine.github.io/posts/10309/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10309/</guid><description>详细的描述了Spring中的SSM整合</description><pubDate>Mon, 26 Jun 2023 14:00:00 GMT</pubDate><content:encoded>&lt;p&gt;详细的描述了Spring中的SSM整合&lt;/p&gt;
&lt;h1&gt;SSM整合流程&lt;/h1&gt;
&lt;h2&gt;1.创建工程&lt;/h2&gt;
&lt;h2&gt;2.SSM整合&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Spring
&lt;ul&gt;
&lt;li&gt;SpringConfig&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;MyBatis
&lt;ul&gt;
&lt;li&gt;MybatisConfig&lt;/li&gt;
&lt;li&gt;JdbcConfig&lt;/li&gt;
&lt;li&gt;Jdbc.properties&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SpringMVC
&lt;ul&gt;
&lt;li&gt;ServletConfig&lt;/li&gt;
&lt;li&gt;SpringMVCConfig&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3.功能模块&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;表与实体类&lt;/li&gt;
&lt;li&gt;dao（接口+自动代理）&lt;/li&gt;
&lt;li&gt;service（接口+实现类）&lt;/li&gt;
&lt;li&gt;业务层接口测试（整合JUnit）&lt;/li&gt;
&lt;li&gt;mapper&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;设计一个简单的图书管理系统&lt;/h1&gt;
&lt;h2&gt;总体思路分析&lt;/h2&gt;
&lt;h3&gt;Spring整合MyBatis&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;配置
&lt;ul&gt;
&lt;li&gt;SpringConfig&lt;/li&gt;
&lt;li&gt;JDBCConfig、jdbc.properties&lt;/li&gt;
&lt;li&gt;MyBatisconfig&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;模型
&lt;ul&gt;
&lt;li&gt;Book&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;数据层标准开发
&lt;ul&gt;
&lt;li&gt;BookDao&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;业务层标准开发
&lt;ul&gt;
&lt;li&gt;BookService&lt;/li&gt;
&lt;li&gt;BookServiceImpl&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;测试接口
BookServiceTest&lt;/li&gt;
&lt;li&gt;事务处理
&lt;ul&gt;
&lt;li&gt;在SpringConfig中开启注解式事务驱动@EnableTransactionManagement&lt;/li&gt;
&lt;li&gt;在JdbcConfig中添加事务管理器PlatformTransactionManager&lt;/li&gt;
&lt;li&gt;在业务层接口上添加Spring事务管理&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Spring整合SpringMVC&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Web配置类（详见SSM07样例代码4）&lt;/li&gt;
&lt;li&gt;SpringMVC配置类 SpringMVCConfig&lt;/li&gt;
&lt;li&gt;基于Restful的Controller开发&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;样例代码以及测试如下&lt;/h1&gt;
&lt;h2&gt;程序框架&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/java-ssm/ssm-request-flow.svg&quot; alt=&quot;SSM 图书管理系统的请求、业务、持久层与容器配置&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：图书请求先由 DispatcherServlet 分派给 BookController，再委托 BookService 与 MyBatis 创建的 BookDao 代理访问数据库。结果按调用链返回，由 MVC 转为 JSON 响应。ServletConfig 分别加载根容器与 MVC 子容器配置，前者管理业务、数据源和持久层，后者管理控制器；这解释了程序如何协作，而不是把目录结构当作实际请求路径。&lt;/p&gt;
&lt;h2&gt;MySQL数据库的设计（简单设计，样例较为简单，用于测试）&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;create database ssm_db default charset utf8mb4;

use ssm_db;

create table tbl_book (
    id int not null auto_increment comment &apos;id&apos;,
    type varchar(20) not null comment &apos;种类&apos;,
    name varchar(50) not null comment &apos;书名&apos;,
    description varchar(255) not null comment &apos;简介&apos;,
    primary key (id)
) COMMENT=&apos;商品表&apos;;

begin;
   insert into tbl_book(type, name, description) values (&apos;计算机理论&apos;, &apos;Spring实战第一版&apos;, &apos;Spring经典教程1&apos;);
   insert into tbl_book(type, name, description) values (&apos;计算机理论&apos;, &apos;Spring实战第二版&apos;, &apos;Spring经典教程2&apos;);
   insert into tbl_book(type, name, description) values (&apos;计算机理论&apos;, &apos;Spring实战第三版&apos;, &apos;Spring经典教程3&apos;);
   insert into tbl_book(type, name, description) values (&apos;计算机理论&apos;, &apos;Spring实战第四版&apos;, &apos;Spring经典教程4&apos;);
   insert into tbl_book(type, name, description) values (&apos;计算机理论&apos;, &apos;Spring实战第五版&apos;, &apos;Spring经典教程5&apos;);
   insert into tbl_book(type, name, description) values (&apos;计算机理论&apos;, &apos;Spring实战第六版&apos;, &apos;Spring经典教程6&apos;);
   insert into tbl_book(type, name, description) values (&apos;计算机理论&apos;, &apos;Spring实战第七版&apos;, &apos;Spring经典教程7&apos;);
   insert into tbl_book(type, name, description) values (&apos;计算机理论&apos;, &apos;Spring实战第八版&apos;, &apos;Spring经典教程8&apos;);
   insert into tbl_book(type, name, description) values (&apos;计算机理论&apos;, &apos;Spring实战第九版&apos;, &apos;Spring经典教程9&apos;);
   insert into tbl_book(type, name, description) values (&apos;市场营销&apos;, &apos;市场营销第一版&apos;, &apos;市场营销教程1&apos;);
   insert into tbl_book(type, name, description) values (&apos;市场营销&apos;, &apos;市场营销第二版&apos;, &apos;市场营销教程2&apos;);
   insert into tbl_book(type, name, description) values (&apos;市场营销&apos;, &apos;市场营销第三版&apos;, &apos;市场营销教程3&apos;);
commit;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;详细的代码放到了github，网址如下，需要自取：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/Wang-nine/smallLibraryManagementSystem&quot;&gt;小型的SSM图书管理系统&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;简单的使用postman对其中的增删改查功能进行了测试&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;按 BookController 的路由，可在 Postman 依次验证：GET /books 查询列表，GET /books/{id} 查询指定图书，POST /books 新增，PUT /books 修改，DELETE /books/{id} 删除。新增和修改请求使用 JSON 请求体，字段对应 Book 的 type、name、description，修改时还需携带 id，并设置 Content-Type 为 application/json。不要只看布尔返回值：本例写操作直接返回 true，验证增删改是否真正生效还应重新查询相应记录；路径中的 id 应使用当前数据中的真实主键。&lt;/p&gt;
&lt;h1&gt;具体代码如下（较为冗长，用于本人复习和调试）&lt;/h1&gt;
&lt;h2&gt;Config模块&lt;/h2&gt;
&lt;h3&gt;JdbcConfig&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;package com.wang.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;

public class JdbcConfig {

    @Value(&quot;${jdbc.driver}&quot;)
    private String driver;
    @Value(&quot;${jdbc.url}&quot;)
    private String url;
    @Value(&quot;${jdbc.username}&quot;)
    private String username;
    @Value(&quot;${jdbc.password}&quot;)
    private String password;

    @Bean
    public DataSource dataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }

    @Bean
    // 2.事务管理器
    public PlatformTransactionManager transactionManager(DataSource dataSource){
        DataSourceTransactionManager ds = new DataSourceTransactionManager();
        ds.setDataSource(dataSource);
        return ds;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;MybatisConfig&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;package com.wang.config;

import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;

import javax.sql.DataSource;

public class MybatisConfig {

    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        factoryBean.setTypeAliasesPackage(&quot;com.wang.domain&quot;);
        return factoryBean;
    }

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer() {
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setBasePackage(&quot;com.wang.dao&quot;);
        return msc;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ServletConfig&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;package com.wang.config;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class ServletConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
    protected Class&amp;lt;?&amp;gt;[] getRootConfigClasses() {
        return new Class[]{SpringConfig.class};
    }

    protected Class&amp;lt;?&amp;gt;[] getServletConfigClasses() {
        return new Class[]{SpringMvcConfig.class};
    }

    protected String[] getServletMappings() {
        return new String[]{&quot;/&quot;};
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;SpringConfig&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;package com.wang.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@ComponentScan(&quot;com.wang.service&quot;)
@PropertySource(&quot;classpath:jdbc.properties&quot;)
@Import({JdbcConfig.class, MybatisConfig.class})
// 1.开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {

}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;SpringMVCConfig&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;package com.wang.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@ComponentScan(&quot;com.wang.controller&quot;)
@EnableWebMvc
public class SpringMvcConfig {
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Controller（表现层）&lt;/h2&gt;
&lt;h3&gt;BookController&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;package com.wang.controller;

import com.wang.domain.Book;
import com.wang.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping(&quot;/books&quot;)
public class BookController {

        @Autowired
        private BookService bookService;

        @PostMapping
        public boolean save(@RequestBody Book book) {
                return bookService.save(book);
        }

        @PutMapping
        public boolean update(@RequestBody Book book) {
                return bookService.update(book);
        }

        @DeleteMapping(&quot;/{id}&quot;)
        public boolean delete(@PathVariable Integer id) {
                return bookService.delete(id);
        }

        @GetMapping(&quot;/{id}&quot;)
        public Book getById(@PathVariable Integer id) {
                return bookService.getById(id);
        }

        @GetMapping
        public List&amp;lt;Book&amp;gt; getAll() {
                return bookService.getAll(      );
        }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;dao（持久层/持久层）&lt;/h2&gt;
&lt;h3&gt;BookDao&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;package com.wang.dao;

import com.wang.domain.Book;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

import java.util.List;

public interface BookDao {
    @Insert(&quot;insert into ssm_db.tbl_book(type, name, description) values (#{type}, #{name}, #{description})&quot;)
    public void save(Book book);
    @Update(&quot;update ssm_db.tbl_book set type=#{type}, name=#{name}, description=#{description} where id = #{id}&quot;)
    public void update(Book book);
    @Delete(&quot;delete from ssm_db.tbl_book where id=#{id}&quot;)
    public void delete(Integer id);
    @Select(&quot;select * from ssm_db.tbl_book where id=#{id}&quot;)
    public Book getById(Integer id);
    @Select(&quot;select * from ssm_db.tbl_book&quot;)
    public List&amp;lt;Book&amp;gt; getAll();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Service（业务层）&lt;/h2&gt;
&lt;h3&gt;BookService&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;package com.wang.service;

import com.wang.domain.Book;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
 * @author Wang
 */


@Transactional
public interface BookService {

    /**
     * 保存
     * @param book
     * @return
     */
    public boolean save(Book book);

    /**
     * 修改
     * @param book
     * @return
     */
    public boolean update(Book book);

    /**
     * 根据id删除
     * @param id
     * @return
     */
    public boolean delete(Integer id);

    /**
     * 根据id查找
     * @param id
     * @return
     */
    public Book getById(Integer id);

    /**
     * 查找全部
     *
     * @return
     */
    public List&amp;lt;Book&amp;gt; getAll();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;BookServiceImpl&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;package com.wang.service.impl;

import com.wang.dao.BookDao;
import com.wang.domain.Book;
import com.wang.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class BookServiceImpl implements BookService {

    @Autowired
    private BookDao bookDao;

    public boolean save(Book book) {
        bookDao.save(book);
        return true;
    }

    public boolean update(Book book) {
        bookDao.update(book);
        return true;
    }

    public boolean delete(Integer id) {
        bookDao.delete(id);
        return true;
    }

    public Book getById(Integer id) {
        return bookDao.getById(id);
    }

    public List&amp;lt;Book&amp;gt; getAll() {
        return bookDao.getAll();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Domain（实体类）&lt;/h2&gt;
&lt;h3&gt;Book&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;package com.wang.domain;

public class Book {
    private Integer id;
    private String type;
    private String name;
    private String description;


    public Book() {
    }

    public Book(Integer id, String type, String name, String description) {
        this.id = id;
        this.type = type;
        this.name = name;
        this.description = description;
    }

    /**
     * 获取
     * @return id
     */
    public Integer getId() {
        return id;
    }

    /**
     * 设置
     * @param id
     */
    public void setId(Integer id) {
        this.id = id;
    }

    /**
     * 获取
     * @return type
     */
    public String getType() {
        return type;
    }

    /**
     * 设置
     * @param type
     */
    public void setType(String type) {
        this.type = type;
    }

    /**
     * 获取
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     * @return description
     */
    public String getDescription() {
        return description;
    }

    /**
     * 设置
     * @param description
     */
    public void setDescription(String description) {
        this.description = description;
    }

    public String toString() {
        return &quot;Book{id = &quot; + id + &quot;, type = &quot; + type + &quot;, name = &quot; + name + &quot;, description = &quot; + description + &quot;}&quot;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;测试类&lt;/h2&gt;
&lt;h3&gt;BookServiceTest&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;package wang;

import com.wang.config.SpringConfig;
import com.wang.domain.Book;
import com.wang.service.BookService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

@RunWith(SpringJUnit4ClassRunner.class )
@ContextConfiguration(classes = SpringConfig.class)
public class BookServiceTest {

    @Autowired
    private BookService bookService;

    @Test
    public void testGetById() {
        Book book = bookService.getById(1);
        System.out.println(book);
    }

    @Test
    public void testGetAll() {
        List&amp;lt;Book&amp;gt; books = bookService.getAll();
        System.out.println(books);
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;pom.xml&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;project xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
  xsi:schemaLocation=&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd&quot;&amp;gt;
  &amp;lt;modelVersion&amp;gt;4.0.0&amp;lt;/modelVersion&amp;gt;
  &amp;lt;groupId&amp;gt;org.example&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;SSMtest&amp;lt;/artifactId&amp;gt;
  &amp;lt;packaging&amp;gt;war&amp;lt;/packaging&amp;gt;
  &amp;lt;version&amp;gt;1.0-SNAPSHOT&amp;lt;/version&amp;gt;
  &amp;lt;name&amp;gt;SSMtest Maven Webapp&amp;lt;/name&amp;gt;
  &amp;lt;url&amp;gt;http://maven.apache.org&amp;lt;/url&amp;gt;
  &amp;lt;dependencies&amp;gt;
    &amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;org.springframework&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;spring-webmvc&amp;lt;/artifactId&amp;gt;
      &amp;lt;version&amp;gt;5.2.10.RELEASE&amp;lt;/version&amp;gt;
    &amp;lt;/dependency&amp;gt;

    &amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;org.springframework&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;spring-jdbc&amp;lt;/artifactId&amp;gt;
      &amp;lt;version&amp;gt;5.2.10.RELEASE&amp;lt;/version&amp;gt;
    &amp;lt;/dependency&amp;gt;

    &amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;org.springframework&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;spring-test&amp;lt;/artifactId&amp;gt;
      &amp;lt;version&amp;gt;5.2.10.RELEASE&amp;lt;/version&amp;gt;
    &amp;lt;/dependency&amp;gt;

    &amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;org.mybatis&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;mybatis&amp;lt;/artifactId&amp;gt;
      &amp;lt;version&amp;gt;3.5.6&amp;lt;/version&amp;gt;
    &amp;lt;/dependency&amp;gt;

    &amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;mysql&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;mysql-connector-java&amp;lt;/artifactId&amp;gt;
      &amp;lt;version&amp;gt;5.1.47&amp;lt;/version&amp;gt;
    &amp;lt;/dependency&amp;gt;

    &amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;org.mybatis&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;mybatis-spring&amp;lt;/artifactId&amp;gt;
      &amp;lt;version&amp;gt;1.3.0&amp;lt;/version&amp;gt;
    &amp;lt;/dependency&amp;gt;

    &amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;com.alibaba&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;druid&amp;lt;/artifactId&amp;gt;
      &amp;lt;version&amp;gt;1.1.16&amp;lt;/version&amp;gt;
    &amp;lt;/dependency&amp;gt;

    &amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;junit&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;junit&amp;lt;/artifactId&amp;gt;
      &amp;lt;version&amp;gt;4.12&amp;lt;/version&amp;gt;
      &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;
    &amp;lt;/dependency&amp;gt;

    &amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;javax.servlet&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;javax.servlet-api&amp;lt;/artifactId&amp;gt;
      &amp;lt;version&amp;gt;3.1.0&amp;lt;/version&amp;gt;
      &amp;lt;scope&amp;gt;provided&amp;lt;/scope&amp;gt;
    &amp;lt;/dependency&amp;gt;

    &amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;com.fasterxml.jackson.core&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;jackson-databind&amp;lt;/artifactId&amp;gt;
      &amp;lt;version&amp;gt;2.9.0&amp;lt;/version&amp;gt;
    &amp;lt;/dependency&amp;gt;
  &amp;lt;/dependencies&amp;gt;
  &amp;lt;build&amp;gt;
    &amp;lt;plugins&amp;gt;
      &amp;lt;plugin&amp;gt;
        &amp;lt;groupId&amp;gt;org.apache.tomcat.maven&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;tomcat7-maven-plugin&amp;lt;/artifactId&amp;gt;
        &amp;lt;version&amp;gt;2.1&amp;lt;/version&amp;gt;
        &amp;lt;configuration&amp;gt;
          &amp;lt;port&amp;gt;80&amp;lt;/port&amp;gt;
          &amp;lt;path&amp;gt;/&amp;lt;/path&amp;gt;
        &amp;lt;/configuration&amp;gt;
      &amp;lt;/plugin&amp;gt;
    &amp;lt;/plugins&amp;gt;
  &amp;lt;/build&amp;gt;
&amp;lt;/project&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>JUC</title><link>https://wang-nine.github.io/posts/20007/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/20007/</guid><description>进程:指在系统中正在运行的一个应用程序﹔程序一旦运行就是进程﹔进程——资源分配的最小单位。。 线程:系统分配处理器时间资源的基本单元，或者说进程之内独立执行的一个单元执行流。线程——程序执行的最小单位。</description><pubDate>Sun, 25 Jun 2023 16:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Java.util.concurrent&lt;/h1&gt;
&lt;p&gt;进程:指在系统中正在运行的一个应用程序﹔程序一旦运行就是进程﹔进程——资源分配的最小单位。。&lt;br /&gt;
线程:系统分配处理器时间资源的基本单元，或者说进程之内独立执行的一个单元执行流。线程——程序执行的最小单位。&lt;/p&gt;
&lt;h2&gt;线程的状态（可以通过Thread.State枚举类查看）&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;NEW （新建态）&lt;/li&gt;
&lt;li&gt;RUNNABLE（准备就绪）&lt;/li&gt;
&lt;li&gt;BLOCKED（阻塞）&lt;/li&gt;
&lt;li&gt;WAITING（不见不散）&lt;/li&gt;
&lt;li&gt;TIMED WAITING（过时不候）&lt;/li&gt;
&lt;li&gt;TERMINATED（终止）&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Wait和Sleep的区别&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;sleep是Thread的静态方法，wait是 Object的方法，任何对象实例都能调用&lt;/li&gt;
&lt;li&gt;sleep不会释放锁，它也不需要占用锁。wait会释放锁，但调用它的前提是当前线程占有锁(即代码要在synchronized中)&lt;/li&gt;
&lt;li&gt;它们都可以被interrupted方法中断&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>项目重构</title><link>https://wang-nine.github.io/posts/20006/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/20006/</guid><description>简化开发：二者效果相同</description><pubDate>Sat, 24 Jun 2023 16:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;环境搭建&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;准备数据库表（dept，emp)&lt;/li&gt;
&lt;li&gt;创建SpringBoot工程
&lt;ul&gt;
&lt;li&gt;引入web开发起步依赖，mybatis依赖（mybatis依赖和mysql驱动），lombok依赖&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;在application.properties中引入mybatis的配置信息&lt;/li&gt;
&lt;li&gt;准备对应的实体类&lt;/li&gt;
&lt;li&gt;准备对应的Mapper、Service(接口、实现类)、Controller基础结构&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;部门管理开发：根据接口文档阅读开发Restful风格&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;明确三层架构的功能&lt;/li&gt;
&lt;li&gt;Controller
&lt;ul&gt;
&lt;li&gt;1.接收请求&lt;/li&gt;
&lt;li&gt;2.调用service层功能&lt;/li&gt;
&lt;li&gt;3.响应&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Service
&lt;ul&gt;
&lt;li&gt;调用mapper接口功能&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Mapper
&lt;ul&gt;
&lt;li&gt;执行对应的SQL语句与数据库进行互动&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;简化开发：二者效果相同&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@RequestNapping(value = &quot;/depts&quot;, method = RequestNethod.GET)
@GetMapping(&quot;/ depts&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意在接口中注入Bean对象，例如Controller中注入Service对象，Service中注入Mapper对象，达到解耦的效果&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;==接口测试，postman / 前后端联调==&lt;/p&gt;
&lt;p&gt;==日志：@Slf4j==&lt;/p&gt;
&lt;p&gt;==注意某些路径的 @PathVariable==&lt;/p&gt;
&lt;p&gt;==注意解析json格式的前端请求参数可以使用@RequestBody来将json封装到实体类中==&lt;/p&gt;
&lt;p&gt;==@RequestMapping(“/depts”) 公共路径抽取：完整的请求路径，应该是类上的@RequestMapping 的value属性＋方法上的@RequestMapping的value属性。==&lt;/p&gt;
&lt;h1&gt;员工管理开发：&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;难点：分页查询&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;分页查询&lt;/h2&gt;
&lt;h3&gt;需要两条SQL语句&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;显示当前页面&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;select * from emp limit ?, ?;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;显示所有数据&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;select count(*) from emp;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;前端传来的数据&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;当前页码:page&lt;/li&gt;
&lt;li&gt;每页展示记录数:pageSize&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;后端响应的数据&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;数据列表:List select * from emp limit ?,?;&lt;/li&gt;
&lt;li&gt;总记录数:total select count (*) from emp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;==目前需要返回两项数据，方法只能返回一个数据，使用实体类封装返回==&lt;/p&gt;
&lt;p&gt;==@ResultParam(defaultvalue = ”默认值“) 参数 可以设置参数的默认值==&lt;/p&gt;
&lt;h3&gt;分页查询插件PageHelper&lt;/h3&gt;
&lt;p&gt;使用 PageHelper 时，先确保项目已引入匹配的依赖并注册分页拦截器，再在要分页的 MyBatis 查询前紧邻调用 &lt;code&gt;PageHelper.startPage(page, pageSize)&lt;/code&gt;，随后立即执行目标查询，避免夹入其他查询。分页插件负责改写分页 SQL，并可按配置执行总数查询；返回时将本页数据与总记录数封装到同一个响应对象。检查页码与每页条数的范围、排序是否稳定，以及筛选条件是否同时作用于数据和总数，不要分页之后再对结果重复做内存切片。&lt;/p&gt;
&lt;h1&gt;文件上传&lt;/h1&gt;
&lt;p&gt;文件上传请求使用 &lt;code&gt;multipart/form-data&lt;/code&gt;，让普通字段与文件作为不同部分传给后端。接口联调时，对本文带普通参数的示例填写 &lt;code&gt;username&lt;/code&gt;、&lt;code&gt;age&lt;/code&gt;，将 &lt;code&gt;image&lt;/code&gt; 字段设为文件并选择本地文件；字段名应与接收参数一致。服务端用 &lt;code&gt;MultipartFile&lt;/code&gt; 读取文件，校验大小与实际类型后生成安全的唯一名称，再保存到本地目录或交给 OSS 工具。浏览器提交 &lt;code&gt;FormData&lt;/code&gt; 时应让浏览器生成含 boundary 的请求头，不要手写一个缺少 boundary 的 Content-Type。&lt;/p&gt;
&lt;h2&gt;方法1：将文件保存到本地&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;/*@PostMapping(&quot;/upload&quot;)
public Result upload(String username, Integer age, MultipartFile image) throws Exception {
    log.info(&quot;文件上传：{}，{}，{}&quot;, username, age, image);

    //获取原始文件名
    String originalFilename = image.getOriginalFilename();

    //构造唯一的文件名（不能重复）-- uuid（通用唯一识别码）
    int index = originalFilename.lastIndexOf(&apos;.&apos;);
    String extname = originalFilename.substring(index);
    String newFileName = UUID.randomUUID().toString() + extname;
    log.info(&quot;新的文件名：{}&quot;, newFileName);

    //将接收到的文件存储在服务器的磁盘目录中 D:\image
    image.transferTo(new File(&quot;D:\\image\\&quot; + newFileName));

    return Result.success();
}*/
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;方法2：将文件保存到AliOSS&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;@PostMapping(&quot;/upload&quot;)
public Result upload(MultipartFile image) throws Exception {
    log.info(&quot;文件上传，文件名：{}&quot;, image.getOriginalFilename());

    //调用阿里云OSS工具类
    String url = aliOSSUtils.upload(image);
    log.info(&quot;文件上传完成，文件访问的url为：{}&quot;, url);

    return Result.success(url);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;==修改员工需要查询回显和update语句==&lt;/p&gt;
&lt;p&gt;==配置注入使用@Value(“#{配置中的key}”)注解来防止硬编码==&lt;/p&gt;
&lt;h1&gt;登陆校验（难点）&lt;/h1&gt;
&lt;p&gt;==若按照普通的开发区编写映射页面，通过数据库判别是否用户名与密码正确式不可取的==&lt;/p&gt;
&lt;p&gt;如此的话，直接访问类似于 /emps /depts 等页面可以直接进入控制台面板而跳过了判断是否为管理员的步骤&lt;/p&gt;
&lt;p&gt;因此，我们需要在 /login /depts /emps 之间建立一种联系&lt;/p&gt;
&lt;p&gt;实现思路：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;员工登陆成功存储一个标记&lt;/li&gt;
&lt;li&gt;在每一个方法接口前判断员工是否登陆，登陆则访问，未登录则返回给前端错误信息&lt;/li&gt;
&lt;li&gt;因此，需要两种技术==1.统一拦截== ==2.登陆标记缓存==
&lt;ul&gt;
&lt;li&gt;统一拦截
&lt;ul&gt;
&lt;li&gt;Servlet提供的过滤器Filter&lt;/li&gt;
&lt;li&gt;Spring提供的拦截器Interceptor&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;用户登陆
&lt;ul&gt;
&lt;li&gt;会话技术&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;会话技术&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;会话：指的是用户打开浏览器，访问web服务器的资源，会话建立，直到有一方断开连接，会话结束。在一次会话中可以包含&lt;strong&gt;多次&lt;/strong&gt;请求和响应。&lt;/li&gt;
&lt;li&gt;会话跟踪：一种维护浏览器状态的方法，服务器需要识别多次请求是否来自于同一浏览器，以便在同一次会话的多次请求间&lt;strong&gt;共享数据&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;​ http协议是无状态协议，不会携带上一次会话的信息，因此无法数据共享&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;会话跟踪方案
&lt;ul&gt;
&lt;li&gt;客户端会话跟踪技术：Cookie（存储在浏览器）&lt;/li&gt;
&lt;li&gt;服务端会话跟踪技术：Session（存储在服务器端）&lt;/li&gt;
&lt;li&gt;令牌技术&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Cookie：浏览器第一次请求时在服务器端设置Cookie，响应时携带Cookie，而后Cookie缓存在浏览器本地，之后的每次请求都会携带Cookie与客户端进行对比，服务器自动响应Cookie，浏览器自动保存Cookie，发起请求自动携带Cookie，三个自动表名Http支持Cookie技术
&lt;ul&gt;
&lt;li&gt;请求头Cookie&lt;/li&gt;
&lt;li&gt;响应头Set-Cookie&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;优点：HTTP协议支持&lt;/p&gt;
&lt;p&gt;缺点：1.移动端无法使用 2.不安全，用户可以自己禁用 3.Cookie不能跨域（跨域：协议、IP/域名、端口不同）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Session：浏览器第一次请求时在服务器端设置Session（ID），响应时通过Cookie返回Session的ID，之后的每次请求都会携带Cookie中的Session的ID到服务器端对比&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;优点：存储在服务器端，安全&lt;/p&gt;
&lt;p&gt;缺点：1.服务器集群在负载均衡后有可能无法使用，因此无法在服务器集群环境下使用 2.Cookie的缺点&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;令牌技术（==建议使用==）：用户身份表示，登陆成功生成令牌，响应给浏览器，可以存储在Cookie或者其他存储空间中，之后的每次请求都会携带令牌，比对是否有效，而且想要共享数据只需要将数据保存在令牌中即可&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;优点：1.支持PC、移动端 2、解决集群环境下的认证问题 3、减轻了服务器端的存储压力&lt;/p&gt;
&lt;p&gt;缺点：需要自己去实现&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/blockquote&gt;
&lt;h2&gt;JWT（JSON Web Token）令牌&lt;/h2&gt;
&lt;p&gt;定义了一种简洁的、自包含的格式，用于在通信双方以json数据格式安全的传输信息。由于数字签名的存在，这些信息是可靠的。&lt;/p&gt;
&lt;p&gt;jwt令牌组成&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一部分:Header(头)，记录令牌类型、签名算法等。例如: {“alg”:”HS256” ,”type”:”JWT”}&lt;/li&gt;
&lt;li&gt;第二部分:Payload(有效载荷)，携带一些自定义信息、默认信息等。例如: {“id”:” 1” ,”username”:”Tom”}&lt;/li&gt;
&lt;li&gt;第三部分:Signature(签名)，防止Token被篡改、确保安全性。将header、 payload，并加入指定秘钥，通过指定签名算法计算而来。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;pom.xml引入&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!--JTW令牌--&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;io.jsonwebtoken&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;jjwt&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;0.9.1&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;jwt生成&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class JwtUtils {

    private static String signKey = &quot;wangnine&quot;;
    private static Long expire = 43200000L;

    /**
     * 生成JWT令牌
     * @param claims JWT第二部分负载 payload 中存储的内容
     * @return
     */
    public static String generateJwt(Map&amp;lt;String, Object&amp;gt; claims){
        String jwt = Jwts.builder()
                .addClaims(claims)
                .signWith(SignatureAlgorithm.HS256, signKey)
                .setExpiration(new Date(System.currentTimeMillis() + expire))
                .compact();
        return jwt;
    }

    /**
     * 解析JWT令牌
     * @param jwt JWT令牌
     * @return JWT第二部分负载 payload 中存储的内容
     */
    public static Claims parseJWT(String jwt){
        Claims claims = Jwts.parser()
                .setSigningKey(signKey)
                .parseClaimsJws(jwt)
                .getBody();
        return claims;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;统一拦截技术&lt;/h2&gt;
&lt;h3&gt;Filter过滤器&lt;/h3&gt;
&lt;p&gt;JavaWeb三大组件(Servlet、Filter、Listener)之一。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;过滤器可以把对资源的请求拦截下来，从而实现一些特殊的功能。&lt;/li&gt;
&lt;li&gt;过滤器一般完成一些通用的操作，比如:登录校验、统一编码处理、敏感字符处理等。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;相当于抽取了各个页面的登陆校验，优点aop的思想&lt;/p&gt;
&lt;p&gt;==如何实现过滤器==&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.定义Filter:定义一个类，实现 Filter接口，并重写其所有方法。&lt;/li&gt;
&lt;li&gt;配置Filter: Filter类上加@WebFilter注解，配置拦截资源的路径。引导类上加@ServletComponentScan开启Servlet组件支持。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;结合了jwt后的代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Slf4j
//@WebFilter(urlPatterns = &quot;/*&quot;)  //拦截所有
public class LoginCheckFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        HttpServletResponse resp = (HttpServletResponse) servletResponse;
        //1.获取请求url
        String url = req.getRequestURL().toString();
        log.info(&quot;请求的url：{}&quot;, url);

        //2.判断请求的url中是否包含login，如果包含，说明是登录操作，直接放行
        if(url.contains(&quot;login&quot;)) {
            log.info(&quot;登录操作，放行...&quot;);
            filterChain.doFilter(servletRequest, servletResponse);
            return;
        }

        //3.获取请求头中的token令牌
        String jwt = req.getHeader(&quot;token&quot;);

        //4.判断令牌是否存在，如果不存在，则返回错误结果（未登录）
        if(!StringUtils.hasLength(jwt)) {
            log.info(&quot;请求头token为空，返回未登录的信息&quot;);
            Result error = Result.error(&quot;NOT_LOGIN&quot;);
            //手动将对象转换为json格式
            String notLogin = JSONObject.toJSONString(error);
            resp.getWriter().write(notLogin);
            return;
        }

        //5.解析token，如果解析失败，则返回错误结果（未登录）
        try {
            JwtUtils.parseJWT(jwt);
        } catch (Exception e) {
            log.info(&quot;解析令牌失败，返回未登录的误信息&quot;);
            Result error = Result.error(&quot;NOT_LOGIN&quot;);
            //手动将对象转换为json格式
            String notLogin = JSONObject.toJSONString(error);
            resp.getWriter().write(notLogin);
            return;
        }

        //6.放行
        log.info(&quot;令牌合法，放行&quot;);
        filterChain.doFilter(servletRequest, servletResponse);
    }
}

//对于init 和 destroy 有默认实现，二者只调用一次
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Interceptor拦截器&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;@Slf4j
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {

    @Override   //目标资源方法运行前运行，返回true放行，返回false不放行
    public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {

        //1.获取请求url
        String url = req.getRequestURL().toString();
        log.info(&quot;请求的url：{}&quot;, url);

        //2.判断请求的url中是否包含login，如果包含，说明是登录操作，直接放行
        if(url.contains(&quot;login&quot;)) {
            log.info(&quot;登录操作，放行...&quot;);
            return true;
        }

        //3.获取请求头中的token令牌
        String jwt = req.getHeader(&quot;token&quot;);

        //4.判断令牌是否存在，如果不存在，则返回错误结果（未登录）
        if(!StringUtils.hasLength(jwt)) {
            log.info(&quot;请求头token为空，返回未登录的信息&quot;);
            Result error = Result.error(&quot;NOT_LOGIN&quot;);
            //手动将对象转换为json格式
            String notLogin = JSONObject.toJSONString(error);
            resp.getWriter().write(notLogin);
            return false;
        }

        //5.解析token，如果解析失败，则返回错误结果（未登录）
        try {
            JwtUtils.parseJWT(jwt);
        } catch (Exception e) {
            log.info(&quot;解析令牌失败，返回未登录的误信息&quot;);
            Result error = Result.error(&quot;NOT_LOGIN&quot;);
            //手动将对象转换为json格式
            String notLogin = JSONObject.toJSONString(error);
            resp.getWriter().write(notLogin);
            return false;
        }

        //6.放行
        log.info(&quot;令牌合法，放行&quot;);
        return true;
    }

    @Override   //目标资源方法运行后运行
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println(&quot;postHandle...&quot;);
    }

    @Override   //视图渲染完毕后运行，最后运行
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println(&quot;afterCompletion...&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;异常处理&lt;/h1&gt;
&lt;p&gt;在Controller层定义一个全局异常处理器&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * 全局异常处理器
 */

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)      //捕获所有的异常
    public Result ex(Exception ex) {
        ex.printStackTrace();
        return Result.error(&quot;对不起，操作失败，请联系管理员&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;@RestControllerAdvice&lt;/p&gt;
&lt;p&gt;@ExceptionHandler(Exception.class) //捕获所有的异常&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>JavaSE02-Java中的文件与方法递归与IO流</title><link>https://wang-nine.github.io/posts/10102/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10102/</guid><description>详细的描述了Java中 File类的使用，一步一步剖析了IO流如何创建、如何使用以及其中的注意事项。</description><pubDate>Sat, 24 Jun 2023 15:00:00 GMT</pubDate><content:encoded>&lt;p&gt;详细的描述了Java中 File类的使用，一步一步剖析了IO流如何创建、如何使用以及其中的注意事项。&lt;/p&gt;
&lt;p&gt;File类使用：详细的描述了使用File的对象操作文件，如:删除、获取文件信息、创建文件夹等&lt;br /&gt;
方法递归：详细的描述了递归算法思想并能完成常见递归题目，以及文件搜索&lt;br /&gt;
字符集：程序中经常要读取文件中的数据，知道数据的底层形式才能够去学习如何读写数据&lt;br /&gt;
IO流的作用：使用IO流完成文件数据的读写等操作&lt;br /&gt;
字节流与字符流：数据的类型很多，要学会选择不同的流进行读写操作&lt;/p&gt;
&lt;h1&gt;File&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;File类在包java.io.File下、代表操作系统的文件对象（文件、文件夹）。&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;File类提供了诸如∶定位文件，获取文件本身的信息、删除文件、创建文件（文件夹）等功能&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;但是不能读写文件内容。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;File类创建对象API&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;public File(String pathname)&lt;/td&gt;
&lt;td&gt;根据文件路径创建文件对象&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;public File(String parent, String child)&lt;/td&gt;
&lt;td&gt;从父路径名字符串和子路径名字符串创建文件对象&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;public File(File parent, String child)&lt;/td&gt;
&lt;td&gt;根据父路径对应文件对象和子路径名字符串创建文件对象&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;案例：查询文件的大小&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;import java.io.File;

public class Main {
    public static void main(String[] args) {
        // 1.创建一个FILE文件，指代某个具体的文件
        File f1 = new File(&quot;D:\\test.txt&quot;);
        File f2 = new File(&quot;D:&quot; + File.separator + &quot;test.txt&quot;); //跨平台写法，不同系统的文件分隔符不同
        System.out.println(f1.length());    //文件大小
        System.out.println(f2.length());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;查询文件夹大小为文件夹大小，不包括文件夹内部内容大小。&lt;/li&gt;
&lt;li&gt;File对象既可以代表文件、也可以代表文件夹。&lt;/li&gt;
&lt;li&gt;File封装的对象仅仅是一个路径名，这个路径可以是存在的，也允许是不存在的。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;FILE类的常用方法&lt;/h2&gt;
&lt;h3&gt;File提供的判断文件类型、获取文件信息功能&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;public boolean exists()&lt;/td&gt;
&lt;td&gt;判断当前文件对象，对应的文件路径是否存在，存在返回true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;public boolean isFile()&lt;/td&gt;
&lt;td&gt;判断当前文件对象指代的是否是文件，是文件返回true，反之。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;public boolean isDirectory()&lt;/td&gt;
&lt;td&gt;判断当前文件对象指代的是否是文件夹，是文件夹返回true，反之。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;public string getName()&lt;/td&gt;
&lt;td&gt;获取文件的名称((包含后缀)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;public long length()&lt;/td&gt;
&lt;td&gt;获取文件的大小，返回字节个数&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;public long lastModified()&lt;/td&gt;
&lt;td&gt;获取文件的最后修改时间。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;public string getPath()&lt;/td&gt;
&lt;td&gt;获取创建文件对象时，使用的路径&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;public string getAbsolutePath()&lt;/td&gt;
&lt;td&gt;获取绝对路径&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;FILE提供的创建文件，删除文件&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;public boolean preateNewFile()&lt;/td&gt;
&lt;td&gt;创建一个新文件（文件内容为空，创建成功返回true，反之。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;public boolean mkdir()&lt;/td&gt;
&lt;td&gt;用于创建文件夹，注意：只能创建一级文件夹&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;public boolean mkdirs()&lt;/td&gt;
&lt;td&gt;用于创建文件夹，注意：可以创建多级文件夹&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;public boolean delete()&lt;/td&gt;
&lt;td&gt;删除文件，或者空文件，注意：不能删除非空文件夹。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;注意：delete方法默认只能删除文件和空文件夹，删除后的文件不会进入回收站。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;FILE提供的遍历文件夹&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;public string[] list()&lt;/td&gt;
&lt;td&gt;获取当前目录下所有的”一级文件名称”到一个字符串数组中去返回。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;public File[] listFiles()&lt;/td&gt;
&lt;td&gt;获取当前目录下所有的”—级文件对象”到一个文件对象数组中去返口&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;使用listFiles方法时的注意事项：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当主调是文件，或者路径不存在时，返回null&lt;/li&gt;
&lt;li&gt;当主调是空文件夹时，返回一个长度为O的数组&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;当主调是一个有内容的文件夹时，将里面所有一级文件和文件夹的路径放在File数组中返回&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;当主调是一个文件夹，且里面有隐藏文件时，将里面所有文件和文件夹的路径放在File数组中返回，包含隐藏文件&lt;/li&gt;
&lt;li&gt;当主调是一个文件夹，但是没有权限访问该文件夹时，返回null&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>SSM08-REST风格</title><link>https://wang-nine.github.io/posts/10308/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10308/</guid><description>详细的描述了Spring中的REST开发风格</description><pubDate>Sat, 24 Jun 2023 14:00:00 GMT</pubDate><content:encoded>&lt;p&gt;详细的描述了Spring中的REST开发风格&lt;/p&gt;
&lt;h1&gt;REST简介&lt;/h1&gt;
&lt;p&gt;REST (Representational state Transfer)，表现形式状态转换&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;传统风格资源描述形式
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://localhost/user/getById?id=1&quot;&gt;http://localhost/user/getById?id=1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://localhost/user/saveUser&quot;&gt;http://localhost/user/saveUser&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;REST风格描述形式
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://localhost/user/1&quot;&gt;http://localhost/user/1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://localhost/user&quot;&gt;http://localhost/user&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;优点:
&lt;ul&gt;
&lt;li&gt;隐藏资源的访问行为，无法通过地址得知对资源是何种操作&lt;/li&gt;
&lt;li&gt;书写简化&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;按照REST风格访问资源时使用行为动作区分对资源进行了何种操作
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://localhost/users&quot;&gt;http://localhost/users&lt;/a&gt; 查询全部用户信息 GET(查询)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://localhost/users/1&quot;&gt;http://localhost/users/1&lt;/a&gt; 查询指定用户信息 GET(查询)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://localhost/users&quot;&gt;http://localhost/users&lt;/a&gt; 添加用户信息 POST(新增/保存)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://localhost/users&quot;&gt;http://localhost/users&lt;/a&gt; 修改用户信息 PUT(修改/更新)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://localhost/users/1&quot;&gt;http://localhost/users/1&lt;/a&gt; 删除用户信息 DELETE(删除)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;根据REST风格对资源进行访问称为 RESTful&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;注意事项&lt;br /&gt;
上述行为是约定方式，约定不是规范，可以打破，所以称REST风格，而不是REST规范&lt;br /&gt;
描述模块的名称通常使用复数，也就是加s的格式描述，表示此类资源，而非单个资源，例如: users、books、account..&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;样例&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;//设置当前请求方法为POST，表示REST风格中的添加操作
@RequestMapping(value = &quot;/users&quot;,method = RequestMethod.POST)
@ResponseBody
public String save(){
    System.out.println(&quot;user save...&quot;);
    return &quot;{&apos;module&apos;:&apos;user save&apos;}&quot;;
}

//设置当前请求方法为DELETE，表示REST风格中的删除操作
//@PathVariable注解用于设置路径变量（路径参数），要求路径上设置对应的占位符，并且占位符名称与方法形参名称相同
@RequestMapping(value = &quot;/users/{id}&quot;,method = RequestMethod.DELETE)
@ResponseBody
public String delete(@PathVariable Integer id){
    System.out.println(&quot;user delete...&quot; + id);
    return &quot;{&apos;module&apos;:&apos;user delete&apos;}&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;@RequestMapping&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;方法注解，SpringMVC控制器方法定义上方，设置当前控制器方法请求访问路径&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;属性
&lt;ul&gt;
&lt;li&gt;value (默认)：请求访问路径&lt;/li&gt;
&lt;li&gt;method：http请求动作，标准动作(GET/POST/PUT/DELETE)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;放在类上方时，即设置了公共路径&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;@RequestBody @RequestParam @Pathvariable&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;区别
&lt;ul&gt;
&lt;li&gt;@RequestParam用于接收url地址传参或表单传参&lt;/li&gt;
&lt;li&gt;@RequestBody用于接收json数据&lt;/li&gt;
&lt;li&gt;@Pathvariable用于接收路径参数，使用{参数名称}描述路径参数&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;应用
&lt;ul&gt;
&lt;li&gt;后期开发中，发送请求参数超过1个时，以json格式为主，@RequestBody应用较广&lt;/li&gt;
&lt;li&gt;如果发送非json格式数据，选用@RequestParam接收请求参数&lt;/li&gt;
&lt;li&gt;采用RESTful进行开发，当参数数量较少时，例如1个，可以采用@PathVariable接收请求路径变量，通常用于传递id值&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;@RestController&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;类注解&lt;/p&gt;
&lt;p&gt;@RestController = @ResponseBody + @Controller&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;@GetMapping @PostMapping @PutMapping @DeleteMapping&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;方法注解&lt;br /&gt;
基于SpringMVC的RESTful开发控制器方法定义上方&lt;br /&gt;
设置当前控制器方法请求访问路径与请求动作，每种对应一个请求动作，例如@GetMapping对应GET请求&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;注意直接访问页面会404，需要添加访问过滤，本来使用Tomcat负责，现在由SpringMVC拦截，需要放行&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
    //设置静态资源访问过滤，当前类需要设置为配置类，并被扫描加载
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        //当访问/pages/????时候，从/pages目录下查找内容
        registry.addResourceHandler(&quot;/pages/**&quot;).addResourceLocations(&quot;/pages/&quot;);
        registry.addResourceHandler(&quot;/js/**&quot;).addResourceLocations(&quot;/js/&quot;);
        registry.addResourceHandler(&quot;/css/**&quot;).addResourceLocations(&quot;/css/&quot;);
        registry.addResourceHandler(&quot;/plugins/**&quot;).addResourceLocations(&quot;/plugins/&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>SpringBoot基础原理</title><link>https://wang-nine.github.io/posts/20005/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/20005/</guid><description>优先级(低→高)</description><pubDate>Fri, 23 Jun 2023 16:00:00 GMT</pubDate><content:encoded>&lt;p&gt;优先级(低→高)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;application.yaml （忽略)&lt;/li&gt;
&lt;li&gt;application.yml&lt;/li&gt;
&lt;li&gt;application.properties&lt;/li&gt;
&lt;li&gt;java系统属性(-Dxxx=xxx)&lt;/li&gt;
&lt;li&gt;命令行参数(- -xxx=xxx)&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;获取Bean&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;默认情况下，Spring项目启动时，会把bean都创建好放在IOC容器中，如果想要主动获取这些bean，可以通过如下方式：
&lt;ul&gt;
&lt;li&gt;根据name获取bean：object getBean (String name)&lt;/li&gt;
&lt;li&gt;根据类型获取bean： T getBean(ClasscrequiredType)&lt;/li&gt;
&lt;li&gt;根据name获取bean(带类型转换): T getBean(String name，Class requiredType)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;上述所说的【Spring项目启动时，会把其中的bean都创建好】还会受到作用域及延迟初始化影响，这里主要针对于默认的单例非延迟加载的bean而言。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;Bean的作用域&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;作用域&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;singleton&lt;/td&gt;
&lt;td&gt;容器内同名称的bean只有一个实例(单例)（默认)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;prototype&lt;/td&gt;
&lt;td&gt;每次使用该 bean时会创建新的实例(非单例)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;request&lt;/td&gt;
&lt;td&gt;每个请求范围内会创建新的实例（web环境中，了解)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;session&lt;/td&gt;
&lt;td&gt;每个会话范围内会创建新的实例(web环境中，了解)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;application&lt;/td&gt;
&lt;td&gt;每个应用范围内会创建新的实例(web环境中，了解)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;注意事项&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;默认singleton的bean，在容器启动时被创建，可以使用@Lazy注解来延迟初始化(延迟到第一次使用时）&lt;/li&gt;
&lt;li&gt;prototype的bean，每一次使用该bean的时候都会创建一个新的实例。&lt;/li&gt;
&lt;li&gt;实际开发当中，绝大部分的Bean是单例的，也就是说绝大部分Bean不需要配置scope属性。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h1&gt;第三方Bean&lt;/h1&gt;
&lt;p&gt;==@Bean==&lt;br /&gt;
==如果要管理的bean对象来自于第三方〈不是自定义的)，是无法用@Component及衍生注解声明bean的，就需要用到@Bean注解。==&lt;/p&gt;
</content:encoded></item><item><title>Mybatis基础入门</title><link>https://wang-nine.github.io/posts/20004/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/20004/</guid><description>MyBatis是一款优秀的持久层框架，==用于简化JDBC的开发。==</description><pubDate>Thu, 22 Jun 2023 16:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Mybatis&lt;/h1&gt;
&lt;p&gt;MyBatis是一款优秀的持久层框架，==用于简化JDBC的开发。==&lt;/p&gt;
&lt;h2&gt;JDBC&lt;/h2&gt;
&lt;p&gt;JDBC：（Java DataBase Connectivity），就是使用ava语言操作关系型数据库的一套API。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;sun公司官方定义的一套操作所有关系型数据库的规范，即接口。&lt;/li&gt;
&lt;li&gt;各个数据库厂商去实现这套接口，提供数据库驱动jar包。&lt;/li&gt;
&lt;li&gt;我们可以使用这套接口（JDBC）编程，真正执行的代码是驱动jar包中的实现类。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;import java.sql.*;
import java.util.ArrayList;
import java.util.List;

@SpringBootTest //springboot整合单元测试的注解
class SpringbootMybatisQuickstartApplicationTests {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void testListUser(){
        List&amp;lt;User&amp;gt; userList = userMapper.list();
        userList.stream().forEach(user -&amp;gt; {
            System.out.println(user);
        });
    }

    @Test
    public void testJdbc() throws Exception {
        //1. 注册驱动
        Class.forName(&quot;com.mysql.cj.jdbc.Driver&quot;);

        //2. 获取连接对象
        String url = &quot;jdbc:mysql://localhost:3306/mybatis&quot;;
        String username = &quot;root&quot;;
        String password = &quot;1234&quot;;
        Connection connection = DriverManager.getConnection(url, username, password);

        //3. 获取执行SQL的对象Statement,执行SQL,返回结果
        String sql = &quot;select * from user&quot;;
        Statement statement = connection.createStatement();
        ResultSet resultSet = statement.executeQuery(sql);

        //4. 封装结果数据
        List&amp;lt;User&amp;gt; userList = new ArrayList&amp;lt;&amp;gt;();
        while (resultSet.next()){
            int id = resultSet.getInt(&quot;id&quot;);
            String name = resultSet.getString(&quot;name&quot;);
            short age = resultSet.getShort(&quot;age&quot;);
            short gender = resultSet.getShort(&quot;gender&quot;);
            String phone = resultSet.getString(&quot;phone&quot;);

            User user = new User(id,name,age,gender,phone);
            userList.add(user);
        }

        //5. 释放资源
        statement.close();
        connection.close();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;MyBatis使用流程&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;1.准备工作(创建springboot工程、数据库表user、实体类User)&lt;/li&gt;
&lt;li&gt;2.引入Mybatis的相关依赖，配置Mybatis&lt;/li&gt;
&lt;li&gt;3.编写SQL语句(注解/XML)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;数据库连接池&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;数据库连接池是个容器，负责分配、管理数据库连接(Connection)&lt;/li&gt;
&lt;li&gt;它允许应用程序重复使用一个现有的数据库连接，而不是再重新建立一个&lt;/li&gt;
&lt;li&gt;释放空闲时间超过最大空闲时间的连接，来避免因为没有释放连接而引起的数据库连接遗漏&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;优点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;资源重用&lt;/li&gt;
&lt;li&gt;提升系统响应速度&lt;/li&gt;
&lt;li&gt;避免数据库连接遗漏&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h2&gt;lombok&lt;/h2&gt;
&lt;p&gt;Lombok是一个实用的Java类库，能通过注解的形式自动生成构造器、getter/setter、equals、hashcode、toString等方法，并可以自动化生成日志变量，简化java开发、提高效率。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;注解&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;@Getter/ @Setter&lt;/td&gt;
&lt;td&gt;为所有的属性提供get/set方法&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;@ToString&lt;/td&gt;
&lt;td&gt;会给类自动生成易阅读的toString方法&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;@EqualsAndHashcode&lt;/td&gt;
&lt;td&gt;根据类所拥有的非静态字段自动重写equals方法和 hashCode方法&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;@Data&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;提供了更综合的生成代码功能(@Getter + @Setter +@ToString +@EqualsAndHashCode)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;@NoArgsConstructor&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;为实体类生成无参的构造器方法&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;@AllArgsConstructor&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;为实体类生成除了static修饰的字段之外带有各参数的构造器方法。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;第一步：准备工作&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;准备好数据库表 Emp （员工表）&lt;/li&gt;
&lt;li&gt;创建一个新的springboot工程，选择引入对应的起步依赖（mybatis、mysql驱动、lombok）&lt;/li&gt;
&lt;li&gt;application.properties中引入数据库连接信息&lt;/li&gt;
&lt;li&gt;创建对应的实体类Emp（实体类属性采用驼峰命名）&lt;/li&gt;
&lt;li&gt;准备Mapper接口EmpMapper（加上Mapper接口）&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;第二步：先开发简单的增删改查Mapper&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;使用@Delete @Insert @Select&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;注意SQL语句中的占位符 #{变量名}&lt;/p&gt;
&lt;p&gt;注意接口方法的返回值现在先可以自己定义，在后期会统一换成自己设定的Result类，确保返回给前端的数据解析方便&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;每次开发好一个接口方法时，进行测试，确保测试的覆盖率&lt;/li&gt;
&lt;li&gt;application.properties配置mybatis的日志，指定输出到控制台&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;#配置mybatis的日志，指定输出到控制台
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdoutImpl
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;==注意变量名与pojo中一致，表示获取实体类的属性==&lt;/p&gt;
&lt;h2&gt;预编译SQL&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;为什么java会使用预编译的SQL语句？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;性能更高&lt;/li&gt;
&lt;li&gt;更安全（防止SQL注入）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;java项目在执行SQL语句时的步骤&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;缓存
&lt;ul&gt;
&lt;li&gt;SQL语法解析检查&lt;/li&gt;
&lt;li&gt;优化SQL&lt;/li&gt;
&lt;li&gt;编译SQL&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;执行SQL&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;为什么性能更高呢？&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;例如：&lt;/p&gt;
&lt;p&gt;delete from emp where id = 1;&lt;/p&gt;
&lt;p&gt;delete from emp where id = 2;&lt;/p&gt;
&lt;p&gt;delete from emp where id = 3;&lt;/p&gt;
&lt;p&gt;上述三条不进行预编译会缓存编译三次&lt;/p&gt;
&lt;p&gt;预编译如下添加占位符：&lt;/p&gt;
&lt;p&gt;delete from emp where id = ?&lt;/p&gt;
&lt;p&gt;1 2 3&lt;/p&gt;
&lt;p&gt;缓存编译一次&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;为什么更安全呢？&lt;/p&gt;
&lt;p&gt;SQL注入是通过操作输入的数据来修改事先定义好的SQL语句，以达到执行代码对服务器进行攻击的方法&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;解决了SQL注入&lt;/p&gt;
&lt;p&gt;例如正常的账号 wang 密码 123456&lt;/p&gt;
&lt;p&gt;SQL语句：select conut(*) from emp where username = ‘wang’ and password = ‘123456’;&lt;/p&gt;
&lt;p&gt;SQL注入攻击：&lt;/p&gt;
&lt;p&gt;账号：whatever 密码 ‘ or ’1‘ = ’1&lt;/p&gt;
&lt;p&gt;SQL语句：select conut(*) from emp where username = ‘whatever’ and password = ‘==‘ or ’1‘ = ’1==’;&lt;/p&gt;
&lt;p&gt;上述语句必然成立，达到了攻击的效果&lt;/p&gt;
&lt;p&gt;预编译SQL：&lt;/p&gt;
&lt;p&gt;SQL语句：select count(*) from emp where username =? and password = ?&lt;/p&gt;
&lt;p&gt;参数传递，避免了SQL注入&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;==使用预编译SQL语句（#{}）==&lt;/li&gt;
&lt;li&gt;==使用拼接SQL语句（${}）==&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;如何拿到主键值？&lt;/h2&gt;
&lt;p&gt;正常的属性方法getId无法拿到主键值，需要使用注解&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;@Options(keyProperty=“id”, useGeneratedKeys=true)&lt;/p&gt;
&lt;p&gt;useGeneratedKeys=true代表我们需要拿到主键，keyProperty=‘Id’表示我们需要将主键封装的emp的Id中去&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;数据封装&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;实体类属性名和数据库表查询返回的字段名一致，mybatis会自动封装。&lt;/li&gt;
&lt;li&gt;实体类属性名和数据库表查询返回的字段名不一致，不能自动封装。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如何封装&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.为字段不同的字段起别名，就是SQL中的as起别名，将别名与实体类属性名一致即可&lt;/li&gt;
&lt;li&gt;2.通过@Results，@Result注解手动映射封装（不建议）&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;@Results({
	@Result(column = &quot;dept_id&quot;, property = &quot;deptId&quot; ),
	@Result(column = &quot;create_time&quot;, property = &quot;createTime&quot;)
    @Result(column = &quot;update_time&quot;, property = &quot;updateTime&quot;)
})
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;3.开启Mybatis驼峰命名映射开关（==建议，但是要确保开发时的命名规范==）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;application.properties配置mybatis的驼峰命名映射开关&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#开启mybatis的驼峰命名自动映射开关a_column ------&amp;gt; aCloumn
mybatis.configuration.map-underscore-to-camel-case=true
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;注意${}的使用时机&lt;/h2&gt;
&lt;h1&gt;{}不能出现在引号中，所以类似于SQL中的like ‘%#{name}%’ 这类模糊匹配需要改为&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;like ‘%${name}%’ ==性能低，存在SQL注入攻击问题==&lt;/li&gt;
&lt;li&gt;concat(‘%’, #{name}, ‘%’) ==性能高，更安全==&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;XML映射文件&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;规范&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;XML映射文件的名称与Mapper接口名称一致，并且将XML映射文件和Mapper接口放置在相同包下(同包同名)&lt;/li&gt;
&lt;li&gt;XML映射文件的namespace属性为Mapper接口全限定名一致。&lt;/li&gt;
&lt;li&gt;XML映射文件中sql语句的id与Mapper接口中的方法名一致，并保持返回类型一致。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/java-ssm/mybatis-mapping.svg&quot; alt=&quot;MyBatis 接口与 XML 语句及结果映射关系&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：Mapper 代理通过接口全限定名对应 XML 的 namespace，通过方法名对应语句 id。执行时绑定参数，再把查询结果封装为实体或集合；列名与属性名不一致时，需要别名、显式映射或开启驼峰映射，不能仅靠文件同名完成结果转换。&lt;/p&gt;
&lt;h2&gt;动态SQL&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;标签&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;\ \&lt;/td&gt;
&lt;td&gt;用于判断条件是否成立。使用test属性进行条件判断，如果条件为true，则拼接SQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;\\&lt;/td&gt;
&lt;td&gt;where 元素只会在子元素有内容的情况下才插入where子句。而且会自动去除子句的开头的AND或OR。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;\\&lt;/td&gt;
&lt;td&gt;动态地在行首插入SET关键字，并会删掉额外的逗号。(用在update语句中)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;\\&lt;/td&gt;
&lt;td&gt;collection:遍历的集合  &amp;lt;br&amp;gt;item:遍历出来的元素  &amp;lt;br&amp;gt;separator:分隔符  &amp;lt;br&amp;gt;open:遍历开始前拼接的SQL工片段  &amp;lt;br&amp;gt;close:遍历结束后拼接的SQL片段&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;\\&lt;/td&gt;
&lt;td&gt;抽取相同的SQL片段&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;\\&lt;/td&gt;
&lt;td&gt;引用SQL片段&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</content:encoded></item><item><title>VIM基础教程</title><link>https://wang-nine.github.io/posts/20002/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/20002/</guid><description>vim有三种基本的工作模式，分别为：命令模式、末行模式、编辑模式。关于这三种工作模式的介绍，请见下文。</description><pubDate>Tue, 20 Jun 2023 16:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1、vim 三种基本的工作模式&lt;/h2&gt;
&lt;p&gt;vim有三种基本的工作模式，分别为：命令模式、末行模式、编辑模式。关于这三种工作模式的介绍，请见下文。&lt;/p&gt;
&lt;h3&gt;1.1、命令模式&lt;/h3&gt;
&lt;p&gt;使用vim打开文件之后，首先进入命令模式，它是vim编辑器的入口模式。在命令模式下，通过使用命令对文件进行常规的编辑操作，例如：复制、粘贴、删除、定位、翻页等等。&lt;/p&gt;
&lt;h3&gt;1.2、末行模式&lt;/h3&gt;
&lt;p&gt;末行模式是vim编辑器的出口，要退出vim，必须要在末行模式下。在末行模式下，执行退出文件、保存并退出文件等操作。&lt;/p&gt;
&lt;h3&gt;1.3、编辑模式&lt;/h3&gt;
&lt;p&gt;正常的编辑文字模式。&lt;/p&gt;
&lt;h2&gt;2、三种模式的切换操作&lt;/h2&gt;
&lt;p&gt;（1）vim打开一个文件进入的就是命令模式，另外两种模式需要回到命令模式才能进入彼此。&lt;/p&gt;
&lt;p&gt;（2）在命令模式中按下&lt;code&gt;i，I，o，O，a，A，r，R&lt;/code&gt;任何一个字母后进入编辑模式，进入编辑模式后，下方会出现&lt;code&gt;INSERT&lt;/code&gt;或者&lt;code&gt;REPLACE&lt;/code&gt;字样，按&lt;code&gt;Esc&lt;/code&gt;键退出编辑模式，回到命令模式。&lt;/p&gt;
&lt;p&gt;（3）在命令模式中按下&lt;code&gt;:，/，?&lt;/code&gt;中任意一个即可进入末行模式，末行模式可以对文档或者vim环境进行操作，例如查找、读取、存盘、替换、离开、显示行号等。&lt;/p&gt;
&lt;h2&gt;3、各种模式切换&lt;/h2&gt;
&lt;h3&gt;普通模式 - - NORMAL - - ———&amp;gt; 插入模式 - - INSERT - -&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;insert
&lt;ul&gt;
&lt;li&gt;i 在光标前插入&lt;/li&gt;
&lt;li&gt;I 在行首插入&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;open a new line
&lt;ul&gt;
&lt;li&gt;o 在下一行插入&lt;/li&gt;
&lt;li&gt;O 在上一行插入&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;append
&lt;ul&gt;
&lt;li&gt;a 在光标后插入&lt;/li&gt;
&lt;li&gt;A 在行尾插入&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;普通模式 - - NORMAL - - &amp;lt;——— 插入模式 - - INSERT - -&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;ESC&lt;/li&gt;
&lt;li&gt;jj / CapsLock&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;普通模式 - - NORMAL - - ———&amp;gt; 可视模式 - - VISUAL - -&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;v&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;普通模式 - - NORMAL - - &amp;lt;——— 可视模式 - - VISUAL - -&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;v&lt;/li&gt;
&lt;li&gt;Esc&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;普通模式 - - NORMAL - - ———&amp;gt; 命令模式&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;普通模式 - - NORMAL - - &amp;lt;——— 命令模式&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Esc&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;总结&lt;/h3&gt;
&lt;p&gt;先区分输入内容与执行命令：普通模式下按键用于移动和编辑，不会直接写入文本；按 &lt;code&gt;i&lt;/code&gt; 进入插入模式后才开始输入正文，按 &lt;code&gt;Esc&lt;/code&gt; 回到普通模式。若不确定当前状态，先按 &lt;code&gt;Esc&lt;/code&gt; 再执行下一步，比连续尝试不同按键更容易判断行为。后续的移动、删除与复制示例，除非另有说明，都以普通模式为起点。&lt;/p&gt;
&lt;p&gt;进入插入模式要先选好位置：&lt;code&gt;i&lt;/code&gt; 在光标前插入，&lt;code&gt;a&lt;/code&gt; 在光标后追加；&lt;code&gt;I&lt;/code&gt; 移到当前行第一个非空白字符前插入，&lt;code&gt;A&lt;/code&gt; 移到行尾追加。练习时在同一行分别尝试这些入口，输入少量文字后按 &lt;code&gt;Esc&lt;/code&gt;，观察光标位置与文本变化。这里的大写和小写是不同命令，不能把 &lt;code&gt;I&lt;/code&gt; 简单当作 &lt;code&gt;i&lt;/code&gt; 的另一种写法。&lt;/p&gt;
&lt;p&gt;需要新增一行时，在普通模式按 &lt;code&gt;o&lt;/code&gt; 会在当前行下方打开新行并进入插入模式，按 &lt;code&gt;O&lt;/code&gt; 则在上方打开新行。它们不同于只移动光标的命令，也不同于在原行内部插入字符；如果只是想定位到邻行，应使用移动命令。输入完成后按 &lt;code&gt;Esc&lt;/code&gt; 返回普通模式，下一次按键才会继续作为编辑指令解释。&lt;/p&gt;
&lt;p&gt;可视模式用于先选范围再执行操作：普通模式按 &lt;code&gt;v&lt;/code&gt; 开始按字符选择，随后通过移动命令扩大或缩小选区，再决定复制、删除或修改。放弃当前选择时按 &lt;code&gt;Esc&lt;/code&gt; 返回普通模式，不必执行破坏性操作来退出。要留意状态栏显示的模式，避免把选择阶段的移动按键误认为已经在插入正文。&lt;/p&gt;
&lt;p&gt;普通模式按 &lt;code&gt;:&lt;/code&gt; 进入命令行后，可以输入保存、退出或设置命令，按回车执行，按 &lt;code&gt;Esc&lt;/code&gt; 取消本次命令输入。&lt;code&gt;jj&lt;/code&gt; 或把 &lt;code&gt;CapsLock&lt;/code&gt; 当作退出键并不是 Vim 原生的统一行为，它们需要相应映射或系统配置；迁移到新环境时先使用 &lt;code&gt;Esc&lt;/code&gt;。单字符替换 &lt;code&gt;r&lt;/code&gt; 也不同于持续输入的插入模式，不应把所有编辑入口混为一类。&lt;/p&gt;
&lt;h2&gt;4、光标移动&lt;/h2&gt;
&lt;p&gt;光标移动先从 &lt;code&gt;h&lt;/code&gt;、&lt;code&gt;j&lt;/code&gt;、&lt;code&gt;k&lt;/code&gt;、&lt;code&gt;l&lt;/code&gt; 开始：分别向左、下、上、右移动。可以在命令前加次数，例如 &lt;code&gt;3j&lt;/code&gt; 向下移动三行，这里的数字属于移动次数，不会写入正文。移动只改变位置，不会删改文本；在普通模式熟悉方向后，再把相同的移动范围组合进删除或复制命令，能更清楚地判断操作影响。&lt;/p&gt;
&lt;p&gt;按单词移动时，&lt;code&gt;w&lt;/code&gt; 去下一个单词开头，&lt;code&gt;b&lt;/code&gt; 返回当前或前一个单词开头，&lt;code&gt;e&lt;/code&gt; 到当前或下一个单词结尾，&lt;code&gt;ge&lt;/code&gt; 向后寻找前一个单词结尾。大写的 &lt;code&gt;W&lt;/code&gt;、&lt;code&gt;B&lt;/code&gt;、&lt;code&gt;E&lt;/code&gt; 以空白分隔的较大文本块为单位，对包含标点的代码行尤其有区别。练习时把光标放在单词中间再比较各命令，注意起始位置会影响实际落点。&lt;/p&gt;
&lt;p&gt;解释：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;w : word&lt;/li&gt;
&lt;li&gt;b : begin&lt;/li&gt;
&lt;li&gt;e : end&lt;/li&gt;
&lt;li&gt;ge : 不太常用&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;行内与文件级定位分开记忆：&lt;code&gt;0&lt;/code&gt; 到行的第一个字符，&lt;code&gt;^&lt;/code&gt; 到第一个非空白字符，&lt;code&gt;$&lt;/code&gt; 到行尾；&lt;code&gt;gg&lt;/code&gt; 到文件首行，&lt;code&gt;G&lt;/code&gt; 到末行，&lt;code&gt;nG&lt;/code&gt; 或 &lt;code&gt;ngg&lt;/code&gt; 到指定行号。处理有缩进的代码时，&lt;code&gt;0&lt;/code&gt; 与 &lt;code&gt;^&lt;/code&gt; 的落点不同，前者包含缩进位置。先准确到行，再用单词或字符查找细调，通常比重复逐字符移动更容易控制。&lt;/p&gt;
&lt;p&gt;当前行内查找字符可用 &lt;code&gt;f{字符}&lt;/code&gt; 向前落在目标字符上，&lt;code&gt;t{字符}&lt;/code&gt; 向前停在目标字符之前；大写 &lt;code&gt;F&lt;/code&gt; 与 &lt;code&gt;T&lt;/code&gt; 反向查找。按 &lt;code&gt;;&lt;/code&gt; 沿原方向重复，按 &lt;code&gt;,&lt;/code&gt; 反向重复，不需要重新输入目标字符。跨越更大文本结构时，可以使用 &lt;code&gt;(&lt;/code&gt;、&lt;code&gt;)&lt;/code&gt; 按句子移动，或 &lt;code&gt;{&lt;/code&gt;、&lt;code&gt;}&lt;/code&gt; 按段落边界移动，先分清搜索范围再选择命令。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;字符&lt;/th&gt;
&lt;th&gt;单词&lt;/th&gt;
&lt;th&gt;行&lt;/th&gt;
&lt;th&gt;跳转&lt;/th&gt;
&lt;th&gt;句和段&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;h 左&lt;/td&gt;
&lt;td&gt;w下一个单词开头&lt;/td&gt;
&lt;td&gt;0 行首&lt;/td&gt;
&lt;td&gt;f char 跳到char位置&lt;/td&gt;
&lt;td&gt;( 到句首&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;j 下&lt;/td&gt;
&lt;td&gt;b本单词或上一个单词开头&lt;/td&gt;
&lt;td&gt;^ 行首非空字符&lt;/td&gt;
&lt;td&gt;F char 反向&lt;/td&gt;
&lt;td&gt;) 到句尾&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;k 上&lt;/td&gt;
&lt;td&gt;e本单词或下一个单词结尾&lt;/td&gt;
&lt;td&gt;$ 行尾&lt;/td&gt;
&lt;td&gt;t char char前一位置&lt;/td&gt;
&lt;td&gt;{ 到段首&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;l 右&lt;/td&gt;
&lt;td&gt;ge上一个单词结尾&lt;/td&gt;
&lt;td&gt;gg 首行&lt;/td&gt;
&lt;td&gt;T char char后一位置&lt;/td&gt;
&lt;td&gt;} 到段尾&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;W长单词 E长单词 B长单词&lt;/td&gt;
&lt;td&gt;G 尾行&lt;/td&gt;
&lt;td&gt;; 重复上次的跳转&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;ngg 第n行&lt;/td&gt;
&lt;td&gt;, 反向查找上次的查找&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;5、动作 motion&lt;/h2&gt;
&lt;p&gt;移动命令描述从当前位置到目标位置的范围，文本对象则描述当前位置所属的一块结构。使用文本对象时通常先输入操作符或进入可视模式，再选择范围，例如 &lt;code&gt;iw&lt;/code&gt; 表示单词内部，&lt;code&gt;aw&lt;/code&gt; 包含单词及相关空白。把“要做什么”和“作用在哪一块”分开思考，能避免先手动选很长一段文本，再猜测是否漏选边界。&lt;/p&gt;
&lt;p&gt;文本对象的 &lt;code&gt;i&lt;/code&gt; 表示内部范围，&lt;code&gt;a&lt;/code&gt; 通常连同外围分隔或相关空白一起选取。&lt;code&gt;it&lt;/code&gt; 面向标签内部，&lt;code&gt;is&lt;/code&gt; 面向句子内部，&lt;code&gt;ip&lt;/code&gt; 面向段落内部；它们是操作范围，不是普通模式下任意单独敲击就会完成编辑的命令。可以先用 &lt;code&gt;v&lt;/code&gt; 配合对象检查选区，再决定执行复制或修改；不同对象的边界规则不同，遇到嵌套结构时尤其要先确认范围。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;it : inner tag&lt;/li&gt;
&lt;li&gt;is : inner sentence&lt;/li&gt;
&lt;li&gt;ip : inner paragraph&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;6、操作符&lt;/h2&gt;
&lt;p&gt;操作符按“操作 + 范围”组合：&lt;code&gt;d&lt;/code&gt; 删除指定范围，&lt;code&gt;c&lt;/code&gt; 删除后进入插入模式，&lt;code&gt;y&lt;/code&gt; 复制范围；例如 &lt;code&gt;yy&lt;/code&gt; 复制当前行，&lt;code&gt;dd&lt;/code&gt; 删除当前行。&lt;code&gt;p&lt;/code&gt; 是把寄存器内容放到当前位置之后的命令，不需要再跟一个移动范围。表中的 &lt;code&gt;b&lt;/code&gt;、&lt;code&gt;B&lt;/code&gt; 可用于特定括号文本对象，而不是通用操作符；&lt;code&gt;e&lt;/code&gt; 在原生 Vim 中是移到单词结尾，不能当作默认全选命令。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;操作符&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;d(delete)&lt;/td&gt;
&lt;td&gt;删除&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;c(change)&lt;/td&gt;
&lt;td&gt;修改（删除并且进入插入模式）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;y(yank)&lt;/td&gt;
&lt;td&gt;复制&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v(visual)&lt;/td&gt;
&lt;td&gt;选中并且进入 VISUAL 模式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;p(paste)&lt;/td&gt;
&lt;td&gt;粘贴&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;b(bracket)&lt;/td&gt;
&lt;td&gt;小括号的简写&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B(Bracket)&lt;/td&gt;
&lt;td&gt;大括号的简写&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;e(entire)&lt;/td&gt;
&lt;td&gt;全选&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;7、大小写&lt;/h2&gt;
&lt;p&gt;大小写转换使用明确的操作范围：&lt;code&gt;gU&lt;/code&gt; 把范围转为大写，&lt;code&gt;gu&lt;/code&gt; 转为小写，&lt;code&gt;g~&lt;/code&gt; 切换大小写；例如 &lt;code&gt;gUw&lt;/code&gt; 对当前移动范围执行大写转换，&lt;code&gt;guu&lt;/code&gt; 将当前行转为小写。可视模式中先选定文本，再按 &lt;code&gt;U&lt;/code&gt; 或 &lt;code&gt;u&lt;/code&gt; 也能转换选区。普通模式单独的 &lt;code&gt;u&lt;/code&gt; 是撤销，不是转小写；也不要把所有 &lt;code&gt;g&lt;/code&gt; 前缀命令都理解成跳转，应按完整命令记忆。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;‘g’ means goto&lt;/li&gt;
&lt;li&gt;‘U’ means Uppercase&lt;/li&gt;
&lt;li&gt;‘u’ 与上一个相反&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Tips&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;gd : goto definition 跳转到定义&lt;/li&gt;
&lt;li&gt;ctrl + o : 跳回&lt;/li&gt;
&lt;li&gt;gh : goto (mouse) hover 鼠标悬浮&lt;/li&gt;
&lt;li&gt;gt : goto tab 跳转下一个标签页&lt;/li&gt;
&lt;li&gt;gT : 与上一个相反 跳转上一个标签页&lt;/li&gt;
&lt;li&gt;ngt / ngT : 跳转到第n个标签页&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;VSCode中的Vim&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;vscode中默认为space&lt;/li&gt;
&lt;li&gt;S{Charactor} : 寻找某个字符&lt;/li&gt;
&lt;li&gt;xxx : 详见配置&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Motion Command&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;leader&amp;gt;&amp;lt;leader&amp;gt; s &amp;lt;char&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Search character&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;leader&amp;gt;&amp;lt;leader&amp;gt; f &amp;lt;char&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Find character forwards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;leader&amp;gt;&amp;lt;leader&amp;gt; F &amp;lt;char&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Find character backwards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;leader&amp;gt;&amp;lt;leader&amp;gt; t &amp;lt;char&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Til character forwards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;leader&amp;gt;&amp;lt;leader&amp;gt; T &amp;lt;char&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Til character backwards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;leader&amp;gt;&amp;lt;leader&amp;gt; w&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Start of word forwards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;leader&amp;gt;&amp;lt;leader&amp;gt; b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Start of word backwards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;leader&amp;gt;&amp;lt;leader&amp;gt; l&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Matches beginning &amp;amp; ending of word, camelCase, after &lt;code&gt;_&lt;/code&gt;, and after &lt;code&gt;#&lt;/code&gt; forwards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;leader&amp;gt;&amp;lt;leader&amp;gt; h&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Matches beginning &amp;amp; ending of word, camelCase, after &lt;code&gt;_&lt;/code&gt;, and after &lt;code&gt;#&lt;/code&gt; backwards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;leader&amp;gt;&amp;lt;leader&amp;gt; e&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;End of word forwards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;leader&amp;gt;&amp;lt;leader&amp;gt; ge&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;End of word backwards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;leader&amp;gt;&amp;lt;leader&amp;gt; j&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Start of line forwards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;leader&amp;gt;&amp;lt;leader&amp;gt; k&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Start of line backwards&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;leader&amp;gt;&amp;lt;leader&amp;gt; / &amp;lt;char&amp;gt;... &amp;lt;CR&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Search n-character&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;leader&amp;gt;&amp;lt;leader&amp;gt;&amp;lt;leader&amp;gt; bdt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Til character&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;leader&amp;gt;&amp;lt;leader&amp;gt;&amp;lt;leader&amp;gt; bdw&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Start of word&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;leader&amp;gt;&amp;lt;leader&amp;gt;&amp;lt;leader&amp;gt; bde&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;End of word&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;leader&amp;gt;&amp;lt;leader&amp;gt;&amp;lt;leader&amp;gt; bdjk&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Start of line&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;leader&amp;gt;&amp;lt;leader&amp;gt;&amp;lt;leader&amp;gt; j&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;JumpToAnywhere motion; default behavior matches beginning &amp;amp; ending of word, camelCase, after &lt;code&gt;_&lt;/code&gt; and after &lt;code&gt;#&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;leader&amp;gt;&amp;lt;leader&amp;gt; (2s|2f|2F|2t|2T) &amp;lt;char&amp;gt;&amp;lt;char&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;leader&amp;gt;&amp;lt;leader&amp;gt;&amp;lt;leader&amp;gt; bd2t &amp;lt;char&amp;gt;char&amp;gt;&lt;/code&gt; are also available. The difference is character count required for search. For example, &lt;code&gt;&amp;lt;leader&amp;gt;&amp;lt;leader&amp;gt; 2s &amp;lt;char&amp;gt;&amp;lt;char&amp;gt;&lt;/code&gt; requires two characters, and search by two characters. This mapping is not a standard mapping, so it is recommended to use your custom mapping.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;surround&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Surround Command&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;y s &amp;lt;motion&amp;gt; &amp;lt;desired&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Add &lt;code&gt;desired&lt;/code&gt; surround around text defined by &lt;code&gt;&amp;lt;motion&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;d s &amp;lt;existing&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Delete &lt;code&gt;existing&lt;/code&gt; surround&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;c s &amp;lt;existing&amp;gt; &amp;lt;desired&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Change &lt;code&gt;existing&lt;/code&gt; surround to &lt;code&gt;desired&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;S &amp;lt;desired&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Surround when in visual modes (surrounds full selection)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Some examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&quot;test&quot;&lt;/code&gt; with cursor inside quotes type &lt;code&gt;cs&quot;&apos;&lt;/code&gt; to end up with &lt;code&gt;&apos;test&apos;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&quot;test&quot;&lt;/code&gt; with cursor inside quotes type &lt;code&gt;ds&quot;&lt;/code&gt; to end up with &lt;code&gt;test&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&quot;test&quot;&lt;/code&gt; with cursor inside quotes type &lt;code&gt;cs&quot;t&lt;/code&gt; and enter &lt;code&gt;123&amp;gt;&lt;/code&gt; to end up with &lt;code&gt;&amp;lt;123&amp;gt;test&amp;lt;/123&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;多光标功能&lt;/h2&gt;
&lt;h3&gt;🖱️ Multi-Cursor Mode&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;:warning: Multi-Cursor mode is experimental. Please report issues in our &lt;a href=&quot;https://github.com/VSCodeVim/Vim/issues/824&quot;&gt;feedback thread.&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Enter multi-cursor mode by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;On OSX, &lt;code&gt;cmd-d&lt;/code&gt;. On Windows, &lt;code&gt;ctrl-d&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gb&lt;/code&gt;, a new shortcut we added which is equivalent to &lt;code&gt;cmd-d&lt;/code&gt; (OSX) or &lt;code&gt;ctrl-d&lt;/code&gt; (Windows). It adds another cursor at the next word that matches the word the cursor is currently on.&lt;/li&gt;
&lt;li&gt;Running “Add Cursor Above/Below” or the shortcut on any platform.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once you have multiple cursors, you should be able to use Vim commands as you see fit. Most should work; some are unsupported (ref &lt;a href=&quot;https://github.com/VSCodeVim/Vim/pull/587&quot;&gt;PR#587&lt;/a&gt;).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Each cursor has its own clipboard.&lt;/li&gt;
&lt;li&gt;Pressing Escape in Multi-Cursor Visual Mode will bring you to Multi-Cursor Normal mode. Pressing it again will return you to Normal mode.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;vscode中可以使用 cmd + d 批量修改&lt;/p&gt;
&lt;p&gt;vim中可以使用 gb 批量膝盖&lt;/p&gt;
&lt;h2&gt;将光标定位到终端&lt;/h2&gt;
&lt;p&gt;在settings.json添加如下快捷键&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//由editor 转到 terminal
{
    &quot;before&quot;: [&quot;leader&quot;, &quot;t&quot;],
    &quot;commands&quot;: [&quot;workbench.action.terminal.focus&quot;]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;space + t 即可跳转到终端中&lt;/p&gt;
&lt;h2&gt;跳转Tab&lt;/h2&gt;
&lt;p&gt;进入命令模式 :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;输入 tabn : tab next 即进入下一个标签页&lt;/li&gt;
&lt;li&gt;输入 tabp : tab prev 即进入下一个标签页&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>MySQL11-InnoDB引擎02-事务原理</title><link>https://wang-nine.github.io/posts/10414/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10414/</guid><description>详细的描述了MySQL中的InnoDB引擎的事务原理。</description><pubDate>Sat, 10 Jun 2023 16:00:00 GMT</pubDate><content:encoded>&lt;p&gt;详细的描述了MySQL中的InnoDB引擎的事务原理。&lt;/p&gt;
&lt;h1&gt;事务回顾&lt;/h1&gt;
&lt;h2&gt;事务&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;事务是一组操作的集合，它是一个不可分割的工作单位，事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求，即这些操作要么同时成功，要么同时失败。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;特性&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;原子性(Atomicity):事务是不可分割的最小操作单元，要么全部成功，要么全部失败。&lt;/li&gt;
&lt;li&gt;一致性(Consistency) :事务完成时，必须使所有的数据都保持一致状态。&lt;/li&gt;
&lt;li&gt;隔离性(lsolation)∶数据库系统提供的隔离机制，保证事务在不受外部并发操作影响的独立环境下运行。&lt;/li&gt;
&lt;li&gt;持久性(Durability):事务一旦提交或回滚，它对数据库中的数据的改变就是永久的。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;事务原理&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/mysql/transaction-mechanisms.svg&quot; alt=&quot;事务中 undo、redo、WAL、锁与 MVCC 的机制分工&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：撤销信息支持失败时回滚，重做日志配合先写日志原则支持崩溃恢复，锁与多版本机制按隔离级别协调并发访问。数据一致性还依赖约束及正确的业务事务逻辑，并非重做日志单独实现。提交的持久性与刷盘策略有关，也不意味着每个修改过的数据页都已立即写回磁盘。&lt;/p&gt;
&lt;h2&gt;redolog(一致性)&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;重做日志，记录的是事务提交时数据页的物理修改，是用来实现事务的持久性。&lt;br /&gt;
该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log file) ,前者是在内存中，后者在磁盘中。当事务提交之后会把所有修改信息都存到该日志文件中，用于在刷新脏页到磁盘,发生错误时,进行数据恢复使用。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;undolog(原子性)&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;回滚日志，用于记录数据被修改前的信息，作用包含两个:提供回滚和MVCC(多版本并发控制)。&lt;br /&gt;
undo log和redo log记录物理日志不一样，它是逻辑日志。可以认为当delete一条记录时,undo log中会记录一条对应的insert记录，反之亦然，当update一条记录时，它记录一条对应相反的update记录。当执行rollback时，就可以从undo log中的逻辑记录读取到相应的内容并进行回滚。&lt;/p&gt;
&lt;p&gt;Undo log销毁: undo log在事务执行时产生，事务提交时，并不会立即删除undo log，因为这些日志可能还用于MVCC。&lt;/p&gt;
&lt;p&gt;Undo log存储: undo log采用段的方式进行管理和记录，存放在前面介绍的 rollback segment回滚段中，内部包含1024个undo log segment。&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>MySQL10-锁</title><link>https://wang-nine.github.io/posts/10412/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10412/</guid><description>详细的描述了MySQL中的锁。</description><pubDate>Wed, 07 Jun 2023 10:00:00 GMT</pubDate><content:encoded>&lt;p&gt;详细的描述了MySQL中的锁。&lt;/p&gt;
&lt;h1&gt;概述&lt;/h1&gt;
&lt;p&gt;锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中，除传统的计算资源(CPU、RAM、I/O)的争用以外，数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题，锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说，锁对数据库而言显得尤其重要，也更加复杂。&lt;/p&gt;
&lt;h1&gt;全局锁&lt;/h1&gt;
&lt;p&gt;全局锁就是对整个数据库实例加锁，加锁后整个实例就处于只读状态，后续的DML的写语句，DDL语句，已经更新操作的事务提交语句都将被阻塞。&lt;br /&gt;
==其典型的使用场景是做全库的逻辑备份，对所有的表进行锁定，从而获取一致性视图，保证数据的完整性==&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/mysql/global-read-lock.svg&quot; alt=&quot;全局读锁持有期间的备份读取、写入等待及解锁顺序&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：持锁连接执行 &lt;code&gt;FLUSH TABLES WITH READ LOCK&lt;/code&gt; 后，备份可以读取稳定的数据，而其他连接的相应写入和结构变更需要等待。备份结束应由持锁连接执行 &lt;code&gt;UNLOCK TABLES&lt;/code&gt;。这是一条操作关系图，不表示实际等待时长；纯 InnoDB 的一致性事务备份是另一种方案。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flush tables with read lock; -- 加全局锁
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;mysqldump [-h192.168.xxx.xxx] -uroot -p123456 databasename &amp;gt; D:/xxx.sql  //数据备份 访问远程加 -h
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;unlock table; -- 释放锁
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;==数据库中加全局锁，是一个比较重的操作，存在以下问题:==&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;如果在主库上备份，那么在备份期间都不能执行更新，业务基本上就得停摆。&lt;/li&gt;
&lt;li&gt;如果在从库上备份，那么在备份期间从库不能执行主库同步过来的二进制日志（binlog)，会导致主从延迟。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在InnoDB引擎中，我们可以在备份时加上参数—single-transaction 参数来完成不加锁的一致性数据备份。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mysqldump --single-transaction -uroot-p123456 Wang &amp;gt; Wang.sql
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;表级锁&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;表级锁，每次操作锁住整张表。锁定粒度大，发生锁冲突的概率最高，并发度最低。应用在MyISAM、InnoDB、BDB等存储引擎中。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于表级锁，主要分为以下三类：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;表锁&lt;/li&gt;
&lt;li&gt;元数据锁（meta datallock，MDL)&lt;/li&gt;
&lt;li&gt;意向锁&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;表锁&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;表共享读锁（read lock）&lt;/li&gt;
&lt;li&gt;表独占写锁（wirte lock）&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;语法&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;加锁&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;lock tables 表名... read/write
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;释放锁&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;unlock tables / 客户端断开连接
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;读锁与写锁的特点&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/mysql/table-lock-compatibility.svg&quot; alt=&quot;显式表读锁与表写锁对其他连接读写的兼容情况&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：针对同一张表，某连接持有显式表读锁时，其他连接仍可读，但写入需要等待；持有显式表写锁时，其他连接的读写都需要等待。此处讨论 &lt;code&gt;LOCK TABLES&lt;/code&gt;，不是把普通 InnoDB 快照读当作共享行锁；图下方另列索引记录上共享锁与排他锁的兼容关系。&lt;/p&gt;
&lt;p&gt;==读锁不会阻塞其他客户端的读，但是会阻塞写。写锁既会阻塞其他客户端的读，又会阻塞其他客户端的写。==&lt;/p&gt;
&lt;h2&gt;元数据锁(meta data lock, MDL)&lt;/h2&gt;
&lt;p&gt;==MDL加锁过程是系统自动控制，无需显式使用，在访问一张表的时候会自动加上。MDL锁主要作用是维护表元数据的数据一致性，在表上有活动事务的时候，不可以对元数据进行写入操作。主要作用是避免DML与DDL冲突，保证读写的正确性。==&lt;/p&gt;
&lt;p&gt;在MySQL5.5中引入了MDL，当对一张表进行增删改查的时候，加MDL读锁(共享);当对表结构进行变更操作的时候，加MDL写锁(排他)。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;对于SQL&lt;/th&gt;
&lt;th&gt;锁类型&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;lock tables xxx read / write&lt;/td&gt;
&lt;td&gt;SHARED_READ_ONLY / SHARED_NO_READ_WRITE&lt;/td&gt;
&lt;td&gt;/&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;select . select … lock in share mode&lt;/td&gt;
&lt;td&gt;SHARE_READ&lt;/td&gt;
&lt;td&gt;与SHARED_READ、SHARED_WRITE兼容，与EXCLUSIVE互斥&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;insert . update、delete、select … for update&lt;/td&gt;
&lt;td&gt;SHARED_WRITE&lt;/td&gt;
&lt;td&gt;与SHARED_READ、SHARED_WRITE兼容,与EXCLUSIVE互斥&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;alter table …&lt;/td&gt;
&lt;td&gt;EXCLUSIVE&lt;/td&gt;
&lt;td&gt;与其他的MDL都互斥&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;查看元数据锁&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;select object type,object schema,object_name,lock_type,lock_ duration from performance schema.metadata loacks;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;意向锁&lt;/h2&gt;
&lt;p&gt;为了避免 DML 在执行时，加的行锁与表锁的冲突，在InnoDB中引入了意向锁，使得表锁不用检查每行数据是否加锁，使用意向锁来减少表锁的检查。&lt;/p&gt;
&lt;h3&gt;意向共享锁(IS)&lt;/h3&gt;
&lt;p&gt;==电语句select … lock in share mode添加。==&lt;/p&gt;
&lt;p&gt;和与表锁共享锁(read)兼容，与表锁排它锁(write）互斥。&lt;/p&gt;
&lt;h3&gt;意向排他锁(IX)︰&lt;/h3&gt;
&lt;p&gt;==由insert、update、delete、select … for update添加。==&lt;/p&gt;
&lt;p&gt;与表锁共享锁(read)及排它锁(write)都互斥。意向锁之间不会互斥。&lt;/p&gt;
&lt;h3&gt;查看意向锁和行锁加锁情况&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;select object schema,object_name,index_name,lock_type.lock mode,Jock_data from performance schema.data_locks;
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;行级锁&lt;/h1&gt;
&lt;p&gt;行级锁，每次操作锁住对应的行数据，锁定粒度最小，发生锁冲突的概率最低，并发度最高，应用在InnoDB引擎中。&lt;/p&gt;
&lt;p&gt;InnoDB的数据是基于索引组织的，行锁是通过对索引上的索引项加锁来实现的，而不是对记录加的锁。对于行级锁，主要分为以下三类:&lt;/p&gt;
&lt;h2&gt;行锁(Record Lock)&lt;/h2&gt;
&lt;p&gt;锁定单个行记录的锁，防止其他事务对此行进行update和delete。在RC、RR隔离级别下都支持。&lt;/p&gt;
&lt;p&gt;nnoDB实现了以下两种类型的行锁&lt;/p&gt;
&lt;h3&gt;共享锁(S)&lt;/h3&gt;
&lt;p&gt;允许一个事务去读一行，阻止其他事务获得相同数据集的排它锁。&lt;/p&gt;
&lt;h3&gt;排他锁(X)&lt;/h3&gt;
&lt;p&gt;允许获取排他锁的事务更新数据，阻止其他事务获得相同数据集的共享锁和排他锁。&lt;/p&gt;
&lt;h3&gt;兼容情况&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;锁类型&lt;/th&gt;
&lt;th&gt;S(共享锁)&lt;/th&gt;
&lt;th&gt;X(排它锁)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;S(共享锁)&lt;/td&gt;
&lt;td&gt;兼容&lt;/td&gt;
&lt;td&gt;冲突&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;X(排它锁)&lt;/td&gt;
&lt;td&gt;冲突&lt;/td&gt;
&lt;td&gt;冲突&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;加锁情况&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;SQL&lt;/th&gt;
&lt;th&gt;行锁类型&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;INSERT …&lt;/td&gt;
&lt;td&gt;排他锁&lt;/td&gt;
&lt;td&gt;自动加锁&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UPDATE …&lt;/td&gt;
&lt;td&gt;排他锁&lt;/td&gt;
&lt;td&gt;自动加锁&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DELETE …&lt;/td&gt;
&lt;td&gt;排他锁&lt;/td&gt;
&lt;td&gt;自动加锁&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SELECT(正常)&lt;/td&gt;
&lt;td&gt;不加任何锁&lt;/td&gt;
&lt;td&gt;/&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SELECT … LOCK IN SHARE MODE&lt;/td&gt;
&lt;td&gt;共享锁&lt;/td&gt;
&lt;td&gt;需要手动在SELECT之后加LOCK IN SHARE MODE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SELECT … FOR UPDATE&lt;/td&gt;
&lt;td&gt;排他锁&lt;/td&gt;
&lt;td&gt;需要手动在SELECT之后加FOR UPDATE&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;行锁的注意事项&lt;/h3&gt;
&lt;p&gt;默认情况下，InnoDB在REPEATABLE READ事务隔离级别运行，InnoDB使用next-key锁进行搜索和索引扫描，以防止幻读。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;针对唯一索引进行检索时，对已存在的记录进行等值匹配时，将会自动优化为行锁。&lt;/li&gt;
&lt;li&gt;InnoDB的行锁是针对于索引加的锁，不通过索引条件检索数据，那么InnoDB将对表中的所有记录加锁，此时就会升级为表锁。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;查看意向锁和行锁加锁情况&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;select object schema,object_name,index_name,lock_type.lock mode,Jock_data from performance schema.data_locks;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;间隙锁(Gap Lock)与临键锁(Next-Key Lock)&lt;/h2&gt;
&lt;p&gt;锁定索引记录间隙（不含该记录)，确保索引记录间隙不变，防止其他事务在这个间隙进行insert，产生幻读。在RRP隔离级别下都支持。&lt;/p&gt;
&lt;p&gt;行锁和间隙锁组合，同时锁住数据，并锁住数据前面的间隙Gap。在RR隔离级别下支持。&lt;/p&gt;
&lt;p&gt;默认情况下，InnoDB 在 REPEATABLE READ事务隔离级别运行，InnoDB使用next-key锁进行搜索和索引扫描，以防止幻读。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;索引上的等值查询(唯一索引)，给不存在的记录加锁时,优化为间隙锁。&lt;/li&gt;
&lt;li&gt;索引上的等值查询(普通索引)，向右遍历时最后一个值不满足查询需求时，next-key lock退化为间隙锁。&lt;/li&gt;
&lt;li&gt;索引上的范围查询(唯一索引) — 会访问到不满足条件的第一个值为止。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;==注意：间隙锁唯一目的是防止其他事务插入间隙。间隙锁可以共存，一个事务采用的间隙锁不会阻止另一个事务在同一间隙上采用间隙锁。==&lt;/p&gt;
&lt;h2&gt;乐观锁与悲观锁&lt;/h2&gt;
&lt;p&gt;乐观锁：（Optimistic Locking）认为对同一数据的并发操作不会总发生，属于小概率事件，不用每次都对数据上锁，也就是不采用数据库自身的锁机制，而是通过程序来实现。在程序上，我们可以采用版本号机制或者时间戳机制实现。（在程序上进行实现）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;版本号机制：
设计一个版本字段 version，第一次读的时候，会获取 version 字段的取值。然后对数据进行更新或删除操作时，会执行&lt;code&gt;UPDATE ... SET version=version+1 WHERE version=version&lt;/code&gt;。此时如果已经有事务对这条数据进行了更改，修改就不会成功。
（先获取一个版本号，在后续进行更新删除操作时，确认版本号是否正确）&lt;/li&gt;
&lt;li&gt;时间戳机制：与版本号机制一样，只不过这里叫做时间戳&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;悲观锁：认为会出现并发操作，使用数据库本身的锁机制&lt;/p&gt;
&lt;p&gt;这里可以看到基本的使用场景：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;乐观锁适合读操作比较多的场景&lt;/li&gt;
&lt;li&gt;悲观锁适合写操作比较多的场景&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;解决死锁：乐观锁（治标不治本），MyISAM储存引擎，一次获取所有锁，就不会死锁，大粒度锁；&lt;/p&gt;
</content:encoded></item><item><title>MySQL11-InnoDB引擎01-架构</title><link>https://wang-nine.github.io/posts/10413/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10413/</guid><description>详细的描述了MySQL中的InnoDB引擎的架构。</description><pubDate>Tue, 06 Jun 2023 16:00:00 GMT</pubDate><content:encoded>&lt;p&gt;详细的描述了MySQL中的InnoDB引擎的架构。&lt;/p&gt;
&lt;h1&gt;逻辑存储结构&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/mysql/storage-hierarchy.svg&quot; alt=&quot;InnoDB 表空间、段、区、页及页内记录的逻辑层级&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：表空间组织存储页，索引使用段管理叶节点与非叶节点的空间，区由连续页构成，记录再组织在页内。默认页大小为十六千字节时，一个区包含六十四页；该关系不能无条件套到所有页大小配置，段也可以先分配零散页，长字段还可能引用溢出页。&lt;/p&gt;
&lt;h1&gt;架构&lt;/h1&gt;
&lt;h2&gt;总架构&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/mysql/innodb-architecture.svg&quot; alt=&quot;InnoDB 缓冲池、日志缓冲与磁盘表空间、双写和重做日志的关系&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：缓冲池缓存数据页和索引页，日志缓冲区暂存重做记录。脏页经过相应保护路径写回表空间，而重做日志独立持久化，用于崩溃恢复。两条路径并不意味着提交时全部脏页已经落盘；更改缓冲、自适应哈希及不同表空间也不能混为同一种缓存文件。&lt;/p&gt;
&lt;h2&gt;内存结构&lt;/h2&gt;
&lt;h3&gt;缓冲池&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/mysql/buffer-pool.svg&quot; alt=&quot;Buffer pool 的页命中、未命中装入与脏页刷新路径&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：读写请求访问的是缓冲池中的页，命中时直接使用，未命中时才需要从相应表空间装入。修改后的页成为脏页，之后由刷新机制写回数据文件，相关重做记录必须先满足持久化要求。缓冲池不是查询结果缓存，也不能仅凭这张关系图推断命中率、磁盘读取量或查询耗时。&lt;/p&gt;
&lt;h3&gt;更改缓冲区&lt;/h3&gt;
&lt;p&gt;更改缓冲区针对尚未在缓冲池中的、符合条件的非唯一二级索引页，暂存其索引项变更，等到页面被读入或后台合并时再应用。它不是缓存所有表行修改的通用队列，也不能替代重做日志。其内存部分占用缓冲池，持久化部分位于系统表空间；是否启用以及哪些操作可缓冲，取决于配置与索引条件。&lt;/p&gt;
&lt;h3&gt;自适应哈希&lt;/h3&gt;
&lt;p&gt;自适应哈希索引由 InnoDB 观察索引访问模式后，按需为经常访问的索引页建立内存中的辅助查找结构。它可能只涉及部分索引键前缀，不是把整棵索引永久转换成哈希表，也不是用户创建的一种独立哈希索引。原有树形索引仍然存在；是否受益、是否启用，应结合实际工作负载判断。&lt;/p&gt;
&lt;h3&gt;日志缓冲区&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/mysql/redo-doublewrite.svg&quot; alt=&quot;日志缓冲写入重做日志与脏页双写刷新的两条路径&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：日志缓冲区保存待写入磁盘的重做记录，而不是待刷新的完整数据页。日志写入与刷盘时机受提交及刷新策略控制，不能把日志仍在内存理解为已经具备断电后的持久性。下方的双写页路径是另一类保护：页落盘前，相关重做记录须先持久化。&lt;/p&gt;
&lt;h2&gt;磁盘结构&lt;/h2&gt;
&lt;h3&gt;系统表空间与单个表文件空间&lt;/h3&gt;
&lt;p&gt;系统表空间与独立表空间的职责不同。系统表空间保存更改缓冲等共享结构，也可按布局容纳表和索引；独立表空间则把单表的数据与索引组织在对应文件中。&lt;code&gt;innodb_file_per_table&lt;/code&gt; 控制新建表的默认布局，但不代表所有既有表都自动搬入独立文件；数据字典与双写区域的位置还需要结合具体版本判断。&lt;/p&gt;
&lt;h3&gt;通用表空间与撤销表空间与临时表空间&lt;/h3&gt;
&lt;p&gt;通用表空间允许多个表共享同一表空间，不能与一表一文件的布局混同。撤销表空间组织撤销日志，为事务回滚和一致性读重建历史版本提供信息；临时表空间则服务于相应临时表活动，不是保存持久业务行的替代位置。这几类名称表示用途与生命周期的差异，并不意味着每次更新都会向所有类型的文件各写一份相同数据。&lt;/p&gt;
&lt;h3&gt;双写缓冲区与重做日志&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/mysql/redo-doublewrite.svg&quot; alt=&quot;双写保留完整页副本与重做日志记录页修改的职责对照&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：启用完整双写保护时，脏页先进入双写区域，再写入数据文件的最终位置，以便在页写入中断时取得完整副本。重做日志则记录必要的修改，用于崩溃恢复；二者不是同一种日志，也不是把日志简单复制两遍。双写区域的具体文件位置随版本变化，图中不固定文件名和个数。&lt;/p&gt;
&lt;h2&gt;后台线程&lt;/h2&gt;
&lt;p&gt;后台工作把前台事务产生的状态逐步整理为可持续运行的存储结构：页面清理负责推动脏页刷新，清理历史版本的工作在旧版本不再被回滚或一致性读需要后回收相应信息，异步读写由相关后台机制协作处理。更改缓冲也可能延后合并；这些任务并非每次提交都同步执行完毕，不能从职责名称推断固定线程数或执行耗时。&lt;/p&gt;
</content:encoded></item><item><title>MySQL09-视图</title><link>https://wang-nine.github.io/posts/10411/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10411/</guid><description>详细的描述了MySQL中的视图。</description><pubDate>Tue, 06 Jun 2023 11:00:00 GMT</pubDate><content:encoded>&lt;p&gt;详细的描述了MySQL中的视图。&lt;/p&gt;
&lt;h1&gt;视图的简介&lt;/h1&gt;
&lt;p&gt;视图(View)是一种虚拟存在的表。视图中的数据并不在数据库中实际存在，行和列数据来自定义视图的查询中使用的表，并且是在使用视图时动态生成的。&lt;br /&gt;
​通俗的讲，视图只保存了查询的SQL逻辑，不保存查询结果。所以我们在创建视图的时候，主要的工作在创建这条SQL查询语句上。&lt;/p&gt;
&lt;h1&gt;视图的语法&lt;/h1&gt;
&lt;h2&gt;创建&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;CREATE [OR REPLACE] VIEW 视图名称 [(列名列表)] AS SELECT 语句[WITH[CASCADED|LOCAL] CHECK OPTION ]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;查询&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;SHOW CREATE VIEW 视图名称; --查看创建视图语句
SELECT * FROM 视图名称.......; --查看视图数据
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;修改&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;-- 方式一
CREATE [OR REPLACE] VIEW 视图名称(列名列表)] AS SELECT语句 [WITH[CASCADED|LOCAL] CHECK OPTION]
-- 方式二
ALTER VIEW 视图名称(列名列表)] AS SELECT语句 [WITH[CASCADED|LOCAL] CHECK OPTION]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;删除&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;DROP VIEW [IF EXISTS] 视图名称[视图名称] ...
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;视图的检查选项&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;​ 当使用 WITH CHECK OPTION 子句创建视图时，My5QL会通过视图检查正在更改的每个行，例如插入，更新，删除，以使其符合视图的定义。&lt;/p&gt;
&lt;p&gt;​ MySQL允许基于另一个视图创建视图，它还会检查依赖视图中的规则以保持一致性。为了确定检查的范围，mysql 提供了两个选项: CASCADED和LOCAL，默认值为CASCADED。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;cascaded&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;WITH CASCADED CHECK OPTION&lt;/code&gt; 约束通过可更新视图执行的插入和更新：新行必须满足当前视图条件，并递归满足所依赖视图的条件，即使底层视图没有显式声明检查选项也要检查。未写检查范围关键字时默认为级联检查；它不是给删除操作附加同样的新行条件，也不会把虚拟视图变成保存查询结果的实体表。&lt;/p&gt;
&lt;h2&gt;local&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;WITH LOCAL CHECK OPTION&lt;/code&gt; 检查当前视图条件，并继续沿依赖关系处理底层视图自身的检查规则；底层视图若没有检查选项，就不强制检查它自己的条件，但仍继续向下递归。因此，局部检查不等于完全忽略所有底层检查，也不能绕过底层已声明的级联约束。这里关注的是插入和更新后行是否仍满足相应可见条件。&lt;/p&gt;
&lt;h1&gt;视图的更新&lt;/h1&gt;
&lt;p&gt;要使视图可更新，视图中的行与基础表中的行之间必须存在一对一的关系。如果视图包含以下任何一项，则该视图不可更新:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;聚合函数或窗口函数(SUM()、MIN()、MAX()、COUNT()等)&lt;/li&gt;
&lt;li&gt;DISTINCT&lt;/li&gt;
&lt;li&gt;GROUP BY&lt;/li&gt;
&lt;li&gt;HAVING&lt;/li&gt;
&lt;li&gt;UNION 或者 UNION ALL&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>MySQL07-存储函数</title><link>https://wang-nine.github.io/posts/10409/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10409/</guid><description>详细的描述了MySQL中的存储函数。</description><pubDate>Mon, 05 Jun 2023 15:50:00 GMT</pubDate><content:encoded>&lt;p&gt;详细的描述了MySQL中的存储函数。&lt;/p&gt;
&lt;h1&gt;存储函数&lt;/h1&gt;
&lt;p&gt;存储函数是有返回值的存储过程，存储函数的参数只能是IN类型的。&lt;/p&gt;
&lt;h2&gt;语法&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;CREATE FUNCTION 存储函数名称([参数列表])
RETURNS type [characteristic ...]
BEGIN
	--SQL语句
	RETURN ...;
END;

characteristic说明:
 + DETERMINISTIC:相同的输入参数总是产生相同的结果
 + NO SQL:不包含SQL语句。
 + READS SQL DATA:包含读取数据的语句，但不包含写入数据的语句。
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;样例&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;-- 存储函数
-- 计算从1到n的累加
create function fun1(n int)
returns int deterministic 
begin
    declare total int default 0;

    while n &amp;gt; 0 do
        set total := total + n;
        set n := n - 1;
    end while;

    return total;
end;

select fun1(100);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意当未添加 deterministic 会产生如下bug：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;此例中，函数仅按输入整数做累加，相同输入能确定相同结果，因此声明 &lt;code&gt;DETERMINISTIC&lt;/code&gt; 有明确依据。若启用了二进制日志，且函数创建者信任设置保持严格，创建函数时未声明确定性、无数据访问或只读数据等特征，可能被拒绝。这里讨论的是创建阶段的日志安全限制，并非所有环境中省略确定性声明都会出错。&lt;/p&gt;
&lt;p&gt;运行结果：&lt;/p&gt;
&lt;p&gt;此例中，函数从传入的 &lt;code&gt;100&lt;/code&gt; 开始，每轮先累加再递减，直到参数不再为正，最后通过 &lt;code&gt;RETURN total&lt;/code&gt; 返回总和。由一至一百的等差求和可得 &lt;code&gt;5050&lt;/code&gt;，因此 &lt;code&gt;SELECT fun1(100)&lt;/code&gt; 读取的是这一标量值；这与存储过程中直接执行查询返回结果集的写法不同。&lt;/p&gt;
&lt;h1&gt;注意&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;存储函数必须要有返回值，而且参数只能是输入类型的参数，其过程实现完全可以被存储过程所代替。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;因此存储函数的使用较少，这里仅做了解。&lt;/strong&gt;&lt;/p&gt;
</content:encoded></item><item><title>MySQL08-触发器</title><link>https://wang-nine.github.io/posts/10410/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10410/</guid><description>详细的描述了MySQL中的触发器。</description><pubDate>Mon, 05 Jun 2023 15:50:00 GMT</pubDate><content:encoded>&lt;p&gt;详细的描述了MySQL中的触发器。&lt;/p&gt;
&lt;h1&gt;触发器简介&lt;/h1&gt;
&lt;h2&gt;简介&lt;/h2&gt;
&lt;p&gt;触发器是与表有关的数据库对象，指在 insert / update / delete 之前或之后，触发并执行触发器中定义的SQL语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性，日志记录，数据校验等操作。&lt;br /&gt;
​&lt;/p&gt;
&lt;p&gt;使用别名 OLD 和 NEW 来引用触发器中发生变化的记录内容，这与其他的数据库是相似的。&lt;/p&gt;
&lt;p&gt;现在触发器还只支持行级触发（影响几行触发几次）&lt;/p&gt;
&lt;p&gt;现在触发器不支持语句级触发（执行一条语句触发一次）&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;触发器类型&lt;/th&gt;
&lt;th&gt;NEW 和 OLD&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;INSERT触发器&lt;/td&gt;
&lt;td&gt;NEW表示将要或者已经新增的数据&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UPDATE型触发器&lt;/td&gt;
&lt;td&gt;OLD表示修改之前的数据，NEW表示将要或已经修改后的数据&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DELETE型触发器&lt;/td&gt;
&lt;td&gt;OLD表示将要或者已经删除的数据&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;语法&lt;/h2&gt;
&lt;h3&gt;创建&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;CREATE TRIGGER trigger_name
BEFORE/AFTER INSERT/UPDATE/DELETE
ON tbl_name FOR EACH ROW --行级触发器
BEGIN
	trigger_stmt;
END;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;查看&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;SHOW TRIGGERS;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;删除&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;DROP TRIGGER [schema_name.]Jtrigger_name; --如果没有指定schema_name，默认为当前数据库。
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;INSERT 类型触发器样例&lt;/h3&gt;
&lt;p&gt;样例：&lt;/p&gt;
&lt;p&gt;通过触发器记录 tb_user 表的数据变更日志，将变更日志插入到日志表user_logs中，包含增加，修改，删除；、、&lt;/p&gt;
&lt;p&gt;建表语句如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;create table user_logs(
  id int(11) not null auto_increment,
  operation varchar(20) not null comment &apos;操作类型, insert/update/delete&apos;,
  operate_time datetime not null comment &apos;操作时间&apos;,
  operate_id int(11) not null comment &apos;操作的ID&apos;,
  operate_params varchar(500) comment &apos;操作参数&apos;,
  primary key(`id`)
)engine=innodb default charset=utf8;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;-- 插入数据时的触发器
create trigger tb_user_insert_trigger
    after insert on tb_user for each row
begin
    insert into user_logs(id, operation, operate_time, operate_id, operate_params) values
    (null, &apos;insert&apos;, now(), new.id, concat(&apos;插入的数据内容为: id = &apos;, new.id, &apos; name = &apos;, new.name, &apos; phone = &apos;, new.phone),
     &apos; email = &apos;, new.email, &apos; profession = &apos;, new.profession);
end;

-- 查看触发器指令
show triggers;

-- 插入数据
insert into tb_user(id, name, phone, email, profession, age, gender, status, createtime)
values (25,&apos;Wang&apos;,&apos;99999999999&apos;,&apos;wang-nine@gmail.com&apos;,&apos;计科&apos;,19,&apos;1&apos;,&apos;1&apos;,now());
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行结果：&lt;/p&gt;
&lt;p&gt;此处的 &lt;code&gt;SHOW TRIGGERS&lt;/code&gt; 用于查看当前数据库中已创建的触发器定义信息，重点核对名称、触发事件、关联表、触发时机与触发语句，而不是查看用户变更日志。目标定义是用户表上的逐行插入后触发器；但上方插入日志语句的拼接括号提前结束，须先修正列值数量不匹配的问题，不能仅凭这段文本认定创建已经成功。&lt;/p&gt;
&lt;p&gt;此例的意图是：用户记录成功插入后，逐行触发器读取 &lt;code&gt;NEW&lt;/code&gt; 中的新行字段，再向日志表写入操作类型、用户主键与描述。上方日志插入语句需要把邮箱、专业也纳入同一个拼接表达式，使值的数量与目标列一致。修正并成功创建后，插入的用户主键为 &lt;code&gt;25&lt;/code&gt;，日志引用 &lt;code&gt;NEW.id&lt;/code&gt;；日志自增编号和执行时间则由运行环境决定。&lt;/p&gt;
&lt;h3&gt;UPDATE 类型触发器样例&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;-- 更新数据时的触发器
create trigger tb_user_update_trigger
    after update on tb_user for each row
begin
    insert into user_logs(id, operation, operate_time, operate_id, operate_params) values
    (null, &apos;update&apos;, now(), new.id,
     concat(&apos;更新前的数据内容为: id = &apos;, old.id, &apos; name = &apos;, old.name, &apos; phone = &apos;, old.phone,&apos; email = &apos;, old.email, &apos; profession = &apos;, old.profession,
     &apos;更新前的数据内容为: id = &apos;, new.id, &apos; name = &apos;, new.name, &apos; phone = &apos;, new.phone,&apos; email = &apos;, new.email, &apos; profession = &apos;, new.profession));
end;

-- 查看触发器指令
show triggers;

-- 更新数据
update tb_user set age = 99 where id = 25;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行结果：&lt;/p&gt;
&lt;p&gt;此处查看触发器时，应核对更新触发器是否关联 &lt;code&gt;tb_user&lt;/code&gt;、事件是否为更新、时机是否在更新之后，以及是否声明逐行执行。&lt;code&gt;SHOW TRIGGERS&lt;/code&gt; 展示的是触发器元信息，不会展示某次更新前后的用户数据，也不等同于查询日志表；实际返回哪些定义取决于当前数据库中已成功创建的对象。&lt;/p&gt;
&lt;p&gt;此例中，若主键为 &lt;code&gt;25&lt;/code&gt; 的用户存在且年龄实际发生变化，更新后的 &lt;code&gt;NEW.age&lt;/code&gt; 为 &lt;code&gt;99&lt;/code&gt;，更新前的值则来自 &lt;code&gt;OLD.age&lt;/code&gt;。不过触发器的日志拼接只包含主键、姓名、电话、邮箱和专业，并未记录年龄，所以这次年龄变化不会直接显示在描述字段里；两段文案还都写成了“更新前”，应与 &lt;code&gt;OLD&lt;/code&gt;、&lt;code&gt;NEW&lt;/code&gt; 的真实含义区分。&lt;/p&gt;
&lt;h3&gt;DELETE 类型触发器样例&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;-- 删除数据时的触发器
create trigger tb_user_delete_trigger
    after delete on tb_user for each row
begin
    insert into user_logs(id, operation, operate_time, operate_id, operate_params) values
    (null, &apos;delete&apos;, now(), old.id, concat(&apos;插入的数据内容为: id = &apos;, old.id, &apos; name = &apos;, old.name, &apos; phone = &apos;, old.phone,
     &apos; email = &apos;, old.email, &apos; profession = &apos;, old.profession));
end;

-- 查看触发器指令
show triggers;

-- 删除数据
delete from tb_user where id = 25;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行结果：&lt;/p&gt;
&lt;p&gt;此处的查看命令用于确认删除触发器的定义：事件为删除，时机在删除之后，并对每个被删除的用户行执行一次。删除场景只能从 &lt;code&gt;OLD&lt;/code&gt; 读取原行字段，不存在删除后的新行可供 &lt;code&gt;NEW&lt;/code&gt; 引用。查看定义不会删除用户，也不会直接返回删除操作写入日志表的内容。&lt;/p&gt;
&lt;p&gt;此例中，删除语句针对主键为 &lt;code&gt;25&lt;/code&gt; 的用户；若该行存在并成功删除，触发器就用 &lt;code&gt;OLD&lt;/code&gt; 保存的原行字段生成删除日志，操作类型取字面量 &lt;code&gt;delete&lt;/code&gt;。描述字符串虽然写着“插入的数据内容为”，并不改变它属于删除事件这一事实。若目标行不存在便没有相应的逐行触发，不能预设日志条数、自增编号或操作时间。&lt;/p&gt;
</content:encoded></item><item><title>MySQL06-存储过程</title><link>https://wang-nine.github.io/posts/10408/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10408/</guid><description>详细的描述了MySQL中的存储过程。</description><pubDate>Sun, 04 Jun 2023 14:30:00 GMT</pubDate><content:encoded>&lt;p&gt;详细的描述了MySQL中的存储过程。&lt;/p&gt;
&lt;h1&gt;存储过程介绍&lt;/h1&gt;
&lt;h2&gt;简介&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;存储过程是事先经过编译并存储在数据库中的一段SQL语句的集合，调用存储过程可以简化应用开发人员的很多工作，减少数据在数据库和应用服务器之间的传输，对于提高数据处理的效率是有好处的。&lt;/li&gt;
&lt;li&gt;存储过程思想上很简单，就是数据库SQL语言层面的代码封装与重用。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;特点&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;封装，复用&lt;/li&gt;
&lt;li&gt;可以接收参数，也可以返回数据&lt;/li&gt;
&lt;li&gt;减少网络交互，效率提升&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;存储过程语法&lt;/h1&gt;
&lt;h2&gt;创建&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;CREATE PROCEDURE 存储过程名称([参数列表])
BEGIN
	...SQL语句
END;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;调用&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;CALL 名称([参数]);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;查看&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;SELECT * FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUINE_SQHEMA = &apos;xxx&apos;;  --查询指定数据库的存储过程及状态信息
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;SHOW CREATE PROCEDURE 存储过程名称; --查询某个存储过程的定义
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;删除&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;DROP PROCEDURE [IF EXISTS] 存储过程名称;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;注意&lt;/h2&gt;
&lt;p&gt;注意：在命令行中，执行创建存储过程的SQL时，需要通过关键字 delimiter 指定SQL语句的结束符。&lt;/p&gt;
&lt;p&gt;可以设置为两个$$符号&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;delimiter $$
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;变量&lt;/h1&gt;
&lt;h2&gt;系统变量&lt;/h2&gt;
&lt;p&gt;系统变量是MySQL服务器提供，不是用户定义的，属于服务器层面。分为全局变量（GLOBAL）、会话变量（SESSION）&lt;/p&gt;
&lt;h3&gt;查看系统变量&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;SHOW [SESSION|GLOBAL] VARIABLES; --查看所有系统变量
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;SHOW [SESSION|GLOBAL] VARIABLES LIKE ...; --可以通过LIKE模糊匹配方式查找变量
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;SELECT @@[SESSION|GLOBAL] 系统变量名; --查看指定变量的值
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;设置系统变量&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;SET [SESSION|GLOBAL] 系统变量名 = 值;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;SET @@[SESSION|GLOBAL] 系统变量名 = 值;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如果没有指定SESSION/GLOBAL，默认是SESSION，会话变量。&lt;/li&gt;
&lt;li&gt;mysql服务重新启动之后，所设置的全局参数会失效，要想不失效，可以在/etc/my.cnf 中配置。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;用户定义变量&lt;/h2&gt;
&lt;p&gt;用户定义变量是用户根据需要自己定义的变量，用户变量不用提前声明，在用的时候直接用“@变量名”使用就可以。其作用域为当前连接即当前会话。&lt;/p&gt;
&lt;h3&gt;赋值&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;SET @var_name = expr [,@var_name = expr] ...;
SET @var_name := expr [,@var_name := expr] ...;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;SELECT @var_name := expr [, @var_name := expr]... ;
SELECT 字段名 INTO @var_name FROM 表名;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;使用&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;SELECT @var_name;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;在MySQL中， = 既可以作为赋值，也可以作为比较，因此在赋值时建议使用 :=&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;用户定义的变量无需对其进行声明或初始化，只不过获取到的值为NULL.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;局部变量&lt;/h2&gt;
&lt;p&gt;局部变量是根据需要定义的在局部生效的变量，访问之前，需要DECLARE声明。可用作存储过程内的局部变量和输入参数，局部变量的范围是在其内声明的BEGIN….END块。&lt;/p&gt;
&lt;h3&gt;声明&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;DECLARE 变量名变量类型 [DEFAULT ...];
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;赋值&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;SET 变量名 = 值;
SET 变量名 := 值;
SELECT 字段名 INTO 变量名 FROM 表名 ...;
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;存储过程&lt;/h1&gt;
&lt;h2&gt;存储过程 - if 判断&lt;/h2&gt;
&lt;h3&gt;语法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;IF 条件1 THEN
	...
ELSE IF 条件2 THEN --可选
	...
ELSE --可选
	...
END IF;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;无参数的存储过程样例&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;-- 判定分数对应的分数等级。
-- score &amp;gt;= 85分，等级为优秀。
-- score &amp;gt;= 60分且score &amp;lt; 85分，等级为及格。
-- score &amp;lt; 60分，等级为不及格。
create procedure p1()
begin
    declare score int default 58;
    declare result varchar(10);
    if score &amp;gt;= 85 then
        set result := &apos;优秀&apos;;
    elseif score &amp;gt;= 60 then
        set result := &apos;及格&apos;;
    else
        set result := &apos;不及格&apos;;
    end if;
    select result;
end;

call p1();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行结果：&lt;/p&gt;
&lt;p&gt;此例中，局部变量 &lt;code&gt;score&lt;/code&gt; 初始为 &lt;code&gt;58&lt;/code&gt;，既不满足优秀分支，也不满足及格分支，因此进入最后的分支，将 &lt;code&gt;result&lt;/code&gt; 设为“不及格”。过程末尾的 &lt;code&gt;SELECT result&lt;/code&gt; 返回这一等级文本；这是根据分支与初值推导的含义，不涉及用户表中的数据。&lt;/p&gt;
&lt;h2&gt;参数&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;th&gt;备注&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;IN&lt;/td&gt;
&lt;td&gt;该类参数作为输入，也就是需要调用时传入值&lt;/td&gt;
&lt;td&gt;默认&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OUT&lt;/td&gt;
&lt;td&gt;该类参数作为输出,也就是该参数可以作为返回值&lt;/td&gt;
&lt;td&gt;/&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;INOUT&lt;/td&gt;
&lt;td&gt;既可以作为输入参数,也可以作为输出参数&lt;/td&gt;
&lt;td&gt;/&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;语法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;CREATE PROCEDURE 存储过程名称 ([IN / OUT / INOUT 参数名 参数类型])
BEGIN
	--SQL语句
END;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;有参数的存储过程样例&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;-- 根据传入(in)参数score，判定当前分数对应的分数等级，并返回(out)。
-- score &amp;gt;= 85分，等级为优秀。
-- score &amp;gt;= 60分且score &amp;lt; 85分，等级为及格。
-- score &amp;lt; 60分，等级为不及格。
create procedure p1(in score int, out result varchar(10))
begin
    if score &amp;gt;= 85 then
        set result := &apos;优秀&apos;;
    elseif score &amp;gt;= 60 then
        set result := &apos;及格&apos;;
    else
        set result := &apos;不及格&apos;;
    end if;
end;

call p1(68, @result);
select @result;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行结果：&lt;/p&gt;
&lt;p&gt;此例中，传入的分数为 &lt;code&gt;68&lt;/code&gt;，未达到优秀阈值，但满足及格条件，因此输出参数 &lt;code&gt;result&lt;/code&gt; 被赋为“及格”。调用结束后，输出值写入当前会话变量 &lt;code&gt;@result&lt;/code&gt;，随后 &lt;code&gt;SELECT @result&lt;/code&gt; 读取该等级；不要把输出参数与过程内部直接返回结果集混为一谈。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-- 将传入的 200分制的分数,进行换算,换算成百分制，然后返阿分数---&amp;gt; inout
create procedure p2(inout score double)
begin
    set score := score * 0.5;
end;

set @score = 163;
call p2(@score);
select @score;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行结果：&lt;/p&gt;
&lt;p&gt;此例中，&lt;code&gt;@score&lt;/code&gt; 先设为 &lt;code&gt;163&lt;/code&gt;，再作为输入输出参数传给过程。过程把分数乘以 &lt;code&gt;0.5&lt;/code&gt;，按算术可得 &lt;code&gt;81.5&lt;/code&gt;，并将新值写回同一个会话变量。随后的 &lt;code&gt;SELECT @score&lt;/code&gt; 读取换算后的百分制分数，体现输入输出参数既接收值又带回修改值的作用。&lt;/p&gt;
&lt;h2&gt;存储过程 - Case&lt;/h2&gt;
&lt;h3&gt;语法一&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;CASE case_value
	WHEN when_value1 THEN statement_list1
	[WHEN when_value2 THEN statement_Kist 2] ...
	[ELSE statement_list]
END CASE;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;语法二&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;CASE
	WHEN search_condition1 THEN statement_list1
	[WHEN search_condition2 THEN statement_list2] ...
	[ELSE statemept tlist]
END CASE;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;样例&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;-- 根据传入的月份，判定月份所属的季节（要求采用case结构)。
-- 1.1-3月份,为第一季度
-- 2.4-6月份，为第二季度
-- 3.7-9月份，为第三季度
-- 4.10-12月份，为第四季度
create procedure p3(in month int)
begin
    declare result varchar(10);

    case
        when month &amp;gt;= 1 and month &amp;lt;= 3
            then set result := &apos;第一季度&apos;;
        when month &amp;gt;= 4 and month &amp;lt;= 6
            then set result := &apos;第二季度&apos;;
        when month &amp;gt;= 7 and month &amp;lt;= 9
            then set result := &apos;第三季度&apos;;
        when month &amp;gt;= 10 and month &amp;lt;= 12
            then set result := &apos;第四季度&apos;;
        else
            set result := &apos;非法参数&apos;;
    end case;
    select concat(&apos;您输入的月份为&apos;, month, &apos;所属的季度为&apos;, result);
end;

call p3(4)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行结果：&lt;/p&gt;
&lt;p&gt;此例中，月份参数为 &lt;code&gt;4&lt;/code&gt;，命中四月至六月的分支，因而将结果设为“第二季度”。末尾的字符串拼接表达式会形成“您输入的月份为4所属的季度为第二季度”。这里实际判断的是季度而不是气象季节；其余合法月份按各自区间分类，区间之外进入非法参数分支。&lt;/p&gt;
&lt;h2&gt;存储过程循环 - while&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;while循环是有条件的循环控制语句。满足条件后，再执行循环体中的SQL语句。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;语法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#先判定条件，如果条件为true，则执行逻辑，否则，不执行逻辑
WHILE 条件 DO
	SQL逻辑...
END WHILE;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;样例&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;-- 计算从1紧加到的值，n为传入的参数值。
-- 1. 定义局部变量，记录累加后的值
-- 2. 每新环一次,就会n进行减1,如果n减到0，则退出循环
create procedure p4(in n int)
begin
    declare total int default 0;
    while n &amp;gt; 0 do
        set total := total + n;
        set n := n - 1;
    end while;
    select total;
end;

call p4(10);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行结果：&lt;/p&gt;
&lt;p&gt;此例中，初始参数为 &lt;code&gt;10&lt;/code&gt;、累计值为零，循环逐次把当前参数加入累计值，再将参数减一，直到退出。因此，从十累加到一可推导出 &lt;code&gt;total = 55&lt;/code&gt;，末尾的 &lt;code&gt;SELECT total&lt;/code&gt; 读取该累计值。这一输入下，先判断的循环、后判断的循环以及显式退出的循环可得到相同总和，但退出条件的检查位置不同。&lt;/p&gt;
&lt;h2&gt;存储过程循环 - repeat&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;repeat是有条件的循环控制语句，当满足条件的时候退出循环。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;语法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#先执行一次逻辑，然后判定逻辑是否满足，如果满足，则退出。如果不满足，则继续下一次循环
REPEAT
	SQL逻辑 ...
	UNTIL 条件
END REPEAT;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;样例&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;-- 计算从1紧加到的值，n为传入的参数值。
-- 1. 定义局部变量，记录累加后的值
-- 2. 每新环一次,就会n进行减1,如果n减到0，则退出循环
create procedure p5(in n int)
begin
    declare total int default 0;
    repeat
        set total := total + n;
        set n := n - 1;
    until n &amp;lt;= 0
    end repeat;
    select total;
end;

call p5(10);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行结果：&lt;/p&gt;
&lt;p&gt;此例中，初始参数为 &lt;code&gt;10&lt;/code&gt;、累计值为零，循环逐次把当前参数加入累计值，再将参数减一，直到退出。因此，从十累加到一可推导出 &lt;code&gt;total = 55&lt;/code&gt;，末尾的 &lt;code&gt;SELECT total&lt;/code&gt; 读取该累计值。这一输入下，先判断的循环、后判断的循环以及显式退出的循环可得到相同总和，但退出条件的检查位置不同。&lt;/p&gt;
&lt;h2&gt;存储过程循环 - loop&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;LOOP实现简单的循环，如果不在SQL逻辑中增加退出循环的条件，可以用其来实现简单的死循环。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;OOP可以配合一下两个语句使用**&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;LEAVE：配合循环使用，退出循环。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ITERATE：必须用在循环中，作用是跳过当前循环剩下的语句，直接进入下一次循环。&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;语法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;[begin_label:] LOOP
	SQL逻辑 ...
END LOOP [end_label];
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;LEAVE label; --退出指定标记的循环体
ITERATE label; --直接进入下一次循环
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;样例1&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;-- 计算从1紧加到的值,n为传入的参数值。
-- 1. 定义局部变量,记录累加后的值
-- 2. 每新环一次,就会n进行减1,如果n减到0，则退出循环   ---&amp;gt; leave xxx;
create procedure p6(in n int)
begin
    declare total int default 0;
    sum : loop
        if n &amp;lt;= 0 then
            leave sum;
        end if;

        set total := total + n;
        set n := n - 1;
    end loop;
    select total;
end;

call p6(10);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行结果：&lt;/p&gt;
&lt;p&gt;此例中，初始参数为 &lt;code&gt;10&lt;/code&gt;、累计值为零，循环逐次把当前参数加入累计值，再将参数减一，直到退出。因此，从十累加到一可推导出 &lt;code&gt;total = 55&lt;/code&gt;，末尾的 &lt;code&gt;SELECT total&lt;/code&gt; 读取该累计值。这一输入下，先判断的循环、后判断的循环以及显式退出的循环可得到相同总和，但退出条件的检查位置不同。&lt;/p&gt;
&lt;h3&gt;样例2&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;-- 计算从1到n之问的偶数累加的值，n为传入的参数值。
-- 1．定义局部变量，记录累加之后的值;
-- 2．每循环一次，就会对n进行-1，如果n减到0，则退出循环   ---&amp;gt; leave xxx
-- 3．如果当次累加的数据是奇数，则直接进入下一次循环   ---&amp;gt; iterate xxx
create procedure p7(in n int)
begin
    declare total int default 0;
    sum : loop
        if n &amp;lt;= 0 then
            leave sum;
        end if;

        if n % 2 = 1 then
            set n := n - 1;
            iterate sum;
        end if;

        set total := total + n;
        set n := n - 1;
    end loop;
    select total;
end;

call p7(10);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行结果：&lt;/p&gt;
&lt;p&gt;此例中，&lt;code&gt;p7(10)&lt;/code&gt; 只累加十以内的正偶数。遇到奇数时，先把参数减一，再通过 &lt;code&gt;ITERATE&lt;/code&gt; 跳过本轮余下语句；偶数则加入累计值。因而十、八、六、四、二之和为 &lt;code&gt;30&lt;/code&gt;，最后的 &lt;code&gt;SELECT total&lt;/code&gt; 读取该值；当参数降到零时，由 &lt;code&gt;LEAVE&lt;/code&gt; 退出循环。&lt;/p&gt;
&lt;h2&gt;游标 - Cursor&lt;/h2&gt;
&lt;p&gt;游标（CURSOR）是用来存储查询结果集的数据类型，在存储过程和函数中可以使用游标对结果集进行循环的处理。&lt;/p&gt;
&lt;p&gt;游标的使用包括游标的声明、OPEN、FETCH 和 CLOSE，其语法分别如下。&lt;/p&gt;
&lt;h3&gt;语法：&lt;/h3&gt;
&lt;h4&gt;声明游标&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;DECLARE 游标名称 CURSOR FOR 查询语句;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;打开游标&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;OPEN 游标名称;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;获取游标记录&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;FETCH 游标名称 INTO 变量[变量];
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;关闭游标&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;CLOSE 游标名称;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;样例&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;-- 游标
-- 根据传入的参数uage，来查询用户表tb_user中，所有的用户年龄小于等于uage的用户姓名(name)和专业（profession)，
-- 并将用户的姓名和专业插入到所创建的一张新表(id,name,profession)中

-- 逻辑
-- 1. 声明游标，存储查询到的结果集
-- 2. 准备：创建表结构
-- 3. 开启游标
-- 4， 获取游标中的记录
-- 5. 将记录插入到新的表中
-- 6. 关闭游标
create procedure p8(in uage int)
begin
    declare uname varchar(100);
    declare upro varchar(100);
    declare u_cursor cursor for select name, profession from tb_user where age &amp;lt;= uage;

    drop table if exists tb_user_pro;
    create table if not exists tb_user_pro(
        id int primary key auto_increment,
        name varchar(100),
        profession varchar(100)
    );

    open u_cursor;

    while true do
        fetch u_cursor into uname, upro;
        insert into tb_user_pro values (null, uname, upro);
    end while;

    close u_cursor;

end;

call p8(30);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意while的循环退出条件为true，即死循环，产生如下bug&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;此例中，游标读取年龄不大于三十的用户姓名和专业，但 &lt;code&gt;WHILE TRUE&lt;/code&gt; 本身没有退出条件。结果集耗尽后，下一次 &lt;code&gt;FETCH&lt;/code&gt; 会触发 &lt;code&gt;NOT FOUND&lt;/code&gt; 条件（SQLSTATE &lt;code&gt;&apos;02000&apos;&lt;/code&gt;）。按 MySQL 的处理器规则，正常产生的 &lt;code&gt;NOT FOUND&lt;/code&gt; 在没有处理器时默认执行 &lt;code&gt;CONTINUE&lt;/code&gt;，而不是 &lt;code&gt;EXIT&lt;/code&gt;，所以过程不会自动退出控制流：它会继续执行后面的 &lt;code&gt;INSERT&lt;/code&gt;，再次进入循环并执行下一次 &lt;code&gt;FETCH&lt;/code&gt;。要结束循环，必须在过程中显式处理耗尽，例如像后面的改进例那样用退出型处理器捕获该条件并关闭游标；实际插入次数和最终表内容取决于运行时的游标与表状态，不能仅凭这段代码确定。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;普通变量的声明先与游标&lt;/li&gt;
&lt;li&gt;循环中如何得知循环结束的条件？即游标什么时候遍历完？&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;条件处理程序&lt;/h2&gt;
&lt;p&gt;条件处理程序（Handler）可以用来定义在流程控制结构执行过程中遇到问题时相应的处理步骤。&lt;/p&gt;
&lt;h3&gt;语法&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;DECLARE handler_action HANDLER FOR condition_value [,condition_value] ... statement;

handler_action
	cONTINUE:继续执行当前程序
	EXIT:终止执行当前程序
condition_value
	SQLSTATE:sqlstate_value:状态码，如02000
	SQLWARNING:所有以01开头的SQLSTATE代码的简写
	NOT FOUND:所有以02开头的SQLSTATE代码的简写
	SQLEXCEPTION:所有没有被SQLWARNING 或 NOT FOUND捕获的SQLSTATE代码的简写
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;样例(改进了循环退出判断条件)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;-- 游标
-- 根据传入的参数uage，来查询用户表tb_user中，所有的用户年龄小于等于uage的用户姓名(name)和专业（profession)，
-- 并将用户的姓名和专业插入到所创建的一张新表(id,name,profession)中

-- 逻辑
-- 1. 声明游标，存储查询到的结果集
-- 2. 准备：创建表结构
-- 3. 开启游标
-- 4， 获取游标中的记录
-- 5. 将记录插入到新的表中
-- 6. 关闭游标
create procedure p8(in uage int)
begin
    declare uname varchar(100);
    declare upro varchar(100);
    declare u_cursor cursor for select name, profession from tb_user where age &amp;lt;= uage;

    -- 声明条件处理程序，当状态码为 02000 时关闭游标
    -- 也可以写为 declare exit handler for not found close u_cursor;
    declare exit handler for sqlstate &apos;02000&apos; close u_cursor;

    drop table if exists tb_user_pro;
    create table if not exists tb_user_pro(
        id int primary key auto_increment,
        name varchar(100),
        profession varchar(100)
    );

    open u_cursor;

    while true do
        fetch u_cursor into uname, upro;
        insert into tb_user_pro values (null, uname, upro);
    end while;

    close u_cursor;

end;

call p8(30);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;具体的详细的官方解释的状态码如下&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://dev.mysql.com/doc/mysql-errors/8.0/en/server-error-reference.html&quot;&gt;MySQL官网文档状态码解释&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>HTML5+CSS301-HTML</title><link>https://wang-nine.github.io/posts/10501/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10501/</guid><description>第一天笔记</description><pubDate>Sat, 03 Jun 2023 16:00:00 GMT</pubDate><content:encoded>&lt;p&gt;第一天笔记&lt;/p&gt;
&lt;h1&gt;HTML&lt;/h1&gt;
&lt;p&gt;HTML超文本标记语言―—HyperText Markup Language。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;超文本是什么? 链接&lt;/li&gt;
&lt;li&gt;标记是什么? 标记也叫标签，带尖括号的文本&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;标签语法&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;标签成对出现，中间包裹内容&lt;/li&gt;
&lt;li&gt;&amp;lt;&amp;gt;里面放英文字母(标签名)&lt;/li&gt;
&lt;li&gt;结束标签比开始标签多/&lt;/li&gt;
&lt;li&gt;拓展
&lt;ul&gt;
&lt;li&gt;双标签:成对出现的标签&lt;/li&gt;
&lt;li&gt;单标签:只有开始标签，没有结束标签&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;基本骨架&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;html:整个网页&lt;/li&gt;
&lt;li&gt;head:网页头部，用来存放给浏览器看的信息，例如CSS
&lt;ul&gt;
&lt;li&gt;title:网页标题&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;body:网页主体，用来存放给用户看的信息，例如图片、文字&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;标签的关系&lt;/h2&gt;
&lt;p&gt;作用：明确标签的书写位置;让代码格式更整齐&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;父子关系（嵌套关系)&lt;/li&gt;
&lt;li&gt;兄弟关系（并列关系)&lt;/li&gt;
&lt;li&gt;代码格式&lt;/li&gt;
&lt;li&gt;父子关系：子级标签换行且缩进(Tab键)&lt;/li&gt;
&lt;li&gt;兄弟关系：兄弟标签换行要对齐&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;注释&lt;/h2&gt;
&lt;h2&gt;排版标签，标题和段落&lt;/h2&gt;
&lt;h3&gt;标题标签&lt;/h3&gt;
&lt;p&gt;一般用在新闻标题、文章标题、网页区域名称、产品名称等等。&lt;/p&gt;
&lt;p&gt;标签名: h1 ~h6（双标签）&lt;/p&gt;
&lt;p&gt;显示特点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;文字加粗&lt;/li&gt;
&lt;li&gt;字号逐渐减小&lt;/li&gt;
&lt;li&gt;独占一行（换行）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;经验分享：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;h1标签在一个网页中只能用一次&lt;/li&gt;
&lt;li&gt;用来放新闻标题或网页的logo. h2~ h6没有使用次数的限制&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;段落标签&lt;/h3&gt;
&lt;p&gt;一般用在新闻段落、文章段落、产品描述信息等等。&lt;/p&gt;
&lt;p&gt;标签名: p（双标签）&lt;/p&gt;
&lt;p&gt;显示特点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;独占一行&lt;/li&gt;
&lt;li&gt;段落之间存在间隙&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;换行和水平线标签&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;换行标签 \
（单标签）&lt;/li&gt;
&lt;li&gt;水平线标签 \ （单标签）&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;文本格式化标签&lt;/h2&gt;
&lt;p&gt;作用：为文本添加特殊格式，以突出重点。常见的文本格式&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;加粗&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;倾斜&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;下划线&lt;/li&gt;
&lt;li&gt;&lt;s&gt;删除线&lt;/s&gt;&lt;/li&gt;
&lt;li&gt;等等&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;标签名&lt;/th&gt;
&lt;th&gt;效果&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;\&lt;strong&gt;\&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;加粗&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;\&lt;em&gt;\&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;倾斜&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;\\&lt;/td&gt;
&lt;td&gt;下划线&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;\&lt;s&gt;\&lt;/s&gt;&lt;/td&gt;
&lt;td&gt;删除线&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;标签名&lt;/th&gt;
&lt;th&gt;效果&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;b&lt;/td&gt;
&lt;td&gt;加错&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;i&lt;/td&gt;
&lt;td&gt;倾斜&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;u&lt;/td&gt;
&lt;td&gt;下划线&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;s&lt;/td&gt;
&lt;td&gt;删除线&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;strong、em、ins、 del标签自带强调含义（语义），一般使用这一套&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;两个文本格式化标签是在一行显示&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;图像标签&lt;/h2&gt;
&lt;p&gt;作用：在网页中插入图片。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;img src=&quot;图片的url&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;src用于指定图像的位置和名称，是的必须属性。&lt;/p&gt;
&lt;p&gt;标签属性&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;alt&lt;/td&gt;
&lt;td&gt;替换文本&lt;/td&gt;
&lt;td&gt;图片无法显示时显示的文字&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;title&lt;/td&gt;
&lt;td&gt;提示文本&lt;/td&gt;
&lt;td&gt;鼠标悬停在图片上时显示的文字&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;width&lt;/td&gt;
&lt;td&gt;图片的宽度&lt;/td&gt;
&lt;td&gt;值为数字，没有单位&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;height&lt;/td&gt;
&lt;td&gt;图片的高度&lt;/td&gt;
&lt;td&gt;值为数字，没有单位&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;属性名=”属性值”&lt;/li&gt;
&lt;li&gt;属性写在尖括号里面，标签名后面，标签名和属性之间用空格隔开，不区分先后顺序&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;文件路径&lt;/h2&gt;
&lt;p&gt;路径分类：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;相对路径：从当前文件位置出发查找目标文件&lt;/li&gt;
&lt;li&gt;绝对路径：从盘符出发查找目标文件&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;相对路径写法：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;. 当前文件所在文件夹&lt;/li&gt;
&lt;li&gt;../ 当前文件上一级文件夹&lt;/li&gt;
&lt;li&gt;/ 进入某个文件夹里面&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;绝对路径写法：&lt;/p&gt;
&lt;p&gt;Windows 默认是\，其他系统是/，建议统一写为/&lt;/p&gt;
&lt;h2&gt;超链接标签&lt;/h2&gt;
&lt;p&gt;作用：点击跳转到其他页面。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意：属性 target=”_blank” 表示新窗口跳转页面&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;经验分享：开发初期，不知道超链接的跳转地址，href属性值写#，表示空链接，不会跳转&lt;/p&gt;
&lt;h2&gt;音频标签&lt;/h2&gt;
&lt;p&gt;常见属性&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;th&gt;特殊说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;src（必须属性）&lt;/td&gt;
&lt;td&gt;音频URL&lt;/td&gt;
&lt;td&gt;支持格式：MP3、Ogg、Wav&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;controls&lt;/td&gt;
&lt;td&gt;展示音频控制面板&lt;/td&gt;
&lt;td&gt;/&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;loop&lt;/td&gt;
&lt;td&gt;循环播放&lt;/td&gt;
&lt;td&gt;/&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;autoplay&lt;/td&gt;
&lt;td&gt;自动播放&lt;/td&gt;
&lt;td&gt;为了提升用户体验，浏览器一般会禁用自动播放&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;简写：属性名和属性值一样，可以进行简写，例如&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;audio src=&quot;音频的url&quot; contrils loop&amp;gt;&amp;lt;/audio&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;视频标签&lt;/h2&gt;
&lt;p&gt;常见属性&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;th&gt;特殊说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;src（必须属性）&lt;/td&gt;
&lt;td&gt;视频URL&lt;/td&gt;
&lt;td&gt;支持格式：MP4、WebM、Ogg&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;controls&lt;/td&gt;
&lt;td&gt;展示音频控制面板&lt;/td&gt;
&lt;td&gt;/&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;loop&lt;/td&gt;
&lt;td&gt;循环播放&lt;/td&gt;
&lt;td&gt;/&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;muted&lt;/td&gt;
&lt;td&gt;静音播放&lt;/td&gt;
&lt;td&gt;/&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;autoplay&lt;/td&gt;
&lt;td&gt;自动播放&lt;/td&gt;
&lt;td&gt;为了提升用户体验，浏览器支持在静音模式下自动播放&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;列表&lt;/h2&gt;
&lt;p&gt;作用：布局内容排列整齐的区域。&lt;/p&gt;
&lt;h3&gt;无序列表&lt;/h3&gt;
&lt;p&gt;作用：布局排列整齐的不需要规定顺序的区域。&lt;/p&gt;
&lt;p&gt;标签：ul嵌套li，ul是无序列表，li是列表条目。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;ul&amp;gt;
	&amp;lt;li&amp;gt;第一项&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;第二项&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;第三项&amp;lt;/li&amp;gt;
    ......
&amp;lt;/ul&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ul标签里面只能包裹li标签&lt;/li&gt;
&lt;li&gt;li标签里面可以包裹任何内容&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;有序列表&lt;/h3&gt;
&lt;p&gt;作用：布局排列整齐的需要规定顺序的区域。&lt;/p&gt;
&lt;p&gt;标签：ol嵌套li，ol是有序列表，li是列表条目。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;ol&amp;gt;
	&amp;lt;li&amp;gt;第一项&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;第二项&amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;第三项&amp;lt;/li&amp;gt;
    ......
&amp;lt;/ol&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ol标签里面只能包裹li标签&lt;/li&gt;
&lt;li&gt;li标签里面可以包裹任何内容&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;定义列表&lt;/h3&gt;
&lt;p&gt;标签：dl嵌套dt和dd，dl是定义列表，dt是定义列表的标题，dd是定义列表的描述/详情。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;dl&amp;gt;
	&amp;lt;dt&amp;gt;列表标题&amp;lt;/dt&amp;gt;
	&amp;lt;dd&amp;gt;列表描述/详情&amp;lt;/dd&amp;gt;
    ......
&amp;lt;/dl&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;dl标签里面只能包裹li标签&lt;/li&gt;
&lt;li&gt;dt和dd标签里面可以包裹任何内容&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;表格&lt;/h2&gt;
&lt;h3&gt;表格标签&lt;/h3&gt;
&lt;p&gt;网页中的表格与Excel表格类似，用来展示数据。&lt;/p&gt;
&lt;p&gt;标签:table嵌套tr，tr嵌套td / th。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;标签名&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;table&lt;/td&gt;
&lt;td&gt;表格&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tr&lt;/td&gt;
&lt;td&gt;行&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;td&lt;/td&gt;
&lt;td&gt;表头单元格&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;th&lt;/td&gt;
&lt;td&gt;内容单元格&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;提示：在网页中，表格默认没有边框线，使用border属性可以为表格添加边框线。&lt;/p&gt;
&lt;h3&gt;表格结构标签&lt;/h3&gt;
&lt;p&gt;作用：用表格结构标签把内容划分区域，让表格结构更清晰，语义更清晰。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;标签名&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;th&gt;特殊说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;thead&lt;/td&gt;
&lt;td&gt;表格头部&lt;/td&gt;
&lt;td&gt;表格头部内容&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tbody&lt;/td&gt;
&lt;td&gt;表格主体&lt;/td&gt;
&lt;td&gt;主要内容区域&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;tfoot&lt;/td&gt;
&lt;td&gt;表格底部&lt;/td&gt;
&lt;td&gt;汇总信息区域&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;合并单元格&lt;/h3&gt;
&lt;p&gt;作用：将多个单元格合并成一个单元格，以合并同类信息&lt;/p&gt;
&lt;p&gt;跨行合并&lt;br /&gt;
跨列合并&lt;/p&gt;
&lt;p&gt;合并单元格的步骤：&lt;/p&gt;
&lt;p&gt;1．明确合并的目标&lt;/p&gt;
&lt;p&gt;2．保留&lt;strong&gt;最左最上&lt;/strong&gt;的单元格，添加属性（&lt;strong&gt;取值是数字，表示需要合并的单元格数量&lt;/strong&gt;）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;跨行合并，保留&lt;strong&gt;最上&lt;/strong&gt;单元格，添加属性&lt;strong&gt;rowspan&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;跨列合并，保留&lt;strong&gt;最左&lt;/strong&gt;单元格，添加属性&lt;strong&gt;colspan&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;3．删除其他单元格&lt;/p&gt;
&lt;h2&gt;表单&lt;/h2&gt;
&lt;p&gt;input 标签的 type 属性不同，则功能不同&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;type属性值&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;td&gt;文本框，用于输入单行文本&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;password&lt;/td&gt;
&lt;td&gt;密码框&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;radio&lt;/td&gt;
&lt;td&gt;单选框&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;checkbox&lt;/td&gt;
&lt;td&gt;多选框&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;file&lt;/td&gt;
&lt;td&gt;上传文件&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;input 标签的占位文本：即为提示信息&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;input type=&quot;xxx&quot; placeholder=&quot;提示信息&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;单选框 radio&lt;/h3&gt;
&lt;p&gt;常用属性&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;name&lt;/td&gt;
&lt;td&gt;控件名称&lt;/td&gt;
&lt;td&gt;控件功能，同时只能选中一个（单选功能）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;checked&lt;/td&gt;
&lt;td&gt;默认选中&lt;/td&gt;
&lt;td&gt;属性名和属性值相同，简写为一个单词&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;上传文件 file&lt;/h3&gt;
&lt;p&gt;常用属性&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;multiple&lt;/td&gt;
&lt;td&gt;多选文件&lt;/td&gt;
&lt;td&gt;多选文件上传&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;多选框 checkedbox&lt;/h3&gt;
&lt;p&gt;常用属性&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;checked&lt;/td&gt;
&lt;td&gt;默认选中&lt;/td&gt;
&lt;td&gt;属性名和属性值相同，简写为一个单词&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;下拉菜单&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;标签：select嵌套option，select是下拉菜单整体，option是下拉菜单的每一项。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;默认选中：selected&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;文本域&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;标签：textarea，双标签。&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;不用于input的text，这个会自动换行&lt;/li&gt;
&lt;li&gt;有拖拽功能，一般会禁用&lt;/li&gt;
&lt;li&gt;一般使用css设置尺寸&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h3&gt;label 标签&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;作用：网页中，某个标签的说明文本&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;经验：用label标签绑定文字和表单控件的关系，增大表单控件的点击范围。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;示例：增大单选框点击范围&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;写法1&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;label标签只包裹内容，不包裹表单控件&lt;/li&gt;
&lt;li&gt;设置label标签的for属性值和表单控件的id属性值相同&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;input type=&quot;radio&quot; id=&quot;man&quot;&amp;gt;
&amp;lt;label for=&quot;man&quot;&amp;gt;男&amp;lt;/label&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;写法2&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;使用label标签包裹文字和表单控件，不需要属性&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;label&amp;gt;&amp;lt;input type=&quot;radio&quot;&amp;gt;女&amp;lt;/label&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;注意：&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;outline: none 屬性可取消表单控件的焦点框&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;如何设置 placeholder 的文字属性呢？::placeholder 选中就是 placeholder 属性文字样式&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;按钮标签&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;button type=&quot;&quot;&amp;gt;&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;常用属性（&lt;strong&gt;记得使用form表单标签包裹使button发挥作用&lt;/strong&gt;）&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性名&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;submit&lt;/td&gt;
&lt;td&gt;提交按钮，点击后可以提交数据到后台（默认功能）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;reset&lt;/td&gt;
&lt;td&gt;重置按钮，点击后可以将表单控件恢复默认值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;button&lt;/td&gt;
&lt;td&gt;普通按钮，默认没有功能，一般配合JavaScript使用&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;无语义的布局标签&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;作用：&lt;strong&gt;布局网页&lt;/strong&gt;（划分网页区域，摆放内容）
&lt;ul&gt;
&lt;li&gt;div（默认换行展示）大盒子&lt;/li&gt;
&lt;li&gt;span（不换行）小盒子&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;字符实体&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;作用：在网页中显示预留字符&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;显示结果&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;th&gt;实体名称&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;\&amp;amp;nbsp&lt;/td&gt;
&lt;td&gt;空格&lt;/td&gt;
&lt;td&gt;不换行空格&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;\&amp;lt;&lt;/td&gt;
&lt;td&gt;小于&lt;/td&gt;
&lt;td&gt;&amp;lt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;gt;&lt;/td&gt;
&lt;td&gt;大于&lt;/td&gt;
&lt;td&gt;&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;在代码中敲的键盘的空格，在网页中只会被识别成一个空格&lt;/strong&gt;&lt;/p&gt;
</content:encoded></item><item><title>SSM07-SpringMVC的请求与响应</title><link>https://wang-nine.github.io/posts/10307/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10307/</guid><description>详细的描述了SpringMVC中的请求与响应</description><pubDate>Sat, 03 Jun 2023 11:00:00 GMT</pubDate><content:encoded>&lt;p&gt;详细的描述了SpringMVC中的请求与响应&lt;/p&gt;
&lt;h1&gt;SpringMVC概述&lt;/h1&gt;
&lt;p&gt;SpringMVC是一种基于Java实现MVC模型的轻量级web框架&lt;/p&gt;
&lt;p&gt;优点&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用简单，开发便捷（相比于Servlet）&lt;/li&gt;
&lt;li&gt;灵活性强&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;样例&lt;/h1&gt;
&lt;h2&gt;样例代码&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;使用SpringMVC技术需要先导入SpringMIVC坐标与Servlet坐标&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;javax.servlet&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;javax.servlet-api&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;3.1.0&amp;lt;/version&amp;gt;
  &amp;lt;scope&amp;gt;provided&amp;lt;/scope&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;org.springframework&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;spring-webmvc&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;5.2.10.RELEASE&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;创建SpringMvc控制器类（等同于servlet功能）&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;// 2.定义Controller
// 2.1 使用Controller定义Bean
@Controller
public class UserController {
    // 2.2 设置当前操作的访问路径
    @RequestMapping(&quot;/save&quot;)
    // 2.3 设置当前操作的返回值类型
    @ResponseBody
    public String save(){
        System.out.println(&quot;user save...&quot;);
        return &quot;{&apos;moudle&apos; : &apos;springmvc&apos;}&quot;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;初始化SpringMVC环境(同Spring环境)，设定SpringMVC加载对应的bean&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;// 3.创建springmvc的配置文件，加载controller对应的Bean
@Configuration
@ComponentScan(&quot;com.wang.controller&quot;)
public class SpringMvcConfig {
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;初始化Servlet容器，加载SpringMVC环境，并设置SpringMVC技术处理的请求&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;// 4.定义一个Servlet容器启动的配置类，在里面加载spring的配置
public class ServletContainerInitConfig extends AbstractDispatcherServletInitializer {

    //加载springmvc容器配置
    protected WebApplicationContext createServletApplicationContext() {
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(SpringMvcConfig.class);
        return ctx;
    }

    //设置哪些请求归属springmvc处理
    protected String[] getServletMappings() {
        return new String[]{&quot;/&quot;};
    }

    //加载spring容器配置
    protected WebApplicationContext createRootApplicationContext() {
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(SpringConfig.class);
        return ctx;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上述案例中用到的注解说明：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;@Controller&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;类注解，放置于SpringMVC控制器定义上方，作用为设定SpringMVC的核心控制器Bean&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;@RequestMapping&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;方法注解，放置于SpringMVC控制器方法上方，作用为设定当前控制器方法的请求访问路径&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;@ResponseBody&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;方法注解，放置于SpringMVC控制器方法上方，作用为设置当前控制器方法的响应内容为当前返回值，无需解析&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;@ComponentScan&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;属性1：excludeFilters：排除扫描路径中加载的bean，需要指定类别(type)与具体项(classes)&lt;/p&gt;
&lt;p&gt;属性2：includeFilters：加载指定的bean，需要指定类别(type)与具体项(classes)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;SpringMVC入门程序开发总结（1+N）&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;一次性工作
&lt;ul&gt;
&lt;li&gt;创建工程，设置服务器，加载工程&lt;/li&gt;
&lt;li&gt;导入坐标&lt;/li&gt;
&lt;li&gt;创建web容器启动类，加载SpringMVC配置，并设置SpringMVC请求拦截路径&lt;/li&gt;
&lt;li&gt;SpringMvc核心配置类（设置配置类，扫描controller包，加载Controller控制器bean）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;多次工作
&lt;ul&gt;
&lt;li&gt;定义处理请求的控制器类&lt;/li&gt;
&lt;li&gt;定义处理请求的控制器方法，并配置映射路径（@RequestMapping）与返回json数据（@ResponseBody）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;工作流程&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;启动服务器初始化过程&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;服务器启动，执行ServletcontainersInitConfig类，初始化web容器&lt;/li&gt;
&lt;li&gt;执行createServletApplicationcontext方法，创建了webApplicationContext对象&lt;/li&gt;
&lt;li&gt;加载SpringMvcConfig&lt;/li&gt;
&lt;li&gt;执行@ComponentScan加载对应的bean&lt;/li&gt;
&lt;li&gt;加载UserController，每个@RequestMapping的名称对应一个具体的方法&lt;/li&gt;
&lt;li&gt;执行getServletMappings方法，定义所有的请求都通过springMVC&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;单次请求过程&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;发送请求localhost/save&lt;/li&gt;
&lt;li&gt;web容器发现所有请求都经过SpringMVC，将请求交给SpringMVC处理&lt;/li&gt;
&lt;li&gt;解析请求路径/save&lt;/li&gt;
&lt;li&gt;由/save匹配执行对应的方法save()&lt;/li&gt;
&lt;li&gt;执行save()&lt;/li&gt;
&lt;li&gt;检测到有@ResponseBody直接将save()方法的返回值作为响应求体返回给请求方&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;问题&lt;/h2&gt;
&lt;p&gt;因为功能不同，如何避免Spring错误的加载到SpringMVC的bean？&lt;/p&gt;
&lt;p&gt;加载Spring控制的bean的时候排除掉SpringMVC控制的bean&lt;/p&gt;
&lt;p&gt;SpringMIVC相关bean(表现层bean)&lt;/p&gt;
&lt;p&gt;Spring控制的bean&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;业务bean (Service)&lt;/li&gt;
&lt;li&gt;功能bean (DataSource等)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;SpringMVC相关bean加载控制&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SpringMVC加载的bean对应的包均在com.xxx.controller包内&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Spring相关bean加载控制&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;方式一：Spring加载的bean设定扫描范围为com.xxx，排除掉controller包内的bean&lt;/li&gt;
&lt;li&gt;方式二： Spring加载的bean设定扫描范围为精准范围，例如service包、dao包等&lt;/li&gt;
&lt;li&gt;方式三：不区分Spring与SpringMVC的环境，加载到同一个环境中&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;方式一如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@ComponentScan(value = &quot;com.wang&quot;,
        excludeFilters = @ComponentScan.Filter(
                type = FilterType.ANNOTATION,
                classes = Controller.class
        ))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;方式二如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@ComponentScan({&quot;com.wang.service&quot;, &quot;com.wang.dao&quot;})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意：简化开发代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class ServletContainerInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class&amp;lt;?&amp;gt;[] getRootConfigClasses() {
        return new Class[]{SpringConfig.class};
    }

    @Override
    protected Class&amp;lt;?&amp;gt;[] getServletConfigClasses() {
        return new Class[]{SpringMvcConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{&quot;/&quot;};
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;请求&lt;/h1&gt;
&lt;h2&gt;设置请求的映射路径&lt;/h2&gt;
&lt;p&gt;团队多人开发，每人设置不同的请求路径，冲突问题如何解决？&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;设置模块名作为请求路径前缀&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;@RequestMapping(“/xxx”)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;当写在类上方时，即为统计设置请求路径的前缀，类方法中的@RequestMapping会自动加上前缀&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;PostMan发送get请求和post请求&lt;/p&gt;
&lt;p&gt;get请求&lt;/p&gt;
&lt;p&gt;普通参数：url地址传参，地址参数名与形参变量名相同，定义形参即可接收参数&lt;/p&gt;
&lt;p&gt;post请求&lt;/p&gt;
&lt;p&gt;普通参数：form表单post请求传参，表单参数名与形参变量名相同，定义形参即可接收参数&lt;/p&gt;
&lt;p&gt;post请求中文乱码问题：&lt;/p&gt;
&lt;p&gt;为web容器添加过滤器并指定字符集，Spring-web包中提供了专用的字符过滤器&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//乱码处理
@override
protected Filter[] getServletFilters(){
	CharacterEncodingFilter filter = new CharacterEncodingFilter();
    filter.setEncoding(&quot;UTF-8&quot;);
	return new Filter[](filter);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;参数种类&lt;/h2&gt;
&lt;p&gt;若传入的属性名与实体类中的属性名一致，则会自动放入实体类中&lt;/p&gt;
&lt;p&gt;样例代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//请求参数
@Controller
public class UserController {

    //普通参数：请求参数与形参名称对应即可完成参数传递
    @RequestMapping(&quot;/commonParam&quot;)
    @ResponseBody
    public String commonParam(String name ,int age){
        System.out.println(&quot;普通参数传递 name ==&amp;gt; &quot;+name);
        System.out.println(&quot;普通参数传递 age ==&amp;gt; &quot;+age);
        return &quot;{&apos;module&apos;:&apos;common param&apos;}&quot;;
    }

    //普通参数：请求参数名与形参名不同时，使用@RequestParam注解关联请求参数名称与形参名称之间的关系
    @RequestMapping(&quot;/commonParamDifferentName&quot;)
    @ResponseBody
    public String commonParamDifferentName(@RequestParam(&quot;name&quot;) String userName , int age){
        System.out.println(&quot;普通参数传递 userName ==&amp;gt; &quot;+userName);
        System.out.println(&quot;普通参数传递 age ==&amp;gt; &quot;+age);
        return &quot;{&apos;module&apos;:&apos;common param different name&apos;}&quot;;
    }

    //POJO参数：请求参数与形参对象中的属性对应即可完成参数传递
    @RequestMapping(&quot;/pojoParam&quot;)
    @ResponseBody
    public String pojoParam(User user){
        System.out.println(&quot;pojo参数传递 user ==&amp;gt; &quot;+user);
        return &quot;{&apos;module&apos;:&apos;pojo param&apos;}&quot;;
    }

    //嵌套POJO参数：嵌套属性按照层次结构设定名称即可完成参数传递
    @RequestMapping(&quot;/pojoContainPojoParam&quot;)
    @ResponseBody
    public String pojoContainPojoParam(User user){
        System.out.println(&quot;pojo嵌套pojo参数传递 user ==&amp;gt; &quot;+user);
        return &quot;{&apos;module&apos;:&apos;pojo contain pojo param&apos;}&quot;;
    }

    //数组参数：同名请求参数可以直接映射到对应名称的形参数组对象中
    @RequestMapping(&quot;/arrayParam&quot;)
    @ResponseBody
    public String arrayParam(String[] likes){
        System.out.println(&quot;数组参数传递 likes ==&amp;gt; &quot;+ Arrays.toString(likes));
        return &quot;{&apos;module&apos;:&apos;array param&apos;}&quot;;
    }

    //集合参数：同名请求参数可以使用@RequestParam注解映射到对应名称的集合对象中作为数据
    @RequestMapping(&quot;/listParam&quot;)
    @ResponseBody
    public String listParam(@RequestParam List&amp;lt;String&amp;gt; likes){
        System.out.println(&quot;集合参数传递 likes ==&amp;gt; &quot;+ likes);
        return &quot;{&apos;module&apos;:&apos;list param&apos;}&quot;;
    }


    //集合参数：json格式
    //1.开启json数据格式的自动转换，在配置类中开启@EnableWebMvc
    //2.使用@RequestBody注解将外部传递的json数组数据映射到形参的集合对象中作为数据
    @RequestMapping(&quot;/listParamForJson&quot;)
    @ResponseBody
    public String listParamForJson(@RequestBody List&amp;lt;String&amp;gt; likes){
        System.out.println(&quot;list common(json)参数传递 list ==&amp;gt; &quot;+likes);
        return &quot;{&apos;module&apos;:&apos;list common for json param&apos;}&quot;;
    }

    //POJO参数：json格式
    //1.开启json数据格式的自动转换，在配置类中开启@EnableWebMvc
    //2.使用@RequestBody注解将外部传递的json数据映射到形参的实体类对象中，要求属性名称一一对应
    @RequestMapping(&quot;/pojoParamForJson&quot;)
    @ResponseBody
    public String pojoParamForJson(@RequestBody User user){
        System.out.println(&quot;pojo(json)参数传递 user ==&amp;gt; &quot;+user);
        return &quot;{&apos;module&apos;:&apos;pojo for json param&apos;}&quot;;
    }

    //集合参数：json格式
    //1.开启json数据格式的自动转换，在配置类中开启@EnableWebMvc
    //2.使用@RequestBody注解将外部传递的json数组数据映射到形参的保存实体类对象的集合对象中，要求属性名称一一对应
    @RequestMapping(&quot;/listPojoParamForJson&quot;)
    @ResponseBody
    public String listPojoParamForJson(@RequestBody List&amp;lt;User&amp;gt; list){
        System.out.println(&quot;list pojo(json)参数传递 list ==&amp;gt; &quot;+list);
        return &quot;{&apos;module&apos;:&apos;list pojo for json param&apos;}&quot;;
    }

    //日期参数
    //使用@DateTimeFormat注解设置日期类型数据格式，默认格式yyyy/MM/dd
    @RequestMapping(&quot;/dataParam&quot;)
    @ResponseBody
    public String dataParam(Date date,
                            @DateTimeFormat(pattern=&quot;yyyy-MM-dd&quot;) Date date1,
                            @DateTimeFormat(pattern=&quot;yyyy/MM/dd HH:mm:ss&quot;) Date date2){
        System.out.println(&quot;参数传递 date ==&amp;gt; &quot;+date);
        System.out.println(&quot;参数传递 date1(yyyy-MM-dd) ==&amp;gt; &quot;+date1);
        System.out.println(&quot;参数传递 date2(yyyy/MM/dd HH:mm:ss) ==&amp;gt; &quot;+date2);
        return &quot;{&apos;module&apos;:&apos;data param&apos;}&quot;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;h3&gt;普通的形参&lt;/h3&gt;
&lt;p&gt;若形参与请求参数名称相同，则会自动绑定&lt;/p&gt;
&lt;p&gt;@RequestParam&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;可以绑定请求参数与处理器方法形参之间的关系&lt;/p&gt;
&lt;p&gt;参数：required:是否为必传参数 defaultValue:参数默认值&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Pojo参数&lt;/h3&gt;
&lt;p&gt;请求参数名与形参对象属性名相同，定义PO30类型形参即可接收参数&lt;/p&gt;
&lt;h3&gt;嵌套的Pojo参数&lt;/h3&gt;
&lt;p&gt;请求参数名与形参对象属性名相同，按照对象层次结构关系即可接收嵌套POJo属性参数&lt;/p&gt;
&lt;h3&gt;数组参数&lt;/h3&gt;
&lt;p&gt;请求参数名与形参对象属性名相同且请求参数为多个，定义数组类型形参即可接收参数&lt;/p&gt;
&lt;h3&gt;集合保存普通参数&lt;/h3&gt;
&lt;p&gt;请求参数名与形参集合对象名相同且请求参数为多个，@RequestParam绑定参数关系&lt;/p&gt;
&lt;h3&gt;json 数据参数&lt;/h3&gt;
&lt;p&gt;四个步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;pom.xml中添加json坐标&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;com.fasterxml.jackson.core&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;jackson-databind&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;2.9.0&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;主配置类中加入注解&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;@EnableWebMvc&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;开启由json对象装换位对象的功能&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;postman中传入参数&lt;/li&gt;
&lt;li&gt;在参数前面加上@RequestBody&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;json数组&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;[&quot;game&quot;,&quot;music&quot;,&quot;hobby&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;json对象(Pojo)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;{
	&quot;name&quot;:&quot;wang&quot;,
    &quot;age&quot;:15,
	&quot;address&quot;:{
		&quot;province&quot;:&quot;beijing&quot;,
        &quot;city&quot;:&quot;beijing&quot;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;json数组(Pojo)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;[
    {&quot;name&quot;:&quot;wang&quot;,&quot;age&quot;:9},
    {&quot;name&quot;:&quot;nine&quot;,&quot;age&quot;:99}
]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;@RequestBody与@RequestParam区别&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;区别
&lt;ul&gt;
&lt;li&gt;@RequestParam用于接收url地址传参，表单传参【application/x-www-form-urlencoded】&lt;/li&gt;
&lt;li&gt;@RequestBody用于接收json数据【application/json】&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;应用
&lt;ul&gt;
&lt;li&gt;后期开发中，发送json格式数据为主，@RequestBody应用较广&lt;/li&gt;
&lt;li&gt;如果发送非json格式数据，选用@Requestparam接收请求参数&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;日期格式参数&lt;/h2&gt;
&lt;p&gt;@DateTimeFormat(patter=“yyyy-MM-dd”)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;指定日期的格式&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;类型转换器&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Convert接口&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;public interface Converter&amp;lt;S, T&amp;gt; {
    @Nullable
    T convert(S var1);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;响应&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;响应页面&lt;/li&gt;
&lt;li&gt;响应数据
&lt;ul&gt;
&lt;li&gt;文本数据&lt;/li&gt;
&lt;li&gt;json数据&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;样例代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Controller
public class UserController {

    //响应页面/跳转页面
    //返回值为String类型，设置返回值为页面名称，即可实现页面跳转
    @RequestMapping(&quot;/toJumpPage&quot;)
    public String toJumpPage(){
        System.out.println(&quot;跳转页面&quot;);
        return &quot;page.jsp&quot;;
    }

    //响应文本数据
    //返回值为String类型，设置返回值为任意字符串信息，即可实现返回指定字符串信息，需要依赖@ResponseBody注解
    @RequestMapping(&quot;/toText&quot;)
    @ResponseBody
    public String toText(){
        System.out.println(&quot;返回纯文本数据&quot;);
        return &quot;response text&quot;;
    }

    //响应POJO对象
    //返回值为实体类对象，设置返回值为实体类类型，即可实现返回对应对象的json数据，需要依赖@ResponseBody注解和@EnableWebMvc注解
    @RequestMapping(&quot;/toJsonPOJO&quot;)
    @ResponseBody
    public User toJsonPOJO(){
        System.out.println(&quot;返回json对象数据&quot;);
        User user = new User();
        user.setName(&quot;itcast&quot;);
        user.setAge(15);
        return user;
    }

    //响应POJO集合对象
    //返回值为集合对象，设置返回值为集合类型，即可实现返回对应集合的json数组数据，需要依赖@ResponseBody注解和@EnableWebMvc注解
    @RequestMapping(&quot;/toJsonList&quot;)
    @ResponseBody
    public List&amp;lt;User&amp;gt; toJsonList(){
        System.out.println(&quot;返回json集合数据&quot;);
        User user1 = new User();
        user1.setName(&quot;Tom&quot;);
        user1.setAge(15);

        User user2 = new User();
        user2.setName(&quot;Jerry&quot;);
        user2.setAge(12);

        List&amp;lt;User&amp;gt; userList = new ArrayList&amp;lt;User&amp;gt;();
        userList.add(user1);
        userList.add(user2);

        return userList;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;@ResponseBody&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;方法注解，SpringMVC控制器方法定义上方，设置当前控制器方法响应内容为当前返回值，无需解析&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;内部是由类型转换器 HttpMessageConverter 接口发挥作用&lt;/p&gt;
</content:encoded></item><item><title>SSM06-Spring中的事务</title><link>https://wang-nine.github.io/posts/10306/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10306/</guid><description>详细的描述了Spring中的事务概念。</description><pubDate>Sat, 03 Jun 2023 10:00:00 GMT</pubDate><content:encoded>&lt;p&gt;详细的描述了Spring中的事务概念。&lt;/p&gt;
&lt;h1&gt;事务的简介&lt;/h1&gt;
&lt;p&gt;事务作用：在数据层保障一系列的数据库操作同成功同失败&lt;br /&gt;
Spring事务作用：在数据层或业务层保障一系列的数据库操作同成功同失败&lt;/p&gt;
&lt;p&gt;如何开启事务管理？&lt;/p&gt;
&lt;p&gt;步骤如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在业务层接口上添加Spring事务管理&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;public interface xxxxx {
    @Transactional
    public void xxx {
        代码体
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意事项&lt;/strong&gt;：&lt;br /&gt;
Spring注解式事务通常添加在业务层接口中而不会添加到业务层实现类中，降低耦合&lt;br /&gt;
注解式事务可以添加到业务方法上表示当前方法开启事务，也可以添加到接口上表示当前接口所有方法开启事务&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;设置事务管理器&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
	DataSourceTransactionManager ptm = new DataSourceTransactionManager();
    ptm.setDataSource(dataSource);
	return ptm;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;在主配置类中开启事务型驱动
添加如下注解：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;@EnableTransactionmanagement
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Spring事务角色&lt;/h1&gt;
&lt;p&gt;事务角色&lt;/p&gt;
&lt;h2&gt;事务管理员&lt;/h2&gt;
&lt;p&gt;发起事务方，在Spring中通常指代业务层开启事务的方法&lt;/p&gt;
&lt;h2&gt;事务协调员&lt;/h2&gt;
&lt;p&gt;加入事务方，在Spring中通常指代数据层方法，也可以是业务层方法&lt;/p&gt;
&lt;h1&gt;事务属性&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;属性&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;th&gt;实例&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;readOnly&lt;/td&gt;
&lt;td&gt;设置是否为只读事务&lt;/td&gt;
&lt;td&gt;readOnly = true 只读事务&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;timeout&lt;/td&gt;
&lt;td&gt;设置事务的超过时间&lt;/td&gt;
&lt;td&gt;timeout = -1 永不超时&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rollbackFor&lt;/td&gt;
&lt;td&gt;设置事务回滚异常(class)&lt;/td&gt;
&lt;td&gt;rollbackFor = (NullPointException.class)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rollbackForClassName&lt;/td&gt;
&lt;td&gt;设置事务回滚异常(String)&lt;/td&gt;
&lt;td&gt;同上格式为字符串&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;noRollbackFor&lt;/td&gt;
&lt;td&gt;设置事务不回滚异常(class)&lt;/td&gt;
&lt;td&gt;noRollbackFor = (NullPointException.class)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;noRollbackForClassName&lt;/td&gt;
&lt;td&gt;设置事务不回滚异常(String)&lt;/td&gt;
&lt;td&gt;同上格式为字符串&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;propagation&lt;/td&gt;
&lt;td&gt;设置事务传播行为&lt;/td&gt;
&lt;td&gt;…&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;事务传播行为&lt;/strong&gt;：事务协调员对事务管理员所携带事务的处理态度&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/java-ssm/transaction-propagation.svg&quot; alt=&quot;Spring 事务传播的共享事务、独立事务与保存点关系&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：已有事务时，REQUIRED 加入同一个物理事务，REQUIRES_NEW 挂起外层并新建独立事务，NESTED 在支持时使用同一事务内的保存点。没有外层事务时，这三种通常都开启事务；SUPPORTS、MANDATORY、NEVER 与 NOT_SUPPORTED 则分别决定是否加入、是否强制存在或排除事务。传播描述的是调用边界的处理方式，需经过事务代理且由具体事务管理器支持。&lt;/p&gt;
</content:encoded></item><item><title>SSM05-Spring中的AOP概念</title><link>https://wang-nine.github.io/posts/10305/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10305/</guid><description>详细的描述了Spring中的AOP概念。</description><pubDate>Tue, 30 May 2023 13:00:00 GMT</pubDate><content:encoded>&lt;p&gt;详细的描述了Spring中的AOP概念。&lt;/p&gt;
&lt;h1&gt;AOP简介&lt;/h1&gt;
&lt;h2&gt;基础概念&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;AOP(Aspect Oriented Programming)面向切面编程，一种编程范式，指导开发者如何组织程序结构&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;OOP(object Oriented Programming)面向对象编程&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;作用：在不惊动原始设计的基础上为其进行功能增强&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Spring理念：无入侵式/无侵入式&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;核心概念&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;连接点 ( JoinPoint )：程序执行过程中的任意位置，粒度为执行方法、抛出异常、设置变量等&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;在SpringAOP中，理解为方法的执行&lt;/li&gt;
&lt;li&gt;切入点 ( Pointcut )：匹配连接点的式子
&lt;ul&gt;
&lt;li&gt;在springAoP中，一个切入点可以只描述一个具体方法，也可以匹配多个方法
&lt;ul&gt;
&lt;li&gt;一个具体方法: com.itheima.dao包下的BookDao接口中的无形参无返回值的save方法&lt;/li&gt;
&lt;li&gt;匹配多个方法:所有的save方法，所有的get开头的方法，所有以Dao结尾的接口中的任意方法，所有带有一个参数的方法&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;通知(Advice) ：在切入点处执行的操作，也就是共性功能&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;在SpringAOP中，功能最终以方法的形式呈现&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;通知类∶定义通知的类&lt;/li&gt;
&lt;li&gt;切面( Aspect )︰描述通知与切入点的对应关系&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;样例（源自黑马程序员，&lt;strong&gt;侵删&lt;/strong&gt;）&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/java-ssm/aop-concepts.svg&quot; alt=&quot;切面通过代理将通知组合到目标方法调用&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：连接点是可匹配的方法执行，切入点描述哪些方法需要增强，通知实现要加入的共性功能，切面把二者绑定。调用者使用容器提供的代理，代理组织通知与目标方法的执行，目标对象仍保留原始业务职责。是否经过代理是重要边界，同一对象内部的 this 调用不会自动再次触发代理增强。&lt;/p&gt;
&lt;h1&gt;AOP入门案例&lt;/h1&gt;
&lt;p&gt;案例设定:测定接口执行效率&lt;br /&gt;
简化设定:在接口执行前输出当前系统时间&lt;br /&gt;
开发模式:XML or &lt;strong&gt;注解&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;思路分析∶&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;导入坐标（ pom.xml )&lt;/li&gt;
&lt;li&gt;制作连接点方法（原始操作，Dao接口与实现类）&lt;/li&gt;
&lt;li&gt;制作共性功能（通知类与通知)&lt;/li&gt;
&lt;li&gt;定义切入点&lt;/li&gt;
&lt;li&gt;绑定切入点与通知关系（切面)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;基础代码样例（源自黑马程序员）：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;导入坐标&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;org.springframework&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;spring-context&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;5.2.10.RELEASE&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;org.aspectj&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;aspectjweaver&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;1.9.4&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;切面类&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;@Component
@Aspect
public class MyAdvice {

    @Pointcut(&quot;execution(切入点表达式)&quot;)
    private void pt(){}

    @Around(&quot;pt()&quot;)
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println(&quot;------------------------------&quot;);
        Long startTime = System.currentTimeMillis();
        for (int i = 0 ; i&amp;lt;10000 ; i++) {
            //调用原始操作
            pjp.proceed();
        }
        Long endTime = System.currentTimeMillis();
        Long totalTime = endTime-startTime;
        System.out.println(&quot;执行万次消耗时间：&quot; + totalTime + &quot;ms&quot;);
        return null;
    }

}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;主配置类&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;加上注解 @EnableAspectJAutoProxy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;剩余的代码不变，例如Dao接口和实现类，主函数&lt;/p&gt;
&lt;p&gt;以上步骤总结：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;导入AOP坐标&lt;/li&gt;
&lt;li&gt;定义dao接口与实现类&lt;/li&gt;
&lt;li&gt;定义通知类，制作通知&lt;/li&gt;
&lt;li&gt;定义切入点 &lt;strong&gt;说明︰切入点定义依托一个不具有实际意义的方法进行，即无参数，无返回值，方法体无实际逻辑&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;绑定切入点与通知关系，并指定通知添加到原始连接点的具体执行&lt;strong&gt;位置&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;定义通知类受Spring容器管理，并定义当前类为切面类，即添加 &lt;strong&gt;@Component&lt;/strong&gt; 和 &lt;strong&gt;@Aspect&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;开启Spring对AOP注解驱动支持，即在主配置类添加 &lt;strong&gt;@EnableAspectJAutoProxy&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;AOP工作流程&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;Spring容器启动&lt;/li&gt;
&lt;li&gt;读取所有切面配置中的切入点&lt;/li&gt;
&lt;li&gt;初始化bean，判定bean对应的类中的方法是否匹配到任意切入点&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;匹配失败：创建对象&lt;/li&gt;
&lt;li&gt;匹配成功：创建原始对象（&lt;strong&gt;目标对象&lt;/strong&gt;）的&lt;strong&gt;代理对象&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;获取bean的执行方法&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;获取bean，调用方法并执行，完成操作&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;获取的bean是代理对象时，根据代理对象的运行模式运行原始方法与增强的内容，完成操作&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;目标对象（Target ）：原始功能去掉共性功能对应的类产生的对象，这种对象是无法直接完成最终工作的&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;代理（Proxy）：目标对象无法直接完成工作，需要对其进行功能回填，通过原始对象的代理对象实现&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;AOP深入&lt;/h1&gt;
&lt;h2&gt;AOP的切入点表达式&lt;/h2&gt;
&lt;p&gt;切入点∶要进行增强的方法&lt;br /&gt;
切入点表达式∶要进行增强的方法的描述方式&lt;/p&gt;
&lt;h3&gt;语法格式&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;即可以对接口进行描述，也可以对实现类进行描述&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;切入点表达式标准格式&lt;/h4&gt;
&lt;p&gt;动作关键字（访问修饰符 返回值.包名.类/接口名.方法名（参数）异常名)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;execution (public User com.xxx.service.UserService.findById (int))&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;动作关键字∶描述切入点的行为动作，例如execution表示执行到指定切入点&lt;/li&gt;
&lt;li&gt;访问修饰符：public , private等，可以省略&lt;/li&gt;
&lt;li&gt;返回值&lt;/li&gt;
&lt;li&gt;包名&lt;/li&gt;
&lt;li&gt;类/接口名&lt;/li&gt;
&lt;li&gt;方法名&lt;/li&gt;
&lt;li&gt;参数&lt;/li&gt;
&lt;li&gt;异常名：方法定义中抛出指定异常，可以省略&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;可以使用通配符描述切入点，快速描述&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;ul&gt;
&lt;li&gt;︰单个独立的任意符号，可以独立出现，也可以作为前缀或者后缀的匹配符出现&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;execution (public &lt;em&gt;com.xxx.&lt;/em&gt;.UserService.find*(*))&lt;br /&gt;
匹配com.xxx包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;.. ：多个连续的任意符号，可以独立出现，常用于简化包名与参数的书写
&lt;blockquote&gt;
&lt;p&gt;execution (public User com..UserService.findById (..) )&lt;br /&gt;
匹配com包下的任意包中的UserService类或接口中所有名称为findByld的方法&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/li&gt;
&lt;li&gt;+︰专用于匹配子类类型&lt;br /&gt;
execution(..&lt;em&gt;Service+.&lt;/em&gt;(..))&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;切入点表达式的书写技巧&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;所有代码按照标准规范开发，否则以下技巧全部失效&lt;/li&gt;
&lt;li&gt;描述切入点&lt;strong&gt;通常描述接口&lt;/strong&gt;，而不描述实现类&lt;/li&gt;
&lt;li&gt;访问控制修饰符针对接口开发均采用public描述（&lt;strong&gt;可省略访问控制修饰符描述&lt;/strong&gt;）&lt;/li&gt;
&lt;li&gt;返回值类型对于增删改类使用精准类型加速匹配，对于查询类使用*通配快速描述&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;包名&lt;/strong&gt;书写&lt;strong&gt;尽量不使用..匹配&lt;/strong&gt;，效率过低，常用*做单个包描述匹配，或精准匹配&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;接口名&lt;/strong&gt;/类名书写名称与模块相关的&lt;strong&gt;采用*匹配&lt;/strong&gt;，例如UserService书写成*Service，绑定业务层接口名&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;方法名&lt;/strong&gt;书写以&lt;strong&gt;动词进行精准匹配&lt;/strong&gt;，名词采用&lt;em&gt;匹配，例如getByld书写成getBy&lt;/em&gt;,selectAll书写成selectAll&lt;/li&gt;
&lt;li&gt;参数规则较为复杂，根据业务方法灵活调整&lt;/li&gt;
&lt;li&gt;通常&lt;strong&gt;不使用异常作为匹配规则&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;AOP通知类型&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;AOP通知描述了抽取的共性功能，根据共性功能抽取的位置不同，最终运行代码时要将其加入到合理的位置AOP通知共分为5种类型&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;前置通知 &lt;strong&gt;@Before&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;后置通知 &lt;strong&gt;@After&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;环绕通知（&lt;strong&gt;重点&lt;/strong&gt;） &lt;strong&gt;@Around****需要对原始操作进行调用&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;返回后通知（了解） &lt;strong&gt;@AfterReturning&lt;/strong&gt; 正常运行完才会运行&lt;/li&gt;
&lt;li&gt;抛出异常后通知（了解） &lt;strong&gt;@AfterThrowing&lt;/strong&gt; 抛出异常才会运行&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;环绕通知对原始操作的调用样例（&lt;strong&gt;注意有返回值时需要返回原始操作的返回值&lt;/strong&gt;）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println(&quot;around before advice ...&quot;);
        Object ret = pjp.proceed();
        System.out.println(&quot;around after advice ...&quot;);
        return ret;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;@Around注意事项&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用，进而实现原始方法调用前后同时添加通知&lt;/li&gt;
&lt;li&gt;通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行&lt;/li&gt;
&lt;li&gt;对原始方法的调用可以不接收返回值，通知方法设置成void即可，如果接收返回值，必须设定为Object类型&lt;/li&gt;
&lt;li&gt;原始方法的返回值如果是void类型，通知方法的返回值类型可以设置成void，也可以设置成Object&lt;/li&gt;
&lt;li&gt;由于无法预知原始方法运行后是否会抛出异常，因此环绕通知方法必须抛出Throwable对象&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;signature signature = pjp.getsignature();	//获取代理类和被代理类的信息。
system.out.println(signature.getDeclaringTypeName());	//获取原始方法的类名
system.out.println(signature.getName());	//获取原始方法的方法名
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;AOP通知获取数据&lt;/h1&gt;
&lt;h2&gt;获取参数&lt;/h2&gt;
&lt;p&gt;获取切入点方法的参数&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JoinPoint :适用于前置、后置、返回后、抛出异常后通知&lt;/li&gt;
&lt;li&gt;ProceedJointPoint :适用于环绕通知&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;获取返回值&lt;/h2&gt;
&lt;p&gt;获取切入点方法返回值&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;返回后通知&lt;/li&gt;
&lt;li&gt;环绕通知&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;获取异常&lt;/h2&gt;
&lt;p&gt;获取切入点方法运行异常信息&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;抛出异常后通知&lt;/li&gt;
&lt;li&gt;环绕通知&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;样例代码如下（来自黑马程序员，侵删）&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/java-ssm/aop-data.svg&quot; alt=&quot;AOP 通知获取参数、返回值和异常的分支&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：普通通知可通过 JoinPoint 读取方法参数，环绕通知使用 ProceedingJoinPoint，并以 proceed() 调用后续执行链。正常返回与抛出异常是不同分支，分别由返回后通知和异常后通知接收数据；环绕通知若接收了原始结果，应把结果返回给调用者。图中不规定多个切面的执行次序。&lt;/p&gt;
</content:encoded></item><item><title>MySQL05-SQL优化</title><link>https://wang-nine.github.io/posts/10407/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10407/</guid><description>详细的描述了MySQL中的SQL语句的优化。</description><pubDate>Mon, 29 May 2023 07:00:00 GMT</pubDate><content:encoded>&lt;p&gt;详细的描述了MySQL中的SQL语句的优化。&lt;/p&gt;
&lt;h1&gt;MySQL语句优化&lt;/h1&gt;
&lt;h2&gt;插入数据优化&lt;/h2&gt;
&lt;h3&gt;批量插入&lt;/h3&gt;
&lt;blockquote&gt;
&lt;h1&gt;客户端连接服务端时，加上参数&lt;/h1&gt;
&lt;p&gt;—local-infilemysql —local-infile -u root -p&lt;/p&gt;
&lt;h1&gt;设置全局参数local_infile为，开启从本地加载文件导入数据的开关&lt;/h1&gt;
&lt;p&gt;set global local_infile = 1;&lt;/p&gt;
&lt;h1&gt;执行load指令将准备好的数据，加载到表结构中&lt;/h1&gt;
&lt;p&gt;load data local infile ‘/root/sql1.log’ into table ‘table’ fields terminated by ‘,’ lines terminated by ‘\n’;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;手动提交事务&lt;/h3&gt;
&lt;h3&gt;主键顺序插入&lt;/h3&gt;
&lt;p&gt;主键顺序插入性能高于乱序插入&lt;/p&gt;
&lt;h2&gt;主键优化&lt;/h2&gt;
&lt;h3&gt;数据组织方式&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;在InnoDB存储引擎中，表数据都是根据主键顺序组织存放的，这种存储方式的表称为索引组织表(index organized table IOT)。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;页分裂&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;页可以为空，也可以填充一半，也可以填充100%。每个页包含了2-N行数据(如果一行数据多大，会行溢出)，根据主键排列。&lt;br /&gt;
主键乱序插入&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;页合并&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;当删除一行记录时，实际上记录并没有被物理删除，只是记录被标记（flaged)为删除并且它的空间变得允许被其他记录声明使用。&lt;br /&gt;
当页中删除的记录达到MERGE_THRESHOLD(默认为页的50%)，InnoDB会开始寻找最靠近的页（前或后）看看是否可以将两个页合并以优化空间使用。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;主键设计原则&lt;/h3&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;满足业务需求的情况下，尽量降低主键的长度。&lt;/li&gt;
&lt;li&gt;插入数据时，尽量选择顺序插入，选择使用AUTO_INCREMENT自增主键。&lt;/li&gt;
&lt;li&gt;尽量不要使用UUID做主键或者是其他自然主键，如身份证号。&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2&gt;order by 语句优化&lt;/h2&gt;
&lt;h3&gt;Using flesort&lt;/h3&gt;
&lt;p&gt;通过表的索引或全表扫描，读取满足条件的数据行，然后在排序缓冲区sort buffer中完成排序操作，所有不是通过索引直接返回排序结果的排序都叫FileSort排序。&lt;/p&gt;
&lt;h3&gt;Using index&lt;/h3&gt;
&lt;p&gt;通过有序索引顺序扫描直接返回有序数据，这种情况即为using index，不需要额外排序，操作效率高。&lt;/p&gt;
&lt;h3&gt;优化原则&lt;/h3&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;根据排序字段建立合适的索引，多字段排序时，也遵循最左前缀法则。&lt;/li&gt;
&lt;li&gt;尽量使用覆盖索引。&lt;/li&gt;
&lt;li&gt;多字段排序,一个升序一个降序，此时需要注意联合索引在创建时的规则(ASC/DESC)。&lt;/li&gt;
&lt;li&gt;如果不可避免的出现filesort，大数据量排序时，可以适当增大排序缓冲区大小 sort_buffer_size(默认256k)。&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2&gt;group by 语句优化&lt;/h2&gt;
&lt;h3&gt;优化原则&lt;/h3&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;在分组操作时，可以通过索引来提高效率。&lt;/li&gt;
&lt;li&gt;分组操作时，索引的使用也是满足最左前缀法则的。&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;h2&gt;limit 语句优化&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;一个常见又非常头疼的问题就是limit 20000000,10，此时需要MySQL排序前2000010记录，仅仅返回2000000 - 2000010的记录，其他记录丢弃，查询排序的代价非常大。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;优化原则&lt;/h3&gt;
&lt;p&gt;一般分页查询时，通过创建覆盖索引能够比较好地提高性能，可以通过覆盖索引加子查询形式进行优化。&lt;/p&gt;
&lt;h2&gt;count 语句优化&lt;/h2&gt;
&lt;p&gt;MylSAM引擎把二个表的总行数存在了磁盘上，因此执行count(&lt;em&gt;)的时候会直接返回这个数，效率很高&lt;br /&gt;
InnoDB引擎就麻烦了，它执行count(&lt;/em&gt;)的时候，需要把数据一行一行地从引擎里面读出来，然后累积计数&lt;/p&gt;
&lt;h3&gt;优化原则：自己计数&lt;/h3&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;count (主键)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;InnoDB引擎会遍历整张表，把每一行的主键id值都取出来，返回给服务层。服务层拿到主键后，直接按行进行累加(主键不可能为null)&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;count (字段)
没有not null约束: InnoDB引擎会遍历整张表把每一行的字段值都取出来，返回给服务层，服务层判断，不为null，计数累加。有not null约束: InnoDB引擎会遍历整张表把每一行的字段值都取出来，返回给服务层，直接按行进行累加。&lt;/li&gt;
&lt;li&gt;count (1)
InnoDB引擎遍历整张表，但不取值。服务层对于返回的每一行，放一个数字“1”进去，直接按行进行累加。&lt;/li&gt;
&lt;li&gt;count (*)
lnnoDB引擎并不会把全部字段取出来，而是专门做了优化，不取值，服务层直接按行进行累加。&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;按照效率排序的话，count(字段):xcount(主键id)&amp;lt; count(1) = count(&lt;em&gt;)，所以尽量使用count(&lt;/em&gt;)。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;update 语句优化(避免行级锁变为表级锁)&lt;/h2&gt;
&lt;h3&gt;优化原则&lt;/h3&gt;
&lt;p&gt;InnoDB的行锁是针对索引加的锁，不是针对记录加的锁,并且该索引不能失效，否则会从行锁升级为表锁。&lt;/p&gt;
&lt;h1&gt;MySQL优化总结&lt;/h1&gt;
&lt;h3&gt;插入数据&lt;/h3&gt;
&lt;p&gt;insert:批量插入、手动控制事务、主键顺序插入&lt;br /&gt;
大批量插入: load data local infile&lt;/p&gt;
&lt;h3&gt;主键优化&lt;/h3&gt;
&lt;p&gt;主键长度尽量短、顺序插入&lt;br /&gt;
AUTO_INCREMENT &lt;s&gt;UUID&lt;/s&gt;&lt;/p&gt;
&lt;h3&gt;order by优化&lt;/h3&gt;
&lt;p&gt;using index:直接通过索引返回数据，性能高&lt;br /&gt;
using filesort:需要将返回的结果在排序缓冲区排序&lt;/p&gt;
&lt;h3&gt;group by优化&lt;/h3&gt;
&lt;p&gt;索引，多字段分组满足最左前缀法则&lt;/p&gt;
&lt;h3&gt;limit优化&lt;/h3&gt;
&lt;p&gt;覆盖索引+子查询&lt;/p&gt;
&lt;h3&gt;count优化&lt;/h3&gt;
&lt;p&gt;性能: count(字段) &amp;lt; count(主键id) &amp;lt; count(1) &amp;lt; count(*)&lt;/p&gt;
&lt;h3&gt;update优化&lt;/h3&gt;
&lt;p&gt;尽量根据主键/索引字段进行数据更新&lt;/p&gt;
</content:encoded></item><item><title>SSM04-Spring整合MyBatis</title><link>https://wang-nine.github.io/posts/10304/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10304/</guid><description>详细的描述了Spring整合myBatis和Spring整合Junit。</description><pubDate>Sat, 27 May 2023 11:00:00 GMT</pubDate><content:encoded>&lt;p&gt;详细的描述了Spring整合myBatis和Spring整合Junit。&lt;/p&gt;
&lt;h1&gt;Spring整合myBatis&lt;/h1&gt;
&lt;p&gt;正常的mybatis核心代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class App {
    public static void main(String[] args) throws IOException {
        
//----------------------------------------------------------------------------------------------------//
        // 1. 创建SqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        // 2. 加载SqlMapConfig.xml配置文件
        InputStream inputStream = Resources.getResourceAsStream(&quot;SqlMapConfig.xml.bak&quot;);
        // 3. 创建SqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
//---------------------------------  一、初始化SqlSessionFactory对象   ----------------------------------//

        // 4. 获取SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 5. 执行SqlSession对象执行查询，获取结果User
        AccountDao accountDao = sqlSession.getMapper(AccountDao.class);

//---------------------------------  二、获取连接，获取实现   --------------------------------------------//

        Account ac = accountDao.findById(2);
        System.out.println(ac);

//---------------------------------  三、获取数据层接口   -----------------------------------------------//

        // 6. 释放资源
        sqlSession.close();

//---------------------------------  四、关闭连接   ----------------------------------------------------//
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;那么上述的代码需要将那个核心对象放入IoC中进行管理呢&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SqlSessionFactory&lt;/strong&gt; 最核心&lt;/p&gt;
&lt;h2&gt;原来的整合方法&lt;/h2&gt;
&lt;p&gt;xml配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;!DOCTYPE configuration
        PUBLIC &quot;-//mybatis.org//DTD Config 3.0//EN&quot;
        &quot;http://mybatis.org/dtd/mybatis-3-config.dtd&quot;&amp;gt;
&amp;lt;configuration&amp;gt;
    &amp;lt;properties resource=&quot;jdbc.properties&quot;&amp;gt;&amp;lt;/properties&amp;gt;
    &amp;lt;typeAliases&amp;gt;
        &amp;lt;package name=&quot;com.itheima.domain&quot;/&amp;gt;
    &amp;lt;/typeAliases&amp;gt;
    &amp;lt;environments default=&quot;mysql&quot;&amp;gt;
        &amp;lt;environment id=&quot;mysql&quot;&amp;gt;
            &amp;lt;transactionManager type=&quot;JDBC&quot;&amp;gt;&amp;lt;/transactionManager&amp;gt;
            &amp;lt;dataSource type=&quot;POOLED&quot;&amp;gt;
                &amp;lt;property name=&quot;driver&quot; value=&quot;${jdbc.driver}&quot;&amp;gt;&amp;lt;/property&amp;gt;
                &amp;lt;property name=&quot;url&quot; value=&quot;${jdbc.url}&quot;&amp;gt;&amp;lt;/property&amp;gt;
                &amp;lt;property name=&quot;username&quot; value=&quot;${jdbc.username}&quot;&amp;gt;&amp;lt;/property&amp;gt;
                &amp;lt;property name=&quot;password&quot; value=&quot;${jdbc.password}&quot;&amp;gt;&amp;lt;/property&amp;gt;
            &amp;lt;/dataSource&amp;gt;
        &amp;lt;/environment&amp;gt;
    &amp;lt;/environments&amp;gt;
    &amp;lt;mappers&amp;gt;
        &amp;lt;package name=&quot;com.itheima.dao&quot;&amp;gt;&amp;lt;/package&amp;gt;
    &amp;lt;/mappers&amp;gt;
&amp;lt;/configuration&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;主函数：&lt;/p&gt;
&lt;p&gt;即上述的mybatis核心代码&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意：xml配置与配置类的写法一一对应，原来的配置文件被新的配置类所代替，如下位配置类。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;更改后的&lt;/h2&gt;
&lt;p&gt;配置类：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;

import javax.sql.DataSource;

public class MybatisConfig {
    //定义bean，SqlSessionFactoryBean，用于产生SqlSessionFactory对象
    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
        SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
        ssfb.setTypeAliasesPackage(&quot;com.itheima.domain&quot;);
        ssfb.setDataSource(dataSource);
        return ssfb;
    }
    
    //定义bean，返回MapperScannerConfigurer对象
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setBasePackage(&quot;com.itheima.dao&quot;);
        return msc;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;注意在主配置类中添加当前配置类的扫描&lt;/p&gt;
&lt;p&gt;注意：第二种方法中 pom.xml 新增两个坐标&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;org.springframework&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;spring-jdbc&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;5.2.10.RELEASE&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;

&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;org.mybatis&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;mybatis-spring&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;1.3.0&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;主函数：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import com.itheima.config.SpringConfig;
import com.itheima.domain.Account;
import com.itheima.service.AccountService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App2 {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);

        AccountService accountService = ctx.getBean(AccountService.class);

        Account ac = accountService.findById(1);
        System.out.println(ac);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Spring整合Junit&lt;/h1&gt;
&lt;p&gt;新增依赖&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;junit&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;junit&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;4.12&amp;lt;/version&amp;gt;
  &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;
&amp;lt;/dependency&amp;gt;

&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;org.springframework&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;spring-test&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;5.2.10.RELEASE&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;测试样例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package com.itheima.service;

import com.itheima.config.SpringConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
//设置类运行器
@RunWith(SpringJUnit4ClassRunner.class)
//设置Spring环境对应的配置类
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
    //支持自动装配注入bean
    @Autowired
    private AccountService accountService;

    @Test
    public void testFindById(){ System.out.println(accountService.findById(1)); }

    @Test
    public void testFindAll(){ System.out.println(accountService.findAll()); }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意两点&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;设置类运行器：@RunWith(SpringJUnit4ClassRunner.class)
设置Spring环境对应的配置类：@ContextConfiguration(classes = SpringConfig.class)&lt;/li&gt;
&lt;li&gt;想用什么用@Autowired自动注入即可&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>MySQL04-索引03-使用规则</title><link>https://wang-nine.github.io/posts/10406/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10406/</guid><description>详细的描述了MySQL中的索引的使用规则和设计原则。</description><pubDate>Sat, 27 May 2023 07:00:00 GMT</pubDate><content:encoded>&lt;p&gt;详细的描述了MySQL中的索引的使用规则和设计原则。&lt;/p&gt;
&lt;h1&gt;使用规则&lt;/h1&gt;
&lt;h2&gt;联合索引&lt;/h2&gt;
&lt;h3&gt;最左前缀法则&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;如果索引了多列(联合索引)，要遵守最左前缀法则。最左前缀法则指的是查询从索引的最左列开始，并且不跳过索引中的列。如果跳跃某一列，索引将部分失效(后面的字段索引失效)。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;范围查询&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;联合索引中，出现范围查询(&amp;gt;,&amp;lt;)，范围查询右侧的列索引失效&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;索引失效&lt;/h2&gt;
&lt;h3&gt;索引列运算&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;不要在索引列上进行运算操作,索引将失效。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;字符串不加引号&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;字符串类型字段使用时，不加引号，索引将失效。会在内部自动进行隐式类型转换。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;模糊查询&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;如果仅仅是尾部模糊匹配，索引不会失效。如果是头部模糊匹配，索引失效。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;or连接的条件&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;用or分割开的条件，如果or前的条件中的列有索引，而后面的列中没有索引，那么涉及的索引都不会被用到。&lt;/p&gt;
&lt;p&gt;解决办法：针对另一个数据列也建立索引即可&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;数据分布影响&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;如果MysQL评估使用索引比全表更慢，则不使用索引。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;SQL提示&lt;/h2&gt;
&lt;h3&gt;SQL提示&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;SQL提示，是优化数据库的一个重要手段，简单来说，就是在SQL语句中加入一些人为的提示来达到优化操作的目的。&lt;/p&gt;
&lt;p&gt;放在where条件前，use/ignore/force index(索引名)&lt;/p&gt;
&lt;p&gt;use index: 建议使用索引，数据库可以选择不使用&lt;/p&gt;
&lt;p&gt;ignore index: 忽略某个索引&lt;/p&gt;
&lt;p&gt;force index: 强制使用某个索引&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;覆盖索引&lt;/h2&gt;
&lt;h3&gt;覆盖索引&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;尽量使用覆盖索引（查询使用了索引，并且需要返回的列，在该索引中已经全部能够找到)，减少select *。&lt;/p&gt;
&lt;p&gt;尽量避免回表查询&lt;/p&gt;
&lt;p&gt;extra中：&lt;br /&gt;
using index condition :查找使用了索引,但是需要回表查询数据&lt;br /&gt;
using where; using index:查找使用了索引，但是需要的数据都在索引列中能找到，所以不需要回表查询数据&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;前缀索引&lt;/h2&gt;
&lt;h3&gt;前缀索引&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;当字段类型为字符串(varchar, text等）时，有时候需要索引很长的字符串，这会让索引变得很大，查询时，浪费大量的磁盘IO，影响查询效率。此时可以只将字符串的一部分前缀，建立索引，这样可以大大节约索引空间，从而提高索引效率。&lt;/p&gt;
&lt;p&gt;语法：&lt;br /&gt;
create index idx_xxx on table_name(column(n)) ; - - n为前缀长度&lt;/p&gt;
&lt;p&gt;前缀长度的选择：&lt;br /&gt;
可以根据索引的选择性来决定，选择性是指不重复的索引值（基数）和数据表的记录总数的比值，索引选择性越高则查询效率越高，&lt;br /&gt;
唯一索引的选择性是1，这是最好的索引选择性，性能也是最好的。&lt;br /&gt;
可以使用聚合函数查询选择性&lt;br /&gt;
select count(distinct substring(xxx,1,5))/ count(*) from table ; - - 即长度为5的选择性&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;单列/联合索引&lt;/h2&gt;
&lt;h3&gt;单列索引与联合索引&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;单列索引:即一个索引只包含单个列。&lt;/p&gt;
&lt;p&gt;联合索引:即一个索引包含了多个列。&lt;/p&gt;
&lt;p&gt;在业务场景中，如果存在多个查询条件，考虑针对于查询字段建立索引时，建议建立联合索引，而非单列索引。&lt;/p&gt;
&lt;p&gt;多条件联合查询时，MySQL优化器会评估哪个字段的索引效率更高，会选择该索引完成本次查询。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;索引设计原则（总结自黑马程序员，侵删）&lt;/h1&gt;
&lt;h3&gt;索引设计原则&lt;/h3&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;针对于数据量较大，且查询比较频繁的表建立索引。&lt;/li&gt;
&lt;li&gt;针对于常作为查询条件（where)、排序(order by)、分组(group by)操作的字段建立索引。&lt;/li&gt;
&lt;li&gt;尽量选择区分度高的列作为索引，尽量建立唯一索引，区分度越高，使用索引的效率越高。&lt;/li&gt;
&lt;li&gt;如果是字符串类型的字段，字段的长度较长，可以针对于字段的特点，建立前缀索引。&lt;/li&gt;
&lt;li&gt;尽量使用联合索引，减少单列索引，查询时，联合索引很多时候可以覆盖索引，节省存储空间，避免回表，提高查询效率。&lt;/li&gt;
&lt;li&gt;要控制索引的数量，索引并不是多多益善，索引越多，维护索引结构的代价也就越大，会影响增删改的效率。&lt;/li&gt;
&lt;li&gt;如果索引列不能存储NULL值，请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含NULL值时，它可以更好地确定哪个索引最有效地用于查询。&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;==模型数空运最快==&lt;/p&gt;
&lt;p&gt;模：字符串的模糊匹配&lt;/p&gt;
&lt;p&gt;型：数据类型&lt;/p&gt;
&lt;p&gt;数：索引字段使用函数会失效&lt;/p&gt;
&lt;p&gt;空：不限制索引 not null&lt;/p&gt;
&lt;p&gt;运：对索引列进行运算失效&lt;/p&gt;
&lt;p&gt;最：最左前缀法则&lt;/p&gt;
&lt;p&gt;快：全表比索引快，则不适用&lt;/p&gt;
</content:encoded></item><item><title>Hexo-Butterfly主题博客搭建03</title><link>https://wang-nine.github.io/posts/10003/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10003/</guid><pubDate>Thu, 25 May 2023 07:00:00 GMT</pubDate><content:encoded/></item><item><title>Hexo-Butterfly主题博客搭建02</title><link>https://wang-nine.github.io/posts/10002/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10002/</guid><description>Hexo+Butterfly主题博客搭建02 ——— 下载有关butterfly主题并进行配置</description><pubDate>Wed, 24 May 2023 07:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Hexo+Butterfly主题博客搭建02 ——— 下载有关butterfly主题并进行配置&lt;/p&gt;
&lt;h1&gt;Butterfly主题的安装&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;在 hexo 项目根目录下执行操作clone主题&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;git clone -b master https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;如果沒有 pug 以及 &lt;a href=&quot;https://so.csdn.net/so/search?q=stylus&amp;amp;spm=1001.2101.3001.7020&quot;&gt;stylus&lt;/a&gt; 的渲染器，还需要下载，否则在项目运行时会报错：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;npm install hexo-renderer-pug hexo-renderer-stylus --save
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;修改项目根目录下的_config.yml文件（称为站点配置文件），开启主题&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;# extensions
## plugins: https://hexo.io/plugins/
## themes: https://hexo.io/themes/
#theme: landscape
theme: butterfly
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;升级建议&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;为了減少升级主题带来的不便，我们还需要做以下操作：&lt;br /&gt;
把主题文件夹中的 _config.yml 复制到 hexo 根目录下，同重命名为 _config.butterfly.yml&lt;/p&gt;
&lt;p&gt;hexo会自动合并主题中的 _config.yml 和 _config.butterfly.yml 里的配置，如果存在同名配置，会使用 _config.butterfly.yml 的配置，其优先度较高。&lt;/p&gt;
&lt;h1&gt;butterfly主题&lt;/h1&gt;
</content:encoded></item><item><title>MySQL4-索引02-性能分析</title><link>https://wang-nine.github.io/posts/10405/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10405/</guid><description>详细的描述了MySQL中的索引的性能分析。</description><pubDate>Wed, 24 May 2023 06:00:00 GMT</pubDate><content:encoded>&lt;p&gt;详细的描述了MySQL中的索引的性能分析。&lt;/p&gt;
&lt;h1&gt;索引创建的语法&lt;/h1&gt;
&lt;h2&gt;创建索引&lt;/h2&gt;
&lt;p&gt;CREATE [UNIQUE|FULLTEXT] INDEX index_name ON table_name ( index_col_name … ) ;&lt;/p&gt;
&lt;h2&gt;查看索引&lt;/h2&gt;
&lt;p&gt;SHOW INDEX FROM table_name ;&lt;/p&gt;
&lt;h2&gt;删除索引&lt;/h2&gt;
&lt;p&gt;DROP INDEX index_name ON table_name ;&lt;/p&gt;
&lt;h1&gt;性能分析&lt;/h1&gt;
&lt;h2&gt;SQL执行频次&lt;/h2&gt;
&lt;p&gt;MySQL客户端连接成功后，通过show [session] global status 命令可以提供服务器状态信息。通过如下指令，可以查看当前数据库的INSERT、UPDATE、DELETE、SELECT的访问频次:&lt;br /&gt;
SHOW GLOBAL STATUS LIKE ‘Com**__**‘；&lt;br /&gt;
&lt;strong&gt;注：Com后面六个下划线即为INSERT、UPDATE、DELETE、SELECTINSERT、UPDATE、DELETE、SELECT等单词的长度。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;慢查询日志&lt;/h2&gt;
&lt;p&gt;慢查询日志记录了所有执行时间超过指定参数 (long_query_time，单位:秒，默认10秒) 的所有SQL语句的日志。&lt;/p&gt;
&lt;p&gt;MySQL的慢查询日志默认没有开启，需要在MySQL的配置文件 (/etc/my.cnf) 中配置如下信息:&lt;/p&gt;
&lt;blockquote&gt;
&lt;h1&gt;开启MySQL慢日志查询开关&lt;/h1&gt;
&lt;p&gt;slow_query_log=1&lt;/p&gt;
&lt;h1&gt;设置慢日志的时间为2秒，SQL语句执行时间超过2秒，就会视为慢查询，记录慢查询日志&lt;/h1&gt;
&lt;p&gt;long_query_time=2&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;配置完毕之后，通过以下指令重新启动MySQL服务器进行测试，查看慢日志文件中记录的信息/var/lib/mysql/localhost-slow.log&lt;/p&gt;
&lt;h2&gt;profile详情&lt;/h2&gt;
&lt;p&gt;show profiles能够在做SQL优化时帮助我们了解时间的具体耗费。通过have_profiling参数，能够看到当前MySQL是否支持profile操作&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;SELECT @@have_profiling;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;默认profiling是关闭的，可以通过set语句在session/global级别开启profiling&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;SET profiling = 1;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;(重要！！！)explain执行计划&lt;/h2&gt;
&lt;p&gt;EXPLAIN 或者 DESC 命令获取MySQL如何执行SELECT语句的信息，包括在SELECT语句执行过程中表如何连接和连接的顺序。&lt;/p&gt;
&lt;p&gt;语法：&lt;/p&gt;
&lt;blockquote&gt;
&lt;h1&gt;直接在select语句之前加上关键字explain / desc&lt;/h1&gt;
&lt;p&gt;EXPLAIN SELECT 字段列表 FROM 表名 WHERE 条件;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;explain执行计划中包含的信息如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;id: 查询序列号&lt;/li&gt;
&lt;li&gt;select_type: 查询类型&lt;/li&gt;
&lt;li&gt;table: 表名或者别名&lt;/li&gt;
&lt;li&gt;partitions: 匹配的分区&lt;/li&gt;
&lt;li&gt;type: 访问类型&lt;/li&gt;
&lt;li&gt;possible_keys: 可能用到的索引&lt;/li&gt;
&lt;li&gt;key: 实际用到的索引&lt;/li&gt;
&lt;li&gt;key_len: 索引长度&lt;/li&gt;
&lt;li&gt;ref: 与索引比较的列&lt;/li&gt;
&lt;li&gt;rows: 估算的行数&lt;/li&gt;
&lt;li&gt;filtered: 按表条件筛选的行百分比&lt;/li&gt;
&lt;li&gt;Extra: 额外信息&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;一、id列&lt;/h3&gt;
&lt;p&gt;select查询的序列号(一组数字)，表示查询中执行select子句或者操作表的顺序。&lt;br /&gt;
id列分为三种情况：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;如果id相同，那么执行顺序从上到下&lt;/li&gt;
&lt;li&gt;如果id不同，如果是子查询，id的序号会递增，id值越大优先级越高，越先被执行&lt;/li&gt;
&lt;li&gt;id相同和不同的，同时存在，同时满足以上两种规则即可。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;二、select_type列&lt;/h3&gt;
&lt;p&gt;复制代码主要用来分辨查询的类型，是普通查询还是联合查询还是子查询&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;sample: 简单的查询，不包含子查询和union&lt;/li&gt;
&lt;li&gt;primary: 查询中若包含任何复杂的子查询，最外层查询则被标记为Primary&lt;/li&gt;
&lt;li&gt;union: 在union，union all和子查询中的第二个和随后的select被标记为union&lt;/li&gt;
&lt;li&gt;dependent union: 在包含UNION或者UNION ALL的大查询中，如果各个小查询都依赖于外层查询的话，那除了最左边的那个小查询之外，其余的小查询的select_type的值就是DEPENDENT UNION。&lt;/li&gt;
&lt;li&gt;union result: 从union表获取结果的select。&lt;/li&gt;
&lt;li&gt;subquery: 在select或者where列表中包含子查询（不在from子句中）&lt;/li&gt;
&lt;li&gt;dependent subquery: 子查询中的第一个select（不在from子句中），而且取决于外面的查询。&lt;/li&gt;
&lt;li&gt;derived: 在FROM列表中包含的子查询被标记为DERIVED，也叫做派生类&lt;/li&gt;
&lt;li&gt;UNCACHEABLE SUBQUERY：一个子查询的结果不能被缓存，必须重新评估外链接的第一行对于外层的主表，子查询不可被物化，每次都需要计算（耗时操作）&lt;/li&gt;
&lt;li&gt;uncacheable union: 表示union的查询结果不能被缓存：没找到具体的sql语句验证&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;三、table列&lt;/h3&gt;
&lt;p&gt;对应行正在访问哪一个表，表名或者别名，可能是临时表或者union合并结果集.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;如果是具体的表名，则表明从实际的物理表中获取数据，当然也可以是表的别名.&lt;/li&gt;
&lt;li&gt;表名是derivedN的形式，表示使用了id为N的查询产生的衍生表.&lt;/li&gt;
&lt;li&gt;当有union result的时候，表名是union n1,n2等的形式，n1,n2表示参与union的id.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;四，type列&lt;/h3&gt;
&lt;p&gt;type是访问类型，访问类型表示以何种方式去访问我们的数据，&lt;br /&gt;
访问类型很多，效率从最好到最坏是：&lt;br /&gt;
&lt;strong&gt;NULL &amp;gt; system &amp;gt; const &amp;gt; eq_ref &amp;gt; ref &amp;gt; fulltext &amp;gt; ref_or_null &amp;gt; index_merge &amp;gt; unique_subquery &amp;gt; index_subquery &amp;gt; range &amp;gt; index &amp;gt; ALL&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;all: 全表扫描，需要扫描整张表，从头到尾找到需要的数据行。一般情况下出现这样的sql语句而且数据量比较大的话那么就需要进行优化。&lt;/li&gt;
&lt;li&gt;index：全索引扫描这个比all的效率要好，主要有两种情况，一种是当前的查询时覆盖索引，即我们需要的数据在索引中就可以索取，或者是使用了索引进行排序，这样就避免数据的重排序&lt;/li&gt;
&lt;li&gt;range：表示利用索引查询的时候限制了范围，在指定范围内进行查询，这样避免了index的全索引扫描，适用的操作符：=, &amp;lt;&amp;gt;, &amp;gt;, &amp;gt;=, &amp;lt;, &amp;lt;=, IS NULL, BETWEEN, LIKE, or IN()&lt;/li&gt;
&lt;li&gt;index_subquery：利用索引来关联子查询，不再扫描全表但是大多数情况下使用SELECT子查询时，MySQL查询优化器会自动将子查询优化为联表查询，因此 type 不会显示为 index_subquery,而是ref&lt;/li&gt;
&lt;li&gt;unique_subquery: 该连接类型类似于index_subquery,使用的是唯一索引。大多数情况下使用SELECT子查询时，MySQL查询优化器会自动将子查询优化为联表查询，因此 type 不会显示为 index_subquery,而是eq_ref&lt;/li&gt;
&lt;li&gt;index_merge：在查询过程中需要多个索引组合使用.&lt;/li&gt;
&lt;li&gt;ref_or_null：对于某个字段即需要关联条件，也需要null值的情况下，查询优化器会选择这种访问方式.&lt;/li&gt;
&lt;li&gt;ref：使用了非唯一性索引进行数据的查找&lt;/li&gt;
&lt;li&gt;eq_ref ：当进行等值联表查询使用主键索引或者唯一性非空索引进行数据查找(实际上唯一索引等值查询type不是eq_ref而是const)&lt;/li&gt;
&lt;li&gt;const：最多只能匹配到一条数据，通常使用主键或唯一索引进行等值条件查询&lt;/li&gt;
&lt;li&gt;system：表只有一行记录（等于系统表），这是const类型的特例，平时不会出现,不需要进行磁盘io&lt;/li&gt;
&lt;li&gt;NULL：直接显示，例如 SELECT ‘A’ ，一般业务不可能优化为NULL&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;五、possible_keys列&lt;/h3&gt;
&lt;p&gt;显示可能应用在这张表中的索引，一个或多个，查询涉及到的字段上若存在索引，则该索引将被列出，但不一定被查询实际使用。&lt;/p&gt;
&lt;h3&gt;六、key列&lt;/h3&gt;
&lt;p&gt;实际使用的索引，如果为null，则没有使用索引，查询中若使用了覆盖索引，则该索引和查询的select字段重叠。&lt;/p&gt;
&lt;h3&gt;七、key_len列&lt;/h3&gt;
&lt;p&gt;表示索引中使用的字节数，可以通过key_len计算查询中使用的索引长度，在不损失精度的情况下长度越短越好。&lt;br /&gt;
索引越大占用存储空间越大，这样io的次数和量就会增加，影响执行效率&lt;/p&gt;
&lt;h3&gt;八、ref列&lt;/h3&gt;
&lt;p&gt;显示之前的表在key列记录的索引中查找值所用的列或者常量&lt;/p&gt;
&lt;h3&gt;九、rows列&lt;/h3&gt;
&lt;p&gt;根据表的统计信息及索引使用情况，大致估算出找出所需记录需要读取的行数，此参数很重要，直接反应的sql找了多少数据，在完成目的的情况下越少越好。&lt;/p&gt;
&lt;h3&gt;十、filtered列&lt;/h3&gt;
&lt;p&gt;针对表中符合某个条件(where子句或者联接条件)的记录数的百分比所做的一个悲观估算。&lt;/p&gt;
&lt;h3&gt;十一、extra列&lt;/h3&gt;
&lt;p&gt;包含额外的信息。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;using filesort: 说明mysql无法利用索引进行排序，只能利用排序算法进行排序，会消耗额外的资源&lt;/li&gt;
&lt;li&gt;using temporary: 建立临时表来保存中间结果，查询完成之后把临时表删除&lt;/li&gt;
&lt;li&gt;using index: 这个表示当前的查询是覆盖索引的，直接从索引中读取数据，而不用访问数据表。如果同时出现using where 表名索引被用来执行索引键值的查找，如果没有，表面索引被用来读取数据，而不是真的查找&lt;/li&gt;
&lt;li&gt;using where: 使用where进行条件过滤&lt;/li&gt;
&lt;li&gt;using join buffer: 使用连接缓存&lt;/li&gt;
&lt;li&gt;impossible where：where语句的结果总是false&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>SSM03-Spring的纯注解开发模式</title><link>https://wang-nine.github.io/posts/10303/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10303/</guid><description>详细的描述了Spring中的注解开发。</description><pubDate>Tue, 23 May 2023 17:03:00 GMT</pubDate><content:encoded>&lt;p&gt;详细的描述了Spring中的注解开发。&lt;/p&gt;
&lt;h1&gt;注解开发定义Bean&lt;/h1&gt;
&lt;h2&gt;步骤：&lt;/h2&gt;
&lt;h3&gt;1.使用@Component定义Bean&lt;/h3&gt;
&lt;p&gt;以下两种方式都可以定义Bean&lt;/p&gt;
&lt;p&gt;@Component(“Bean的名称”)&lt;/p&gt;
&lt;p&gt;@Component&lt;/p&gt;
&lt;p&gt;其中第一种方式可以使用 ctx.getBean(“名称”)&lt;/p&gt;
&lt;p&gt;其中第二种方式只能使用 ctx.getBean(“类型.class”) 因为没有指定名称&lt;/p&gt;
&lt;h3&gt;2.核心配置文件中通过组件扫描加载Bean&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;context:component-scan base-package=&quot;wang&quot;/&amp;gt; &amp;lt;!-- 递归扫描 wang --&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;注意：&lt;/h2&gt;
&lt;p&gt;Spring提供@Component注解的三个衍生注解&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;@controller：用于表现层bean定义&lt;br /&gt;
@service：用于业务层bean定义.&lt;br /&gt;
@Repository：用于数据层bean定义&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;样例代码：&lt;/p&gt;
&lt;p&gt;impl实现类：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package wang.dao.impl;

import org.springframework.stereotype.Component;
import wang.dao.Dao;

@Component(&quot;dao&quot;)
public class DaoImpl implements Dao {
    @Override
    public void save() {
        System.out.println(&quot;Dao save() ...&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;xml配置文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot;
       xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
       xmlns:context=&quot;http://www.springframework.org/schema/context&quot;
       xsi:schemaLocation=&quot;
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd
            &quot;&amp;gt;

    &amp;lt;context:component-scan base-package=&quot;wang&quot;/&amp;gt;

&amp;lt;/beans&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;主函数：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package wang;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import wang.dao.Dao;

public class App4 {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext(&quot;applicationContext.xml.bak&quot;);
        Dao dao = (Dao) ctx.getBean(&quot;dao&quot;);
        dao.save();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行结果：&lt;/p&gt;
&lt;p&gt;DaoImpl 上的 @Component 注解显式提供 dao 这个 Bean 名称，XML 中对 wang 包的组件扫描负责发现这个类并注册它。主函数从容器按 dao 取出对象后调用 save()，展示的是注解定义对象、XML 保留扫描入口的组合方式。需要特别核对资源名：示例主函数读取 applicationContext.xml.bak，实际保存的配置文件必须与这个名称一致，否则还没有进入业务方法就会在加载配置阶段失败。&lt;/p&gt;
&lt;h1&gt;纯注解开发模式&lt;/h1&gt;
&lt;p&gt;Spring3.0升级了纯注解开发模式，使用Java类替代配置文件，开启了Spring快速开发赛道&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如何取消配置文件呢，使用一个配置类来代替配置文件&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package wang.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(&quot;wang&quot;)
public class SpringConfig {

}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;@ComponentScan(“wang”) 代替了中间的组件扫描器的功能&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;@Configuration代替了 xml 配置文件中的其他配置，如下图：&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/java-ssm/annotation-config.svg&quot; alt=&quot;XML 组件扫描与 Java 配置类的对应关系&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：@Configuration 把 SpringConfig 声明为配置类，@ComponentScan 指定扫描 wang 包，表达原来 XML 的包扫描入口；两种路径都能发现标注组件的 DaoImpl。切换为纯注解时，容器入口也应改为读取配置类。这里对应的是本例的组件扫描配置，不代表加一个注解就能自动搬走全部 XML 功能，其他配置仍需逐项转换。&lt;/p&gt;
&lt;p&gt;注意：ComponentScan({“wang.xxx01”, “wang.xxx02”}) 可以扫描多个类&lt;/p&gt;
&lt;p&gt;主函数中需要改变一行代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ApplicationContext ctx = new ClassPathXmlApplicationContext(&quot;applicationContext.xml&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;改为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时删除xml配置文件运行结果相同：&lt;/p&gt;
&lt;p&gt;主函数改用 AnnotationConfigApplicationContext(SpringConfig.class) 后，容器从配置类读取扫描范围，不再靠原来的 XML 文件寻找 DaoImpl。只要扫描范围包含带组件注解的实现类，注册名称仍与获取方式对应，原来获取 Bean 并调用 save() 的职责就可以保持不变。验证纯注解切换时，应确认配置类与扫描结果完整，而不是仅凭删除 XML 后的一段提示语判断所有配置都已经迁移。&lt;/p&gt;
&lt;h1&gt;Bean的管理&lt;/h1&gt;
&lt;h2&gt;Bean的作用范围&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;@Scope(“prototype”)即可更改&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Bean的生命周期&lt;/h2&gt;
&lt;p&gt;如下代码：&lt;/p&gt;
&lt;p&gt;实现类：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package wang.dao.impl;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import wang.dao.Dao;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

@Component
@Scope(&quot;singleton&quot;)
public class DaoImpl implements Dao {
    @Override
    public void save() {
        System.out.println(&quot;Dao save() ...&quot;);
    }

    @PostConstruct
    public void init() {
        System.out.println(&quot;init() ... &quot;);
    }

    @PreDestroy
    public void destroy() {
        System.out.println(&quot;destroy() ...&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;主函数：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package wang;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import wang.config.SpringConfig;
import wang.dao.Dao;

public class AppForAnnotation {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        Dao dao1 = ctx.getBean(Dao.class);
        Dao dao2 = ctx.getBean(Dao.class);
        System.out.println(dao1);
        System.out.println(dao2);
        ctx.close();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意关闭容器或关闭钩子可以看到销毁操作&lt;/p&gt;
&lt;p&gt;运行结果如下：&lt;/p&gt;
&lt;p&gt;本例 DaoImpl 被设置为 singleton，主函数连续两次按 Dao 类型获取对象，因此在候选唯一的前提下，两次引用都指向同一个受管实例。@PostConstruct 标记初始化回调，ctx.close() 则触发该单例的 @PreDestroy 回调。注意主函数只是打印两次对象引用，并没有调用 save()，所以不能把业务方法提示语当作这一段代码的必然输出；对象打印出来的具体地址样式也不是判断单例的固定值。&lt;/p&gt;
&lt;h1&gt;注解开发依赖注入&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;@AutoWired&lt;/strong&gt; —&amp;gt; 实现了引用类型的注入&lt;/p&gt;
&lt;p&gt;注意：自动装配基于反射设计创建对象并&lt;strong&gt;暴力反射&lt;/strong&gt;对应属性为私有属性初始化数据，因此无需提供setter方法&lt;/p&gt;
&lt;p&gt;注意：自动装配建议使用&lt;strong&gt;无参构造方法&lt;/strong&gt;创建对象（默认），如果不提供对应构造方法，请提供唯一的构造方法&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;样例代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package wang.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import wang.dao.Dao;
import wang.service.Service;

@Service
public class ServiceImpl implements Service {
    @Autowired
    @Qualifier(&quot;dao&quot;)
    private Dao dao;

    @Override
    public void save() {
        System.out.println(&quot;Service save() ...&quot;);
        dao.save();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;@Value(“变量值”)&lt;/strong&gt; —&amp;gt; 实现了简单类型的注入&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;简单的样例代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Service
public class ServiceImpl implements Service {
    @Value(&quot;wang&quot;)
    private string name;

    @Override
    public void save() {
        System.out.println(&quot;Service save() ...&quot; + name);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;那么为什么不直接将name定义为“wang”呢？为什么还要多此一举呢？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;因为可以通过@Value注入properties&lt;/p&gt;
&lt;p&gt;在配置类中加入如下注解&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;@PropertySource({“xxx.properties”，“xxx.properties”}) &lt;strong&gt;注意：不允许使用通配符&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;此时可以通过@Value(“${ 属性名 }”) 访问properties中的属性&lt;/p&gt;
&lt;h1&gt;注解开发管理第三方Bean&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;@Bean 表示当前方法的返回值是一个Bean&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;@Configuration
public class SpringConfig {
    //1.定义一个方法获得要管理的对象
    //2.添加@Bean，表示当前方法的返回值是一个bean
    @Bean
    public Datasource dataSource() {
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(&quot;com.mysq1.jdbc.Driver&quot;);
        ds.setUr1(&quot;jdbc:mysql://localhost:3306/spring_db&quot;);
        ds.setUsername(&quot;root&quot;);
        ds.setPassword(&quot;1234&quot;);
        return ds;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;写好当前配置文件后，在独立的配置类中导入当前配置类&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;方法1.扫描式 @ComponentScan({“xxx.service”, “xxx.dao”, “xxx.config”}) 不推荐&lt;/p&gt;
&lt;p&gt;方法2.导入式 @Import(xxxconfig.class) 推荐，更为清晰&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;注入普通类型：&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;@Value(&quot;com.mysql.jdbc.Driver&quot;)
private string driver;
@value(&quot;jdbc:mysq1://localhost:3306/spring_db&quot;)
private string url;
@Value(&quot;root&quot;)
private string userName;
@Value(&quot;1234&quot;)
private string password;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;使用@Value注入&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;注入引用类型&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;public Datasource dataSource() {
    DruidDataSource ds = new DruidDataSource(Dao dao);
    System.out.println(dao);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;直接在形参声明即可，自动注入&lt;/strong&gt;&lt;/p&gt;
&lt;h1&gt;XML配置对比注解配置&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;功能&lt;/th&gt;
&lt;th&gt;XML配置&lt;/th&gt;
&lt;th&gt;注解配置&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;定义Bean&lt;/td&gt;
&lt;td&gt;bean标签  &amp;lt;br&amp;gt; • id属性  &amp;lt;br&amp;gt; • class属性&lt;/td&gt;
&lt;td&gt;@Component  &amp;lt;br&amp;gt; • @Controller  &amp;lt;br&amp;gt; • @Service  &amp;lt;br&amp;gt; • @Repository&amp;lt;br&amp;gt;@ComponentScan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;设置依赖注入&lt;/td&gt;
&lt;td&gt;setter注入（set方法）  &amp;lt;br&amp;gt; • 引用类型/简单类型  &amp;lt;br&amp;gt;构造器注入（构造方法）  &amp;lt;br&amp;gt; • 引用类型/简单类型  &amp;lt;br&amp;gt;自动装配&lt;/td&gt;
&lt;td&gt;@AutoWired  &amp;lt;br&amp;gt; • @Qualifier&amp;lt;br&amp;gt;@Value&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;配置第三方Bean&lt;/td&gt;
&lt;td&gt;bean标签  &amp;lt;br&amp;gt;静态工厂、实例工厂、FactoryBean&lt;/td&gt;
&lt;td&gt;@Bean&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;作用范围&lt;/td&gt;
&lt;td&gt;• scope属性&lt;/td&gt;
&lt;td&gt;@Scope&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;生命周期&lt;/td&gt;
&lt;td&gt;标椎接口  &amp;lt;br&amp;gt; • init-method  &amp;lt;br&amp;gt; • destroy-method&lt;/td&gt;
&lt;td&gt;@PostConstructor&amp;lt;br&amp;gt;@PreDestroy&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</content:encoded></item><item><title>MySQL04-索引01-结构</title><link>https://wang-nine.github.io/posts/10404/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10404/</guid><description>详细的描述了MySQL中的索引的结构。</description><pubDate>Tue, 23 May 2023 17:00:00 GMT</pubDate><content:encoded>&lt;p&gt;详细的描述了MySQL中的索引的结构。&lt;/p&gt;
&lt;h1&gt;介绍&lt;/h1&gt;
&lt;p&gt;索引（index）是帮助MySQL&lt;strong&gt;高效获取数据&lt;/strong&gt;的数据结构（有序）。在数据之外，数据库系统还维护着满足特定查找算法的数据结构，这些数据结构以某种方式引用（指向）数据，这样就可以在这些数据结构上实现高级查找算法，这种数据结构就是索引。&lt;/p&gt;
&lt;h1&gt;优缺点&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;优势&lt;/th&gt;
&lt;th&gt;劣势&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;提高数据检索的效率，降低数据库的IO成本&lt;/td&gt;
&lt;td&gt;索引列也会占用空间&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;通过索引列对数据进行排序，降低数据排序的成本，降低CPU的消耗。&lt;/td&gt;
&lt;td&gt;索引大大提高了查询效率，同时却也降低更新表的速度，如对表进行INSERT、UPDATE、DELETE时,效率降低。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;但是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;目前磁盘很便宜，无所谓占用空间&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;目前业务查询 SELECT 频次很高，INSERT、UPDATE、DELETE等操作频次较少&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;综上，使用索引是非常有必要的&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;结构&lt;/h1&gt;
&lt;h2&gt;分类&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;索引结构&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;B+Tree索引&lt;/td&gt;
&lt;td&gt;最常见的索引类型，大部分引擎都支持B+树索引&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hash索引&lt;/td&gt;
&lt;td&gt;底层数据结构是用哈希表实现的，只有精确匹配索引列的查询才有效不支持范围查询&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R-tree（空间索引）&lt;/td&gt;
&lt;td&gt;空间索引是MylSAM引擎的一个特殊索引类型，主要用于地理空间数据类型，使用较少&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full-text（全文索引）&lt;/td&gt;
&lt;td&gt;是一种通过建立倒排索引,快速匹配文档的方式。类似于Lucene，Solr，ES&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;引擎对于索引的支持情况&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;索引&lt;/th&gt;
&lt;th&gt;InnoDB&lt;/th&gt;
&lt;th&gt;MyISAM&lt;/th&gt;
&lt;th&gt;Memory&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;B+tree索引&lt;/td&gt;
&lt;td&gt;支持&lt;/td&gt;
&lt;td&gt;支持&lt;/td&gt;
&lt;td&gt;支持&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hash索引&lt;/td&gt;
&lt;td&gt;不支持&lt;/td&gt;
&lt;td&gt;不支持&lt;/td&gt;
&lt;td&gt;支持&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R-tree索引&lt;/td&gt;
&lt;td&gt;不支持&lt;/td&gt;
&lt;td&gt;支持&lt;/td&gt;
&lt;td&gt;不支持&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full-text&lt;/td&gt;
&lt;td&gt;5.6版本之后支持&lt;/td&gt;
&lt;td&gt;支持&lt;/td&gt;
&lt;td&gt;不支持&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;具体结构可以点击以下链接进入页面进行模拟操作：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.cs.usfca.edu/~galles/visualization/Algorithms.html&quot;&gt;点击此处链接进行模拟数据结构&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.cs.usfca.edu/~galles/visualization/BPlusTree.html&quot;&gt;点击此处链接进行模拟B+树结构&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;具体数据结构在《数据结构》课中都有详细描述。&lt;/p&gt;
&lt;h2&gt;B树结构样例如下：&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/mysql/btree-bplus-comparison.svg&quot; alt=&quot;B 树内部节点含记录与 B+ 树叶子集中存记录、叶链相连的对照&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：两棵树使用同一组示例键。B 树中的二十和四十对应记录保存在根中，不再重复放入叶子；B+ 树的内部节点只用分隔键导航，全部键对应的记录都在叶子。下半图按 InnoDB 的形式画出双向叶链，便于说明范围扫描，不表示实际页容量或磁盘读取次数。&lt;/p&gt;
&lt;h2&gt;B+树结构样例如下：&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/supplemental/b-plus-tree.svg&quot; alt=&quot;B+树索引结构示意&quot; /&gt;&lt;/p&gt;
&lt;p&gt;说明：图中数字均为示例键值。根节点以 20、40 作为分隔键，通过子指针将查找路由到相应键区间；全部示例记录保存在叶节点中，分隔键对应的记录也在叶层。叶节点内部及节点之间按键有序，相邻叶节点用双向链连接，体现 InnoDB 叶层顺序访问的概念。例如查询 20 ≤ 键 ≤ 48 时，先从根节点定位到包含 20 的叶节点，再沿叶链向右读取至 48。图中的“记录”泛指叶子索引记录：InnoDB 聚集索引的叶子保存行记录，二级索引的叶子包含索引列与对应主键；此图不表示实际物理页中的键排列、页内记录组织或容量。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;B树与B+树的区别：&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;1.所有的数据都会出现在叶子节点&lt;/p&gt;
&lt;p&gt;2.叶子节点形成了一个单向链表&lt;/p&gt;
&lt;p&gt;3.MySQL索引数据结构对经典的B+Tree进行了优化。在原B+Tree的基础上，增加一个指向相邻叶子节点的链表指针，就形成了带有顺序指针的B+Tree，提高区间访问的性能。即MySQL将单向链表优化为双向链表。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Hash索引&lt;/h2&gt;
&lt;p&gt;Hash通过hash算法将键值对映射到指定位置，出现碰撞（hash冲突）可以通过链表解决。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hash索引特点&lt;br /&gt;
1.Hash索引只能用于对等比较(=，in)，不支持范围查询(between，&amp;gt;，&amp;lt;，….&lt;br /&gt;
2.无法利用索引完成排序操作&lt;br /&gt;
3.查询效率高，通常只需要一次检索就可以了，效率通常要高于B+tree索引&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;在MySQL中，支持hash索引的是Memory引擎，而InnoDB中具有&lt;strong&gt;自适应hash功能&lt;/strong&gt;，hash索引是存储引擎根据B+Tree索引在指定条件下自动构建的。&lt;/p&gt;
&lt;h2&gt;为什么InnoDB存储引擎选择使用B+tree索引结构?&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;1.相对于二叉树，层级更少，搜索效率高;&lt;br /&gt;
2.对于B-tree，无论是叶子节点还是非叶子节点，都会保存数据，这样导致一页中存储的键值减少，指针跟着减少，要同样保存大量 数据，只能增加树的高度，导致性能降低;&lt;br /&gt;
3.相对Hash索引，B+tree支持范围匹配及排序操作;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;索引分类&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;分类&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;th&gt;特点&lt;/th&gt;
&lt;th&gt;关键字&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;主键索引&lt;/td&gt;
&lt;td&gt;针对表中主键创建的索引&lt;/td&gt;
&lt;td&gt;默认自动创建，只能有一个&lt;/td&gt;
&lt;td&gt;PRIMARY&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;唯一索引&lt;/td&gt;
&lt;td&gt;避免数据列重复&lt;/td&gt;
&lt;td&gt;可以多个&lt;/td&gt;
&lt;td&gt;UNIQUE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;常规索引&lt;/td&gt;
&lt;td&gt;快速查询&lt;/td&gt;
&lt;td&gt;可以多个&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;全文索引&lt;/td&gt;
&lt;td&gt;查找文本关键词，并非比较索引值&lt;/td&gt;
&lt;td&gt;可以多个&lt;/td&gt;
&lt;td&gt;FULLTEXT&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;InnoDB索引分类&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;分类&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;th&gt;特点&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;聚集索引(Clustered Index)&lt;/td&gt;
&lt;td&gt;数据与索引放在一起，叶子节点保存了一行的数据&lt;/td&gt;
&lt;td&gt;必须存在，且只有一个&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;二级索引(Secondary Index)&lt;/td&gt;
&lt;td&gt;数据与索引分开存储，叶子节点存储对应主键&lt;/td&gt;
&lt;td&gt;可以多个&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;聚集索引选取规则：&lt;br /&gt;
1.如果存在主键，主键索引就是聚集索引。&lt;br /&gt;
2.如果不存在主键，将使用第一含唯一（UNIQUE）索引作为聚集索引。&lt;br /&gt;
3.如果表没有主键，或没有合适的唯一索引，则InnoDB会自动生成一个rowid作为隐藏的聚集索引。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;回表查询：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;在二级索引中查询到主键，在聚集索引中根据主键查询到想要的数据&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;InnoDB主键索引的B+树高度为多高呢？&lt;/p&gt;
&lt;p&gt;假设:&lt;br /&gt;
一行数据大小为1k，一页中可以存储16行这样的数据。InnoDB的指针占用6个字节的空间，主键即使为bigint，占用字节数为8。&lt;br /&gt;
高度为2:&lt;br /&gt;
n &lt;em&gt;8 + (n + 1)&lt;/em&gt; 6 = 16 &lt;em&gt;1024，算出n约为1170&lt;br /&gt;
1171&lt;/em&gt; 16=18736&lt;br /&gt;
高度为3:&lt;br /&gt;
1171 &lt;em&gt;1171&lt;/em&gt; 16 = 21939856&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>博客更新文档</title><link>https://wang-nine.github.io/posts/1/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/1/</guid><description>采用next主题，进行了主题优化</description><pubDate>Tue, 23 May 2023 16:00:00 GMT</pubDate><content:encoded>&lt;p&gt;[toc]&lt;/p&gt;
&lt;h3&gt;新增或修改的文件结构&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;root
│
├─_config.yml(修改)
│
├─_config.butterfly.yml(修改)
│
└─themes
    └─butterfly
        ├─layout
        │   ├─includes
        │	│	├─custom
        │	│	│	└─switch.pug(明暗切换动画)
        │	│	│
        │	│	├─header
        │	│	│	└─menu_item.pug(修改，添加子菜单栏居中)
        │	│	│
        │   │   ├─wedget
        │   │   │   ├─card_calendar.pug(新增写作日历)
        │	│	│	├─card_webinfo.pug(新增写作日历)
        │	│	│	└─index.pug(修改，添加写作日历)
		│	│	│
        │	│	└─rightside.pug(黑暗模式一图流)
        │	│	
        │	│	
        │   ├─head.pug(新增，引入昼夜切换)	
        │	├─rightside.pug(修改，昼夜切换)
        │   └─sliderbar.pug
        └─source
            ├─css
            │   ├─_layout
            │   │   ├─calendar.styl(新增日历样式)
            │   │   ├─footer.styl(修改，删除底部蓝条)
            │   │   └─switch.styl(新增，明暗切换样式)
            │   ├─custom
            │   │   ├─background.css(黑暗模式一图流配置)
            │	│	├─fontface.css(字体样式)
            │	│	├─header_beautify.css(子菜单美化)
            │	│	├─slideshow.css
            │	│	├─starlight.css(星光背景)
            │	│	├─twikoo_beautify.css(Twikoo评论美化)
            │	│	└─double_row.css(双栏布局美化)
            │	├─FangZhengKaiTiJianTi-1.ttf(字体美化)
            │	│
            │   └─img(新增，主题图片)
            │   
            └─js
            	└─custom
            		├─foot.js(页脚修改)
            		├─jquery.js(页脚修改支持)
            		├─sakura.js(樱花背景动效)
            		├─snow.js(雪花背景动效)
            		├─snowflake.js(雪花背景动效)
            		├─switch.js(切换明暗模式)
            		└─universe.js(星空背景)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;注意：abbrlink&lt;/h3&gt;
&lt;h4&gt;100 开头为博客搭建相关&lt;/h4&gt;
&lt;h4&gt;101 开头为JavaSE相关&lt;/h4&gt;
&lt;h4&gt;102 开头为 JavaWeb相关&lt;/h4&gt;
&lt;h4&gt;103 开头为ssm相关&lt;/h4&gt;
&lt;h4&gt;104 开头为MySQL相关&lt;/h4&gt;
&lt;h4&gt;105开头为HTML+CSS相关&lt;/h4&gt;
&lt;h4&gt;106开头为SpringCloud相关&lt;/h4&gt;
&lt;h4&gt;107开头为Redis相关&lt;/h4&gt;
&lt;h4&gt;108开头为JavaScript相关&lt;/h4&gt;
&lt;h4&gt;2 开头为杂项&lt;/h4&gt;
&lt;h4&gt;9开头为刷题笔记&lt;/h4&gt;
&lt;h3&gt;更新日志&lt;/h3&gt;
&lt;h4&gt;2023-05-18&lt;/h4&gt;
&lt;h5&gt;更新相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;无&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;bug相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;无&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;杂项&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;初步创建github仓库，安装了nodejs和hexo，初始化了hexo博客&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2023-05-19&lt;/h4&gt;
&lt;h5&gt;更新相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;无&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;bug相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;无&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;杂项&lt;/h5&gt;
&lt;p&gt;采用next主题，进行了主题优化&lt;/p&gt;
&lt;h4&gt;2023-05-20&lt;/h4&gt;
&lt;h5&gt;更新相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;无&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;bug相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;无&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;杂项&lt;/h5&gt;
&lt;p&gt;放弃使用next主题，改用butterfly主题&lt;/p&gt;
&lt;h4&gt;2023-05-21&lt;/h4&gt;
&lt;h5&gt;更新相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;无&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;bug相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;无&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;杂项&lt;/h5&gt;
&lt;p&gt;计划将博客迁移到腾讯云上，购买了腾讯云服务器与域名，申请了ssl证书，在等待24小时后进行备案&lt;/p&gt;
&lt;h4&gt;2023-05-22&lt;/h4&gt;
&lt;h5&gt;更新相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;无&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;bug相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;无&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;杂项&lt;/h5&gt;
&lt;p&gt;进行备案，同时将空闲的服务器制作成了饥荒游戏的服务器&lt;/p&gt;
&lt;h4&gt;2023-05-23&lt;/h4&gt;
&lt;h5&gt;更新相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;无&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;bug相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;无&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;杂项&lt;/h5&gt;
&lt;p&gt;备案验证&lt;/p&gt;
&lt;h4&gt;2023-05-24&lt;/h4&gt;
&lt;h5&gt;更新相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;主题优化，更改了博客的背景样式包括不同页面的背景，底部角标如运行时间等，背景透明度&lt;/li&gt;
&lt;li&gt;添加了樱花特效与雪花特效，选择了樱花特效&lt;/li&gt;
&lt;li&gt;添加了评论系统&lt;/li&gt;
&lt;li&gt;添加了文章随机封面&lt;/li&gt;
&lt;li&gt;更改了代码块显示&lt;/li&gt;
&lt;li&gt;添加了本地搜索功能&lt;/li&gt;
&lt;li&gt;修改了头像以及主页封面等&lt;/li&gt;
&lt;li&gt;添加了文章版权信息&lt;/li&gt;
&lt;li&gt;更改了图片放大查看&lt;/li&gt;
&lt;li&gt;添加了相关文章功能&lt;/li&gt;
&lt;li&gt;更改了侧边栏显示&lt;/li&gt;
&lt;li&gt;更改了公告栏设置&lt;/li&gt;
&lt;li&gt;更改了toc目录&lt;/li&gt;
&lt;li&gt;更改了鼠标点击效果&lt;/li&gt;
&lt;li&gt;更改了打字效果&lt;/li&gt;
&lt;li&gt;更改了footer背景&lt;/li&gt;
&lt;li&gt;添加了字数统计功能&lt;/li&gt;
&lt;li&gt;更改了分享功能&lt;/li&gt;
&lt;li&gt;更新了懒加载功能&lt;/li&gt;
&lt;li&gt;在iconfont中添加了自定义图标&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;bug相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;无&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;杂项&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;无&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2023-05-25&lt;/h4&gt;
&lt;h5&gt;更新相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;无&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;bug相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;无&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;杂项&lt;/h5&gt;
&lt;p&gt;备案成功，一整天在尝试将自己的博客由github迁移到腾讯云上，晚上成功，博客进行了cdn加速，在国内的访问速度也非常快了&lt;/p&gt;
&lt;h4&gt;2023-05-26&lt;/h4&gt;
&lt;h5&gt;更新相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;让域名被百度引擎收录&lt;/li&gt;
&lt;li&gt;添加了博客日历功能&lt;/li&gt;
&lt;li&gt;git贡献日历（已经删除）&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;bug相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;晚上发现博客更新后会出现cname失效的bug，在dns解析页面删除了存储桶的解析后bug解决（花了一晚上。。）&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;杂项&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;知道了腾讯云CDN加速需要收费，不过收费较为便宜&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2023-05-27&lt;/h4&gt;
&lt;h5&gt;更新相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;添加了博客搜索功能&lt;/li&gt;
&lt;li&gt;更新了评论功能，绑定了域名，新增部署，创建了定时任务，设置了安全域名&lt;/li&gt;
&lt;li&gt;更新了评论功能，绑定了qq邮箱，绑定授权码&lt;/li&gt;
&lt;li&gt;添加了评论回复显示身份功能&lt;/li&gt;
&lt;li&gt;Valine评论功能废弃（改用Twikwoo。。。）&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;bug相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;发现错误，baidu_url_submit配置的count过大，即git命令行出现{“error”:400,”message”:”over quota”}代码，更改count为20后无错误&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;上述错误仍然出现，不知道什么原因。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;遇到了严重问题，github.io跳转失败，CDN加速无法添加A记录？（失败，github自带CDN加速，而自己的网站已经开启了CDN加速，除非使用子域名解决CNAME冲突？以后有需求再说吧，目前是国内wang-nine.cn访问，国外wang-nine.github.io）&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;杂项&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;尝试更新valine，接入自己的域名，但是卡在了部署证书这一步，在论坛反应后等待&lt;/li&gt;
&lt;li&gt;晚上发现已绑定（奇怪。）尝试更新评论系统吧&lt;/li&gt;
&lt;li&gt;卡在了魔改valine的这一步，彻底放弃了，尝试更新更好的twikoo评论系统&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2023-05-28&lt;/h4&gt;
&lt;h5&gt;更新相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;更新twikoo评论功能&lt;/li&gt;
&lt;li&gt;MongoDB申请存储管理挂载twikoo评论(暂时不予采用)&lt;/li&gt;
&lt;li&gt;腾讯云开发赠送学生免费6个月(采用腾讯云开发对twikoo评论进行配置)&lt;/li&gt;
&lt;li&gt;新增公式支持功能&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;bug相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;无&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;杂项&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;无&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2023-05-29&lt;/h4&gt;
&lt;h5&gt;更新相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;更新Twikoo评论样式美化(在D:\Blog\themes\butterfly\source\css\custom中引入了css)&lt;/li&gt;
&lt;li&gt;更新了分离搜索栏与菜单栏&lt;/li&gt;
&lt;li&gt;更新了顶栏常驻&lt;/li&gt;
&lt;li&gt;更新了文档支持公式&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;bug相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;顶部导航栏居中出了问题&lt;/li&gt;
&lt;li&gt;子菜单栏居中出了问题&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;杂项&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;无&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2023-05-30&lt;/h4&gt;
&lt;h5&gt;更新相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;实现了子菜单栏居中的效果&lt;/li&gt;
&lt;li&gt;删除了顶栏常驻效果&lt;/li&gt;
&lt;li&gt;删除了分离搜索栏和菜单栏&lt;/li&gt;
&lt;li&gt;将博客的字体改为了楷体&lt;/li&gt;
&lt;li&gt;更新了博客中文章双栏显示（失败）&lt;/li&gt;
&lt;li&gt;美化了博客中双栏显示的效果（失败）&lt;/li&gt;
&lt;li&gt;修复了双栏显示后页码不居中的bug（失败）&lt;/li&gt;
&lt;li&gt;增加了轮播图的功能（在文章配置中添加swiper_index属性，越大越先出现）&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;bug相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;阅读量和评论数一直加载中（已解决）&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;杂项&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;无&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2023-05-31&lt;/h4&gt;
&lt;h5&gt;更新相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;取消了日历功能（取消）（加上了）&lt;/li&gt;
&lt;li&gt;增加了夜晚模式下的流星背景特效&lt;/li&gt;
&lt;li&gt;增加了首页轮播图&lt;/li&gt;
&lt;li&gt;增加了一图流&lt;/li&gt;
&lt;li&gt;增加了日夜切换动画&lt;/li&gt;
&lt;li&gt;增加了首页轮播图&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;bug相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;首页透明度bug（使用一图流解决）&lt;/li&gt;
&lt;li&gt;黑夜模式首页背景不同bug（使用一图流解决）&lt;/li&gt;
&lt;li&gt;封面不加载&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;杂项&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;无&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2023-06-01&lt;/h4&gt;
&lt;h5&gt;更新相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;无&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;bug相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;博客第二页未更新（重新在cos中上传即可）&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;杂项&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;无&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2023-06-02&lt;/h4&gt;
&lt;h5&gt;更新相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;删除了轮播图&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;bug相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;无&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;杂项&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;无&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2023-06-24&lt;/h4&gt;
&lt;h5&gt;更新相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;无&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;bug相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;直接gitbash上传四件套可以解决博客第二页未更新bug&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;杂项&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;无&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2023-11-28&lt;/h4&gt;
&lt;h5&gt;更新相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;添加了轮播图&lt;/li&gt;
&lt;li&gt;删除了git日历&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;bug相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;无&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;杂项&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;无&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;2023-11-29&lt;/h4&gt;
&lt;h5&gt;更新相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;无&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;bug相关&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;无&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;杂项&lt;/h5&gt;
&lt;ul&gt;
&lt;li&gt;预计为文章添加description属性&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Hexo+Butterfly主题博客搭建01</title><link>https://wang-nine.github.io/posts/10001/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10001/</guid><description>Hexo+Butterfly主题博客搭建01 ——— 个人博客搭建的基础环境配置工作</description><pubDate>Tue, 23 May 2023 16:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Hexo+Butterfly主题博客搭建01 ——— 个人博客搭建的基础环境配置工作&lt;/p&gt;
&lt;h1&gt;Node.js的配置&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://nodejs.org/en/download&quot;&gt;nodejs的官网&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;选择适合自己系统的下载即可，大部分人应该是Windows Installer (.msi) 64位，选择msi位安装程序，mac和linux用户选择适合自己的版本即可。&lt;/p&gt;
&lt;p&gt;安装是一路默认选项即可，&lt;strong&gt;注意安装的路径可以根据自己的需求，一定要记住安装在哪里了&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;安装完成之后可以在cmd中测试是否安装成功&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;node -v
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;npm -v
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装后重新打开终端，分别执行上面的 &lt;code&gt;node -v&lt;/code&gt; 与 &lt;code&gt;npm -v&lt;/code&gt;。两条命令都能返回版本号，才说明当前终端可以找到对应程序；具体版本以本机实际输出为准。如果提示命令无法识别，先确认安装过程已完成、终端已重开，再检查安装目录是否加入 &lt;code&gt;PATH&lt;/code&gt;。若机器上装过多个版本，还应检查命令实际解析到的路径，避免后续安装 Hexo 时使用了另一套环境。&lt;/p&gt;
&lt;h1&gt;Git的配置&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://git-scm.com/&quot;&gt;Git的官网&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;在 Git 官网进入下载区域，按操作系统与处理器架构选择安装包，不要只依据网页上的某个按钮位置判断版本。Windows 用户安装时留意安装目录、默认编辑器以及命令行可用范围；如果希望在普通终端中使用 Git，应选择包含相应 PATH 集成的方案。完成安装后重开终端并执行 &lt;code&gt;git --version&lt;/code&gt;，以能否返回实际版本信息检查命令是否可用，再继续准备博客仓库。&lt;/p&gt;
&lt;p&gt;选择适合自己系统的下载即可，大部分人应该是Windows64位，所以直接点击Downloade for Windows即可。&lt;/p&gt;
&lt;p&gt;安装是一路默认选项即可，&lt;strong&gt;注意安装的路径可以根据自己的需求，一定要记住安装在哪里了&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;安装完成之后可以在cmd中测试是否安装成功&lt;/p&gt;
&lt;h1&gt;github的配置&lt;/h1&gt;
&lt;p&gt;在github新建一个仓库&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;名称为 &lt;strong&gt;你的名称.github.io&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;个人主页仓库的配置要点：&lt;/p&gt;
&lt;p&gt;仓库的关键配置是所有者与名称匹配：个人主页仓库命名为 &lt;code&gt;&amp;lt;用户名&amp;gt;.github.io&lt;/code&gt;，其中用户名使用实际的 GitHub 账号名，而不是昵称。登录后从仓库创建入口填写名称，创建后核对仓库地址和所有者。随后在仓库设置中找到 Pages 的发布配置，按采用的部署方式选择分支发布或工作流发布；建好仓库本身并不代表站点已经发布，还需要后续生成并部署博客内容。&lt;/p&gt;
&lt;h1&gt;博客的初始化&lt;/h1&gt;
&lt;h2&gt;安装Hexo&lt;/h2&gt;
&lt;p&gt;所有必备的应用程序安装完成后，即可使用&lt;code&gt;npm&lt;/code&gt;安装&lt;code&gt;Hexo&lt;/code&gt;。不要觉得安装这个框架有多复杂，只需要一条命令就可以了。&lt;code&gt;npm&lt;/code&gt;是安装工具，&lt;code&gt;install&lt;/code&gt;表示安装，&lt;code&gt;-g&lt;/code&gt;表示全局安装，&lt;code&gt;hexo-cli&lt;/code&gt;表示安装的是hexo框架。&lt;/p&gt;
&lt;p&gt;在 git 命令行下输入下述的命令，&lt;strong&gt;若无法安装可能是因为权限不够，可以以管理员权限运行 gitbash&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install -g hexo-cli
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;初始化博客&lt;/h2&gt;
&lt;p&gt;安装完框架之后新建一个存放博客的文件夹Blog，这里在D盘新建了一个Blog文件夹，在该文件夹下右键Git Bash Here并输入&lt;code&gt;hexo init&lt;/code&gt;命令初始化这个文件夹。注意新建 Blog 文件夹和初始化这个文件夹只需要下面这条命令就可以了。&lt;/p&gt;
&lt;h2&gt;启动和配置博客&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;hexo clean  # 清除缓存
hexo g      # 生成静态网页
hexo d      # 部署到Github
hexo s 		# 是 hexo server 的缩写，启动本地服务器，用于预览主题。默认地址： http://localhost:4000/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时输入 &lt;code&gt;hexo clean&lt;/code&gt; &lt;code&gt;hexo d&lt;/code&gt; &lt;code&gt;hexo h&lt;/code&gt; &lt;code&gt;hexo s&lt;/code&gt; 命令即可初步查看自己的 Blog 页面&lt;br /&gt;
在浏览器输入 &lt;a href=&quot;http://localhost:4000/&quot;&gt;http://localhost:4000/&lt;/a&gt; 即可访问&lt;br /&gt;
顺利的话，在浏览器输入：&lt;code&gt;用户名.github.io&lt;/code&gt; 即可初步访问自己的博客&lt;/p&gt;
</content:encoded></item><item><title>SSM02-Spring中Bean的实例化与依赖注入</title><link>https://wang-nine.github.io/posts/10302/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10302/</guid><description>详细的描述了Spring中Bean的实例化与依赖注入。</description><pubDate>Tue, 23 May 2023 09:00:00 GMT</pubDate><content:encoded>&lt;p&gt;详细的描述了Spring中Bean的实例化与依赖注入。&lt;/p&gt;
&lt;h3&gt;Bean的实例化&lt;/h3&gt;
&lt;h4&gt;构造方法&lt;/h4&gt;
&lt;p&gt;Spring创建Bean的时候调用无参构造。（有参构造会报错，私有无参也可以，底层使用了反射）&lt;/p&gt;
&lt;p&gt;注意：Spring中的报错从下向上看，直到找到有用的信息。&lt;/p&gt;
&lt;p&gt;注意：若不存在无参构造方法，会抛出异常：BeanCreationException&lt;/p&gt;
&lt;p&gt;添加无参构造后：&lt;br /&gt;
运行结果：&lt;br /&gt;
这一节讨论的是直接按 class 配置、又没有提供构造参数时的实例化方式：容器需要找到可调用的无参构造器，再完成对象创建。补上无参构造器解决的是该配置下的实例化条件，并不意味着 Spring 一律禁止有参构造；后文的 constructor-arg 就是在显式描述构造参数。排查创建失败时应继续查看异常原因链，区分构造器选择、依赖缺失与初始化失败。&lt;/p&gt;
&lt;h4&gt;静态工厂&lt;/h4&gt;
&lt;p&gt;创建对象不需要自己new，创建一个工厂创建对象达到解耦的效果&lt;/p&gt;
&lt;p&gt;样例代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package wang.factory;

import wang.dao.Dao;
import wang.dao.impl.DaoImpl;

public class DaoFactory {
    public static Dao getDao() {
    	System.out.println(&quot;factory set up ...&quot;);
        return new DaoImpl();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配置文件添加如下代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;bean id=&quot;daoFactory&quot; class=&quot;wang.factory.DaoFactory&quot; factory-method=&quot;getDao&quot;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;主函数：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package wang;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import wang.dao.Dao;

public class App3 {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext(&quot;applicationContext.xml&quot;);
        Dao dao = (Dao) ctx.getBean(&quot;daoFactory&quot;);
        dao.save();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行结果：&lt;/p&gt;
&lt;p&gt;这里 bean 的 class 指向 DaoFactory，factory-method 指向静态方法 getDao。容器调用这个方法取得它返回的 DaoImpl，并把该返回对象作为名称为 daoFactory 的 Bean 提供给应用。因此按 daoFactory 名称获取 Bean 的使用者拿到的是 Dao 类型的产品，而不是工厂本身；主函数再调用 dao.save()。对象创建逻辑被集中到工厂方法，调用者无需直接写出产品实现类的构造语句。&lt;/p&gt;
&lt;h4&gt;实例工厂&lt;/h4&gt;
&lt;p&gt;工厂代码同上&lt;/p&gt;
&lt;p&gt;配置文件修改为如下代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;bean id=&quot;daoFactorytemp&quot; class=&quot;wang.factory.DaoFactory&quot;/&amp;gt;                 &amp;lt;!-- 创建工厂bean --&amp;gt;
&amp;lt;bean id=&quot;daoFactory&quot; factory-bean=&quot;daoFactorytemp&quot; factory-method=&quot;getDao&quot;/&amp;gt;
&amp;lt;!-- factory-bean指定工厂bean --&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;第一行为一个无用的bean，改良后即为第四种方法&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;主函数不变&lt;/p&gt;
&lt;p&gt;运行结果：&lt;/p&gt;
&lt;p&gt;工厂方式的共同目标是让容器对外提供 Dao 产品，主函数继续按产品的 Bean 名称获取对象并调用 save()。实例工厂配置使用 factory-bean 找到工厂对象，再由 factory-method 指定生产方法；FactoryBean 方式则实现统一的 getObject() 与 getObjectType() 契约。使用 FactoryBean 时，普通名称取到产品，带 &amp;amp; 前缀的名称才取得工厂自身。两者都要正确提供创建逻辑，不能只凭调用端相同就认定配置已经等价。&lt;/p&gt;
&lt;h4&gt;FactoryBean（重要！）&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;在第三种方法存在两个问题&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;一、factory-bean属性使用时会创建一个无用的Bean&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;二、factory-method属性方法名不固定，非动态实现&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;因此，对此方法做了改良&lt;/p&gt;
&lt;p&gt;factory实现一个接口FactoryBean&lt;/p&gt;
&lt;p&gt;工厂代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package wang.factory;

import org.springframework.beans.factory.FactoryBean;
import wang.dao.Dao;
import wang.dao.impl.DaoImpl;

public class DaoFactoryBean implements FactoryBean&amp;lt;Dao&amp;gt; {
    // 代替原始实例工厂创建对象的方法
    @Override
    public Dao getObject() throws Exception {
        return new DaoImpl();
    }
    // 得到Bean的类型
    @Override
    public Class&amp;lt;?&amp;gt; getObjectType() {
        return Dao.class;
    }
    // 设置Bean是否单例子，return true为单例，false为非单例
    // 该方法一般不重写
    /*@Override
    public boolean isSingleton() {
        return true;
    }*/
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配置文件修改为如下代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;bean id=&quot;daoFactory&quot; class=&quot;wang.factory.DaoFactoryBean&quot;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;主函数不变&lt;/p&gt;
&lt;p&gt;运行结果：&lt;/p&gt;
&lt;p&gt;工厂方式的共同目标是让容器对外提供 Dao 产品，主函数继续按产品的 Bean 名称获取对象并调用 save()。实例工厂配置使用 factory-bean 找到工厂对象，再由 factory-method 指定生产方法；FactoryBean 方式则实现统一的 getObject() 与 getObjectType() 契约。使用 FactoryBean 时，普通名称取到产品，带 &amp;amp; 前缀的名称才取得工厂自身。两者都要正确提供创建逻辑，不能只凭调用端相同就认定配置已经等价。&lt;/p&gt;
&lt;h4&gt;补充：Bean的生命周期&lt;/h4&gt;
&lt;h5&gt;控制生命周期&lt;/h5&gt;
&lt;h4&gt;方法一：&lt;/h4&gt;
&lt;p&gt;控制方法代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public static DaoImpl implements Dao {
    public void save() {
        System.out.println(&quot;Dao save ... &quot;)
    }
    public void init() {
        System.out.println(&quot;Dao init ... &quot;)
    }
    public void destroy() {
        System.out.println(&quot;Dao destroy ... &quot;)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配置控制方法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;bean id=&quot;Dao&quot; class=&quot;wang.dao.impl.DaoImpl&quot; init-method=&quot;init&quot; destroy-method=&quot;destory&quot;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;方法二&lt;/h4&gt;
&lt;p&gt;实现InitializingBean, DisposableBean接口&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public static DaoImpl implements Dao, InitializingBean, DisposableBean {
    public void save() {
        System.out.println(&quot;Dao save ... &quot;)
    }
    public void init() throws Exception {
        System.out.println(&quot;Dao init ... &quot;)
    }
    public void destroy() throws Exception {
        System.out.println(&quot;Dao destroy ... &quot;)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;生命周期&lt;/h5&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;初始化容器&lt;/strong&gt;&lt;br /&gt;
1.创建对象（内存分配）&lt;br /&gt;
2.执行构造方法&lt;br /&gt;
3.执行属性注入( set操作）&lt;br /&gt;
4.执行bean初始化方法&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;使用bean&lt;/strong&gt;&lt;br /&gt;
1.行业务操作&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;关闭/销毁容器&lt;/strong&gt;&lt;br /&gt;
1.执行bean销毁方法&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;注：**销毁的时机：**容器关闭前触发bean的销毁&lt;br /&gt;
关闭容器方式：&lt;br /&gt;
手工关闭容器&lt;br /&gt;
ConfigurableApplicationContext接口close（）&lt;/p&gt;
&lt;p&gt;操作注册关闭钩子，在虚拟机退出前先关闭容器再退出虚拟机&lt;br /&gt;
ConfigurableApplicationcontext接口registerShutdownHook （）操作&lt;/p&gt;
&lt;h3&gt;依赖注入&lt;/h3&gt;
&lt;h4&gt;setter注入&lt;/h4&gt;
&lt;h5&gt;引用类型&lt;/h5&gt;
&lt;p&gt;详情见博客 SSM01-Spring中的Bean&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;步骤如下&lt;br /&gt;
1.在bean中定义引用类型属性并提供可访问的set方法&lt;br /&gt;
2.配置中使用property标签ref属性注入引用类型对象&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h5&gt;简单类型&lt;/h5&gt;
&lt;blockquote&gt;
&lt;p&gt;步骤如下&lt;br /&gt;
1.在bean中定义引用类型属性并提供可访问的set方法&lt;br /&gt;
2.配置中使用property标签value属性注入简单类型数据&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;构造器注入&lt;/h4&gt;
&lt;h5&gt;引用类型（仅做了解）&lt;/h5&gt;
&lt;blockquote&gt;
&lt;p&gt;步骤如下&lt;br /&gt;
1.在bean中定义引用类型属性并提供可访问的构造方法&lt;br /&gt;
2.配置中使用constructor-arg标签ref属性注入引用类型对象&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h5&gt;简单类型&lt;/h5&gt;
&lt;blockquote&gt;
&lt;p&gt;步骤如下&lt;br /&gt;
1.在bean中定义引用类型属性并提供可访问的set方法&lt;br /&gt;
2.配置中使用property标签value属性注入简单类型数据&lt;br /&gt;
注意：&lt;br /&gt;
配置中使用constructor-arg标签type属性设置按形参类型注入&lt;br /&gt;
配置中使用constructor-arg标签index属性设置按形参位置注入&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;依赖注入方式的选择（总结自黑马程序员，侵删）&lt;/h4&gt;
&lt;p&gt;1.强制依赖使用构造器进行，使用setter注入有概率不进行注入导致null对象出现&lt;br /&gt;
2.可选依赖使用setter注入进行，灵活性强&lt;br /&gt;
3.Spring框架倡导使用构造器，第三方框架内部大多数采用构造器注入的形式进行数据初始化，相对严谨&lt;br /&gt;
4.如果有必要可以两者同时使用，使用构造器注入完成强制依赖的注入，使用setter注入完成可选依赖的注入&lt;br /&gt;
5.实际开发过程中还要根据实际情况分析，如果受控对象没有提供setter方法就必须使用构造器注入&lt;br /&gt;
6.自己开发的模块推荐使用setter注入&lt;/p&gt;
&lt;h4&gt;自动装配（优先级低于setter注入与构造器注入）&lt;/h4&gt;
&lt;p&gt;IoC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程称为自动装配&lt;/p&gt;
&lt;p&gt;自动装配方式&lt;/p&gt;
&lt;h5&gt;按类型（常用）（不能对简单类型装配）&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;bean id=&quot;daoFactory&quot; class=&quot;wang.dao.impl.DaoImpl&quot; autowire=&quot;byType&quot;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意：需要set方法&lt;/p&gt;
&lt;p&gt;注意：按类型（byType）时，同类型的应该只有一个bean，否则分不清。&lt;/p&gt;
&lt;h5&gt;按名称(set方法第一个字母改为小写的名称，即标椎的变量名)&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;bean id=&quot;daoFactory&quot; class=&quot;wang.dao.impl.DaoImpl&quot; autowire=&quot;byName&quot;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意：因为变量名与byName耦合度高，不建议使用。&lt;/p&gt;
&lt;h5&gt;按构造方法&lt;/h5&gt;
&lt;h4&gt;集合注入（主要学习格式）&lt;/h4&gt;
&lt;p&gt;Dao接口&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package wang.dao;

public interface DataDao {
    public void save();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Dao实现&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package wang.dao.impl;

import wang.dao.DataDao;

import java.util.*;

public class DataDaoImpl implements DataDao {

    private int[] array;

    private List&amp;lt;String&amp;gt; list;

    private Set&amp;lt;String&amp;gt; set;

    private Map&amp;lt;String,String&amp;gt; map;

    private Properties properties;




    public void setArray(int[] array) {
        this.array = array;
    }

    public void setList(List&amp;lt;String&amp;gt; list) {
        this.list = list;
    }

    public void setSet(Set&amp;lt;String&amp;gt; set) {
        this.set = set;
    }

    public void setMap(Map&amp;lt;String, String&amp;gt; map) {
        this.map = map;
    }

    public void setProperties(Properties properties) {
        this.properties = properties;
    }




    public void save() {
        System.out.println(&quot;book dao save ...&quot;);

        System.out.println(&quot;遍历数组:&quot; + Arrays.toString(array));

        System.out.println(&quot;遍历List&quot; + list);

        System.out.println(&quot;遍历Set&quot; + set);

        System.out.println(&quot;遍历Map&quot; + map);

        System.out.println(&quot;遍历Properties&quot; + properties);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配置文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot;
       xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
       xsi:schemaLocation=&quot;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd&quot;&amp;gt;
    &amp;lt;bean id=&quot;dataDao&quot; class=&quot;wang.dao.impl.DataDaoImpl&quot;&amp;gt;
        &amp;lt;!--数组注入--&amp;gt;
        &amp;lt;property name=&quot;array&quot;&amp;gt;
            &amp;lt;array&amp;gt;
                &amp;lt;value&amp;gt;100&amp;lt;/value&amp;gt;
                &amp;lt;value&amp;gt;200&amp;lt;/value&amp;gt;
                &amp;lt;value&amp;gt;300&amp;lt;/value&amp;gt;
            &amp;lt;/array&amp;gt;
        &amp;lt;/property&amp;gt;
        &amp;lt;!--list集合注入--&amp;gt;
        &amp;lt;property name=&quot;list&quot;&amp;gt;
            &amp;lt;list&amp;gt;
                &amp;lt;value&amp;gt;java&amp;lt;/value&amp;gt;
                &amp;lt;value&amp;gt;c++&amp;lt;/value&amp;gt;
                &amp;lt;value&amp;gt;python&amp;lt;/value&amp;gt;
                &amp;lt;value&amp;gt;php&amp;lt;/value&amp;gt;
            &amp;lt;/list&amp;gt;
        &amp;lt;/property&amp;gt;
        &amp;lt;!--set集合注入--&amp;gt;
        &amp;lt;property name=&quot;set&quot;&amp;gt;
            &amp;lt;set&amp;gt;
                &amp;lt;value&amp;gt;java&amp;lt;/value&amp;gt;
                &amp;lt;value&amp;gt;python&amp;lt;/value&amp;gt;
                &amp;lt;value&amp;gt;java&amp;lt;/value&amp;gt;
                &amp;lt;value&amp;gt;php&amp;lt;/value&amp;gt;
            &amp;lt;/set&amp;gt;
        &amp;lt;/property&amp;gt;
        &amp;lt;!--map集合注入--&amp;gt;
        &amp;lt;property name=&quot;map&quot;&amp;gt;
            &amp;lt;map&amp;gt;
                &amp;lt;entry key=&quot;country&quot; value=&quot;china&quot;/&amp;gt;
                &amp;lt;entry key=&quot;province&quot; value=&quot;shanxi&quot;/&amp;gt;
                &amp;lt;entry key=&quot;city&quot; value=&quot;xian&quot;/&amp;gt;
            &amp;lt;/map&amp;gt;
        &amp;lt;/property&amp;gt;
        &amp;lt;!--Properties注入--&amp;gt;
        &amp;lt;property name=&quot;properties&quot;&amp;gt;
            &amp;lt;props&amp;gt;
                &amp;lt;prop key=&quot;country&quot;&amp;gt;china&amp;lt;/prop&amp;gt;
                &amp;lt;prop key=&quot;province&quot;&amp;gt;shanxi&amp;lt;/prop&amp;gt;
                &amp;lt;prop key=&quot;city&quot;&amp;gt;xian&amp;lt;/prop&amp;gt;
            &amp;lt;/props&amp;gt;
        &amp;lt;/property&amp;gt;
    &amp;lt;/bean&amp;gt;
&amp;lt;/beans&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;主函数：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package wang.dao;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App6 {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext(&quot;applicationContext.xml&quot;);
        DataDao dataDao = (DataDao) ctx.getBean(&quot;dataDao&quot;);
        dataDao.save();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行结果：&lt;/p&gt;
&lt;p&gt;DataDaoImpl 的五个 setter 分别接收数组、List、Set、Map 和 Properties。XML 中 array 与 list 保留配置的元素顺序，set 表达不重复元素，因此配置里重复的 java 不应成为两个独立集合元素；map 与 props 则把键对应到值。save() 会逐项打印这些已注入的属性，检查重点是容器类型与内容是否符合配置，而不是照着某个 Set、Map 或 Properties 的显示顺序判断注入是否正确。&lt;/p&gt;
&lt;h4&gt;第三方资源注入&lt;/h4&gt;
&lt;h3&gt;容器&lt;/h3&gt;
&lt;h4&gt;两种加载容器的方式&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;ApplicationContext ctx = new ClassPathXmlApplicationContext(&quot;applicationContext.xml&quot;); //推荐
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;ApplicationContext ctx = new FileSystemXmlApplicationContext(&quot;D:\\ssm\\ssm\\springtest\\springtest\\src\\main\\resources\\applicationContext.xml&quot;)	//绝对路径寻找
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;两种获取bean的方式&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;Dao dao = (DataDao) ctx.getBean(&quot;dao&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Dao dao = ctx.getBean(&quot;dao&quot;, Dao.class);  //多个同种类型报错
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;对比ApplicationContext和BeanFactory&lt;/h4&gt;
&lt;p&gt;BeanFactory是顶级接口的Bean是延迟加载&lt;/p&gt;
&lt;p&gt;ApplicationContext是子接口（常用）也可以延迟加载，在bean中加入参数 lazy-init=“true” 即可&lt;/p&gt;
&lt;h4&gt;加载第三方Bean&lt;/h4&gt;
&lt;h4&gt;加载Properties文件&lt;/h4&gt;
&lt;p&gt;步骤如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;开启Context命名空间&lt;/li&gt;
&lt;li&gt;使用context加载properties文件&lt;/li&gt;
&lt;li&gt;使用属性占位符${}读取properties配置文件中的属性&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;样例代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot;
       xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
       xmlns:context=&quot;http://www.springframework.org/schema/context&quot;
       xsi:schemaLocation=&quot;
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd
            &quot;&amp;gt;
    &amp;lt;!--步骤--&amp;gt;
    &amp;lt;!--1.开启命名空间--&amp;gt;
    &amp;lt;!--2.使用context加载properties文件--&amp;gt;
    &amp;lt;context:property-placeholder location=&quot;xxxx.properties&quot;/&amp;gt;

    &amp;lt;!--3.使用属性占位符${}读取properties配置文件中的属性--&amp;gt;
    &amp;lt;bean class=&quot;xxxx&quot;&amp;gt;
        &amp;lt;property name=&quot;xxxx&quot; value=&quot;${ 配置中的变量名称 }&quot;/&amp;gt;
    &amp;lt;/bean&amp;gt;
&amp;lt;/beans&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意：加载时可以关闭系统属性，因为系统属性的优先级高于&lt;/p&gt;
&lt;p&gt;注意：多个配置文件用逗号隔开&lt;/p&gt;
&lt;p&gt;注意：专业的写法加载所有配置文件如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!-- 加载properties文件标准格式 --&amp;gt;
&amp;lt;context:property-placeholder location=&quot;classpath:*.properties&quot; system-properties-mode=&quot;NEVER&quot;/&amp;gt;
&amp;lt;!-- 从类路径或jar包中搜索并加载properties文件
&amp;lt;context:property-placeholder location=&quot;classpath*:*.properties&quot; system-properties-mode=&quot;NEVER&quot;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;总结（来自黑马程序员，侵删）&lt;/h3&gt;
&lt;h4&gt;容器相关&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/java-ssm/container-overview.svg&quot; alt=&quot;Spring 容器加载配置、创建 Bean 与获取对象的路径&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：类路径配置与文件系统配置分别交给相应的 ApplicationContext 实现读取，再由容器创建对象、装配依赖并执行初始化。应用按名称和类型获取 Bean；普通上下文通常在刷新时创建非懒加载单例，懒加载设置可推迟创建。关闭容器则为其管理的单例触发销毁回调，不应把所有作用域都理解成同样的生命周期。&lt;/p&gt;
&lt;h4&gt;bean相关&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/java-ssm/bean-lifecycle.svg&quot; alt=&quot;受管单例 Bean 从实例化到容器关闭的生命周期&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：Bean 先实例化，再接受依赖注入，随后执行初始化回调，之后才供应用使用；容器关闭时对受管单例执行销毁回调。XML 可以通过 init-method 与 destroy-method 指定方法，注解写法则标记相应方法。singleton 在容器内共享实例，prototype 每次请求可创建新实例，其完整销毁过程不由容器代管。&lt;/p&gt;
&lt;h4&gt;依赖注入相关&lt;/h4&gt;
&lt;p&gt;依赖注入可以先按入口区分：setter 注入使用 property，构造器注入使用 constructor-arg；再按数据区分，ref 指向另一个 Bean，value 提供简单值。集合使用 array、list、set、map、props 描述内容。自动装配则让容器按规则寻找依赖，按类型时要解决候选歧义，按名称时要对应属性与 Bean 名称。选择方式应结合强制依赖与可选依赖，而不是把所有对象都机械地改成同一种装配形式。&lt;/p&gt;
</content:encoded></item><item><title>MySQL03-存储引擎</title><link>https://wang-nine.github.io/posts/10403/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10403/</guid><description>详细的描述了MySQL中的引擎之间的区别和选择。</description><pubDate>Tue, 23 May 2023 08:00:00 GMT</pubDate><content:encoded>&lt;p&gt;详细的描述了MySQL中的引擎之间的区别和选择。&lt;/p&gt;
&lt;h1&gt;MySQL体系结构&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/mysql/server-architecture.svg&quot; alt=&quot;MySQL 连接层、服务层、引擎层与持久存储的调用关系&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：客户端请求先经过连接管理与身份认证，再由服务层解析、优化和执行 SQL，最后通过存储引擎接口访问数据。不同引擎的存储方式不同，不能把 MEMORY 的表数据也理解为磁盘文件；查询结果沿调用链返回。&lt;/p&gt;
&lt;h2&gt;连接层&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;最上层是一些客户端和链接服务，主要完成一些类似于连接处理、授权认证、及相关的安全方案。服务器也会为安全接入的每个客户端验证它所具有的操作权限。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;服务层&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;第二层架构主要完成大多数的核心服务功能，如SQL接口，并完成缓存的查询，SQL的分析和优化，部分内置函数的执行。所有跨存储引擎的功能也在这一层实现，如过程、函数等。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;引擎层&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;存储引擎真正的负责了MySQL中数据的存储和提取，服务器通过API和存储引擎进行通信。不同的存储引擎具有不同的功能，这样我们可以根据自己的需要，来选取合适的存储引擎。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;存储层&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;主要是将数据存储在文件系统之上，并完成与存储引擎的交互。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;存储引擎简介&lt;/h1&gt;
&lt;p&gt;建表语句中使用 ENGINE = 引擎名 可以指定使用何种引擎&lt;br /&gt;
查看当前数据库支持的引擎： SHOW ENGINES；&lt;/p&gt;
&lt;h1&gt;存储引擎的特点&lt;/h1&gt;
&lt;h2&gt;InnoDB&lt;/h2&gt;
&lt;p&gt;简介&lt;br /&gt;
InnoDB是一种兼顾高可靠性和高性能的通用存储引擎，在MySQL 5.5之后，InnoDB是默认的MySQL存储引擎。&lt;/p&gt;
&lt;p&gt;特点（事务，外键，行级锁）&lt;br /&gt;
DML操作遵循ACID模型,支持&lt;strong&gt;事务&lt;/strong&gt;&lt;br /&gt;
支持&lt;strong&gt;外键&lt;/strong&gt;FOREIGN KEY约束，保证数据的完整性和正确性&lt;br /&gt;
&lt;strong&gt;行级锁&lt;/strong&gt;，提高并发访问性能&lt;/p&gt;
&lt;p&gt;文件&lt;br /&gt;
xxx.ibd: xxx代表的是表名，innoDB引擎的每张表都会对应这样一个表空间文件，存储该表的表结构(frm、gdi) 、数据和索引。参数: innodb_file_per_table&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/mysql/innodb-table-index.svg&quot; alt=&quot;InnoDB 独立表空间内的数据与索引及回表关系&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：采用独立表空间布局时，表的数据和索引位于对应的 &lt;code&gt;.ibd&lt;/code&gt; 文件。聚集索引的叶子保存完整行，二级索引叶子携带主键；当所需列不能由二级索引覆盖时，再凭主键回表。图中的姓名和主键仅为示例，事务、外键与行级锁则是引擎能力。&lt;/p&gt;
&lt;h2&gt;MyISAM&lt;/h2&gt;
&lt;p&gt;简介&lt;br /&gt;
MylSAM是MySQL早期的默认存储引擎。&lt;/p&gt;
&lt;p&gt;特点&lt;br /&gt;
不支持事务&lt;br /&gt;
不支持外键支持表锁&lt;br /&gt;
不支持行锁访问速度快&lt;/p&gt;
&lt;p&gt;文件&lt;br /&gt;
xxx.sdi:存储表结构信息&lt;br /&gt;
xxx.MYD:存储数据&lt;br /&gt;
xxx.MYI:存储索引&lt;/p&gt;
&lt;h2&gt;Memory&lt;/h2&gt;
&lt;p&gt;介绍&lt;br /&gt;
Memory引擎的表数据时存储在内存中的，由于受到硬件问题、或断电问题的影响，只能将这些表作为临时表或缓存使用。&lt;/p&gt;
&lt;p&gt;特点&lt;br /&gt;
内存存放&lt;br /&gt;
hash索引（默认)&lt;/p&gt;
&lt;p&gt;文件&lt;br /&gt;
xxx.sdi:存储表结构信息&lt;/p&gt;
&lt;p&gt;比较：（来自黑马程序员，侵删）&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;特点&lt;/th&gt;
&lt;th&gt;InnoDB&lt;/th&gt;
&lt;th&gt;MyISAM&lt;/th&gt;
&lt;th&gt;Memory&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;存储限制&lt;/td&gt;
&lt;td&gt;64TB&lt;/td&gt;
&lt;td&gt;有&lt;/td&gt;
&lt;td&gt;有&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;事务安全&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;支持&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;锁机制&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;行锁&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;表锁&lt;/td&gt;
&lt;td&gt;表锁&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B+Tree索引&lt;/td&gt;
&lt;td&gt;支持&lt;/td&gt;
&lt;td&gt;支持&lt;/td&gt;
&lt;td&gt;支持&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hash索引&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;支持&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;全文索引&lt;/td&gt;
&lt;td&gt;支持&lt;/td&gt;
&lt;td&gt;支持&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;空间使用&lt;/td&gt;
&lt;td&gt;高&lt;/td&gt;
&lt;td&gt;低&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;内存使用&lt;/td&gt;
&lt;td&gt;高&lt;/td&gt;
&lt;td&gt;低&lt;/td&gt;
&lt;td&gt;中等&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;批量插入速度&lt;/td&gt;
&lt;td&gt;低&lt;/td&gt;
&lt;td&gt;高&lt;/td&gt;
&lt;td&gt;高&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;支持外键&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;支持&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;存储引擎的选择（总结自黑马程序员MySQL课程，侵删）&lt;/h1&gt;
&lt;p&gt;在选择存储引擎时，应该根据应用系统的特点选择合适的存储引擎。对于复杂的应用系统，还可以根据实际情况选择多种存储引擎进行组合。&lt;/p&gt;
&lt;p&gt;InnoDB:是Mysql的默认存储引擎，支持事务、外键。如果应用对事务的完整性有比较高的要求，在并发条件下要求数据的一致性，数据操怍除了插入和查询之外，还包含很多的更新、删除操作，那么InnoDB存储引擎是比较合适的选择。&lt;/p&gt;
&lt;p&gt;MyISAM:如果应用是以读操作和插入操作为主，只有很少的更新和删除操作，并且对事务的完整性、并发性要求不是很高，那么选择这个存储引擎是非常合适的。（被MongoDB取代）&lt;/p&gt;
&lt;p&gt;MEMORY:将所有数据保存在内存中，访问速度快，通常用于临时表及缓存。MEMORY的缺陷就是对表的大小有限制，太大的表无法缓存在内存中，而且无法保障数据的安全性。（被Redis取代）&lt;/p&gt;
</content:encoded></item><item><title>SSM01-Spring中的Bean</title><link>https://wang-nine.github.io/posts/10301/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10301/</guid><description>简单的介绍了Spring中的架构以及IOC和DI。</description><pubDate>Tue, 23 May 2023 04:00:00 GMT</pubDate><content:encoded>&lt;p&gt;简单的介绍了Spring中的架构以及IOC和DI。&lt;/p&gt;
&lt;h1&gt;Spring FrameWork架构&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;Spring Framework是Spring生态圈中最基础的项目，是其他项目的根基&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Spirng FrameWork的架构&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/java-ssm/spring-modules.svg&quot; alt=&quot;Spring Framework 以核心容器为基础的能力分层&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：核心容器负责 Bean 的创建、依赖装配和应用上下文，是理解本篇 IoC 与 DI 的入口。在这一基础上，AOP 提供方法增强，数据访问与事务模块协调数据库操作，Web MVC 处理请求与响应。图中按职责分层说明各部分如何使用核心能力，不把它当作某一版本所有模块及依赖的完整清单。&lt;/p&gt;
&lt;h1&gt;Spring核心概念&lt;/h1&gt;
&lt;h2&gt;&lt;strong&gt;IoC ( Inversion of Control )控制反转&lt;/strong&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;使用对象时，由主动new产生对象转换为由&lt;strong&gt;外部&lt;/strong&gt;提供对象，此过程中对象创建控制权由程序转移到&lt;strong&gt;外部&lt;/strong&gt;，此思想称为控制反转&lt;/p&gt;
&lt;p&gt;Spring技术对IoC思想进行了实现Spring提供了一个容器，称为IoC容器，用来充当IoC思想中的”&lt;strong&gt;外部&lt;/strong&gt;“&lt;/p&gt;
&lt;p&gt;IoC容器负责对象的创建、初始化等一系列工作，被创建或被管理的对象在IoC容器中统称为Bean&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;&lt;strong&gt;DI ( Dependency Injection)依赖注入&lt;/strong&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;在容器中建立bean与bean之间的依赖关系的整个过程，称为依赖注入&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;IoC入门案例&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;思路分析：&lt;br /&gt;
1.管理什么? （Service与Dao）&lt;br /&gt;
2.如何将被管理的对象告知IoC容器?（配置）&lt;br /&gt;
3.被管理的对象交给IoC容器，如何获取到IoC容器?（接口）&lt;br /&gt;
4.IoC容器得到后，如何从容器中获取bean ?（接口方法）&lt;br /&gt;
5.使用Spring导入哪些坐标?（pom.xml）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;非Spring环境下&lt;/h2&gt;
&lt;p&gt;案例代码如下：&lt;/p&gt;
&lt;p&gt;Service层的代码：&lt;br /&gt;
接口：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package wang.service;

public interface Service {
    public void save();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;实现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package wang.service.impl;

import wang.dao.Dao;
import wang.dao.impl.DaoImpl;
import wang.service.Service;

public class ServiceImpl implements Service {

    private Dao dao = new DaoImpl();

    @Override
    public void save() {
        System.out.println(&quot;Service save() ...&quot;);
        dao.save();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;DAO层的代码：&lt;br /&gt;
接口：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package wang.dao;

public interface Dao {
    public void save();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;实现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package wang.dao.impl;

import wang.dao.Dao;

public class DaoImpl implements Dao {
    @Override
    public void save() {
        System.out.println(&quot;Dao save() ...&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;主函数代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package wang;

import wang.service.Service;
import wang.service.impl.ServiceImpl;

public class App {
    public static void main(String[] args) {
        Service service = new ServiceImpl();
        service.save();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行结果：&lt;br /&gt;
在不使用 Spring 的写法中，主函数主动创建 ServiceImpl，而 ServiceImpl 的属性初始化又主动创建 DaoImpl。调用 service.save() 时，业务层先执行自己的提示语句，再调用 dao.save() 执行数据层逻辑。这段例子的重点是两层协作及对象创建位置：实现类名称直接写进调用代码，更换依赖实现时需要修改这些创建语句，而不只是修改外部配置。&lt;/p&gt;
&lt;h2&gt;Spring环境下&lt;/h2&gt;
&lt;p&gt;别忘记在pom.xml中引入spring-context&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;org.springframework&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;spring-context&amp;lt;/artifactId&amp;gt;
      &amp;lt;version&amp;gt;5.2.10.RELEASE&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在resources中创建配置文件（名称：applicationContext.xml）：如图&lt;br /&gt;
在项目的 resources 资源目录中创建 applicationContext.xml，使它随构建进入运行时类路径。文件以 beans 为根元素，声明正文随后给出的 Spring beans 命名空间与 XML Schema，再用 bean 的 id 指定容器内名称、class 指定实现类全限定名。这里的文件名应与 ClassPathXmlApplicationContext 构造器传入的资源名一致；配置文件的位置和内容才是要点，不依赖编辑器界面的具体样式。&lt;/p&gt;
&lt;p&gt;在配置文件中输入以下的代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;beans xmlns=&quot;http://www.springframework.org/schema/beans&quot;
       xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
       xsi:schemaLocation=&quot;http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd&quot;&amp;gt;

    &amp;lt;!--导入spring的坐标spring-context，对应版本是5.2.10.RELEASE--&amp;gt;

    &amp;lt;!--配置bean--&amp;gt;
    &amp;lt;!--bean标签标示配置bean
    id属性标示给bean起名字
    class属性表示给bean定义类型--&amp;gt;
    &amp;lt;bean id=&quot;Dao&quot; class=&quot;wang.dao.impl.DaoImpl&quot;/&amp;gt;

    &amp;lt;bean id=&quot;Service&quot; class=&quot;wang.service.impl.ServiceImpl&quot;/&amp;gt;
&amp;lt;/beans&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;主函数代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package wang;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import wang.service.Service;

public class App2 {
    public static void main(String[] args) {
        // 获取IoC容器
        ApplicationContext ctx = new ClassPathXmlApplicationContext(&quot;applicationContext.xml&quot;);

        // 获取Bean
        Service service = (Service) ctx.getBean(&quot;Service&quot;);
        service.save();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时运行样例与非Spring方式相同如下：&lt;br /&gt;
主函数现在先读取 applicationContext.xml 建立容器，再按名称 Service 取出业务对象，随后仍然调用同一个 save() 方法，所以业务调用链的职责没有改变。不过此时 ServiceImpl 内部的 Dao 仍然由属性初始化中的 new 创建，配置中另外声明一个 Dao Bean 不会自动替换它。这个阶段完成的是对象获取方式的切换，下一步还需用依赖注入建立两个 Bean 之间的关系。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;综上：可以分为四步&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;1.导入Spring坐标&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2.定义Spring管理的类（接口）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3.创建Spring配置文件进行配置&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4.初始化IoC容器，从容器中获取Bean（getBean方法）&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;问题：业务层中仍然是使用new创建对象，仍然耦合，那么如何解耦合呢？&lt;/strong&gt;&lt;/p&gt;
&lt;h1&gt;DI入门案例&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;思路分析：&lt;br /&gt;
1.基于IoC管理bean&lt;br /&gt;
2.Service使用new形式创建的Dao对象是否保留?（否）&lt;br /&gt;
3.Service中需要的Dao对象如何进入到service中?（提供方法）&lt;br /&gt;
4.Service与Dao间的关系如何描述?（配置）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;改变Service层实现代码如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package wang.service.impl;

import wang.dao.Dao;
import wang.service.Service;

public class ServiceImpl implements Service {
    // 删除业务层以new的方式创建Dao对象
    //private Dao dao = new DaoImpl();
    private Dao dao;

    @Override
    public void save() {
        System.out.println(&quot;Service save() ...&quot;);
        dao.save();
    }

    // 提供对应的set方法
    public void setDao(Dao dao) {
        this.dao = dao;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修改applicationContext.xml中配置：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;bean id=&quot;Service&quot; class=&quot;wang.service.impl.ServiceImpl&quot;&amp;gt;
        &amp;lt;!--7.配置server与dao的关系--&amp;gt;
        &amp;lt;!--property标签表示配置当前bean的属性
        name属性表示配置哪一个具体的属性
        ref属性表示参照哪一个bean--&amp;gt;
        &amp;lt;property name=&quot;dao&quot; ref=&quot;Dao&quot;/&amp;gt;
    &amp;lt;/bean&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意：name是上述的（private Dao dao;）语句中的变量名，ref是bean的名字。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;运行结果与上述一样：&lt;br /&gt;
&lt;img src=&quot;/article-diagrams/java-ssm/setter-di.svg&quot; alt=&quot;容器通过 setDao 装配 Service 与 Dao&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：容器创建 Service 与 Dao 后，依据 property 的 name 找到 dao 属性对应的 setter，再把 ref 指向的 Dao Bean 传入。Service 不再自行 new DaoImpl，但运行时仍按原来的业务链路调用 dao.save()。因此调用职责保持不变，改变的是依赖对象的来源；属性名与 Bean 名称是不同的配置概念。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;综上：可以分为三步&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;1.&lt;strong&gt;删除以new的形式创建对象的代码&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;2.&lt;strong&gt;提供对应的Set方法&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;3.&lt;strong&gt;配置Service与Dao之间的关系&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意在Bean标签中还可以进行以下的配置：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性name：定义Bean的别名&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;属性scope：定义作用范围 singleton（默认，单例） prototype（非单例）&lt;/strong&gt;&lt;/p&gt;
</content:encoded></item><item><title>MySQL02-事务</title><link>https://wang-nine.github.io/posts/10402/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10402/</guid><description>详细的描述了MySQL中的事务的四大特性以及事务的隔离级别。</description><pubDate>Mon, 22 May 2023 14:00:00 GMT</pubDate><content:encoded>&lt;p&gt;详细的描述了MySQL中的事务的四大特性以及事务的隔离级别。&lt;/p&gt;
&lt;h1&gt;事务的简介&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;事务&lt;/strong&gt;是一组操作的集合，它是一个不可分割的工作单位，事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求，即这些操作要么同时成功，要么同时失败。&lt;/p&gt;
&lt;p&gt;默认MySQL的事务是自动提交的，也就是说，当执行一条DML语句，MySQL会立即隐式的提交事务。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;事务的操作&lt;/h1&gt;
&lt;h2&gt;提交事务的方式&lt;/h2&gt;
&lt;h3&gt;1.方式一&lt;/h3&gt;
&lt;h4&gt;① 查看/设置事务的提交方式&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;SELECT @@autocommit;&lt;br /&gt;
SET @@autocommit = 0; —1是自动 0是手动&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;② 提交事务&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;COMMIT; —提交事务&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;③ 回滚事务&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;ROLLBACK; —回滚事务&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;2.方式二（手动开启并且提交）&lt;/h3&gt;
&lt;h4&gt;① 开启事务&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;START TRANSACTION / BEGIN;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;② 提交事务&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;COMMIT；&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;③ 回滚事务&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;ROLLBACK;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;事务的四大特性（ACID）&lt;/h1&gt;
&lt;blockquote&gt;
&lt;h2&gt;原子性（&lt;strong&gt;A&lt;/strong&gt;tomicity)：事务是不可分割的最小操作单元，要么全部成功，要么全部失败。&lt;/h2&gt;
&lt;h2&gt;一致性（Consistency)：事务完成时，必须使所有的数据都保持一致状态。&lt;/h2&gt;
&lt;h2&gt;隔离性（Isolation)：数据库系统提供的隔离机制，保证事务在不受外部并发操作影响的独立环境下运行。&lt;/h2&gt;
&lt;h2&gt;持久性（Durability) :事务一旦提交或回滚，它对数据库中的数据的改变就是永久的。&lt;/h2&gt;
&lt;/blockquote&gt;
&lt;h1&gt;并发事务的问题&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;问题&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;脏读&lt;/td&gt;
&lt;td&gt;一个事务读到另外一个事务还没有提交的数据。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;不可重复读&lt;/td&gt;
&lt;td&gt;一个事务先后读取同一条记录，但两次读取的数据不同，称之为不可重复读。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;幻读&lt;/td&gt;
&lt;td&gt;一个事务按照条件查询数据时，没有对应的数据行，但是在插入数据时，又发现这行数据已存在，好像出现了’幻影”。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;事务的隔离级别&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;隔离级别&lt;/th&gt;
&lt;th&gt;脏读&lt;/th&gt;
&lt;th&gt;不可重复读&lt;/th&gt;
&lt;th&gt;幻读&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Read Uncommitted&lt;/td&gt;
&lt;td&gt;√&lt;/td&gt;
&lt;td&gt;√&lt;/td&gt;
&lt;td&gt;√&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read Committed&lt;/td&gt;
&lt;td&gt;×&lt;/td&gt;
&lt;td&gt;√&lt;/td&gt;
&lt;td&gt;√&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Repeatable Read（默认）&lt;/td&gt;
&lt;td&gt;×&lt;/td&gt;
&lt;td&gt;×&lt;/td&gt;
&lt;td&gt;√&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Serializable&lt;/td&gt;
&lt;td&gt;×&lt;/td&gt;
&lt;td&gt;×&lt;/td&gt;
&lt;td&gt;×&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</content:encoded></item><item><title>MySQL01-基础语法</title><link>https://wang-nine.github.io/posts/10401/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10401/</guid><description>详细的描述了MySQL中的基础语法。</description><pubDate>Sun, 21 May 2023 14:00:00 GMT</pubDate><content:encoded>&lt;p&gt;详细的描述了MySQL中的基础语法。&lt;/p&gt;
&lt;h1&gt;SQL语法&lt;/h1&gt;
&lt;h2&gt;SQL通用语法&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;1.SQL语句可以单行或多行书写，以分号结尾。&lt;/li&gt;
&lt;li&gt;2.SQL语句可以使用空格/缩进来增强语句的可读性。&lt;/li&gt;
&lt;li&gt;3.MySQL数据库的SQL语句不区分大小写，关键字建议使用大写。&lt;/li&gt;
&lt;li&gt;4.注释：
&lt;ul&gt;
&lt;li&gt;单行注释：—注释内容或#注释内容(MySQL特有)&lt;/li&gt;
&lt;li&gt;多行注释：/&lt;em&gt;注释内容&lt;/em&gt;/&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;SQL分类&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;分类&lt;/th&gt;
&lt;th&gt;全称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DDL&lt;/td&gt;
&lt;td&gt;Data Definition Language&lt;/td&gt;
&lt;td&gt;数据定义语言，用来定义数据库对象(数据库，表，字段)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DML&lt;/td&gt;
&lt;td&gt;Data Manipulation Language&lt;/td&gt;
&lt;td&gt;数据操作语言，用来对数据库表中的数据进行增删改&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DQL&lt;/td&gt;
&lt;td&gt;Data Query Language&lt;/td&gt;
&lt;td&gt;数据查询语言，用来查询数据库中表的记录&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DCL&lt;/td&gt;
&lt;td&gt;Data Control Language&lt;/td&gt;
&lt;td&gt;数据控制语言，用来创建数据库用户、控制数据库的访问权限&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;DDL(Data Definition Language 数据定义语言)&lt;/h1&gt;
&lt;h2&gt;数据库操作&lt;/h2&gt;
&lt;h3&gt;查询&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;查询所有数据库&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;SHOW DATABASES;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;查询当前数据库&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;SELECT DATABASE();
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;创建&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;CREATE DATABASE [IF NOT EXISTS] 数据库名 [DEFAULT CHARSET 字符集] [COLLATE 排序规则];
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;删除&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;DROP DATABASE [IF EXISTS] 数据库名;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;使用&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;USE 数据库名;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;表操作&lt;/h2&gt;
&lt;h3&gt;查询&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;查询当前数据库所有表&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;SHOW TABLES;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;查询表结果&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;DESC 表明;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;查询指定表的建表语句&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;SHOW CREATE TABLE 表名;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;创建&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;CREATE TABLE表名(
	字段1 字段1类型[COMMENT 字段1注释],
    字段2 字段2类型[COMMENT 字段2注释],
    字段3 字段3类型[COMMENT 字段3注释],
    ......
	字段n 字段n类型[COMMENT 字段n注释]
)[COMMENT 表注释];
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;最后一个字段后面没有逗号&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;字段类型如下&lt;/h4&gt;
&lt;h5&gt;数值类型&lt;/h5&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;大小&lt;/th&gt;
&lt;th&gt;有符号(SIGNED)范围&lt;/th&gt;
&lt;th&gt;无符号(UNSIGNED)范围&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TINYINT&lt;/td&gt;
&lt;td&gt;1 byte&lt;/td&gt;
&lt;td&gt;(-128,127)&lt;/td&gt;
&lt;td&gt;(0，255)&lt;/td&gt;
&lt;td&gt;小整数值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SMALLINT&lt;/td&gt;
&lt;td&gt;2 bytes&lt;/td&gt;
&lt;td&gt;(-32768，32767)&lt;/td&gt;
&lt;td&gt;(0，65535)&lt;/td&gt;
&lt;td&gt;大整数值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MEDIUMINT&lt;/td&gt;
&lt;td&gt;3 bytes&lt;/td&gt;
&lt;td&gt;(-8388608，8388607)&lt;/td&gt;
&lt;td&gt;(0，16777215)&lt;/td&gt;
&lt;td&gt;大整数值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;INT或INTEGER&lt;/td&gt;
&lt;td&gt;4 bytes&lt;/td&gt;
&lt;td&gt;(-2147483648，2147483647)&lt;/td&gt;
&lt;td&gt;(0，4294967295)&lt;/td&gt;
&lt;td&gt;大整数值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BIGINT&lt;/td&gt;
&lt;td&gt;8 bytes&lt;/td&gt;
&lt;td&gt;(-2^63^，2^63^-1)&lt;/td&gt;
&lt;td&gt;(0，2^64^-1)&lt;/td&gt;
&lt;td&gt;极大整数值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FLOAT&lt;/td&gt;
&lt;td&gt;4 bytes&lt;/td&gt;
&lt;td&gt;(-3.402823466 E+38，3.402823466351 E+38)&lt;/td&gt;
&lt;td&gt;0和(1.175494351 E-38，3.402823466 E+38)&lt;/td&gt;
&lt;td&gt;单精度浮点数值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DOUBLE&lt;/td&gt;
&lt;td&gt;8 bytes&lt;/td&gt;
&lt;td&gt;(-1.7976931348623157 E+308，1.7976931348623157 E+308)&lt;/td&gt;
&lt;td&gt;0和(2.2250738585072014 E-308，1.7976931348623157 E+308)&lt;/td&gt;
&lt;td&gt;双精度浮点数值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DECIMAL&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;依赖于M(精度)和D(标度)的值&lt;/td&gt;
&lt;td&gt;依赖于M(精度)和D(标度)的值&lt;/td&gt;
&lt;td&gt;小数值(精确定点数)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h5&gt;字符串类型&lt;/h5&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;大小&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CHAR&lt;/td&gt;
&lt;td&gt;0-255 bytes&lt;/td&gt;
&lt;td&gt;定长字符串&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VARCHAR&lt;/td&gt;
&lt;td&gt;0-65535 bytes&lt;/td&gt;
&lt;td&gt;变长字符串&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TINYBLOB&lt;/td&gt;
&lt;td&gt;0-255 bytes&lt;/td&gt;
&lt;td&gt;不超过255个字符的二进制数据&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TINYTEXT&lt;/td&gt;
&lt;td&gt;0-255 bytes&lt;/td&gt;
&lt;td&gt;短文本字符串&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BLOB&lt;/td&gt;
&lt;td&gt;0-65 535 bytes&lt;/td&gt;
&lt;td&gt;二进制形式的长文本数据&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TEXT&lt;/td&gt;
&lt;td&gt;0-65 535 bytes&lt;/td&gt;
&lt;td&gt;长文本数据&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MEDIUMBLOB&lt;/td&gt;
&lt;td&gt;0-16 777 215 bytes&lt;/td&gt;
&lt;td&gt;二进制形式的中等长度文本数据&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MEDIUMTEXT&lt;/td&gt;
&lt;td&gt;0-16 777 215 bytes&lt;/td&gt;
&lt;td&gt;中等长度文本数据&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LONGBLOB&lt;/td&gt;
&lt;td&gt;0-4 294 967 295 bytes&lt;/td&gt;
&lt;td&gt;二进制形式的极大文本数据&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LONGTEXT&lt;/td&gt;
&lt;td&gt;0-4 294 967 295 bytes&lt;/td&gt;
&lt;td&gt;极大文本数据&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h5&gt;日期类型&lt;/h5&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;类型&lt;/th&gt;
&lt;th&gt;大小&lt;/th&gt;
&lt;th&gt;范围&lt;/th&gt;
&lt;th&gt;格式&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DATE&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1000-01-01 至 9999-12-31&lt;/td&gt;
&lt;td&gt;YYYY-MM-DD&lt;/td&gt;
&lt;td&gt;日期值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TIME&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;-838:59:59 至 838:59:59&lt;/td&gt;
&lt;td&gt;HH:MM:SS&lt;/td&gt;
&lt;td&gt;时间值或持续时间&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;YEAR&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1901 至 2155&lt;/td&gt;
&lt;td&gt;YYYY&lt;/td&gt;
&lt;td&gt;年份值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DATETIME&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;1000-01-01 00:00:00 至 9999-12-31 23:59:59&lt;/td&gt;
&lt;td&gt;YYYY-MM-DD HH:MM:SS&lt;/td&gt;
&lt;td&gt;混合日期和时间值&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TIMESTAMP&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1970-01-01 00:00:01 至 2038-01-19 03:14:07&lt;/td&gt;
&lt;td&gt;YYYY-MM-DD HH:MM:SS&lt;/td&gt;
&lt;td&gt;混合日期和时间值 时间戳&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;修改&lt;/h2&gt;
&lt;h3&gt;字段&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;添加&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;ALTER TABLE 表名 ADD 字段名 类型(长度) [COMMENT 注释][约束];
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;修改
&lt;ul&gt;
&lt;li&gt;修改数据类型&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;ALTER TABLE 表名 MODIFY 字段名 新数据类型(长度);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;修改字段名和字段类型&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;ALTER TABLE 表名 CHANGE 旧字段名 新字段名 类型(长度) [COMMENT 注释][约束];
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;删除&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;ALTER TABLE 表名 DROP 字段名;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;表&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;修改表名&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;ALTER TABLE 表名 RENAME TO 新表名;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;删除
&lt;ul&gt;
&lt;li&gt;删除表&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;DROP TABLE [IF EXISTS] 表名;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;删除指定表，并重新创建该表&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;TRUNCATE TABLE 表名;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;DML(Data Manipulation Language 数据操作语言)&lt;/h1&gt;
&lt;h2&gt;添加数据&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;给指定字段添加数据&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;INSERT INTO 表名(字段名1, 字段名2, ...) VALUES (值1, 值2, ...);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;给全部字段添加数据&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;INSERT INTO 表名 VALUES (值1, 值2, ...);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;批量添加数据&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;INSERT INTO 表名(字段名1, 字段名2, ...) VALUES (值1, 值2, ...), (值1, 值2, ...), (值1, 值2, ...);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;INSERT INTO 表名 VALUES (值1, 值2, ...), (值1, 值2, ...), (值1, 值2, ...);
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;注意&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;插入数据时，指定的字段顺序需要与值的顺序是一一对应&lt;/li&gt;
&lt;li&gt;字符串和日期型数据应该包含在引号中&lt;/li&gt;
&lt;li&gt;插入的数据大小，应该在字段的规定范围内。&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h2&gt;修改数据&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;UPDATE 表名 SET 字段名1 = 值1, 字段名2 = 值2, ... [WHERE 条件];
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;注意：修改语句的条件可以有，也可以没有，如果没有条件，则会修改整张表的所有数据。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;删除数据&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;DELETE FROM 表名 [WHERE 条件];
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;注意&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DELETE语句的条件可以有，也可以没有，如果没有条件，则会删除整张表的所有数据&lt;/li&gt;
&lt;li&gt;DELET语句不能删除某一个字段的值(可以使用UPDATE)&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h1&gt;DQL(Data Query Language 数据查询语言，用来查询数据库中表的记录)&lt;/h1&gt;
&lt;h2&gt;语法&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;SELECT
	字段列表
FROM
	表名列表
WHERE
	条件列表
GROUP BY
	分组字段列表
HAVING
	分组后条件列表
ORDER BY
	排序字段列表
LIMIT
	分页参数
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;基本查询&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;查询多个字段&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;SELECT 字段1, 字段2, 字段3 ... FROM 表名;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;SELECT * FROM 表名;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;设置别名&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;SELECT 字段1 [AS 别名1],字段2[AS 别名2] ... FROM 表名;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;去除重复记录&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;SELECT DISTINCT 字段列表 FROM 表名;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;条件查询(WHERE)&lt;/h2&gt;
&lt;h2&gt;聚合函数(count、max、min、avg、sum)&lt;/h2&gt;
&lt;h2&gt;分组查询(GROUP BY)&lt;/h2&gt;
&lt;h2&gt;排序查询(ORDER BY)&lt;/h2&gt;
&lt;h2&gt;分页查询(LIMIT)&lt;/h2&gt;
</content:encoded></item><item><title>博客搭建教程总览</title><link>https://wang-nine.github.io/posts/10000/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10000/</guid><description>Hexo+Butterfly主题博客搭建01</description><pubDate>Fri, 19 May 2023 16:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;Hexo+Butterfly主题博客搭建01&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10001/&quot;&gt;Hexo+Butterfly主题博客搭建01&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Hexo+Butterfly主题博客搭建02&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10002/&quot;&gt;Hexo+Butterfly主题博客搭建02&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Hexo+Butterfly主题博客搭建03&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10003/&quot;&gt;Hexo+Butterfly主题博客搭建03&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>SSM总览</title><link>https://wang-nine.github.io/posts/10300/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10300/</guid><description>SSM01-Spring中的Bean</description><pubDate>Fri, 19 May 2023 16:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;SSM01-Spring中的Bean&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10301/&quot;&gt;SSM01-Spring中的Bean&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;SSM02-Spring中Bean的实例化与依赖注入&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10302/&quot;&gt;SSM02-Spring中Bean的实例化与依赖注入&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;SSM03-Spring的纯注解开发模式&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10303/&quot;&gt;SSM03-Spring的纯注解开发模式&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;SSM04-Spring整合MyBatis&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10304/&quot;&gt;SSM04-Spring整合MyBatis&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;SSM05-Spring中的AOP概念&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10305/&quot;&gt;SSM05-Spring中的AOP概念&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;SSM06-Spring中的事务&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10306/&quot;&gt;SSM06-Spring中的事务&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;SSM07-SpringMVC的请求与响应&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10307/&quot;&gt;SSM07-SpringMVC的请求与响应&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;SSM08-REST风格&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10308/&quot;&gt;SSM08-REST风格&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>MySQL总览</title><link>https://wang-nine.github.io/posts/10400/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10400/</guid><description>MySQL01-事务</description><pubDate>Fri, 19 May 2023 16:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;MySQL01-事务&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10401/&quot;&gt;MySQL01-事务&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;MySQL02-存储引擎&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10402/&quot;&gt;MySQL02-存储引擎&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;MySQL03-索引01-结构&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10403/&quot;&gt;MySQL03-索引01-结构&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;MySQL03-索引02-性能分析&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10404/&quot;&gt;MySQL03-索引02-性能分析&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;MySQL03-索引03-使用规则&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10405/&quot;&gt;MySQL03-索引03-使用规则&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;MySQL04-SQL优化&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10406/&quot;&gt;MySQL04-SQL优化&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;MySQL05-存储过程&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10407/&quot;&gt;MySQL05-存储过程&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;MySQL06-存储函数&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10408/&quot;&gt;MySQL06-存储函数&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;MySQL07-触发器&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10409/&quot;&gt;MySQL07-触发器&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;MySQL08-视图&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10410/&quot;&gt;MySQL08-视图&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;MySQL09-锁&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10411/&quot;&gt;MySQL09-锁&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>HTML5+CSS3总览</title><link>https://wang-nine.github.io/posts/10500/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10500/</guid><description>HTML5+CSS301-HTML</description><pubDate>Fri, 19 May 2023 16:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;HTML5+CSS301-HTML&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10501/&quot;&gt;HTML5+CSS301-HTML&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;HTML5+CSS302-CSS&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10502/&quot;&gt;HTML5+CSS302-CSS&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;HTML5+CSS307-综合页面设计&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10503/&quot;&gt;HTML5+CSS303-综合页面设计&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;HTML5+CSS304-前端项目学习&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10504/&quot;&gt;HTML5+CSS304-前端项目学习&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;HTML5+CSS305-移动端Web&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;/posts/10505/&quot;&gt;HTML5+CSS305-移动端Web&lt;/a&gt;&lt;/p&gt;
</content:encoded></item><item><title>Java中的多线程</title><link>https://wang-nine.github.io/posts/10101/</link><guid isPermaLink="true">https://wang-nine.github.io/posts/10101/</guid><description>详细的描述了Java中多线程的创建、使用、同步问题，以及线程池的创建、使用问题，详细的描述了定时器的创建和使用，详细的分析了Java中六种不同的线程转态以及转换。</description><pubDate>Fri, 19 May 2023 14:00:00 GMT</pubDate><content:encoded>&lt;p&gt;详细的描述了Java中多线程的创建、使用、同步问题，以及线程池的创建、使用问题，详细的描述了定时器的创建和使用，详细的分析了Java中六种不同的线程转态以及转换。&lt;/p&gt;
&lt;h1&gt;1.Java中的多线程创建&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;java中多线程的创建可以分为三种不同的形式：&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;① 继承Thread类&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;具体步骤：&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;1.定义一个类MyThread继承&lt;br /&gt;
2.创建MyThread对象&lt;br /&gt;
3.调用线程对象的start方法启动线程（启动后会自动执行MyThread中重写的run()方法）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;具体的例子如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class ThreadDemo1 {
    public static void main(String[] args) {
        /**
         * 3.new一个线程对象
         */
        Thread t = new MyThread();
        /**
         * 4.调用start()方法执行线程
         */
        t.start();

        for (int i = 0; i &amp;lt; 5; i++) {
            System.out.println(&quot;主线程执行输出&quot; + i);
        }
    }
}

/**
 * 1.定义一个线程类继承Thread
 */
class MyThread extends Thread {
    /**
     * 2.重写run方法
     */
    @Override
    public void run() {
        for (int i = 0; i &amp;lt; 5; i++) {
            System.out.println(&quot;子线程执行输出&quot; + i);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意：&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;1.为什么不直接调用run方法，而是调用start启动线程&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;答：直接调用run方法会当成普通方法执行，此时还是相当于单线程执行。&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;2.为什么主线程任务放在子线程之后？&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;答：否则会先执行主线程任务再执行子线程任务。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优缺点：&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;优点：编程简单&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;缺点：单继承的局限性，不能继承其他类，不便于扩展。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;② 实现Runnable接口&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;具体步骤：&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;1.定义一个线程任务类MyRunnable实现Runnable接口，重写run方法&lt;br /&gt;
2.创建MyRunnable对象&lt;br /&gt;
3.把MyRunnable对象交给Thread处理&lt;br /&gt;
具体的例子如下&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;public class ThreadDemo2 {
    public static void main(String[] args) {
        /**
         * 2.创建MyRunnable对象
         */
        Runnable target = new MyRunnable();

        /**
         * 3.把MyRunnable对象交给Thread处理
         */
        Thread t = new Thread(target);
        t.start();

        for (int i = 0; i &amp;lt; 5; i++) {
            System.out.println(&quot;主线程执行输出&quot; + i);
        }
    }
}


/**
 * 1.定义一个线程任务类MyRunnable实现Runnable接口，重写run方法
 */
class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i &amp;lt; 5; i++) {
            System.out.println(&quot;子线程执行输出&quot; + i);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;优缺点：&lt;br /&gt;
优点：扩展性强，可以继续继承和实现。&lt;br /&gt;
缺点：如果线程有执行结果不能直接返回。&lt;/p&gt;
&lt;p&gt;注意：也可通过匿名内部类实现（建议的写法）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;/**
 * 通过匿名内部类来实现
 */
new Thread(new MyRunnable() {
    @Override
    public void run() {
        for (int i = 0; i &amp;lt; 5; i++) {
            System.out.println(&quot;匿名内部类子线程的输出&quot; + i);

        }
    }
}).start();
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;③ 实现Callable接口，通过FutureTask接口接收返回值（JDK5新增）&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;1.得到任务对象&lt;br /&gt;
第一步：定义一个线程任务类MyCallable实现Callable接口，重写call方法，该方法可以返回结果&lt;br /&gt;
第二步：用Future吧Callable对象封装成线程任务对象&lt;br /&gt;
2.把线程任务对象交给Thread处理&lt;br /&gt;
3.调用Thread的start方法启动任务&lt;br /&gt;
4.线程执行完毕后，通过FutureTask的get方法获得结果&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;具体的例子如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class ThreadDemo3 {
    public static void main(String[] args) {
        /**
         * 3.创建任务对象
         */
        Callable&amp;lt;String&amp;gt; call1 = new MyCallable(100);

        /**
         * 4.把Callable任务对象交给FutureTask对象
         * FutureTask的作用1：FutureTask实现了Runnable接口，此时就可以交给Thread了
         * FutureTask的作用2：可以在线程执行完毕后调用get方法得到线程执行的结果
         */
        //Thread t = new Thread(call);  报错，Thread不能接收call对象
        FutureTask&amp;lt;String&amp;gt; f1 = new FutureTask&amp;lt;&amp;gt;(call1);

        /**
         * 5.交给线程处理
         */
        Thread t1 = new Thread(f1);

        /**
         * 6.启动线程
         */
        t1.start();



        Callable&amp;lt;String&amp;gt; call2 = new MyCallable(200);
        FutureTask&amp;lt;String&amp;gt; f2 = new FutureTask&amp;lt;&amp;gt;(call2);
        Thread t2 = new Thread(f2);
        t2.start();

        try {
            String rs1 = f1.get();   //直接调用call方法，可能还没有执行完，使用get时若发现线程未执行完会先等线程执行完毕
            System.out.println(&quot;第一个结果为：&quot; + rs1);
        } catch (Exception e) {
           e.printStackTrace();
        }

        try {
            String rs2 = f2.get();   //直接调用call方法，可能还没有执行完，使用get时若发现线程未执行完会先等线程执行完毕
            System.out.println(&quot;第二个结果为：&quot; + rs2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


/**
 * 1.定义一个实现类，实现Callable接口，记得声明结果的数据类型
 */
class MyCallable implements Callable&amp;lt;String&amp;gt; {
    /**
     * 2.重写call方法
     */

    private int n;

    public MyCallable(int n) {
        this.n = n;
    }

    @Override
    public String call() throws Exception {
        int sum = 0;
        for (int i = 1; i &amp;lt;= n; i++) {
            sum += i;
        }
        return &quot;子线程执行的结果是&quot; + sum;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;优缺点：&lt;br /&gt;
优点：扩展性强，可以继续继承和实现。&lt;br /&gt;
可以在线程执行完毕后获取线程执行的结果。&lt;br /&gt;
缺点：编程较为复杂。&lt;/p&gt;
&lt;p&gt;注意：Callable 与 FutureTask 创建对象时都需要声明类型&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;2.Java中的多线程常用方法&lt;/h1&gt;
&lt;h2&gt;① 线程名称有关的方法&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;void setName()&lt;/td&gt;
&lt;td&gt;设置线程名称&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;String getName(String name)&lt;/td&gt;
&lt;td&gt;获得线程名称&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CuurentThread()&lt;/td&gt;
&lt;td&gt;获取当前的线程对象，哪个线程执行，拿到哪个线程对象&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;也可在构造器中取名&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;public Thread(String name)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;public Thread(Runnable target)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;public Thread(Runnable target, String name)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;② 线程休眠有关的方法&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;sleep(long time)&lt;/td&gt;
&lt;td&gt;单位为ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;具体的例子如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class ThreadDemo1 {
    public static void main(String[] args) throws Exception {
        Thread t1 = new MyThread(&quot;一号线程&quot;);
        t1.start();
//        t1.setName(&quot;一号线程&quot;);
        System.out.println(t1.getName());

        Thread t2 = new MyThread(&quot;二号线程&quot;);
        t2.start();
//        t2.setName(&quot;二号线程&quot;);
        System.out.println(t2.getName());

        //哪个线程执行，拿到哪个线程对象
        Thread m = Thread.currentThread();
        System.out.println(m.getName());
        for (int i = 1; i &amp;lt;= 5; i++) {
            System.out.println(&quot;Main线程输出&quot; + i);
            if(i == 3)
                Thread.sleep(3 * 1000); //主线程休眠3000ms
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;线程的子类，自定义的线程MyThread&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class MyThread extends Thread{
    public MyThread(String name) {
        //让父类的构造器进行构造
        super(name);
    }

    @Override
    public void run() {
        for (int i = 1; i &amp;lt;= 5; i++) {
            System.out.println(Thread.currentThread().getName() + &quot;输出&quot; + i);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;3.Java中的线程安全问题&lt;/h1&gt;
&lt;h2&gt;问题描述&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;多个线程同时操作一个共享资源可能会出现业务安全问题，称为线程安全问题&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;例如小明和小红有一个共同账户，余额为10万元，模拟两人同时取钱10万元&lt;br /&gt;
基础的定义代码如下：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;① 提供一个账户类，创建一个账户对象代表两个人的共享账户&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class Account {
    private String cardId;
    private double money;   //余额


    public Account() {
    }

    public Account(String cardId, double money) {
        this.cardId = cardId;
        this.money = money;
    }

    public void DrawMoney(double money) {
        String name = Thread.currentThread().getName(); //获取取钱人的姓名
        //1.判断账户中的钱是否足够
        if (this.money &amp;gt;= money) {
            //2.取钱
            System.out.println(name + &quot;来取钱成功，吐出：&quot; + money);
            //3.更新余额
            this.money -= money;
            System.out.println(&quot;取钱后剩余：&quot; + this.money);
        } else {
            //4.余额不足
            System.out.println(name + &quot;来取钱，余额不足！&quot;);
        }
    }

    /**
     * 获取
     * @return cardId
     */
    public String getCardId() {
        return cardId;
    }

    /**
     * 设置
     * @param cardId
     */
    public void setCardId(String cardId) {
        this.cardId = cardId;
    }

    /**
     * 获取
     * @return money
     */
    public double getMoney() {
        return money;
    }

    /**
     * 设置
     * @param money
     */
    public void setMoney(double money) {
        this.money = money;
    }

    public String toString() {
        return &quot;Account{cardId = &quot; + cardId + &quot;, money = &quot; + money + &quot;}&quot;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;② 定义一个线程类，用来处理账户对象&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/**
 * 取钱的线程类
 */
public class DrawThread extends Thread {
    private Account acc;    //定义一个账户对象，便于对账户对象进行操作

    public DrawThread(Account acc, String name) {
        super(name);
        this.acc = acc;
    }

    @Override
    public void run() {
        acc.DrawMoney(100000);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;③ 创建两个线程对象，传入同一个账户对象&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;④ 启动两个线程，去同一个账户对象中取钱10万&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/**
 * 模拟取钱案例
 */
public class ThreadDemo {
    public static void main(String[] args) {
        /**
         * 1.定义线程类，创建一个共享的账户对象
         */
        Account acc = new Account(&quot;ICBC-999&quot;, 100000);

        /**
         * 2.创建两个线程对象，代表小明和小红同时取钱
         */
        new DrawThread(acc, &quot;小明&quot;).start();
        new DrawThread(acc, &quot;小红&quot;).start();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意会出现下面的错误样例：&lt;/p&gt;
&lt;p&gt;两个取钱线程共享同一个 Account，但余额判断和扣款之间没有互斥保护。如果两者都在第一次扣款完成前通过余额检查，就可能都进入取款分支，破坏余额不能被重复支取的约束。问题在于检查与更新不是一个不可分割的操作；某次看似正常的打印顺序并不能证明线程安全，也不能把某个最终余额当作每次运行的固定结果。&lt;/p&gt;
&lt;h2&gt;解决方法：线程同步&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;针对上述错误解决线程安全问题：线程同步（同一个时刻仅有一个线程可以访问共享资源）&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;① 同步代码块&lt;/h3&gt;
&lt;p&gt;synchorized(同步锁对象) {&lt;br /&gt;
操作共享资源的代码（核心代码）&lt;br /&gt;
}&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意：同步锁对象对于不同的对象来说唯一即可，同一时间只能被一个人占用，因此可以使用字符串为同步锁对象，字符串在常量池中&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;注意：上述同步锁对象对于小明小红唯一，但是若有小黑小白另一家庭取钱，此时也会等待，此时会出问题。&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;锁对象使用任意的唯一对象可以吗？&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;答：不可以，会影响其他无关线程的执行。&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;锁对象的规范要求：使用共享资源作为锁对象&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;因此：对于实例方法：使用this作为锁对象&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;对于静态方法：使用字节码（类名.class)作为锁对象&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;实例代码如下(修改了上述的代码之后，结果正确)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public void DrawMoney(double money) {
    String name = Thread.currentThread().getName(); //获取取钱人的姓名
    //同步代码块
    synchronized (this) {
        //1.判断账户中的钱是否足够
        if (this.money &amp;gt;= money) {
            //2.取钱
            System.out.println(name + &quot;来取钱成功，吐出：&quot; + money);
            //3.更新余额
            this.money -= money;
            System.out.println(&quot;取钱后剩余：&quot; + this.money);
        } else {
            //4.余额不足
            System.out.println(name + &quot;来取钱，余额不足！&quot;);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果如下：&lt;/p&gt;
&lt;p&gt;同步代码块把余额检查、取款提示和余额更新都放在同一个 Account 对象的监视器保护下。按本例初始余额与取款金额，先获锁的一方完成扣款后，另一方再检查时余额已不足；谁先取到钱仍由调度决定。关键不是指定打印顺序，而是让所有访问同一账户的取款线程使用同一把锁，避免同时通过旧余额检查。&lt;/p&gt;
&lt;h3&gt;② 同步方法&lt;/h3&gt;
&lt;p&gt;修饰符 synchorized 返回值类型 方法名称(形参列表) {&lt;br /&gt;
操作共享资源的代码（核心代码）&lt;br /&gt;
}&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public synchronized void DrawMoney(double money) {     //同步方法
    String name = Thread.currentThread().getName(); //获取取钱人的姓名
    //1.判断账户中的钱是否足够
    if (this.money &amp;gt;= money) {
        //2.取钱
        System.out.println(name + &quot;来取钱成功，吐出：&quot; + money);
        //3.更新余额
        this.money -= money;
        System.out.println(&quot;取钱后剩余：&quot; + this.money);
    } else {
        //4.余额不足
        System.out.println(name + &quot;来取钱，余额不足！&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意：同步方法底层也有锁对象，为隐式锁对象，锁的范围是整个方法代码&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;如果方法是实例方法，会用this作为同步锁对象&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;如果方法是静态方法，会用类名.class作为同步锁对象&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意，上述两种方法中，同步代码块性能较好，范围越小性能越好，相当于一个在厕所外面排队，一个在坑外面排队，但是同步方法更好些更直观，在实际应用中多数使用同步方法&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;③ 同步锁（JDK5后提供了新的锁对象Lock，更加的灵活方便）&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Lock是接口，采用实现类ReentrantLock来构建Lock锁对象&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;常用api如下&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;public ReentrantLock()&lt;/td&gt;
&lt;td&gt;获得Lock锁的实现类对象&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;void lock()&lt;/td&gt;
&lt;td&gt;获得锁&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;void unlock()&lt;/td&gt;
&lt;td&gt;释放锁&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre&gt;&lt;code&gt;//用final修饰，锁对象是唯一且不可替换的
private final Lock lock = new ReentrantLock();
public void DrawMoney(double money) {     //同步方法
    String name = Thread.currentThread().getName(); //获取取钱人的姓名
    //1.判断账户中的钱是否足够
    lock.lock();    //上锁
    try {
        if (this.money &amp;gt;= money) {
            //2.取钱
            System.out.println(name + &quot;来取钱成功，吐出：&quot; + money);
            //3.更新余额
            this.money -= money;
            System.out.println(&quot;取钱后剩余：&quot; + this.money);
        } else {
            //4.余额不足
            System.out.println(name + &quot;来取钱，余额不足！&quot;);
        }
    } finally {
        lock.unlock();  //解锁
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;注意：一、锁对象声明为final代表锁是唯一且不可替换的&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;二、unlock放在finally中，防止代码出错而不执行unlock导致程序卡死&lt;/strong&gt;&lt;/p&gt;
&lt;h1&gt;4.Java中的多线程通信（综合以上内容的综合样例）&lt;/h1&gt;
&lt;p&gt;常用的模型：生产者与消费者模型&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;object中提供的等待唤醒方法如下&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;void wait()&lt;/td&gt;
&lt;td&gt;当前线程等待并且释放所占锁，直到另外一个线程调用notify()或者notifyaAll&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;void notify()&lt;/td&gt;
&lt;td&gt;唤醒正在等待的单个线程&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;void notufyAll()&lt;/td&gt;
&lt;td&gt;唤醒正在等待的所有线程&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;上述方法应该使用当前同步锁对象调用&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;注意：一定是先唤醒别人在等待，相当于自己叫醒别人再晕过去，否则相当于自己先晕过去没法叫人。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;实例代码如下&lt;br /&gt;
&lt;strong&gt;账户类：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class Account {
    private String cardId;
    private double money;   //余额

    public Account(String cardId, double money) {
        this.cardId = cardId;
        this.money = money;
    }

    /**
     * 小红 小明：取钱
     *
     * @param money
     */
    public synchronized void DrawMoney(double money) {
        String name = Thread.currentThread().getName(); //取钱人
        try {
            if (this.money &amp;gt;= money) {
                // 钱够，可以取钱
                this.money -= money;
                System.out.println(name + &quot;取钱成功，取出&quot; + money + &quot;元！余额是：&quot; + this.money);
                // 没钱了
                // 钱不够
                this.notifyAll();   //唤醒其他所有的线程
                this.wait();    //锁对象，让当前线程进入等待
            } else {
                // 钱不够，不可取
                // 唤醒别人存钱，等待自己取钱
                this.notifyAll();   //唤醒其他所有的线程
                this.wait();    //锁对象，让当前线程进入等待
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * 亲爹 干爹 岳父：取钱
     *
     * @param money
     */
    public synchronized void depositMoney(double money) {
        String name = Thread.currentThread().getName(); //存钱人
        try {
            if (this.money == 0) {
                //没钱了，存钱
                this.money += money;
                System.out.println(name + &quot;存钱&quot; + money + &quot;元成功，余额为&quot; + this.money + &quot;元！&quot;);
                // 存完钱了
                this.notifyAll();   //唤醒其他所有的线程
                this.wait();    //锁对象，让当前线程进入等待
            } else {
                // 有钱了，不用存钱
                this.notifyAll();   //唤醒其他所有的线程
                this.wait();    //锁对象，让当前线程进入等待
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public String getCardId() {
        return cardId;
    }

    public void setCardId(String cardId) {
        this.cardId = cardId;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;取钱线程类：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/**
 * 取钱的线程类
 */
public class DrawThread extends Thread {
    private Account acc;    //定义一个账户对象，便于对账户对象进行操作

    public DrawThread(Account acc, String name) {
        super(name);
        this.acc = acc;
    }

    @Override
    public void run() {
        //不断的存取前
        while (true) {
            acc.DrawMoney(100000);
            try {
                Thread.sleep(3 * 1000);     //每隔三秒取一次钱
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;存钱线程类：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/**
 * 取钱的线程类
 */
public class DepositThread extends Thread {
    private Account acc;    //定义一个账户对象，便于对账户对象进行操作

    public DepositThread(Account acc, String name) {
        super(name);
        this.acc = acc;
    }

    @Override
    public void run() {
        //不断的存取前
        while (true) {
            acc.depositMoney(100000);
            try {
                Thread.sleep(2 * 1000);     //每隔两秒存一次钱
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;主程序：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class ThreadDemo {
    public static void main(String[] args) {
        // 三个父亲线程（生产者） 两个孩子线程（消费者） 模拟存取钱通信思想（一存 一取）
        // 1.创建账户对象，代表五个人共同操作的账户
        Account acc = new Account(&quot;ICBC-999&quot;, 0);   //初始化零元

        // 2.创建两个取钱线程 小明 小红
        new DrawThread(acc, &quot;小明&quot;).start();
        new DrawThread(acc, &quot;小红&quot;).start();

        // 3.创建三个存钱线程 亲爹 干爹 岳父
        new DepositThread(acc, &quot;亲爹&quot;).start();
        new DepositThread(acc, &quot;干爹&quot;).start();
        new DepositThread(acc, &quot;岳父&quot;).start();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行结果&lt;/p&gt;
&lt;p&gt;这里三个存钱线程与两个取钱线程围绕同一账户协作：存钱逻辑检查余额是否为零，取钱逻辑检查余额是否足够，并通过账户对象的 notifyAll() 与 wait() 传递继续工作的机会。wait() 会释放该对象的监视器，notifyAll() 不会立即释放锁，也不保证谁先执行。实际使用应在 while 条件循环中等待并在醒来后重查条件，不能把本例的 if 与固定人物交替当成可靠的生产者消费者协议。&lt;/p&gt;
&lt;h1&gt;5.线程池的创建以及使用&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;线程池是一个可以复用线程的技术。解决了每次都为新请求创建线程，导致开销较大的问题&lt;/strong&gt;&lt;br /&gt;
JDK5.0起提供了代表线程池的接口：ExecutorService&lt;br /&gt;
得到线程池对象的两种办法：&lt;br /&gt;
一、使用ExecutorService的实现类ThreadPoolExecutor自己创建一个线程池对象。&lt;br /&gt;
二、使用Executors（线程池的工具类）调用方法放回不同特点的线程池对象。&lt;/p&gt;
&lt;h2&gt;① 线程池的API简介&lt;/h2&gt;
&lt;p&gt;ThreadPoolExecutor构造器：&lt;br /&gt;
public ThreadPoolExecutor（int corePoolSize&lt;br /&gt;
int maximumPoolSize,&lt;br /&gt;
long keepAliveTIme,&lt;br /&gt;
TimeUnit unit,&lt;br /&gt;
BlockingQueue workQueue,&lt;br /&gt;
ThreadFactory threadFactory,&lt;br /&gt;
RejectedExecutionHandler handler)&lt;br /&gt;
参数一：指定线程池的线程数量（核心线程）：corePoolsize ——-&amp;gt; 不能小于0&lt;br /&gt;
参数二：指定线程池可支持的最大线程数：maximumPoolSize ———&amp;gt; 最大数量&amp;gt;=核心线程数量&lt;br /&gt;
参数三：指定临时线程的最大存活时间：keepAliveTime ———&amp;gt; 不能小于0&lt;br /&gt;
参数四：存活时间的单位（秒，分，时，天）：unit ———&amp;gt; 时间单位&lt;br /&gt;
参数五：指定任务队列：workQueue ———&amp;gt; 不能为null&lt;br /&gt;
参数六：指定用那个线程工厂创建线程：threadFactory ———&amp;gt; 不能为null&lt;br /&gt;
参数七：指定线程忙，任务慢的时候，新任务来了怎么办，即拒绝策略：handler ———&amp;gt; 不能为null&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;（重要！！！）注意两点：&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;临时线程什么时候创建：新任务提交时发现核心线程都在忙，任务队列满，还可以创建临时线程时才会创建。&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;什么时候开始拒绝任务：核心线程和临时线程都在忙，任务队列都满，新任务过来会拒绝。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;ExecutorService的常用方法：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;void execute(Runable command)&lt;/td&gt;
&lt;td&gt;执行任务，无返回值，一般用于执行Runnable任务&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Future\ submit(Callable\ Task)&lt;/td&gt;
&lt;td&gt;执行任务，返回未来任务对象获取线程结果，一般用于执行Callable任务&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;void shutdown()&lt;/td&gt;
&lt;td&gt;等待任务执行完毕后关闭线程池&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;List\ shutdownNow()&lt;/td&gt;
&lt;td&gt;立即关闭，停止正在执行的任务，返回队列中未执行的任务&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;拒绝策略：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;策略&lt;/th&gt;
&lt;th&gt;详解&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ThreadPoolExecutor.AbortPolicy&lt;/td&gt;
&lt;td&gt;丢弃任务抛出RejectedExecutionException异常&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ThreadPoolExecutor.DiscardPolicy&lt;/td&gt;
&lt;td&gt;丢弃任务不抛出异常（不推荐）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ThreadPoolExecutor.DiscardOldestPolicy&lt;/td&gt;
&lt;td&gt;抛弃等待最久的任务并把当前任务加入队列&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ThreadPoolExecutor.CallerRunsPolicy&lt;/td&gt;
&lt;td&gt;由主线程负责调用的run()方法绕过线程池直接执行，即来新任务主线程亲自服务&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;② 线程池处理Runnable任务&lt;/h2&gt;
&lt;p&gt;样例代码：&lt;br /&gt;
Runnable实现代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i &amp;lt; 5; i++) {
            System.out.println(Thread.currentThread().getName() + &quot;输出了：HelloWorld&quot; + i);
        }
        // 睡眠线程，观察线程的执行情况
        try {
            System.out.println(Thread.currentThread().getName() + &quot;本任务与线程绑定了，进入休眠状态了~~~&quot;);
            Thread.sleep(1000000);  //让其睡眠时间久一点，便于观察线程池的复用情况
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;线程池创建代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import java.util.concurrent.*;

public class threadPoolDemo1 {
    public static void main(String[] args) {
        // 1.创建线程池对象
        /**
         *     public ThreadPoolExecutor(int corePoolSize,
         *                               int maximumPoolSize,
         *                               long keepAliveTime,
         *                               TimeUnit unit,
         *                               BlockingQueue&amp;lt;Runnable&amp;gt; workQueue,
         *                               ThreadFactory threadFactory,
         *                               RejectedExecutionHandler handler) {
         */
        ExecutorService pool = new ThreadPoolExecutor(3, 5, 6,
                TimeUnit.SECONDS, new ArrayBlockingQueue&amp;lt;&amp;gt;(5), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

        // 2.把任务给线程池处理
        Runnable target = new MyRunnable();
        //核心线程运行
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);

        //不创建临时线程
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);       //任务队列满了！！核心线程都忙！！

        //创建临时线程
        pool.execute(target);
        pool.execute(target);

        //拒绝策略触发
        pool.execute(target);

        //关闭线程池（开发一般不使用，线程池作用就是长久存活）
        //pool.shutdownNow();     //立即关闭，即使任务未完成也关闭
        //pool.shutdown();    //等待任务执行完毕再关闭
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;拒绝策略样例：&lt;br /&gt;
本例线程池的核心线程数为三、最大线程数为五，等待队列容量为五，并配置 AbortPolicy。在前面提交的任务仍被长时间休眠占住、尚未完成的条件下，前三个任务启动核心线程，接着五个任务入队，再来两个任务使用扩展线程；继续提交就会触发拒绝并抛出 RejectedExecutionException。这是配置和任务占用情况的推导，不代表任何负载下第十一个任务都必然被拒绝。&lt;/p&gt;
&lt;p&gt;线程池运行样例：&lt;br /&gt;
每次 execute(target) 都提交一次 Runnable 执行，即使传入同一个任务对象，也不代表线程池只执行一次。任务先循环打印，再进入长时间休眠；休眠期间工作线程仍被这次任务占用，队列中的任务必须等待工作线程腾出位置。线程复用发生在一次任务结束之后，因此不能把多个线程名称的交错输出等同于队列任务已经全部完成，也不应依赖输出的固定顺序。&lt;/p&gt;
&lt;h2&gt;③ 线程池处理Callable任务&lt;/h2&gt;
&lt;p&gt;样例代码（简单测试一下，Callable实现在上面写过，具体使用方式与Runnable一致）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import java.util.concurrent.*;

public class threadPoolDemo2 {
    public static void main(String[] args) throws Exception {
        // 1.创建线程池对象
        /**
         *     public ThreadPoolExecutor(int corePoolSize,
         *                               int maximumPoolSize,
         *                               long keepAliveTime,
         *                               TimeUnit unit,
         *                               BlockingQueue&amp;lt;Runnable&amp;gt; workQueue,
         *                               ThreadFactory threadFactory,
         *                               RejectedExecutionHandler handler) {
         */
        ExecutorService pool = new ThreadPoolExecutor(3, 5, 6,
                TimeUnit.SECONDS, new ArrayBlockingQueue&amp;lt;&amp;gt;(5), Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

        // 2.把任务给线程池处理
        Future&amp;lt;String&amp;gt; f1 = pool.submit(new MyCallable(100));
        Future&amp;lt;String&amp;gt; f2 = pool.submit(new MyCallable(200));
        Future&amp;lt;String&amp;gt; f3 = pool.submit(new MyCallable(300));
        Future&amp;lt;String&amp;gt; f4 = pool.submit(new MyCallable(400));

        System.out.println(f1.get());
        System.out.println(f2.get());
        System.out.println(f3.get());
        System.out.println(f4.get());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;线程池运行样例：&lt;br /&gt;
四次 submit 分别提交参数为一百、二百、三百和四百的 MyCallable，每个任务按前面的 call() 实现累加从一到该上限的整数，并把说明字符串作为结果。主线程按 f1、f2、f3、f4 的顺序调用 get()，所以结果的读取顺序由这几行代码决定，并不表示任务按同样顺序完成；若对应任务尚未结束，get() 会等待，任务失败则通过 Future 暴露异常。&lt;/p&gt;
&lt;h2&gt;④ （重要！） Executors工具类实现线程池&lt;/h2&gt;
&lt;p&gt;Executors获得线程池的常用方法：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名称&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;public static ExecutorService newCachedThreadPool()&lt;/td&gt;
&lt;td&gt;线程数随着任务增加而增加，若线程任务执行完毕且空闲一段时间会被回收&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;public static ExecutorService newFixedThreadPool()&lt;/td&gt;
&lt;td&gt;创建固定数量的线程池。若某个线程因为异常而结束，那么线程池会补充一个新线程代替它&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;public static ExecutorService newSingleThreadExecutor()&lt;/td&gt;
&lt;td&gt;创建只有一个线程对象的线程池对象，如果该线程因为异常而结束，那么线程池会补充一个新线程&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;public static ScheduledExecutorService newScheduledThreadPool(int PoolSize)&lt;/td&gt;
&lt;td&gt;创建一个线程池，可以实现在给定的延迟后运行任务，或者定期执行任务&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意：Executors的底层其实也是基于线程池的实现类ThreadPoolExecutor创建线程池对象&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;注意：大型并发环境使用Executors如果不注意可能会出现系统风险，例如阿里开发手册不建议使用Executors创建线程池对象，而是建 议使用ThreadPoolExecutor自己创建&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法名称&lt;/th&gt;
&lt;th&gt;存在问题&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;public static ExecutorService newCachedThreadPool()&lt;/td&gt;
&lt;td&gt;允许请求的任务队列长度为Integer.MAX_VALUE，可能出现OOM错误(OutOfMemoryError)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;public static ExecutorService newFixedThreadPool()&lt;/td&gt;
&lt;td&gt;同上&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;public static ExecutorService newSingleThreadExecutor()&lt;/td&gt;
&lt;td&gt;创建的线程数量上限为Integer.MAX_VALUE，线程数随着任务1:1增长，可能出现OOM错误(OutOfMemoryError)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;public static ScheduledExecutorService newScheduledThreadPool(int PoolSize)&lt;/td&gt;
&lt;td&gt;同上&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;6.补充知识&lt;/h1&gt;
&lt;h2&gt;① 定时器&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;定时器是一种控制任务延时调用，或者周期调用的技术&lt;br /&gt;
&lt;strong&gt;作用：闹钟，定时邮件发送&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;**定时器的两种实现方式如下**&lt;/p&gt;
&lt;h3&gt;Timer&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;构造器&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;public Timer()&lt;/td&gt;
&lt;td&gt;创建Timer定时器对象&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方法&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;public void schedule(TimerTask task, long delay, long period）&lt;/td&gt;
&lt;td&gt;开启一个定时器，按照计划处理TimerTask任务，delay是延时，period是周期&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意：TimerTask是Runnable的实现类&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;存在的问题：&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;Timer是单线程，处理多个任务有序执行，存在演示与设置定时器的时间有出入&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;可能因为某个任务的异常使得Timer线程死掉，从而影响后续任务的执行&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;样例代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class TimerDemo1 {
    public static void main(String[] args) {
        // 1.创建Timer定时器
        Timer timer = new Timer();  //本身就是一个单线程

        // 2.调用方法，给定定时任务
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + &quot;执行任务A~~~&quot; + new Date());
                try {
                    Thread.sleep(5000); //休眠5s
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, 0, 2000); //三秒后执行，每隔两秒执行一次

        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + &quot;执行任务B~~~&quot; + new Date());
            }
        }, 0, 2000);

        // 注意，本来A应该7s一次（2s间隔 + 5s睡眠），B应该2s一次
        // 实际情况与上述不符合，只存在一个线程，
        // B任务受到了A任务的影响
        // 其次，如果中间某个线程异常，其余线程都死亡
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行样例如下:&lt;br /&gt;
两个任务都提交给同一个 Timer，而 Timer 只有一条执行任务的线程。任务 A 内部休眠五秒时，这条线程仍被占用，任务 B 即使已到计划时间也只能等待；设置两秒周期并不保证任务能准点开始，也不能简单把实际周期恒定相加成七秒。若任务抛出未捕获的运行时异常并终止 Timer 线程，其余待调度任务也会受到影响，因此应把串行占用与定时参数分别理解。&lt;/p&gt;
&lt;h3&gt;ScheduledExecutorService&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;ScheduledExecutorService弥补了Timer的缺陷&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Executors的方法&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;public static ScheduledExecutorService newScheduledThreadPool(int PoolSize&lt;/td&gt;
&lt;td&gt;得到线程池对象&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ScheduledExecutorService的方法&lt;/th&gt;
&lt;th&gt;说明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;public Scheduled&amp;lt;?&amp;gt; scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)&lt;/td&gt;
&lt;td&gt;周期调度方法&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;优点：基于线程池，某个任务的执行情况不会影响其他任务的执行&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;样例代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class TimerDemo2 {
    public static void main(String[] args) {
        // 1.创建ScheduledExecutorService线程池对象
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(3);

        // 2.开启定时任务
        pool.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + &quot;执行任务A~~~&quot;);
                //把该线程堵塞
                try {
                    Thread.sleep(100000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, 0, 2, TimeUnit.SECONDS);

        pool.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + &quot;执行任务B~~~&quot;);
                System.out.println(10 / 0); //使该线程异常
            }
        }, 0, 3, TimeUnit.SECONDS);


        pool.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + &quot;执行任务C~~~&quot;);
            }
        }, 0, 3, TimeUnit.SECONDS);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行样例如下：&lt;br /&gt;
本例创建三条工作线程，分别周期调度任务 A、B、C。A 长时间休眠会占用一条工作线程，但同一周期任务不会因此并发重入；B 的除零异常会终止 B 这次周期任务后续的调度，而不是让整个线程池和 C 一起停止。只要仍有可用工作线程，C 可以继续被调度，但线程池资源不足时其他任务仍会延迟，因此“互不影响”应理解为任务异常被隔离，而非无条件准点执行。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;异常不会影响&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;也不受其他任务的影响&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;② 并发与并行&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;并发与并行：&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;正在运行的程序（软件）就是一个独立的进程，线程是属于进程的，多个线程其实是并发与并行同时进行的。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;并发的理解：&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;CPU同时处理线程的数量有限。&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;CPU会轮询为系统的每个线程服务，由于CPU切换的速度很快，给我们的感觉这些线程在同时执行，这就是并发。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;并行的理解：&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;在同一个时刻上，同时有多个线程在被CPU处理并执行。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;③ （重点！！！）线程的生命周期&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;线程的状态:&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;也就是线程从生到死的过程，以及中间经历的各种状态及状态转换。理解线程的状态有利于提升并发编程的理解能力。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Java线程的状态：&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;Java总共定义了6种状态&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;6种状态都定义在Thread类的内部枚举类中。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;线程6种状态的转换如下图&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/article-diagrams/java-ssm/thread-states.svg&quot; alt=&quot;Java 线程六种状态及典型转换条件&quot; /&gt;&lt;/p&gt;
&lt;p&gt;原理示意：NEW 调用 start() 后进入 RUNNABLE，后者同时包括等待调度与执行中的情况。竞争对象监视器失败进入 BLOCKED，等待其他线程动作可进入 WAITING，带时限的等待进入 TIMED_WAITING。wait() 释放监视器，醒来后还要重新竞争锁；sleep() 则不释放已经持有的锁，超时也不意味着马上得到执行机会。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意以下几点：&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;sleep()是睡眠，过程中不会释放锁对象，醒来后直接进入可运行状态&lt;/strong&gt;&lt;br /&gt;
&lt;strong&gt;wait()是等待，过程中会释放锁对象，但是时间到了或者被notity时会重新获得锁对象，进入可运行状态&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;**总结：**&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;线程状态&lt;/th&gt;
&lt;th&gt;描述&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NEW（新建）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;线程刚被创建，但是并未启动。&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Runnable（可运行）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;线程已经调用了start()等待CPU调度&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Blocked（锁阻塞）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;线程在执行的时候未竞争到锁对象，则该线程进入Blocked状态&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Waiting（无限等待）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;一个线程进入Waiting状态，另一个线程调用notify或者notifyAll方法才能够唤醒&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Time Waiting（计时等待）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;同waiting状态，有几个方法有超时参数，调用他们将进入Timed waiting状态。带有超时参数的常用方法有Thread.sleep和Object.wait&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Teminated（被终止）&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;因为run方法正常退出而死亡，或者因为没有捕获的异常终止了run方法而死亡。&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</content:encoded></item></channel></rss>