• 重点理解为什么B树，为什么索引 step 2: 实现一个B树，代码理解参考下面 http://blog.csdn.net/qifengzou/article/details/21079325 #include #include #include #include typedef struct _btree_...
step 1: 数据库的最简单实现
重点理解为什么要用B树，为什么要有索引

step 2:
实现一个B树，代码理解参考下面 http://blog.csdn.net/qifengzou/article/details/21079325

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
typedef struct _btree_node_t
{
int num;                        /* 关键字个数 */
int *key;                       /* 关键字：所占空间为(max+1) - 多出来的1个空间用于交换空间使用 */
struct _btree_node_t **child;   /* 子结点：所占空间为（max+2）- 多出来的1个空间用于交换空间使用 */
struct _btree_node_t *parent;   /* 父结点 */
}btree_node_t;

typedef struct
{
int max;                        /* 单个结点最大关键字个数 - 阶m=max+1 */
int min;                        /* 单个结点最小关键字个数 */
int sidx;                       /* 分裂索引 = (max+1)/2 */
btree_node_t *root;             /* B树根结点地址 */
}btree_t;

static int btree_merge(btree_t *btree, btree_node_t *node);
static int _btree_merge(btree_t *btree, btree_node_t *left, btree_node_t *right, int mid);
static btree_node_t *btree_creat_node(btree_t *btree)
{
btree_node_t *node = NULL;

node = (btree_node_t *)calloc(1, sizeof(btree_node_t));
if(NULL == node) {
fprintf(stderr, "[%s][%d] errmsg:[%d] %s\n", __FILE__, __LINE__, errno, strerror(errno));
return NULL;
}

node->num = 0;

/* More than (max) is for move */
node->key = (int *)calloc(btree->max+1, sizeof(int));
if(NULL == node->key) {
free(node), node=NULL;
fprintf(stderr, "[%s][%d] errmsg:[%d] %s\n", __FILE__, __LINE__, errno, strerror(errno));
return NULL;
}

/* More than (max+1) is for move */
node->child = (btree_node_t **)calloc(btree->max+2, sizeof(btree_node_t *));
if(NULL == node->child) {
free(node->key);
free(node), node=NULL;
fprintf(stderr, "[%s][%d] errmsg:[%d] %s\n", __FILE__, __LINE__, errno, strerror(errno));
return NULL;
}

return node;
}

btree_t* btree_creat(int m)
{
btree_t *btree = NULL;

if(m < 3) {
fprintf(stderr, "[%s][%d] Parameter 'max' must geater than 2.\n", __FILE__, __LINE__);
return NULL;
}

btree = (btree_t *)calloc(1, sizeof(btree_t));
if(NULL == btree) {
fprintf(stderr, "[%s][%d] errmsg:[%d] %s!\n", __FILE__, __LINE__, errno, strerror(errno));
return NULL;
}

btree->max= m - 1;
btree->min = m/2;
if(0 != m%2) {
btree->min++;
}
btree->min--;
btree->sidx = m/2;
btree->root = NULL; /* 空树 */

return btree;
}

static int btree_split(btree_t *btree, btree_node_t *node)
{
int idx = 0, total = 0, sidx = btree->sidx;
btree_node_t *parent = NULL, *node2 = NULL;

while(node->num > btree->max) {
/* Split node */
total = node->num;

node2 = btree_creat_node(btree);
if(NULL == node2) {
fprintf(stderr, "[%s][%d] Create node failed!\n", __FILE__, __LINE__);
return -1;
}

/* Copy data */
memcpy(node2->key, node->key + sidx + 1, (total-sidx-1) * sizeof(int));
memcpy(node2->child, node->child+sidx+1, (total-sidx) * sizeof(btree_node_t *));

node2->num = (total - sidx - 1);
node2->parent  = node->parent;

node->num = sidx;
/* Insert into parent */
parent  = node->parent;
if(NULL == parent)  {
/* Split root node */
parent = btree_creat_node(btree);
if(NULL == parent) {
fprintf(stderr, "[%s][%d] Create root failed!", __FILE__, __LINE__);
return -1;
}

btree->root = parent;
parent->child[0] = node;
node->parent = parent;
node2->parent = parent;

parent->key[0] = node->key[sidx];
parent->child[1] = node2;
parent->num++;
}
else {
/* Insert into parent node */
for(idx=parent->num; idx>0; idx--) {
if(node->key[sidx] < parent->key[idx-1]) {
parent->key[idx] = parent->key[idx-1];
parent->child[idx+1] = parent->child[idx];
continue;
}
break;
}

parent->key[idx] = node->key[sidx];
parent->child[idx+1] = node2;
node2->parent = parent;
parent->num++;
}

memset(node->key+sidx, 0, (total - sidx) * sizeof(int));
memset(node->child+sidx+1, 0, (total - sidx) * sizeof(btree_node_t *));

/* Change node2's child->parent */
for(idx=0; idx<=node2->num; idx++) {
if(NULL != node2->child[idx]) {
node2->child[idx]->parent = node2;
}
}
node = parent;
}

return 0;
}

