目录
B+树搜索过程与B树的查询过程没有区别。但实际上有三点不一样:(B+Tree优
6)红黑树任意节点其左右子树最多相差2层红节点(自动相对平衡)
红黑树任意节点其左右子树最多相差2层红节点。所以大致上是平衡的。
数据结构:
程序 = 数据结构 + 算法 (一段逻辑性代码);
典型实例示例容器:
(1)ArrList, LinkedList,hashMap;
(2)这些容器的底层是: ArrList(数组),LinkedList(链表),hashMap(哈希表);你了解过哪些数据结构:
数组,链表,队列,栈,树;这些数据结构的优缺点:
数组:
就是一块大内存空间中有一块块小的内存空间就和船底的隔板一样,我们将来就可以把数据存进去,他的优点查询快在于数据在每一块小空间,可以通过下标快速定位到要查询的位置,缺点在与增删慢,原因在于如果原数组的内存不够用,就会创建更大新的数组并把原数组的数据拷贝进新数组完成后还需要销毁原数组所以慢.
链表:
每一个链表都保存了前后节点的地址,增加或者删除都不会影响他的速度,不管增加或者删除他都会去找前面或者后面的节点相连,但是查询比较慢因为都需要遍历所有数据链,查两端除外.
队列:顺序消费先进先出,后进后出.
比如火车:
栈:
jvm,方法栈,方法执行都是方法栈中执行的,最先进入的后出,最后进入的先出.
杯子里放石头:
树: 实例,mysql使用的为B+Tree
二叉树:
特点:
(把每个蓝色圆球看作一个节点)由图可以看出三点:
1.一个主节点最多有两个子节点 2.每一个节点都只能存一个关键字 3.每一个主节点下的,两个子节点都会和主节点进行对比来分配位置,左节点关键字永远小于,右节点关键字;二叉树适合做磁盘存储吗:
假设:把本地磁盘(好处:在于可以保证持久性)现在把磁盘中的所有数据,都放到二叉树中作为存储,现在应用程序作为内存需要获取二叉树中数据的话,应该分层;一层一层的拿取二叉树中的数据,在结合二叉树的特点来说,一个主节点最多分两个子节点;那在面对百万级大数据量的情况下,树的层数会越来越高,我们把数据从二叉树中读取到应用内存中;使用的方法是以IO流的方式进行读取的,IO很浪费性能,随着树越来越高IO次数也会越大,性能也会越低,所以不适合做磁盘存储.
缺点:
①由于二叉树只有最多两个子节点,因此不适合存储大量的数据
②二叉树无法动态维护树的平衡,操作不当会造成树的倾斜
B-Tree:
(二叉树进化版)是一种多路搜索树,可以让原来的二叉树变得“矮胖”---> 高度降低 浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所 示)和指针(黄色所示),指针指向的是存放索引数据的磁盘页,红点表示是指向卫 星数据的指针,卫星数据就是数据库中一条实际数据记录。 如磁盘块1包含数据项17和35,包含指针P1、P2、P3,P1表示小于17的磁盘块,P2 表示在17和35之间的磁盘块,P3表示大于35的磁盘块。b-树的查找过程:
如图所示,如果要查找数据项29,那么首先会把磁盘块1由磁盘加载到内存,此时发 生一次IO,在内存中用二分查找确定29在17和35之间,锁定磁盘块1的P2指针,内 存时间因为非常短(相比磁盘的IO)可以忽略不计 通过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,发生第二次IO,29 在26和30之间,锁定磁盘块3的P2指针 通过指针加载磁盘块8到内存,发生第三次IO,同时内存中做二分查找找到29,结束 查询,总计三次IO。 真实的情况是,3层的Btree可以表示上百万的数据,如果上百万的数据查找只需要三 次IO,性能提高将是巨大的。思考:
层数的降低,导致了IO次数的减少,保证了性能,适合做磁盘存储;但是从用户时间稳定度这个问题来讲的话,由上图来说明一个新问题的话;我们就是希望从根节点查询某个关键字哪个关键字是第一条也好,还是查哪个关键字向下查询一百万条是哪个关键字也好;查询到关键字时,查询的这两条关键字,返回给我们的时间是一样的都是0.5秒,不管查询远近时间都是相同的,但是B-Tree他做不到,B-Tree他的特点在于定位的位置是否在于我们进的节点上,如果很远就很耗费时间了,保证不了这个时间的稳定性距离越远,时间耗费的时间越长.
特点:
①他是由二叉树的基础之上演变而来,B树也叫多路搜索树
②B树的每个节点保存多个数据,这样的话我们可以在使用很少的io流次数的情况下就能够读取到数据
③B树适合磁盘存储
缺点:
①不适合范围查找
②B树查找元素存在最好和最坏的情况,因此树的时间复杂度很不稳定
B+Tree:
(B-Tree升级版)是B-树的变体,也是一种多路搜索树,比B-tree效率更高: 1、根节点和分支节点中不保存索引数据,只用于索引,所有数据都保存在叶子节点 中。 2、所有分支节点和根节点都同时存在于子节点中且在子节点元素中是最大或者最小 的元素。 3、叶子节点会包含所有的索引数据(关键字),以及指向数据记录的指针,并且叶 子节点本身是根据关键字的大小从小到大顺序链接。 更直观的图: 1、红点表示是指向卫星数据的指针,指针指向的是存放实际数据的磁盘页,卫星数 据就是数据库中一条数据记录。 2、叶子节点中还有一个指向下一个叶子节点的next指针,所以叶子节点形成了一个 有序的链表,方便遍历B+树。B+树搜索过程与B树的查询过程没有区别。但实际上有三点不一样:(B+Tree优
势)
1、首先B+树的中间节点不存储卫星数据,所以同样大小的磁盘页可以容纳更多的节 点元素,如此一来,相同数量的数据下,B+树就相对来说要更加矮胖些,磁盘IO的 次数更少。 2、由于只有叶子节点才保存卫星数据,B+树每次查询都要到叶子节点;而B-树每次 查询则不一样,最好的情况是根节点,最坏的情况是叶子节点,没有B+树稳定。 3、B+树叶子节点形成有序链表, 范围 查找性能更优 如:范围查找3-11的数据: B-树是逐个查询该范围内的数据; B+树范围查找3-11的过程如下图: 先从上到下找到下限元素3,然后通过链表指针,依次遍历得到元素5/6/8/9/11; 如此一来,就不用像B树那样一个个元素进行查找。 Mysql的MyIsam存储引擎和Innodb存储引擎的索引的数据结构都是 B+树结构, ES的倒排索引底层结构也是B+tree等等。简述B+Tree:
①根节点与分支节点不保存数据,只保存数据的索引,数据只存在叶子节点上,相对稳定②叶子节点之间会形成一个有序的双向链表,我们只需要找到链表两端数据,就可以获取到范围数据,适合范围查询
不经历IO的情况下,可以直接使用二叉树吗?
如果二叉树的所有非叶子结点的左右子树的结点数目均保持差不多(平衡),那么二叉树的 搜索性能逼近二分查找,效率还算可以;但是二叉树在经过多次插入与删除后,有可能导致 不同的结构: 右边也是一个二叉树,但它的搜索性能已经是线性的了,查询效率跟链表差不多,凸显不出 二分查找的优势,所以使用二叉树还要考虑尽可能让二叉树保持左图的结构,和避免右图的 结构,也就是所谓的二叉 “平衡” 问题;红黑树:
红黑树又称R-B Tree,全称是Red-Black Tree, 红黑树是一种含有红黑结点并 能 自平衡的二叉查找树 。红黑树具有以下五个特性:
1)节点要么是红色要么是黑色 2)根节点是黑色 3)如果一个结点是红色的,则它的所有子结点必须是黑色的 4)从一个结点到该结点的子孙结点的所有路径上包含相同数目的黑结点。 5)每个叶子的节点都是黑色的空节点(NULL)6)红黑树任意节点其左右子树最多相差2层红节点(自动相对平衡)
缺点:
数据量大,树高度会很高导致查找速度变慢
红黑树任意节点其左右子树最多相差2层红节点。所以大致上是平衡的。
当我们在对红黑树进行插入和删除等操作时,对树做了修改 可能会违背红黑树 的性质。 为了保持红黑树的性质,红黑树对相关节点做一系列的调整,通过对树进行 变色 和 旋转 (例如 左旋 和 右旋 操作),即修改树中某些结点的颜色及指针结构,以达 到对红黑树进行插入、删除结点等操作时,红黑树依然能保持它特有的性质(五 点性质)。 无论是左旋还是右旋,都不会超过三次树旋转 。 对于JDK中集合类:TreeMap、TreeSet以及 jdk1.8之后 的 HashMap底层结构都使用了 红 黑树。 注: B-Tree、B+Tree是为了减少磁盘IO提高搜索效率; 红黑树多用于内存存储,不经历IO,只要保证二叉树的平衡就保证了搜索效 率。hashmap的原理:
特点:
底层使用hash表数据结构,即数组+(链表或者红黑树)
添加数据时,计算key的值确定元素在数组中的下标,key相同则替换不同则存入链表或红黑树中
获取数据通过key的hash计算数组下标获取元素
hashmap的put流程:
判断table是否为空,如果空的话,会先调用resize扩容;
根据当前key的 hash 值,通过 (n - 1) & hash计算应当存放在数组中的下标 index ;
查看链表是否存在数据,没有数据就构造一个 Node 节点存放在链表中;
存在数据,说明发生了 hash 冲突,继续判断 key 是否相等,如果相等,用新的 value 替换原数据;
如果不相等,判断当前节点类型是不是树型节点,如果是树型节点,创建树型节点插入红黑树中;
如果不是树型节点,则采用尾插法,把新节点加入到链表尾部;判断链表长度是否大于 8, 大于的话链表转换为红黑树;
插入完成之后判断当前节点数是否大于扩容因子阈值,如果大于开始扩容为原数组的二倍。
扩容时机:
当数据的容量到达总容量的0.75的时候就会扩容;
为什么不上来就树化:
红黑树使用TreeNode比链表使用的node内存占用要大;
为了防止dos攻击解决超长链表出现的特殊手段,几率比较小.
什么时候会退化成链表:
当树的元素小于6个的时候会退化成链表.
为什么hashmap每次扩容都是2的n次幂:
只有2的n次幂计算索引的时候才能把取模运算转换为位运算.
为什么选择0.75作为扩容因子:
0.75只不过说是在时间上和空间上做了一个比较好的权衡;
大于0.75说明扩容机会少,这样会形成超长链表;
小于0.75说明扩容机会多,链表减少,但是空间变大.
hashmap1.8和1.7的区别:
JDK1.7用的是头插法,而JDK1.8及之后使用的都是尾插法,JDK1.7是用单链表进行的纵向延伸, 当采用头插法时多线程环境会容易出现扩容死链(ABA)问题。
但是在JDK1.8之后是因为加入了红黑树使用尾插法,能够避免出现逆序且链表死循环的问题。
concurrentHashMap的原理:
使用:
1.7版本:
使用segment分段锁实现concurrentHashMap,每个segment的下面都有一个hashtable,这样比单个hashtable并发量要高,使用segment
锁的方式最大支持16个并发,但是需要经过两次hash hash时间太长
1.8版本:
去除segment分段锁的概念,改用cas+sync的方式实现concurrentHashMap,当多个线程锁定一个数组的下标的时候,让多个线程进行cas
自旋抢锁,当抢到锁之后 直接进行写的操作,写的操作不能并发 需要结合sync完成数据的写入