Pytorch

PyTorch是一个开源的Python机器学习库,基于Torch库,底层由C++实现,应用于人工智能领域,如计算机视觉和自然语言处理。它最初由Meta Platforms的人工智能研究团队开发,现在属于Linux基金会的一部分。它是在修改后的BSD许可证下发布的自由及开放源代码软件。

许多深度学习软件都是基于 PyTorch 构建的,包括特斯拉自动驾驶、Uber的Pyro、Hugging Face的Transformers、 PyTorch Lightning。

TensorBoard

打开localhost:6006,访问TensorBoard,查看TensorFlow模型的图形、损失函数、精度等信息。

1
2
3
4
5
from tensorboardX import SummaryWriter
writer = SummaryWriter("xxx") # 在文件夹里新建一个abc文件夹
writer.add_scalar("y=2i", 2*i, i) # 加坐标
writer.add_images("test_data", imgs, step) # 加图
writer.close()
1
2
# Pycharm -> Terminal
PS C:\Users\cf\Documents\Visual Studio Code\Python\learn_pytorch> tensorboard --logdir="xxx"

nn.Module

基本神经网络

1
2
3
4
5
class cf(nn.Module):
def __int__(self): super().__init__()
def forward(self, input): # 必须重写forword()
output = input + 1
return output

输入层(Input Layer):接受原始图像或数据作为输入。
卷积层(Convolutional Layer):进行特征提取。通过卷积操作,利用卷积核(或过滤器)从输入图像中提取特定的特征,如边缘、纹理等。卷积核会在整个图像上滑动,产生特征图。
卷积核的输入通道数与输入数据的通道数对应,卷积核的输出通道数与卷积核的数量对应。
卷积操作在神经网络中扮演着关键的角色,它通过特征提取、参数共享、空间局部性和参数数量控制等方面,使得卷积神经网络成为图像处理和计算机视觉任务中非常有效的工具。
池化层(Pooling Layer)【最大ceil, 最小floor】:降采样和减少参数,避免过拟合。池化操作对特征图进行降采样,减少特征图的尺寸,并保留重要的特征信息。常用的池化方式为最大池化(Max Pooling)和平均池化(Average Pooling)。
非线性激活:线性变换(如全连接层)在神经网络中只能实现线性映射,而现实世界中的数据和任务通常都是非线性的。为了解决这个问题,需要在网络中引入非线性激活函数,以增加网络的表达能力和灵活性。 给网络中引入非线性特征,以训练出符合各种曲线的模型。
·ReLU(Rectified Linear Unit):f(x) = max(0, x),它在x大于0时是线性的,小于等于0时为0,能够有效地解决梯度消失问题。
·Sigmoid:f(x) = 1 / (1 + exp(-x)),它将输入映射到[0, 1]区间,用于二分类问题。
·Tanh:f(x) = (exp(x) - exp(-x)) / (exp(x) + exp(-x)),它将输入映射到[-1, 1]区间,也用于解决梯度消失问题。
正则化层(Regularization Layer):正则化层用于防止过拟合。过拟合是指模型在训练数据上表现良好,但在未见过的测试数据上表现较差的现象。正则化层通过添加一些额外的约束或惩罚项来控制模型的复杂度,以避免过度拟合。
·L1正则化:将参数的绝对值加入到损失函数中,使得模型倾向于产生稀疏的权重矩阵。
·L2正则化:将参数的平方加入到损失函数中,限制权重的大小,防止权重过大造成过拟合。
·Dropout正则化:随机在训练过程中丢弃一部分神经元,减少神经元之间的共适应性,提高泛化能力。
全连接层(Fully Connected Layer):将特征矩阵集合向量化。与卷积层不同,全连接层的神经元排成一列,这些神经元与前一层神经元通过权值互连,呈全连接结构。全连接层的层数以及每层神经元数并不固定。通常层数越高,神经元数目越少。
功能:进行分类。在卷积和池化层之后,通过全连接层将得到的特征映射转换为一维向量,并通过一系列的全连接神经元进行分类操作,将输入映射到对应的类别概率。
输出层(Output Layer):经过多层特征提取后,最后一层输出层可视为分类器,预测输入样本的类别。通常使用Softmax函数将全连接层的输出转换为类别概率分布,确定输入图像最可能属于哪个类别。

Sequential

1
2
3
4
5
6
7
8
9
10
11
self.model1 = Sequential(
Conv2d(in_channels=3, out_channels=32, kernel_size=5, padding=2),
MaxPool2d(kernel_size=2, ceil_mode=True),
Conv2d(32, 32, 5, padding=2), # 二维卷积层
MaxPool2d(2),
Conv2d(32, 64, 5, padding=2),
MaxPool2d(2),
Flatten(),
Linear(1024, 64),
Linear(64, 10)
)

一次训练+验证:准备数据 - 加载数据 - 准备模型 - 设置损失函数 - 设置优化器 - 开始训练 - 验证 - 聚合展示

1
2
3
4
5
6
7
8
9
10
11
for epoch in range(20):  # 共20轮训练
running_loss = 0.0
for data in dataloader: # 对数据进行一轮学习
imgs, targets = data
output = fc(imgs)
result_loss = loss(output, targets) # 计算损失
optim.zero_grad() # 优化器中,梯度置0
result_loss.backward() # 损失函数求梯度,反向传播
optim.step() # 优化器,优化卷积核参数
running_loss = running_loss + result_loss
print(running_loss)

Colab

Google Colab是谷歌提供的免费Jupyter笔记本环境,不需要配置环境(本质是Linux虚拟机),可以加!运行bash命令。提供一定免费的GPU,可以跑 Tensorflow、Pytorch 等深度学习框架。Google Colab提供的资源量是受限制的,所有 Colab 运行时都会在一段时间后重置。Colab Pro 订阅者的使用量仍会受到限制,但相比非订阅者可享有的限额要多出大约一倍。Colab Pro+ 订阅者还可获享更高的稳定性。

使用Colab训练比较重要的是处理好路径的关系,找到哪个文件在哪里,文件夹的执行目录在哪里,就可以比较简单的运行起程序了,不过Colab确实存在断线问题,我们需要时刻保存好文件,因此我将权值直接保存在云盘上,这样也不会丢失。
12h的限额比较难受,有时候没到时间就限额了。因此需要及时保存训练的模型,长时间不用gpu的情况下也会被限额(Pro版也一样),因此训练完建议及时改成gpu或停止。

Google Drive

谷歌云盘,谷歌云端硬盘。免费用户可以获取15G的空间,付费用户根据套餐可以选择最大20TB的储存空间。
虚拟机根目录为 /content,谷歌云盘地址为 /content/Drive/MyDrive

深度学习库的下载

使用git clone指令进行下载,然后通过cd指令将根目录转移到了xx文件夹。此时根目录为 /content/xx

1
2
!git clone https://github.com/xx/xx.git
%cd xx/

数据集与预训练权重的上传

数据集压缩,上传Google Drive,在colab中打开的jupyter文件(等于打开了一个Linux虚拟机本地)中挂载Google Drive;将数据集从Drive中复制到当前根目录,解压。直接将数据集布置在谷歌云盘会导致大量的云盘数据传输,且谷歌云盘和虚拟机之间存在通信带宽,速度远不及本地(虚拟机)文件,因此需要将数据集复制解压到本地(虚拟机)里进行处理。 (https://blog.csdn.net/weixin_44791964/article/details/123659637?spm=1001.2014.3001.5501)

1
2
!cp /content/Drive/MyDrive/xx.zip ./
!unzip ./xx.zip -d ./

预训练权重存放在 /content/Drive/MyDrive/Models/xx,然后创建logs和model_data。model_data放置的是预训练文件,logs放置的是网络训练过程中产生的权值。

保存路径设置

如果将权值保存在当前根目录下的logs文件夹(/content/xx/logs),发生断线网络就白训练了,浪费大量的时间。
可以将google云盘软连接到根目录下,那么即使断线,权值也保留在云盘中。
本文之前在云盘中创建了logs文件夹(/content/Drive/MyDrive/Models/xx/logs),将该文件夹链接到当前根目录下的logs文件夹中。

1
!ln -s /content/Drive/MyDrive/Models/xx/logs logs   # 即/content/xx/logs

开始训练

1、标注文件的处理
2、训练文件的处理
1)预训练文件的使用。
2)保存周期的设置,这个设置是因为云盘的存储空间有限,每代都保存会导致存储空间满出。
3、开始训练:!python train.py

常用 colab 命令

1
2
3
4
5
6
7
8
9
10
import shutil
shutil.copytree("复制的文件夹","目标路径+目标路径下的文件夹") # 复制路径下的文件夹
shutil.copytree("/content/drive/My Drive/channel","/content/drive/My Drive/channel10") # ex
shutil.rmtree("移除路径下的文件夹") # 移除路径下的文件夹
shutil.copy("","") # 复制单个文件的命令 同上
# 删除单个文件可以右键删除
!pwd # 显示当前目录, /content 为根目录
import os
os.chdir("/content/federated-learning") # 变更根目录操作
!python "/content/federated-learning/main_nn.py" # 运行python 文件

Git

First Project:)being confused and embrassed… luckily no one is staring at you but willing to help,as long as you try to find and explain a bug,try debugging、、

Git Bash

  • 分布式版本控制工具 git:没有“中央服务器”,每个人的电脑上都是一个完整的版本库(区别于集中式版本库管理系统如 svn)。
  • Git Bash:Git提供的命令行工具,可运行linux命令 cd、ls、touch…
    1
    git config --list                         # 查看配置
  • 配置 Git 的全局用户信息,这些信息在你提交代码到 Git 仓库时会被记录下来,用于标识提交代码的作者信息。
    1
    2
    git config --global user.name "xx"        # 配置 user.name caifeng
    git config --global user.email "xx" # 配置 user.email 1908454905@qq.com

Github

  • leo710aka @qq.com xxxxxxx
  • 使用 SSH 密钥连接 GitHub:如果没有的话,使用???以下命令在本地生成 SSH 密钥
    1
    ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
    生成 SSH 密钥后,将 ~/.ssh/id_rsa.pub(公钥)的内容添加到 GitHub 账号的 SSH 密钥设置中。
  • 使用 HTTPS 认证连接 GitHub:在 GitHub 上克隆仓库时,使用 HTTPS 链接,然后在推送更改时,会提示输入 GitHub 账号的用户名和密码,或者使用个人访问令牌(Personal Access Token)代替账号密码。
  • 访问令牌(Access Token)是一种用于通过API进行身份验证的方式。
    • 创建访问令牌:1. 登录到你的GitHub帐户。 2. 点击右上角的头像,选择 “Settings”。 3. 在左侧导航栏中选择 “Developer settings”。 4. 点击 “Personal access tokens”。 5. 点击 “Generate token”。 6. 提供一个描述,选择需要的权限(scope),然后点击 “Generate token”。 7. 复制生成的访问令牌。请注意,生成后会显示一次,如果忘记了,需要重新生成。
    • 使用访问令牌:可以通过多种方式使用,例如在命令行中、在脚本中或者在应用程序中。
    • Token和SSH密钥是两种不同的身份验证机制,用于在GitHub或其他版本控制系统中进行身份验证。存在一些区别。
      用途不同: 访问令牌用于API身份验证,而SSH密钥用于远程仓库的SSH访问。
      获取方式不同: 访问令牌通过GitHub设置生成,而SSH密钥需要用户生成并添加到GitHub帐户。
      身份验证方式不同: 访问令牌通过HTTP标头进行身份验证,而SSH密钥使用SSH协议进行本地身份验证。
      适用场景不同: 访问令牌适用于与GitHub API进行交互,而SSH密钥适用于直接使用Git与远程仓库进行交互。

Git 指令

  1. clone (克隆):从远程仓库中克隆代码到本地仓库。会下载远程仓库的所有分支的引用,但只会检出[chekout]默认分支到本地。
  2. checkout (检出) :从本地仓库中检出一个仓库分支然后进行修订
  3. add (添加):在提交前先将代码提交到暂存区
  4. commit (提交): 提交到本地仓库。本地仓库中保存修改的各个历史版本
  5. fetch (抓取): 从远程库抓取到本地仓库。这会更新所有远程分支的引用信息,但不会自动合并这些更新到本地分支。
  6. merge(合并): 将指定的分支合并到当前分支。
  7. pull(拉取) : 相当于fetch+merge,获取最新的远程提交,但只合并当前检出的本地分支与其对应的远程分支,然后放到到工作区。
  8. push (推送) :修改完成后,需要和团队成员共享代码时,将代码推送到远程仓库

本地仓库

  • 要使用Git对我们的代码进行版本控制,首先需要创建本地仓库
    (1) 在电脑的任意位置创建一个空目录(文件夹) 作为我们的本地Git仓库
    (2) 进入这个目录中,右键打开Git bash
    (3) 执行命令git init
    (4) 如果创建成功后可在文件夹下看到隐藏的.git目录。
  • 新建仓库中的文件,其状态都是未被跟踪的(未纳入版本管理)
    git add <name> / git add .:跟踪一个/所有文件或目录的状态,将其纳入版本管理(即暂存?),记录其状态或内容变化
    git rm <name>:删除文件
    git rm --cache <name>:保留文件但不再跟踪
  • 添加,提交,推送:在IDEA中,点击Git提交并推送,选择提交、添加信息、修改远程仓库的分支(默认origin:main),推送。
    1
    2
    3
    4
    5
    6
    $ git status                                       # 查看当前状态(红色的字体显示的是修改的文件
    $ git add 单个文件名|通配符(. 添加所有修改) # 添加工作区代码到本地git暂存区
    $ git commit -m "提交的信息" # 提交暂存区代码到本地git仓库(生成一个版本)
    $ git log # 查看提交日志信息(head指向当前操作的分支)
    $ git push <远程仓库名> <本地分支名> # 推送本地代码到远程仓库
    $ git push <远程仓库名> <本地分支名>:<远程分支名> # 将指定的本地分支推送到远程仓库的指定分支

远程仓库

  • 上传本地仓库项目到远程
    本地git bash生成ssh公钥,添加到gitee中,本地验证,添加远程仓库
    1
    2
    3
    4
    5
    6
    $ ssh-keygen -t rsa                                       # 生成ssh密钥
    $ cat ~/.ssh/id_rsa.pub # 查看密钥
    $ ssh -T git@gitee.com # 验证
    $ git remote add origin git@gitee.com:leo710aka/test.git # 添加远程仓库名为origin(别名),加ssh地址
    $ git remote -v # 检查远程仓库配置是否正确
    $ git push origin master # 完成添加、提交后,将本地master分支推送至远程origin仓库
    或者直接在idea中,对一个项目文件添加、提交后,直接在idea中git remote,使用github账号密码进行远程仓库的连接,然后推送。(连接远程仓库,要么使用ssh密钥,要么用github账号密码。)
  • 添加远程仓库项目到本地
    1
    2
    $ git clone <远程仓库地址>                 # 克隆远程仓库到本地(默认分支master)
    $ git clone -b <分支名> <远程仓库地址> # 克隆指定分支(如develop)
    也可以先从远程仓库git clone下来一个项目,完成修改后对项目文件添加、提交,在推送到远程仓库时,再进行 ssh密钥/github账号密码 的验证。

分支

  • 实际开发新功能,是在develop分支上创建feature分支,完成后合并到develop分支(部门开发分支)上,经测试,最后合并到master分支(线上分支,主分支,项目正在上线的分支)
    1
    2
    3
    4
    5
    6
    7
    8
    $ git branch                  # 查看本地所有的分支,当前激活的分支会在列表中以星号(*)标识。
    $ git branch -r # 查看远程仓库的分支
    $ git branch -a # 查看所有分支,包括本地分支和远程分支。
    $ git branch caifeng # 从当前分支上新建分支
    $ git checkout caifeng # 切换分支
    $ git checkout -b caifeng # 创建(若不存在)并切换分支
    $ git checkout -d caifeng # 删除分支(-D 强制删除)
    $ git merge <分支名> # 先切换回如master分支上,将其他分支上的提交合并到master上
  • 分支合并
    1
    2
    3
    $ git merge [remote name] [branch name]   
    $ git log --merge # 显示合并冲突的提交日志
    $ git log --graph --oneline --all # 可视化分支和合并历史,以简化理解合并的情况
  • 更新项目
    1
    2
    $ git fetch [remote name] [branch name]   # 抓取仓库里的更新到本地,不会进行合并
    $ git pull [remote name] [branch name] # 拉取远端仓库的修改拉到本地并自动进行合并,等同于fetch+merge
    在一段时间,A、B用户修改了同一个文件,且修改了同一行位置的代码,此时会发生合并冲突A用户在本地修改代码后优先推送到远程仓库,此时B用户在本地修订代码,提交到本地仓库后,也需要推送到玩程仓库,此时B用户晚于A用户,故需要先拉取远程仓库的提交,经过合并后才能推送到远端分支
    或直接在IDEA里面选择分支pull(Git拉取),克隆/更新后记得修改配置文件为测试/本地(如果有)。
  • 冲突:当两个分支上对文件的修改可能会存在冲突,例如同时修改了同一个文件的同一行,这时就需要手动解决冲突,解决步骤、、
    1.处理文件中冲突的地方
    2.将解决完冲突的文件加入暂存区(add)
    3,提交到仓库(commit)

经典Git流模型

其他操作

  • 贮藏代码:当前代码没写完,要切换分支时,必须先提交当前代码或贮藏

    1
    $ git stash    
    1
    2
    $ git pop   # 弹出暂存的修改后销毁
    $ git apply # 可以反复使用某个暂存
  • 版本回退

    1
    2
    $ git reset --hard <提交id>    # 通过提交id切换版本
    $ git reflog # 查看历史操作(已清除提交日志、版本切换日志。。。
  • 分支变基:将一系列的提交从一个分支上 “移动” 到另一个分支的起点,并将它们重新应用(或重新播放)到目标分支上。

    1
    $ git rebase   # 可以让提交历史更线性、更简洁