static int _btree_insert(btree_t *btree, btree_node_t *node, int key, int idx)
{
int i = 0;

/* 1. 移动关键字:首先在最底层的某个非终端结点上插入一个关键字,因此该结点无孩子结点，故不涉及孩子指针的移动操作 */
for(i=node->num; i>idx; i--) {
node->key[i] = node->key[i-1];
}

node->key[idx] = key; /* 插入 */
node->num++;

/* 2. 分裂处理 */
if(node->num > btree->max) {
return btree_split(btree, node);
}

return 0;
}

int btree_insert(btree_t *btree, int key)
{
int idx = 0;
btree_node_t *node = btree->root;

/* 1. 构建第一个结点 */
if(NULL == node) {
node = btree_creat_node(btree);
if(NULL == node) {
fprintf(stderr, "[%s][%d] Create node failed!\n", __FILE__, __LINE__);
return -1;
}

node->num = 1;
node->key[0] = key;
node->parent = NULL;

btree->root = node;
return 0;
}

/* 2. 查找插入位置：在此当然也可以采用二分查找算法，有兴趣的可以自己去优化 */
while(NULL != node) {
for(idx=0; idx<node->num; idx++) {
if(key == node->key[idx]) {
fprintf(stderr, "[%s][%d] The node is exist!\n", __FILE__, __LINE__);
return 0;
}
else if(key < node->key[idx]) {
break;
}
}

if(NULL != node->child[idx]) {
node = node->child[idx];
}
else {
break;
}
}

/* 3. 执行插入操作 */
return _btree_insert(btree, node, key, idx);
}
static int _btree_merge(btree_t *btree, btree_node_t *left, btree_node_t *right, int mid)
{
int m = 0;
btree_node_t *parent = left->parent;

left->key[left->num++] = parent->key[mid];

memcpy(left->key + left->num, right->key, right->num*sizeof(int));
memcpy(left->child + left->num, right->child, (right->num+1)*sizeof(btree_node_t *));
for(m=0; m<=right->num; m++) {
if(NULL != right->child[m]) {
right->child[m]->parent = left;
}
}
left->num += right->num;

for(m=mid; m<parent->num-1; m++) {
parent->key[m] = parent->key[m+1];
parent->child[m+1] = parent->child[m+2];
}

parent->key[m] = 0;
parent->child[m+1] = NULL;
parent->num--;
free(right);

/* Check */
if(parent->num < btree->min) {
return btree_merge(btree, parent);
}

return 0;
}

