2023春数据库开发复习

T1 视图可以用的几个场景?

  1. 不同表字段聚合、信息重组:当某个查询涉及多表连接、次数频繁时,可以创建视图隐藏底层表的复杂性,简化查询。

  2. 控制权限:根据不同用户的权限,可以建立不同的视图,让用户查看部分数据(如只公开非敏感数据)。

  3. 安全性需要:用户无法对视图进行随意的修改和删除,只能在只读视图中检索数据,增加了安全性。

  4. 更新/重构数据库,同时满足原来原有应用的访问:当数据库需要重构(如删除了一些旧表,创建了一些新表),但不希望这些更改影响之前的应用程序时,可以使用与已删除、修改的旧表相同的表结构创建数据库视图。因为视图保留了原有的表架构,应用程序可以访问视图来完成此前功能而无需修改。

  5. 计算列需要:数据库设计范式要求减少冗余字段,所以数据库表大多没有计算列。但报表中通常需要总销售额等计算列字段,所以可以创建一个含有计算列字段的视图。

T2 不能把数据库当“黑盒”使用,为什么?

  1. 因为每个数据库都是不相同的。在一个数据库上取得的经验也许可以部分用于另一个数据库,但必须有心理准备,二者可能存在一些基本差别,可能还有一些细微差别。

  2. 细微差别(如对null的处理)与基本差别(并发控制机制)可能有同样显著的影响

  3. 所以我们应当了解数据库,知道它如何工作,其特性如何实现,这是解决这些问题的唯一途径

不同数据库在性能、安全、连接、锁机制、空值等方面有不同,下面以空值处理和锁机制为例:

空值处理

不同的数据库系统可能会对空值的处理有所不同,有四种场景:

  1. unknown:几乎所有的关系型数据库都认为空值为unknown,如MySQL、Oracle 和 SQL Server,它们遵循SQL标准。
  2. null:SQLite 数据库则将空值视为 NULL,并支持将 NULL 视为一个特殊的值来进行处理
  3. 不包括:一些 NoSQL 数据库不包括空值(或者叫做缺失值),因为它们的数据模型可能不需要将所有属性都定义为必填的,此时数据项缺少某些属性时,将不会存储空值。
  4. 实数:一些数据库可能允许在对实数列进行聚合操作时将空值视为0,这样可以避免在计算总和或平均值时出现错误。这取决于数据库的实现和特性,而不是标准的行为。

锁机制

锁机制是实现并发控制机制的一种主要手段。不同的数据库,实现锁机制是不一样的。

比如Oracle采取多版本控制(MVVC),读一致性的并发模型,支持读一致查询和非阻塞查询,有以下特性:

  • 只有修改才加行级锁
  • Read绝对不会对数据加锁
  • Writer不会阻塞Reader
  • 读写器绝对不会阻塞写入器

这种机制让Oracle存在有时读不到正确数据的现象,因为在读取时可能会读到已提交的旧版本数据,而不像其他数据库一样选择检查时间戳和退回。

此外,读不阻塞写可以极大提高数据库的吞吐能力,所以Oracle拥有散回特性。但其他数据库可能不是这样的,比如SQLite不支持非阻塞查询。

此外,因为锁粒度、锁类型、事务隔离级别等不同,Oracle与其他数据库的锁机制还有很多不同。

总之,我们不能简单把数据库当黑盒,否则可能会出错,比如Oracle的无阻塞设计有一个副作用,若想保证一次最多只有一个用户访问一行数据,就需要开发人员自己一些做工作。

T3 索引

MySQL为什么不用BST,而是用B+树?

已知在数据库系统中,通常采用磁盘来存储索引数据,以支持对大量数据的高效查询操作。

因为索引是顺序结构,可以用二分搜索树构建索引。但是当N比较大的时候,树的深度比较高。数据查询的时间主要依赖于磁盘IO的次数,二叉树深度越大,查找的次数越多,性能越差。最坏的情况是退化成了链表。即使做Tree Balancing,也比较耗时。其存储上的不连续性,也会导致存储空间的浪费。

而B+树有高扇出和低高度的特性,更适合磁盘实现。

  • 高扇出:指在数据库索引中,每个父节点下包含的子节点数量较大,可以改善临近键的数据局限性。
  • 低高度:遍历深度小,可以减少遍历期间的寻道次数。