hexo | Hello World

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Hexo

Hexo 是一个基于 Node.js 的静态博客框架,提供简单的方式来创建和部署静态博客,使作者专注于写作而不必担心后端服务器的维护。
Hexo 使用 Markdown 作为主要的文章撰写语言,这是一种轻量级的标记语言,使得写作变得简单且易于阅读。
Hexo 生成的是静态页面,因此页面加载速度非常快。这对于提供更好的用户体验和搜索引擎优化(SEO)非常有利。
Hexo 使用版本控制系统(通常是 Git)来管理博客内容,因此用户可以轻松地追踪和管理博客文章的历史。
Hexo 可以将博客部署到各种平台,包括 GitHub Pages、Netlify、Vercel 等。这使得博客的部署变得非常简单。

配置 hexo

https://www.bilibili.com/video/BV1Eg41157tL

1
2
$ git config --global user.name "leo710aka"
$ git config --global user.email "1908454905@qq.com"

_cnfig.yml不能直接复制粘贴过去。。

Create a new post

在C:\Users\cf\Documents\Visual Studio Code\blog下右键Git Bash Here

1
$ hexo new "文件名"

或者直接在..\source\posts下编写md文件. More info: Writing

Run server

1
$ hexo s

在本地服务器看看效果. http://localhost:4000/

Clean、 Generate static files 、Deploy to remote sites

1
2
3
$ hexo clean   # 清理缓存、清理之前生成的静态文件
$ hexo g # 构建网页文件(通过md文件生成html、css、js..)
$ hexo d # 同步github,更新博客上的内容(这一步可能需要多试几次)

没办法同步的话,直接把新构建的 E:\Code\Blog\blog\.deploy_git 中的文件手动上传github仓库;
成功后若博客内容未更新,ctrl+f5清下缓存。

其他配置

_config.yml_config.landscape 中设置,修改配置后先 hexo clean 一下,再发布。

hexo d 报错 Error: Spawn failed ?

  1. 寻找github.com的最新ip,加了host解析后,能够ping通,但hexo d仍失败
  2. 优先解决 SSL/TLS 连接超时问题​​:修改 Hexo 配置文件 _config.yml,将仓库地址从 HTTPS 改为 SSH 协议,根治 HTTPS 不稳定
  3. 将 C:\Users\caife.ssh\id_rsa.pub(公钥)文件内容加到 GitHub → ​​Settings → SSH and GPG Keys → New SSH Key​​,再次执行hexo d成功了。
  4. ​​为何旧密钥直接生效?​​
    你已有的 id_rsa 密钥对是之前通过合法流程生成的(可能曾用于其他服务),只是未添加到 GitHub。上传公钥后,GitHub 即可识别该密钥的签名,因此无需重新生成。生成命令示例​​:
    1
    ssh-keygen -t ed25519 -C "your_email@example.com"  # 推荐更安全的 Ed25519 算法
  5. GitHub 身份验证流程​
    当你执行 hexo d 时:
    ​​客户端发起连接​​:Hexo 通过 Git 向 GitHub 发起 SSH 连接请求。
    ​​服务器生成挑战​​:GitHub 生成一个随机字符串(Challenge),用你账户中存储的​​公钥​​加密后发送给客户端。
    ​​客户端响应签名​​:本地 Git 使用​​私钥​​解密该字符串,生成签名并返回给 GitHub。
    ​​服务器验证签名​​:GitHub 用公钥验证签名有效性,若匹配则认证通过,允许推送文件

Python / Anaconda / Django

Python是一种高级、通用、解释型、面向对象的编程语言。
Python是一种解释型语言,其代码不是直接编译成机器码,而是由解释器逐行解释执行。常见的Python解释器有 CPython、Jython 等。
其中 CPython 是官方标准实现,是由Python的创始人Guido van Rossum领导开发的官方Python解释器。它是使用C语言编写的,是最广泛使用的Python解释器。

Python 环境

  1. 配置 Python 环境:
    • 官方Python(CPython):通过官方网站下载并安装,使用官方的 pip 包管理器进行包的安装。不具备虚拟环境管理工具,但可使用 venv 或 virtualenv 创建虚拟环境。
      使用:添加解释器 -> Virtualenv -> 基础解释器 -> C:\Users\caife\AppData\Local\Programs\Python\Python312\python.exe
    • Anaconda Python:通过 Anaconda 官方网站下载并安装。使用 conda 包管理器进行包的安装和环境的管理。具备强大的虚拟环境管理工具,可以轻松创建、导出、列出和删除环境。
    • 项目中自带的环境:在开发环境中配置项目中的 Python 解释器(xxproject\env\Scripts\python.exe),使项目中使用的 Python 环境与系统中的 Python 环境独立。
  2. Python环境通常包括了解释器、标准库、开发工具和其他一些组件:
    • 虚拟环境(Virtual Environment): 虚拟环境是一个独立的Python环境,允许您在同一台机器上同时运行多个项目,每个项目都有其独立的依赖项和库。venvvirtualenv 是用于创建虚拟环境的工具。
    • 解释器:如 C:\Python312\python.exe,用于运行代码。
    • 包管理器:如 C:\Python312\Scripts\pip3.exe,用于(为解释器/环境)安装、升级和卸载软件包。pip 是Python的官方包管理器,用于从 PyPI 安装第三方库。
    • 库:如 C:\Python312\lib,其中包含了该环境中安装的所有Python包。
  3. 开发环境:
    • IDE(集成开发环境):提供了代码编辑、调试、版本控制等一体化功能的工具。常见的 Python IDE包括 PyCharm、Visual Studio Code、Jupyter Notebook 等。
    • Jupyter Notebook: 交互式计算环境,支持在浏览器中编写和运行 Python代码,并包含文本、图像和公式等。
  4. PyCharm 配置解释器:
    • 使用 Pycharm 开发项目时, 点击添加解释器 —> 添加本地解释器,选择将要运行该项目的虚拟环境下的 Python 解释器。
      选择使用这个虚拟环境对应的编辑器,就是选择了使用这个环境运行项目,使用这个环境中配好的包,版本和依赖库等等。 可以在“外部库”查看当前环境中的类库。
    • 一般来说, 可以直接选择本地Python解释器(Python3.10)环境中已经配好了大部分的类库,项目中需要的软件包可以手动安装(指定地址 C:\Users\caife\AppData\Local\Programs\Python\Python312\Lib)。
  5. PyCharm终端的Python环境不一定与当前加载的项目使用的解释器器环境一致!!
    • 所以要注意终端使用的是哪个编辑器,是否与当前项目使用的编辑器匹配。否则在终端 pip install 把包装到 Pycharm 终端的环境下,而当前项目运行在另一个虚拟环境中,便无法 import 已从终端安装的包。
    • 看pycharm提示符的地址,其实就等于是cmd中cd到该地址执行命令

Pycharm 终端指令

1
2
3
4
pip install xx    # pip安装包到终端的环境中
pip install xx --target C:\Users\caife\AppData\Local\Programs\Python\Python312\Lib # 指定安装位置
python --version # 查看 Python 版本
pip list # 列出已安装的包

数据结构与函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
my_list = [1, 2, 3, "apple"]  # 列表
my_tuple = (1, 2, 3) # 元组
my_dict = {"name": "John", "age": 25} # 字典
my_set = {1, 2, 3} # 集合

# 定义函数
def greet(name):
return "Hello, " + name
message = greet("Alice") # 调用函数

# 面向对象编程(OOP)
# 定义类
class Dog:
def __init__(self, name):
self.name = name
def bark(self):
print("Woof!")
my_dog = Dog("Buddy") # 创建对象
my_dog.bark() # 调用对象方法

异常处理

1
2
3
4
5
6
try:
result = 10 / 0
except ZeroDivisionError:
print("Cannot divide by zero")
finally:
print("This will be executed no matter what")

文件操作

1
2
3
4
5
6
7
8
# 写入文件
with open("example.txt", "w") as file:
file.write("Hello, Python!")

# 读取文件
with open("example.txt", "r") as file:
content = file.read()
print(content)

Pandas

Pandas 是一个强大的数据分析库,主要用于数据处理和分析。它提供了两种主要的数据结构:SeriesDataFrame

  • Series 是一个一维标记数组,可以保存任何数据类型。它由两个主要部分组成:索引(index)和数据(data)。
  • DataFrame 是一个二维表格,类似于 Excel 表格或 SQL 表。它由行索引、列索引和数据组成。
    数据组织:DataFrame 以表格的形式组织数据,包括多个列,每一列可以包含不同的数据类型(整数、浮点数、字符串等)。
    索引:每个行和列都有一个标签索引。行索引表示 DataFrame 中的每个数据行,列索引表示 DataFrame 中的每个数据列。
    :DataFrame 的每一列是一个 Pandas Series 对象,这意味着它们可以包含相同类型的数据。你可以将每一列视为一个数据字段,类似于数据库表中的列。 下面是一个示例 DataFrame 的结构:
    1
    2
    3
    4
    5
    6
    |    | Name     | Age | City       |
    |----|----------|-----|------------|
    | 0 | Alice | 25 | Niu York | -- 行索引(0, 1, 2, 3)标识每一行的位置
    | 1 | Bob | 30 | Los Angeles| -- 列索引(Name, Age, City)表示不同的数据字段
    | 2 | Carol | 28 | Chicago | -- 每一列(Name, Age, City)都包含相应的数据
    | 3 | David | 22 | Houston | -- 每一列都是一个 Pandas Series,包含相同类型的数据
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    import pandas as pd
    import numpy as np
    s = pd.Series([1, 3, 5, np.nan, 6, 8]) # 创建一个 Series
    df = pd.DataFrame({ # 创建一个 DataFrame
    'Name': ['Alice', 'Bob', 'Charlie'],
    'Age': [25, 30, 35],
    'City': ['New York', 'San Francisco', 'Los Angeles']
    })
    df = pd.DataFrame(columns=, data=) # 指定创建 DataFrame 的列索引和其中的数据

    for index, row in df.iterrows(): # 遍历DataFrame的每一行
    print(f"Index: {index}, Name: {row['Name']}, Age: {row['Age']}, City: {row['City']}")
    # Index: 0, Name: Alice, Age: 25, City: New York...

    # 将 DataFrame 对象保存为 CSV 文件
    # sep 表示数据字段之间的分隔符,header 表示是否将列名写入文件,index 表示是否写入行索引。
    df.to_csv('data.txt', sep = ' ', index = False, header = False)
    # 从 CSV、Excel文件读取数据
    df = pd.read_csv('data.csv')
    df = pd.read_excel('data.xlsx')

Numpy

numpy(Numerical Python的缩写)是一个用于科学计算的强大Python库。以下是一些 numpy 的主要功能和用法:

  1. 多维数组: numpy 提供了ndarray对象,是一个多维数组,用于存储同类型的元素。
    1
    2
    3
    import numpy as np
    # 创建一个numpy数组
    arr = np.array([1, 2, 3, 4, 5])
  2. 数组操作: numpy 提供了许多对数组进行操作的函数,包括数学、逻辑、形状操作等。
    1
    2
    3
    4
    # 数组的数学运算
    result = arr + 2
    # 数组形状操作
    reshaped_arr = arr.reshape(5, 1)
  3. 矩阵操作: numpy 具有广泛的矩阵操作,包括矩阵乘法、转置等。
    1
    2
    3
    4
    matrix_a = np.array([[1, 2], [3, 4]])
    matrix_b = np.array([[5, 6], [7, 8]])
    # 矩阵乘法
    result_matrix = np.dot(matrix_a, matrix_b)
  4. 数学函数: numpy 包括大量的数学函数,用于三角函数、对数、指数等。
    1
    2
    3
    x = np.array([0, 1, 2, 3, 4])
    # 求sin(x)
    sin_values = np.sin(x)
  5. 随机数生成: numpy 提供了生成随机数的函数。
    1
    random_numbers = np.random.rand(5)  # 生成5个在[0, 1)范围内的随机数

Python爬虫