static int btree_merge(btree_t *btree, btree_node_t *node)
{
int idx = 0, m = 0, mid = 0;
btree_node_t *parent = node->parent, *right = NULL, *left = NULL;

/* 1. node是根结点, 不必进行合并处理 */
if(NULL == parent) {
if(0 == node->num) {
if(NULL != node->child[0]) {
btree->root = node->child[0];
node->child[0]->parent = NULL;
}
else {
btree->root = NULL;
}
free(node);
}
return 0;
}

/* 2. 查找node是其父结点的第几个孩子结点 */
for(idx=0; idx<=parent->num; idx++) {
if(parent->child[idx] == node) {
break;
}
}

if(idx > parent->num) {
fprintf(stderr, "[%s][%d] Didn't find node in parent's children array!\n", __FILE__, __LINE__);
return -1;
}
/* 3. node: 最后一个孩子结点(left < node)
* node as right child */
else if(idx == parent->num) {
mid = idx - 1;
left = parent->child[mid];

/* 1) 合并结点 */
if((node->num + left->num + 1) <= btree->max) {
return _btree_merge(btree, left, node, mid);
}

/* 2) 借用结点:brother->key[num-1] */
for(m=node->num; m>0; m--) {
node->key[m] = node->key[m - 1];
node->child[m+1] = node->child[m];
}
node->child[1] = node->child[0];

node->key[0] = parent->key[mid];
node->num++;
node->child[0] = left->child[left->num];
if(NULL != left->child[left->num]) {
left->child[left->num]->parent = node;
}

parent->key[mid] = left->key[left->num - 1];
left->key[left->num - 1] = 0;
left->child[left->num] = NULL;
left->num--;
return 0;
}

/* 4. node: 非最后一个孩子结点(node < right)
* node as left child */
mid = idx;
right = parent->child[mid + 1];

/* 1) 合并结点 */
if((node->num + right->num + 1) <= btree->max) {
return _btree_merge(btree, node, right, mid);
}

/* 2) 借用结点: right->key[0] */
node->key[node->num++] = parent->key[mid];
node->child[node->num] = right->child[0];
if(NULL != right->child[0]) {
right->child[0]->parent = node;
}

parent->key[mid] = right->key[0];
for(m=0; m<right->num; m++) {
right->key[m] = right->key[m+1];
right->child[m] = right->child[m+1];
}
right->child[m] = NULL;
right->num--;
return 0;
}

static int _btree_delete(btree_t *btree, btree_node_t *node, int idx)
{
btree_node_t *orig = node, *child = node->child[idx];

/* 使用node->child[idx]中的最大值替代被删除的关键字 */
while(NULL != child) {
node = child;
child = node->child[child->num];
}

orig->key[idx] = node->key[node->num - 1];

/* 最终其处理过程相当于是删除最底层结点的关键字 */
node->key[--node->num] = 0;
if(node->num < btree->min) {
return btree_merge(btree, node);
}

return 0;
}

int btree_delete(btree_t *btree, int key)
{
int idx = 0;
btree_node_t *node = btree->root;

while(NULL != node) {
for(idx=0; idx<node->num; idx++) {
if(key == node->key[idx]) {
return _btree_delete(btree, node, idx);
}
else if(key < node->key[idx]) {
break;
}
}

node = node->child[idx];
}

return 0;
}

void Inorder(btree_node_t *root,int deep){
int i,j,k,a=1;
if(root != NULL)
{
if(deep)
printf("\n");
for(j = 0;j < deep;j++){
printf("---");
}
for(i = 0; i <= root->num;i++){
if(a){
printf("< %d | ",root->num);
for( k = 0;k < root->num;k++){
printf("%d ",root->key[k]);
}
a--;
printf(">");
}
Inorder(root->child[i],deep+1);
}
printf("\n");
}
}

int main(){
btree_t *bt;
int i;
int a[21]={3,4,44,12,67,98,32,43,24,100,34,55,33,13,25,8,5,41,77,200};
bt = btree_creat(4);
for(i = 0;i < 20;i++){
printf("insert %d: %d\n",i+1,a[i]);
btree_insert(bt,a[i]);
Inorder(bt->root,0);
printf("\n");
}

for(i = 0;i < 10;i++){
printf("delete %d: %d\n",i+1,a[i]);
btree_delete(bt,a[i]);
Inorder(bt->root,0);
}

return 0;
}

以上代码用Ubuntu16的gcc编译通过，包含一个演示插入删除的过程

step 3:
理解索引与主键的联系与区别 深入浅出数据库索引原理

step 4:
总结以及提升 MySQL索引背后的数据结构及算法原理
展开全文
• 学习iOS逆向有什么用？ 我们一般都是正向开发，那逆向开发有什么用呢？ 有助于深入理解iOS内部原理 这个应该是很容易懂的，因为不懂内部原理的话，如何来逆向App呢。 查看竞品，分析竞品 例如你想做一款IM产品...
学习iOS逆向有什么用？
我们一般都是正向开发，那逆向开发有什么用呢？

