daily commit

This commit is contained in:
2022-04-13 15:53:57 +08:00
parent 1763f67e34
commit 9e408f7516
15 changed files with 770 additions and 704 deletions

View File

@@ -207,4 +207,11 @@
* git rebase复杂操作
```shell
# 调用git rebase可以将一个分支基于另一个分支的改变replay到第三分支上
$ git rebase --onto target-branch base-branch topic-branch
$ git rebase --onto target-branch base-branch topic-branch
```
* 相较于mergerebase操作可能会产生一些问题。
* 如一个分支已经被push到远程库且被其他协作者pull并在此基础之上进行开发工作。那么当分支的创建者在本地对分支进行rebase操作后再用git push --force命令对远程分支的history进行覆盖时那么其他协作者对更新之后的远程库进行pull操作merge操作会破坏history
* rebase操作的守则
* 一个rebase操作只能再本地库进行
* 一旦分支被push到远程库就有可能被其他开发者clone并在此基础之上进行修改
* 如果分支被push到远程库后那么就无法再进行rebase操作。因为rebase操作可能会破坏已有的history结构如果用git push --force强制覆盖remote仓库的hsitory结构那么其他开发者再次pull的时候就会进一步破坏history结构

View File

@@ -1,21 +1,21 @@
# HTTPS原理
* ## HTTPS加密步骤
1. ### client hello客户端向服务器):
1. 客户端支持的TLS版本
2. 客户端支持的加密组件Cipher Suites内含支持的加密算法信息
3. 客户端生成的随机数
2. ### server hello服务器端向客户端发送
1. 服务器端的TLS版本
2. 服务器端从客户端支持的加密组件中挑选中的组件Cipher Suite
3. 服务器端生成的随机数
3. ### Certificate服务器向客户端发送
1. 服务器会向客户端发送其被CA签名的证书
4. ### Server Key Exchange服务器端向客户端发送
1. 服务器向客户端发送密钥交换算法的参数Server Params服务器会用自己的私钥对Server Params进行签名防止客户端收到的Server Params被伪造
5. ### Server hello Done服务器端向客户端发送
1. 服务器端向客户端告知协商部分已经结束
### (以上步骤客户端和服务器之间交换数据是明文的)
***
6. ### CLient Key Exchange客户端向服务端发送
1. 在验证服务器证书可信后客户端生成另一个密钥交换算法参数Client Params根据之前服务端发送过来的Server Params结合会生成一个随机数R3。由于之前客户端和服务器端都生成过自己的随机数故而结合3个随机数会生成会话密钥。生成的会话密钥会通过服务器端公钥加密后发送给服务器端
7. ### 之后客户端和服务器端之间的http通信都会通过会话密钥进行对称加密
# HTTPS原理
* ## HTTPS加密步骤
1. ### client hello客户端向服务器):
1. 客户端支持的TLS版本
2. 客户端支持的加密组件Cipher Suites内含支持的加密算法信息
3. 客户端生成的随机数
2. ### server hello服务器端向客户端发送
1. 服务器端的TLS版本
2. 服务器端从客户端支持的加密组件中挑选中的组件Cipher Suite
3. 服务器端生成的随机数
3. ### Certificate服务器向客户端发送
1. 服务器会向客户端发送其被CA签名的证书
4. ### Server Key Exchange服务器端向客户端发送
1. 服务器向客户端发送密钥交换算法的参数Server Params服务器会用自己的私钥对Server Params进行签名防止客户端收到的Server Params被伪造
5. ### Server hello Done服务器端向客户端发送
1. 服务器端向客户端告知协商部分已经结束
### (以上步骤客户端和服务器之间交换数据是明文的)
***
6. ### CLient Key Exchange客户端向服务端发送
1. 在验证服务器证书可信后客户端生成另一个密钥交换算法参数Client Params根据之前服务端发送过来的Server Params结合会生成一个随机数R3。由于之前客户端和服务器端都生成过自己的随机数故而结合3个随机数会生成会话密钥。生成的会话密钥会通过服务器端公钥加密后发送给服务器端
7. ### 之后客户端和服务器端之间的http通信都会通过会话密钥进行对称加密

View File

@@ -21,6 +21,12 @@
* 通过覆盖State对象的build方法返回一个Widget对象来显示StatefulWidget的UI
* 每次对State中维护的状态进行修改时都要调用setState方法并且传入一个函数
* 每次调用setState方法时都会重新调用build方法来对StatefulWidget的UI进行重新渲染
* StatefulWdiget管理状态的三种方式
* widget管理自己的状态
* 此时widget为stateful的并在state中存储状态信息
* 由父widget管理状态
* 此时widget是stateless的而父widget是stateful的当前无状态widget只负责显示信息而父widget则会通过构造函数向stateless widget传递存储在父widget中的状态信息父widget也会传递一个回调用来对存储在父widget中的数据进行修改
* 一部分状态存储在父widget中一部分状态存储在自身的widget中
* ## Flutter中的布局
* Row和Column通常用于创建行和列的布局
* mainAxisAlignment/crossAxisAlignment用于控制主轴和交叉轴如何对其元素
@@ -39,4 +45,57 @@
* ListTile用于显示行信息具有如下属性
* title为行指定title信息
* subtilte为行指定附加信息如果isThreeLine为falsesubtitle不能换行如果为truesubtitle可以为两行
* leading为行定义icon
* leading为行定义icon
* ## 添加图片和资源
* 在flutter程序中通过pubspec.yaml中的flutter:assets来app需要的资源
* 资源变体:
* 当一个资源在pubspec.yaml中被指定时在该资源路径的同级子目录下具有相同名称的资源也会被包含。
```yaml
<!--
例如,当.../background.png文件在yaml文件中被指定时
.../dark/background.png文件也会被包含的app中
-->
flutter:
assets:
- img/background.png
- img/dark/background.png
```
* 加载文本资源:
* 可以通过rootBundle静态全局变量来加载资源
* 在当前构建上下文中通常推荐使用DefaultAssetBundle来获取文本资源相比较于rootBundleDefaultAssetBundle能够在运行时替换AssetBundle
* 在上下文之外可以使用rootBundle来导入文本资源
* 加载图片资源:
* 为了加载图片资源可以通过AssetImage来加载
* 使用依赖包中的图片在通过AssetImage来获取图象时需要指定package包名
* ## Navigation导航机制
* ### 从一个页面中传回数据
* 可以通过Navigator.push方法来将新页面压入该方法是异步方法应该要通过await关键字等待返回结果。
* Navigator.push方法接受如下参数
* contextBuildContext
* MaterialPageRouterbuilder:(context)=>return a widget here...)
* 可以通过Navigator.pop方法回退界面并且返回结果数据
* Navigator.pop方法接受如下参数
* context
* 返回的结果
* ### 从一个页面向另一个页面中传参
* 为了向另一个路由中进行传参可以在MaterialPageRouter中为settings赋值RouteSettings并且向arguments属性中加入参数
* 在另一个页面中可以通过Modal.of(context)方法来获取route对象
* ### 在两个路由之间进行跳转
* 在路由之间进行跳转可以分为如下部分
* 创建两个路由
* 使用Navigator.push来跳转到第二个路由
* 使用Navigator.pop回退到第一个路由
* ### 导航到命名路由中
* 步骤:
* 定义两个界面
* 定义路由
* 通过Navigator.pushNamed方法跳转到命名的路由
* 通过Navigator.pop方法返回
* 在创建命名路由时需要在MaterialApp中指定initialRoute属性和routes属性
* initialRoute属性指定开始时的路由路径
* routes属性指定了所有可用的命名路由以及为每个路由指定构建的Widget
* 通过Navigator.pushNamed方法可以跳转到指定的路由而不用像push方法一样指定builder
* ### 向命名路由传递参数
* 可以通过pushNamed方法中的arguments参数向命名路由传递参数传递的参数可以通过ModalRoute.of方法来获取参数位于settings.arguments属性中
* ### 为组件之间添加关联动画
* 可以通过Hero组件为组件之间添加关联

View File

