手写一个跳表,跪了。。。

尼恩说在前面

在40岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团、蚂蚁、得物的面试资格,遇到很多很重要的相关面试题:

手写一个跳表?

redis为什么用跳表不用B+树吗?

最近有小伙伴在蚂蚁、面试字节,都问到了相关的面试题,可以说是逢面必问。

小伙伴没有系统的去梳理和总结,所以支支吾吾的说了几句,面试官不满意,面试挂了。

所以,尼恩给大家做一下系统化、体系化的梳理,使得大家内力猛增,可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”,然后实现”offer直提”。

当然,这道面试题,以及参考答案,也会收入咱们的 《尼恩Java面试宝典PDF》V175版本,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。

《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》的PDF,请到文末公号【技术自由圈】获取

本文作者:

  • 第一作者 Moen (负责写初稿 )
  • 第二作者 尼恩 (40岁老架构师, 负责提升此文的 技术高度,让大家有一种 俯视 技术的感觉)

本文目录

- 尼恩说在前面

- 什么是跳表

  • 对有序链表查询优化过程

  • 跳表的特点

  • 随机层数在性能上的优化

- Redis中ZSet是怎么实现的

  • Redis中ZSet底层数据结构

  • zpilist和skiplist之间何时进行转换

  • Redis对跳表的实现及改进与优化

- 跳表与平衡树、哈希表的比较

- Redis为什么使用跳表而不用平衡树

- 跳表与B+树的比较

- 基于Java实现跳表

- 总结

- 说在最后:有问题找老架构取经

什么是跳跃表(skiplist)?

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。

Redis使用跳跃表作为有序集合键的底层实现之一,如果一个有序集合包含的元素数量比较多,又或者有序集合中元素的成员(member)是比较长的字符串时,Redis就会使用跳跃表来作为有序集合键的底层实现。

跳跃表(skiplist)是一种随机化的数据, 由 William Pugh 在论文《Skip lists: a probabilistic alternative to balanced trees》中提出, 跳跃表以有序的方式在层次化的链表中保存元素, 效率和平衡树媲美 —— 查找、删除、添加等操作都可以在对数期望时间下完成, 并且比起平衡树来说, 跳跃表的实现要简单直观得多。

跳表是一种带多级索引的链表,本质上也是一种查找结构, 用于解决算法中的查找问题,即根据给定的key,快速查找它所在的位置(value)。跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。

跳跃表支持平均0 (1ogN)、最坏O(N) 复杂度的节点查找,还可以通过顺序性操作来批量处理节点。

在大部分情况下,跳跃表的效率可以和平衡树相媲美,并且因为跳跃表的实现比平衡树要来得更为简单,所以有不少程序都使用跳跃表来代替平衡树。

从直观理解的维度来绘制,一个3层索引结构的skiplist , demo 示意图如下:

在这里插入图片描述

此图来自于 尼恩和小伙伴们的 本系列文章的第一篇:

字节面试: Mysql为什么用B+树,不用跳表?

换一种方式,从编码实现的维度来绘制, 一个3层索引结构的skiplist 的 demo 示意图如下:
在这里插入图片描述

对有序链表的查询优化过程

对于一个单链表来讲,即便链表中存储的数据是有序的,如果我们要想在其中查找某个数据,也只能从头到尾遍历链表。

这样查找效率就会很低,时间复杂度会很高,是 O(n)。

在这里插入图片描述

step1:建立一级索引

对链表中每两个节点建立第一级索引 , 大致的方法如下:

假如为每相邻两个节点增加一个指针,让指针指向下下个节点 ,如下图:
在这里插入图片描述

这样所有新增加的指针连成了一个新的链表(上图中第一级索引指向的链表),

但第一级索引 包含的节点个数只有原来的一半(上图中是5, 9, 16)。

请注意:实际应用中的skiplist每个节点应该包含key和value两部分。

这里为了方便描述,并没有具体区分key和value,但实际上列表中是按照key进行排序的,查找过程也是根据key进行比较。

现在当我们想查找数据的时候,查找的过程如下:

  • 可以先第一级索引层遍历进行查找。

  • 当碰到比待查数据大的节点时,再回到原来的链表中进行查找。

