两年JAVA后端研发路线

序言
今天有同事问,两年的Java后端开发应达到什么水平?讨论一番,趁着余热,简要记录一下,技术栈JAVA,鉴于博主水平有限,如有不周之处,欢迎各位读者拍砖

  • 算法能力
  • JDK 基础数据结构
  • JVM 内存模型分析
  • 多线程并发
  • 常用中间件
  • 数据库
  • 设计、架构思维
  • 心态

一、算法能力

算法能力为什么要摆在第一位?
第一,这是后端开发能力的根基。作为初级软件开发人员,你的本职工作就是玩内存,使用最小的时间复杂度、空间复杂度实现系统功能。有算法能力作为内力保证,就像天龙八部里面的乔大帮主,无论什么招式,在你的手里就是发挥出与众不同的能量;
第二,这直接关联到你的薪水。务实一点,现在哪个大厂面试不要求算法呢?20分钟内手写算法题目且BUG FREE,是最基础的要求。

那如何提升算法能力呢?
无它,努力刷LeetCode!但是,刷题讲究方法:
第一,讲究由易到难,循序渐进,建议有一定做题经验后,按主题刷题,难度选Middle即可;
第二,及时总结经验,形成产出。博主挑选了部分经典算法题,总结如下:


双指针
	• LRU算法:使用链表+Map实现,队头是在容量不足时候,会首先出队的数据;所以凡是初次插入或被访问,都会从队列中先移出,再放入队尾
	• 三数之和:锁定第一位,后两位采用双指针(一个在头,一个在尾),去重的关键是找到相邻数,值不相同
	• 四数之和:锁定第一位和第二位,后两位采用双指针(一个在头,一个在尾),去重的关键是找到相邻数,值不相同
	• 盛最多水的容器:容器取决于短的边,稳住长边,短的边迭代,取面积比较

滑动窗口
	• 最长无重复字符串:Map记录<字符,最新下标>,窗口的左侧是上一个出现在map里的字符下标+1,右侧是遍历字符串的I,实时计算长度
	• 数组中连续数字之和为K:算出前K 个元素的前缀和dp[],left到right之和为dp[right+1] - dp[left],两层for循环遍历

排序
	• 插入排序的本质:插入排序的本质是,从尾部往首部看,当前要插入的元素>=当前元素I,则找到插入位置为I+1,否则,当前元素往后搓一位
	• 数组中逆序对总和:
		○ 归并排序先分别megersort左、右子集,再merge子集,自底向上的过程
		○ 在merge时候统计逆序对总和
	• 快速排序:自顶向下的过程,partition先获取mid,然后再分别左、右两边排序(quicksort)
	• 堆排序:在java里就是PriorityQueue,往构造函数内传Comparator比较器,排序的依据可在外部数据结构中,只要比较的时能找到,比如Map
	• 下一个字典序排列:从后往前找相邻的顺序对,定位小数,再从后往前找,定位大数,进行交换后,排序后方数
	• 搜索旋转数组:本质上也是变种的二分查找,mid后必然有一段是有序的,若不在当前范围,则找另一段
	• 升序数组中查找给定值的开始、结束位置:做两次二分查找,第一次拿left,第二次拿right
	• 最大的第K个数:快速排序
	• 最大的K个数:优先队列
	
栈:能记录下遍历前的信息
	• 字符串解码:使用栈,数字、字符串、左括号均存下;遇到右括号出栈字符串、数字、左括号,存入字符串;出栈到空
	• 接雨水:单调栈,栈顶元素最小,当前值大于栈顶时,将栈顶元素出队,计算当前填充值,直到小于栈顶元素,入队
	• 每日温度:单调栈,栈顶元素最小,当前值大于栈顶时,将栈顶元素出队,记录下当前值与出栈元素之差,直到小于栈顶元素,入队


数组
	• 除自身外数组乘积:以当前下标为中心,将左、右两边之和相乘
	• 跳跃游戏:维护一个最远端的值,遍历数组更新
	• 合并区间:先做Arrays.sort()对数组排序,然后遍历数组,将结果加入使用链表数组,最后转换为数组