@@ -1,147 +1,147 @@
# Java文件操作
* ## Path路径
* path路径可分为绝对路径和相对路径绝对路径从根路径开始而相对路径则不是
* Path类的api如下
```java
/*
* 获取路径,其会将参数中的给定字符串连接起来,创建路径
* 如果不是文件系统中的合法路径会抛出InvalidPathException
* Paths.get并不要求文件系统中一定存在对应的文件
*/
Paths.get(String first,String... more)
// 解析路径
// 如果path2是绝对路径则会返回path2
// 否则以path1为基本路径返回path2想对于path1产生的路径
path1.resolve(path2)
path1.resolve(String)
// 将path1父路径作为基本路径解path2的析兄弟路径
path1.resolveSibling(path2)
path1.resolveSibling(String)
// 以path1为基本路径返回path2想对于path1的相对路径
path1.relativize(path2)
// 移除.和..等多余元素
path1.toAbsolutePath()
// 获取文件名称
path1.getFileName()
// 获取根路径
path1.getRoot()
// 获取父路径如果当前文件没有父路径返回null
path1.getParent()
// 以当前路径创建元素
path1.toFile()
# 上述操作都不要求path所对应的文件在文件系统中已经存在
```
* ## 文件读写
* Files类相关文件操作的api
```java
// 读取路径所对应文件的所有字节
Files.readAllBytes(path)
// 按行读入文件的内容,并且将文件的行保存在字符串的集合中
Files.readAllLines(path,charset)
// 向文件中写入数据
// 可以通过StandardOpenOpition来指定数据是append还是覆盖
Files.write(path,bytes,charset,StandardOpenOpition.xxx)
Files.write(path,lines,charset,StandardOpenOption.xxx)
// 通过FIles类获取输入输出流
Files.newInputStream(path,options)
Files.newOutputStream(path,options)
FIles.newBufferedReader(path,charset)
Files.newBufferedWriter(path,charset,options)
```
* ## 创建文件和目录
```java
// 通过Files类创建目录
// 其中createDirectory方法中path路径中的中间目录都应该已
// 存在,而且当想要创建的目录已经存在时会抛出异常
// 而createDirectories方法中中间路径可以不存在在通过path创
// 建目录时会联通中间路径一起创建,但是,此方法在想要创
// 建的目录已经存在时不会抛出异常
Files.createDirectory(path)
Files.createDirectories(path)
# 注意其中createDirecotry(path)操作是原子的
# 但是createDirectories()不是原子的,多进程条件下可能存在竞争
// 通过FIles类创建文件
// 当文件已经存在时,会抛出一场
// 该创建文件的操作是原子的
Files.createFile(path)
// 创建临时文件和临时文件夹
Files.createTempFile(basedir,prefix,suffix)
FIles.createTempFile(prefix,suffix)
Files.createTempDirecotry(basedir,prefix)
FIles.createTempDirectory(prefix)
# 可以为生成的临时文件调用deleteOnExit方法来让临时文件在进程退出时删除
# 在linux环境下临时文件和目录生成在/tmp目录下
```
* ## 移动、复制、删除文件
```java
// 移动、复制文件
Files.copy(from,to)
FIles.move(from,to)
# 对于move和copy操作如果目标路径已经存在那么操作将失败并抛出异常
# 可以指定REPLACE_EXISTING来覆盖已经存在的路径
# 默认情况下移动和复制都不是原子的可以通过ATOMIC_MOVE来指定移动操
# 作是原子的,但是复制操作并不支持原子性操作
# 可以将输入流中的内容复制到path对应的文件中也可以将path对应文件的
# 内容输出到流中
// 删除文件
// 若该文件不存在,会抛出异常
// 删除操作并不是原子的,由于要检查该文件是否是目录
// 如果文件是目录,只有当该目录为空时才能删除成功
Files.delete(path)
```
* ## 获取文件属性
```java
// 文件是否存在
Files.exists()
// 文件是否是隐藏文件爱你
Files.isHidden()
// 对文件是否有读写执行权限
Files.isReadable()
Files.isWritable()
FIles.isExecutable()
// 文件类型是否是目录、普通文件、符号链接
Files.isRegularFIle()
FIles.isDirectory()
FIles.isSymbolicLink()
// 获取文件长度
FIles.size()
```
* ## 访问目录中的项
```java
// 将目录中的项以Stream的形式返回
// Files.list并不会递归的列出子目录中的项
Files.list(dirPath)
// 递归列出子目录的项
// 可以通过指出depth来指定遍历的深度
FIles.walk(rootPath,depth)
# Java文件操作
* ## Path路径
* path路径可分为绝对路径和相对路径绝对路径从根路径开始而相对路径则不是
* Path类的api如下
```java
/*
* 获取路径,其会将参数中的给定字符串连接起来,创建路径
* 如果不是文件系统中的合法路径会抛出InvalidPathException
* Paths.get并不要求文件系统中一定存在对应的文件
*/
Paths.get(String first,String... more)
// 解析路径
// 如果path2是绝对路径则会返回path2
// 否则以path1为基本路径返回path2想对于path1产生的路径
path1.resolve(path2)
path1.resolve(String)
// 将path1父路径作为基本路径解path2的析兄弟路径
path1.resolveSibling(path2)
path1.resolveSibling(String)
// 以path1为基本路径返回path2想对于path1的相对路径
path1.relativize(path2)
// 移除.和..等多余元素
path1.toAbsolutePath()
// 获取文件名称
path1.getFileName()
// 获取根路径
path1.getRoot()
// 获取父路径如果当前文件没有父路径返回null
path1.getParent()
// 以当前路径创建元素
path1.toFile()
# 上述操作都不要求path所对应的文件在文件系统中已经存在
```
* ## 文件读写
* Files类相关文件操作的api
```java
// 读取路径所对应文件的所有字节
Files.readAllBytes(path)
// 按行读入文件的内容,并且将文件的行保存在字符串的集合中
Files.readAllLines(path,charset)
// 向文件中写入数据
// 可以通过StandardOpenOpition来指定数据是append还是覆盖
Files.write(path,bytes,charset,StandardOpenOpition.xxx)
Files.write(path,lines,charset,StandardOpenOption.xxx)
// 通过FIles类获取输入输出流
Files.newInputStream(path,options)
Files.newOutputStream(path,options)
FIles.newBufferedReader(path,charset)
Files.newBufferedWriter(path,charset,options)
```
* ## 创建文件和目录
```java
// 通过Files类创建目录
// 其中createDirectory方法中path路径中的中间目录都应该已
// 存在,而且当想要创建的目录已经存在时会抛出异常
// 而createDirectories方法中中间路径可以不存在在通过path创
// 建目录时会联通中间路径一起创建,但是,此方法在想要创
// 建的目录已经存在时不会抛出异常
Files.createDirectory(path)
Files.createDirectories(path)
# 注意其中createDirecotry(path)操作是原子的
# 但是createDirectories()不是原子的,多进程条件下可能存在竞争
// 通过FIles类创建文件
// 当文件已经存在时,会抛出一场
// 该创建文件的操作是原子的
Files.createFile(path)
// 创建临时文件和临时文件夹
Files.createTempFile(basedir,prefix,suffix)
FIles.createTempFile(prefix,suffix)
Files.createTempDirecotry(basedir,prefix)
FIles.createTempDirectory(prefix)
# 可以为生成的临时文件调用deleteOnExit方法来让临时文件在进程退出时删除
# 在linux环境下临时文件和目录生成在/tmp目录下
```
* ## 移动、复制、删除文件
```java
// 移动、复制文件
Files.copy(from,to)
FIles.move(from,to)
# 对于move和copy操作如果目标路径已经存在那么操作将失败并抛出异常
# 可以指定REPLACE_EXISTING来覆盖已经存在的路径
# 默认情况下移动和复制都不是原子的可以通过ATOMIC_MOVE来指定移动操
# 作是原子的,但是复制操作并不支持原子性操作
# 可以将输入流中的内容复制到path对应的文件中也可以将path对应文件的
# 内容输出到流中
// 删除文件
// 若该文件不存在,会抛出异常
// 删除操作并不是原子的,由于要检查该文件是否是目录
// 如果文件是目录,只有当该目录为空时才能删除成功
Files.delete(path)
```
* ## 获取文件属性
```java
// 文件是否存在
Files.exists()
// 文件是否是隐藏文件爱你
Files.isHidden()
// 对文件是否有读写执行权限
Files.isReadable()
Files.isWritable()
FIles.isExecutable()
// 文件类型是否是目录、普通文件、符号链接
Files.isRegularFIle()
FIles.isDirectory()
FIles.isSymbolicLink()
// 获取文件长度
FIles.size()
```
* ## 访问目录中的项
```java
// 将目录中的项以Stream的形式返回
// Files.list并不会递归的列出子目录中的项
Files.list(dirPath)
// 递归列出子目录的项
// 可以通过指出depth来指定遍历的深度
FIles.walk(rootPath,depth)
```

View File

@@ -1,54 +1,54 @@
# 垃圾回收器
* ## 垃圾回收器分类:
* 串行垃圾回收器:
* 单线程情况下使用
* 吞吐量优先的垃圾回收器:
* 适合在多线程情况下使用
* 其会让单位时间内STW时间之和尽可能变短
* 响应时间优先的垃圾回收器:
* 同样适用于多核cpu情况
* 其会尽可能让单次垃圾回的STW时间变短
* ## 串行垃圾回收器:
* 开启串行垃圾回收器:
```java
// 可以通过指定JVM参数来指定使用串行垃圾回收器
// 其会使用Serial和Serial Old来作为垃圾回收器
// Serial年轻代垃圾回收器使用复制算法
// Serial Old老年代垃圾回收器使用标记整理算法
-XX+UseSerialGC
```
* 串行垃圾回收器的回收流程:
* 串行垃圾回收器只有一个垃圾回收的线程,当垃圾回收线程进行垃圾回收时,会暂停所有的用户线程,直到垃圾回收完成后用户线程才继续运行
* ## 吞吐量优先的垃圾回收器:
* 开启吞吐量优先的垃圾回收器:
```java
// 指定JVM参数-XX:+UseParallelGC可以开启吞吐量优先的垃圾回收器
// 其是JDK1.8环境下的默认垃圾回收器
// 其会允许多个垃圾回收线程并行的执行垃圾回收工作
-XX+UseParallelGC
```
* ## 响应时间优先的垃圾回收器:
* 响应时间优先的垃圾回收器采用cms垃圾回收其作为老年代垃圾回收器而使用ParNewGC作为新生代垃圾回收器
```java
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC
```
* CMS垃圾回收工作流程
* 初始标记此时需要STW用户线程会暂停
* 并发标记:并发标记时用户线程不需要暂停
* 再标记再标记时用户线程需要暂停会产生STW
* 并发清除此阶段用户线程不会暂停不需要STW
* CMS垃圾回收缺点
* 在CMS进行并发清除时并没有STW其他用户线程在运行时可能会产生垃圾。产生的垃圾在这次垃圾回收时无法被回收会留给下一次垃圾回收时进行清理。留给下一次垃圾回收进行清理的垃圾称之为浮动垃圾。、
* CMS垃圾回收器采用的是标记清除算法在长时间运行后可能会产生内存碎片。在产生内存碎片过多后CMS会退化为Serial Old垃圾回收器并通过标记整理算法来避免内存碎片过多的情况
* ## G1垃圾回收器
* G1垃圾回收器特性
* G1垃圾回收器同时注重了低延时和吞吐量
* G1垃圾回收器适合与超大堆内存的管理
* G1垃圾回收器会将对内存划分为多个大小相等的Region每个Region可以独立作为Eden区、Surivor区或老年代区域
* G1垃圾回收气整体上采用的是标记整理算法但是两个Region之间采用的是复制算法
* G1垃圾回收器的开启操作
```java
// 通过指定JVM参数开启G1垃圾回收器
-XX:+UseG1GC
# 垃圾回收器
* ## 垃圾回收器分类:
* 串行垃圾回收器:
* 单线程情况下使用
* 吞吐量优先的垃圾回收器:
* 适合在多线程情况下使用
* 其会让单位时间内STW时间之和尽可能变短
* 响应时间优先的垃圾回收器:
* 同样适用于多核cpu情况
* 其会尽可能让单次垃圾回的STW时间变短
* ## 串行垃圾回收器:
* 开启串行垃圾回收器:
```java
// 可以通过指定JVM参数来指定使用串行垃圾回收器
// 其会使用Serial和Serial Old来作为垃圾回收器
// Serial年轻代垃圾回收器使用复制算法
// Serial Old老年代垃圾回收器使用标记整理算法
-XX+UseSerialGC
```
* 串行垃圾回收器的回收流程:
* 串行垃圾回收器只有一个垃圾回收的线程,当垃圾回收线程进行垃圾回收时,会暂停所有的用户线程,直到垃圾回收完成后用户线程才继续运行
* ## 吞吐量优先的垃圾回收器:
* 开启吞吐量优先的垃圾回收器:
```java
// 指定JVM参数-XX:+UseParallelGC可以开启吞吐量优先的垃圾回收器
// 其是JDK1.8环境下的默认垃圾回收器
// 其会允许多个垃圾回收线程并行的执行垃圾回收工作
-XX+UseParallelGC
```
* ## 响应时间优先的垃圾回收器:
* 响应时间优先的垃圾回收器采用cms垃圾回收其作为老年代垃圾回收器而使用ParNewGC作为新生代垃圾回收器
```java
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC
```
* CMS垃圾回收工作流程
* 初始标记此时需要STW用户线程会暂停
* 并发标记:并发标记时用户线程不需要暂停
* 再标记再标记时用户线程需要暂停会产生STW
* 并发清除此阶段用户线程不会暂停不需要STW
* CMS垃圾回收缺点
* 在CMS进行并发清除时并没有STW其他用户线程在运行时可能会产生垃圾。产生的垃圾在这次垃圾回收时无法被回收会留给下一次垃圾回收时进行清理。留给下一次垃圾回收进行清理的垃圾称之为浮动垃圾。、
* CMS垃圾回收器采用的是标记清除算法在长时间运行后可能会产生内存碎片。在产生内存碎片过多后CMS会退化为Serial Old垃圾回收器并通过标记整理算法来避免内存碎片过多的情况
* ## G1垃圾回收器
* G1垃圾回收器特性
* G1垃圾回收器同时注重了低延时和吞吐量
* G1垃圾回收器适合与超大堆内存的管理
* G1垃圾回收器会将对内存划分为多个大小相等的Region每个Region可以独立作为Eden区、Surivor区或老年代区域
* G1垃圾回收气整体上采用的是标记整理算法但是两个Region之间采用的是复制算法
* G1垃圾回收器的开启操作
```java
// 通过指定JVM参数开启G1垃圾回收器
-XX:+UseG1GC
```