比如,我们想查找14,查找的路径是沿着下图中标红的指针所指向的方向进行的:
在这里插入图片描述

  • 14首先和5比较,再和9比较,比它们都大,继续向后比较。
  • 但14和16比较的时候,比16要小,因此回到下面的链表(原链表),与13比较。
  • 14比13要大,沿下面的指针继续向后和16比较。14比16小,说明待查数据14在原链表中不存在,而且它的插入位置应该在13和16之间。

在这个查找过程中,由于新增加的指针,我们不再需要与链表中每个节点逐个进行比较了。

通过第一层索引,我们发现:需要比较的节点数大概只有原来的一半

step2:建立二级索引

利用同样的方式,我们可以在第一层新产生的链表上,继续为每相邻的两个节点增加一个指针,从而产生第二层链表,这一层链表是第二层索引。

第二层索引如下图:
在这里插入图片描述

在这个新的第二层索引结构上,如果我们还是查找14,

那么沿着最上层链表首先要比较的是9,发现14比9大,接下来我们就知道只需要到9的后面去继续查找,从而一下子跳过了9前面的所有节点。

从上述过程我们看出,加了一层索引之后,查找一个结点需要遍历的结点个数减少了,也就是说查找效率提高了。

跳表的特点

跳表是一种动态数据结构,支持快速地插入、删除、查找操作,时间复杂度都是 O(logn)。

跳表的空间复杂度是 O(n)。不过,跳表的实现非常灵活,可以通过改变索引构建策略,有效平衡执行效率和内存消耗。

跳表即表示跳跃表,指的就是除了最下面第1层链表之外,它会产生若干层稀疏的链表,这些链表里面的指针故意跳过了一些节点(而且越高层的链表跳过的节点越多)。这就使得我们在查找数据的时候,可由高层链表到底层链表逐层降低,在这个过程中,跳过了一些节点,从而也就加快了查找速度。

跳表使用空间换时间的设计思路,通过构建多级索引来提高查询的效率,实现了基于链表的“二分查找”。即上面每一层链表的节点个数,是下面一层的节点个数的一半。

随机层数在插入性能上的优化

新插入一个节点之后,就会打乱上下相邻两层链表上节点个数严格的2:1的对应关系。

如果要维持这种对应关系,就必须把新插入的节点后面的所有节点(也包括新插入的节点)重新进行调整,这会让时间复杂度重新蜕化成O(n)。删除数据也有同样的问题。

skiplist为了避免这一问题,它不要求上下相邻两层链表之间的节点个数有严格的对应关系,而是为每个节点随机出一个层数(level)。

比如,一个节点随机出的层数是3,那么就把它链入到第1层到第3层这三层链表中。

为了表达清楚,下面通过多个图的方式,一步一步展示了插入操作如何通过随机层数,决定一个节点要插入跳表的哪几层的问题:

在这里插入图片描述

上图, 插入9的时候, 随机层数为2,就需要插入2层:

  • 在原始链表层,插入 9。
  • 和第一个索引层,插入 9。
    在这里插入图片描述

上图, 插入5的时候, 随机层数为4,就需要插入4层:

  • 在原始链表层,插入 5。
  • 和第一个索引层,插入 5。
  • 和第二个索引层,插入 5。
  • 和第三个索引层,插入 5。
    在这里插入图片描述

上图, 插入1的时候, 随机层数为1,就需要插入1层:

  • 在原始链表层,插入 1。

在这里插入图片描述

上图, 插入21的时候, 随机层数为3,就需要插入3层:

  • 在原始链表层,插入 21。
  • 和第一个索引层,插入 21。
  • 和第二个索引层,插入 21。
    在这里插入图片描述

上图, 插入6的时候, 随机层数为1,就需要插入1层:

  • 在原始链表层,插入 1。
    在这里插入图片描述

上图, 插入16的时候, 随机层数为1,就需要插入1层:

  • 在原始链表层,插入 1。

从上面skiplist的创建和插入过程可以看出,每一个节点的层数(level)是随机出来的,而且新插入一个节点不会影响其它节点的层数。

skiplist 在插入的效率比较高,插入操作只需要修改插入节点前后的指针,而不需要对很多节点都进行调整。这就降低了插入操作的复杂度。

与之相比,B+树在插入的时候要维护树的平衡,插入过程中会发生 page 的分裂, 插入的性能就会差很多,具体请见:

字节面试: Mysql为什么用B+树,不用跳表?