链表
	• 排序链表:插入排序
	• 合并两个有序链表:简单的归并排序,声明头结点dummy,next指向两个有序链表头较小的node,直到两个链表有一个是null,退出,接上非null的链表
	• 合并多个排序链表:声明小顶堆,定义头结点最小的链表放堆顶,每次堆顶出队,再入队(出队链表的下一个结点),直到堆空

二进制 
	• 比特位计数:高位加1,与之前生成的数组组合,可以生成新序列:// 0生1,0-1生2-3,0-3生4-7,0-7生8-15,等等

BFS 
	• 课程表(拓扑排序):
		○ 每门课程表记录自己的入度Int[],入度代表先修课程的数量,入度为0代表本门课程可修,无前置课程
		○ 使用数组链表,记录课程间的关系,获取入度为0的课程,入队列
			§ List<Integer>[] union = new ArrayList[numCourses];
		○ 使用队列,执行入度为0课程的后续结点入度-1,并实时判断入度为0的结点入队列
	• 在二维矩阵,标记下标时,(M,N)可以使用一个int值表示, X = M*矩阵列数+N

DFS  
	• 排列组合题:括号的生成((())):核心是只要左括号大于等于右括号,该迭代可以继续,dfs关注好剪枝条件
	• 组合总和:搜索回溯,剪枝的关键是判断剩余量大于0,去重的关键是数组做排序,dfs时候记录下当前下标
	• 数组全排列:排列过的元素移到数组的前方,记录下当前的下标,回溯时候再移回来
	• N 皇后问题:声明数组array,第n行的array[n]列存皇后,专注于当前行,往上、往左上、往右上方向,不存在皇后,则当前存皇后,继续下一行

树
	• 后序遍历,注意退栈后若右树存在且没处理过,则访问右树
	• 层次遍历,注意保存当前队列长度后,再行出队;万万不可边出队,边计算长度
	• 将二叉树原地换成链表:采取前序遍历,用栈存储右、左子结点,出栈时候,接到root结点后(视为根访问)
	• 反转二叉树,迭代使用BFS,出队根节点,反转左、右子树,入队子树
	• 检验标准二叉树,dfs返回树的深度,同时比较左、右子树相差值
	• 检验搜索二叉树,使用中序遍历,看是否是递增序列,有相同值的结点不算
	• 树的最大路径,dfs返回当前结点作为子树的最大值,result=Max(全局最大,当前结点作为根结点的深度)
	• 前序和中序生成树:前序第一个是根,在中序找到相应位置,左侧是左儿子,右侧是右儿子
	