View File

@@ -1,8 +1,8 @@
# 类加载过程
* ## 类加载过程
* 加载次过程中会根据要加载类的全类名获取该类字节吗文件的二进制流并且将其加载到内存的方法区中。并且为该要加载的类生成一个Class对象可以作为方法区类数据的访问入口
* 链接:链接过程又分为验证、准备、解析三个阶段
* 验证:验证文件格式是否正确,并且加载的字节码数据能否被正确
* 准备:正式为类变量(静态变量)分配内存空间并且设置默认初始值
* 解析:将常量池中的符号引用替换为直接引用的过程
# 类加载过程
* ## 类加载过程
* 加载次过程中会根据要加载类的全类名获取该类字节吗文件的二进制流并且将其加载到内存的方法区中。并且为该要加载的类生成一个Class对象可以作为方法区类数据的访问入口
* 链接:链接过程又分为验证、准备、解析三个阶段
* 验证:验证文件格式是否正确,并且加载的字节码数据能否被正确
* 准备:正式为类变量(静态变量)分配内存空间并且设置默认初始值
* 解析:将常量池中的符号引用替换为直接引用的过程
* 初始化初始化阶段会调用clinit方法调用该方法会为类变量进行赋值操作对于用final修饰的类变量除外其并不是在clinit阶段被赋予最终值而是在准备阶段就赋予了最终值

View File

@@ -1,27 +1,27 @@
# 常用的linux命令
> ## 文件、字符串操作命令
* **awk命令**:
awk是好用的行处理工具其能够将每行的文本当作参数传递给自定义的动作其数据既可以来源于管道的输入也可以来源于指定的文件
* awk命令的格式类似于
```shell
awk '条件1{动作1} 条件2{动作2}...' filename
```
* awk处理行数据时分割字段默认用'空格'或者tab'进行分割
* awk内建变量
1. NR 当前行的行号
2. NF 当前行字段的数量
3. FS 目前的分隔字符,默认是空格
* 若想要在动作中定义FS必须在定义FS的动作之前加上BEGIN关键字表示预先设定FS。否则FS的设定对第一行不会生效因为只有将第一行读入并按默认的FS进行分割后为FS赋值的动作才会执行
```shell
#例如为awk命令定义BEGIN 的FS赋值动作
cat /etc/passwd | head -n 10 | awk 'BEGIN{FS=":";}
{print NR "\t" $1 "\t" $3 ""}'
```
* uniq命令
uniq命令将整合相邻且相同的行为一行通常配合sort使用来消去排序后重复的行若指定-c选项则在字符串前加上一列表示各行字符串的频数
* sort命令
* -r : 反向排序
* -f : 忽略大小写差异
* -n : 使用纯数字来排序
* -b : 忽略前面的空格部分
* -f : 针对特定的域来进行排序
# 常用的linux命令
> ## 文件、字符串操作命令
* **awk命令**:
awk是好用的行处理工具其能够将每行的文本当作参数传递给自定义的动作其数据既可以来源于管道的输入也可以来源于指定的文件
* awk命令的格式类似于
```shell
awk '条件1{动作1} 条件2{动作2}...' filename
```
* awk处理行数据时分割字段默认用'空格'或者tab'进行分割
* awk内建变量
1. NR 当前行的行号
2. NF 当前行字段的数量
3. FS 目前的分隔字符,默认是空格
* 若想要在动作中定义FS必须在定义FS的动作之前加上BEGIN关键字表示预先设定FS。否则FS的设定对第一行不会生效因为只有将第一行读入并按默认的FS进行分割后为FS赋值的动作才会执行
```shell
#例如为awk命令定义BEGIN 的FS赋值动作
cat /etc/passwd | head -n 10 | awk 'BEGIN{FS=":";}
{print NR "\t" $1 "\t" $3 ""}'
```
* uniq命令
uniq命令将整合相邻且相同的行为一行通常配合sort使用来消去排序后重复的行若指定-c选项则在字符串前加上一列表示各行字符串的频数
* sort命令
* -r : 反向排序
* -f : 忽略大小写差异
* -n : 使用纯数字来排序
* -b : 忽略前面的空格部分
* -f : 针对特定的域来进行排序

View File

