首页 > 新闻中心 > 情感心理
你真的懂Redis的5种基本数据结构吗?这些知识点或许你还需要看看(图文并茂,浅显易懂,建议收藏)

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注Java)

正文 存储消耗的高于单个字符串的存储 2 hash(字典)相关指令

2.1 hash(字典)常用指令

hset -> hash(字典)插下值,字典不存在则创建 key代表字典名称,field 相当于 key,value是key的值

hset key field value

hmset -> 批量设值

hmset key field value [field value …]

示例:

17.0.0.1:6379>?hset?book?java?“Thinking?in?Java”????????#?字符串包含空格需要""包裹

2(integer)?1

3127.0.0.1:6379>?hset?book?python?“Python?code”

4(integer)?1

5127.0.0.1:6379>?hset?book?c?“The?best?of?c”

6(integer)?1

7127.0.0.1:6379>?hmset?book?go?“concurrency?in?go”?mysql?“high-performance?MySQL”?#?批量设值

8OK

hget -> 获取字典中的指定key的value

hget key field

hgetall -> 获取字典中所有的key和value,换行输出

hgetall key

示例:

1127.0.0.1:6379>?hget?book?java

2"Thinking?in?Java"

3127.0.0.1:6379>?hgetall?book

41)?“java”

52)?“Thinking?in?Java”

63)?“python”

74)?“Python?code”

85)?“c”

96)?“The?best?of?c”

hlen -> 获取指定字典的key的个数

hlen key

举例:

1127.0.0.1:6379>?hlen?book

2(integer)?5

2.2 hash(字典)使用小技巧

在string(字符串)中可以使用incr和incrby对value是整数的字符串进行自加操作,在hash(字典)结构中如果单个子key是整数也可以进行自加操作。

hincrby -> 增对hash(字典)中的某个key的整数value进行自加操作

hincrby key field increment

1127.0.0.1:6379>?hset?liziba?money?10

2(integer)?1

3127.0.0.1:6379>?hincrby?liziba?money?-1

4(integer)?9

5127.0.0.1:6379>?hget?liziba?money

6"9"

注意如果不是整数会报错。

1127.0.0.1:6379>?hset?liziba?money?10.1

2(integer)?1

3127.0.0.1:6379>?hincrby?liziba?money?1

4(error)?ERR?hash?value?is?not?an?integer

五、set(集合) 1、set(集合)相关介绍

1.1 set(集合)的内部结构

Redis的set(集合)相当于Java语言里的HashSet,它内部的键值对是无序的、唯一的。它的内部实现了一个所有value为null的特殊字典。

集合中的最后一个元素被移除之后,数据结构被自动删除,内存被回收。

1.2 set(集合)的使用场景

set(集合)由于其特殊去重复的功能,我们可以用来存储活动中中奖的用户的ID,这样可以保证一个用户不会中奖两次。

? 2、set(集合)相关指令

sadd -> 添加集合成员,key值集合名称,member值集合元素,元素不能重复

sadd key member [member …]

1127.0.0.1:6379>?sadd?name?zhangsan

2(integer)?1

3127.0.0.1:6379>?sadd?name?zhangsan????????#?不能重复,重复返回0

4(integer)?0

5127.0.0.1:6379>?sadd?name?lisi?wangwu?liumazi?#?支持一次添加多个元素

6(integer)?3

smembers -> 查看集合中所有的元素,注意是无序的

smembers key

1127.0.0.1:6379>?smembers?name????#?无序输出集合中所有的元素

21)?“lisi”

32)?“wangwu”

43)?“liumazi”

54)?“zhangsan”

sismember -> 查询集合中是否包含某个元素

sismember key member

1127.0.0.1:6379>?sismember?name?lisi??#?包含返回1

2(integer)?1

3127.0.0.1:6379>?sismember?name?tianqi?#?不包含返回0

4(integer)?0

scard -> 获取集合的长度

scard key

1127.0.0.1:6379>?scard?name

2(integer)?4

spop -> 弹出元素,count指弹出元素的个数

spop key [count]

1127.0.0.1:6379>?spop?name????????????#?默认弹出一个

2"wangwu"

3127.0.0.1:6379>?spop?name?3

41)?“lisi”

52)?“zhangsan”

63)?“liumazi”

六、zset(有序集合) 1、zset(有序集合)相关介绍

1.1 zset(有序集合)的内部结构