反爬虫策略

  • 设置合理的请求头。
    1
    2
    3
    headers = {   # 伪装浏览器请求头
    'Cookie': 'ispeed_lsm=2; baikeVisitId=b84d4a50-436c-4e0f-9e29-dc2393e9cdca; COOKIE_SESSION=6_1_8_5_10_9_1_0_7_5_0_3_33002_0_2_0_1650022038_1650022034_1650022036%7C8%230_1_1650022030%7C1; BD_UPN=1126314751; BD_HOME=1; BD_CK_SAM=1; H_PS_645EC=89b2Pt9WoxiJHIC80g9QL3FIo7tdoc9Z9Gm9Nd6gkOPipOmTDtckrFlLxEpchFYkItCM; BAIDUID=FD56AC9125756B81A0E4EB7A60F27700:FG=1; BIDUPSID=FD56AC9125756B81E8CE802CC99B8074; PSTM=1648004100; BDUSS=Jpc2d4NGIwdzRCNVFTR0xNeS1IYXBLNTQwfjhzRnl3Z0xRSlZJTDhZeU1ibnhpRVFBQUFBJCQAAAAAAAAAAAEAAAAnQDHOyfq77rXDd2luZHkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIzhVGKM4VRiT2; H_PS_PSSID=36426_31660_35912_36167_34584_35979_36055_36235_26350; BA_HECTOR=ak20800k8kag8h8le71h8646s0q; BDRCVFR[feWj1Vr5u3D]=I67x6TjHwwYf0; delPer=0; PSINO=6',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36'}
  • 使用代理IP。
  • 限制请求频率,避免被封IP。
    1
    2
    sleep_time = random.uniform(0, 2)
    time.sleep(sleep_time)
  • 处理验证码和登录等复杂场景。

网络请求库:

  • requests库:用于发送HTTP请求,获取网页内容。
    1
    2
    3
    4
    5
    import requests
    # url_base = 'http://leo/index.phtml?reportdate={year}&quarter={quarter}&p={page}'
    # url = url_base.format(year=iyear, quarter=iquarter, page=page) # 替换url模板中的占位符
    response = requests.get(url=url, headers=self.headers)
    res = response.content.decode() # 获取服务器响应的内容,将其解码成字符串

HTML解析库:

  • BeautifulSoup:用于解析HTML文档,提取所需信息。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    from bs4 import BeautifulSoup
    soup = BeautifulSoup(res, 'html.parser') # 解析 HTML 内容
    first_paragraph = soup.find('p') # 查找第一个<p>标签
    script = soup.find(id=tag_id) # 找到具有指定 id 属性的标签
    # script.string.replace_with(new_string) # 替换标签内容
    # script.append(new_tag) # 在标签内追加新标签
    # script.extract() # 从文档中删除标签
    text = script.text # 获取标签内容
    # json_str = re.findall(r'\[.+\]', text)[0] # 用正则表达式 re 从 text 中查找一个或多个包含 JSON 数据的字符串的第一个匹配项
    # data = json.loads(json_str) # 把json格式的字符串转换为Python类型
    script1 = soup.find('p', class_='paragraph') # 查找带有指定class的<p>标签
    script2 = soup.find('a', href='https://...') # 查找带有指定属性的<a>标签
    labels = soup.find_all('a', attrs={'href': True}) # 模糊搜索HTML代码中所有含href属性的<a>标签
  • lxml:使用类似 XPath 的功能解析HTML文档。
    1
    2
    3
    4
    from lxml import html
    tree = html.fromstring(html_doc)
    paragraphs = tree.xpath('//p') # 使用 XPath 表达式查找<p>标签
    paragraphs_with_class = tree.xpath('//p[@class="paragraph"]') # 使用 XPath 表达式查找具有指定class属性值的<p>标签

数据存储:

  • 文件存储:将爬取的数据保存为文本文件、CSV文件等。
  • 数据库:使用SQLite、MySQL、MongoDB等数据库存储数据。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    def load(self, path):
    with open(path, encoding='utf-8') as fp: # 加载存储在JSON文件中的数据, 指定文件为UTF-8编码
    data = json.load(fp)
    return data
    def save(self, data, path):
    with open(path, 'w', encoding='utf-8') as fp: # 以json格式保存, 最近一日各国疫情数据
    json.dump(data, fp, ensure_ascii=False)

    def baidu_search(v_result_file): # 保存csv数据
    df = pd.DataFrame(
    {
    '关键字': kw_list, '页码': page_list, '标题': title_list, '百度链接': href_list,
    '真实链接': real_url_list, '更新时间': time_list, '简介': desc_list,
    '网站名称': site_list,
    }
    )
    if os.path.exists(v_result_file):
    header = None # 已存在的csv文件保存时不用加标头
    else:
    # 创建新文件时,设置csv文件标头
    header = ['关键词', '页码', '标题', '百度链接', '真实链接', '更新时间', '简介', '网站名称']
    df.to_csv(v_result_file, mode='a+', index=False, header=header, encoding='utf_8_sig')
    print('结果保存成功:{}'.format(v_result_file))

爬虫框架

  • Scrapy:一个强大的Python爬虫框架,提供了高级功能如异步处理、中间件等。
    1
    scrapy startproject myproject

动态网页爬取

  • 使用Selenium或Headless浏览器模拟浏览器行为。
  • 处理JavaScript渲染的页面。

Anaconda

  1. 利用 Anaconda,创建不同版本的虚拟环境,配置不同的包(如不同版本的 Pytorch),以管理不同的项目。
  2. 用 Pycharm 打开项目时,点击添加解释器 —> 添加本地解释器,选择该项目对应的 conda 虚拟环境下,该虚拟环境的 python 解释器。选择使用这个虚拟环境对应的编辑器,就是选择了使用这个环境运行项目,使用这个环境中配好的包等等。
  3. 所以要注意终端使用的是哪个编辑器,是否与当前项目使用的编辑器匹配。否则 pip install 把包装到一个虚拟环境,而当前项目运行在另一个虚拟环境中,便无法 import 已安装的包。

新建虚拟环境

打开Anaconda Prompt,创建一个名字为py36的python版本为3.6的虚拟环境(默认地址已改成D:\anaconda3\envs),并查看已有环境:

1
2
3
4
5
6
7
(base) C:\Users\cf>conda create -n py36 python=3.6
Proceed ([y]/n)? y
(base) C:\Users\cf>conda env list
# conda environments:
#
base * D:\anaconda3
py36 D:\anaconda3\envs\py36

激活(切换)环境,并查看该环境下已有包:

1
2
(base) C:\Users\cf>conda activate py36
(py36) C:\Users\cf>pip list

装包

方法1:在 Anaconda Prompt 中进入该环境后,执行 conda install … 或 pip install …(需要关闭代理)
方法2:在 Pycharm的terminal(终端),执行安装指令(此时所处的是 Pycharm终端 的环境(不一定是此时加载的项目的环境))

1
2
3
4
5
# 其他pip指令
pip list # 列出所有已安装的pip包
pip install package_name==desired_version # 安装指定版本的Python包
pip install --target=... package_name # 指定pip install包的路径
pip show package_name # 查看安装好的包的信息

Jupyter Notebook

以任意行为块,便于代码阅读和测试修改。 在一个环境中安装 jupyter notebook 后,运行:

1
(py36) C:\Users\cf>jupyter notebook        

在浏览器打开8888端口使用 http://localhost:8888/?token=7e39781c1e364fa7a5ee95ef290ffa92eb0caef0c19aaad8


Django

Django是一个基于Python的开源Web应用框架,它遵循 MTV 框架,与传统的 MVC 有一些区别,但是其基本思想和流程是类似的,旨在简化Web开发过程,提高开发效率,同时保持代码的可读性和可维护性。

MTV 架构

  1. Model(模型):负责处理数据逻辑,定义数据库模型(ORM),进行数据的增删改查操作。
  2. Template(模板):负责呈现用户界面,包含 HTML、CSS、JavaScript 等前端代码,展示数据给用户。
  3. View(视图): 处理用户请求,根据请求调用适当的模型和模板,返回响应给用户。

MTV 流程

  1. 用户发起请求Request到 Django 服务器。
  2. Django 的 URL 配置根据请求的 URL 路径找到对应的视图(View)函数。
  3. 视图函数处理请求,可能需要进行数据库操作,调用模型(Model)获取数据。
  4. 视图函数将获取的数据传递给模板(Template),并渲染生成最终的 HTML 页面。
  5. 服务器将生成的 HTML 页面作为响应Response返回给用户。

目录结构

  1. 项目目录:包含整个 Django 项目的配置和管理文件,settings.py、urls.py、wsgi.py(WSGI 入口)、asgi.py(ASGI 入口)等。
  2. 应用目录:包含具体的应用程序代码和文件,models.py(模型)、views.py(视图)、urls.py(应用URL配置)、templates/ 等。
  3. 静态文件目录: 存放静态资源文件,如 CSS、JavaScript、图片等。默认路径是项目目录下的 static/ 文件夹。
  4. 模板目录: 存放 HTML 模板文件。 默认路径是应用目录下的 templates/ 文件夹。
  5. 数据库文件:默认情况下,Django 使用 SQLite 作为默认数据库,并将数据库文件存放在项目目录下的 db.sqlite3 文件中。
    一个Django项目(project)就是一个基于Django的Web应用,一个Django项目包含一组配置和若干个Django应用
    一个Django应用(blog)就是一个可重用的Python软件包,每个应用可以自己管理模型、视图、模板、路由和静态文件等

几个入门命令

  • 文件路径cmd
    1
    2
    3
    4
    5
    6
    cd E:/.../mainproject                  # 一定要把终端路径切换成项目根目录!!!
    django-admin startproject mainproject # 创建项目,生成工程目录
    python manage.py startapp firstWEB # 创建APP(进入工程目录/IDE console)
    python manage.py makemigrations # 创建库表(进入工程目录/IDE console)
    python manage.py migrate # 执行库表建立(进入工程目录/IDE console)
    python manage.py runserver # 启动项目(进入IDE console)
  • 几个入门设置(settings.py):Django 框架时间设置,Django APP添加,Templates目录设置(在APP日录下,需要独立建立)

HelloWorld

  • 实现一个请求view,访问 主项目project 中的 blog应用 的hello_world接口
    1. 编写应用请求 project/blog/views.py
      1
      2
      def hello_world(request):
      return HttpResponse("2323")
    2. 配置应用路由 project/blog/urls.py
      1
      2
      3
      urlpatterns = [
      path('hello_world', blog.views.hello_world)
      ]
    3. 配置项目路由 project/project/urls.py
      1
      2
      3
      4
      5
      urlpatterns = [
      path('admin/', admin.site.urls),
      # 如果url中含有 'blog',就转发到应用层面的路由处理
      path('blog/', include('blog.urls'))
      ]
    4. 添加blog应用到项目配置中 project/project/settings.py
      1
      2
      3
      4
      INSTALLED_APPS = [
      # ...
      'blog.apps.BlogConfig', # 创建APP时自动创建
      ]
  • 请求转发流程 http://127.0.0.1:8000/blog/
    浏览器 –(项目路由)–> Blog App –> view.py –(应用路由)–> hello_world函数 –> 浏览器

模型层

  • 模型层是什么?
    • 模型层:位于Django视图层和数据库之间, Python对象和数据库表之间转换
    • 模型层作用:屏蔽不同数据库之间的差异, 开发者更加专注于业务逻辑的开发, 提供很多便捷工具有助开发
    • 模型层配置 project/project/settings.py
      1
      2
      3
      4
      5
      6
      DATABASES = {
      'default': {
      'ENGINE': 'django.db.backends.sqlite3', # 自带的数据库
      'NAME': BASE_DIR / 'db.sqlite3',
      }
      }
  • 模型 Model:Model是数据库表的抽象表示。
    • 每个模型都继承自django.db.models.Model类,并定义了一组字段(Field),这些字段对应于数据库表中的列。
      1
      2
      3
      4
      5
      6
      7
      8
      from django.db import models  

      class Author(models.Model): # Author模型有两个字段:name和email
      name = models.CharField(max_length=100)
      email = models.EmailField()
      class Book(models.Model): # Book模型有一个title字段和一个指向Author模型的外键
      title = models.CharField(max_length=200)
      author = models.ForeignKey(Author, on_delete=models.CASCADE)
    • 一旦你定义了模型,Django就会自动为你生成一个数据库表(除非你明确告诉它不要这样做)。然后,你可以使用Django的ORM(对象关系映射)API来查询、创建、更新和删除数据库中的记录。以下是一些基本示例:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      # 获取所有作者  
      authors = Author.objects.all()
      # 获取标题为"Django教程"的书
      book = Book.objects.get(title="Django教程")
      # 创建一个新作者
      new_author = Author.objects.create(name="John Doe", email="john@example.com")
      # 创建一个新书,并将其与作者关联
      new_book = Book.objects.create(title="Django入门", author=new_author)
      # 获取并更新一本书的标题
      book = Book.objects.get(title="Django教程")
      book.title = "Django进阶"
      book.save()
      # 删除一本书
      book = Book.objects.get(title="Django进阶")
      book.delete()
  • 迁移 Migrations:Django框架中用于管理数据库模式更改的一种强大工具。
    • 当你更改Django模型(Model)时(例如添加、删除或修改字段),Django能够自动计算出需要应用到数据库中的更改,并将这些更改保存为迁移文件。然后,你可以使用Django的迁移命令将这些更改应用到数据库中。
    • 创建迁移文件
      1
      python manage.py makemigrations  # IDE console
    • 运行迁移文件 同步sqlite3数据库
      1
      python manage.py migrate  # IDE console
  • Django shell操作
    • Django shell:继承Django项目环境,用于交互的Python编程,方便快捷
    • 新建文章:IDE console操作
      1
      python manage.py shell  # 进入 Djungo shell
      1
      2
      3
      4
      5
      6
      7
      >>> from blog.models import Article  # 导入模型
      >>> a = Article() # 创建文章
      >>> a.title = 'Test Django Shell'
      >>> a.brief = '...'
      >>> a.save() # 保存文章
      >>> articles = Article.objects.all() # 获取文章
      >>> print(articles[0].title)
  • Django Admin模块
    • Django的后台管理工具,简化Djungo shell的使用;直接读取定义的模型元数据,提供强大的管理使用页面
    • Django的使用
      1
      python manage.py createsuperuser  # 创建管理员用户:caif / cxxxxxxx
      1
      2
      3
      from .models import Article

      admin.site.register(Article) # 将模型注册到admin模块 project/blog/admin.py
      登录页面进行管理:http://127.0.0.1:8000/admin/login/?next=/admin/, 选择模型,进行创建、修改、删除对象操作
  • Model数据返回页面
    1. 编写应用请求 project/blog/views.py
      1
      2
      3
      4
      5
      6
      from blog.models import Article

      def article_content(request): # 就是返回处理过的model数据
      article = Article.object.all()[0] # 从sqlite3中获取对应模型的数据
      ans_str = article.title + ...
      return HttpResponse(ans_str)
    2. 配置应用路由 project/blog/urls.py
    3. 配置项目路由 project/project/urls.py
    4. 添加blog应用到项目配置中 project/project/settings.py

连接本地MySQL

  • 默认情况下,Django连接的是自己带的sqlite数据库。好处是方便,不需要远程连接,打包项目挪到其他电脑上安装一下依赖一会就跑起来了,但是缺点就是,可能会出现各种莫名其面的问题,所以,尽可能在开始的时候,就配置上连接Mysql。
  • Django 连接 MySQL:
    • 安装 Python 访问 MySQL的 客户端模块
      1
      pip install mysqlclient  # Django官方已不建议使用pymysql库,而是改用mysqlclient
    • 修改 Django项目文件夹下的settings.py文件:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      DATABASES = {
      'default': {
      'ENGINE': 'django.db.backends.mysql', # 使用MySQL引擎
      'NAME': 'HKStock', # 数据库名称
      'USER': 'root', # 数据库用户名
      'PASSWORD': '123456', # 数据库密码
      'HOST': 'localhost', # 数据库主机(如果在本地)
      'PORT': '3306', # 数据库端口(默认为3306)
      }
      }
    • Pycharm 连接 MySQL:点击 Pycharm右上角 database -> “+” -> source -> MySQL,输入数据库名称、用户、密码连接成功
    • 修改项目文件夹下的__init.py文件:要替换默认的数据库引擎,在项目文件夹下的__init__.py 添加以下内容。
      1
      2
      3
      import pymysql

      pymysql.install_as_MySQLdb()
  • 定义模型 Model:将数据库 SQL 映射到面向对象的 Python 中来,使得你可以在 Django 中像操作普通对象一样操作数据库。
  • 迁移 Migrations:定义好了Model,数据库中的表并不会神奇的出现,你还需要把模型转化为对数据库的操作,这就是迁移。
    当你更改Django模型(Model)时(例如添加、删除或修改字段)Django能够自动计算出需要应用到数据库中的更改,并将这些更改保存为迁移文件。然后,你可以使用Django的迁移命令将这些更改应用到数据库中。
  • 新model迁移:
    1. 定义模型(mainapp/models.py)
      1
      2
      3
      class pen(models.Model):
      price = models.IntegerField()
      color = models.CharField(default='black', max_length=20)
    2. 创建迁移文件:指令执行完毕后会生成 mainapp/migrations/0001_initial.py 文件。Django 不会检查你的数据库,而是根据目前的模型的状态,创建一个操作列表,使项目状态与模型定义保持最新。
      1
      python manage.py makemigrations
    3. 运行迁移文件:打开数据库可以看到多了 mainapp_pen 表,并且里面的字段和模型是完全匹配的。插入一些数据。
      1
      python manage.py migrate 
    4. 功能demo
      1
      2
      3
      4
      5
      6
      # 编写url:在项目文件夹 datasite/urls.py
      from mainapp import views

      urlpatterns = [
      path('pen', views.pen) # 配置项目url即可,无需配置应用url
      ]
      1
      2
      3
      4
      5
      6
      7
      # 编写视图(views):在应用文件夹web/views.py,操作model从MySQL中获取数据
      from .models import Pen

      def pen(request):
      pen = Pen.objects.all()
      # 结合html(templates/pen.html)展示数据
      return render(request,"pen.html",{"pen_queryset":pen_queryset})
    5. 启动web:python manage.py runserver
    6. 发送请求:http://127.0.0.1:8000/pen
  • 操作MySQL中已有的表:
    1. 配置Django的数据库设置(项目级settings.py),确保已安装MySQL客户端并且设置好__init__.py文件。
    2. 创建Django模型:尽管数据库表已经存在,但仍需为这些表创建Django模型。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      from django.db import models  

      class Student(models.Model):
      name = models.CharField(max_length=255)
      code = models.CharField(max_length=255)

      class Meta:
      db_table = 'mainapp_student' # 数据库中的实际表名,注意这里应该与你的数据库表名一致
      managed = False # 指示Django该模型对应于现有的数据库表,不由Django迁移管理
    3. 同步Django模型与数据库:只需python manage.py makemigrations,无需python manage.py migrate ??
    4. 在Django中操作数据库表:使用Django的ORM API来查询、创建、更新和删除数据库中的记录了。

视图与模版

  • 使用Bootstrap实现静态博客页面
    • 在 project/blog/template 下创建 index.html
      1
      2
      3
      4
      5
      <!-- 在 header 引入 -->
      <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
      <link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
      <!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
      <script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script>
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      <!-- 静态代码块 -->
      <div class="container page-header">
      <h1 class="text-center mb-4">博客标题</h1>
      </div>
      <div class="container page-body">
      <div class="col-lg-9">
      <!-- 博客详情1, 2, ... -->
      </div>
      <div class="col-lg-3">
      <!-- 博客链接1, 2, ... -->
      </div>
      </div>
  • Django模板系统
    • 一个类似html的文本文件,结合view,实现动态页面。模板语法:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      <!DOCTYPE html>
      <html>
      <head>
      <title>Blog Post</title>
      </head>
      <body>
      <h1>{{ post.title }}</h1>

      {% if user.is_authenticated %}
      <a href="{% url 'logout' %}">Logout</a>
      {% else %}
      <a href="{% url 'login' %}">Login</a>
      {% endif %}

      <ul>
      {% for comment in post.comments.all %}
      <li>{{ comment.content }} - {{ comment.author.username }}</li>
      {% endfor %}
      </ul>
      </body>
      </html>
    • 使用模板系统渲染博客页面
      在 Django 中,模板文件通常存储在每个应用的 /templates目录下。在应用view中渲染这个模板文件,并传递上下文数据:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      from django.shortcuts import render
      from datetime import datetime

      # home_view 视图函数渲染了 home.html 模板,并向模板传递了两个变量:name 和 date
      def home_view(request):
      context = {
      'name': 'My Django Website',
      'date': datetime.now(),
      }
      return render(request, 'home.html', context)

: (


ECharts

ECharts,一个使用 JavaScript 实现的开源可视化库,可以流畅的运行在 PC 和移动设备上,兼容当前绝大部分浏览器(IE9/10/11,Chrome,Firefox,Safari等),底层依赖矢量图形库 ZRender,提供直观,交互丰富,可高度个性化定制的数据可视化图表。
https://echarts.apache.org

  • 在项目中引入echarts
    • mainapp/static/assets/js/ 目录中保存 echarts.js
    • mainapp/templates/xx.html 文件中引入 echarts.js
    • 示例:一个简单柱状图
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      <!DOCTYPE html>
      <html>
      <head>
      <meta charset="utf-8" />
      <title>ECharts</title>
      <!-- 引入刚刚下载的 ECharts 文件 -->
      <script src="echarts.js"></script>
      </head>
      <body>
      <!-- 为 ECharts 准备一个定义了宽高的 DOM -->
      <div id="main" style="width: 600px;height:400px;"></div>
      <script type="text/javascript">
      // 基于准备好的dom,初始化echarts实例
      var myChart = echarts.init(document.getElementById('main'));
      // 指定图表的配置项和数据
      var option = {
      xAxis: {
      data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
      },
      yAxis: {},
      series: [
      {
      type: 'bar',
      data: [23, 24, 18, 25, 27, 28, 25]
      }
      ]
      };
      // 使用刚指定的配置项和数据显示图表。
      myChart.setOption(option);
      </script>
      </body>
      </html>
  • 传入数据渲染echarts模版:
    1. 在view函数中准备数据,比如一个Python列表或字典;通过渲染函数(render())将数据传递到模板中;
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      from django.shortcuts import render  

      def chart_view(request):
      # 准备数据
      x_axis_data = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
      y_axis_data = [23, 24, 18, 25, 27, 28, 55]

      # 将数据传递给模板
      context = {
      'x_axis_data_json': json.dumps(x_axis_data), # JSON 字符串形式的数据
      'y_axis_data_json': json.dumps(y_axis_data),
      }
      return render(request, '2.html', context)
    2. 复制示例模版from https://echarts.apache.org/examples/zh/index.html, 使用 Django模板标签将数据嵌入到 js代码中
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      <!DOCTYPE html>  
      <html>
      <head>
      <meta charset="utf-8" />
      <title>ECharts</title>
      <!-- 引入ECharts文件,确保路径正确 -->
      <script src="echarts.js"></script>
      </head>
      <body>
      <div id="main" style="width: 600px;height:400px;"></div>
      <script type="text/javascript">
      // 基于准备好的dom,初始化echarts实例
      var myChart = echarts.init(document.getElementById('main'));

      // 从 Django 模板上下文中取出数据,并转换为 JavaScript 数组
      var x_axis_data = JSON.parse('{{ x_axis_data_json|escapejs }}');
      var y_axis_data = JSON.parse('{{ y_axis_data_json|escapejs }}');

      // 指定图表的配置项和数据
      var option = {
      xAxis: {
      type: 'category',
      data: x_axis_data // 使用从 Django 传递的数据
      },
      yAxis: {
      type: 'value'
      },
      series: [
      {
      data: y_axis_data, // 使用从 Django 传递的数据
      type: 'bar'
      }
      ]
      };

      // 使用刚指定的配置项和数据显示图表。
      myChart.setOption(option);
      </script>
      </body>
      </html>

Redis

Redis是一种支持 key-value 等多种数据结构的存储系统。可用于缓存,事件发布或订阅,高速队列等场景。支持网络,提供字符串,哈希,列表,队列,集合结构直接存取,基于内存,可持久化。 默认端口:6379

启动 Redis

下载 Windows版 msi文件:https://github.com/microsoftarchive/redis/releases
在环境变量中配好 Redis 的安装目录后,直接打开 cmd,启动 Redis 客户端
启动客户端后, 在客户端cmd窗口中测试 Redis 是否正常工作,设置密码(命令行设置的密码在服务重启后失效
注意:在 Windows 环境下,Redis 不支持后台运行模式,因此在启动 Redis 服务器时必须保持 cmd 窗口一直打开。(如果想要在后台运行 Redis,可以考虑使用虚拟机或者 Linux 环境下的 Redis)

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    C:\Users\蔡枫>redis-cli   # 启动 Redis 客户端(默认Redis服务端未关闭)
    127.0.0.1:6379> ping # 在 Redis 客户端中输入 ping 命令
    PONG # 返回 PONG 表示 正常工作
    127.0.0.1:6379> config set requirepass chskj.2020 # 修改密码
    OK
    127.0.0.1:6379> auth chskj.2020 # 验证密码
    OK
    127.0.0.1:6379> config get requirepass # 查看密码
    1)"requirepass"
    2)"chskj.2020"

启动(重启) Redis 服务器,客户端

进入 Redis 的安装目录(D:\Redis),打开 cmd 窗口(地址栏cmd回车),执行 redis-server.exe redis.windows.conf,即可启动 Redis 服务器
启动Redis服务器后,在 Redis 安装目录打开另一个 cmd 窗口,执行 redis-cli.exe,(或直接双击文件夹中的redis-cli.exe)即可启动(重启)Redis 客户端

Redis中有16个数据库(Database),每个数据库都是一个独立的命名空间,用于存储键值对数据。这些数据库被编号为0到15,默认情况下客户端连接到数据库0。可以通过SELECT命令选择数据库来切换不同的数据库空间,每个数据库都是相互隔离的,数据不会互相干扰。
每个数据库都可以包含键值对数据,命令和配置,但请注意,Redis的每个数据库是相对较小的,因此可以将不同类型的数据存储在不同的数据库中,以便更好地组织和管理数据。

1
SELECT 1   # 切换到数据库1

数据类型

Redis所有的key(键)都是字符串。我们在谈基础数据结构时,讨论的是存储值的数据类型,主要包括常见的5种数据类型,分别是:String、List、Set、Zset、Hash。 三种特殊的数据类型,分别是 HyperLogLogs(基数统计),Bitmaps (位图) 和 geospatial(地理位置)。 Redis5.0 中还增加了一个数据结构Stream,它借鉴了Kafka的设计,是一个新的强大的支持多播的可持久化的消息队列。

结构类型 结构存储的值 结构的读写能力
String字符串 可以是字符串、整数或浮点数 对整个字符串或字符串的一部分进行操作;对整数或浮点数进行自增或自减操作;
List列表 一个链表,链表上的每个节点都包含一个字符串 对链表的两端进行push和pop操作,读取单个或多个元素;根据值查找或删除元素;
Set集合包 含字符串的无序集合 字符串的集合,包含基础的方法有看是否存在添加、获取、删除;还包含计算交集、并集、差集等
Hash散列 包含键值对的无序散列表 包含方法有添加、获取、删除单个元素
Zset有序集合 和散列一样,用于存储键值对字符串成员与浮点数分数之间的有序映射;元素的排列顺序由分数的大小决定; 包含方法有添加、获取、删除单个元素以及根据分值范围或成员来获取元素

String字符串

  • String是redis中最基本的数据类型,一个key对应一个value。
  • String类型是二进制安全的,意思是 redis 的 string 可以包含任何数据。如数字,字符串,jpg图片或者序列化的对象。
  • 实战场景
    • 缓存: 经典使用场景,把常用信息,字符串,图片或者视频等信息放到redis中,redis作为缓存层,mysql做持久化层,降低mysql的读写压力。
    • 计数器:redis是单线程模型,一个命令执行完才会执行下一个,同时数据可以一步落地到其他的数据源。session:常见方案spring session + redis实现session共享,
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    127.0.0.1:6379> set hello world    # 设置存储在给定键中的值
    OK
    127.0.0.1:6379> get hello # 获取存储在给定键中的值
    "world"
    127.0.0.1:6379> del hello # 删除存储在给定键中的值
    (integer) 1
    127.0.0.1:6379> get hello
    (nil)
    127.0.0.1:6379> set counter 2
    OK
    127.0.0.1:6379> get counter
    "2"
    127.0.0.1:6379> incr counter # 将键存储的值加1
    (integer) 3
    127.0.0.1:6379> get counter
    "3"
    127.0.0.1:6379> incrby counter 100 # 将键存储的值加上整数
    (integer) 103
    127.0.0.1:6379> get counter
    "103"
    127.0.0.1:6379> decr counter # 将键存储的值减1
    (integer) 102
    127.0.0.1:6379> get counter
    "102"

List列表

  • Redis中的List其实就是链表(Redis用双端链表实现List)。
  • 使用List结构,我们可以轻松地实现最新消息排队功能(比如新浪微博的TimeLine)。List的另一个应用就是消息队列,可以利用List的 PUSH 操作,将任务存放在List中,然后工作线程再用 POP 操作将任务取出进行执行。
  • 使用列表的技巧:lpush+lpop=Stack(栈),lpush+rpop=Queue(队列),lpush+ltrim=Capped Collection(有限集合),lpush+brpop=Message Queue(消息队列)
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    127.0.0.1:6379> lpush mylist 1 2 ll ls mem   # 将给定值推入到列表左端,RPUSH 将给定值推入到列表右端
    (integer) 5
    127.0.0.1:6379> lrange mylist 0 -1 # 获取列表在给定范围上的所有值
    1) "mem"
    2) "ls"
    3) "ll"
    4) "2"
    5) "1"
    127.0.0.1:6379> lindex mylist -1 # 可以使用负数下标,以 -n 表示列表的倒数第n个元素
    "1"
    127.0.0.1:6379> rpop mylist # 从列表的右端弹出一个值,并返回被弹出的值;lpop 从左弹
    "1"
    127.0.0.1:6379> lindex mylist 10 # index不在 mylist 的区间范围内
    (nil)