@@ -1,233 +1,233 @@
# **Shell脚本编程**
>## **shell脚本结构**
* **shell脚本头部:**
在shell脚本中首行通过 #!bin/bash 来指定该shell脚本所使用的语法和执行的shell环境。
```shell
#!/bin/bash
#除了首行之外,其他以'#'开头的行均为注释,在执行时会被忽略
```
* **程序体:**
程序体中包含了该脚本的具体执行逻辑并且在执行结束后通过exit命令来返回该脚本的执行执行结果。
```shell
#用exit命令来返回脚本的执行状态若返回值status为0,代表正常退出
exit status
```
>## **Shell语法**
* **输出shell脚本中定义的变量或是环境变量**
可以通过${VARNAME}的形式来输出变量的值,例如:
```shell
#注意”“和‘’的区别,
# 当使用”“来包围字符串时,字符串中的$(expression)和${varname}
# 都会被替换为变量值和执行结果
# 而当使用‘’来包围字符串时,$(expression)和${varname}并不会被
# 替换为执行结果,而是以字面量的形式输出
echo "PATH VALUE IS : ${PATH}
```
* **${expr}的使用:**
1. 字符串的摘取:
* '#'符号用来去掉左前缀的部分,’#‘作用于于第一个匹配,’##‘作用于最后一个匹配
```shell
#设置路径为/var/log/mysql.log
export VARPATH=/var/log/mysql.log
#通过 '#*/'去掉第一'/'字符之前的子串
#输出结果为var/log/mysql.log
echo ${VARPATH#*/}
#通过'##*/'来去掉最后一个'/'字符之前的子串
#输出结果为mysql.log
echo ${VARPATH##*/}
```
* '%'符号用来去掉右后缀的部分,'%'作用于从右往左的第一条匹配,而'%%'则作用于从右往左的最后一条匹配
```shell
#通过'%/*'可以去除最后一个'/'字符之后的部分
#输出结果为/var/log
echo ${VARPATH%/*}
#通过'%%/*'可以删除第一个’/'之后的部分
#结果为空串
echo ${VARPATH%%/*}
```
* 可以通过'\${\${expr1}expr2}'对前一次的操作结果进行再一次操作,例如
```shell
#设置路径为/etc/init.d/idea.tar.gz
export VARPATH=/etc/init.d/idea.tar.gz
#截取文件扩展名tar.gz
#输出结果为tar.gz
echo ${${VARPATH##*/}#*.}
```
2. **字符串子串的截取和替换**
* \${STR:from:len}可以截取字符串从第from个字符开始长度为len的子串
```shell
export STR=/var/log/mysql.log
#截取第6到第8个字符结果为log
echo ${STR:5:3}
```
* 可用/src/target来将字符串中第一个src子串替换为target用//src/target可以将字符串中所有的src子串替换为target
```shell
export STR='linux is not unix'
echo ${STR/n/N} #输出liNux is not unix,替换第一个n
echo ${STR//n/N} #输出liNux is Not uNix替换所有n
```
* 字符串长度可用${#STR}来获取
```shell
export STR=linux
echo ${#STR} #输出结果为5
```
3. **根据状态为字符串赋值**
* ${VAR-'value'}:如果当前VAR没有设置则返回value'值
* ${VAR:-'value'}:如果当前VAR没有设置或为空串返回'value'值
* ${VAR+'value'}:如果当前VAR没有设置返回空值否则返回'value'值
* ${VAR='value'}:如果当前VAR没有设置将VAR设置为'value',并且返回'value'
* ${VAR:='value'}:如果当前VAR没有设置或者为空串将VAR设置为'value',并且返回'value'
4. **数组**
* 可以通过ele1 ele2 ele3 ...)来定义数组
* 可以通过\${ARR[i]}来访问数组中的元素
* ${ARR[@]}或者${ARR[*]}可以返回数组中所有的元素
* 获取数组中元素的个数可以使用如下表达式
```shell
${#ARR[@]}
#注意,获取数组中元素数量必须用${#ARR[@]}而不是${#ARR}
#在bash中${ARR}只会返回ARR数组的第一个元素
#在bash中${#ARR}也只会返回数组中的第一个字符串的字符数量
```
* 在bash中向数组中添加元素需要用如下方式
```shell
ARR[${#ARR[@]}]=value
```
* **整数运算**
* 在shell脚本中可以使用 var=$((expr)) 来执行浮点数运算和赋值但是在bash中expr仅支持整形的运算并不支持浮点数运算而zsh中expr既可以是整形也可以是浮点型运算
```shell
#在bash中可以通过bc和管道来实现浮点数的运算
#注意在使用bc命令执行浮点数运算时必须指定scale精度否则起默认情况下并不保留小数
echo "scale=3;1.0/3" | bc
```
* **test命令和条件判断**
* test命令可以用于判断表达式为true/false
1. 文件判断:
* -e : 文件是否存在
* -f : 文件是否存在且为普通文件
* -d : 文件是否存在且为目录类型
* -b : 文件是否存在且是块设备
* -c : 文件是否存在且是字符型设备
* -S : 文件爱呢是否存在且为sock
* -p : 文件是否存在且为FIFOpipe
* -L : 文件是否存在且为链接link
2. 权限判断:
* -r : 文件是否存在且对其有读权限
* -w : 文件是否存在且对其有写权限
* -x : 文件是否存在且对其有执行权限
* -s : 文件是否非空白若为空文件返回为false
3. 文件之间的比较:
* f1 -ef f2 : 比较f1和f2两个文件是否是同一个文件
* f1 -ot f2 : 比较f1文件的mtime是否比f2文件老
* f1 -nt f2 : 比较f1的mtime是否比f2新
* test命令用来比较整数浮点数无效
* a -eq b : 比较a和b是否相等
* a -ne b : 比较a和b是否不等
* a -lt b : 比较a是否小于b
* a -gt b : 比较a是否大于b
* a -le b : 比较a是否小于等于b
* a -ge b : 比较a是否大于等于b
* test判断字符串
* -n : 判断字符串是否不为空
* -z : 判断字符串是否为空
* == : 判断字符串是否相等
* != : 判断字符串是否不相等
* 多重条件判断
* -a : and
* -o : or
* ! : not
* 可以用[]来代替test命令例如 test a -eq b 可以被替换为[ a -eq b ],注意[ expr ]中'['和expr之间、expr和']'之间都需要有空格分隔
* 通常在test或者[ expr]表达式中使用\${VARNAME}时,都需要用" "将起扩起来,因为\${VARNME}可能在字符串中包含空格,例如"touma kazua"若是不用”“将其包含那么起将被命令行识别为两个参数会导致too many arguments异常
* **shell传参**
* shell脚本可以在脚本名之后附加参数如 mysum.sh 1 2 ,其中 $0 为 mysum.sh ,$1 为1,$2 为2,以此类推。还有如下特殊参数:
* $# : 除了脚本名称之外的参数个数
* $@ : 除了脚本名之外其他参数组成的数组,各个变量仍然独立
* $* : 除了脚本名之外所有参数聚合成的字符串,默认使用空格分隔
* **Shell条件判断**
* if-then结构
```shell
if [ expr ];then
some operations...
fi
```
* if-else-if结构
```shell
if [ expr1 ];then
some operations...
elif [ expr2 ];then
some operations...
elif [ expr3 ];then
some operations...
else
some operations...
fi
```
* switch-case结构
```shell
case ${var} in
"str1")
some operations...
;;
"str2")
some operations...
;;
"str3")
some operations...
;;
*)
some operations...
;;
esac
```
* Shell函数
* Shell函数可以通过function关键字来创建格式如下
```shell
function FunctionName()
{
Function Body...
#函数中也可以通过$1、$2来获取传递给函数的参数
}
#函数调用
FunctionName arg1 arg2 ...
#函数内通过$1只能读取传递给函数的参数变量而无法读取shell程序中的参数变量
```
* Shell循环
* while循环
```shell
#while循环结构
while [ expr]
do
some operations...
done
```
* until循环
```shell
until [ expr ]
do
some operations...
done
```
* for-in循环
```shell
#可以用seq命令指定首数/尾数/增量以便进行1n的遍历
#seq 尾数
#seq 首数 尾数
#seq 首数 增量 尾数
for var in obj1 obj2 obj3...
do
some operations...
done
```
* Shell语法检测
# **Shell脚本编程**
>## **shell脚本结构**
* **shell脚本头部:**
在shell脚本中首行通过 #!bin/bash 来指定该shell脚本所使用的语法和执行的shell环境。
```shell
#!/bin/bash
#除了首行之外,其他以'#'开头的行均为注释,在执行时会被忽略
```
* **程序体:**
程序体中包含了该脚本的具体执行逻辑并且在执行结束后通过exit命令来返回该脚本的执行执行结果。
```shell
#用exit命令来返回脚本的执行状态若返回值status为0,代表正常退出
exit status
```
>## **Shell语法**
* **输出shell脚本中定义的变量或是环境变量**
可以通过${VARNAME}的形式来输出变量的值,例如:
```shell
#注意”“和‘’的区别,
# 当使用”“来包围字符串时,字符串中的$(expression)和${varname}
# 都会被替换为变量值和执行结果
# 而当使用‘’来包围字符串时,$(expression)和${varname}并不会被
# 替换为执行结果,而是以字面量的形式输出
echo "PATH VALUE IS : ${PATH}
```
* **${expr}的使用:**
1. 字符串的摘取:
* '#'符号用来去掉左前缀的部分,’#‘作用于于第一个匹配,’##‘作用于最后一个匹配
```shell
#设置路径为/var/log/mysql.log
export VARPATH=/var/log/mysql.log
#通过 '#*/'去掉第一'/'字符之前的子串
#输出结果为var/log/mysql.log
echo ${VARPATH#*/}
#通过'##*/'来去掉最后一个'/'字符之前的子串
#输出结果为mysql.log
echo ${VARPATH##*/}
```
* '%'符号用来去掉右后缀的部分,'%'作用于从右往左的第一条匹配,而'%%'则作用于从右往左的最后一条匹配
```shell
#通过'%/*'可以去除最后一个'/'字符之后的部分
#输出结果为/var/log
echo ${VARPATH%/*}
#通过'%%/*'可以删除第一个’/'之后的部分
#结果为空串
echo ${VARPATH%%/*}
```
* 可以通过'\${\${expr1}expr2}'对前一次的操作结果进行再一次操作,例如
```shell
#设置路径为/etc/init.d/idea.tar.gz
export VARPATH=/etc/init.d/idea.tar.gz
#截取文件扩展名tar.gz
#输出结果为tar.gz
echo ${${VARPATH##*/}#*.}
```
2. **字符串子串的截取和替换**
* \${STR:from:len}可以截取字符串从第from个字符开始长度为len的子串
```shell
export STR=/var/log/mysql.log
#截取第6到第8个字符结果为log
echo ${STR:5:3}
```
* 可用/src/target来将字符串中第一个src子串替换为target用//src/target可以将字符串中所有的src子串替换为target
```shell
export STR='linux is not unix'
echo ${STR/n/N} #输出liNux is not unix,替换第一个n
echo ${STR//n/N} #输出liNux is Not uNix替换所有n
```
* 字符串长度可用${#STR}来获取
```shell
export STR=linux
echo ${#STR} #输出结果为5
```
3. **根据状态为字符串赋值**
* ${VAR-'value'}:如果当前VAR没有设置则返回value'值
* ${VAR:-'value'}:如果当前VAR没有设置或为空串返回'value'值
* ${VAR+'value'}:如果当前VAR没有设置返回空值否则返回'value'值
* ${VAR='value'}:如果当前VAR没有设置将VAR设置为'value',并且返回'value'
* ${VAR:='value'}:如果当前VAR没有设置或者为空串将VAR设置为'value',并且返回'value'
4. **数组**
* 可以通过ele1 ele2 ele3 ...)来定义数组
* 可以通过\${ARR[i]}来访问数组中的元素
* ${ARR[@]}或者${ARR[*]}可以返回数组中所有的元素
* 获取数组中元素的个数可以使用如下表达式
```shell
${#ARR[@]}
#注意,获取数组中元素数量必须用${#ARR[@]}而不是${#ARR}
#在bash中${ARR}只会返回ARR数组的第一个元素
#在bash中${#ARR}也只会返回数组中的第一个字符串的字符数量
```
* 在bash中向数组中添加元素需要用如下方式
```shell
ARR[${#ARR[@]}]=value
```
* **整数运算**
* 在shell脚本中可以使用 var=$((expr)) 来执行浮点数运算和赋值但是在bash中expr仅支持整形的运算并不支持浮点数运算而zsh中expr既可以是整形也可以是浮点型运算
```shell
#在bash中可以通过bc和管道来实现浮点数的运算
#注意在使用bc命令执行浮点数运算时必须指定scale精度否则起默认情况下并不保留小数
echo "scale=3;1.0/3" | bc
```
* **test命令和条件判断**
* test命令可以用于判断表达式为true/false
1. 文件判断:
* -e : 文件是否存在
* -f : 文件是否存在且为普通文件
* -d : 文件是否存在且为目录类型
* -b : 文件是否存在且是块设备
* -c : 文件是否存在且是字符型设备
* -S : 文件爱呢是否存在且为sock
* -p : 文件是否存在且为FIFOpipe
* -L : 文件是否存在且为链接link
2. 权限判断:
* -r : 文件是否存在且对其有读权限
* -w : 文件是否存在且对其有写权限
* -x : 文件是否存在且对其有执行权限
* -s : 文件是否非空白若为空文件返回为false
3. 文件之间的比较:
* f1 -ef f2 : 比较f1和f2两个文件是否是同一个文件
* f1 -ot f2 : 比较f1文件的mtime是否比f2文件老
* f1 -nt f2 : 比较f1的mtime是否比f2新
* test命令用来比较整数浮点数无效
* a -eq b : 比较a和b是否相等
* a -ne b : 比较a和b是否不等
* a -lt b : 比较a是否小于b
* a -gt b : 比较a是否大于b
* a -le b : 比较a是否小于等于b
* a -ge b : 比较a是否大于等于b
* test判断字符串
* -n : 判断字符串是否不为空
* -z : 判断字符串是否为空
* == : 判断字符串是否相等
* != : 判断字符串是否不相等
* 多重条件判断
* -a : and
* -o : or
* ! : not
* 可以用[]来代替test命令例如 test a -eq b 可以被替换为[ a -eq b ],注意[ expr ]中'['和expr之间、expr和']'之间都需要有空格分隔
* 通常在test或者[ expr]表达式中使用\${VARNAME}时,都需要用" "将起扩起来,因为\${VARNME}可能在字符串中包含空格,例如"touma kazua"若是不用”“将其包含那么起将被命令行识别为两个参数会导致too many arguments异常
* **shell传参**
* shell脚本可以在脚本名之后附加参数如 mysum.sh 1 2 ,其中 $0 为 mysum.sh ,$1 为1,$2 为2,以此类推。还有如下特殊参数:
* $# : 除了脚本名称之外的参数个数
* $@ : 除了脚本名之外其他参数组成的数组,各个变量仍然独立
* $* : 除了脚本名之外所有参数聚合成的字符串,默认使用空格分隔
* **Shell条件判断**
* if-then结构
```shell
if [ expr ];then
some operations...
fi
```
* if-else-if结构
```shell
if [ expr1 ];then
some operations...
elif [ expr2 ];then
some operations...
elif [ expr3 ];then
some operations...
else
some operations...
fi
```
* switch-case结构
```shell
case ${var} in
"str1")
some operations...
;;
"str2")
some operations...
;;
"str3")
some operations...
;;
*)
some operations...
;;
esac
```
* Shell函数
* Shell函数可以通过function关键字来创建格式如下
```shell
function FunctionName()
{
Function Body...
#函数中也可以通过$1、$2来获取传递给函数的参数
}
#函数调用
FunctionName arg1 arg2 ...
#函数内通过$1只能读取传递给函数的参数变量而无法读取shell程序中的参数变量
```
* Shell循环
* while循环
```shell
#while循环结构
while [ expr]
do
some operations...
done
```
* until循环
```shell
until [ expr ]
do
some operations...
done
```
* for-in循环
```shell
#可以用seq命令指定首数/尾数/增量以便进行1n的遍历
#seq 尾数
#seq 首数 尾数
#seq 首数 增量 尾数
for var in obj1 obj2 obj3...
do
some operations...
done
```
* Shell语法检测
* 可以使用sh -n来检测shell脚本的语法是否正确