有助于深入理解iOS内部原理

这个应该是很容易懂的，因为不懂内部原理的话，如何来逆向App呢。

查看竞品，分析竞品

例如你想做一款IM产品，就可以看下微信的数据库如何设计，各种类型的消息如何定义，收到不同类型的消息如何处理，有时候产品经理的一句参考微信，那你是不是无从下手了，其实都可以通过逆向进行了解的。

修改已有APP的功能

例如看个视频去广告、增加抖音自动翻页功能、增加微信一键转发功能，总之很多别的App，你觉得设计不合理，或者缺少的功能，都可以动手实现。

学习优秀APP的设计
增加安全防护，增强客户端的安全性

所谓攻防兼备，逆向了别人的App。是不是觉得安全防护很重要，那知道了如何攻，自己的产品就可以针对性的守了。

通用性极强（越底层，通用性越强）
简历上可以增加亮点

作为一个开发者，有一个学习的氛围跟一个交流圈子特别重要，这是一个我的iOS交流群：413038000，不管你是大牛还是小白都欢迎入驻 ，分享BAT,阿里面试题、面试经验，讨论技术， 大家一起交流学习成长！

推荐阅读
iOS开发——最新 BAT面试题合集（持续更新中）
需要用到的工具
逆向的时候，需要用到挺多工具的，这里先列举几个必备的主流工具

动态调试程序UI界面的工具，用来分析界面的神器

charles

抓包修改工具

IDA

是目前最棒的一个静态反编译软件之一，为众多0day世界的成员和ShellCode安全分析人士不可缺少的利器。

Hopper Disassembler

逆向工程工具，可让反汇编，反编译和调试应用程序，也是一款优秀的利器

gdb

GNU Project调试器，可以查看另一个程序在执行过程中正在执行的操作–或该程序崩溃时正在执行的操作

lldb

是一个命令行调试环境，其功能类似于GDB，LLDB为Xcode提供了底层调试环境，该调试环境在调试区域中包含一个控制台窗格，用于在Xcode IDE环境中直接访问LLDB命令。

MachOView

可Mac平台中可查看MachO文件格式信息

[Alfred](Productivity App for macOS)

快速打开软件，可以自定义脚本来执行

jtool

二进制搜索，符号注入，查看文件结构等

类似于UNIX的逆向工程框架和命令行工具集

keystone

一个轻量级的多平台，多体系结构的汇编器框架。

还有其他很多工具用到的时候再说。
逆向的流程
一般来说，我们逆向有如下步骤：

砸壳，导出目标App，导出头文件
界面分析，获取到当前的控制器或者视图
对Mach-O文件的静态分析MachOView、class-dump、Hopper Disassembler、ida等
动态调试 对运行中的APP进行代码调试
模拟或者篡改实现逻辑
注入代码到APP中
必要时还可能需要重新签名、打包ipa

iOS系统文件

我们知道，每个App都有自己的沙盒，自己是访问不了其他App的沙盒的，那么越狱之后就可以访问其他应用的沙盒。

系统文件的作用如下：

/Applications

存放系统App和Cydia下载的app ，但是不包括AppStore下载的App

/Library

系统App的资源，用户设置，例如系统自带铃声，系统日志等，
/Library/MobileSubstrate 存放所有基于Cydia Substrate的插件

/System

包含大量系统组件和库
/System /Library/Carrier Bundles 存储运营商的配置
/System /Library/Frameworks 和/System /Library/PrivateFrameworks 存放系统的动态库

/User

实际指向 /var/mobile
/User/Meida 存放相册等信息
/User/Library 存放短信，邮件等

/usr

用户工具和程序
/usr/include 存放C头文件
/usr/lib 存放库文件

/bin

存放提供系统功能的二进制文件

/var

临时文件、用户数据，日志，从AppStore下载的应用等

补充

Cydia Substrate 是一个可以修改系统和应用程序的框架

MobileHooker 第三方应用，系统的函数hook，执行我们自己的代码

MobileLoader 可以让第三方应用启动的时候加载我们自己的动态库