Set集合

  • Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
  • Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。
  • 实战场景
    • 标签(tag),给用户添加标签,或用户给消息添加标签,这样有同一标签或者类似标签的可以给推荐关注的事或者关注的人。
    • 点赞,或点踩,收藏等,可以放到set中实现
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    127.0.0.1:6379> sadd myset hao hao1 xiaohao hao  # 向集合添加一个或多个成员	
    (integer) 3
    127.0.0.1:6379> scard myset # 获取集合的成员数
    (integer) 3
    127.0.0.1:6379> smembers myset # 返回集合中的所有成员
    1) "xiaohao"
    2) "hao1"
    3) "hao"
    127.0.0.1:6379> sismember myset hao # 判断 member 元素是否是集合 key 的成员
    (integer) 1

Hash散列

  • Redis hash 是一个 string 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。
  • 实战场景 - 缓存: 能直观,相比string更节省空间,的维护缓存信息,如用户信息,视频信息等。
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    127.0.0.1:6379> hset user name1 hao               # 添加键值对	
    (integer) 1
    127.0.0.1:6379> hset user email1 hao@163.com
    (integer) 1
    127.0.0.1:6379> hgetall user # 获取散列中包含的所有键值对
    1) "name1"
    2) "hao"
    3) "email1"
    4) "hao@163.com"
    127.0.0.1:6379> hget user user # 获取指定散列键的值
    (nil)
    127.0.0.1:6379> hget user name1
    "hao"
    127.0.0.1:6379> hset user name2 xiaohao
    (integer) 1
    127.0.0.1:6379> hdel user name1 # 如果给定键存在于散列中,那么就移除这个键
    (integer) 1
    127.0.0.1:6379> hgetall user
    1) "email1"
    2) "hao@163.com"
    3) "name2"
    4) "xiaohao"

Zset有序集合

  • Redis 有序集合和集合一样也是 string 类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个 double 类型的分数。redis 正是通过分数来为集合中的成员进行从小到大的排序。
  • 有序集合的成员是唯一的, 但分数(score)却可以重复。有序集合是通过两种数据结构实现:
    1. 压缩列表(ziplist): ziplist是为了提高存储效率而设计的一种特殊编码的双向链表。它可以存储字符串或者整数,存储整数时是采用整数的二进制而不是字符串形式存储。它能在O(1)的时间复杂度下完成list两端的push和pop操作。但是因为每次操作都需要重新分配ziplist的内存,所以实际复杂度和ziplist的内存使用量相关
    2. 跳跃表(zSkiplist): 跳跃表的性能可以保证在查找,删除,添加等操作的时候在对数期望时间内完成,这个性能是可以和平衡树来相比较的,而且在实现方面比平衡树要优雅,这是采用跳跃表的主要原因。跳跃表的复杂度是O(log(n))。
  • 实战场景 - 排行榜:有序集合经典使用场景。例如小说视频等网站需要对用户上传的小说视频做排行榜,榜单可以按照用户关注数,更新时间,字数等打分,做排行。
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    127.0.0.1:6379> zadd myscoreset 100 hao 90 xiaohao  # 将一个带有给定分值的成员添加到有序集合里面
    (integer) 2
    127.0.0.1:6379> ZRANGE myscoreset 0 -1 # 根据元素在有序集合中所处位置,从有序集合中获取多个元素
    1) "xiaohao"
    2) "hao"
    127.0.0.1:6379> ZSCORE myscoreset hao
    "100"
    127.0.0.1:6379> zrem myscoreset hao # 如果给定元素成员存在于有序集合中,那么就移除这个元素
    (integer) 1

HyperLogLog

采用一种基数算法,用于完成独立总数的统计。(同一个人多次访问,只记一访问量)(独立访客)
占据空间小,无论统计多少个数据,只占12K的内存空间
不精确的统计算法,标准误差为 0.81%。

Bitmap

不是一种独立的数据结构,实际上就是字符串
支持按位存取数据,可以将其看成是byte数组
适合存储索大量的连续的数据的布尔值。(记录一个人连续一年每天的签到情况 0/1)(日活跃用户)


RedisTemplate

  • Spring Boot提供了RedisTemplate作为与Redis交互的强大工具。通过RedisTemplate,您可以在java项目中执行各种Redis命令来操作数据,包括字符串、列表、集合、散列、有序集合等。以下是一些常见的Redis操作示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 存储数据:
    redisTemplate.opsForValue().set("myKey", "myValue");
    String value = (String) redisTemplate.opsForValue().get("myKey");
    // 列表操作:
    redisTemplate.opsForList().leftPush("myList", "value1");
    List<String> myList = redisTemplate.opsForList().range("myList", 0, -1);
    // 集合操作:
    redisTemplate.opsForSet().add("mySet", "member1", "member2");
    Set<String> mySet = redisTemplate.opsForSet().members("mySet");
    // 散列操作:
    Map<String, String> myHash = new HashMap<>();
    myHash.put("field1", "value1");
    myHash.put("field2", "value2");
    redisTemplate.opsForHash().putAll("myHash", myHash);
    String fieldValue = (String) redisTemplate.opsForHash().get("myHash", "field1");
    // 有序集合操作:
    redisTemplate.opsForZSet().add("myZSet", "member1", 1.0);
    Set<String> myZSet = redisTemplate.opsForZSet().range("myZSet", 0, -1);
  • 公共命令
    1
    2
    redisTemplate.delete(key);  // 删除一个数据结构
    redisTemplate.hasKey(key); // 是否存在一个数据结构

Redis 事务管理

  • 要使用编程式事务
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public void testTransaction() {
    Object result = redisTemplate.execute(new SessionCallback() {
    @Override
    public Object execute(RedisOperations redisOperations) throws DataAccessException {
    String redisKey = "text:tx";
    redisOperations.multi(); // 启用事务
    redisOperations.opsForSet().add(redisKey, "zhangsan");
    redisOperations.opsForSet().add(redisKey, "lisi");
    redisOperations.opsForSet().add(redisKey, "wangwu");
    // redis事务内做查询,无效
    // redis事务中的所有代码在事务提交后一起执行
    System.out.println(redisOperations.opsForSet().members(redisKey));
    return redisOperations.exec(); // 提交事务
    }
    });
    }

Redis缓存问题