View File

@@ -1,61 +1,61 @@
# linux环境下的分区操作
* ## 查看分区信息的命令:
* lsblk : 列出设备上所有的磁盘列表和分区信息
```shell
# 通过lsblk命令可以列出所有的磁盘设备和分区信息
# 通过指定-f选项还可以输出分区的UUID
lsblk -f
```
* parted :输出分区类型和分区表信息
```shell
# 输出分区类型(ext/ntfs)和分区表类型(gpt/mbr)还有其他信息
parted /dev/sda print
```
* ## 对磁盘进行分区
* 对于GPT格式进行分区的硬盘来说可以用gdisk命令对其进行操作
* 在通过gdisk /dev/sda进入gdisk程序后
```shell
# 在控制台上输出帮助信息
?
# 打印整颗磁盘的分区信息
p
# 新增分区
n
# 修改分区表后对内核的分区表信息进行更新
# 在shell中使用partprobe
partprobe
# 删除分区
# 在删除后应该重启或者用partprobe命令更新
d
# 在对分区进行处理时,应该先取消对分区的挂载,否则会出现问题
```
* 对mbr分区类型的磁盘进行分区修改时应该用fdisk命令操作与gdisk类似
* ## 分区后对分区进行格式化
* 在分区后,只有对分好的分区进行格式化后,分区才能被操作系统使用
```shell
# 对于ext4文件系统可以采用mkfs.ext4来进行格式化
# 可以使用-L来指定卷标
mkfs.ext4 /dev/sdaxx
# 对于ext4文件系统如果文件系统出现问题可以尝试使用
# fsck.ext4命令进行修复
fsck.ext4 /dev/sdaxx
# 可以通过tune2fs -L name /dev/sdaxx来修改文件系统的卷标
```
* 分区的挂载和卸载
```shell
# 分区的挂载可以使用mount命令
# 卸载可以使用umount命令
# 挂载时,可以使用-o选项指定挂载参数
# remount可以制定重新挂载
# rw、ro可以指定是以只读模式挂载还是以读写模式挂载
# 要想设置自动挂载,可以将想要挂载的设备添加到/etc/fstab中
# linux环境下的分区操作
* ## 查看分区信息的命令:
* lsblk : 列出设备上所有的磁盘列表和分区信息
```shell
# 通过lsblk命令可以列出所有的磁盘设备和分区信息
# 通过指定-f选项还可以输出分区的UUID
lsblk -f
```
* parted :输出分区类型和分区表信息
```shell
# 输出分区类型(ext/ntfs)和分区表类型(gpt/mbr)还有其他信息
parted /dev/sda print
```
* ## 对磁盘进行分区
* 对于GPT格式进行分区的硬盘来说可以用gdisk命令对其进行操作
* 在通过gdisk /dev/sda进入gdisk程序后
```shell
# 在控制台上输出帮助信息
?
# 打印整颗磁盘的分区信息
p
# 新增分区
n
# 修改分区表后对内核的分区表信息进行更新
# 在shell中使用partprobe
partprobe
# 删除分区
# 在删除后应该重启或者用partprobe命令更新
d
# 在对分区进行处理时,应该先取消对分区的挂载,否则会出现问题
```
* 对mbr分区类型的磁盘进行分区修改时应该用fdisk命令操作与gdisk类似
* ## 分区后对分区进行格式化
* 在分区后,只有对分好的分区进行格式化后,分区才能被操作系统使用
```shell
# 对于ext4文件系统可以采用mkfs.ext4来进行格式化
# 可以使用-L来指定卷标
mkfs.ext4 /dev/sdaxx
# 对于ext4文件系统如果文件系统出现问题可以尝试使用
# fsck.ext4命令进行修复
fsck.ext4 /dev/sdaxx
# 可以通过tune2fs -L name /dev/sdaxx来修改文件系统的卷标
```
* 分区的挂载和卸载
```shell
# 分区的挂载可以使用mount命令
# 卸载可以使用umount命令
# 挂载时,可以使用-o选项指定挂载参数
# remount可以制定重新挂载
# rw、ro可以指定是以只读模式挂载还是以读写模式挂载
# 要想设置自动挂载,可以将想要挂载的设备添加到/etc/fstab中
```

View File

@@ -1,6 +1,6 @@
# binlog 日志
* ## binlog二进制日志
* 定义binlog日志文件也称之为变更日志文件记录了数据库记录的所有DDL、DML等数据库更新事件的语句但是不包括任何没有更新数据的语句。
* 用途:
* 数据恢复如果mysql数据库进程意外停止可以通过binlog中的记录来恢复数据库中的数据
# binlog 日志
* ## binlog二进制日志
* 定义binlog日志文件也称之为变更日志文件记录了数据库记录的所有DDL、DML等数据库更新事件的语句但是不包括任何没有更新数据的语句。
* 用途:
* 数据恢复如果mysql数据库进程意外停止可以通过binlog中的记录来恢复数据库中的数据
* 数据复制可以通过binlog来实现mysql数据库的主从一致

View File

@@ -1,49 +1,49 @@
# 多版本并发控制
* ## 多版本并发控制解决的问题:
* 在多个事务并发访问资源时,可能会发生并发安全问题。为了解决多个事务同时对资进行读写的问题,可以采用如下解决方案:
* 多事务同时对资源进行读取:并发安全
* 多事务同时对资源进行写操作:非并发安全,可以对写操作进行加锁来解决并发安全问题
* 多事务同时对资源进行读和写操作:非并发安全
1. 对读操作和写操作都加锁:性能较差
2. 通过MVCC机制来解决并发读写的问题对写操作加锁并用MVCC机制来使读操作读到先前的值
* ## 多版本并发控的概念:
* MVCC通过数据库行的多个版本管理来实现数据库的并发控制。当一个写事务正在更新数据行的值时通过MVCC机制可以在另一个事物对数据行进行读操作时读取到被写事务更新之前的值。
* 通过MVCC机制可以更好的解决事务在读-写操作同时发生时的性能问题。MVCC可以避免在写事务时另一个读事务必须等待当前写事务释放排他锁而是可以通过MVCC读取资源被写事务修改之前的值。
* ## mysql中读操作的种类
* 当前读:读取数据库中当前的值,为了解决并发安全问题,需要对读操作进行加锁操作,可以尝试加排他锁或者共享锁,当当前事务对资源进行写操作时,读事务会阻塞直到写事务释放锁:
```mysql
# 为select语句加上共享锁
select * from user where... lock in share mode
# 为select语句加上排他锁
select * from user where... for update
```
* 快照读mysql中默认select语句的读取方式当当前事务对资源进行读操作时如果另一个事务正在对资源进行写操作那么读操作并不会阻塞而是会读取资源被写事务修改之前的值
* ## MVCC原理
* 隐藏字段对于使用innodb存储引擎的表其聚簇索引包含两个隐藏字段
1. trx_id每个事务对每条记录进行更改时该trx_id字段都会记载最后一次修改该行记录的事务id
2. roll_pointer指向undo log中关于修改该条数据的记录
* ReadViewReadView是MVCC机制中事务对数据进行快照读时产生的一个读视图。
* ReadView原理
* 当事务开启时会产生当前数据库系统的一个快照innodb为每个事务构造了一个数组用来维护当前系统中活跃事务的ID活跃事务为当前开启了但是尚未提交的事务id
* ReadView中保存了如下信息
* creator_trx_id创建当前ReadView的事务id
* trx_ids记录创建失误时当前mysql系统中活跃的事务id集合(已经开始但是还没有被提交的事务id)
* up_limit_idtrx_ids中最小的事务id
* low_limit_id表示生成ReadView中当前系统应该分配给下一个事务的id
* MVCC仅针对读已提交和可重复读的情况在读已提交的情况下事务中每执行一次select操作ReadView都会重复生成而在可重复读的隔离级别下事务仅仅会在第一次select操作时生成ReadView
* MVCC细节
* 当select语句想要对一条记录中的数据进行读取时首先会查看记录的trx_id是是否对当前事务的读操作是可见的判断事务是否可见的标准如下所示
* 如果记录的trx_id大于low_limit_id那么说明在创建ReadView时对记录进行修改的事务还没有被创建当然修改对当前读事务来说是不可见的
* 如果trx_id小于up_limit_id那么说明对该记录进行修改的事务id小于创建ReadView时最小的活跃事务id在创建ReadView时修改记录的事物已经被提交修改对当前事务来说可见
* 如果trx_id位于up_limit_id和low_limit_id之间那么
* trx_id如果与trx_ids中保存的某个活跃事务id相同那么说明在创建ReadView时修改事务的id尚未被提交修改对当前失误不可见
* 如果trx_id与trx_ids中每个活跃事物id都不相同那么修改事务在创建ReadView事物之时已经被提交修改对当前事务可见
* 根据上述规则如果想要读取的记录trx_id对当前事务来说可见那么获取该事务id所对应的数据值如果trx_id对当前ReadView来说不可见那么沿着roll_pointer沿着undo log向前寻找知道找到对当前ReadView可见的事务id如果undo log中所有的事务id对当前ReadView来说都不可见那么对当前事物来说数据表中该条记录并不可见
* ## MVCC与幻读问题
* MVCC仅在读已提交和可重复读的情况下起作用而关于幻读问题MVCC仅在可重复读的隔离级别下解决。
* 在可重复读的隔离级别下,ReadView仅仅在第一次select语句时生成故而在同一事务中多次读取之间其他事务插入了新数据那么该新数据对应的trx_id在readView创建时应该处于活跃或者未创建的状态故而对ReadView所对应事物来说即使其他事务插入了新数据那么新插入的数据也不可见。
# 多版本并发控制
* ## 多版本并发控制解决的问题:
* 在多个事务并发访问资源时,可能会发生并发安全问题。为了解决多个事务同时对资进行读写的问题,可以采用如下解决方案:
* 多事务同时对资源进行读取:并发安全
* 多事务同时对资源进行写操作:非并发安全,可以对写操作进行加锁来解决并发安全问题
* 多事务同时对资源进行读和写操作:非并发安全
1. 对读操作和写操作都加锁:性能较差
2. 通过MVCC机制来解决并发读写的问题对写操作加锁并用MVCC机制来使读操作读到先前的值
* ## 多版本并发控的概念:
* MVCC通过数据库行的多个版本管理来实现数据库的并发控制。当一个写事务正在更新数据行的值时通过MVCC机制可以在另一个事物对数据行进行读操作时读取到被写事务更新之前的值。
* 通过MVCC机制可以更好的解决事务在读-写操作同时发生时的性能问题。MVCC可以避免在写事务时另一个读事务必须等待当前写事务释放排他锁而是可以通过MVCC读取资源被写事务修改之前的值。
* ## mysql中读操作的种类
* 当前读:读取数据库中当前的值,为了解决并发安全问题,需要对读操作进行加锁操作,可以尝试加排他锁或者共享锁,当当前事务对资源进行写操作时,读事务会阻塞直到写事务释放锁:
```mysql
# 为select语句加上共享锁
select * from user where... lock in share mode
# 为select语句加上排他锁
select * from user where... for update
```
* 快照读mysql中默认select语句的读取方式当当前事务对资源进行读操作时如果另一个事务正在对资源进行写操作那么读操作并不会阻塞而是会读取资源被写事务修改之前的值
* ## MVCC原理
* 隐藏字段对于使用innodb存储引擎的表其聚簇索引包含两个隐藏字段
1. trx_id每个事务对每条记录进行更改时该trx_id字段都会记载最后一次修改该行记录的事务id
2. roll_pointer指向undo log中关于修改该条数据的记录
* ReadViewReadView是MVCC机制中事务对数据进行快照读时产生的一个读视图。
* ReadView原理
* 当事务开启时会产生当前数据库系统的一个快照innodb为每个事务构造了一个数组用来维护当前系统中活跃事务的ID活跃事务为当前开启了但是尚未提交的事务id
* ReadView中保存了如下信息
* creator_trx_id创建当前ReadView的事务id
* trx_ids记录创建失误时当前mysql系统中活跃的事务id集合(已经开始但是还没有被提交的事务id)
* up_limit_idtrx_ids中最小的事务id
* low_limit_id表示生成ReadView中当前系统应该分配给下一个事务的id
* MVCC仅针对读已提交和可重复读的情况在读已提交的情况下事务中每执行一次select操作ReadView都会重复生成而在可重复读的隔离级别下事务仅仅会在第一次select操作时生成ReadView
* MVCC细节
* 当select语句想要对一条记录中的数据进行读取时首先会查看记录的trx_id是是否对当前事务的读操作是可见的判断事务是否可见的标准如下所示
* 如果记录的trx_id大于low_limit_id那么说明在创建ReadView时对记录进行修改的事务还没有被创建当然修改对当前读事务来说是不可见的
* 如果trx_id小于up_limit_id那么说明对该记录进行修改的事务id小于创建ReadView时最小的活跃事务id在创建ReadView时修改记录的事物已经被提交修改对当前事务来说可见
* 如果trx_id位于up_limit_id和low_limit_id之间那么
* trx_id如果与trx_ids中保存的某个活跃事务id相同那么说明在创建ReadView时修改事务的id尚未被提交修改对当前失误不可见
* 如果trx_id与trx_ids中每个活跃事物id都不相同那么修改事务在创建ReadView事物之时已经被提交修改对当前事务可见
* 根据上述规则如果想要读取的记录trx_id对当前事务来说可见那么获取该事务id所对应的数据值如果trx_id对当前ReadView来说不可见那么沿着roll_pointer沿着undo log向前寻找知道找到对当前ReadView可见的事务id如果undo log中所有的事务id对当前ReadView来说都不可见那么对当前事物来说数据表中该条记录并不可见
* ## MVCC与幻读问题
* MVCC仅在读已提交和可重复读的情况下起作用而关于幻读问题MVCC仅在可重复读的隔离级别下解决。
* 在可重复读的隔离级别下,ReadView仅仅在第一次select语句时生成故而在同一事务中多次读取之间其他事务插入了新数据那么该新数据对应的trx_id在readView创建时应该处于活跃或者未创建的状态故而对ReadView所对应事物来说即使其他事务插入了新数据那么新插入的数据也不可见。
* 而在读已提交的隔离级别下ReadView在每次读操作的情况下都会被创建故而在两次读操作之间如果新的数据被插入那么新插入的数据对后一次读操作创建的ReadView来说是已经提交的数据是可见的。故而MVCC在读已提交的隔离级别下并不能够解决幻读的问题。

View File

@@ -1,64 +1,64 @@
# mysql锁
* ## msyql中锁的分类
* 从数据库操作的类型划分mysql中的锁可以分为读锁/共享锁和写锁/排他锁
* 读锁S锁Share
* 写锁X锁Exclusive
* 写锁和读锁示例
```mysql
# 为语句加上写锁
select * from table_name for udpate
# 为查询语句加上读锁
select * from table_name lock in share mode
# or
select * from table_name for share(mysql 8.0新增语法)
```
* 在获取读锁或者写锁之后只有当事务结束之后获取到的读锁或者写锁才会被释放。在innodb中for update或者for share语句只会为查询匹配的行数据上锁并且当事务结束或者回滚之后会释放为数据行所加的排他锁或者共享锁
* 在对mysql中数据进行写操作UPDATE、INSERT、DELETE)时,会通过如下方式加锁:
* DELETE操作对mysql表中数据执行DELETE操作需要为要删除数据添加排他锁X锁
* UPDATE操作对mysql表中数据执行UPDATE操作分如下情况
* update操作不改变记录的键值且更新的列在更新前后占用的空间未发生变化
* 此时会在B+数中定位到该条记录并且为该条记录添加X锁在修改完成之后释放X锁
* update操作没有修改键值但是更新前后该条记录的某一列占用空间发生了变化
* 此时会先对原先的记录加上X锁并进行DELETE操作并且在DELETE操作后通过INSERT操作并添加隐式锁来插入修改后的数据
* update操作修改了键值
* 同样也会通过先DELETE再INSERT的操作来更新数据
* INSERT操作通过添加隐式锁的操作来保证mysql中多事务的并发安全
* 从mysql数据库的粒度来对锁进行划分
* 表锁:
* 表级锁会锁住mysql中的整张表其粒度较大受其影响并发性能较低
* 表级锁是mysql中最基本的加锁策略并不依赖于存储引擎不管是innodb还是myisam都支持表级锁
* 表级锁的使用场景:
* 通过的select或者update、delete、insert操作innodb并不会加表级锁但是对于alter table、drop table这类ddl语句对表的结构进行修改时需要对其表的元数据进行加锁MDL对元数据加锁的过程中想要对该表中数据进行select、delete、update、insert的操作会被阻塞
* 对表锁进行加锁的语句:
```mysql
# 对表锁进行加锁
# 在对表进行上锁操作后,无法再去读取其他未上锁的表
lock tables table_name read/write
# 对上锁的表进行释放操作
unlock tables;
```
* 对于myisam存储引擎读操作之前会为涉及到的表加上读锁并且在读操作执行完成之后释放上锁的表而对于写操作myisam存储引擎会在写操作执行之前为涉及到的表加上写锁。反之innodb存储引擎在对数据进行查找和修改时不会在表级别加锁而是会在更细的粒度上为行数据进行加锁以此来提高程序的并发性能。
* 元数据锁MDL对于表结构的修改DDL操作会对其元数据锁进行加锁而对于该表的增删改操作则是会默认加上元数据的读锁。故而在对表结构进行修改时想要对表中数据进行増删改操作需要获取其元数据的读锁此时对表数据的増删改操作会被阻塞。
* 意向锁意向锁通常用来简化行级锁和表级锁是否兼容的判断操作。如果为一个表中某条数据加上行级锁那么innodb存储引擎会自动的为该行记录所在的页和表加上页级和表级的意向锁那么当接下来有事务想要对该表进行表级加锁操作时就无需查看该表中所有数据来判断是否存在表中数据的锁和表级锁冲突。只需判断该表的意向锁和想要加上的表级锁是否冲突即可。
* 行锁
* 相对与表锁,行锁的粒度更小,故而其并发度更高,并发性能更好。但是行锁可能会造成死锁问题,加锁的开销也更大。
* 是否支持行锁取决与存储引擎比如myisam不支持行锁但是innodb却支持行锁
* 行锁种类:
* 记录锁记录锁针对于单条记录在一个事物中如果存在update、delete等修改操作其默认会获得想要修改的那条记录的记录锁并且在执行修改操作之后才会被释放。若一个事务中对某条记录调用update操作那么直到当前事务提交或者回滚前若其他事务想要修改同一行记录会进入阻塞状态知道持有记录写锁的退出才能继续执行。
* 间隙锁间隙锁对记录的范围进行加锁。间隙锁是不冲突的多个事务可以同时持有某一个范围的间隙锁。用间隙锁或者mvcc机制都能够解决死锁问题。但是间隙锁可能会导致死锁问题如果多个事务各自持有自己范围的间隙锁并同时向对方持有间隙锁的范围插入数据此时两个事务都会等待对方释放间隙锁在这种情况下会发生死锁问题。
* 临键锁next-key lock在锁住某条记录的同时也锁定某个范围内的数据使之无法被其他事务插入数据
* 页锁:在页面的粒度上进行上锁
* 在粒度上不同层级的所的数量是有上限的例如innodb其优先会选用行锁来进行加锁。当行锁数量过多占用空间超过表空间上限时会进行锁升级的行为。行锁会升级为页面锁此时锁的粒度会增加锁花费的开销也会变小但是粒度变大后并发性能也会变低。
* 通过对待锁的方式进行划分
* 乐观锁:乐观锁假设每次对数据进行修改时,其他事务不会访问数据,故而不会对数据正真的加锁,只是会在修改时检查数据是否被其他事务修改过。
* 乐观锁的实现方式:
* cas
* 版本号机制
* 悲观锁:
* 悲观锁假设一个事务在对数据进行修改时其他事务也会对数据进行修改故而在每次修改数据时都会对要修改的数据进行加锁悲观锁是通过mysql内部提供的锁机制来实现的
* 通过加锁方式进行划分:
* 隐式锁隐式锁通常用于插入操作。在某一个事务在对所记录进行插入操作时如果其他事务想要访问该条记录会查看最后修改该条记录的事务id是否是活跃的事务如果是活跃事务那么其会帮助插入事务创建一个X锁并且让自己进入阻塞状态。
* 隐式锁是一种延迟加锁的机制,只有当有其他事务想要访问未提交事务插入的记录时,隐式锁才会被创建。该机制能够有效减少创建锁的数量。
# mysql锁
* ## msyql中锁的分类
* 从数据库操作的类型划分mysql中的锁可以分为读锁/共享锁和写锁/排他锁
* 读锁S锁Share
* 写锁X锁Exclusive
* 写锁和读锁示例
```mysql
# 为语句加上写锁
select * from table_name for udpate
# 为查询语句加上读锁
select * from table_name lock in share mode
# or
select * from table_name for share(mysql 8.0新增语法)
```
* 在获取读锁或者写锁之后只有当事务结束之后获取到的读锁或者写锁才会被释放。在innodb中for update或者for share语句只会为查询匹配的行数据上锁并且当事务结束或者回滚之后会释放为数据行所加的排他锁或者共享锁
* 在对mysql中数据进行写操作UPDATE、INSERT、DELETE)时,会通过如下方式加锁:
* DELETE操作对mysql表中数据执行DELETE操作需要为要删除数据添加排他锁X锁
* UPDATE操作对mysql表中数据执行UPDATE操作分如下情况
* update操作不改变记录的键值且更新的列在更新前后占用的空间未发生变化
* 此时会在B+数中定位到该条记录并且为该条记录添加X锁在修改完成之后释放X锁
* update操作没有修改键值但是更新前后该条记录的某一列占用空间发生了变化
* 此时会先对原先的记录加上X锁并进行DELETE操作并且在DELETE操作后通过INSERT操作并添加隐式锁来插入修改后的数据
* update操作修改了键值
* 同样也会通过先DELETE再INSERT的操作来更新数据
* INSERT操作通过添加隐式锁的操作来保证mysql中多事务的并发安全
* 从mysql数据库的粒度来对锁进行划分
* 表锁:
* 表级锁会锁住mysql中的整张表其粒度较大受其影响并发性能较低
* 表级锁是mysql中最基本的加锁策略并不依赖于存储引擎不管是innodb还是myisam都支持表级锁
* 表级锁的使用场景:
* 通过的select或者update、delete、insert操作innodb并不会加表级锁但是对于alter table、drop table这类ddl语句对表的结构进行修改时需要对其表的元数据进行加锁MDL对元数据加锁的过程中想要对该表中数据进行select、delete、update、insert的操作会被阻塞
* 对表锁进行加锁的语句:
```mysql
# 对表锁进行加锁
# 在对表进行上锁操作后,无法再去读取其他未上锁的表
lock tables table_name read/write
# 对上锁的表进行释放操作
unlock tables;
```
* 对于myisam存储引擎读操作之前会为涉及到的表加上读锁并且在读操作执行完成之后释放上锁的表而对于写操作myisam存储引擎会在写操作执行之前为涉及到的表加上写锁。反之innodb存储引擎在对数据进行查找和修改时不会在表级别加锁而是会在更细的粒度上为行数据进行加锁以此来提高程序的并发性能。
* 元数据锁MDL对于表结构的修改DDL操作会对其元数据锁进行加锁而对于该表的增删改操作则是会默认加上元数据的读锁。故而在对表结构进行修改时想要对表中数据进行増删改操作需要获取其元数据的读锁此时对表数据的増删改操作会被阻塞。
* 意向锁意向锁通常用来简化行级锁和表级锁是否兼容的判断操作。如果为一个表中某条数据加上行级锁那么innodb存储引擎会自动的为该行记录所在的页和表加上页级和表级的意向锁那么当接下来有事务想要对该表进行表级加锁操作时就无需查看该表中所有数据来判断是否存在表中数据的锁和表级锁冲突。只需判断该表的意向锁和想要加上的表级锁是否冲突即可。
* 行锁
* 相对与表锁,行锁的粒度更小,故而其并发度更高,并发性能更好。但是行锁可能会造成死锁问题,加锁的开销也更大。
* 是否支持行锁取决与存储引擎比如myisam不支持行锁但是innodb却支持行锁
* 行锁种类:
* 记录锁记录锁针对于单条记录在一个事物中如果存在update、delete等修改操作其默认会获得想要修改的那条记录的记录锁并且在执行修改操作之后才会被释放。若一个事务中对某条记录调用update操作那么直到当前事务提交或者回滚前若其他事务想要修改同一行记录会进入阻塞状态知道持有记录写锁的退出才能继续执行。
* 间隙锁间隙锁对记录的范围进行加锁。间隙锁是不冲突的多个事务可以同时持有某一个范围的间隙锁。用间隙锁或者mvcc机制都能够解决死锁问题。但是间隙锁可能会导致死锁问题如果多个事务各自持有自己范围的间隙锁并同时向对方持有间隙锁的范围插入数据此时两个事务都会等待对方释放间隙锁在这种情况下会发生死锁问题。
* 临键锁next-key lock在锁住某条记录的同时也锁定某个范围内的数据使之无法被其他事务插入数据
* 页锁:在页面的粒度上进行上锁
* 在粒度上不同层级的所的数量是有上限的例如innodb其优先会选用行锁来进行加锁。当行锁数量过多占用空间超过表空间上限时会进行锁升级的行为。行锁会升级为页面锁此时锁的粒度会增加锁花费的开销也会变小但是粒度变大后并发性能也会变低。
* 通过对待锁的方式进行划分
* 乐观锁:乐观锁假设每次对数据进行修改时,其他事务不会访问数据,故而不会对数据正真的加锁,只是会在修改时检查数据是否被其他事务修改过。
* 乐观锁的实现方式:
* cas
* 版本号机制
* 悲观锁:
* 悲观锁假设一个事务在对数据进行修改时其他事务也会对数据进行修改故而在每次修改数据时都会对要修改的数据进行加锁悲观锁是通过mysql内部提供的锁机制来实现的
* 通过加锁方式进行划分:
* 隐式锁隐式锁通常用于插入操作。在某一个事务在对所记录进行插入操作时如果其他事务想要访问该条记录会查看最后修改该条记录的事务id是否是活跃的事务如果是活跃事务那么其会帮助插入事务创建一个X锁并且让自己进入阻塞状态。
* 隐式锁是一种延迟加锁的机制,只有当有其他事务想要访问未提交事务插入的记录时,隐式锁才会被创建。该机制能够有效减少创建锁的数量。
* 显示锁可以通过命令查看到的锁通常会用for update、lock in share mode 或者 update、delete操作会生成显示锁

View File

@@ -1,5 +1,5 @@
# redo log & undo log
* ## undo log
* undo log通常用于事务的回滚操作用来保证事务的原子性。
* 每当发生数据修改操作时update、insert、delete关于当前修改操作的相反操作会被记录到undo log中通常用于在回滚时将数据回复到修改之前的值。
# redo log & undo log
* ## undo log
* undo log通常用于事务的回滚操作用来保证事务的原子性。
* 每当发生数据修改操作时update、insert、delete关于当前修改操作的相反操作会被记录到undo log中通常用于在回滚时将数据回复到修改之前的值。
* undo log默认被记录到mysql的表空间中因而对undo log进行追加时对表中页数据进行修改时也会产生redo log对undo log的追加会通过fsync操作持久化到redo log中。这样即使在一个事务尚未被提交或是回滚之前mysql服务器崩溃下次重启时也可以通过redo log恢复对数据的修改和undo log的内容回滚事务时也能将数据恢复到崩溃前尚未被事务修改的状态。

View File

@@ -1,24 +1,24 @@
# mysql索引
* ## mysql索引的分类
* 从功能逻辑上对索引进行分类:
* 普通索引:只是用于提升查询效率,没有任何的附加约束
* 唯一性索引通过unique关键字可以设定唯一性索引其会限制该索引值必须是唯一的但是允许为null
* 主键索引:特殊的唯一性索引,在唯一性索引的基础上,主键索引还增加了不为空的约束
* 单列索引:作用在一个字段上的索引
* 联合索引:作用于多个字段上的索引
* ## 索引的创建、删除操作
```mysql
# 索引的创建方式
alter table table_name add [unique/index] idx_name (col_name...)
# or
create [unique/index] on table_name(col_name...)
# 索引的删除方式
alter table table_name drop index idx_name
# or
drop index idx_name on table_name
```
* ## 索引的可见性
```mysql
# 通过修改索引的可见性,可以比较创建
# mysql索引
* ## mysql索引的分类
* 从功能逻辑上对索引进行分类:
* 普通索引:只是用于提升查询效率,没有任何的附加约束
* 唯一性索引通过unique关键字可以设定唯一性索引其会限制该索引值必须是唯一的但是允许为null
* 主键索引:特殊的唯一性索引,在唯一性索引的基础上,主键索引还增加了不为空的约束
* 单列索引:作用在一个字段上的索引
* 联合索引:作用于多个字段上的索引
* ## 索引的创建、删除操作
```mysql
# 索引的创建方式
alter table table_name add [unique/index] idx_name (col_name...)
# or
create [unique/index] on table_name(col_name...)
# 索引的删除方式
alter table table_name drop index idx_name
# or
drop index idx_name on table_name
```
* ## 索引的可见性
```mysql
# 通过修改索引的可见性,可以比较创建
```