zset(有序集合)是Redis中最常问的数据结构。它类似于Java语言中的SortedSet和HashMap的结合体,它一方面通过set来保证内部value值的唯一性,另一方面通过value的score(权重)来进行排序。这个排序的功能是通过Skip List(跳跃列表)来实现的。

zset(有序集合)的最后一个元素value被移除后,数据结构被自动删除,内存被回收。

1.2 zset(有序集合)的相关使用场景

利用zset的去重和有序的效果可以由很多使用场景,举两个例子:

存储粉丝列表,value是粉丝的ID,score是关注时间戳,这样可以对粉丝关注进行排序

存储学生成绩,value使学生的ID,score是学生的成绩,这样可以对学生的成绩排名 2、zset(有序集合)相关指令

1、zadd -> 向集合中添加元素,集合不存在则新建,key代表zset集合名称,score代表元素的权重,member代表元素

zadd key [NX|XX] [CH] [INCR] score member [score member …]

1127.0.0.1:6379>?zadd?name?10?zhangsan

2(integer)?1

3127.0.0.1:6379>?zadd?name?10.1?lisi

4(integer)?1

5127.0.0.1:6379>?zadd?name?9.9?wangwu

6(integer)?1

2、zrange -> 按照score权重从小到大排序输出集合中的元素,权重相同则按照value的字典顺序排序([lexicographical order])

超出范围的下标并不会引起错误。 比如说,当 start 的值比有序集的最大下标还要大,或是 start > stop 时, zrange 命令只是简单地返回一个空列表。 另一方面,假如 stop 参数的值比有序集的最大下标还要大,那么 Redis 将 stop 当作最大下标来处理。

可以通过使用 WITHSCORES 选项,来让成员和它的 score 值一并返回,返回列表以 value1,score1, …, valueN,scoreN 的格式表示。 客户端库可能会返回一些更复杂的数据类型,比如数组、元组等。

zrange key start stop [WITHSCORES]

1127.0.0.1:6379>?zrange?name?0?-1?#?获取所有元素,按照score的升序输出

21)?“wangwu”

32)?“zhangsan”

43)?“lisi”

5127.0.0.1:6379>?zrange?name?0?1????????#?获取第一个和第二个slot的元素

61)?“wangwu”

72)?“zhangsan”

8127.0.0.1:6379>?zadd?name?10?tianqi????#?在上面的基础上添加score为10的元素

9(integer)?1

10127.0.0.1:6379>?zrange?name?0?2????#?key相等则按照value字典排序输出

111)?“wangwu”

122)?“tianqi”

133)?“zhangsan”

14127.0.0.1:6379>?zrange?name?0?-1?WITHSCORES?#?WITHSCORES?输出权重

151)?“wangwu”

162)?“9.9000000000000004”

173)?“tianqi”

184)?“10”

195)?“zhangsan”

206)?“10”

217)?“lisi”

228)?“10.1”

3、zrevrange -> 按照score权重从大到小输出集合中的元素,权重相同则按照value的字典逆序排序

其中成员的位置按 score 值递减(从大到小)来排列。 具有相同 score 值的成员按字典序的逆序(reverse lexicographical order)排列。 除了成员按 score 值递减的次序排列这一点外, ZREVRANGE 命令的其他方面和 ZRANGE key start stop [WITHSCORES] 命令一样

zrevrange key start stop [WITHSCORES]

1127.0.0.1:6379>?zrevrange?name?0?-1?WITHSCORES

21)?“lisi”

32)?“10.1”

43)?“zhangsan”

54)?“10”

65)?“tianqi”

76)?“10”

87)?“wangwu”

98)?“9.9000000000000004”

4、zcard -> 当 key 存在且是有序集类型时,返回有序集的基数。 当 key 不存在时,返回 0

zcard key

1127.0.0.1:6379>?zcard?name

2(integer)?4

5、zscore -> 返回有序集 key 中,成员 member 的 score 值,如果 member 元素不是有序集 key 的成员,或 key 不存在,返回 nil

zscore key member z

1127.0.0.1:6379>?zscore?name?zhangsan

2"10"

3127.0.0.1:6379>?zscore?name?liziba

4(nil)

6、zrank -> 返回有序集 key 中成员 member 的排名。其中有序集成员按 score 值递增(从小到大)顺序排列。

排名以 0 为底,也就是说,score 值最小的成员排名为 0

zrank key member

1127.0.0.1:6379>?zrange?name?0?-1