在高并发的业务场景下,数据库大多数情况都是用户并发访问最薄弱的环节。所以,就需要使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问Mysql等数据库。这样可以大大缓解数据库的压力。当缓存库出现时,必须要考虑如下问题:

  1. 缓存穿透
    • 问题来源
      缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求。由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义,
      在流量大时,可能DB就挂掉了,要是有人利用不存在的kev频繁攻击我们的应用,这就是漏洞.
      如发起为id为”-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
    • 1、接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截
      2、从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒 (设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
      3、布隆过滤器。bloomfilter就类似于一个hash set,用于快速判某个元素是否存在于集合中,其典型的应用场景就是快速判断个key是否存在于某容器,不存在就直接返回。布隆过滤器的关键就在于hash算法和容器大小,
  2. 缓存击穿
    • 问题来源: 缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力.
    • 1、设置热点数据永远不过期
      2、接口限流与熔断,降级。重要的接口一定要做好限流策略,防止用户恶意刷接口,同时要降级准备,当接口中的某些 /3务不可用时候,进行熔断,失败快速返回机制。
      3、加互斥锁
  3. 缓存雪崩
    • 问题来源: 缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
    • 1、缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生
      2、如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中
      3、设置热点数据永远不过期.
  4. 缓存污染(或满了)
    • 缓存污染问题说的是缓存中一些只会被访问一次或者几次的的数据,被访问完后,再也不会被访问到,但这部分数据依然留存在缓存中,消耗缓存空间.
    • 缓存污染会随着数据的持续增加而逐渐显露,随着服务的不新运行,缓存中会存在大量的永远不会再次被访问的数据。缓存空间是有限的,如果缓存空间满了,再往缓存里写数据时就会有额外开销,影响Redis性能。这部分额外开销主要是指写的时候判断淘汰策略,根据淘汰策略去选择要淘汰的数据,然后进行删除操作。

缓存淘汰策略

Redis共支持八种淘汰策略,分别是noeviction, volatile-random、 volatile-ttl, volatile-ru、 volatile-lfuallkeys-lru、allkeys-Iandom 和 allkevs-lfu 策略。主要看分三类看:

  • 不淘汰
    noeviction (v4.0后默认的):一旦缓存被写满了,再有写请求来时,Redis 不再提供服务,而是直接返回错误。这种策略不会淘汰数据,所以无法解决缓存污染问题。一般生产环境不建议使用。
  • 对设置了过期时间的数据中进行淘汰
    随机 volatile-random:在设置了过期时间的键值对中,进行随机删除。因为是随机删除,无法把不再访问的数据筛选出来,所以可能依然会存在缓存污染现象,无法解决缓存污染问题。
    tvolatile-ttl:Redis在筛选需删除的数据时,越早过期的数据越优先被选择。随机删除就无法解决缓存污染问题。
    volatile-lru:LRU 算法的全称是 Least Recently Used,按照最近最少使用的原则来筛选数据
    volatile-lfu:LFU 缓存策略是在 LRU 策略基础上,为每个数据增加了一个计数器,来统计这个数据的访问次数。当使用 LFU 策略筛选淘汰数据时,首先会根据数据的访问次数进行筛选,把访问次数最低的数据淘汰出缓存。如果两个数据的访问次数相同,LFU 策略再比较这两个数据的访问时效性,把距离上一次访问时间更久的数据淘汰出缓存。
  • 全部数据进行淘汰
    随机 allkeys-random:从所有键值对中随机选择并删除数据。
    allkeys-lru:使用 LRU 算法在所有数据中进行筛选。
    allkeys-lfu:使用 LFU 算法在所有数据中进行筛选。

Redis 持久化

Redis 提供了两种主要的持久化方式,用于在系统重启时保持数据的持久性:RDB 持久化和 AOF 持久化。
用户可以根据实际需求选择 RDB 持久化、AOF 持久化或两者结合使用。通常情况下,AOF 持久化是更安全的选择,因为它可以提供更好的持久性保障,但也需要更多的磁盘空间。

  1. RDB 持久化:
    • RDB 持久化是通过在指定的时间间隔内将内存中的数据集快照写入磁盘的方式来实现的。
    • 快照是一个二进制文件,它记录了某个时间点上 Redis 数据集的所有键值对。
    • RDB 持久化是一个“点对点”操作,它在指定的时间点创建了一个数据快照。
    • RDB 持久化适用于备份、灾难恢复等场景。
      RDB 持久化的配置选项可以在 Redis 配置文件中进行设置,例如:
      1
      2
      3
      save 900 1      # 表示在 900 秒(15分钟)内,如果至少有 1 个 key 发生了变化,则执行快照操作
      save 300 10 # 表示在 300 秒(5分钟)内,如果至少有 10 个 key 发生了变化,则执行快照操作
      save 60 10000 # 表示在 60 秒内,如果至少有 10000 个 key 发生了变化,则执行快照操作
  2. AOF 持久化:
    • AOF(Append Only File)持久化记录了服务器执行的所有写操作指令,以追加的方式将这些指令记录到一个文件中。
    • AOF 持久化是一个“追加”操作,每个写操作都被追加到文件末尾。
    • AOF 持久化适用于对数据的实时持久化需求。
      AOF 持久化的配置选项可以在 Redis 配置文件中进行设置,例如:
      1
      2
      appendonly yes            # 启用 AOF 持久化
      appendfsync everysec # 每秒钟执行一次 fsync 操作
    • appendfsync 选项可以设置 alwayseverysecnoalways 表示每个写命令都会立即被写入磁盘,everysec 表示每秒执行一次 fsync 操作,no 表示由操作系统自行决定何时进行写入磁盘操作。
    • Redis 还提供了 AOF 重写机制,可以通过 auto-aof-rewrite-percentageauto-aof-rewrite-min-size 选项配置。这允许 Redis 在不停机的情况下优化 AOF 文件的大小。

Mybatis(Plus)

1. Mybatis

MyBatis 是一个开源的Java持久层框架,用于将对象与关系数据库的表之间进行映射。MyBatis 通过 XML或注解配置文件描述 Java 对象与数据库之间的映射关系,并提供了一些方便的查询语言(类似于SQL)来进行数据库操作。
使用 MyBatis 来操作 MySQL 数据库,将数据存储在 MySQL 中,或从 MySQL 中检索数据,同时使用 MyBatis 进行数据映射和数据库操作的管理。它们通常一起使用,以构建 Java 应用程序的持久层。

数据映射:

MyBatis 的核心功能之一是提供简单且强大的数据映射。使用 XML或注解来定义 SQL查询和映射结果,将数据库表记录映射到 Java对象。

  • 1.1 XML 映射文件:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <!-- 定义查询 -->
    <select id="selectUser" resultType="User">
    SELECT * FROM users WHERE id = #{id}
    </select>
    <!-- 映射结果到对象 -->
    <resultMap id="BaseResultMap" type="User">
    <id column="id" property="id"/>
    <result column="username" property="username"/>
    <result column="password" property="password"/>
    </resultMap>
  • 1.2 注解方式
    1
    2
    @Select("SELECT * FROM users WHERE id = #{id}")
    User selectUser(int id);

核心功能:

  • 动态 SQL:MyBatis 允许你在 XML 中编写动态 SQL 语句,可以根据条件动态构建 SQL 查询。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <select id="selectUsers" parameterType="map" resultType="User">
    SELECT * FROM users
    WHERE 1=1
    <if test="username != null">
    AND username = #{username}
    </if>
    <if test="password != null">
    AND password = #{password}
    </if>
    </select>
  • 参数传递:MyBatis 支持多种参数传递方式,包括单个参数、多个参数、Map 和注解等。
    1
    2
    @Select("SELECT * FROM users WHERE id = #{id} AND username = #{username}")
    User selectUserByIdAndUsername(@Param("id") int id, @Param("username") String username);
  • 批处理:MyBatis 允许执行批处理操作,可以有效地执行一组 SQL 语句。
    1
    2
    3
    4
    5
    6
    7
    8
    SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    for (User user : userList) {
    userMapper.insertUser(user);
    }
    sqlSession.flushStatements();
    sqlSession.commit();
    sqlSession.close();

事务管理:

  • MyBatis 也提供了事务管理的支持。可以通过配置数据源和事务管理器来实现事务的控制。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <!-- 数据源配置 -->
    <dataSource type="POOLED">
    <property name="driver" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/mydatabase"/>
    <property name="username" value="root"/>
    <property name="password" value="password"/>
    </dataSource>
    <!-- 事务管理器配置 -->
    <transactionManager type="JDBC"/>
  • MyBatis 可以很容易地与 Spring 框架集成,通过 Spring 的事务管理来控制数据库事务。
    1
    2
    3
    4
    5
    6
    7
    8
    <!-- Spring 配置 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="mapperLocations" value="classpath:mapper/*.xml"/>
    </bean>
    <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
    <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>

SpringBoot实体类 —— VO/DTO/PO

  • VO:View Object,主要用于展示层。它的作用是把某个指定前端页面的所有数据封装起来。他的作用主要是减少传输数据量大小和保护数据库隐私数据(如用户密码、用户邮箱等相关信息)不外泄,同时保护数据库的结构不外泄。

  • DTO:Data Transfer Object,数据传输对象,用于展示层与服务层之间的数据传输对象。(注:实际开发中还存在BO,其作用和DTO类似,当业务逻辑不复杂时一般会被合并。)

  • PO:Persistant Object,持久化对象,和数据库形成映射关系。简单说PO就是每一个数据库中的数据表,一个字段对应PO中的一个变量。(也就是我们常用的Entities)

    1、从前端页面中收到JSON格式数据,后端接口中将其封装为一个VO对象;接口接收到VO对象后将其转换为DTO对象,并调用业务类方法对其进行处理;然后处理为PO对象,调用Dao接口连接数据库进行数据访问(查询、插入、更新等)2、后端从数据库得到结果后,根据Dao接口将结果映射为PO对象,然后调用业务类方法将其转换为需要的DTO对象,再根据前端页面实际需求,转换为VO对象进行返回。
  • 类型转换:上述过程中,VO/DTO/PO等实体类中字段常常会存在多数相同,根据业务需求少数不同。为避免频繁的set和get操作对其进行转换,spring为我们提供了多种方法。(1)使用BeanUtils:(springframework包下)(2)使用BeanUtils:(Apache包下)(3)使用modelMapper??

  • DO(Data Object):通常表示数据库中的数据实体,对应数据库表的结构。它主要用于数据存储和数据库操作,包含与数据库表字段一一对应的属性。类中通常包含与数据库表字段对应的成员变量、getter 和 setter 方法。它不应包含业务逻辑,主要负责数据的持久化和映射。
    尽管 PO 和 DO 在一些情况下用法相似,但它们的侧重点有所不同。PO 更侧重于与数据库的交互,强调持久化和数据表映射;而 DO 侧重于在不同层之间传递数据,强调业务逻辑层面的数据封装。

  • BO(Business Object):通常表示业务层的业务实体,主要用于封装业务逻辑。BO 类一般包含与业务逻辑相关的属性和方法,与具体的数据存储形式无关。包含了一些业务逻辑的操作,比如计算、验证等。它不应直接与数据库进行交互,而是通过调用 Service 层或 DAO 层的方法实现数据的获取和存储。


2. Mybatis-Plus

  • 基于MyBatis:MyBatis-Plus是MyBatis的增强工具包,是在MyBatis基础上的扩展。只做增强不做改变,为简化开发、提高效率而生。它提供了更多的便捷、高效的开发功能,简化了开发人员的编码工作,大幅度提高了开发效率。
  • 功能:MyBatis-Plus 集成了MyBatis的核心功能,同时提供了更多针对CRUD操作、条件构造器、分页、代码生成器等功能的封装。
  • 简化操作:可以减少重复的CRUD代码,提供了一些便捷的API接口和工具,使得开发人员能够更方便地进行数据库操作。
  • 引入 MybatisPlus依赖,可以直接代替 Mybatis依赖
    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version>
    </dependency>
  • MyBatisPlus 的配置项继承了 MyBatis原生配置和一些自己特有的配置。例如:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    mybatis-plus:
    type-aliases-package: com.itheima.mp.domain.po # 别名扫描包
    mapper-locations:"classpath*:/mapper/**/*.xml" # Mapper.xml文件地址,默认值
    configuration:
    map-underscore-to-camel-case: true # 是否开户下划线和驼峰的映射
    cache-enabled: false # 是否开户二级缓存
    global-config:
    db-config:
    id-type: assign_id # id为雪花算法生成
    update-strategy: not_null # 更新笑略:只更新非空字段

BaseMapper

  • 定义 Mapper接口并继承 BaseMapper类,泛型指定要与数据库映射的 Java实体类(pojo类);
    MyBatisPlus通过扫描实体类,并基于反射获取实体类信息作为数据库表信息,自动实现 CRUD的逻辑
    • 默认以类名驼峰转下划线作为表名(User类 -> user表)
    • 默认把名为id的字段作为主键
    • 默认把变量名驼峰转下划线作为表的字段名(createTime类属性 -> create_time表字段)
      1
      public interface UserMapper extends BaseMapper<User>{}
  • 如果实体类和表的对应关系不符合 mp的约定,就要自行配置。可以使用注解:
    • @TableName:指定表名称及全局配置
    • @Tableld:指定id字段及相关配置;
      • IdType的常见类型有:AUTO、ASSIGN ID(默认使用,雪花算法)、INPUT
    • @TableField:指定普通字段及相关配置。使用 @TableField的常见场景是:
      • 1、成员变量名与数据库字段名不一致 2、成员变量名以is开头,且是布尔值
        3、成员变量名与数据库关键字冲突 4、成员变量不是数据库字段

条件构造器

MyBatisPlus支持 使用 Wrapper构造各种复杂的where条件,而不需要在 xml中写sql语句。可以满足日常开发的所有需求。

  • QueryWrapper 和LambdaQueryWrapper通常用来构建 select、delete、update的 where条件部分
  • UpdateWrapper 和LambdaUpdateWrapper通常只有在 set语句比较特殊才使用
  • 尽量使用 LambdaQueryWrapper和 LambdaUpdateWrapper避免硬编码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    // 原sql:SELECT id,username,info,balance FROM user WHERE username LIKE ? AND balance >= ?
    void testQuerywrapper() {
    QueryWrapper<User> wrapper = new QueryWrapper<User>() // 1.构建查询条件
    .select("id", "username", "info", "balance")
    .like("username", "o")
    .ge("balance", 1000);
    List<User> users = userMapper.selectList(wrapper); // 2.查询
    }
    // 使用 Lambda替代上方法中的硬编码
    void testLambdaQuerywrapper() {
    LambdaQuerywrapper<User> wrapper = new LambdaQuerywrapper<User>()
    .select(User::getid, User::getUsername, User::getInfo, User::getBalance)
    .like(User::getUsername, "o")
    .ge(User::getBalance, 1000)
    List<User> users = userMapper.selectList(wrapper);
    }
    // 原sql:UPDATE user SET balance = 2000 WHERE (username = "jack")
    void testUpdateByQuerywrapper() {
    User user = new User()// 1.要更新的数据
    user.setBalance(2000);
    QueryWrapper<User> wrapper = new QueryWrapper<User>().eq("username", "jack");// 2.更新的条件
    userMapper.update(user, wrapper);// 3.执行更新
    }
    // 原sql:UPDATE user SET balance = balance - 200 WHERE id in (1,2,4)
    void testUpdatewrapper() {
    List<Long> ids = List.of(1L2L4L);
    Updatewrapper<User> wrapper = new Updatewrapper<User>()
    .setSql("balance = balance - 200")
    .in("id", ids) ;
    userMapper.update(null, wrapper) ;
    }

自定义SQL

我们可以利用 MyBatisPlus的 Wrapper来构建复杂的 Where条件,然后自己定义SQL语句中剩下的部分。
在业务层编写wrapper包含sql中的where部分,在mapper方法声明wrapper变量名称“ew”,最后在mapper对应的xml中自定义sql编写where以外的部分(解决了 不能在业务层编写sql 和 使用mp简化查询语句编写 的矛盾??

IService接口

  • 简单业务方法,直接在controller中调用对应的IService中的方法;
    对于复杂业务,需要在自定义Servicelmpl中编写逻辑,调用对应的BaseMapper中的方法;
    当BaseMapper不足以满足需求时,需要在mapper中编写自定义sql(处理where之外的sql,如update…);
    对于mapper中自定义sql,简单的使用注解编写,复杂的在xml中编写。。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // 1、自定义Service接口继承IService接口
    public interface IUserService extends IService<User> {
    }
    // 2、自定义Service实现类,实现自定义接口并继承Servicelmpl类(否则要自己一个个实现IService接口的方法)
    public class UserServiceImpl
    extends ServiceImpl<UserMapper, User> // 泛型中指定mapper和实体类类型
    implements IUserService {
    }
    // 使用方法
    class IUserServiceTest {
    @Autowired
    private IUserService userService;
    @Test
    void testSaveUser() {
    User user = new User();
    user.setUsername(uLiLeiu);
    user.setPassword("123");
    userService.save(user); // 如果方法与BaseMapper中的重复,就不需要BaseMapper了??
    }
    }
  • IService的 Lambda方法:在 自定义的 Servicelmpl类中进行 复杂操作
    1. 需求:复杂查询,查询条件如下(name: 用户名关键字,可以为空;status:用户状态,可以为空;minBalance:最小余额,可以为空;maxBalance:最大余额,可以为空)
      1
      2
      3
      4
      5
      6
      7
      8
      public List<User> queryUsers(String name, Integer status, Integer minBalance Integer maxBalance) {
      return lambdaQuery()
      .like(name != null, User::getUsername, name)
      .eg(status != null, User::getStatus, status)
      .ge(minBalance != null, User::getBalance, minBalance)
      .le(maxBalance != null,User::getBalance, maxBalance)
      .list(); // 如果查询一个记录就是.one
      }
    2. 需求:复杂更新,要求如下(按id更新,更新为扣后余额,如果扣减后余额为0,则将用户status修改为冻结状态(2))
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      @Transaction
      public List<User> deductBalance(Long id,Integer money) {
      ...
      // 4.扣城余额 update tb_user set balance = balance - ?
      int remainBalance = user.getBalance() - money;
      lambdaUpdate()
      .set(User::getBalance, remainBalance)
      .set(remainBalance == 0, User::getStatus, 2) // 如果余额0,修改状态
      .eq(User::getId, id) // 相当于 where
      .eq(User::getBalance,user.getBalance()) // 乐观锁
      .update(); // 更新
      }
  • IService批量新增(批处理):开启 rewriteBatchedStatements=true参数

分页插件

首先,要在配置类中注册MyBatisplus的核心插件,同时添加分页插件
接着,就可以使用分页的API了

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class MybatisConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() { // 拦截器的形式实现插件
// 1. 初始化核心插件
MybatisplusInterceptor interceptor = new MybatisplusInterceptor();
// 2. 添加分页插件
PaginationInnerInterceptor pageInterceptor = new PaginationInnerInterceptor(DbType.MYSOL);
pageInterceptor.setMaxLimit(100L); // 设置分页上限
interceptor.addInnerInterceptor(pageInterceptor); // 添加到核心拦截器
return interceptor;
}
}
1
2
3
4
5
6
7
8
void testPageQuery() {
int pageNo = 1, pageSize = 5; // 分页参数
Page<User> page = Page.of(pageNo,pageSize);
page.addOrder(new OrderItem("balance"false)); // 排序参数,通过OrderItem来指定
// 分页查询
Page<User> p = userService.page(page);
// 总条数 p.getTotal(); 总页数 p.getPages(); 分页数据 List<User> records = p.getRecords();
}

MySQL

MySQL是一个开源的关系型数据库管理系统(RDBMS),广泛用于Web应用程序和中小型企业数据库。默认端口是 3306

启动并连接 MySQL

  • 启动 MySQL 服务器(然后尝试连接到 MySQL 服务器;一般情况下 服务器不会自动关闭)
    1
    2
    net start MySQL  # 打开命令提示符(以管理员身份运行),运行以下命令来启动 MySQL 服务
    net stop MySQL # 停止 MySQL 服务
  • 另外,通过 Navicat 创建与本地(或远程) 的 MySQL(或其他)数据库的连接,以对数据库可视化管理
    本机 username: root,password: “0xx1xx”

SQL 术语


关系(Relation):通常是指数据库表(table)。每个关系对应数据库中的一个表格,由多个行和列组成,其中每一行通常代表表格中的一个数据记录,而每一列代表记录中的一个属性(字段)。
属性(attribute):列的名字,上图有学号、姓名、班级、兴趣爱好、班主任、课程、授课主任、分数.
依赖 (relation):列属性间存在的某种联系
元组(tuple):每一个行,如第二行 (1301,小明,13班,篮球,王老师,英语,赵英,70) 就是一个元组
模式(schema):这里指逻辑结构,如 学生信息(学号,姓名,班级,兴趣爱好,班主任,课程,授课主任,分数)的笼统表述。(数据库模式是数据库的结构描述,包括表格、字段、关系、视图、索引等元素的组织方式。描述了数据库中不同关系表格之间的关联和数据的组织方式。通常包含了数据库中表格的定义,包括表格的名称、字段的名称、字段的数据类型、主键等信息。)
(domain):数据类型,如string、integer等,上图中每一个属性都有它的数据类型 (即域)
(key):由关系的一个或多个属性组成,任意两个键相同的元组,所有属性都相同。需要保证表示键的属性最少。一个关系可以存在好几种键,工程中一般从这些候选键中选出一个作为主键 (primary key)
主键(Primary Key):主键是一个表格中的一列或一组列,它的值用来唯一标识表格中的每一行记录。主键的值不能重复,且不能为空。这意味着每一行记录在主键列上必须有唯一的值,用于区分记录。主键用于建立表格之间的关系和确保数据的完整性。通常,每个表格都有一个主键,但也可以由多个列组成复合主键
候选键(candidate key):由关系的一个或多个属性组成,候选键都具备键的特征,都有资格成为主键
超键(super key):包含键的属性集合,无需保证属性集的最小化。每个键也是超键。可以认为是键的超集。
外键(foreign key):如果某一个关系A中的一个(组)属性是另一个关系B的键,则该(组)属性在A中称为外键。
主属性 (prime attribute):所有候选键所包含的属性都是主属性
投影 (proiection):选取特定的列,如将关系学生信息投影为学号、姓名即得到上表中仅包含学号、姓名的列
选择 (selection):按照一定条件选取特定元组,如选择上表中分数>80的元组
笛卡儿积 (交叉连接Cross join):第一个关系每一行分别与第二个关系的每一行组合
自然连接(natural join):第一个关系中每一行与第二个关系的每一行进行匹配,如果得到有交叉部分则合并,若无交叉部分则舍弃。
连接(theta join):即加上约束条件的笛卡儿积,先得到笛卡儿积,然后根据约束条件删除不满足的元组.
外连接 (outer join):执行自然连接后,将舍弃的部分也加入,并且匹配失败处的属性用NULL代替。
除法运算(division):关系R除以关系S的结果为T,则T包含所有在R但不在S中的属性,且T的元组与S的元组的所有组合在R中。


SQL 语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
CREATE DATABASE mydatabase;  /* 创建数据库 */
USE mydatabase; /* 选择数据库 */
CREATE TABLE users ( -- 创建表
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(255),
email VARCHAR(255)
);
DROP TABLE users; -- 删除表
ALTER TABLE users ADD col CHAR(20); -- 添加列
ALTER TABLE mytable DROP COLUMN col; -- 删除列
ALTER TABLE mytable CHANGE col col1 CHAR(32) NOT NULL DEFAULT '123'; -- 修改列和属性
-- 插入数据
INSERT INTO users (username, email) VALUES ('user1', 'user1@example.com');
-- 插入检索出来的数据
INSERT INTO mytable1(col1, col2)
-- 更新数据
UPDATE users SET email='newemail@example.com' WHERE username='user1';
-- 删除数据
DELETE FROM users WHERE username='user1';

查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
SELECT distinct id 
FROM users;
LIMIT 2, 3;
ORDER BY col1 DESC, col2 ASC;
WHERE col is NULL;
-- 子查询:只能返回一个字段的数据,可以将子查询的结果作为 WHRER 语句的过滤条件,配合 (not )in
SELECT * FROM mytable1
WHERE col1 IN (SELECT col2
FROM mytable2);
-- 分组:将数据按照一个或多个列的值分成不同的组,常与聚合函数(如 SUM、COUNT、AVG 等)一起以对每个组聚合操作
-- GROUP BY 子句: 用于指定按哪些列进行分组; HAVING 子句: 用于对分组后的数据进行筛选。
-- WHERE 过滤行,HAVING 过滤分组,行过滤应当先于分组过滤。
SELECT col, COUNT(*) AS num
FROM mytable
WHERE col > 2
GROUP BY col
HAVING num >= 2
ORDER BY SUM(column2) DESC;
  1. SELECT:查询出属性,用 AS 给列名、计算字段和表名取别名,以简化 SQL 语句以及连接相同表;
    select 中 sql 函数计算出的值作为查询出的属性(select round(count()/3, 2) from ..)
    select 中可以加一个select 用于属性计算(select id, count(
    )/(select count(*) from Users) per from ..)
    若有重复列,使用 DISTINCT 去除重复值
  2. LIMIT:LIMIT 2, 3 返回第 3 ~ 5 行。配合排序实现获取最大/最小值。。
  3. WHERE:过滤行,AND 和 OR 用于连接多个过滤条件。优先处理 AND,使用 () 决定优先级;
    is 搭配 null,IN 操作符用于匹配一组值,其后也可以接一个 SELECT 子句,从而匹配子查询得到的一组值。
  4. ORDER:可以按多个列进行排序,并指定不同的排序方式,默认升序ASC, 降序DESC
  5. GROUP BY 可以放在 WHERE 前、后,想清楚在分组前、后过滤
  6. 子查询的结果需要指定别名。

连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-- 连接(内连接):返回多个表中匹配条件满足的行,不匹配的行不会被包括在结果集中。
-- 使用 (INNER )JOIN 关键字,条件语句使用 ON 而不是 WHERE,连接可以替换子查询且效率一般会更快。
SELECT A.value, B.value
FROM tablea AS A JOIN tableb AS B
ON A.key = B.key;
-- 自连接:可以看成内连接的一种,只是连接的表是自身而已。
SELECT e1.name
FROM employee AS e1 INNER JOIN employee AS e2
ON e1.department = e2.department AND e2.name = "Jim";
-- 自然连接:自然连接是把同名列通过等值测试连接起来的,同名列可以有多个。
-- 内连接和自然连接的区别: 内连接提供连接的列,而自然连接自动连接所有同名列。
SELECT A.value, B.value FROM tablea AS A NATURAL JOIN tableb AS B;
-- 外连接:保留了没有关联的那些行。分为左,右外连接以及全外连接。
-- 左连接返回左表的所有行以及与右表匹配的行。如果右表中没有匹配的行,将会返回 NULL 值。
-- on 后等于新连接出了一张表,where 在新表上进行查询
select Employee.name, Bonus.bonus
from Employee left join Bonus
on Employee.empId = Bonus.empId
where Bonus.bonus < 1000 or Bonus.bonus is null

SQL 函数

  1. 数学函数:SUM():计算指定列的总和。 AVG():计算指定列的平均值。 MAX():找出指定列的最大值。 MIN():找出指定列的最小值。 COUNT():计算指定列的行数。 ROUND(x,y):把 x 四舍五入到 y 位小数。 ABS():返回绝对值。
  2. 字符串函数:CONCAT():连接两个或多个字符串。 SUBSTRING()SUBSTR():从字符串中提取子字符串。 LENGTH():返回字符串的长度。 UPPER():将字符串转换为大写。 LOWER():将字符串转换为小写。 TRIM():去除字符串首尾的空格或其他指定字符。 REPLACE():替换字符串中的子串。
  3. 日期和时间函数:NOW()CURRENT_TIMESTAMP():返回当前日期和时间。 DATE():从日期时间值中提取日期部分。 TIME():从日期时间值中提取时间部分。 YEAR():从日期中提取年份。 MONTH():从日期中提取月份。 DAY():从日期中提取天。HOUR():从时间中提取小时。 MINUTE():从时间中提取分钟。 SECOND():从时间中提取秒。
  4. 逻辑函数: IF()CASE:根据条件返回不同的值,if(rating<3, 1, 0);COALESCE():返回第一个非空值。
  5. 聚合函数: GROUP_CONCAT():将组内的值连接成一个字符串。 GROUP_BY:分组聚合查询结果。
  6. 窗口函数:ROW_NUMBER():为结果集的每行分配一个唯一的行号。 RANK():为结果集中的行分配排名。 DENSE_RANK():为结果集中的行分配密集排名。 OVER():定义窗口以进行窗口函数计算。

MySQL 数据类型

  1. 整数类型: 用于存储整数值,包括 TINYINT, SMALLINT, MEDIUMINT, INT, 和 BIGINT
  2. 浮点数类型: 用于存储浮点数,包括 FLOATDOUBLEDECIMAL 为高精度小数类型。CPU 原生支持浮点运算,但是不支持 DECIMAl 类型的计算,因此 DECIMAL 的计算比浮点类型需要更高的代价。
    FLOAT、DOUBLE 和 DECIMAL 都可以指定列宽。如 DECIMAL(18, 9) 表示总共 18 位,取 9 位存储小数部分,剩下 9 位存储整数部分
  3. 定点数类型: 用于存储定点数,包括 DECIMALNUMERIC
  4. 字符串类型: 用于存储文本数据,主要有 CHAR, VARCHAR 两种类型,一种是定长的,一种是变长的。
    VARCHAR 这种变长类型能够节省空间,因为只需要存储必要的内容。VARCHAR 会保留字符串末尾的空格,而 CHAR 会删除。
  5. 二进制数据类型: 用于存储二进制数据,包括 BINARY, VARBINARY, TINYBLOB, BLOB, MEDIUMBLOB, 和 LONGBLOB.
  6. 日期和时间类型(Date and Time Types): DATE 用于存储日期。日期格式为’YYYY-MM-DD’,如’2023-10-12’。 TIME 用于存储时间。时间格式为’HH:MM:SS’,如 ‘14:30:45’。 YEAR 用于存储年份,可以使用两位或四位格式(’YY’或’YYYY’)
    DATETIME 用于存储日期和时间。能够保存从 1001 年到 9999 年的日期和时间,精度为秒,使用 8 字节的存储空间。它与时区无关,格式为’YYYY-MM-DD HH:MM:SS’,如’2023-10-12 14:30:45’。
    TIMESTAMP 用于存储日期和时间,在插入或更新时自动记录当前时间。使用 4 个字节,只能表示从 1970 年到 2038 年。时区有关,即一个时间戳在不同的时区所代表的具体时间是不同的。应该尽量使用 TIMESTAMP,因为它比 DATETIME 空间效率更高。
  7. 布尔类型: 用于存储布尔值,包括 BOOLEAN, BOOL, TINYINT(1)
  8. 枚举类型: 用于存储枚举值,其中一个预定义的枚举值,如 ENUM('value1', 'value2', ...)
  9. 集合类型: 用于存储一个或多个预定义的集合值,如 SET('value1', 'value2', ...)
  10. 自动增长类型: 用于自动生成唯一标识符,如 AUTO_INCREMENT

函数依赖

记 A->B 表示 A 函数决定 B,也可以说 B 函数依赖于 A。
如果 {A1,A2,… ,An} 是关系的一个或多个属性的集合,该集合函数决定了关系的其它所有属性并且是最小的,那么该集合就称为键码。
对于 A->B,如果能找到 A 的真子集 A’,使得 A’-> B,那么 A->B 就是部分函数依赖,否则就是完全函数依赖。
对于 A->B,B->C,则 A->C 是一个传递函数依赖。
有依赖:学号 -> 姓名、学院, 学院 -> 院长, 学号、课程-> 成绩
则 成绩Grade 完全函数依赖于键码(学号,课程),它没有任何冗余数据,每个学生的每门课都有特定的成绩。姓名, 学院 和 院长 都部分依赖于键码,当一个学生选修了多门课时,这些数据就会出现多次,造成大量冗余数据。

MySQL 范式

范式理论是为了解决四种异常。不符合范式的关系,会产生很多异常:1、冗余数据。2、修改异常: 修改了一个记录中的信息,但是另一个记录中相同的信息却没有被修改。3、删除异常: 删除一个信息,那么也会丢失其它信息。4、插入异常: 例如想要插入一个学生的信息,如果这个学生还没选课,那么就无法插入。
高级别范式的依赖于低级别的范式,1NF 是最低级别的范式。

  1. 第一范式 (1NF):属性不可分。
  2. 第二范式 (2NF):每个非主属性完全函数依赖于键码。可以通过分解来满足。(一张表分解成多张表)
  3. 第三范式 (3NF):非主属性不传递函数依赖于键码。可以进行分解。
    for more:https://blog.csdn.net/calcular/article/details/79332453

事务管理

  • 事务:指的是满足 ACID 特性的一组操作,可以通过 Commit 提交一个事务,也可以使用 Rollback 进行回滚。
    • 原子性(Atomicity)事务被视为不可分割的最小单元,事务的所有操作要么全部提交成功,要么全部失败回滚。回滚可以用日志来实现,日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可。
    • 一致性(Consistency)数据库在事务执行前后都保持一致性状态。一致性状态下,所有事务对一个数据的读取结果都是相同的。
    • 隔离性(Isolation)一个事务所做的修改在最终提交以前,对其它事务是不可见的。
    • 持久性(Durability)一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。可以通过数据库备份和恢复来实现,在系统发生崩溃时,使用备份的数据库进行数据恢复。
  • 并发一致性:产生不一致的主要原因是破坏了事务的隔离性,解决方法是通过并发控制来保证隔离性。
    • 丢失修改:T1 和 T2 两个事务都对一个数据进行修改,T1 先修改,T2 随后修改,T2 的修改覆盖了 T1 的修改。
    • 读脏数据:T1 修改一个数据,T2 随后读取这个数据。如果 T1 撤销了这次修改,那么 T2 读取的数据是脏数据。
    • 不可重复读:T2 读取一个数据,T1 对该数据做了修改。 T2 再次读取这个数据时读取的结果和第一次读取的结果不同。
    • 幻影读:T1 读取某个范围的数据,T2 在这个范围内插入新的数据,T1 再次读取这个范围的数据 结果和第一次不同。
      并发控制可以通过 封锁 来实现,但是封锁操作需要用户自己控制,相当复杂。数据库管理系统提供了事务的 隔离级别,让用户以一种更轻松的方式处理并发一致性问题。
  • 封锁
    • 封锁粒度:MySQL 中提供了两种封锁粒度,行级锁以及表级锁。
    • 读写锁:1、排它锁(Exclusive),简写为 X 锁,又称写锁。 2、共享锁(Shared),简写为 S 锁,又称读锁。
    • 意向锁:使用意向锁(Intention Locks)可以更容易地支持多粒度封锁。
    • 封锁协议:1. 三级封锁协议 2. 两段锁协议
  • 隔离等级:MySQL支持 四种 标准的事务隔离级别,这些隔离级别定义了事务之间的可见性和并发控制。
    READ UNCOMMITTED / READ COMMITTED / REPEATABLE READ / SERIALIZABLE 脏读 不可重复读 幻影读
    未提交读(允许一个事务读取另一个事务未提交的数据)
    提交读(一个事务只能读取到另一个事务已经提交的数据) ×
    可重复读(事务执行期间,一个事务多次读取同一行数据时,会得到相同的结果) × ×
    可串行化(最高的隔离级别,确保事务串行执行) × × ×
  • 数据库默认隔离级别
    oracle数据库默认的隔离级别是:读已提交。
    mysql数据库默认的隔离级别是:可重复读。
  • InnoDB 中如何防止幻读 or MVCC 实现
    • 1、执行普通 select,此时会以 MVCC 快照读的方式读取
      (1)一致性非锁定读(快照读),普通的SELECT,通过多版本并发控制(MVCC)实现。
      (2)在快照读下,RR 隔离级别只会在事务开启后的第一次查询生成 Read View ,并使用至事务提交。所以在生成 Read View 之后其它事务所做的更新、插入记录版本对当前事务并不可见,实现了可重复读和防止快照读下的 “幻读”。
      (3)只有在事务隔离级别 RC(读取已提交) 和 RR(可重读)下,InnoDB 才会使用一致性非锁定读。
    • 2、执行 select…for update/lock in share mode、insert、update、delete 等当前读
      (1)一致性锁定读(当前读),SELECT … FOR UPDATE/SELECT … LOCK IN SHARE MODE/INSERT/UPDATE/DELETE,通过锁实现。
      (2)在当前读下,读取的都是最新的数据,如果其它事务有插入新的记录,并且刚好在当前事务查询范围内,就会产生幻读!InnoDB 使用 Next-key Lock 来防止这种情况。当执行当前读时,会锁定读取到的记录的同时,锁定它们的间隙,防止其它事务在查询范围内插入数据。只要我不让你插入,就不会发生幻读。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      START TRANSACTION;  -- 启动事务,用于标志事务的开始。在这之后的 SQL 语句都将在一个事务中执行 
      COMMIT; -- 提交事务,当所有的 SQL 语句都执行成功时,使用 `COMMIT` 命令来提交事务,将更改永久保存到数据库。提交后,事务结束。
      ROLLBACK; -- 回滚事务,如果在事务执行的过程中发生了错误或者不符合条件,可以使用 `ROLLBACK` 命令来回滚事务,撤销所有的更改,使数据库回到事务开始前的状态。

      SAVEPOINT savepoint_name; -- 保存点,用于创建一个保存点,以便在事务中的某一时刻回滚到这个保存点。可以在事务中设置多个保存点。
      ROLLBACK TO savepoint_name; -- 回滚到保存点,如果在事务中的某一步出现问题,可以回滚到之前设置的某个保存点,而不必回滚整个事务。

      -- 设置事务隔离级别,包括 `READ UNCOMMITTED`、`READ COMMITTED`、`REPEATABLE READ` 和 `SERIALIZABLE`。
      SET TRANSACTION ISOLATION LEVEL level;

      -- MySQL 默认是自动提交模式,即每个 SQL 语句都会自动成为一个事务并提交。命令关闭自动提交,再通过 `COMMIT` 手动提交,或者通过 `ROLLBACK` 回滚。
      SET autocommit = 0;

      --示例 1
      START TRANSACTION;
      -- 执行一系列 SQL 语句
      -- 提交事务
      COMMIT;

      --示例 2
      START TRANSACTION;
      -- 执行一系列 SQL 语句
      -- 如果发生错误,回滚事务
      ROLLBACK;

MySQL 存储引擎

  • InnoDB
    • 是 MySQL 默认的事务型存储引擎。 实现了四个标准的隔离级别,默认级别是可重复读。在可重复读隔离级别下,通过多版本并发控制(MVCC)+ 间隙锁(Next-Key Locking)防止幻影读
    • 主索引是聚簇索引,在索引中保存了数据,从而避免直接读取磁盘,因此对查询性能有很大的提升。 内部做了很多优化,包括从磁盘读取数据时采用的可预测性读、能够加快读操作并且自动创建的自适应哈希索引、能够加速插入操作的插入缓冲区等
    • 支持真正的在线热备份。其它存储引擎不支持在线热备份,要获取一致性视图需要停止对所有表的写入,而在读写混合场景中,停止写入可能也意味着停止读取。
  • MyISAM
    • 设计简单,数据以紧密格式存储。对于只读数据,或者表比较小、可以容忍修复操作,则依然可以使用它
    • 提供了大量的特性,包括压缩表、空间数据索引等。
    • 不支持事务
    • 不支持行级锁,只能对整张表加锁,读取时会对需要读到的所有表加共享锁,写入时则对表加排它锁。但在表有读取操作的同时,也可以往表中插入新的记录,这被称为并发插入(CONCURRENT INSERT)。
    • 可以手工或者自动执行检查和修复操作,但是和事务恢复以及崩溃恢复不同,可能导致一些数据丢失,而且修复操作非常慢
    • 如果指定了 DELAY KEY WRITE 选项,在每次修改执行完成时,不会立即将修改的索引数据写入磁盘,而是会写到内存中的键缓冲区,只有在清理键缓冲区或者关闭表的时候才会将对应的索引块写入磁盘。这种方式可以极大的提升写入性能,但是在数据库或者主机崩溃时会造成索引损坏,需要执行修复操作

MySQL 索引

在MySQL中,索引是一种用于提高数据库查询效率的数据结构。它类似于书的目录,通过在数据库表上创建索引,可以快速定位并访问表中的特定数据行,而无需全表扫描。索引在数据库的性能优化中扮演着关键的角色,特别是在大型数据集上。
索引是在存储引擎层实现的,而不是在服务器层实现的,所以不同存储引擎具有不同的索引类型和实现。

  • 索引类型
    1. B+Tree 索引:是大多数 MySQL 存储引擎的默认索引类型。因为不再需要进行全表扫描,只需要对树进行搜索即可,因此查找速度快很多。除了用于查找,还可以用于排序和分组。
      InnoDB 的 B+Tree 索引分为主索引和辅助索引。主索引的叶子节点 data 域记录着完整的数据记录,这种索引方式被称为聚簇索引。因为无法把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。辅助索引的叶子节点的 data 域记录着主键的值,因此在使用辅助索引进行查找时,需要先查找到主键值,然后再到主索引中进行查找。
    2. 哈希索引:能以 O(1) 时间进行查找,但是失去了有序性。存在限制:无法用于排序与分组;只支持精确查找,无法用于部分查找和范围查找。
      InnoDB 存储引擎有一个特殊的功能叫“自适应哈希索引”,当某个索引值被使用的非常频繁时,会在 B+Tree 索引之上再创建一个哈希索引,这样就让 B+Tree 索引具有哈希索引的一些优点,比如快速的哈希查找。
    3. 全文索引:MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而不是直接比较是否相等。查找条件使用 MATCH AGAINST,而不是普通的 WHERE。全文索引一般使用倒排索引实现,它记录着关键词到其所在文档的映射。InnoDB 存储引擎在 MySQL 5.6.4 版本中也开始支持全文索引。
    4. 空间数据索引:MyISAM 存储引擎支持空间数据索引(R-Tree),可以用于地理数据存储。空间数据索引会从所有维度来索引数据,可以有效地使用任意维度来进行组合查询。必须使用 GIS 相关的函数来维护数据
  • 索引的优点:大大减少了服务器需要扫描的数据行数。帮助服务器避免进行排序和分组,也就不需要创建临时表(B+Tree 索引是有序的,可以用于 ORDER BY 和 GROUP BY 操作。临时表主要是在排序和分组过程中创建,因为不需要排序和分组,也就不需要创建临时表)。将随机 I/O 变为顺序 I/O(B+Tree 索引是有序的,也就将相邻的数据都存储在一起)。
  • 索引的使用场景
    • 对于非常小的表、大部分情况下简单的全表扫描比建立索引更高效。
    • 对于中到大型的表,索引就非常有效。
    • 但是对于特大型的表,建立和维护索引的代价将会随之增长。这种情况下,需要用到一种技术可以直接区分出需要查询的一组数据,而不是一条记录一条记录地匹配,例如可以使用分区技术。

B+Tree 索引

是大多数 MySQL 存储引擎的默认索引类型因为不再需要进行全表扫描,只需要对树进行搜索即可,因此查找速度快很多。除了用于查找,还可以用于排序和分组可以指定多个列作为索引列,多个索引列共同组成键.
适用于全键值、键值范围和键前缀查找,其中键前缀查找只适用于最左前缀查找。如果不是按照索引列的顺序进行查找,则无法使用索引。
InnoDB 的 B+Tree 索引分为主索引和辅助索引.
主索引的叶子节点 data 域记录着完整的数据记录,这种索引方式被称为聚簇索引。因为无法把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。

索引优化

1.独立的列
在进行查询时,索引列不能是表达式的一部分,也不能是函数的参数,否则无法使用索引。例如下面的查询不能使用 actor id 列的索引:
2.多列索引
在需要使用多个列作为条件进行查询时,使用多列索引比使用多个单列索引性能更好。例如下面的语句中,最好把 actor id和 film id 设置为多列索引。
3.索引列的顺序
4.前缀索引对于 BLOB、TEXT 和 VARCHAR 类型的列,必须使用前缀索引,只索引开始的部分字符。对于前缀长度的选取需要根据索引选择性来确定。
5.覆盖索引


MySQL - 一条 SQL 的执行过程详解

https://pdai.tech/md/db/sql-mysql/sql-mysql-execute.html

在系统和 MySQL 进行交互之前,MySQL 驱动会帮我们建立好连接,然后我们只需要发送 SQL 语句就可以执行 CRUD 了。
java 系统在通过 MySQL 驱动和 MySQL 数据库连接的时候是基于 TCP/IP 协议的,多线程请求的时候频繁的创建和销毁连接显然是不合理的。在访问 MySQL 数据库的时候,建立的连接并不是每次请求都会去创建的,而是从数据库连接池中去获取,这样就解决了因为反复的创建和销毁连接而带来的性能损耗问题了。MySQL 的架构体系中也已经提供了这样的一个池子,也是数据库连池。双方都是通过数据库连接池来管理各个连接的,这样一方面线程之前不需要是争抢连接,更重要的是不需要反复的创建的销毁连接。

网络连接必须由线程来处理

网络中的连接都是由线程来处理的,所谓网络连接说白了就是一次请求,每次请求都会有相应的线程去处理的。也就是说对于 SQL 语句的请求在 MySQL 中是由一个个的线程去处理的。

SQL 接口:MySQL 中处理请求的线程在获取到请求以后获取 SQL 语句去交给 SQL 接口去处理。
查询解析器:会将 SQL 接口传递过来的 SQL 语句进行解析,翻译成 MySQL 自己能认识的语言
MySQL 查询优化器:查询优化器内部具体怎么实现的我们不需要是关心,我需要知道的是 MySQL 会帮我去使用他自己认为的最好的方式去优化这条 SQL 语句,并生成一条条的执行计划,比如你创建了多个索引,MySQL 会依据成本最小原则来选择使用对应的索引,这里的成本主要包括两个方面, IO 成本和 CPU 成本。MySQL 优化器 会计算 「IO 成本 + CPU」 成本最小的那个索引来执行
存储引擎:查询优化器会调用存储引擎的接口,去执行 SQL,也就是说真正执行 SQL 的动作是在存储引擎中完成的。数据是被存放在内存或者是磁盘中的(存储引擎是一个非常重要的组件,后面会详细介绍)
执行器:执行器是一个非常重要的组件,因为前面那些组件的操作最终必须通过执行器去调用存储引擎接口才能被执行。执行器最终最根据一系列的执行计划去调用存储引擎的接口去完成 SQL 的执行

存储引擎

当我们系统发出这样的查询去交给 MySQL 的时候,MySQL 会按照我们上面介绍的一系列的流程最终通过执行器调用存储引擎去执行,流程图就是上面那个。
在执行这个 SQL 的时候 SQL 语句对应的数据要么是在内存中,要么是在磁盘中,如果直接在磁盘中操作,那这样的随机IO读写的速度肯定让人无法接受的,所以每次在执行 SQL 的时候都会将其数据加载到内存中,这块内存就是 InnoDB 中一个非常重要的组件:缓冲池 Buffer Pool

关于Buffer Pool、Redo Log Buffer 和undo log、redo log、bin log 概念以及关系:
Buffer Pool 是 MySQL 的一个非常重要的组件,因为针对数据库的增删改操作都是在 Buffer Pool 中完成的
Undo log 记录的是数据操作前的样子
redo log 记录的是数据被操作后的样子(redo log 是 Innodb 存储引擎特有)
bin log 记录的是整个操作记录(这个对于主从复制具有非常重要的意义)
、、,,
,。

从准备更新一条数据到事务的提交的流程描述

  • 首先执行器根据 MySQL 的执行计划来查询数据,先是从缓存池中查询数据,如果没有就会去数据库中查询,如果查询到了就将其放到缓存池中
  • 在数据被缓存到缓存池的同时,会写入 undo log 日志文件更
  • 新的动作是在 BufferPool 中完成的,同时会将更新后的数据添加到 redo log buffer 中
  • 完成以后就可以提交事务,在提交的同时会做以下三件事
    • 将redo log buffer中的数据刷入到 redo log 文件中
    • 将本次操作记录写入到 bin log文件中
    • 将 bin log 文件名字和更新内容在 bin log 中的位置记录到redo log中,同时在 redo log 最后添加 commit 标记
  • 至此表示整个更新事务已经完成

关系型数据库是如何工作的

SQL DB 组成

核心组件

进程管理器(process manager): 很多数据库具备一个需要妥善管理的进程/线程池。再者,为了实现纳秒级操作,一些现代数据库使用自己的线程而不是操作系统线程
网络管理器(network manager): 网路 I/O 是个大问题,尤其是对于分布式数据库。所以一些数据库具备自己的网络管理器
文件系统管理器(File system manager): 磁盘 I/O 是数据库的首要瓶颈。具备一个文件系统管理器来完美地处理OS文件系统甚至取代OS文件系统,是非常重要的。
内存管理器(memory manager): 为了避免磁盘 I/O 带来的性能损失,需要大量的内存。但是如果你要处理大容量内存你需要高效的内存管理器,尤其是你有很多查询同时使用内存的时候。
安全管理器 (Security Manager) : 用于对用户的验证和授权
客户端管理器 (Client manager) : 用于管理客户端连接。

工具

备份管理器 (Backup manager) : 用于保存和恢复数据
恢复管理器 (Recovery manager) : 用于崩溃后重启数据库到一个一致状态。
监控管理器 (Monitor manager) : 用于记录数据库活动信息和提供监控数据库的工具。
管理员管理器(Administration manager) : 用于保存元数据(比如表的名称和结构),提供管理数据库、模式、表空的工具

查询管理器

查询解析器 (Query parser) : 用于检查查询是否合法
查询重写器 (Query rewriter) : 用于预优化查询
查询优化器 (Query optimizer) : 用于优化查询
查询执行器 (Query executor) : 用于编译和执行查询

数据管理器

事务管理器 (Transaction manager) : 用于处理事务
缓存管理器 (Cache manager): 数据被使用之前置于内存,或者数据写入磁盘之前置于内存
数据访问管理器 (Data access manager) : 访问磁盘中的数据

数据查询的流程

  • 客户端管理器: 客户端管理器是处理客户端通信的。客户端可以是一个(网站)服务器或者一个最终用户或最终应用。客户端管理器通过一系列知名的API(JDBC, ODBC, OLE-DB …)提供不同的方式来访问数据库。客户端管理器也提供专有的数据库访问API。
    JDBC 是 Java 编程语言中用于连接和操作数据库的标准API。可以理解为 JDBC 是 Java 与数据库之间的桥梁,允许 Java 应用程序与 MySQL 数据库进行通信和交互。MyBatis 是一个开源的Java持久层框架,用于将对象与关系数据库的表之间进行映射,使用 MyBatis 来操作 MySQL 数据库。)
  • 查询管理器:查询首先被解析并判断是否合法、然后被重写,去除了无用的操作并且加入预优化部分、接着被优化以便提升性能,并被转换为可执行代码和数据访问计划、然后计划被编译、最后,被执行
  • 数据管理器:在这一步,查询管理器执行了查询,需要从表和索引获取数据,于是向数据管理器提出请求。
  • 客户端管理器