B+树结构

数据库开发重点存档-编程之家

  1. 基本结构:

    1. 一个结点是一个page
    2. B+树中的所有数据均保存在叶子结点,且根结点和内部结点均只是充当控制查找记录的媒介,并不代表数据本身,所有的内部结点元素都同时存在于子结点中。
    3. 叶子节点之间通过指针链接,可以实现连续范围查找,适合范围查询和分页查找等场景。数据库开发重点存档-编程之家
  2. 使用方式:

    1. 从根节点开始,根据该节点的关键字和待查找数据键值的大小比较,决定应该搜索该节点的哪个子节点。如果关键字都相等,则可以直接在叶子节点返回查询结果。如果比较较大,则到右子树查找;如果比较较小,则到左子树查找。
    2. 在选中的子节点中,重复执行第一步,直到搜索到一个叶子节点,即数据所在的节点。
    3. 在叶子节点中,按照存储顺序直接查找,或在叶子节点之间的指针链接进行有序的连续范围扫描,直到找到满足条件的数据记录。
      1. 前者是排序后直接定位需要查找的记录所在的位置,后者适合连续场景。
  3. 使用场景:

    1. 全键值 Where x = 123 (depth + 1次的固定次数)
    2. 键值范围 Where 45 < x < 123 (先进行x=45,然后顺序读取直到x>=123)
    3. 键前缀查找 where x LIKE J%’

结点分裂

数据库开发重点存档-编程之家

B+树自下而上构建,随着叶结点的数量增加,内部节点的数量和树的高度也增加。

分裂涉及到两层:下面分裂,上面增加。移动的是中间值。

叶结点分裂和非叶结点分裂的区别:因为B+树中的所有数据均保存在叶子结点,13移动到内部结点后,也同时存在于叶子结点。而非叶结点分裂时,13上升后不会出现在原有层。

索引访问基本表

先访问索引再访问基本表,索引只是查询工作的第⼀步,读取基本表中的数据才是查询的结束。

同样的索引,但不同的物理结构,可能会引起查询效率的千差万别:

  • 磁盘访问的速率
  • 物理I/O很可能是内存访问
  • 记录存储

主键索引

主键索引肯定存在,和基本表有对应关系:如文件偏移量对应,记录的是该记录在文件中偏移的字节数 数据库开发重点存档-编程之家

二级索引

二级索引是非主键的其他键值构建的索引,有两种构建方法:

  1. 直接指向主数据文件
    1. 优点:访问快,直接
    2. 缺点:物理组织更新时两边都要处理(2次写)
  2. 指向主键索引,间接访问主数据文件(可以用文件偏移量对应)
    1. 优点:物理组织更新只需要改主键索引,二级索引会随着主键索引的更新而更新,因为不直接对应偏移量
    2. 缺点:多了一次I/O,因为每次读都要经过主键索引

不同数据库选择不同,如MySQL选第二种。

T4 物理组织形式

原始

数据库开发重点存档-编程之家

在数据库中,通常将存储空间划分为多个page,每个page包含多个数据块,数据块中包含多个三元组,其中每个三元组对应一个数据项:

  • 关键字(Key):唯一标识每个数据项;

  • 相关值(Value):与关键字对应的具体数据信息;

  • 指针(Pointer):指向与当前数据项相关的数据块或其他地址信息。

在数据的读写过程中,通过k找到对应的v和p,从而实现数据的定位和操作。

优点:

  • 记录定长数据
  • 实现灵活存储和快速检索数据,提高数据管理的效率。

缺点:

  1. 除非最右侧插入数据,否则需要移动前面的数据
  2. 无法有效管理变长的字段

分槽页

数据库开发重点存档-编程之家

  • header:包括页码、记录的数量和页的其他一些元信息
  • pointer:指向下一个空闲槽的指针
  • cells:存储实际记录的槽

需要满足的需求

  • 最小开销的变长存储需求
  • 回收已删除记录的空间
  • 引用页中的记录,无论它在哪