21)?“wangwu”

32)?“tianqi”

43)?“zhangsan”

54)?“lisi”

6127.0.0.1:6379>?zrank?name?wangwu

7(integer)?0

7、zrangebyscore -> 返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。有序集成员按 score 值递增(从小到大)次序排列。

min 和 max 可以是 -inf 和 +inf ,这样一来,你就可以在不知道有序集的最低和最高 score 值的情况下,使用 [ZRANGEBYSCORE]这类命令。

默认情况下,区间的取值使用闭区间,你也可以通过给参数前增加 ( 符号来使用可选的[开区间]小于或大于)

zrangebyscore key min max [WITHSCORES] [LIMIT offset count]

1127.0.0.1:6379>?zrange?name?0?-1?WITHSCORES?#?输出全部元素

21)?“wangwu”

32)?“9.9000000000000004”

43)?“tianqi”

54)?“10”

65)?“zhangsan”

76)?“10”

87)?“lisi”

98)?“10.1”

10127.0.0.1:6379>?zrangebyscore?name?9?10

111)?“wangwu”

122)?“tianqi”

133)?“zhangsan”

14127.0.0.1:6379>?zrangebyscore?name?9?10?WITHSCORES????#?输出分数

151)?“wangwu”

162)?“9.9000000000000004”

173)?“tianqi”

184)?“10”

195)?“zhangsan”

206)?“10”

21127.0.0.1:6379>?zrangebyscore?name?-inf?10?#?-inf?从负无穷开始

221)?“wangwu”

232)?“tianqi”

243)?“zhangsan”

25127.0.0.1:6379>?zrangebyscore?name?-inf?+inf????#?+inf?直到正无穷

261)?“wangwu”

272)?“tianqi”

283)?“zhangsan”

294)?“lisi”

30127.0.0.1:6379>?zrangebyscore?name?(10?11??#??10?<?score?<=11

311)?“lisi”

32127.0.0.1:6379>?zrangebyscore?name?(10?(10.1??#?10?<?socre?<?-11

33(empty?list?or?set)

34127.0.0.1:6379>?zrangebyscore?name?(10?(11

351)?“lisi”

8、zrem -> 移除有序集 key 中的一个或多个成员,不存在的成员将被忽略

zrem key member [member …]

1127.0.0.1:6379>?zrange?name?0?-1

21)?“wangwu”

32)?“tianqi”

43)?“zhangsan”

54)?“lisi”

6127.0.0.1:6379>?zrem?name?zhangsan?#?移除元素

7(integer)?1

8127.0.0.1:6379>?zrange?name?0?-1

91)?“wangwu”

102)?“tianqi”

113)?“lisi”

七、Skip List 1、简介

跳表全称叫做跳跃表,简称跳表。跳表是一个随机化的数据结构,实质就是一种可以进行二分查找的有序链表。跳表在原有的有序链表上面增加了多级索引,通过索引来实现快速查找。跳表不仅能提高搜索性能,同时也可以提高插下和删除操作的性能。

Skip List(跳跃列表)这种随机的数据结构,可以看做是一个二叉树的变种,它在性能上与红黑树、A-VL树很相近;但是Skip List(跳跃列表)的实现相比前两者要简单很多,目前Redis的zset实现采用了Skip List(跳跃列表)(其它还有LevelDB等也使用了跳跃列表)。

RBT红黑树与Skip List(跳跃列表)简单对比:

RBT红黑树

插下、查询时间复杂度O(logn)

数据天然有序

实现复杂,设计变色、左旋右旋平衡等操作

需要加锁

Skip List跳跃列表

插下、查询时间复杂度O(logn)

数据天然有序

实现简单,链表结构

无需加锁 2、Skip List算法分析

2.1 Skip List论文

这里贴出Skip List的论文,需要详细研究的请看论文,下文部分公式、代码、图片出自该论文。

Skip Lists: A Probabilistic Alternative to Balanced Trees

https://www.cl.cam.ac.uk/teaching/2005/Algorithms/skiplists.pdf

2.2 Skip List动态图

先通过一张动图来了解Skip List的插下节点元素的流程,此图来自维基百科。

2.3 Skip List算法性能分析

2.3.1 计算随机层数算法

首先分析的是执行插下操作时计算随机数的过程,这个过程会涉及层数的计算,所以十分重要。对于节点他有如下特性:

节点都有第一层的指针

节点有第i层指针,那么第i+1层出现的概率为p