safe mode 安全模式，安装插件系统崩溃，这时候所有插件都不会加载

最近准备把逆向知识，整理成系列，地址：https://xiaozhuanlan.com/cydia

包括内容有：

从砸壳，SSH认证，环境搭建，逆向工具介绍和使用，到静态分析，动态调试。
从签名原理，重签名，破解，反破解，汇编分析，到游戏安全，游戏分析，项目实战等。

作为一个开发者，有一个学习的氛围跟一个交流圈子特别重要，这是一个我的iOS交流群：413038000，不管你是大牛还是小白都欢迎入驻 ，分享BAT,阿里面试题、面试经验，讨论技术， 大家一起交流学习成长！

推荐阅读
iOS开发——最新 BAT面试题合集（持续更新中）
作者：eagleyz
链接：https://www.jianshu.com/p/2f3c65bc93ef


展开全文
• 刚学习MyBatis，对于@Mapper注解的接口能够完成POJO对象到数据库记录映射百般疑惑，一开始很纳闷为什么不需要定义Mapper接口的实现类就能完成这个过程。例如以下代码段中只定义了UserDAO这个接口，但是并不影响...
刚学习MyBatis，对于用@Mapper注解的接口能够完成POJO对象到数据库记录映射百般疑惑，一开始很纳闷为什么不需要定义Mapper接口的实现类就能完成这个过程。例如以下代码段中只定义了UserDAO这个接口，但是并不影响POJO对象到数据库的映射。查阅了相关的资料，稍有解惑。
@Mapper
public interface UserDAO {
String TABLE_NAME = "user";
}
public class InitDBTest {
@Autowired
UserDAO userDAO;

User user = new User();
user.setName("user1");
user.setSalt("");
}
}
1. MapperRegistry
程序启动之初MyBatis就创建了这个类的一个实例，它有一个HashMap类型的属性用于存储每个Mapper接口（key）和相应的MapperProxyFactory（value）；另外有两个重要的方法getMapper()和addMapper()，分别用于获取和注册Mapper接口到这个HashMap中。public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if(mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);//通过Mapper接口的type返回一个相应的mapperProxyFactory的一个实例

} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
public <T> void addMapper(Class<T> type) {
if(type.isInterface()) {
if(this.hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}

try {
this.knownMappers.put(type, new MapperProxyFactory(type));//将这个Mapper接口“注册”
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
parser.parse();
} finally {
this.knownMappers.remove(type);
}

}
}
}
2. MapperProxyFactory
正如它的名字mapper代理工厂“，这个类是一个生产mapper代理对象的工厂，而mapper代理对象负责代替mapper接口完成POJO到数据库的映射。在MapperProxyFactory中的两个重要方法如下，用于创建mapper代理类的一个实例。protected T newInstance(MapperProxy<T> mapperProxy) {
//用了java的动态代理，获得真正的代理对象
}