插入的性能很高,这是skiplist的一个很重要的特性,这让它在插入性能上明显优于平衡树的方案。

skiplist执行插入,需要计算随机数,是一个很关键的过程,它对skiplist的统计特性有着很重要的影响。

这并不是一个普通的服从均匀分布的随机数,它的计算过程如下:

  • 首先,每个节点肯定都有第1层指针(每个节点都在第1层链表里)。
  • 如果一个节点有第i层(i>=1)指针(即节点已经在第1层到第i层链表中),那么它有第(i+1)层指针的概率为p。
  • 节点最大的层数不允许超过一个最大值,记为MaxLevel。

跳表查找元素的过程

刚刚创建的这个skiplist总共包含4层链表,现在假设我们在它里面依然查找14,下图给出了查找路径:
在这里插入图片描述

Redis中ZSet是怎么实现的

Redis中 ZSet(Sorted Set)是Redis中的一种特殊数据结构,它内部维护一个有序的dict,这个字典dict包括两个属性:成员(member)、分数(score,double类型)。

这个ZSet 结构价值很大:

  • 可以帮助我们实现排行榜、
  • 朋友圈点赞等记分类型的排行数据,
  • 以及实现延迟队列、限流。

Redis中ZSet实现包括多种结构,有ziplist(压缩列表)、skiplist(跳表)、listpack(紧凑列表,在Redis5.0中新增)。

listpack是为了替代ziplist,在Redis7.0中已经彻底弃用ziplist。

Redis中Zset底层数据结构

前面提到过,Redis中的ZSet是在dict、ziplist(listpack)、skiplist基础上构建起来的:

  • 当数据较少时,sorted set是由一个ziplist来实现的。
  • 当数据多的时候,sorted set是由一个叫zset的数据结构来实现的,这个zset包含一个dict + 一个skiplist。

当数据多的时候,zset包含一个dict + 一个skiplist:

  • dict用来查询数据到分数(score)的对应关系,

  • 而skiplist用来根据分数查询数据(可能是范围查找)。

ziplist就是由很多数据项组成的一大块连续内存。由于sorted set的每一项元素都由数据和score组成,因此,当使用zadd命令插入一个(数据, score)对的时候,底层在相应的ziplist上就插入两个数据项:数据在前,score在后。

ziplist的主要优点是节省内存,但它上面的查找操作只能按顺序查找(可以正序也可以倒序)。因此,sorted set的各个查询操作,就是在ziplist上从前向后(或从后向前)一步步查找,每一步前进两个数据项,跨域一个(数据, score)对。

ZSet中的字典和跳表布局

其中skiplist用来实现有序集合,其中每个元素按照其分值大小在跳表中进行排序,跳表的插入、删除和查找操作时间复杂度都是O(1),能够保证较好的性能。

dict用来实现元素到分值的映射,其中元素作为键,分值作为值。哈希表的插入、删除和查找操作的时间复杂度都是O(1),具备非常高的性能。

redis的zpilist和skiplist之间何时进行转换

随着数据的插入,Redis底层会将ziplist转换成skiplist,那么到底插入多少数据才会转换,下面进行分析。

本文主要涉及两个Redis配置(在redis.conf中的ADVANCED CONFIG部分)

Similarly to hashes and lists, sorted sets are also specially encoded in
order to save a lot of space. This encoding is only used when the length and
elements of a sorted set are below the following limits:

Redis7.0之前的配置

zset-max-ziplist-entries 128 // ziplist中元素个数
zset-max-ziplist-value 64 // ziplist中元素大小

Redis7.0之后的配置

zset-max-listpack-entries 128 // listpack中元素个数
zset-max-listpack-value 64 // listpack中元素大小

在Redis中,以上两个配置说明,只有当长度和排序集的元素个数,同时满足以下两个条件时会使用ziplist(listpack)作为其内部表示,具体条件如下:

  • 元素数量少:集合中的元素数量必须小于配置的阈值:zset-max-ziplist-entries(zset-max-listpack-entries)

  • 插入数据长度:当ZSet中插入的任意一个数据的长度超过64的时候。

总结:当元素数量少于128,每个元素的长度都小于64字节的时候,Redis使用ziplist(listpack),否则使用是skiplist。