节点有最大层数限制,MaxLevel

计算随机层数的伪代码:

论文中的示例

Java版本

1public?int?randomLevel(){

2????int?level?=?1;

3????//?random()返回一个[0…1)的随机数

4????while?(random()?<?p?&&?level?<?MaxLevel){

5????????level?+=?1;

6????}

7????return?level;

8}

代码中包含两个变量P和MaxLevel,在Redis中这两个参数的值分别是:

1p?=?1/4

2MaxLevel?=?64

2.3.2 节点包含的平均指针数目

Skip List属于空间换时间的数据结构,这里的空间指的就是每个节点包含的指针数目,这一部分是额外的内内存开销,可以用来度量空间复杂度。random()是个随机数,因此产生越高的节点层数,概率越低(Redis标准源码中的晋升率数据1/4,相对来说Skip List的结构是比较扁平的,层高相对较低)。其定量分析如下:

level = 1 概率为1-p

level >=2 概率为p

level = 2 概率为p(1-p)

level >= 3 概率为p^2

level = 3 概率为p^2(1-p)

level >=4 概率为p^3

level = 4 概率为p^3(1-p)

……

得出节点的平均层数(节点包含的平均指针数目):

所以Redis中p=1/4计算的平均指针数目为1.33

?

2.3.3 时间复杂度计算

以下推算来自论文内容

假设p=1/2,在以p=1/2生成的16个元素的跳过列表中,我们可能碰巧具有9个元素,1级3个元素,3个元素3级元素和1个元素14级(这不太可能,但可能会发生)。我们该怎么处理这种情况?如果我们使用标准算法并在第14级开始我们的搜索,我们将会做很多无用的工作。那么我们应该从哪里开始搜索?此时我们假设SkipList中有n个元素,第L层级元素个数的期望是1/p个;每个元素出现在L层的概率是p^(L-1), 那么第L层级元素个数的期望是 n * (p^L-1);得到1 / p =n * (p^L-1)

11?/?p?=?n?*?(p^L-1)

2n?=?(1/p)^L

3L?=?log(1/p)^n

所以我们应该选择MaxLevel = log(1/p)^n

定义:MaxLevel = L(n) = log(1/p)^n

?

推算Skip List的时间复杂度,可以用逆向思维,从层数为i的节点x出发,返回起点的方式来回溯时间复杂度,节点x点存在两种情况:

节点x存在(i+1)层指针,那么向上爬一级,概率为p,对应下图situation c.

节点x不存在(i+1)层指针,那么向左爬一级,概率为1-p,对应下图situation b.

设C(k) = 在无限列表中向上攀升k个level的搜索路径的预期成本(即长度)那么推演如下:

1C(0)=0

2C(k)=(1-p)×(情况b的查找长度)?+?p×(情况c的查找长度)

3C(k)=(1-p)(C(k)+1)?+?p(C(k-1)+1)

4C(k)=1/p+C(k-1)

5C(k)=k/p

上面推演的结果可知,爬升k个level的预期长度为k/p,爬升一个level的长度为1/p。

由于MaxLevel = L(n), C(k) = k / p,因此期望值为:(L(n) – 1) / p;将L(n) = log(1/p)^n 代入可得:(log(1/p)^n - 1) / p;将p = 1 / 2 代入可得:2 * log2^n - 2,即O(logn)的时间复杂度。 3、Skip List特性及其实现

2.1 Skip List特性

Skip List跳跃列表通常具有如下这些特性

Skip List包含多个层,每层称为一个level,level从0开始递增

Skip List 0层,也就是最底层,应该包含所有的元素

每一个level/层都是一个有序的列表

level小的层包含level大的层的元素,也就是说元素A在X层出现,那么 想X>Z>=0的level/层都应该包含元素A

每个节点元素由节点key、节点value和指向当前节点所在level的指针数组组成

2.2 Skip List查询

假设初始Skip List跳跃列表中已经存在这些元素,他们分布的结构如下所示:

此时查询节点88,它的查询路线如下所示:

从Skip List跳跃列表最顶层level3开始,往后查询到10 < 88 && 后续节点值为null && 存在下层level2

level2 10往后遍历,27 < 88 && 后续节点值为null && 存在下层level1

level1 27往后遍历,88 = 88,查询命中

2.3 Skip List插下

Skip List的初始结构与2.3中的初始结构一致,此时假设插下的新节点元素值为90,插下路线如下所示:

