先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里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行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!