动态规划(关键是状态转移方程)
	• 最长回文字符串、回文子串个数:dp[I,j] = (dp[i+1][j-1]  or j-i<2 ) && s[i] ==s[j]
	• 零钱凑整:A[amount] = (min) A[amount-coin[i]]  +1,min的获取需要通过迭代来实现,不涉及递归
	• 不同路径:dp[i][j]=dp[i-1][j]+dp[i][j-1];
	• 单词拆分:dp[i]=  dp[j]  && check(s[j…i] ) 
	• 乘积最大子数组:维护当前下标对应的最大值,最小值:dp[i][0]=Math.max(dp[i-1][0]*nums[i],nums[i] if(nums[i]>0)
	• 打家劫舍:f是选当前结点,g是不选当前结点,选当前结点:g(L) + g(R)+ root.val ;不选当前结点:Max(g(L),f(L))+Max(g(R)+f(R))
	• 最大正方形:以当前结点为正方形右下角的最大长度:dp[i][j]=Math.min(Math.min(dp[i-1][j],dp[i][j-1]),dp[i-1][j-1])+1;
	• 最长递增子序列:dp[i]=Math.max(dp[i],dp[j]+1); dp[i]代表当前下标,递增序列长度
	• 通配符匹配:若通配符是*,dp[i][j]=dp[i][j-1]||dp[i-1][j];通配符是*或者点, dp[i][j]=dp[i-1][j-1];
	• 分割两个子集和相同:0-i个元素中任选,值为target,dp[i][j]=dp[i-1][j]|| dp[i-1][j-nums[i]];
	• 目标和:符号可正可负,所以整体的取值范围为[-sum,sum],dp[i][j]=dp[i-1][j-nums[i]]+dp[i-1][j+nums[i]];

二、JDK基础数据结构

问自己两个问题

  • JDK是什么
  • 应该怎么深入理解

JDK是什么?JAVA提供的开发工具包,特别是Java的基础类库,是前浪大佬们给你造好的轮子。
那我们后浪应该怎么做?建议“三步走”:会用轮子,理解轮子,再造轮子。如何理解这句话?举个简单的例子:

  • HashMap会用么
  • 深入看过源码理解么
  • 能自己实现一个简单的HashMap么

想不起来的话,贴心的博主已经帮你准备好了,开始阅读吧

Go语言手撸HashMap(转载)

理解HashMap原理(转载)

三、JVM 内存模型分析

问自己两个问题

  • volatile的应用场景是什么
  • volatile的实现是什么

分别从Java程序、JVM内存模型、处理器、CPU,分析一下吧

深入了解的话,建议阅读《深入理解Java内存模型》,程晓明版本,本书的优点是简明扼要,薄薄的一本,静下心来一天一口气读完,相信你会收益颇丰。比周志明的大部头《深入理解JAVA虚拟机》,好太多

找不到本书,可给博主留言或发邮件,chengkevinhuang@163.com

JVM的基础知识(转载)

四、多线程并发

作为Java程序提升系统性能的利器,开多线程是最普遍的方式,不过博主入门Go语言的goroutine之后,感觉原生的语言级并发还是好用太多,学有余力的读者可深入了解。

问自己两个问题

  • 线程的状态流转是什么
  • 线程池单例会使用么

想不起来的话,贴心的博主已经帮你准备好了,开始阅读吧

多线程并发专题(原创

实现一个简单的线程池(转载)

五、常用中间件

你和新手最大的差距在于,会用好轮子了,博主总结了三个轮子的使用方式:SpringBoot框架下的Redis、Kafka、ES的使用。
走过路过,不要错过

中间件专题(原创

SpringBoot配置数据源(转载)

redis集群部署(转载)

Kafka消息指定分区设置(转载)

Kafka ACK设置(转载)

ES分片介绍(转载)

ES的聚合查询

六、数据库

面试的另一个分水岭是,数据库理解么?
问自己三个问题

  • MySQL你是用了什么隔离级别?
  • 数据库索引的原理是什么?
  • Innodb引擎的三大特性是什么?

贴心的博主已经帮你整理好了

索引原理B+树(转载)

Mysql的13个基础问题(转载)

Innodb 的三个特性(转载)

死锁问题定位(转载)

Mysql集群主从切换(转载)

七、设计、架构思维

咱们稍微务虚一下吧,上面都是初级开发工程师必备的知识点,如果想要成为一个资深的工程师,那你和天天解BUG的他们,区别在哪里呢?思想!设计思路,架构思维,冰冻三尺,非一日之功

博主一家之言,读者辩证看待即可

系统设计专题(原创

共识算法Raft(转载)

PCI-DSS安全标准(转载)

八、心态

心态,这个有点形而上学了,但确实很重要!浮云难能终蔽日,风物长宜放眼量

总结 正解
开放心态 面对复杂环境,勇于拥抱变化
严谨表达 思考后再表述,说话过脑子
提升学历 能力提升下限,学历提升上限
学习方法 做题要有标准答案,提出问题,在文章中寻找到答案,形成自己的思考
深耕领域 选定长期发展的业务领域与技术栈,选择比努力更重要,战略的缺失无法用战术的勤奋弥补

你可能感兴趣的