public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
3. MapperProxy
通过调用特定的mapper代理对象的invoke()方法，实现到数据库的映射，实际上invoke()中又调用了MapperMethod中的execute方法，execute方法主要是用到sqlSession的insert、update、select、delete等，这部分就是我们都很熟悉的了。public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
} else {
MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}
}
4. MapperMethodpublic Object execute(SqlSession sqlSession, Object[] args) {
Object param;
Object result;
if(SqlCommandType.INSERT == this.command.getType()) {
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
} else if(SqlCommandType.UPDATE == this.command.getType()) {
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
} else if(SqlCommandType.DELETE == this.command.getType()) {
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
} else if(SqlCommandType.SELECT == this.command.getType()) {
if(this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if(this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if(this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else if(this.method.returnsCursor()) {
result = this.executeForCursor(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
}
} else {
if(SqlCommandType.FLUSH != this.command.getType()) {
throw new BindingException("Unknown execution method for: " + this.command.getName());
}
回到最上面的demo，userDAO属性被注解为@Autowired，由spring完成bean的自动装配，其中spring会去获取一个MyBatis的mapperProxy代理对象作为这个bean，它是一个真正的对象而不是一个Interface类型，由这个代理人完成到数据库的映射。

展开全文
• 当然存储引擎是个很复杂的东西，但我们可以简单的思考数据的存储方式，用什么样的数据结构比价合适，合理，性能最好。 我们过很多数据结构：集合，数组，链表，hash表，树，堆，栈等等等等。它们是我们存储数据的...
数据库文件存储原理

在说数据库存储原理之前，我们不妨先思考个问题。假如我们是数据库存储引擎的设计者，我们会如何在磁盘上存储数据。当然存储引擎是个很复杂的东西，但我们可以简单的思考数据的存储方式，用什么样的数据结构比价合适，合理，性能最好。

我们学过很多数据结构：集合，数组，链表，hash表，树，堆，栈等等等等。它们是我们存储数据的手段。之前学习Python的时候，有本书叫做《Python学习手册》刚开始它就让我们学习实现写一个数据库。刚开始用元祖，列表这些简单的数据结构，后面用到复杂的字典类型。我们发现，数据结构是我们用来存储数据的手段，使得我们存取数据更加高效。所以学会各种数据结构的特性后我们遇到数据存储的问题就有了若干选项。

我们熟知的关系型数据库和NoSql数据库底层实现都是树，mysql使用的B+树，mongodb用的是B树。

为什么不用数据组，链表，hash表能？我想最初设计者应该也做过选择吧。

数组：

Java中ArrayList是用数组实现的，当插入的数据超过初始值的时候，数组就要重新定义一个新的更长的数组来“装”，而且随机插入一个数据时间复杂度是O（n），性能太差，且数组只能存单一类型的元素，因此无法满足。

链表

也有缺陷虽然它的随机插入时间复杂度是O(1)但随机访问的时间复杂度是O(n)，也是无法满足。

Hash表

会是比较好一点的选择，我们选择适合的数据结构，其实都是为了避免全量IO，我们知道，每次IO请求都涉及到用户态和内核态的上下文切换，会有很大的资源消耗，而全量IO是最糟糕的情况。

所以时间复杂度越高的IO的次数就越多。所以为了避免全量IO我们考虑使用更复杂点的数据结构。Hash表我们都熟悉，Java中的HashMap就是由hash表来实现的。它的组成是一个数组的hash槽，每个槽位上是一个链表（JDK1.8及以后版本对其做了优化），我们将数据hash值取余的方式（最开始是这样的，hashmap中的实现负载一点）均匀的放在各个槽位的链表上，余数相等的hash碰撞，放在同一链表。虽然Hash表一般情况下能避免全量IO，但是它用到了链表来放数据，极端情情况下它也会有全量IO。

二叉树

说到二叉树，不难想到递归这个反人类的算法。它的特点是：

左子树小于根节点，根节点小于右子树（这里比较的是值，比如hash）
非叶子节点最多有2个孩子节点
由于它的特性，在极端情况下它会变成一个链表，时间复杂度是O(n)，所以它不适合。

平衡二叉树

AVL树

基于AVL算法

任何节点的左右子树相差都不能超过1
当然它也属于二叉树，所以拥有二叉树的特点
插入或者删除节点通过1所说的平衡因子来对树进行旋转，来达到平衡。虽然这样解决了变成单链表的极端情况，但这是以牺牲时间为代价的。每次旋转都需消耗时间。数据库这种大量存入数据，频繁查询修改的场景可想而知有多耗时，所以它也不适合。

红黑树

在AVL树的基础上做的优化，用特殊的方法使其减少旋转次数，从而节省时间。它有以下特性：

根节点是黑色节点
非根节点非红即黑
红色节点的两个子节点是黑色（从根节点到叶子节点的每条单路径上没有两个重复的红色节点）
叶子节点必为黑色节点（NUIL节点）
任意节点到叶子节点的所有路径包含的黑色节点数目都相同
我们插入或者删除数据时对红黑树的节点做了操作，打破了以上特性，红黑树通过旋转来维持这些特性。节点的删除和增加算法这里就先不说了。数据库大量存储数据的场景，用红黑树的话势必这棵树会很深，很深就意味着时间复杂度很高，而且还要旋转消耗时间。所以它也不是我们好的选择。

B树

终于说到正题了，我们先来给B-Tree正名，很多人将它翻译成B-树，因为有个B+树，其实这样是不对的。正确的翻译应是B树，中间的那个“-”是我们平时说的“杠”不是减，它是个连字符(hyphen)，所以不是B减树。我不知道别人怎么样，反正我刚开始是这么认为的。所以这个坑就填了吧。

说到B树和B+树，其实并不神秘，因为他们都是从二叉树转化而来的，就是个多路平衡查找树，就是孩子多了点而已。下面看看B树的特性：

假设有一个m阶的B树

1.每个节点最多拥有m-1个key

2.根节点key的个数[1,m-1]

再多就分裂了（insert一个7）：

3.根节点子节点数[2,m]

4. 所有节点的子节点数最大的数值是多少就称为多少阶

现在根节点，而且根节点的key已经是m-1=4了，而且已经有5个孩子了如果我们再插入一个18会发生什么

是的它是5阶的，所以它不能再分裂出一个孩子，只能增加深度了。

5.树里面的关键字都是严格按从小到大排序的，

6.除根节点和叶子节点外其他节点的都至少有m/2（向上取整）个子节点

7.所有叶子节点都位于同一层

为了方便我用个3阶的

再输入一个比13大的key就要分裂增加深度了，那么1和9这两个子树会怎么样？

插入节点过程：

过程是将遍历到13发现14比13大所以加到13后面，发现这个子树最多容纳m-1就是2个key，所以进行了分裂，13被放到根节点，子节点分裂成12和14两个子节点。然后根节点也经历了相同的操作，分裂成出两个子节点7和13，7>1&7<9所以1和9变成7的子树，同理13.

删除节点过程：

删除14这个节点，13这个节点就不满足除根节点和叶子节点外其他节点至少拥有m/2(cil)个子节点且13最大如果归并根节点那么就违反了右边的子节点12要比根节点大的原则，所以只能归并到12的右边。

但如果是这样，根节点左右两边深度不一样，违反了叶子节点都在同一层的原则。

于是会将7这个节点与根节点合并来降低左边子树的层数使左右相等。

最终的结果是：

推荐上面我用的这个工具，有插入删除的动画效果 ，还可以看其他数据结构。好吧这就是B树

B+树

B+树是B-树的一种进化，所以它具有B-树的特点。下面看看它独有的特性

有k个子结点的结点必然有k个关键码；
非叶结点仅具有索引作用，跟记录有关的信息均存放在叶结点中。
树的所有叶结点构成一个有序链表，可以按照关键码排序的次序遍历全部记录。
每个节点都会出现在
节点查询

查询数字3：

第一次I/O

第二次I/O

第三次I/O

B+树在非叶子节点上面不放数据，只放索引

展开全文
• 课件内容比较理论，所以打算问题导向的方式来总结课件内容。 什么是数据（Data）？什么是信息（Information）？Data和Information之间的...为什么要学习数据库系统？主要是因为基于文件处理很多弊端。这些弊端...
• 第三部分　数据库设计理论和方法 219 第10章　函数依赖和关系数据库的规范化 220 10.1　关系模式的非形式化设计准则 221 10.1.1　给予关系中的属性以清晰的语义 221 10.1.2　元组中的冗余信息和更新异常 ...
• 数据库系统基础:高级篇(第5版)(讲述数据库系统原理的经典教材) 基本信息 原书名： Fundamentals of Database Systems (5th Edition) 原出版社： Addison Wesley 作者： (美)Ramez Elmasri Shamkant B. Navathe [作...
• 一，SQL的苦恼 我观察了 865 个 SQL 入门者，...他们用学英语的方法，去SQL，结果发现什么不到，而且感觉越来越难，难。想想你为什么讨厌英语就明白了，每次翻开词典，从 abandon 起来，不到 10 页就...
• 认识数据库自然已经好几年了，大学时就有数据库原理的课程,毕业工作后写的程序大部分也要用到数据库。但大学时学习理论不知其所以然，工作后又只讲实践（小型系统，不需要考虑太多），所以理论与实践不能很好的...
• ## 数据库书籍推荐

千次阅读 2017-11-12 21:17:38
软件开发者编写代码，最终都是要处理数据，因此数据库是必备技能。...然后是数学公式密集的关系模型、关系代数，一连串不知道什么用却很难懂的范式……全完了，数据库咋用、咋编程都不知道，连SQL都只是一知半解。
• （索引的原理）　 为什么能够提高查询速度？ 索引就是通过事先排好序，从而在查找时可以应用二分查找等高效率的算法。 一般的顺序查找，复杂度为O(n)，而二分查找复杂度为O(log2n)。当n很大时，二者的效率相差...
• matlab有什么好玩的代码 title date categories tags README 2019-11-12 14:39:57 -0800 Blogs 今天(10/21/2017)突然发现我的github不能这样子，我完全把这玩意儿当成博客网站了，代码倒是没放多少 于是乎，打算把...
• 现在本科论文、硕士论文、博士论文都需要查重，每个阶段查重的要求都不一样。一般水平越高，查重率的要求越高。你知道硕士论文查重的原理吗？ 事实上，硕士论文查重的原理大家都知道，...一般而言，硕士论文都是用学
• ## 学好数据库，看这9本书就够了

万次阅读 多人点赞 2016-07-22 19:23:17
美团点评技术俱乐部 软件开发者编写代码，最终都是要处理数据，因此数据库是必备技能。...然后是数学公式密集的关系模型、关系代数，一连串不知道什么用却很难懂的范式……全完了，数据库咋用、咋编程都不知道
• MANDATORY、PROPAGATION_REQUIRES_NEW、PROPAGATION_NOT_SUPPORTED、PROPAGATION_NEVER、PROPAGATION_NESTED 分布式事务的理论基础：RPC定理、BASE理论、XA协议都是什么，原理是什么，有什么关联关系。 分布式事务的...
• 文章目录事务简介编程式事务与声明式事务事务原理事务配置事务拦截器事务创建事务执行事务传播性传播性哪些为什么需要传播性如何实现扩展事务隔离级别为什么需要隔离级别隔离级别哪些事务挂起与同步事务挂起事务...
• 数据库进行到底原理什么要主从服务器主从知识扩展Mysql主从服务搭建（1）建立时间同步环境（2）编译安装MySQL数据库（3）登录Master主服务器配置 20.0.0.21（4）登录salve 从服务器配置（5）验证 原理什么要...
• ​ 这一系列文章是根据北大肖臻老师的公开课《区块链技术与应用》整理的笔记，附上公开课的视频链接：区块链技术与应用 北大肖臻 由于看完视频后想要...的确，如果把它当数据库用的话是非常的慢，而且仅仅只能实...
• 　3.1.2 solaris下dbca创建数据库 　3.1.3 diy——solaris下手工创建数据库 　3.1.4 数据库创建疑难解析 　3.2 删除数据库 　3.2.1 数据库删除概述 　3.2.2 数据库删除——手工篇 　3.2.3 数据库删除——...
• 　3.1.2 solaris下dbca创建数据库 　3.1.3 diy——solaris下手工创建数据库 　3.1.4 数据库创建疑难解析 　3.2 删除数据库 　3.2.1 数据库删除概述 　3.2.2 数据库删除——手工篇 　3.2.3 数据库删除——...
• 最近在优化代码的时候，突然想起来TP5的数据库操作中有个cache，之前也过，印象里就是在缓存时间内，请求的速度会大大...首先让我感到疑惑的是，这个cache和我们平常的缓存Cache有什么区别？ 如果单单从功能上.
• Sam R．Alapati是世界顶尖的Oracle技术专家，一位经验丰富的数据库管理员，20多年从业经历。他拥有Oracle OCP DBA证书和HP UNIX System Administrator证书，曾经担任Oracle公司的高级顾问，并在AT&T、雷曼兄弟、...
• 题记： Elasticsearch研究一段时间了，现特将Elasticsearch相关核心知识、原理从初学者认知、学习的角度，从以下9个方面进行详细梳理。欢迎讨论…… 0. 带着问题上路——ES...1）用什么数据库好？(mysql、sybase...
• 能够根据了解的原理对实际的问题进行分析，解决问题，这样才能对技术深入的理解，在工作中遇到复杂问题时，才能解决。 所以我发起了这个项目，允许是自己自己的复习巩固，只需是将这些自己写的解答开源出来分享给...

...