/* Convert the sorted set object into a listpack if it is not already a listpack* and if the number of elements and the maximum element size and total elements size* are within the expected ranges. */
void zsetConvertToListpackIfNeeded(robj *zobj, size_t maxelelen, size_t totelelen) {if (zobj->encoding == OBJ_ENCODING_LISTPACK) return;zset *zset = zobj->ptr;if (zset->zsl->length <= server.zset_max_listpack_entries &&maxelelen <= server.zset_max_listpack_value &&lpSafeToAdd(NULL, totelelen)){zsetConvert(zobj,OBJ_ENCODING_LISTPACK);}
}

Redis对跳表的实现及改进与优化

Redis 的跳跃表是由 redis.h/zskiplistNode和 redis.h/zskiplist 两个结构定义,其中 zskiplistNode 用于表示跳跃节点,而 zskiplist 结构则用于保存跳跃表节点的相关信息,比如节点的数量以及指向表头节点和表尾节点的指针等等。

在这里插入图片描述

上图最左边的是 zskiplist 结构,该结构包含以下属性:

  • header:指向跳跃表的表头节点

  • tail:指向跳跃表的表尾节点

  • level:记录目前跳跃表内,层数最大的那个节点层数(表头节点的层数不计算在内)

  • length:记录跳跃表的长度,也就是跳跃表目前包含节点的数量(表头节点不计算在内)

位于 zskiplist 结构右侧是四个 zskiplistNode 结构,该结构包含以下属性:

  • 层(level):节点中用 L1、L2、L3 等字样标记节点的各个层,L1 代表第一层,L2 代表第二层,以此类推。每个层都带有两个属性:前进指针和跨度。前进指针用于访问位于表尾方向的其它节点,而跨度则记录了前进指针所指向节点和当前节点的距离。

  • 后退(backward)指针:节点中用 BW 字样标识节点的后退指针,它指向位于当前节点的前一个节点。后退指针在程序从表尾向表头遍历时使用。

  • 分值(score):各个节点中的 1.0、2.0 和 3.0 是节点所保存的分值。在跳跃表中,节点按各自所保存的分值从小到大排列。

总结起来,Redis中的skiplist跟前面介绍的经典的skiplist相比,有如下不同:

  • 分数(score)允许重复,即skiplist的key允许重复。这在最开始介绍的经典skiplist中是不允许的。
  • 在比较时,不仅比较分数(相当于skiplist的key),还比较数据本身。在Redis的skiplist实现中,数据本身的内容唯一标识这份数据,而不是由key来唯一标识。另外,当多个元素分数相同的时候,还需要根据数据内容来进字典排序。
  • 第1层链表不是一个单向链表,而是一个双向链表。这是为了方便以倒序方式获取一个范围内的元素。
  • 在skiplist中可以很方便地计算出每个元素的排名(rank)。

跳表与平衡树、哈希表的比较

  • skiplist和各种平衡树(如AVL、红黑树等)的元素是有序排列的,而哈希表不是有序的。因此,在哈希表上只能做单个key的查找,不适宜做范围查找。所谓范围查找,指的是查找那些大小在指定的两个值之间的所有节点。
  • 在做范围查找的时候,平衡树比skiplist操作要复杂。在平衡树上,我们找到指定范围的小值之后,还需要以中序遍历的顺序继续寻找其它不超过大值的节点。如果不对平衡树进行一定的改造,这里的中序遍历并不容易实现。而在skiplist上进行范围查找就非常简单,只需要在找到小值之后,对第1层链表进行若干步的遍历就可以实现。
  • 平衡树的插入和删除操作可能引发子树的调整,逻辑复杂,而skiplist的插入和删除只需要修改相邻节点的指针,操作简单又快速。
  • 从内存占用上来说,skiplist比平衡树更灵活一些。一般来说,平衡树每个节点包含2个指针(分别指向左右子树),而skiplist每个节点包含的指针数目平均为1/(1-p),具体取决于参数p的大小。如果像Redis里的实现一样,取p=1/4,那么平均每个节点包含1.33个指针,比平衡树更有优势。
  • 查找单个key,skiplist和平衡树的时间复杂度都为O(log n),大体相当;而哈希表在保持较低的哈希值冲突概率的前提下,查找时间复杂度接近O(1),性能更高一些。所以我们平常使用的各种Map或dictionary结构,大都是基于哈希表实现的。
  • 从算法实现难度上来比较,skiplist比平衡树要简单得多。

Redis为什么使用跳表而不使用平衡树

