1. 分支主题 1
1.1. codesheep推荐的开源项目
1.1.1. https://github.com/halo-dev/halo 个人博客 https://github.com/looly/hutool 工具集 https://github.com/sqshq/piggymetrics springboot + springcloud 落地脚手架 https://github.com/hankcs/HanLP //nlp的工具集 https://github.com/hansonwang99
1.2. in:name 关键字1 关键字2 in:readme 关键字1 关键字2 stars:>1000 forks:>1000 language:java pushed:>2021-01-01
2. 分支主题 2
2.1. 李千说springcloud基本不会问, 这段时间应该回头学java高级部分、面经 每天4题(2小时) + 多线程、并发 + JVM + 面经 + halo 写简历,先把大概写出来,再逐步修改
2.2. 判断一个公司值不值得去: 1. 薪酬福利 2. 公司规模 脉脉
2.3. 自我缓解压力的感想
2.3.1. 大公司固然好,但工作最重要的是能胜任,如果去了大公司如果完成不了任务,顶着巨大压力,也很难受吧; 直面问题 这世上不存在困难,有的只是问题,直面它,就成功一半了
2.4. 秋招
2.4.1. 了解公司秋招途径
2.4.1.1. 1.⽬标企业的官⽹+公众号 :最及时最权威的获取秋招信息的途径。 2.⽜客⽹ : 每年秋招/春招,都会有⼤批量的公司会到⽜客⽹发布招聘信息,并且还会有⼤量的公 司员⼯来到这⾥发内推的帖⼦; 3.超级简历:超级简历⽬前整合了各⼤企业的校园招聘⼊⼝,地址:https://www.wondercv.com/jobs/,如果你是校招的话,点击“校招⽹申”就可以直接跳转到各⼤企业的校园招聘⼊⼝的整合⻚⾯了; 4.认识的朋友 如果你有认识的朋友在⽬标企业⼯作的话,你也可以找他们了解秋招信息,并且可以让他们帮你内推。 5.宣讲会现场 Guide 当时也参加了⼏场宣讲会。不过,我是在荆州上学,那边没什么⽐好的学校,⼀般没有 公司去开宣讲会。所以,我当时是直接跑到武汉来了,参加了武汉理⼯⼤学以及华中科技⼤学的 ⼏场宣讲会。总体感觉还是很不错的! 6.其他 校园就业信息⽹、学校论坛、班级 or 年级 QQ 群、各⼤招聘⽹站⽐如拉勾...... 除了这些⽅法,我也遇到过这样的经历:有些⼤公司的⼀些部⻔可能暂时没招够⼈,然后如果你 的亲戚或者朋友刚好在这个公司,⽽你正好⼜在寻求offer,那么⾯试机会基本上是有了,⽽且这 种⾯试的难度好像⼀般还普遍⽐其他正规⾯试低很多
2.4.2. 简历
2.4.2.1. 项目:在xx公司xx项目中,在xx情况下,使用xxx技术解决了xxx问题,取得了xxx成绩等量化的词语描述 一个java相关的项目:博客系统 可以把DL中,图片分类(CNN)、机器翻译(LSTM)润色一下,别让别人觉得是学校课程强制做的,可以说自己有兴趣做的。 可以说研究过,开源的HanNLP项目 拿到一个开源项目/接手别人的项目, 第一步,看到用到什么技术栈,也就是说用到了什么技术/知识点,这样你就会有一个大概的方向。——技术栈 第二步,看架构/框架,也可以说是看项目的目录结构,这一步可以知道各个模块是怎么通信的,这个时候可以用纸/思维导图写出来,加深印象。——目录结构 第三步,跑项目/看功能,看一下这个项目实现了什么功能,可以用它来做什么,这一步骤也可以用纸记录一下。——开起来,看功能 第四步,从目录结构看一下整个项目的入口文件引入了什么全局的东西,这个可以单独抽出来学习。 第五步,阅读源码,这个最好就是一个一个模块/功能去阅读,搞清楚是怎么实现的,这一部分的话是最关键的(废话),只看一次的话很难会搞懂,所以要多看几次,为什么用到这个API,为什么要以这种形式写呢,能不能写得更简洁一点,能不能抽取出来具有更高的复用性呢。这些都是要去考虑的。——看源码 第六步,当做好前五步,你就对这个项目很熟悉了,然后就可以做修改了,要么添加功能,要么抽取模块,要么降低复杂度。 最后,学习项目最关键的是思想和技术的运用,要对整体做一个学习而不是只盯着某一个模块或者是某一段代码。谢谢 上⾯思维导图⼤概涵盖了技术⾯试可能会设计的技术,但是你不需要把上⾯的每⼀个知识点都搞 得很熟悉,要分清主次,对于⾃⼰不熟悉的技术不要写在简历上,对于⾃⼰简单了解的技术不要 说⾃⼰熟练掌握!
2.4.2.2. 推荐⼤家使⽤Markdown语法写简历,然后再将Markdown格式转换为PDF格式后进⾏简历投递 可以花半个⼩时简单看⼀下Markdown语法说明: http://www.markdown.cn
2.4.3. 面经
2.4.3.1. 搞清楚⾃⼰⾯试中可能涉及哪些知识点、哪些知识点是重点。⾯试中哪些问题会被经常问到、⾃⼰该如何回答。 不需要把上⾯的每⼀个知识点都搞得很熟悉,要分清主次, 用80%的精力去解决20%主要问题,不要有完美主义 可以在⽹上找找有没有你要⾯试的公司的⾯经、文章
2.4.4. 面试
2.4.4.1. 自我介绍部分
2.4.4.1.1. 我觉得⼀个好的⾃我介绍应该包含这⼏点要素: 技术栈 + 优势、擅长之处 1. ⽤简单的话说清楚⾃⼰主要的技术栈与擅⻓的领域; 2. 把重点放在⾃⼰在⾏的地⽅以及⾃⼰的优势之处; 3. 重点突出⾃⼰的能⼒⽐如⾃⼰的定位的bug的能⼒特别厉害; 另外,⽹上⼀般建议的是 准备好两份⾃我介绍:⼀份对hr说的,主要讲能突出⾃⼰的经历,会的编程技术⼀语带过;另⼀ 份对技术⾯试官说的,主要讲⾃⼰会的技术细节和项⽬经验。 范例: ⾯试官,您好!我叫秀⼉。⼤学时间我主要利⽤课外时间学习了 Java 以及 Spring、MyBatis等框架 。 //技术栈 在校期间参与过⼀个考试系统的开发,这个系统的主要⽤了 Spring、MyBatis 和 shiro 这三种框架。我在其中主要担任后端开发,主要负责了权限管理功能模块的搭建。 //项目 另外,我在⼤学的时候参加过⼀次软件编程⼤赛,我和我的团队做的在线订餐系统成功获得了第⼆名的成绩。 //项目、比赛 我还利⽤⾃⼰的业余时间写了⼀个简单的 RPC 框架,这个框架⽤到了 Netty 进⾏⽹络通信, ⽬前我已经将这个项⽬开源,在 Github 上收获了 2k 的 Star! //项目 说到业余爱好的话,我⽐较喜欢通过博客整理分享⾃⼰所学知识,现在已经是多个博客平台的认证作者。 //展现态度 ⽣活中我是⼀个⽐积极乐观的⼈,⼀般会通过运动打球的⽅式来放松。我⼀直都⾮常想加⼊贵公司,我觉得贵公司的⽂化和技术氛围我都⾮常喜欢,期待能与你共事!
2.4.4.2. 项目介绍
2.4.4.2.1. 技术⾯试第⼀步,⾯试官⼀般都是让你⾃⼰介绍⼀下你的项⽬。你可以从下⾯⼏个⽅向来考虑: 1. 对项⽬整体设计的⼀个感受(⾯试官可能会让你画系统的架构图) //项目架构图 2. 在这个项⽬中你负责了什么、做了什么、担任了什么⻆⾊ 3. 从这个项⽬中你学会了那些东⻄,使⽤到了那些技术,学会了那些新技术的使⽤ 4. 另外项⽬描述中,最好可以体现⾃⼰的综合素质,⽐如你是如何协调项⽬组成员协同开发的 //合作、体现⾃⼰的综合素质 或者在遇到某⼀个棘⼿的问题的时候你是如何解决的⼜或者说你在这个项⽬⽤了什么技术实现了什么功能⽐如:⽤redis做缓存提⾼访问速度和并发量、使⽤消息队列削峰和降流等等 //遇到什么问题,怎么解决,学会了那些新技术的使⽤ 要点:不要一股脑描述项目是干什么的,从这个项⽬中你学会了那些东⻄,使⽤到了那些技术,学会了那些新技术的使⽤
2.4.4.3. 反问环节: 要么问下自己的回答的不足之处,借此积累经验教训; 要么问下部门和工作情况,表达自己的意愿
2.4.5. ⾯试之后记得复盘
2.4.5.1. 如果失败,不要灰⼼;如果通过,切勿狂喜。⾯试和⼯作实际上是两回事,可能很多⾯试未通过 的⼈,⼯作能⼒⽐你强的多,反之亦然。我个⼈觉得⾯试也像是⼀场全新的征程,失败和胜利都 是平常之事。所以,劝各位不要因为⾯试失败⽽灰⼼、丧失⽃志。也不要因为⾯试通过⽽沾沾⾃ 喜,等待你的将是更美好的未来,继续加油!
3. 分支主题 3
3.1. java高级
3.1.1. 多线程、并发
3.1.1.1. 并发基础
3.1.1.1.1. 互斥同步
3.1.1.1.2. 非阻塞同步
3.1.1.1.3. 指令重排
3.1.1.1.4. synchronized
3.1.1.1.5. volatile
3.1.1.2. 线程
3.1.1.3. 锁
3.1.1.3.1. 自旋锁
3.1.1.3.2. 偏向锁
3.1.1.3.3. 可重入锁
3.1.1.4. 线程池
3.1.1.5. 并发容器
3.1.1.6. JUC
3.1.1.6.1. executor
3.1.1.6.2. collections
3.1.1.6.3. locks
3.1.1.6.4. atomic(源自类)
3.1.1.6.5. tools(CountDownLatch、Exchanger、ThreadLocal、CyclicBarrier)
3.1.2. leetcode
3.1.2.1. 数据结构
3.1.2.1.1. 数组
3.1.2.1.2. 栈,队列
3.1.2.1.3. 单/双链表
3.1.2.1.4. 哈希表
3.1.2.1.5. 树
3.1.2.1.6. 堆
3.1.2.1.7. 图
3.1.2.2. 算法
3.1.2.2.1. 排序(8种):冒泡、插入、快排、归并排序、堆排序……
3.1.2.2.2. 查找
3.1.2.2.3. 分治
3.1.2.2.4. 动态规划
3.1.2.2.5. 回溯
3.1.2.2.6. 贪心
3.1.2.2.7. KMP
3.1.2.2.8. Prim
3.1.2.2.9. Kruskal
3.1.2.2.10. 最关路径
3.1.3. 设计模式(23种)
3.1.3.1. 单例模式
3.1.3.2. 观察者模式
3.1.3.3. 工厂模式
3.1.3.4. 适配器模式
3.1.3.5. 装饰者模式
3.1.3.6. 代理模式
3.1.3.7. 模板模式
3.1.3.8. 责任链模式
3.1.3.9. 其他()
3.1.4. JVM
3.1.4.1. JVM体系
3.1.4.2. 类加载过程/机制
3.1.4.3. 字节码执行过程/机制
3.1.4.4. 双亲委派机制/沙箱安全机制
3.1.4.5. JMM java memory model
3.1.4.6. GC
3.1.4.7. JVM性能监控和故障定位
3.1.4.8. JVM调优
3.2. SSM + 数据库 + Netty + springboot + springcloud + 中间件(消息中间件ActiveMQ、RabbitMQ、kafka) 项目
4. 分支主题 5
4.1. leetcode
4.1.1. 经典题目,分析 + 思路 + 代码
4.1.1.1. 01:两数之和——数组
4.1.1.1.1. public class TwoSum_01{ //双层for循环暴力求解 public int[] twoSum(int[] nums, int target) { int[] result = new int[]{0,1}; if(nums.length ==2){ return result; } for (int i = 0; i < nums.length; i++) { for (int j = i+1; j <nums.length ; j++) { if (nums[i] + nums[j] == target){ result[0] = i; result[1] = j; return result; } } } return null; } }
4.1.1.2. 20:括号匹配问题——>栈 分析:自左向右遍历字符串,出现左括号,则放进一个栈,出现右括号,则和栈顶元素匹配, 技巧: 1. 为了快速判断每一对括号,我们可以使用HashMap存储每一对括号,键为右括号,值为左括号
4.1.1.2.1. 伪代码: if(字符串的长度必须要是偶数,如果是奇数,直接返回false) new 一个Stack //创建一个栈,栈里面只用来存放左括号,不存右括号 for(获取String的每一个char,每获取一个char),判断: //PS:不需要把String.toCharArray()变成数组,直接String.charAt(index)获取char即可 if (是右括号){ //判断是不是右括号只需要判断Map集合中有没有这个key即可 if (栈为空 或 栈顶元素是否与之不匹配) { //注意:是不匹配 return false; } else { 否则表示匹配,弹出栈顶元素:Stack对象.pop() } } else { 否则表示是左括号,左括号一律进栈:Stack对象.push(…) } } return stack.isEmpty(); //for循环遍历完String后,判断栈是否空了(里面是否有剩余的左括号),不为空:失败;为空:成功
4.1.1.2.2. class Solution { public boolean isValid(String s) { int n = s.length(); if (n % 2 == 1) { //字符串的长度必须要是偶数,如果是奇数,直接返回false return false; } Map<Character, Character> pairs = new HashMap<Character, Character>() {{ put(')', '('); //注意:K、V是Character,要用单引号,用双引号就是String了,不对 put(']', '['); put('}', '{'); }}; Stack<Character> stack = new Stack<>(); //Character是char的封装类 for (int i = 0; i < n; i++) { //n为String的长度 char ch = s.charAt(i); if (pairs.containsKey(ch)) { //Map的方法,判断是否包含指定的key,返回boolean if (stack.isEmpty() || stack.peek() != pairs.get(ch)) { //栈为空 或者 栈顶元素不匹配,返回false return false; } stack.pop(); //否则表示匹配,弹出栈顶元素:Stack对象.pop() } else { stack.push(ch); } } return stack.isEmpty(); } }
4.1.1.3. 21:合并两个有序链表——> 分析:
4.1.2. 常见数据结构与算法的总结
4.1.3. 数据结构
4.1.3.1. 数组
4.1.3.2. 栈,队列
4.1.3.3. 单/双链表
4.1.3.4. 哈希表
4.1.3.5. 树
4.1.3.5.1. 二叉树 B树 B+树
4.1.3.6. 堆
4.1.3.7. 图
4.1.4. 算法
4.1.4.1. 排序(8种):冒泡、插入、快排、归并排序、堆排序……
4.1.4.1.1. 排序算法可以分为内部排序和外部排序。 内部排序是数据记录在内存中进行排序; 而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存; 常见的内部排序算法有8种:插入排序、希尔排序、选择排序、冒泡排序、归并排序、快速排序、堆排序、基数排序;
4.1.4.1.2. 冒泡排序
4.1.4.1.3. 选择排序
4.1.4.1.4. 插入排序
4.1.4.1.5. 各种排序对比
4.1.4.2. 查找
4.1.4.3. 分治
4.1.4.4. 动态规划
4.1.4.5. 回溯
4.1.4.6. 贪心
4.1.4.7. KMP
4.1.4.7.1. 1
4.1.4.8. Prim
4.1.4.9. Kruskal
4.1.4.10. 最关路径
4.1.4.10.1. floyed
4.1.4.10.2. 迪杰斯特拉
5. 分支主题 5
5.1. java基础
5.1.1. ⾯向对象和⾯向过程的比较
5.1.1.1. ⾯向过程: ⾯向对象 : 相比⾯向过程,性能更低: 因为类调⽤时需要实例化,会有更多的开销,所以当性能是最重要的考量因素的时候,⽐如单⽚机、嵌⼊式开发、Linux/Unix 等⼀般采⽤⾯向过程开发; ⼤多⾯向过程语⾔都是直接编译成机械码在电脑上执⾏, 而Java 是半编译语⾔,最终的执⾏代码并不是可以直接被 CPU 执⾏的⼆进制机械码; ⾯向对象有封装、继承、多态性的特性,所以可以设计出低耦合的系统,使系统更加灵活、易于维护,即:易维护、易复⽤、易扩展
5.1.2. java语言层面
5.1.2.1. java语言本身
5.1.2.1.1. 1. Java 语⾔的诞⽣就是为简化⽹络编程设计的,因此 Java 语⾔不仅⽀持⽹络编程⽽且很⽅便 2. java程序可以跨平台运行:在不同操作系统中,安装对应的JVM,java程序就可以在不同的操作系统上运行 JVM就是个软件,自己不能跨平台:windows上要安装windows版的JVM,MAC上要安装MAC版的JVM
5.1.2.1.2. Java 和 C++的区别?
5.1.2.2. 重载 与 重写
5.1.2.2.1. 重载(overloading)方法:一个类中的多个方法,本质上是彼此独立的、平等的、不同的,方法 编译器通过匹配实参与形参,挑选出具体执行的方法,这个过程称为重载解析(overloading resolution) 重写(override)方法:发生在子类继承父类时,本质是不平等的,因为父类把规矩定死了,方法名、形参、返回值类型都必须相同,总而言之一句话:除了能重写方法体,其他都不能改 如果⽗类⽅法访问修饰符为 private/final/static 则⼦类就不能重写该⽅法,但是被 static 修饰的⽅法能够被再次声明。 父类的构造方法无法被重写
5.1.2.3. 浅拷⻉ vs 深拷⻉
5.1.2.3.1. shallow clone(浅拷⻉):对基本数据类型进⾏值传递,对引⽤数据类型进⾏引⽤传递般的拷⻉,此为浅拷⻉; deep clone(深拷⻉):对基本数据类型进⾏值传递,对引⽤数据类型,创建⼀个新的对象,并复制其内容,此为深拷⻉
5.1.2.3.2. java中: 调用方法时,给方法传实参,方法得到的实际是实参的一个拷贝,也就是说,方法不能修改实参本身,只能修改传递过来的实参的拷贝 基本数据类型的变量,存的是某一个具体的数值 引用数据类型的变量,存的是某个对象在内存中的地址值 当方法的形参是基本数据类型时,调用方法时,会拷贝一份实参(也是基本数据类型),传入方法,方法无论怎么操作这一份拷贝,都不会影响到实参本身 当方法的形参是引用数据类型时,调用方法时,会拷贝一份实参(也是引用数据类型),传入方法,方法无论怎么操作这一份拷贝,都不会影响到实参本身,但方法可以通过实参的这一份拷贝(记录地址值),可以找到内存中的对象,进而可以直接修改这个对象 都是浅拷贝
5.1.2.4. 基本数据类型,String,相关
5.1.2.4.1. 大小 最小值 最大值 封装类 char 16bit(2byte) Character byte 8bits(1byte) Byte short 16bits(2byte) Short int 32bits(4byte) -2的31次 +2的31次-1 Interger long 64bits(8byte) Character float 32bit(4byte) Float double 64bit(8byte) Double void —— —— —— Void
5.1.2.4.2. 字符 与 字符串 字符,char:是一个基础数据类型、用单引号、单个字符、占2个字节 字符串,String:是一个类、用双引号、若干个字符
5.1.2.4.3. String为什么是不可变的?
5.1.2.5. final关键字的⼀些总结
5.1.2.5.1. final 关键字的核心含义是:这个东西一锤定音了,只可以被访问,不能被修改,主要⽤在三个地⽅:变量、⽅法、类; 变量:只能被赋值一次,不能再次被赋值,且变量名所有字母大写,如多是多个单词,所有字母大写且中间用_分割 当用final修饰基本数据类型的变量,则其数值⼀旦在初始化之后便不能更改; 当用final修饰引用数据类型的变量,则在对其初始化之后便只能存放某个地址值,不能存放其他的地址值,即:不能再让其指向另⼀个对象; 类: 当⽤ final 修饰⼀个类时,表明这个类不能被继承。final 类中的所有成员⽅法都会被隐式地指定为 final ⽅法。 方法: 使⽤ final ⽅法的原因有两个: 第⼀个原因是把⽅法锁定,不能被子类重写;类中所有的 private ⽅法都隐式地指定为 final 第⼆个原因是效率。在早期的 Java 实现版本中,会将 final ⽅法转为内嵌调⽤。但是如果⽅法过于庞⼤,可能看不到内嵌调⽤带来的任何性能提升(现在的 Java 版本已经不需要使⽤final ⽅法进⾏这些优化了);
5.1.2.6. String StringBuffer 和 StringBuilder 3者的区别是什么?
5.1.2.6.1. 区别一:可变性 String 中的 value 是不可变的,也就可以理解为常量,每次对 String 类型进⾏改变的时候,都会创建⼀个新的 String 对象,并重新赋给了引用变量 StringBuilder 与 StringBuffer 都继承⾃ AbstractStringBuilder 类,在 AbstractStringBuilder 中, 和String类一样, 也是使⽤字符数组char[] value保存字符串 ,但区别是没有⽤ final关键字修饰,所以StringBuilder 和 StringBuffer是可变的,所以,改变时,是对StringBuilder 与 StringBuffer本身进行修改,⽽不是⽣成新的对象; 区别二:线程安全性 String 中的 value 是不可变的,也就可以理解为常量,线程安全; AbstractStringBuilder 是StringBuilder 与 StringBuffer 的⽗类,定义了⼀些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共⽅法; StringBuffer 在⽅法加了同步锁或者对调⽤的⽅法加了同步锁,所以是线程安全的。 StringBuilder 并没有在⽅法进⾏加同步锁,因此多线程操作字符串缓冲区时可能线程安全问题,但相同情况下使⽤ StringBuilder 相⽐使⽤ StringBuffer 仅能获得 10%~15% 左右的性能提升;
5.1.2.7. == 与 equals(重要)
5.1.2.7.1. 如果用 == 去比较两个基本数据类型,则比较的是具体的值 如果用 == 去比较两个引用数据类型,则比较的是对象的地址值 equals()方法来自于Object,Object中的equals()方法内部就是使用 == ,即:比较的是两者地址值是否相同,对象A.equals(对象B); 但在实际定义javabean类时,通常会override equals()方法(用IDEA自动生成),方法体中比较两者的成员变量的值是否相同 例子: String类定义中,重写了equals()方法:比较两个String内容是否相同(严格区分大小写) 因此,如果要比较两个String内容是否相同(严格区分大小写),由于String是引用数据类型,不能用 == ,要用AString.equals(StringB)
5.1.2.8. hashCode() 与 equals() (重要) 为什么重写 equals 时必须重写hashCode ⽅法? 首先这个问题就是错的! 错误1:提到hashCode(),前提是打算把这个类的实例存入HashSet,或,将这个类实例作为key,存放在HashMap中 错误2:在上面情况下,hashCode()是必须要重写的,问题应该是重写hashCode()的同时,也必须重写equals()
5.1.2.8.1. Object的: public int hashCode() //Object的hashCode()是根据对象的内存地址值,计算出来的int类型的哈希值, 由于Object的hashCode()是根据实例的内存地址值,因此:同一个对象,多次调用hashCode(),返回值一样 不同的对象,地址值肯定不一样,各自调用hashCode(),返回值不一样 public boolean equals() //内部就是使用 == ,即:比较的是两者地址值是否相同 —————————————————— IDEA generat出来的: hashCode() //不再根据对象的地址值,而是根据对象中的property(包括成员变量)计算, 此时:如果不同的对象,但property全都一样,则计算出的哈希值是一样的 equals() //内部就是使用 == ,即:比较的是两者地址值是否相同 ——————————————————— 1. 什么时候会用到hashCode()?hashCode()有什么作用? 只有当:打算将这个类实例存入HashSet,或,将这个类实例作为key,存放在HashMap中时,才会调用其hashCode(),即:只有在需要将这个类实例存入哈希表时,才会去计算一个对象的哈希值,才会去调用其hashCode(); hashCode()的作用是:调用hashCode()所得的哈希值,会和数组的长度(以HashSet为例),一起计算出该对象在哈希表中的位置 2. 为什么打算将这个类实例存入HashSet,或,将这个类实例作为key,存放在HashMap中时,必须要重写类的hashCode()? 因为HashSet不存入重复的元素, 因此:如果不重写类的hashCode(),使用Object的hashCode(),则:两个不同的实例,地址值不同,但property完全一样,它们使用Object的hashCode()计算出的哈希值是不一样的,即它们会被认为是不同的实例,进而存入HashSet,导致没有去除重复元素,这是我们不希望看到的,因此必须重写类的hashCode(); HashMap,所有的Map集合,键不可重复,值可以重复,同理,如果类实例作为key,为了不存入重复的key,必须重写类的hashCode(); 上面得出了结论:当打算将这个类实例存入HashSet,或,将这个类实例作为key,存放在HashMap中时,必须要重写类的hashCode() 3. 事实上,重写hashCode()的同时,也必须重写equals() 原因:这是由于HashSet的存储逻辑:先用hashCode()判断,冲突了再用equall()判断: HashSet的存储逻辑: 会根据元素的哈希值和数组长度计算出应存入的数组索引位置; 判断数组的索引位置是否为null: 如果为null,直接存入 如果该位置不为null,表示该位置有元素了,则调用equall()方法,判断新元素内部的属性值与该位置链表中所有元素是否相同: 如果和链表中某个元素属性值一样,则不存; 如果和链表中所有元素的属性值都不一样,则将新元素存入数组,老元素以链表的形式挂在新元素下面,形成链表结构; 如果只重写hashCode(),没有重写equall(),则: 此时,若有两个不同的实例,地址值不同,但property完全一样,它们使用重写后的hashCode()根据property计算出的哈希值是一样的,哈希值结合数组长度计算出应存入的数组索引位置也一样,即发生冲突了,此时又会调用equall()方法,由于没有重写equall(),Object的equall()比较的是两者地址值是否相同,地址值不同,会认定它们是不同的元素,进而存入HashSet,导致没有去除重复元素,这是我们不希望看到的,因此重写hashCode()的同时,也必须重写equals(); 4. 为什么要采用:先用hashCode()判断,冲突了再用equall()判断,这种逻辑? 这种判断逻辑更高效,计算一个对象的hashCode()是很容易的,逐一比较两个对象的property是否相同是更麻烦的,先用hashCode()计算出应存的位置,可以大大减少equals()的使用次数
5.1.2.9. 自动装箱就是指:将基本数据类型⽤它们对应的包装类型包装起来; 自动拆箱就是指:将包装类型转换为基本数据类型;
5.1.2.10. 静态⽅法中,不能调⽤⾮静态方法,不能访问非静态的成员变量,原因
5.1.2.11. 面向对象,相关
5.1.2.11.1. 为什么每个类中都应该定义一个无参的constructor,哪怕这个constructor不会被用到
5.1.2.11.2. 接⼝和抽象类的区别是什么?
5.1.2.12. IO流
5.1.2.12.1. 既然有了字节流,为什么还要有字符流?
5.1.2.12.2. 对象操作流 序列化:通过ObjectOutputStream,将实例以字节的形式,内存——>本地文件 反序列化:通过ObjectInputStream,将实例以字节的形式,本地文件——>内存
5.1.2.13. java中的网络编程
5.1.2.13.1. 网络IO
5.1.2.14. 获取⽤键盘输⼊常⽤的两种⽅法
5.1.2.14.1. ⽅法 1:new一个Scanner类实例 Scanner sc = new Scanner(System.in); String s = sc.nextLine(); sc.close();
5.1.2.14.2. ⽅法 2:new一个BufferedReader类实例 //字符缓冲输入流 BufferedReader input = new BufferedReader(new InputStreamReader(System.in)); String s = input.readLine();
5.1.2.15. 各种集合,存储时的逻辑,见集合部分
5.1.3. 线程
5.1.3.1. 操作系统层面,进程、线程相关概念
5.1.3.1.1. 并发(Concurrent):多个任务轮流执行,某一时刻只有一个任务在执行; 并行(Parallel):某一时刻,多个任务在执⾏
5.1.3.1.2. 什么是线程和进程?
5.1.3.1.3. 为什么要提出多线程?
5.1.3.1.4. 什么是上下文切换? PS:由于线程是CPU最小执行单位,所以上下文切换,一般默认指的是线程上下文切换
5.1.3.1.5. 什么是死锁(DeadLock)? PS:由于线程是CPU最小执行单位,所以死锁,一般默认指的是线程死锁
5.1.3.2. java,单线程,相关
5.1.3.2.1. 线程状态 Java 线程在运⾏的⽣命周期中的指定时刻只可能处于下⾯ 6 种不同状态的其中⼀个状态
5.1.3.2.2. wait()和sleep()区别和共同点 唯一共同点:使当前正在执行的线程失去CPU执行权
5.1.3.2.3. 为什么我们调⽤ start() ⽅法时会执⾏ run() ⽅法,为什么我们不能直接调⽤ run() ⽅法?
5.1.3.3. 多线程,操作共享变量安全问题
5.1.3.3.1. 总结:所谓的多线程不安全,必须要满足这2个条件 1. 多线程共享一些变量,这是JMM层面本身存在的原因:在JMM(java内存模型)中,我们new的所有对象都会放在heap中,而heap是所有线程共享的,且对象中的成员变量跟着对象也在heap中,因此对象中的成员变量可以被任何一个线程访问并修改; 2. 物理层面的原因(RAM——CPU cache memory——CPU): //PS:物理层面(在实际的计算机内存中)不会区分堆和线程的栈 同时满足上面2个条件,就达成了了多线程不安全的描述: 一个CPU的2个core分别执行两个线程的代码,这两个线程会共同使用到heap中一些变量,一个CPU的2个core各自将”堆和线程的栈“中数据从RAM读进CPU cache memory并暂存,各自操作完共享数据回写到RAM时,导致heap中的共享变量最终不正确; 有的时候,在这个原因上,有人会从JMM的角度回答:各个线程在各自栈中修改共享变量,赋给堆中时导致不正确 实际上,从JMM角度分析是不对的,JMM只是一个model,只是一个抽象出来的模型,实际的执行是在物理层面,而在物理层面,RAM中根本不区分JMM中的heap和线程栈,所以你回答:volatile修饰的共享变量,每次都去堆内存中读取放入栈内存,等于说:从RAM中读取,放到RAM中,显然答非所问; 所以:volatile和synchronized一定都是在物理层面起的作用,才解决了多线程安全问题 所以:从JMM角度分析虽然说得通,但不是线程不安全的本质,导致多线程不安全的本质原因是物理层面
5.1.3.4. 线程池,相关
5.1.3.4.1. 使用线程池的好处:
5.1.3.4.2. 实现 Runnable 接口和 Callable 接口的区别
5.1.3.4.3. ThreadPoolExecutor的继承结构图
5.1.3.4.4. execute()方法和submit()方法的区别是什么呢?
5.1.3.4.5. 创建线程池
5.1.3.4.6. 当ececute()/submit()任务时,execute()方法中的判断逻辑,图见ProcessOn //注意:设计的逻辑和想象的不太一样 核心思路:将Thread对象数维持在corePoolSize: Thread对象数<corePoolSize时,尽量new出新的Thread对象 Thread对象数≥corePoolSize时,优先将任务放入等待队列,而非new出新的Thread对象
5.1.4. JVM相关
5.1.4.1. JVM中的JMM(Java Memory Model, Java 内存模型) ——>ProcessOn JVM模拟的是一整台计算机,JVM中模拟memory的就是JMM
5.1.4.1.1. 在 JDK1.2 之前,Java 的内存模型实现总是从主存(即共享内存)读取变量,是不需要进行特别的注意的。而在当前的 Java 内存模型下,线程可以把变量保存本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致
5.1.4.1.2. Java memory model表明JVM如何与RAM(random-access memory, 内存) The Java virtual machine is a model of a whole computer so this model naturally includes a memory model - AKA the Java memory model. ——JVM模拟的是一整台计算机,所以JVM自然包括一个memory model,也就是Java memory model The Java memory model specifies how and when different threads can see values written to shared variables by other threads, and how to synchronize access to shared variables when necessary. ——JMM明确了:一个线程,how and when,可以看到其他线程写入共享变量的值, An object may contain methods and these methods may contain local variables. These local variables are also stored on the thread stack, even if the object the method belongs to is stored on the heap. ——一个对象可能包含成员方法,这些成员方法的局部变量会存放在创建这个对象的栈内存中,但对象永远只可能存储在heap中, 线程可以通过栈内存中的reference,访问heap中的对象,也就可以访问到对象的成员变量,会拷贝一份放到自己的栈内存中
5.1.4.2. JVM会把它管理的内存(称为运行时数据区域)分为哪些部分? ProcessOn 自动内存管理机制 首先要说明: 1. JVM就是一个应用程序,启动JVM就是启动一个进程,当我们运行 main() 函数时其实就是启动了⼀个 JVM 的进程, Java 程序天⽣就是多线程程序,进程中会有main 线程(运行 main() 函数)和多个其他线程同时运⾏
5.1.4.2.1. 对于 Java 程序员来说,在虚拟机自动内存管理机制下,不再需要像C/C++程序开发程序员这样为每一个 new 操作去写对应的 delete/free 操作,却不容易出现内存泄漏和内存溢出问题,这是因为 Java 程序员把内存控制权利交给 Java 虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那么排查错误将会是一个非常艰巨的任务
5.1.4.2.2. Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域
5.1.4.2.3. 创建一个对象的过程,在JMM中
5.1.5. JDK8新特性
5.1.5.1. 接口中可以有default方法
5.1.5.2. JDK1.8起,调整了HashMap底层结构,进而一起影响HashSet、Hashtable 底层的结构,(源码基本都是调用HashMap的方法) HashMap集合底层结构: JDK8以前,底层采用 数组+链表 JDK8以后,底层采用 数组+链表+红黑树 //当链表长度超过8,将链表转成红黑树,目的:存、取都要遍历链表,链表过长会变慢,红黑树是特殊的平衡二叉树,小的在左,大的在右 ———————— JDK1.8起,彻底改变了ConcurrentHashMap底层结构,开始采用和HashMap相同的底层结构 ConcurrentHashMap集合底层结构: JDK1.8以前,底层采用 大数组中存HashEntry(数组 + 链表(头插法))的地址 //二次哈希 JDK1.8起,底层采用 哈希表(数组) + 链表(尾插法) + 红黑树
5.1.6. JDK-JRE-JDK关系图 JDK(Java Development Kit,Java开发工具),包含了JRE和开发工具 JRE(Java Runtime Environment,Java运行环境),包含一个已编译的java程序运行时所需的所有东西,因此包含了JVM和Java的核心类库(Java API) JVM(Java Virtual Machine,Java虚拟机) 总结:我们只需安装JDK即可,它包含了java的运行环境和虚拟机 如果只是运⾏一个Java 程序,那么只需要安装 JRE 就可以了 如果需要编译Java程序,那么你就需要安装JDK,因为javac在JDK而不在JRE
5.1.6.1. 未命名
5.2. 计算机基础
5.2.1. 网络
5.2.1.1. 网络的各种协议
5.2.1.1.1. 开放式系统互联通信参考模型(英语:Open System Interconnection Reference Model,缩写为 OSI) 上层的协议使用下层的协议进行数据传输,是层层包裹的过程 应用层协议:DNS、HTTP、FTP、SMTP(邮件),单位:报文, 传输层协议:接收到数据后向上传给应用层的进程,单位: TCP:传输控制协议(Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议 UDP:⽤户数据协议(User Datagram Protocol)--提供⽆连接的, 网络层:单位:IP数据报 建立TCP连接时, 路由器与服务器通信时,需要使用数据链路层的ARP协议:将IP地址转换为MAC地址 Domain Name,域名,是因特⽹的⼀项核⼼服务,它作为可以将域名和IP地址相互映射的⼀个分布式数据库,能够使⼈更⽅便的访问 互联⽹,⽽不⽤去记住能够被机器直接读取的IP数串
5.2.1.2. HTTP协议 HyperText Transfer Protocol
5.2.1.2.1. 介绍
5.2.1.2.2. HTTP协议就是清晰的规定了HTTP request和HTTP response的组成格式
5.2.1.3. TCP,UDP 协议的区别
5.2.1.3.1. TCP(Transmission Control Protocol,传输控制协议),是一种面向连接的、可靠的、基于字节流的传输层通信协议 TCP的可靠体现在: TCP在传递数据之前,会有三次握⼿来建⽴连接, 在数据传递时,有确认、窗⼝、重传、拥塞控制机制, 在数据传完后,还会断开连接⽤来节约系统资源 这增加了许多开销,如:确认,流量控制,计时器以及连接管理等;这增大了协议的数据单元的⾸部字节数,占用更多处理机资源 TCP ⼀般⽤于⽂件传输、发送和接收邮件、远程登录等要求通信数据可靠场景 UDP(User Datagram Protocol,⽤户数据协议)--提供⽆连接的:在传送数据之前不需要先建⽴连接,远地主机在收到 UDP 报⽂后,不需要给出任何确认。 UDP⼀般⽤于即时通信,⽐如: 语⾳、视频 、直播等
5.2.1.4. TCP 三次握⼿(⾯试常客
5.2.1.4.1. 未命名
5.2.1.4.2. 发送端 ——发送——> 标有SYN的数据包(Synchronize Sequence Numbers,同步序列编号) ————> 接收端 //SYN可理解为就是一个编号 发送端 < ———— 标有SYN-ACK的数据包(Acknowledge character,确认字符) <——发送——接收端接收到数据包,又给发送端发送… 发送端 ——发送——> 标有ACK的数据包————> 接收端
5.2.1.4.3. 接收端为什么要传回SYN、ACK?
5.2.1.4.4. 为什么要三次握手
5.2.1.5. 断开⼀个 TCP 连接则需要“四次挥⼿”:
5.2.1.5.1. 客户端-发送⼀个 FIN,⽤来关闭客户端到服务器的数据传送 我说完了 服务器-收到这个 FIN,它发回⼀ 个 ACK,确认序号为收到的序号加1 。和 SYN ⼀样,⼀个FIN 将占⽤⼀个序号 回复我知道了,但接着把话讲完, 服务器-关闭与客户端的连接,发送⼀个FIN给客户端 话讲完后,最后说:我讲完了 客户端-发回 ACK 报⽂确认,并将确认序号设置为收到序号加1 我知道了
5.2.1.6. 为什么要四次挥⼿
5.2.1.6.1. 任何⼀⽅都可以在数据传送结束后发出连接释放的通知,待对⽅确认后进⼊半关闭状态。 当另⼀⽅也没有数据再发送的时候,则发出连接释放通知,对⽅确认后就完全关闭了TCP连接。 举个例⼦:A 和 B 打电话,通话即将结束后,A 说“我没啥要说的了”,B回答“我知道了”,但是 B可能还有要说的话,A 不能要求 B 跟着⾃⼰的节奏结束通话,于是 B 可能⼜巴拉巴拉说了⼀通,最后 B 说“我说完了”,A 回答“知道了”,这样通话才算结束。 释放连接的时候也是如此:客户端发起关闭连接的请求,关闭连接意味着客户端结束了自己的工作(即发送数据),但此时仍然处于数据传输的过程中,服务器可能未数据传输完毕,因此当请求到服务器时服务器知道了这个请求,但服务器数据传输未完成无法关闭连接,因此服务器先发送一个ack告诉客户端关闭请求已收到,但老子正忙,一会再关,你再等一会。等服务器工作完成了,就把fin信号发送给客户端,此时服务器要等着客户端给他一个回信,让服务器知道客户端已经知道了。因此客户端收到后就给服务器一个回信,为了防止回信丢失,客户端就再等2MSL个时间,之所以是2个,是因为涉及到来回,第一个MSL中是回信在路上的最大时间,第二个MSL是万一回信没到服务端,服务端重发的FIN确认在路上的时间(不知道这样理解对不对)
5.2.1.7. TCP 协议在传输数据时如何保证可靠传输?
5.2.1.7.1. 1. 数据被分割成 TCP 认为最适合发送的数据块。 2. TCP 给发送的每⼀个包进⾏编号,接收⽅对数据包进⾏排序,把有序数据传送给应⽤层。 3. 校验和:如果接收端收到的检验和有差错,TCP 将丢弃这个报⽂段和不确认收到此报⽂段。 5. 流量控制: TCP 连接的每⼀⽅都有固定⼤⼩的缓冲空间,TCP的接收端只允许发送端发送 接收端缓冲区能接纳的数据。当接收⽅来不及处理发送⽅的数据,能提示发送⽅降低发送的 速率,防⽌包丢失。TCP 使⽤的流量控制协议是可变⼤⼩的滑动窗⼝协议。 (TCP 利⽤滑动窗⼝实现流量控制):滑动窗口:接收⽅发送的确认报⽂中的窗⼝字段可以⽤来控制发送⽅窗⼝⼤⼩,从⽽影响发送⽅的发送速率。将窗⼝字段设置为 0,则发送⽅不能发送数据 6. 拥塞控制: 当⽹络拥塞时,减少数据的发送。 拥塞就是过多的数据注⼊到⽹络中,导致⽹络中的路由器或链路过载 流量控制往往是对点对点的通信量的控制,而拥塞控制是⼀个全局性的过程,涉及到所有的主机,所有的路由器,以及与降低⽹络传输性能有关的所有因素。 为了进⾏拥塞控制,TCP 发送⽅要维持⼀个 拥塞窗⼝(cwnd) 的状态变量。拥塞控制窗⼝的⼤⼩取决于⽹络的拥塞程度,并且动态变化。发送⽅让⾃⼰的发送窗⼝取为拥塞窗⼝和接收⽅的接受窗⼝中较小的⼀个,关于拥塞窗⼝, 慢开始:开始时,cwnd初始值为1,每经过⼀个传播轮次,cwnd翻倍 拥塞避免:每经过⼀个往返时间,发送放的cwnd加1 7. ARQ协议: 也是为了实现可靠传输的,它的基本原理就是每发完⼀个分组就停⽌发送,等 待对⽅确认。在收到确认后再发下⼀个分组。 8. 超时重传: 当 TCP 发出⼀个段后,它启动⼀个定时器,等待⽬的端确认收到这个报⽂段。 如果不能及时收到⼀个确认,将重发这个报⽂段。 ARQ协议:⾃动重传请求(Automatic Repeat-reQuest):是数据链路层和传输层的错误纠正协议之⼀。原理是使⽤确认和超时这两个机制 ARQ有 停⽌等待ARQ协议 和 连续ARQ协议 两种: 停⽌等待ARQ协议:简单但信道利⽤率低 它的基本原理就是每发完⼀个分组就停⽌发送,并设置⼀个超时计时器,等待对⽅确认(回复ACK, Acknowledge character,即确认字符),收到确认后再发下⼀个分组;如果过了⼀段时间(超时时间后),还是没有收到 ACK 确认,说明没有发送成功,需要重新发送,直到收到确认后再发下⼀个分组; 在停⽌等待协议中,若接收⽅收到重复分组,会丢弃该分组,但仍需要给发送端发送确认; 连续 ARQ 协议:可提⾼信道利⽤率,但窗口中间有一个分组丢失,后续的都要重发 发送方维持⼀个发送窗⼝,凡位于发送窗⼝内的分组可连续发送出去,⽽不需要等待对⽅确认。接收⽅⼀般采⽤累积确认,对按序到达的最后⼀个分组发送确认,表明到这个分组位置的所有分组都已经正确收到了
5.2.1.8. 在浏览器中输⼊url地址 ->> 显示主⻚的过程(⾯试常客) 首先明确:HTTP协议是使用TCP作为其传输层协议的,即HTTP报文是包裹在TCP报文中发送的,而不是UDP什么的
5.2.1.8.1. 总体来说分为以下⼏个过程: 1. DNS解析 DNS查找过程:浏览器缓存、路由器缓存、本地域名服务器上的DNS缓存 互联网上每一台计算机的唯一标识是它的IP地址,但是IP地址并不方便记忆,用户更喜欢用方便记忆的网址去寻找互联网上的其它计算机; 一个网址到IP地址的转换,这个过程就是DNS解析。它实际上充当了一个翻译的角色,实现了网址到IP地址的转换。网址到IP地址转换的过程是如何进行的? 查找www.google.com的IP地址过程(域名解析过程): 首先在本地域名服务器中查询IP地址,如果没有找到,本地域名服务器会向根域名服务器发送一个请求,如果根域名服务器也不存在该域名时,本地域名会向com顶级域名服务器发送一个请求,会告知google.com域名服务器的IP,找它去…,会告知www.google.com服务器的IP; 直到最后本地域名服务器得到google的IP地址并把它缓存到本地域名服务器本地,供下次查询使用。 从上述过程中,可以看出网址的解析是一个从右向左的过程: com -> google.com -> www.google.com。但是你是否发现少了点什么,根域名服务器的解析过程呢?事实上,真正的网址是www.google.com.,并不是我多打了一个.,这个.对应的就是根域名服务器,默认情况下所有的网址的最后一位都是.,既然是默认情况下,为了方便用户,通常都会省略,浏览器在请求DNS的时候会自动加上,所有网址真正的解析过程为: . -> .com -> google.com. -> www.google.com. DNS负载均衡 事实上,一个大型网站有千上百台服务器,DNS需要根据每台服务器的负载量,该机器离用户地理位置的距离等等,返回一个合适的机器的IP给用户,这种过程就是DNS负载均衡,又叫做DNS重定向。 2. TCP连接 三次握手 3. 发送HTTP request,过程: 按照HTTP协议规定的格式,构建出HTTP request报文, HTTP协议是使用TCP作为其传输层协议的,即HTTP报文是包裹在TCP报文中发送的(会将报文分割成很多段),通过TCP协议中连接,发送到服务器指定端口;但是这个过程中存在一定的风险,HTTP报文是明文,如果中间被截取的话会存在一些信息泄露的风险,那么在进入TCP报文之前对HTTP做一次加密就可以解决这个问题了; HTTPS协议的本质就是HTTP + SSL(or TLS);(Secure Sockets Layer, 安全socket协议)(Transport Layer Security, 传输层安全)。 HTTPS过程: 在HTTP报文进入TCP报文之前,先使用SSL对HTTP报文进行加密,加密采⽤对称加密,但对称加密的密钥⽤服务器⽅的证书进⾏了⾮对称加密;TLS/SSL使用了非对称加密,对称加密以及hash等 HTTPS在传输数据之前需要客户端与服务器进行一个握手(TLS/SSL握手),在握手过程中将确立双方加密传输数据的密码信息,具体握手过程参考阮一峰的博客TLS/SSL握手过程。 HTTPS相比于HTTP,虽然提供了安全保证,但是势必会带来一些时间上的损耗,如握手和加密等过程,是否使用HTTPS需要根据具体情况在安全和性能方面做出权衡。 4. 服务器处理请求并返回HTTP response web server从在监听的端口接收到TCP报文时会解包提取出HTTP报文,对报文进行解析,并按照HTTP协议规定的组成格式封装成HTTP request对象,供后续使用,这一部分对应于编程语言中的socket; 5. 浏览器解析渲染⻚⾯ 浏览器对HTTP response解析(解析HTML文件的DOM-Tree,解析CSS,浏览器解析引擎解析JavaScript),渲染(render) 浏览器在解析过程中,如果遇到请求外部资源时,如图像,iconfont,JS等。浏览器会重复1-6过程请求该资源。请求过程是异步的,并不会影响HTML文档进行加载,但是当文档加载过程中遇到JS文件, HTML文档加载会挂起,要等到文档中JS文件加载完毕并且解析完毕,才会继续HTML文档加载;原因是因为JS有可能修改DOM结构,这就意味着JS执行完成前,后续所有资源的下载是没有必要的,这就是JS阻塞后续资源下载的根本原因。CSS文件的加载不影响JS文件的加载,但是却影响JS文件的执行。JS代码执行前浏览器必须保证CSS文件已经下载并加载完毕。 6. 连接结束,释放连接
5.2.1.9. URI和URL的区别是什么?
5.2.1.9.1. URL(Uniform Resource Locator,统一资源定位符): URI(Uniform Resource Identifier,统一资源标志符) A URI can be further classified as a locator, a name, or both. 一个URI可以继续分类为一个URL,URN,或者同时是两者 总结: URI是以一种抽象的概念去辨别、标识一个资源,而URL和URN则是以更具体的资源标识的方式; URL是URI的子集,URL是URI的一种,所以每一个URL都是一个具体的URI,”具体“体现在指明了如何 “定位、locate” 这个资源 举例: URI是辨别、标识每一个人的一种抽象概念,URL是家庭住址,URN是名字(假设世上不存在重名问题),两者都进行唯一标识,但URL还具体的指明了如何 “定位、locate” 这个资源 可能还有有待考证的说法:URL提供使用的协议,URI不包含协议