好处

  • 可以根据指针快速查找到记录的位置;

  • 两边可以伸缩

  • 最小开销:唯一额外开销是一个指针数组,用于保存记录实际所在位置偏移量

  • 空间回收:通过对页进行碎片整理和重写,可以回收空间

  • 动态布局:从页外部只能通过槽ID引用槽,确切位置由页内部决定

更新情况

  • 只重排指针
  • 记录从后往前放

迁移情况:溢出、两边迁移怎么办

当第一次数据溢出迁移时,系统会将数据存储到其他的块或页中,但是由于系

统自身的机制,可能会出现数据不平均地分布在多个块或页中的情况。

后面再需要进行迁移时,系统会将两端的无效数据都删除掉,只保留有效数

据,然后将数据存储到新的块或页(溢出页)中。

区别

行迁移是原来的页中还会存一部分的数据和指针,而行溢出是原来的页只存指

针。(优先进行行迁移)

为什么同一个分槽页中行迁移只会发生一次

由于数据页中的数据是按照主键值排序的,因此当某个数据行被迁移时,会导致该数据页中的其他数据行的位置发生改变。

因此多次数据迁移操作可能会导致数据页中的数据行位置变得十分混乱,甚至可能会影响查询性能。

所以如果之后的超额数据都将存储在溢出页中,可以减少数据行位置变化的次数,提高查询性能。

freeblock

  1. 为什么要有freeblock?避免更多可能出现的行迁移。留一块空白,update就可以在本页中直接操作
  2. 遵循70%/30%原则,即留出30%空白

管理版本

  1. 在文件名中加入版本前缀
  2. 版本使用专门文件存储,PostgreSQL将版本存在PG_VERSION
  3. 直接存在每⼀个具体文件(索引)的头部,头部按照不变格式编码

映射校验

映射校验通常用于大规模数据的验证。

映射校验将数据划分为多个任务,然后在每个任务内部通过计算生成本地校验

(Local Checksum)。最后,在一个Reduce节点上将所有本地校验和进行

合并计算,以生成最终的全局校验和

分槽页一定要有校验,一页存一个校验和。这样可以尽早识别磁盘文件问题,避免传播到其他子系统和节点。

XOR和CRC是常见的计算校验和方法:

  • XOR:简单快速,对每个字节做异或运算
  • CRC循环冗余校验:检测连续比特位的损坏

T5/6 sql

船只租赁

设有一个船员租赁船只系统,表结构如下:

sailors表,

+--------------+---------+
| Column Name  | Type    |
+--------------+---------+
|  sid         | int     |
|  sname       | varchar |
|  rating      | int     |
|  age         | int     |
+--------------+---------+
sid为该表主键。
该表包含船员的编号,姓名,等级和年龄

boats表,

+--------------+---------+
| Column Name  | Type    |
+--------------+---------+
|  bid         | int     |
|  bname       | varchar |
|  color       | varchar |
+--------------+---------+
bid为该表主键。
该表包含船只编号,船只名称和船只颜色

reserves表,

+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
|  sid          | int     |
|  bid          | int     |
|  reserve_date | date    |
+---------------+---------+
(sid,bid)为该表主键。
该表包含船员编号,船员预定的船只编号,船员预定船只的日期
  1. 编写一个sql语句,找出年龄在35以上的并且在2020-09-01至2020-09-30期间没有预定红色(RED)船只的水手,结果返回水手姓名sname。

    select sailors.sname from sailors 
    where sailors.age > 35 and sailors.sid not in (select reserves.sid from reserves, boats where reserves.bid = boats.bid and boats.color = 'RED' and reserves.reserve_date between '2020-09-01' and '2020-09-30'
    )
    
  2. 编写一个sql语句,找出预定了所有船的水手,结果返回水手姓名sname。

    select s.sname from sailors s where not exists (select * from boats b where not exists(select * from reserves r where b.bid = r.bid and s.sid = r.sid)
    )
    
  3. 编写一个sql语句,找出2020-05-01至2020-05-31期间预定过绿色船(GREEN)的等级最高的水手,结果返回水手姓名sname。

    select sailors.sname from sailors 
    where sailors.sid in (select reserves.sid from reserves, boats where reserves.bid = boats.bid and boats.color = 'GREEN' and reserves.reserve_date between '2020-05-01' and '2020-05-31'
    ) 
    and sailors.rating >= ALL(select sailors.rating from sailors where sailors.sid in (select reserves.sid from reserves, boats where reserves.bid = boats.bid and boats.color = 'GREEN' and reserves.reserve_date between '2020-05-01' and '2020-05-31')
    )
    
  4. 编写一个sql语句,找出年龄在35岁以上,并且在2020-08-01至2020-08-31期间同时预定了红色船(RED)和绿色船(GREEN)的水手,结果返回水手姓名sname。

    select sailors.sname from sailors 
    where sailors.age > 35 and sailors.sid in (select r.sid from reserves r, boats bwhere r.bid = b.bid and b.color = 'GREEN' and r.reserve_date between '2020-08-01' and '2020-08-31'
    ) and sailors.sid in (select r.sid from reserves r, boats bwhere r.bid = b.bid and b.color = 'RED' and r.reserve_date between '2020-08-01' and '2020-08-31'
    )
    