关于这个问题,Redis作者是这么说的:

There are a few reasons:
1、They are not very memory intensive. It’s up to you basically. Changing parameters about the probability of a node to have a given number of levels will make then less memory intensive than btrees.
2、A sorted set is often target of many ZRANGE or ZREVRANGE operations, that is, traversing the skip list as a linked list. With this operation the cache locality of skip lists is at least as good as with other kind of balanced trees.
3、They are simpler to implement, debug, and so forth. For instance thanks to the skip list simplicity I received a patch(already in Redis master) with augmented skip lists implementing ZRANK in O(log(N)). It required little changes to the code.

主要是从内存占用、对范围查找的支持、实现难易程度这三方面总结的原因,简单翻译如下:

  • 它们不是非常内存密集型的。基本上由你决定。改变关于节点具有给定级别数的概率的参数将使其比 btree 占用更少的内存。

  • Zset 经常需要执行 ZRANGE 或 ZREVRANGE 的命令,即作为链表遍历跳表。通过此操作,跳表的缓存局部性至少与其他类型的平衡树一样好。

  • 它们更易于实现、调试等。例如,由于跳表的简单性,我收到了一个补丁(已经在Redis master中),其中扩展了跳表,在 O(log(N) 中实现了 ZRANK。它只需要对代码进行少量修改。

关于上述观点,做几点补充如下:

  • 从内存占用上来比较,跳表比平衡树更灵活一些。平衡树每个节点包含 2 个指针(分别指向左右子树),而跳表每个节点包含的指针数目平均为 1/(1-p),具体取决于参数 p 的大小。如果像 Redis里的实现一样,取 p=1/4,那么平均每个节点包含 1.33 个指针,比平衡树更有优势。

  • 在做范围查找的时候,跳表比平衡树操作要简单。在平衡树上,我们找到指定范围的小值之后,还需要以中序遍历的顺序继续寻找其它不超过大值的节点。如果不对平衡树进行一定的改造,这里的中序遍历并不容易实现。而在跳表上进行范围查找就非常简单,只需要在找到小值之后,对第 1 层链表进行若干步的遍历就可以实现。

  • 从算法实现难度上来比较,跳表比平衡树要简单得多。平衡树的插入和删除操作可能引发子树的调整,逻辑复杂,而跳表的插入和删除只需要修改相邻节点的指针,操作简单又快速。

跳表与B+树的比较

  • 相同点:skiplist和B+树的最下面一层,都包含了所有数据,且都是有序的,适合用于范围查询。

  • 不同点:

    • B+树本质上是一种多叉平衡二叉树。当数据库表不断插入新的数据时,为了维持B+树的平衡,B+树会不断分裂调整数据页,来保证B+树的子树们的高度层级尽量一致(一般最多差一个层级)。适合读多写少的场景。(存储引擎RocksDB内部使用了跳表,对比使用B+树的innodb,虽然写性能更好,但读性能属实差了些。)

    • skiplist在新增/删除数据时,依靠随机函数,即可确定是否需要向上添加索引,达到一个二分的效果,无需平衡数据结构,少了旋转平衡的开销。

    • skiplist占用更少的内存,且更容易实现、调试。

  • B+树是多叉平衡搜索树,只需要3层左右就能存放2kw左右的数据,同样情况下跳表则需要24层左右,假设层高对应磁盘IO,那么B+树的读性能会比跳表要好,因此mysql选了B+树做索引。

  • redis的读写全在内存里进行操作,不涉及磁盘IO,同时跳表实现简单,相比B+树、AVL树、少了旋转树结构的开销,因此redis使用跳表来实现ZSET,而不是树结构。

总之:

  • B+树是 “磁盘友好” 型的 数据机构,适合于 DB。

  • 跳表 是 “内存友好” 型的数据结构, 适合于 Cache。

字节的真题:手写一个跳表

第一步:定义好 跳表的节点

每一个节点, 都带着一个 指针数组,

一个最大16层的跳表,每一个node结构 包含一个 规模为16大小的指针数组

在这里插入图片描述

第二步:定义好 生产 随机层数的方法

// 理论来讲,一级索引中元素个数应该占原始数据的 50%,二级索引中元素个数占 25%,三级索引12.5% ,一直到最顶层。// 因为这里每一层的晋升概率是 50%。对于每一个新插入的节点,都需要调用 randomLevel 生成一个合理的层数。// 该 randomLevel 方法会随机生成 1~MAX_LEVEL 之间的数,且 ://        50%的概率返回 1//        25%的概率返回 2//      12.5%的概率返回 3 ...private int randomLevel() {int level = 1;while (Math.random() < SKIPLIST_P && level < MAX_LEVEL)level += 1;return level;}

第三步:定义好 数据插入的方法

在这里插入图片描述

第四步:定义好 数据删除的方法

在这里插入图片描述

写到这里,字节的offer到手

40岁老架构师尼恩提示大家, 搞定 手写跳表 也很容易的哦。

锁定5000页《尼恩Java面试宝典》 ,大厂机会,滚滚而来。

总结

Redis 中的有序集合支持的核心操作主要有下面这几个:

  • 插入一个数据;

  • 删除一个数据;

  • 按照区间查找数据(比如查找值在[100, 356]之间的数据);

  • 迭代输出有序序列。

其中,插入、删除、查找以及迭代输出有序序列这几个操作,红黑树也可以完成,时间复杂度跟跳表是一样的。但是,按照区间来查找数据这个操作,红黑树的效率没有跳表高。

对于按照区间查找数据这个操作,跳表可以做到 O(logn) 的时间复杂度定位区间的起点,然后在原始链表中顺序往后遍历就可以了。这样做非常高效。

当然,Redis 之所以用跳表来实现有序集合,还有其他原因,比如,跳表更容易代码实现。虽然跳表的实现也不简单,但比起红黑树来说还是好懂、好写多了,而简单就意味着可读性好,不容易出错。还有,跳表更加灵活,它可以通过改变索引构建策略,有效平衡执行效率和内存消耗。

不过,跳表也不能完全替代红黑树。因为红黑树比跳表的出现要早一些,很多编程语言中的 Map 类型都是通过红黑树来实现的。我们做业务开发的时候,直接拿来用就可以了,不用费劲自己去实现一个红黑树,但是跳表并没有一个现成的实现,所以在开发中,如果你想使用跳表,必须要自己实现。

说在最后:有问题找老架构取经

跳表 、B+ 相关的面试题,是非常重要的面试题。

高级岗位、大厂岗位,这个必备。

此文,是 尼恩团队写的 跳表 、B+ 本系列文章的第二篇,两篇文章结合使用效果更佳, 第一篇如下:

字节面试: Mysql为什么用B+树,不用跳表?

手写跳表? redis为何要用跳表?

如果能按照以上的内容,对答如流,如数家珍,基本上 面试官会被你 震惊到、吸引到。

最终,让面试官爱到 “不能自已、口水直流”。offer, 也就来了。

在面试之前,建议大家系统化的刷一波 5000页《尼恩Java面试宝典》V174,在刷题过程中,如果有啥问题,大家可以来 找 40岁老架构师尼恩交流。

另外,如果没有面试机会,可以找尼恩来帮扶、领路。

尼恩已经指导了大量的就业困难的小伙伴上岸,前段时间,帮助一个40岁+就业困难小伙伴拿到了一个年薪100W的offer,小伙伴实现了 逆天改命 。
II9lw)