查询插下位置,与Skip List查询方式一致,这里需要查询的是第一个比90大的节点位置,插下在这个节点的前面, 88 < 90 < 100

构造一个新的节点Node(90),为插下的节点Node(90)计算一个随机level,这里假设计算的是1,这个level时随机计算的,可能时1、2、3、4…均有可能,level越大的可能越小,主要看随机因子x ,层数的概率大致计算为 (1/x)^level ,如果level大于当前的最大level3,需要新增head和tail节点

节点构造完毕后,需要将其插下列表中,插下十分简单步骤 -> Node(88).next = Node(90); Node(90).prev = Node(80); Node(90).next = Node(100); Node(100).prev = Node(90);

2.4 Skip List删除

删除的流程就是查询到节点,然后删除,重新将删除节点左右两边的节点以链表的形式组合起来即可,这里不再画图

? 4、手写实现一个简单Skip List

实现一个Skip List比较简单,主要分为两个步骤:

定义Skip List的节点Node,节点之间以链表的形式存储,因此节点持有相邻节点的指针,其中prev与next是同一level的前后节点的指针,down与up是同一节点的多个level的上下节点的指针

定义Skip List的实现类,包含节点的插下、删除、查询,其中查询操作分为升序查询和降序查询(往后和往前查询),这里实现的Skip List默认节点之间的元素是升序链表

3.1 定义Node节点

Node节点类主要包括如下重要属性:

score -> 节点的权重,这个与Redis中的score相同,用来节点元素的排序作用

value -> 节点存储的真实数据,只能存储String类型的数据

prev -> 当前节点的前驱节点,同一level

next -> 当前节点的后继节点,同一level

down -> 当前节点的下层节点,同一节点的不同level

up -> 当前节点的上层节点,同一节点的不同level

1package?com.liziba.skiplist;

2

3/**

4?*?

5?*??????跳表节点元素

6?*?

7?*

8?*?@Author:?Liziba

9?*?@Date:?2021/7/5?21:01

10?*/

11public?class?Node?{

12

13????/**?节点的分数值,根据分数值来排序?*/

14????public?Double?score;

15????/**?节点存储的真实数据?*/

16????public?String?value;

17????/**?当前节点的?前、后、下、上节点的引用?*/

18????public?Node?prev,?next,?down,?up;

19

20????public?Node(Double?score)?{

21????????this.score?=?score;

22????????prev?=?next?=?down?=?up?=?null;

23????}

24

25????public?Node(Double?score,?String?value)?{

26????????this.score?=?score;

27????????this.value?=?value;

28????}

29}

3.2 SkipList节点元素的操作类

SkipList主要包括如下重要属性:

head -> SkipList中的头节点的最上层头节点(level最大的层的头节点),这个节点不存储元素,是为了构建列表和查询时做查询起始位置的,具体的结构请看2.3中的结构

tail -> SkipList中的尾节点的最上层尾节点(level最大的层的尾节点),这个节点也不存储元素,是查询某一个level的终止标志

level -> 总层数

size -> Skip List中节点元素的个数

random -> 用于随机计算节点level,如果 random.nextDouble() < 1/2则需要增加当前节点的level,如果当前节点增加的level超过了总的level则需要增加head和tail(总level)

1package?com.liziba.skiplist;

2

3import?java.util.Random;

4

5/**

6?*?

7?*??????跳表实现

8?*?

9?*

10?*?@Author:?Liziba

11?*/