比昨天温度高

表 Weather

+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| id            | int     |
| recordDate    | date    |
| temperature   | int     |
+---------------+---------+

id 是这个表的主键

SELECT tod.`Id` AS Id
FROM Weather yes
JOIN Weather tod
ON DATEDIFF(tod.`RecordDate`, yes.`RecordDate`) = 1
WHERE tod.`Temperature` > yes.`Temperature` 

平均值、中位数、replace

  1. 删除不想要的字符:replace
UPDATE tbl_name 
SET field_name = REPLACE(field_name, string_to_find, string_to_replace)
WHEREconditions;UPDATE products --表名
SET productDescription = REPLACE(productDescription, 'abuot', 'about');  --列名

查询查找所有出现的拼写错误词:abuot,并通过products表的productDescription列中使用正确单词将其替换

数据库开发重点存档-编程之家

数据库开发重点存档-编程之家

  1. 计算平均值,最大最小值

数据库开发重点存档-编程之家

coalesce(num,0) // 去除null

  1. 计算中位数

数据库开发重点存档-编程之家

  1. 计算众数

数据库开发重点存档-编程之家

count(*)包括了所有的列,相当于行数,不会忽略列值为NULL

count(列名)只包括列名那一列,在统计结果的时候,会忽略列值为空

T7 BOM问题

存储简单树

简单树:单一结点只有一个父节点

  • 邻接模型:父节点id作为子节点id的一个属性
  • 物化路径:把树中的每个节点和它在树中的描述数据相结合(描述数据是从根节点到直接父节点的串联序列,类似1 ->1.1->1.1.1)
  • 嵌套集合模型:每个节点被赋予一对数字,其中父节点的两个数字包含其所有节点的左右数字,然后一层一层这样包含下去。数据库开发重点存档-编程之家
  • 嵌套间隔模型:以两个数字为特定节点的路径编码,这个数字被解释成有理数的分子和分母(不常用,可以忽略)

存储多父节点的树

BOM:单一结点可能有多个父节点。

一定通过多表,一张表存不了。一个表存内容,一个表存关系。

如下图Components存内容,Composition存关系。

数据库开发重点存档-编程之家

以邻接表模型为例:使用两个表来存储多父节点的树:

  • 存储节点的信息的表:记录节点的各种属性。
  • 存储父子关系的表:记录节点间的父子关系。
    • 增加一个额外的字段,用于存储每个父子关系在多父树中的顺序。

Node表:

| id | value | … |

Relationship表:

| id | parent_id | child_id | order |

计算百分比

--先查询初始成分的占比,再对其他部分计算实际占比
with recursive_composition(actual_pct, component_id)
as (select a.pct, a.component_id
from composition a, components b
where b.component_id = a.recipe_id
and b.component_name = 'Philter #5'
union all
select parent.pct * child.pct, child.component_id
from recursive_composition parent, composition child
where child.recipe_id = parent.component_id)--主查询,去重
select x.component_name, sum(y.actual_pct)
from recursive_composition y, components x
where x.component_id = y.component_id
and x.component_type = 'I'
group by x.component_name

附加题 回忆一下上课时听到的感兴趣的知识点?

我对函数索引中where f(indexed_col)='some value'这样的检索条件会使索引无法发挥作用感兴趣。