手写跳表? redis为何要用跳表?

如果能按照以上的内容,对答如流,如数家珍,基本上 面试官会被你 震惊到、吸引到。

最终,让面试官爱到 “不能自已、口水直流”。offer, 也就来了。

在面试之前,建议大家系统化的刷一波 5000页《尼恩Java面试宝典》V174,在刷题过程中,如果有啥问题,大家可以来 找 40岁老架构师尼恩交流。

另外,如果没有面试机会,可以找尼恩来帮扶、领路。

尼恩已经指导了大量的就业困难的小伙伴上岸,前段时间,帮助一个40岁+就业困难小伙伴拿到了一个年薪100W的offer,小伙伴实现了 逆天改命 。

尼恩技术圣经系列PDF

  • 《NIO圣经:一次穿透NIO、Selector、Epoll底层原理》
  • 《Docker圣经:大白话说Docker底层原理,6W字实现Docker自由》
  • 《K8S学习圣经:大白话说K8S底层原理,14W字实现K8S自由》
  • 《SpringCloud Alibaba 学习圣经,10万字实现SpringCloud 自由》
  • 《大数据HBase学习圣经:一本书实现HBase学习自由》
  • 《大数据Flink学习圣经:一本书实现大数据Flink自由》
  • 《响应式圣经:10W字,实现Spring响应式编程自由》
  • 《Go学习圣经:Go语言实现高并发CRUD业务开发》

