数据处理杂记
1. pandas
data = pd.read_csv("data/iris-data.csv")
y = data.iloc[0:100, 4].values #取前100行的数据
loc函数:通过行索引 “Index” 中的具体值来取行数据(如取”Index”为”A”的行)
iloc函数:通过行号来取行数据(如取第二行的数据)
.values可以将数据转换为NDARRAY类型
-
利用loc、iloc提取列数据
In[4]:data.loc[:,['A']] #取'A'列所有行,多取几列格式为 data.loc[:,['A','B']] In[7]:data.iloc[[0,1],[0,1]] #提取第0、1行,第0、1列中的数据 In[8]:data.loc[:,:] #取A,B,C,D列的所有行 In[10]: data.loc[data['A']==0] #提取data数据(筛选条件: A列中数字为0所在的行数据) In[11]: data.loc[(data['A']==0)&(data['B']==2)] #提取data数据(多个筛选条件)
2.numpy
- where,类似CC的三元操作
y = np.where(y==’Iris-setosa’, -1, 1)
- np.random.normal()正态分布
numpy.random.normal(loc=0.0, scale=1.0, size=None)
参数的意义为:
loc:float
概率分布的均值,对应着整个分布的中心center
scale:float
概率分布的标准差,对应于分布的宽度,scale越大越矮胖,scale越小,越瘦高
size:int or tuple of ints
输出的shape,默认为None,只输出一个值
我们更经常会用到np.random.randn(size)所谓标准正太分布(μ=0, σ=1),对应于np.random.normal(loc=0, scale=1, size)
- numpy.random.RandomState()函数用法
可以通过numpy工具包生成模拟数据集,使用RandomState获得随机数生成器
np.random.rand()
返回[0,1)之间的数,rand()返回一个数字,rand(1)返回一个一维的一个数字数组,rand(2)返回一个一维的2个数字数组,以此类推。rand(3,4)返回3行4列的二维数组。
numpy.random.randn(d0,d1,…,dn)
用法同np.random.rand()一样,只是服从正态分布。用法同上。
标准正态分布又称为u分布,是以0为均值、以1为标准差的正态分布,记为N(0,1)。
np.random.randint()
numpy.random.randint(low, high=None, size=None, dtype=’l’)
通过low来指定起点,通过high来指定终点,通过size参数来指定数组的维度,通过dtype来确定类型。
返回随机整数,范围区间为[low,high),包含low,不包含high
high没有填写时,默认生成随机数的范围是[0,low)
np.random.random(size=None)
通过size参数来指定维数 ,生成[0,1)之间的浮点数
np.random.RandomState(seed)
rgen = np.random.RandomState(self.radom_state)
self.w_ = rgen.normal(loc=0.0, scale=0.01,size=1+X.shape[1])
保证种子相同每次生成的值相同,可重入性
a = np.unique(A)
对于一维数组或者列表,unique函数去除其中重复的元素,并按元素由大到小返回一个新的无元素重复的元组或者列表
a,s,p = np.unique(A, return_index
=True, return_inverse
=True)
return_index=True
表示返回新列表元素在旧列表中的位置,并以列表形式储存在s中
return_inverse=True
表示返回旧列表元素在新列表中的位置,并以列表形式储存在p中
numpy.meshgrid()——生成网格点坐标矩阵
语法:X,Y = numpy.meshgrid(x1, y1)
输入的x1,y1,就是网格点的横纵坐标列向量(非矩阵)
输出的X,Y,就是坐标矩阵。X-横坐标,Y-纵坐标
xx1,xx2=np.meshgrid(np.arange(0,3,1),np.arange(0,3,1))
print(xx1)
[[0 1 2]
[0 1 2]
[0 1 2]]
print(xx2)
[[0 0 0]
[1 1 1]
[2 2 2]]
numpy.ravel() vs numpy.flatten()
两者所要实现的功能是一致的(将多维数组降位一维),两者的区别在于返回拷贝(copy)还是返回视图(view),numpy.flatten()返回一份拷贝,对拷贝所做的修改不会影响(reflects)原始矩阵,而numpy.ravel()返回的是视图(view,也颇有几分C/C++引用reference的意味),会影响(reflects)原始矩阵。
两者默认均是行序优先
x = np.array([[1, 2], [3, 4]])
x.reval()
array([1, 2, 3, 4])
numpy.random中的Permutation()
Permutation()函数的意思的打乱原来数据中元素的顺序。
输入为整数,返回一个打乱顺序的数组
输入为数组/list,返回顺序打乱的数组/list
与Shuffle()的区别:
Shuffle()在原有数据的基础上操作,打乱元素的顺序,无返回值
Permutation,不是在原有数据的基础上操作,而是返回一个新的打乱顺序的数组
rgen=np.random.RandomState(0)
r = rgen.permutation(2)
print(r)
[1 0]
3.matplotlib.pyplot
scatter散点图
def scatter(x, y, s=None, c=None, marker=None, cmap=None, norm=None, vmin=None, vmax=None, alpha=None, linewidths=None, verts=None, edgecolors=None, hold=None, data=None, **kwargs)
x/y :数据,都是向量,而且必须长度相等
s :标记大小
以平方磅为单位的标记面积,指定为下列形式之一:
数值标量 : 以相同的大小绘制所有标记。
行或列向量 : 使每个标记具有不同的大小。x、y 和 sz 中的相应
元素确定每个标记的位置和面积。sz 的长度必须等于 x 和 y 的长度。
[] : 使用 36 平方磅的默认面积。
c:标记颜色
RGB 三元数或颜色名称 - 使用相同的颜色绘制所有标记。
由 RGB 三元数组成的三列矩阵 - 对每个标记使用不同的颜色。矩阵的每行为
对应标记指定一种 RGB 三元数颜色。行数必须等于 x 和 y 的长度。
向量 - 对每个标记使用不同的颜色,并以线性方式将 c 中的值映射到当前颜色
图中的颜色。c 的长度必须等于 x 和 y 的长度。要更改坐标区的颜色图,请使用 colormap 函数。
'red' 或 'r' 红色 [1 0 0]
'green' 或 'g' 绿色 [0 1 0]
'blue' 或 'b' 蓝色 [0 0 1]
'yellow' 或 'y' 黄色 [1 1 0]
'magenta' 或 'm' 品红色 [1 0 1]
'cyan' 或 'c' 青蓝色 [0 1 1]
'white' 或 'w' 白色 [1 1 1]
'black' 或 'k' 黑色 [0 0 0] marker:标记样式
'o' 圆圈
'+' 加号
'*' 星号
'.' 点
'x' 叉号
'square' 或 's' 方形
'diamond' 或 'd' 菱形
'^' 上三角
'v' 下三角
'>' 右三角
'<' 左三角
'pentagram' 或 'p' 五角星(五角形)
'hexagram' 或 'h' 六角星(六角形)
'none' 无标记
edgecolors:轮廓颜色,和c类似,参数也相同
alpha:透明度, [0,1]:1不透明,0透明
设置 cmap 的几种方式:
plt.imshow(image, cmap=plt.get_cmap('gray_r'))
plt.imshow(image, cmap='gray_r')
plt.imshow(image, cmap=plt.cm.binary)
from matplotlib.colors import ListedColormap
colors = ('lightgreen', 'cyan', 'gray', 'r', 'b')
cmp = ListedColormap(colors[:np.unique(y_train)])
matplotlib.pyplot里contour与contourf的区别
contour和contourf都是画三维等高线图的,不同点在于contourf会对等高线间的区域进行填充,
x:指定 X 轴数据。
y:指定 Y 轴数据。
z:指定 X、Y 坐标对应点的高度数据。
colors:指定不同高度的等高线的颜色。
alpha:指定等高线的透明度。
cmap:指定等高线的颜色映射,即自动使用不同的颜色来区分不同的高度区域。
linewidths:指定等高线的宽度。
linestyles:指定等高线的样式。
Matplotlib 之 Legend: 图例
loc,图例所有figure位置
prop,字体参数
fontsize,字体大小 (prop未使用时有效)
markerscale,图例标记与原始标记的相对大小
markerfirst,如果为True,则图例标记位于图例标签的左侧
numpoints,为线条图图例条目创建的标记点数
scatterpoints,为散点图图例条目创建的标记点数
scatteryoffsets,为散点图图例条目创建的标记的垂直偏移量
frameon,控制是否应在图例周围绘制框架
fancybox,控制是否应在构成图例背景的FancyBboxPatch周围启用圆边
shadow,控制是否在图例后面画一个阴影
framealpha,控制图例框架的 Alpha 透明度
edgecolor,Frame edgecolor.
facecolor,Frame facecolor.
ncol,设置图例分为n列展示
borderpad,图例边框的内边距
labelspacing,图例条目之间的垂直间距
handlelength,图例句柄的长度
handleheight,图例句柄的高度
handletextpad,图例句柄和文本之间的间距
borderaxespad,轴与图例边框之间的距离
columnspacing,列间距
title,图例标题
bbox_to_anchor,指定图例在轴的位置
bbox_transform, the transform for the bbox. transAxes if None.
(1)位置,使用loc参数
plt.legend(loc='lower left')
0: ‘best'
1: ‘upper right'
2: ‘upper left'
3: ‘lower left'
4: ‘lower right'
5: ‘right'
6: ‘center left'
7: ‘center right'
8: ‘lower center'
9: ‘upper center'
10: ‘center'
(2)设置图例字体
#设置字体大小
fontsize : int or float or {‘xx-small’, ‘x-small’,
‘small’, ‘medium’, ‘large’, ‘x-large’, ‘xx-large’}
(3)设置图例边框及背景
plt.legend(loc='best',frameon=False) #去掉图例边框
plt.legend(loc='best',edgecolor='blue') #设置图例边框颜色
plt.legend(loc='best',facecolor='blue') #设置图例背景颜色,若无边框,参数无效
(4)设置图例标题
plt.legend(loc='best',title='figure 1 legend') #去掉图例边框
示例:
import matplotlib.pyplot as plt
import numpy as np
x = np.arange(1, 11)
fig = plt.figure(1)
ax1 = plt.subplot(2, 1, 1)
ax2 = plt.subplot(2, 1, 2)
l1, = ax1.plot(x, x*x, 'r') #这里关键哦
l2, = ax2.plot(x, x*x, 'b') #注意
plt.legend([l1, l2], ['first', 'second'], loc = 'upper right')
plt.show()
matplotlib 之 tight_layout
matplotlib.pyplot.tight_layout(pad=1.08, h_pad=None, w_pad=None, rect=None)
设置图像的布局,边缘距离
pad : float
Padding between the figure edge and the edges of
subplots, as a fraction of the font size.
h_pad, w_pad : float, optional
Padding (height/width) between edges of adjacent
subplots, as a fraction of the font size. Defaults to pad.
rect : 元组 (left, bottom, right, top), optional
一个所有子图都包含的矩形区域 (包括标签). 默认为(0, 0, 1, 1).
4.python 模块引用失败问题:No module named ‘__main__
’
ModuleNotFoundError: No module named '__main__.dataProcess'; '__main__' is n
目录结构,当前模块有两个文件,其中一个引用另一个中的类.
解决办法:不使用相对路径。
把name.py的上级路径放到系统path里 把name.py的上级目录作为工程目录打开
原来: 为相当引用
from .dataProcess import PrecentEx
改为: 绝对引用
from processor.dataProcess import PrecentEx
5.python Xx.DLL 找不到,模块加载不正确,但是模块已经安装
增加目录到PATH中,D:\anaconda3\Library\bin
Anaconda+pycharm配置qtdesigner提示could not find or load the Qt platform plugin windows
解决方法:
增加环境变量: QT_QPA_PLATFORM_PLUGIN_PATH
,值为C:\Anaconda2\pkgs\qt-5.6.0-vc9_0\Library\plugins,此处与不适用anaconda环境配置不同,需要找qt目录下的plugins
6.sklearn归一化与标准化
区别:归一化,使值到[0,1]之间,标准化使值以0为中心
xx = np.arange(0,10,1)
print('a1d:', xx.shape)
xx=xx.reshape((-1,1))
print('a2d:',xx.shape)
from sklearn.preprocessing import MinMaxScaler
mms = MinMaxScaler(feature_range=(0,1))
xx_norm = mms.fit_transform(xx)
xx_norm_2 = mms.transform(xx)
from sklearn.preprocessing import StandardScaler
std = StandardScaler()
xx_std = std.fit_transform(xx)
xx_std_2 = std.transform(xx)
plt.plot(xx_norm, label="norm")
plt.plot(xx_std, label="std")
plt.legend(loc="lower right")
plt.show()
7. Python使用combinations实现排列组合
python 的内置模块: combinations方法重点在组合,permutations方法重在排列
from itertools import combinations
indics=tuple(range(10))
pp=combinations(indics, 8)
list(pp)
[(0, 1, 2, 3, 4, 5, 6, 7)
(0, 1, 2, 3, 4, 5, 6, 8)
(0, 1, 2, 3, 4, 5, 6, 9)
(0, 1, 2, 3, 4, 5, 7, 8)
(0, 1, 2, 3, 4, 5, 7, 9)
(0, 1, 2, 3, 4, 5, 8, 9)
(0, 1, 2, 3, 4, 6, 7, 8)
(0, 1, 2, 3, 4, 6, 7, 9)
(0, 1, 2, 3, 4, 6, 8, 9)
(0, 1, 2, 3, 4, 7, 8, 9)
(0, 1, 2, 3, 5, 6, 7, 8)
(0, 1, 2, 3, 5, 6, 7, 9)
(0, 1, 2, 3, 5, 6, 8, 9)
(0, 1, 2, 3, 5, 7, 8, 9)
(0, 1, 2, 3, 6, 7, 8, 9)
(0, 1, 2, 4, 5, 6, 7, 8)
(0, 1, 2, 4, 5, 6, 7, 9)
(0, 1, 2, 4, 5, 6, 8, 9)
(0, 1, 2, 4, 5, 7, 8, 9)
(0, 1, 2, 4, 6, 7, 8, 9)
(0, 1, 2, 5, 6, 7, 8, 9)
(0, 1, 3, 4, 5, 6, 7, 8)
(0, 1, 3, 4, 5, 6, 7, 9)
(0, 1, 3, 4, 5, 6, 8, 9)
(0, 1, 3, 4, 5, 7, 8, 9)
(0, 1, 3, 4, 6, 7, 8, 9)
(0, 1, 3, 5, 6, 7, 8, 9)
(0, 1, 4, 5, 6, 7, 8, 9)
(0, 2, 3, 4, 5, 6, 7, 8)
(0, 2, 3, 4, 5, 6, 7, 9)
(0, 2, 3, 4, 5, 6, 8, 9)
(0, 2, 3, 4, 5, 7, 8, 9)
(0, 2, 3, 4, 6, 7, 8, 9)
(0, 2, 3, 5, 6, 7, 8, 9)
(0, 2, 4, 5, 6, 7, 8, 9)
(0, 3, 4, 5, 6, 7, 8, 9)
(1, 2, 3, 4, 5, 6, 7, 8)
(1, 2, 3, 4, 5, 6, 7, 9)
(1, 2, 3, 4, 5, 6, 8, 9)
(1, 2, 3, 4, 5, 7, 8, 9)
(1, 2, 3, 4, 6, 7, 8, 9)
(1, 2, 3, 5, 6, 7, 8, 9)
(1, 2, 4, 5, 6, 7, 8, 9)
(1, 3, 4, 5, 6, 7, 8, 9)
(2, 3, 4, 5, 6, 7, 8, 9)]
直方图,条形图
直方图
import matplotlib.pyplot as plt
import numpy as np
import matplotlib
# 设置matplotlib正常显示中文和负号
matplotlib.rcParams['font.sans-serif']=['SimHei'] # 用黑体显示中文
matplotlib.rcParams['axes.unicode_minus']=False # 正常显示负号
# 随机生成(10000,)服从正态分布的数据
data = np.random.randn(10000)
"""
绘制直方图
data:必选参数,绘图数据
bins:直方图的长条形数目,可选项,默认为10
normed:是否将得到的直方图向量归一化,可选项,默认为0,代表不归一化,显示频数。normed=1,表示归一化,显示频率。
facecolor:长条形的颜色
edgecolor:长条形边框的颜色
alpha:透明度
"""
plt.hist(data, bins=40, normed=0, facecolor="blue", edgecolor="black", alpha=0.7)
# 显示横轴标签
plt.xlabel("区间")
# 显示纵轴标签
plt.ylabel("频数/频率")
# 显示图标题
plt.title("频数/频率分布直方图")
plt.show()
条形图
xx=range(4)
yy=[12,34,45,23]
plt.bar(x=xx, height=yy)
plt.xticks(xx,xx)
plt.show()
NUMPY 布尔矩阵
如果用于计算的时候,布尔量会被转换成1和0,True转换成1,False转换成0
plt.scatter(X_train_PCA[y_train==j, 0],
X_train_PCA[y_train==j, 1],
c=c, label=j, marker=m)
array([False, True, True, True])
X_train_PCA[y_train==j, 0],只取第0列,对应为Ture的行.
当使用布尔数组b作为下标存取数组x中的元素时,将收集数组x中所有在数组b中对应下标为True的元素。使用布尔数组作为下标获得的数组不和原始数组共享数据空间,注意这种方式只对应于布尔数组,不能使用布尔列表。
如果是布尔列表,则把True当作1, False当作0,按照整数序列方式获取x中的元素
1 >>> x = np.arange(5,0,-1)
2 >>> x
3 array([5, 4, 3, 2, 1])
4 >>> x[np.array([True, False, True, False, False])]
5 >>> # 下标为True的取出来,布尔数组中下标为0,2的元素为True,因此获取x中下标为0,2的元素
6 array([5, 3])
7 >>> x[[True, False, True, False, False]]#Error,这不是我们想要的结果
8 >>> # 如果是布尔列表,则把True当作1, False当作0,按照整数序列方式获取x中的元素
9 array([4, 5, 4, 5, 5])
10 >>> x[np.array([True, False, True, True])]
11 >>> # 布尔数组的长度不够时,不够的部分都当作False
12 array([5, 3, 2])
13 >>> x[np.array([True, False, True, True])] = -1, -2, -3#只修改下标为True的元素
14 >>> # 布尔数组下标也可以用来修改元素
15 >>> x
16 array([-1, 4, -2, -3, 1])
Numpy中stack(),hstack(),vstack()函数的使用方法
stack()与hstack(),vstack()不同,前者堆叠数组是联结(join),而后两者是串联(concatenation),可以体会一下。
numpy.hstack
numpy.hstack(tup)[source] Stack arrays in sequence horizontally (column wise).
>>> a = np.array((1,2,3))
>>> b = np.array((2,3,4))
>>> np.hstack((a,b))
array([1, 2, 3, 2, 3, 4])
>>> a = np.array([[1],[2],[3]])
>>> b = np.array([[2],[3],[4]])
>>> np.hstack((a,b))
array([[1, 2],
[2, 3],
[3, 4]])
主成分分析PCA
PCA(Principal Component Analysis),即主成分分析方法,是一种使用最广泛的数据降维算法。PCA的主要思想是将n维特征映射到k维上,这k维是全新的正交特征也被称为主成分,是在原有n维特征的基础上重新构造出来的k维特征。PCA的工作就是从原始的空间中顺序地找一组相互正交的坐标轴,新的坐标轴的选择与数据本身是密切相关的。其中,第一个新坐标轴选择是原始数据中方差最大的方向,第二个新坐标轴选取是与第一个坐标轴正交的平面中使得方差最大的.
步骤:
- 计算数据矩阵的协方差矩阵(N阶方阵).协方差为正时,说明X和Y是正相关关系;协方差为负时,协方差矩阵和散度矩阵关系密切,散布矩阵(散度矩阵)前乘以系数1/(n-1)就可以得到协方差矩阵。因此它们的特征值和特征向量是一样的:
- 选择特征值最大的K个向量,nxk
- 原数据投影到主成分上,kxn.nxk,得到新数据.
由于得到协方差矩阵的特征值特征向量有两种方法:特征值分解协方差矩阵、奇异值分解协方差矩阵,所以PCA算法有两种实现方法:基于特征值分解协方差矩阵实现PCA算法、基于SVD分解协方差矩阵实现PCA算法。
np.newaxis
np.newaxis分别是在行或列上增加维度
a = np.array([1, 2, 3, 4, 5, 6])#(6,)
b=a[np.newaxis,:]
c=a[:,np.newaxis]#列上增加维度
b=[[1 2 3 4 5 6]]#(1,6)
c=[[1] #(6,1)
[2]
[3]
[4]
[5]
[6]]
协方差矩阵
当协方差为0时,表示两个字段完全独立。为了让协方差为0,我们选择第二个基时只能在与第一个基正交的方向上选择。因此最终选择的两个方向一定是正交的。
线性判别分析 Linear Discriminate Analysis, LDA
思想:给定训练集样例,设法将样例投影到一条直线上,使得同类样例的投影尽可能接近,异类样例的投影点尽可能原理;在对新的样本进行分类时,将其投影到同样的这条直线上,再根据投影点的位置来确定新样本的类别。(下图截自 周志华《机器学习》)
https://blog.csdn.net/weixin_40604987/article/details/79615968
numpy. mean() 函数定义:
numpy.mean(a, axis, dtype, out,keepdims )
mean()函数功能:求取均值 经常操作的参数为axis,以m * n矩阵举例:
axis 不设置值,对 mn 个数求均值,返回一个实数 axis = 0:压缩行,对各列求均值,返回 1 n 矩阵 axis =1 :压缩列,对各行求均值,返回 m *1 矩阵
RBF函数
所谓径向基函数 (Radial Basis Function 简称 RBF), 就是某种沿径向对称的标量函数.最常用的径向基函数是高斯核函数 ,形式为 k(||x-xc||)=exp{- ||x-xc||^2/(2*σ^2) } 其中xc为核函数中心,σ为函数的宽度参数 , 控制了函数的径向作用范围。
计算机视觉中的作用
在计算机视觉中,有时也简称为高斯函数。高斯函数具有五个重要的性质,这些性质使得它在早期图像处理中特别有用.这些性质表明,高斯平滑滤波器无论在空间域还是在频率域都是十分有效的低通滤波器,且在实际图像处理中得到了工程人员的有效使用.高斯函数具有五个十分重要的性质,它们是:
(1)二维高斯函数具有旋转对称性,即滤波器在各个方向上的平滑程度是相同的.一般来说,一幅图像的边缘方向是事先不知道的,因此,在滤波前是无法确定一个方向上比另一方向上需要更多的平滑.旋转对称性意味着高斯平滑滤波器在后续边缘检测中不会偏向任一方向.
(2)高斯函数是单值函数.这表明,高斯滤波器用像素邻域的加权均值来代替该点的像素值,而每一邻域像素点权值是随该点与中心点的距离单调增减的.这一性质是很重要的,因为边缘是一种图像局部特征,如果平滑运算对离算子中心很远的像素点仍然有很大作用,则平滑运算会使图像失真.
(3)高斯函数的傅立叶变换频谱是单瓣的.这一性质是高斯函数傅立叶变换等于高斯函数本身这一事实的直接推论.图像常被不希望的高频信号所污染(噪声和细纹理).而所希望的图像特征(如边缘),既含有低频分量,又含有高频分量.高斯函数傅里叶变换的单瓣意味着平滑图像不会被不需要的高频信号所污染,同时保留了大部分所需信号.
(4)高斯滤波器宽度(决定着平滑程度)是由参数σ表征的,而且σ和平滑程度的关系是非常简单的.σ越大,高斯滤波器的频带就越宽,平滑程度就越好.通过调节平滑程度参数σ,可在图像特征过分模糊(过平滑)与平滑图像中由于噪声和细纹理所引起的过多的不希望突变量(欠平滑)之间取得折中.
(5)由于高斯函数的可分离性,大高斯滤波器可以得以有效地实现.二维高斯函数卷积可以分两步来进行,首先将图像与一维高斯函数进行卷积,然后将卷积结果与方向垂直的相同一维高斯函数卷积.因此,二维高斯滤波的计算量随滤波模板宽度成线性增长而不是成平方增长.
参考:
- my coding.net
- Pandas中loc和iloc函数用法详解
- numpy里random总结
- https://blog.csdn.net/qq_38486203/article/details/80578260
- https://blog.csdn.net/weixin_40683253/article/details/87367437
- ListedColormap
- np.meshgrid
- numpy.ravel() vs numpy.flatten()
- Python可视化库matplotlib.pyplot里contour与contourf的区别
- https://matplotlib.org/examples/pylab_examples/contourf_demo.html
- 用matplotlib画等高线图详解
- matplotlib命令与格式:图例legend语法及设置
- https://www.jianshu.com/p/2e48282b55c5
- https://blog.csdn.net/hohaizx/article/details/79101322
- https://www.cnblogs.com/moon1992/p/4946114.html
- Numpy中stack(),hstack(),vstack()函数的使用方法
- 协方差矩阵和散布矩阵(散度矩阵)的意义
- https://blog.csdn.net/yeqiang19910412/article/details/78259892