View File

@@ -1,14 +1,14 @@
# qml基本概念
* ## 坐标系概念
* qml组件的坐标系为迪卡尔坐标系以屏幕水平方向为x轴垂直方向为y轴分为以下两种
* scene坐标系坐标系通常与根组件的组件坐标系相同0,0位置与想要渲染的画布的左上角相对应
* item坐标系通常若当前组件不是根组件则当前组件的坐标xy属性是相对与父组件的组件坐标系的
* ## visual parent可视化父组件
* qml对象的visual parent和qobject parent是两个不同但是相关连的概念。
* object用于管理qml对象的生命周期和内存管理。
* 在使用qtquick模块时该模块中所有类型都包含了一个visual parent的概念用来决定该组建在场景渲染时的父item坐标系。每个item都有一个parent属性该属性对应的值就是该item的visual parent。如果某item的parent属性为null则该item为渲染该场景的根item。
* 如果将一个对象赋值给一个item的data属性那么该对象在qojbect层次结构中会变成item对象的子对象;如果该对象还是item对象那么该对象还会在visual parent层次结构中变为item对象的子对象
* 方便起见如果一个item中声明另一个item并且没有将该item赋值给外层item的仍然属性那么将会默认将内层item添加到外层item的data属性内层item变为外层item的qobject子对象和visual层次结构的子对象
* visual parent可以在运行时通过修改parent属性改变故而visual parent和qobject层次的父对象并不一定相同
* ## scene中的item的渲染顺序
# qml基本概念
* ## 坐标系概念
* qml组件的坐标系为迪卡尔坐标系以屏幕水平方向为x轴垂直方向为y轴分为以下两种
* scene坐标系坐标系通常与根组件的组件坐标系相同0,0位置与想要渲染的画布的左上角相对应
* item坐标系通常若当前组件不是根组件则当前组件的坐标xy属性是相对与父组件的组件坐标系的
* ## visual parent可视化父组件
* qml对象的visual parent和qobject parent是两个不同但是相关连的概念。
* object用于管理qml对象的生命周期和内存管理。
* 在使用qtquick模块时该模块中所有类型都包含了一个visual parent的概念用来决定该组建在场景渲染时的父item坐标系。每个item都有一个parent属性该属性对应的值就是该item的visual parent。如果某item的parent属性为null则该item为渲染该场景的根item。
* 如果将一个对象赋值给一个item的data属性那么该对象在qojbect层次结构中会变成item对象的子对象;如果该对象还是item对象那么该对象还会在visual parent层次结构中变为item对象的子对象
* 方便起见如果一个item中声明另一个item并且没有将该item赋值给外层item的仍然属性那么将会默认将内层item添加到外层item的data属性内层item变为外层item的qobject子对象和visual层次结构的子对象
* visual parent可以在运行时通过修改parent属性改变故而visual parent和qobject层次的父对象并不一定相同
* ## scene中的item的渲染顺序
* 默认情况下归属于统一父item的兄弟子item之间后声明的item渲染在先声明的item之上。若item a渲染在item b之上那么item a子树中所有子item都渲染在以item a为根的子树之上即使item a中某个子item的z属性大于item a子树中某个item的z属性值。若想将item a子树中的item渲染到item b子树之上只能够改变item a的z属性值令其渲染在item b之上。