……完整版尼恩技术圣经PDF集群,请找尼恩领取

《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》PDF,请到下面公号【技术自由圈】取↓↓↓

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

技能篇:如何批量替换文件名称 一招批量替换文件名

在日常生活和工作中&#xff0c;我们经常需要处理大量的文件&#xff0c;而文件名的设置对于文件的管理和查找至关重要。一个清晰、有序的文件名能够帮助我们快速找到所需的文件&#xff0c;提高工作效率。然而&#xff0c;随着时间的推移和项目的增多&#xff0c;我们可能需要…

【JS】JavaScript 中的原型与原型链

JavaScript 中的原型与原型链 原型1 函数中 prototype 指向原型对象2 对象中 __proto__ 指向原型对象3 原型对象中 constructor 指向构造函数4 __proto__ 与 [[Prototype]] 的关系5 所有非空类型数据&#xff0c;都具有原型对象6 new运算符做了哪些事情 原型链1 举个栗子1.1 直…

使用有道bce-embedding-vase-v1模型构建知识向量库并进行相似度搜索

国产embedding 最开始使用LangChain结合通义千问API实现了基础的RAG&#xff08;Retrieval-Augmented Generation&#xff09;过程&#xff0c;当时认为embedding模型似乎是LangChain的一部分&#xff0c;然后又通过学习OpenAI的API发现&#xff0c;其实使用embedding模型不需要…

智能农业:农业技术与效益

文章目录 什么是智慧农业&#xff1f;智能农业的好处智能农业技术物联网智能农业解决方案智能农业软件和移动应用程序智能农业的挑战作物监测卫星智能农业解决方案使用卫星数据数据测量历史数据和预测在便携式设备上使用应用程序 智能农业的未来参考 现代技术的发展影响着人类活…

走进jvm之垃圾回收器篇

这里我想首先说明一下&#xff0c;虽然我们经常会拿垃圾回收器来做比较&#xff0c;虽然想挑选一个最好的收集器出来&#xff0c;但是目前也没有说哪一款收集器是完美的&#xff0c;更不存在万能的收集器&#xff0c;我们也只是对收集器选择最适合场景的一个收集器。 那么作者将…

深入解析权限之钥RBAC模型!

在2B系统中设计中&#xff0c;角色基于访问控制&#xff08;RBAC&#xff0c;Role-Based Access Control&#xff09;是最常见的权限管理模型之一。它将权限分配给角色而非个别用户&#xff0c;简化了权限管理的过程。接下来我们一起了解下几种常见的RBAC模型。 1. 标准 RBAC&…

母亲的奶牛(蓝桥杯,acwing每日一题)

题目描述&#xff1a; 农夫约翰有三个容量分别为 A,B,C升的挤奶桶。 最开始桶 A 和桶 B 都是空的&#xff0c;而桶 C 里装满了牛奶。 有时&#xff0c;约翰会将牛奶从一个桶倒到另一个桶中&#xff0c;直到被倒入牛奶的桶满了或者倒出牛奶的桶空了为止。 这一过程中间不能有…

每日学习笔记:C++ STL 的无序容器(unordered_set、unordered_map)

定义 特性 能够快速查找元素 操作函数 负载系数 元素个数 / bucket个数 提供哈希函数 提供等价准则 方法一&#xff1a;重写元素的操作符 方法二&#xff1a;自定义函数对象 提供自定义哈希函数和等价准则例子 例一&#xff1a;传入函数对象 例二&#xff1a;传入lambda 检…

蓝桥杯2023省赛:矩阵总面积|模拟、数学(几何)

题目链接&#xff1a; 0矩形总面积 - 蓝桥云课 (lanqiao.cn) 说明&#xff1a; 参考文章&#xff1a;矩形总面积计算器&#xff1a;计算两个矩形的总面积&#xff0c;包括重叠区域_矩形r1的左下角坐标为x1, yl 、宽度为w1、高度为h1, 矩形r2的左下角坐标为x2,y2、宽-CSDN博客…

移卡 2023 年支付GPV超 2.88 万亿 龙头地位稳固