12public?class?SkipList?{

13

14????/**?最上层头节点?*/

15????public?Node?head;

16????/**?最上层尾节点?*/

17????public?Node?tail;

18????/**?总层数?*/

19????public?int?level;

20????/**?元素个数?*/

21????public?int?size;

22????public?Random?random;

23

24????public?SkipList()?{

25????????level?=?size?=?0;

26????????head?=?new?Node(null);

27????????tail?=?new?Node(null);

28????????head.next?=?tail;

29????????tail.prev?=?head;

30????}

31

32????/**

33?????*?查询插下节点的前驱节点位置

34?????*

35?????*?@param?score

36?????*?@return

37?????*/

38????public?Node?fidePervNode(Double?score)?{

39????????Node?p?=?head;

40????????for(;?;?{

41????????????//?当前层(level)往后遍历,比较score,如果小于当前值,则往后遍历

42????????????while?(p.next.value?==?null?&&?p.prev.score?<=?score)

43????????????????p?=?p.next;

44????????????//?遍历最右节点的下一层(level)

45????????????if?(p.down?!=?null)

46????????????????p?=?p.down;

47????????????else

48????????????????break;

49????????}

50????????return?p;

51????}

52

53????/**

54?????*?插下节点,插下位置为fidePervNode(Double?score)前面

55?????*

56?????*?@param?score

57?????*?@param?value

58?????*/

59????public?void?insert(Double?score,?String?value)?{

60

61????????//?当前节点的前置节点

62????????Node?preNode?=?fidePervNode(score);

63????????//?当前新插下的节点

64????????Node?curNode?=?new?Node(score,?value);

65????????//?分数和值均相等则直接返回

66????????if?(curNode.value?!=?null?&&?preNode.value?!=?null?&&?preNode.value.equals(curNode.value)

67??????????????????&&?curNode.score.equals(preNode.score))?{

68????????????return;

69????????}

70

71????????preNode.next?=?curNode;

72????????preNode.next.prev?=?curNode;

73????????curNode.next?=?preNode.next;

74????????curNode.prev?=?preNode;

75

76????????int?curLevel?=?0;

77????????while?(random.nextDouble()?<?1/2)?{

78????????????//?插下节点层数(level)大于等于层数(level),则新增一层(level)

79????????????if?(curLevel?>=?level)?{

80????????????????Node?newHead?=?new?Node(null);

81????????????????Node?newTail?=?new?Node(null);

82????????????????newHead.next?=?newTail;

83????????????????newHead.down?=?head;

84????????????????newTail.prev?=?newHead;

85????????????????newTail.down?=?tail;

86????????????????head.up?=?newHead;

87????????????????tail.up?=?newTail;

88????????????????//?头尾节点指针修改为新的,确保head、tail指针一直是最上层的头尾节点

89????????????????head?=?newHead;

90????????????????tail?=?newTail;

91????????????????++level;

92????????????}

93

94????????????while?(preNode.up?==?null)

95????????????????preNode?=?preNode.prev;

96

97????????????preNode?=?preNode.up;

98

99????????????Node?copy?=?new?Node(null);

100????????????copy.prev?=?preNode;

总体来说,如果你想转行从事程序员的工作,Java开发一定可以作为你的第一选择。但是不管你选择什么编程语言,提升自己的硬件实力才是拿高薪的唯一手段。

如果你以这份学习路线来学习,你会有一个比较系统化的知识网络,也不至于把知识学习得很零散。我个人是完全不建议刚开始就看《Java编程思想》、《Java核心技术》这些书籍,看完你肯定会放弃学习。建议可以看一些视频来学习,当自己能上手再买这些书看又是非常有收获的事了。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

while?(random.nextDouble()?<?1/2)?{

78????????????//?插下节点层数(level)大于等于层数(level),则新增一层(level)

79????????????if?(curLevel?>=?level)?{

80????????????????Node?newHead?=?new?Node(null);

81????????????????Node?newTail?=?new?Node(null);

82????????????????newHead.next?=?newTail;

83????????????????newHead.down?=?head;

84????????????????newTail.prev?=?newHead;

85????????????????newTail.down?=?tail;

86????????????????head.up?=?newHead;

87????????????????tail.up?=?newTail;

88????????????????//?头尾节点指针修改为新的,确保head、tail指针一直是最上层的头尾节点

89????????????????head?=?newHead;

90????????????????tail?=?newTail;

91????????????????++level;

92????????????}

93

94????????????while?(preNode.up?==?null)

95????????????????preNode?=?preNode.prev;

96

97????????????preNode?=?preNode.up;

98

99????????????Node?copy?=?new?Node(null);

100????????????copy.prev?=?preNode;

总体来说,如果你想转行从事程序员的工作,Java开发一定可以作为你的第一选择。但是不管你选择什么编程语言,提升自己的硬件实力才是拿高薪的唯一手段。

如果你以这份学习路线来学习,你会有一个比较系统化的知识网络,也不至于把知识学习得很零散。我个人是完全不建议刚开始就看《Java编程思想》、《Java核心技术》这些书籍,看完你肯定会放弃学习。建议可以看一些视频来学习,当自己能上手再买这些书看又是非常有收获的事了。

[外链图片转存中…(img-TbdrpUqa-1713681152682)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)

[外链图片转存中…(img-h1z38CKd-1713681152683)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

您可能还喜欢的
最新信息
返回顶部