管理用户权限

1
2
3
4
5
6
-- 创建用户
CREATE USER 'newuser'@'localhost' IDENTIFIED BY 'password';
-- 授予用户权限
GRANT ALL PRIVILEGES ON mydatabase.* TO 'newuser'@'localhost';
-- 刷新权限
FLUSH PRIVILEGES;

备份和恢复

1
2
3
4
# 备份数据库
mysqldump -u username -p mydatabase > backup.sql
# 恢复数据库
mysql -u username -p mydatabase < backup.sql

VMware / SSH

虚拟机 (VM)

  • 虚拟机(Virtual Machine)是一种利用软件模拟硬件的技术,使得一台物理计算机可以同时运行多个虚拟的操作系统或应用程序实例。虚拟机技术提供了更好的资源利用率、隔离性和灵活性,常见的虚拟机有硬件虚拟机和软件虚拟机两种。
  • 它包括虚拟的 CPU、内存、硬盘、网络接口等组件。
  • 可以在虚拟机上安装操作系统和应用程序,就像在物理计算机上一样。
  • 虚拟机和容器的区别:虚拟机模拟整个操作系统,资源占用相对较多;容器共享主机的操作系统内核,更轻量级,启动速度快。

VMware Workstation

  • VMware Workstation 是一款用于虚拟化的桌面软件,它允许用户在单个物理计算机上运行多个虚拟操作系统。
  • 创建虚拟机:启动 VMware Workstation,
    1、创建新虚拟机,选择ISO文件,填写虚拟机的名称以及将来保存的位置;
    2、虚拟机配置:按照向导的指示配置虚拟机,包括选择操作系统、分配内存、创建虚拟硬盘等;安装操作系统:
    3、启动虚拟机,按照正常流程安装选定的操作系统。
  • 虚拟机克隆: VMware Workstation 允许你克隆虚拟机,以便快速创建相似的虚拟机。
  • 虚拟机导入:(除了创建虚拟机外) 你可以导入其他虚拟化平台的虚拟机。导入虚拟机时,通常使用的是虚拟机的配置文件和虚拟硬盘文件。(存放于 C:\Users\蔡枫\Documents\Virtual MachinesD:\Virtual Machines
    • 虚拟机配置文件: 这个文件包含了虚拟机的设置、硬件配置、网络配置等信息。在 VMware 中,这个文件通常有一个扩展名为 .vmx。当你导入虚拟机时,你需要选择这个配置文件。
    • 虚拟硬盘文件: 这个文件包含了虚拟机的硬盘数据,即操作系统和应用程序的安装等信息。在 VMware 中,虚拟硬盘文件的格式通常是 .vmdk。你也需要选择这个文件来导入虚拟机。
  • 在VMware界面中操作虚拟机非常不友好,一般推荐使用专门的SSH客户端
    市面上常见有:Xshell,Finshell,MobarXterm

CentOS

  • 一个基于Red Hat Enterprise Linux(RHEL)源代码构建的开源操作系统。默认账号:root,,账号2:leo,密码:
  • 虚拟机目录:D:\VMCentOS-7,(网络适配器)名称:ens33;默认路由(网关):192.168.111.2,DNS:192.168.111.2
    1
    2
    3
    4
    5
    6
    7
    8
    9
    [leo@localhost ~]$ ifconfig
    ens33: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
    inet 192.168.111.128 netmask 255.255.255.0 broadcast 192.168.111.255 # IPv4 子网掩码 广播地址
    inet6 fe80::2304:6b2e:1716:b51 prefixlen 64 scopeid 0x20<link> # IPv6地址
    ether 00:0c:29:1a:cb:1a txqueuelen 1000 (Ethernet) # MAC 地址
    RX packets 1704 bytes 1628741 (1.5 MiB)
    RX errors 0 dropped 0 overruns 0 frame 0
    TX packets 850 bytes 71942 (70.2 KiB)
    TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
    设置 ipv4 method为手动(网卡地址改为静态IP,这样可以避免每次启动虚拟机IP都变化)

Kali Linux

Kali Linux(简称Kali)是一个基于Debian Linux的专业渗透测试和网络安全评估发行版。它提供了大量的安全工具,方便安全专业人员进行渗透测试、网络扫描、恢复密码等操作。默认账号:kali,默认密码:kali
专业工具集:预装了大量的渗透测试和安全评估工具,包括 Metasploit、Wireshark、Nmap、Aircrack-ng 等。


SSH

  1. SSH(Secure Shell)是一种用于在网络上进行加密通信的协议。它通常用于通过不安全的网络(例如互联网)安全地访问远程计算机上的终端。SSH 协议的主要目的是提供加密和身份验证,确保在客户端和服务器之间传输的数据是安全的。
  2. SSH 协议有两个主要组件:SSH客户端:用于发起远程连接的计算机上的程序。通过 SSH 客户端,用户可以连接到远程计算机并执行命令; SSH服务器:运行在远程计算机上的程序,接受来自客户端的连接并提供终端访问或执行其他操作。
  3. SSH 还可以用于文件传输和端口转发等用途,使其成为管理和维护远程服务器的强大工具。在日常工作中,开发人员、系统管理员和网络管理员经常使用 SSH 来远程管理和维护服务器。

SSH命令行工具

Windows命令提示符和PowerShell都支持SSH命令。你可以使用ssh命令来连接到远程服务器

1
2
ssh username@hostname  # 用户名 @ 公网IP
ssh -i 密钥文件 username@hostname # 密钥登录

SSH客户端:MobarXterm

如果远程服务器上的操作系统的界面使用起来不方便,可以使用ssh客户端连接并操控远程服务器,包括本机上的虚拟机;
在VMware界面中操作虚拟机非常不友好,一般推荐使用专门的SSH客户端,常见有 Xshell,Finshell,MobarXterm

连接虚拟机

1、打开MobarXterm,点击session按钮,进入会话管理:
2、在弹出的session管理页面中,填写信息:Remote Host(虚拟机的ipv4地址),Specify name(root)
3、输入密码:(与虚拟机的一样?)cx0xx1xx,连接本机上 VMware中的 CentOS7系统的虚拟机
4、连接(虚拟机)成功后,进入操作界面了,通过 MobarXterm界面 对 VMware中的虚拟机 进行操作。

使用 MobarXterm 操作 Docker

1、启动虚拟机,使用 MobarXterm 连接虚拟机成功后,进入操作界面。在 MobarXterm界面上 对 VMware中的虚拟机 进行操作。
2、安装配置 Docker

1
2
3
4
5
6
7
8
9
10
# 首先要安装一个yum工具,并配置Docker的yum源
yum install -y yum-utils
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# 安装Docker
yum install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
systemctl start docker # 启动Docker
systemctl stop docker # 停止Docker
systemctl restart docker # 重启
systemctl enable docker # 设置开机自启
docker ps # 执行docker ps命令,如果不报错,说明安装启动成功

3、配置阿里云镜像加速,重启docker

Linux

Linux 是一种免费、开源的类Unix操作系统内核。它最初由芬兰的Linus Torvalds在1991年创建,并迅速发展成为一个庞大且活跃的开源社区项目。Linux内核是操作系统的核心部分,但通常与 GNU 工具和其他软件一起使用,形成完整的(GNU/Linux)操作系统,通常被称为 Linux发行版(如 Ubuntu、Red Hat、CentOS)

GNU

GNU 计划,译为革奴计划,它的目标是创建一套完全自由的操作系统,称为 GNU,其内容软件完全以 GPL 方式发布。其中 GPL 全称为 GNU 通用公共许可协议,包含了以下内容:
1、以任何目的运行此程序的自由;2、再复制的自由;3、改进此程序,并公开发布改进的自由。

发行版

Linux 发行版是 Linux 内核及各种应用软件的集成版本。

基于的包管理工具 商业发行版 社区发行版
RPM Red Hat Fedora / CentOS
DPKG Ubuntu Debian

Bash

是一种Unix和Linux操作系统中常用的命令行解释器和脚本语言。它是许多Linux发行版和Unix系统默认的命令行shell,使用户能够以文本方式与系统进行交互,用于与操作系统进行交互、管理文件、执行命令和编写脚本。
命令行shell是一种计算机程序,它允许用户通过文本界面与计算机操作系统进行交互,以执行各种命令和操作。用户可以在命令行shell中输入文本命令,然后系统会解释和执行这些命令。这种方式通常与图形用户界面 GUI相对。
Bash支持许多功能,包括:

  • 执行命令和程序:Bash允许用户执行各种命令和程序,可以通过命令行或脚本文件来执行。
  • 变量和环境设置:Bash支持变量,用户可以定义和使用环境变量,这些变量可用于存储数据和配置。
  • 文件操作:用户可以使用Bash执行文件和目录的操作,如创建、复制、移动、删除文件等。
  • 管道和重定向:Bash允许将命令的输出连接到其他命令的输入,还支持文件重定向,使用户能够将命令的输出保存到文件或从文件中读取输入。
  • 条件语句和循环:Bash支持条件语句(如if语句)和循环结构(如for循环和while循环),以实现更复杂的控制流程。
  • 脚本编写:用户可以编写Bash脚本,这是一系列命令和操作的集合,用于执行自动化任务、自定义系统配置和处理数据等。

而在 Windows 系统上,PowerShell 是一个更现代、功能更强大的命令行工具,特别适用于系统管理和自动化任务。尽管命令行程序cmd 仍然存在并且仍然可以使用,但PowerShell已成为Windows系统管理和开发的首选工具。

WSL

WSL(Windows Subsystem for Linux)允许用户直接在 Windows 上运行Linux 环境(包括大多数命令行工具、实用程序和应用程序),无需修改,无需单独的虚拟机或双重启动。与完整虚拟机相比,WSL 需要的资源(CPU、内存和存储)更少。
WSL是 Windows10/11 自带的一个功能,默认是关闭的,旨在为希望同时使用 Windows 和 Linux 的用户提供无缝且高效的体验。能够在 Bash shell 中运行Linux,并选择您的发行版(Ubuntu、Debian、OpenSUSE、Kali、Alpine 等)。

  • 开启 Hype-V:ctrl+r - control - 程序 - 开启Windows功能 - 勾选Hype-V
  • 启动 msl:以管理员打开powershell输入下列命令
    1
    dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
  • 启用虚拟化
    1
    dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
  • 更新wsl版本;或手动下载最新版本的 Linux 内核更新包,运行(双击运行 - 系统将提示您提供提升的权限,选择“是”以安装)
    1
    wsl --update
  • 将 WSL 2 设置为默认版本
    1
    wsl --set-default-version 2
  • 安装Linux发行版,创建账号用户密码,Installation successful
    1
    wsl --install -d Ubuntu
  • 至此,已成功安装并设置了一个与 Windows 操作系统完全集成的 Linux 发行版!下次,您可以以管理员身份打开 PowerShell 或 cmd 命令控制台,输入 wsl 命令即可打开安装好的Linux发行版(如Ubuntu)了。
    注意:在wsl中,本地磁盘都位于 /mnt 目录。比如 c 盘位于 /mnt/c 。要进入 d 盘,执行命令 cd /mnt/d
  • PowerShell 进出 wsl:
    1
    2
    3
    4
    PS C:\Windows\system32> wsl
    root@caifeng7138:/mnt/c/Windows/system32# exit
    logout
    PS C:\Windows\system32>

Linux 命令

Linux 操作系统拥有丰富的命令行工具。可以使用--help获取指令的基本用法与选项介绍;在终端中使用man命令查看每个命令的手册页以获取更多信息,例如 man lsdoc:/usr/share/doc 存放着软件的一整套说明文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# **管理员:** 允许一般用户使用 root 可执行的命令,不过只有在 /etc/sudoers 配置文件中添加的用户才能使用该指令。
sudo + 命令
which # 指令搜索。-a : 将所有指令列出,而不是只列第一个
# **文件和目录管理:**
ls # 列出目录中的文件和子目录,是 list 的缩写(dir 命令通常不是默认可用的)
cd # 切换当前工作目录。
pwd # 显示当前工作目录的路径。
mkdir # 创建新目录。
rmdir / rm -r # 删除目录。
touch # 创建新文件或更新文件的访问时间戳。
vim + 文件名 # 编辑文件
cp # 复制文件或目录。
mv # 移动文件或目录,也可用于重命名文件。
rm # 删除文件。
cat # 查看文件内容。
tac # 是 cat 的反向操作,从最后一行开始打印。
more # 和 cat 不同的是它可以一页一页查看文件内容,比较适合大文件的查看。
head # 取得文件前几行,-n指定n行
tail # 是 head 的反向操作,只是取得是后几行。
od # 以字符或者十六进制的形式显示二进制文件。
whereis # 文件搜索。速度比较快,因为它只搜索几个特定的目录。
locate # 文件搜索。可以用关键字或者正则表达式进行搜索
find # 文件搜索。可以使用文件的属性和权限进行搜索。example: find . -name "shadow*"
## **压缩,打包:**
gzipg # zip 是 Linux 使用最广的压缩指令,可以解开 compress、zip 与 gzip 所压缩的文件。
tar # 打包。tar 不仅可以用于打包,也可以使用 gip、bzip2、xz 将打包文件进行压缩。
## **系统管理:**
ps # 查看运行中的进程。
top # 实时显示进程信息。示例: 两秒钟刷新一次 ## top -d 2
netstat # 查看占用端口的进程
kill # 终止进程。
shutdown / reboot # 关机或重启系统。
uname # 显示系统信息。
date # 显示或设置系统时间。
useradd / userdel # 添加或删除用户。
passwd # 更改用户密码。
## **包管理:** RPM 和 DPKG 为最常见的两类软件包管理工具;YUM 基于 RPM,具有依赖管理功能,并具有软件升级的功能
apt-get # 安装、更新和删除软件包。(Debian/Ubuntu)
yum # 安装、更新和删除软件包。(Red Hat/CentOS)
snap install # 下载最新
dpkg # 直接管理软件包。(Debian/Ubuntu)
rpm # 直接管理软件包。(Red Hat/CentOS)
pip # Python包管理器。
## **网络工具:**
ping # 测试网络连接。
curl / wget # 下载文件或内容。
ssh # 远程登录到其他计算机。
scp # 安全拷贝文件到远程主机。
ifconfig # 显示网络接口的配置信息,包括IP地址、子网掩码等。(Windows系统上是 ipconfig)

二、磁盘

Linux 中每个硬件都被当做一个文件,包括磁盘。磁盘以磁盘接口类型进行命名,常见磁盘的文件名如下:

  • IDE 磁盘: /dev/hd[a-d]
  • SATA/SCSI/SAS 磁盘: /dev/sd[a-p]
    其中文件名后面的序号的确定与系统检测到磁盘的顺序有关,而与磁盘所插入的插槽位置无关。

三、分区

磁盘分区表主要有两种格式,一种是限制较多的 MBR 分区表,一种是较新且限制较少的 GPT 分区表。

四、文件系统

Linux 文件系统是一种层次化的组织结构,用于存储和管理文件。这个文件系统通常由一个根目录和一系列子目录、文件以及链接组成。
Linux 系统是以文件目录系统为根基的,一切东西都是文件,包括硬件、进程、命令、系统设置等等。

  1. 树状结构: Linux文件系统采用树状结构,以根目录(/)作为顶层目录。树状结构允许文件和目录以层次结构进行组织,方便用户和应用程序定位和访问文件。(Windows 中的目录斜杠与之相反,是右斜杠 \)
  2. 根目录(/): 根目录是文件系统的最顶层目录,包含整个文件系统的目录和文件。所有的文件和目录都位于根目录下或其子目录中。
  3. 目录(文件夹): 目录是用于组织和存储文件的容器。它类似于Windows中的文件夹。目录中可以包含文件和其他子目录。
  4. 文件: 文件是数据的容器。在Linux中,一切都是文件,包括文本文件、二进制文件、设备文件等。
  5. 路径: 路径是指定文件或目录位置的方式。绝对路径从根目录开始,而相对路径从当前工作目录开始。例如,/home/user/documents 是一个绝对路径,表示文档目录在用户主目录下。

十、进程管理


Vim

Vim(Vi Improved)是一款强大的文本编辑器,广泛用于Linux和Unix系统。它是Vi编辑器的改进版本,提供了许多额外的功能和改进。

启动和退出

打开文件:进入正常模式

1
vim filename

退出Vim:

  • 进入命令模式(按 Esc 键),然后输入 :q,按 Enter;按 u 来撤销上一步操作。
  • 如果有未保存的更改,使用 :q! 来强制退出,或者 :wq 保存并退出。

模式驱动

  1. 普通模式:在这个模式下,键盘输入被解释为命令。你可以使用这些命令来移动光标、删除文本、复制粘贴等。
  2. 插入模式:在这个模式下,你可以输入文本。按下 “i” 进入插入模式,按 “Esc” 退出插入模式并返回到普通模式。
  3. 可视模式:这模式下,你可以选择文本块以进行复制、剪切或其他操作。按下 “v” 进入可视模式,然后使用移动命令来选择文本。
  4. 命令行模式:在这个模式下,你可以输入各种命令,例如保存文件、退出 Vim、搜索等。按下 “:” 进入命令行模式。
  5. 选择模式*与可视模式有些类似,但是在选择后直接进入插入模式。按下 “v” 进入可视模式,然后按 “Shift” 和 “v” 进入选择模式。

移动光标

  • 移动光标: h:左移 - j:下移 - k:上移 - l:右移

  • 移动到行首或行尾: 0:移动到行首 - $:移动到行尾

搜索和替换

  • 搜索: 在命令模式下,按 /,然后输入要搜索的文本,按 Enter

  • 替换: 在命令模式下,输入 :%s/old/new/g 来将所有匹配的 old 替换为 new

操 作 系 统

硬件结构

1.1 CPU是如何执行程序的

  • 图灵机:基本思想是用机器来模拟人们用纸笔进行数学运算的过程,而且还定义了计算机由哪些部分组成,程序又是如何执行的。

  • 冯诺依曼模型
    1945 年冯诺依曼和其他计算机科学家们提出了计算机具体实现的报告,其遵循了图灵机的设计,而且还提出用电子元件构造计算机,并约定了用二进制进行计算和存储,还定义计算机基本结构为 5 个部分,分别是中央处理器(CPU)、内存、输入设备、输出设备、总线。

    • 内存:我们的程序和数据都是存储在内存,存储的区域是线性的。数据存储的单位是一个二进制位(bit),即 0 或 1。最小的存储单位是字节(byte),1 字节等于 8 位。内存的地址是从 0 开始编号的,然后自增排列,最后一个地址为内存总字节数-1,这种结构好似我们程序里的数组,所以内存的读写任何一个数据的速度都是一样的。
    • 中央处理器:即 CPU,32 位和 64 位 CPU 最主要区别在于一次能计算多少字节数据:32 位 CPU 一次可以计算 4 个字节;64 位 CPU 一次可以计算 8 个字节;这里的 32 位和 64 位,通常称为 CPU 的位宽。这样设计,是为了能一次计算更大的数值, 8 位的 CPU一次只能计算 1 个字节0~255 范围内的数值,CPU 位宽越大,可以计算的数值就越大。
      CPU 内部还有一些组件,常见的有寄存器、控制单元和逻辑运算单元等。其中,控制单元负责控制 CPU工作,逻辑运算单元负责计算,而寄存器可以分为多种类,常见的寄存器种类:
      • 通用寄存器,用来存放需要进行运算的数据,比如需要进行加和运算的两个数据。
      • 程序计数器,用来存储 CPU 要执行下一条指令「所在的内存地址」,注意不是存储了下一条要执行的指令,此时指令还在内存中,程序计数器只是存储了下一条指令的地址。
      • 指令寄存器,用来存放程序计数器指向的指令,也就是指令本身,指令被执行完成之前,指令都存储在这里。
    • 总线:用于 CPU 和内存以及其他设备之间的通信,总线可分为 3 种:
      • 地址总线,用于指定 CPU 将要操作的内存地址;
      • 数据总线,用于读写内存的数据;
      • 控制总线,用于发送和接收信号,比如中断、设备复位等信号,CPU 收到信号后自然进行响应,这时也需要控制总线;
        当 CPU 要读写内存数据的时候,一般需要通过两个总线:首先要通过「地址总线」来指定内存的地址;再通过「数据总线」来传输数据;
    • 输入、输出设备:输入设备向计算机输入数据,计算机经过计算后,把数据输出给输出设备。期间,如果输入设备是键盘,按下按键时是需要和 CPU 进行交互的,这时就需要用到控制总线了。
  • 线路位宽 与 CPU 位宽
    为了避免低效率的串行传输的方式,线路的位宽最好一次就能访问到所有的内存地址。 CPU 要想操作的内存地址就需要地址总线,如果地址总线只有 1 条,那每次只能表示 「0 或 1」这两种情况,所以 CPU 一次只能操作 2 个内存地址,如果想要 CPU 操作 4G 的内存,那么就需要 32 条地址总线,因为 2 ^ 32 =4G 。
    CPU 的位宽最好不要小于线路位宽,比如 32 位 CPU 控制 40 位宽的地址总线和数据总线的话,工作起来就会非常复杂且麻烦,所以 32 位的 CPU 最好和 32 位宽的线路搭配,因为 32 位 CPU 一次最多只能操作32 位宽的地址总线和数据总线。

  • 程序执行的基本过程
    程序实际上是一条一条指令,所以程序的运行过程就是把每一条指令一步一步的执行起来,负责执行指令的就是 CPU 了。
    第一步,CPU 读取「程序计数器」的值,这个值是指令的内存地址,然后 CPU 的「控制单元」操作「地址总线」指定需要访问的内存地址,接着通知内存设备准备数据,数据准备好后通过「数据总线」将指令数据传给 CPU,CPU 收到内存传来的数据后,将这个指令数据存入到「指令寄存器」。
    第二步,CPU 分析「指令寄存器」中的指令,确定指令的类型和参数,如果是计算类型的指令,就把指令交给「逻辑运算单元」运算;如果是存储类型的指令,则交由「控制单元」执行;
    第三步,CPU 执行完指令后,「程序计数器」的值自增,表示指向下一条指令。这个自增的大小,由CPU 的位宽决定,比如 32 位的 CPU,指令是 4 个字节,需要 4 个内存地址存放,因此「程序计数器」的值会自增 4;

  • a = 1 + 2 执行具体过程(P13
    、、

1.2 存储器金字塔(小林p20)

、、

1.3 如何写出让 CPU 跑得更快的代码?(

,,

1.4 CPU缓存一致性(

、、

1.5 CPU 是如何执行任务的?(p65)

、、

1.6 软中断

。。


操作系统结构

Linux内核 vs Windows内核

,,


内存管理

虚拟内存

,,


进程与线程

,,


调度算法

,,


文件管理

,,


设备管理


网络系统

,,


Linux指令

,,