老师上课以日期函数为例,说第一种写法会导致 MySQL 无法使用 order_date 上的索引,因为需要对每一行记录执行 DATE_FORMAT 函数,这个过程无法利用索引。

SELECT COUNT(*) FROM orders WHERE DATE_FORMAT(order_date, '%Y-%m-%d') = '2023-03-08';
SELECT COUNT(*) FROM orders WHERE order_date >= '2023-03-08 00:00:00' AND order_date <= '2023-03-08 23:59:59';

隐式类型转换因为触发了CAST函数,也无法发挥作用。因此应该尽量避免在查询条件或者查询字段中使用函数,以便能够充分利用索引提高查询性能。

我好奇背后的原因,通过课下学习,发现B+ 树提供的这个快速定位能力,来源于同一层兄弟节点的有序性。而运行函数后的值会被修改或者变形,可能与实际存储在B+树中的原始值不同。比如假设树的第一层数据是2018-1-1,2019-9-1,2020-7-1,对每个值执行month()函数,得到的值(1,9,7),破坏了有序的前提。

mysql中规定只要对索引字段使用函数操作(无论是否破坏了了有序性),就放弃走树搜索功能,所以效率降低。

查看全文

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.dgrt.cn/a/2294787.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章:

数据库开发重点存档-编程之家

数据库开发重点存档

2023春数据库开发复习
T1 视图可以用的几个场景? 不同表字段聚合、信息重组:当某个查询涉及多表连接、次数频繁时,可以创建视图隐藏底层表的复杂性,简化查询。 控制权限:根据不同用户的权限,可以建立不同……

数据库开发重点存档-编程之家

【每日一题Day175】LC1147段式回文 | 贪心 +双指针

段式回文【LC1147】 你会得到一个字符串 text 。你应该把它分成 k 个子字符串 (subtext1, subtext2,…, subtextk) ,要求满足: subtexti 是 非空 字符串所有子字符串的连接等于 text ( 即subtext1 subtext2 … subtextk text )对于所有 ……

数据库开发重点存档-编程之家

字典树(trie树)实现词频查找

碎碎念: 在大学数据结构中,有关于树的应用部分。其中一个就是利用树来进行词频的统计,我们主要希望的是查询效率高,对于树来说查询效率和插入都比较快,时间复杂度都能做到较好。我们来看一段来自百度百科对trie树的解释:字典树又称……

数据库开发重点存档-编程之家

网页适配小技巧一

我们这里指的是原生的html代码,因为先写好原生的才能更好的驾驭框架嘛!
1.字体问题 字体的单位:px em rem px:像素单位,这个是现在网页设计的一个bug 我都很不喜欢用,因为现在的设备千奇百怪,什……

数据库开发重点存档-编程之家

js问题-解决定时器不能传入函数参数问题

碎碎念:
在用JavaScript的定时器时 没有参数是一个非常难受的事情。
无论是window.setTimeout还是window.setInterval,在使用函数名作为调用句柄时都不能带参数,而在许多场合必须要带参数,这就需要想方法解决。经网上查询后整……

数据库开发重点存档-编程之家

js ajax跨域请求报错的解决办法

在做项目时,遇到一个请求或着提交的错误,大概出现如下代码,原因是不能跨域请求。No Access-Control-Allow-Origin header …等主要的解决方法有三个。1.在后台更改header
header(Access-Control-Allow-Origin:*);//允许所有来源访问
header……

数据库开发重点存档-编程之家

ajax使用说明

AJAX Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。AJAX 不是新的编程语言,而是一种使用现有标准的新方法。AJAX 最大的优点是在不重新加载整个页面的情况下,可以与服务器交换数据并更新部分网页内容。AJAX 不需……

数据库开发重点存档-编程之家

vue router(路由)的最基础使用

假装很重要:才写博客不久,自己写的难免思维比较混沌,所以大家提提意见,可以评论,也可以留联系方式一起进步。我们这里只是第一步使用router,最简单的一步先踏出去,不涉及动态路由、嵌套路由的使用。 刚听……

数据库开发重点存档-编程之家

linux 基础(常用)命令