3月21日&#xff0c;中国领先的基于支付的科技平台——移卡有限公司&#xff08;以下简称“移卡”或“公司”&#xff0c;股份代号&#xff1a;09923.HK&#xff09;发布2023年年度业绩报告。与上年同期相比&#xff0c;移卡2023年收入同比增长15.6%&#xff0c;至人民币39.51亿…

Java代码基础算法练习-求一个三位数的各位平方之和-2024.03.21

任务描述&#xff1a; 输入一个正整数n&#xff08;取值范围&#xff1a;100<n<1000&#xff09;&#xff0c;然后输出每位数字的平方和。 任务要求&#xff1a; 代码示例&#xff1a; package march0317_0331;import java.util.Scanner;public class m240321 {public …

YOLOV5 改进:替换backbone为Swin Transformer

1、前言 本文会将YOLOV5 backbone更换成Swin Transformer 具体为什么这样实现参考上文:YOLOV5 改进:替换backbone(MobileNet为例)-CSDN博客 这里只贴加入的代码 训练结果如下: 2、common文件更改 在common文件中加入下面代码: 这里是swin transformer的实现,参考:…

如何申请免费通配符SSL证书

步骤1&#xff1a;了解免费通配符证书的选项 首先&#xff0c;您需要了解哪些机构或项目提供免费的通配符证书。目前绝大部分CA机构只提供免费的单域名证书&#xff0c;只有少数服务商提供商可以提供免费的通配符证书&#xff0c;比如JoySSL。 免费通配符证书申请地址https://…

GPT2从放弃到入门(二)

引言 本文介绍如何利用GPT2从零训练一个多轮对话聊天机器人&#xff0c;按照本文的思路可以轻松地训练自己的数据。 数据处理 ⚠️ 这是本文的核心部分&#xff0c;其他的内容甚至可以不用看。 本小节阐述多轮对话数据的处理。 数据来自网上的一份开源数据&#xff1a;htt…

Java特性之设计模式【装饰器模式】

一、装饰器模式 概述 装饰器模式&#xff08;Decorator Pattern&#xff09;允许向一个现有的对象添加新的功能&#xff0c;同时又不改变其结构。这种类型的设计模式属于结构型模式&#xff0c;它是作为现有的类的一个包装 装饰器模式通过将对象包装在装饰器类中&#xff0c;以…

C语言牛客网刷题

1.最大公约数和最小公倍数的组合问题 &#xff08;1&#xff09;在调试的过程中涉及到很大的数据&#xff0c;我们我们在定义变量的时候定义为long long类型 &#xff08;2&#xff09;这个里面我们自定义了max2用来求最大公约数&#xff0c;min2用来求最小公倍数 &#xff0…

MYSQL报 - Lock wait timeout exceeded; try restarting transaction

前言 今天在使用数据库编辑数据时&#xff0c;页面突然卡主&#xff0c;退出程序后重新编辑&#xff0c;发现报错&#xff0c;1205 - Lock wait timeout exceeded&#xff1b; try restarting transaction&#xff08;如下图&#xff09;&#xff0c;正巧在和同事开会&#xf…

大屏页面 电子数字 制作

字体包下载地址 链接: https://pan.baidu.com/s/1pjslpT5QQi7-oALDM-uX8g 提取码: zxcv 效果展示 使用前使用后 使用方式 1.解压后将文件夹放入public 2.在公用样式中加入 font-face {font-family: mFont;src: url(../../public/DS-Digital/DS-DIGI-1.ttf); } 3. 在项目…

技术总结: 基于http3的动态网页图片爬虫设计

目录 写在前面第一步: 打开网页第二步: 学会模拟浏览器发送请求第三步: 分析网页结果, 找到爬取内容第四步: 处理动态网页 所有程序 写在前面 作为一名算法工程师, 收集处理数据的能力也是比较重要的. 能够充分利用好互联网的数据资源, 加上优秀的算法能力, 就如虎添翼. 这次就…

关于在CentOS中卸载MySQL

想要卸载MySQL当然要知道自己的MySQL是用那种方法来安装的了&#xff0c;一般来说MySQL的安装方法在市面上有三种 编译安装、YUM安装、RPM安装&#xff0c;下面会介绍到后两种安装的卸载方法 首先查看是否安装MySQL&#xff0c;一般可以看到版本信息就证明安装了 mysql -V 卸载…