1.一般命令格式
Linux命令通常由以下三部分组成:command [-option] [argument]command:即是要运行命令的本身,像我们调用的函数。
option :的话是可选的,即有些命令是没有选项的,选项(也有人称之为参数……

数据库开发重点存档-编程之家

图形学 直线算法

1.DDA算法
我们在画直线时我们取的像素都在确定的两个像素里选择。DDA直线算法在于利用K或者K的倒数,我们都知道kdy/dx,所以是单位x内y的变化。不过在屏幕内我们通常以像素为单位,所以当|k|<1时我们以dx1,dyk。当|k|>1,我们发现y比x走的……

数据库开发重点存档-编程之家

cocos2d-3.10 整合版本连接

官方给出的是在:http://www.cocos2d-x.org/filedown/CocosForWin-v3.10.exe 如果下载不了,可以在这里下http://cdn.cocos2d-x.org/CocosForWin-v3.10.exe…

数据库开发重点存档-编程之家

code=45, title=禁止登录, message=登录失败,建议升级最新版本后重试,或通过问题反馈与我们联系。

如果你是采用 java 开发的,你可以参考本文章,java 和 kotlin 都是可以相互转换的。 在解决之前,先说明环境: JDK版本:java version "17.0.3.1" 【Oracle JDK】 Kotlin版本:1.8.20 采取simbot核心包开发&am……

数据库开发重点存档-编程之家

cocos2d屏幕适配方案以及winsize,framesize,VisibleSize,contentSize的区别和联系

一、首先要吐槽小cocos官方把这个问题描述的很模糊,讲解的不清不楚,很多人工作两三年的人也不明白。
二、言归正传:阐述下winsize,framesize,VisibleSize,contentSize的概念。frameSize表示的是屏幕的分辨率, 这里多说……

数据库开发重点存档-编程之家

lua协程详解

https://www.cnblogs.com/zrtqsk/p/4374360.html…

数据库开发重点存档-编程之家

lua协程实例

一、lua协程的和c中协程的区分 1. lua 程序设计中的关于lua多线程以及协程的概述 上述说的意思我认为就是 lua的协程类似于但是不等同于真正意义的多线程(同时执行一些操作);我做过测试,同时创建1000个协程并执行(代码……

数据库开发重点存档-编程之家

cocos内存管理原理详解

转载自:COCOS学习笔记–Cocod2dx内存管理(三)-Coco2d-x内存运行原理_RapdoZoroの博客-CSDN博客_cocos2dx引擎内存管理工具
一、cocos2dx之如何优化内存使用(高级篇)_yixiao3660的专栏-CSDN博客
cocos2d中对于图片动画加载缓存的使用 – 简书……

数据库开发重点存档-编程之家

cocos creator实战项目记录(一)

creator(一下简称ccc)貌似从16年发布到现在已经有五年了,当时我还是游戏开发菜鸟,不过当时我已经在从事cocos-js的工作,所以对ccc的发布还是比较关注。不过后来,阴差阳错的一直没有干ccc相关的项目&#xf……

数据库开发重点存档-编程之家

cocos creator实战项目记录(二)—-屏幕适配

屏幕适配应该是很多游戏引擎都会有的东西;先发下我在之前博客汇中写关于屏幕适配的文章,cocos2d-x中的屏幕适配,这篇文章主要讲解了原来cocos2d-x(下文简称cc-2d)中的屏幕适配;对比现在的cocoscreator (以下简称ccc)&a……

数据库开发重点存档-编程之家

k8s学习-CKS真题-K8S安全策略PodSecurityPolicy

目录题目环境搭建解题参考题目 Task 使用 Trivy 开源容器扫描器检测 namespace kamino 中 Pod 使用的具有严重漏洞的镜像。 查找具有 High 或 Critical 严重性漏洞的镜像,并删除使用这些镜像的 Pod。 注意:Trivy 仅安装在 cluster 的 master 节点上&……

数据库开发重点存档-编程之家

STM32F103基于HAL库I2C/SPI硬件接口+DMA驱动 SSD1306 Oled

STM32F103基于HAL库I2C/SPI硬件接口DMA驱动 SSD1306 Oled✨由于手上只有I2C接口的SSD1306 OLED屏幕,仅测试了硬件I2C驱动显示功能,实际测试的FPS帧率在37或38变化。 📢本项目从Github开源项目中移植过来,开源地址:http……