今天过去,"3个工作日"之期已满,我的春招应该就正式宣告结束了。大概一个月之前,特别期待写下这个题目的这一天,那时候大概没想到,结果会是这样,还是总结一下自己找工作的心路历程,多多少少算是一点可怜的经验,自己也渴望留下一点东西。
先简单介绍我的背景,二本文科院校里的计算机,2021届,春招至今0offer。这可能是我过去二十年间遇到的最大的挫折了。从小到大好像都没有遇到过太大的困难。”既不努力,也不放弃“,一句话就能总结。从小遇到的每件渴望的事情好像最终的结果都还不错,考初中,刚好过线几分,是班里的倒数,浑浑噩噩的混到了考高中的时候,发现混了中等。考高中也是刚过线,成了市里最好的高中的倒数几名,混了三年,莫名其妙又混成了班里的中等。
大学被疫情分为了两端截然不同的时期。疫情之前的大一大二在佛山校区,天堂,什么心思都没有,上完课就玩游戏。大三来到广州校区,疫情后回到学校愕然发现同学都去实习了,我连前端后端是啥都不知道。秋招的时间我开始学习,焦虑的学习。b站看了个spring-boot的视频,跟着另一个视频写了个垃圾项目就开始找实习,实力太菜一无所获。我看了一下,b站上比较火的java学习视频有三个,尚硅谷,狂神,黑马。我开始一直看狂神的视频,唉,这人喜欢吹水,听他吹水让我有种错觉:我看了他的视频,我也很厉害。还是要多看看别人的,互相参考互相印证,偏听则喑。
最后学校里的招聘会找到一个深圳的公司,招人没有什么要求,电话面试了几个问题,我就从广州来到了深圳。实习三个月,回来春招两个月,就到了现在。
上个星期面试了5、6场,全都都挂了。非常痛苦,痛苦在于:1. 网上的面经我都看得七七八八了,问我的问题基本都能知道个大概,但是一追问,问个深的,我就一点办法都没有。特别是一些需要平时长时间学习积累的,短时间内我也没有办法去提升;2. 我知道我是有机会的,运气是占一部分的,但是肯定是我太菜,总不能把原因归结于运气身上,但是我会去想如果运气好一点会不会我就进了?这一点让我非常痛苦。找到问题的原因并解决是我一直以来的思维方式。以前我清楚的知道原因在于我太菜,我就如饥似渴的看面经,搜索每一个问题,总结成自己的问题。在面试之前,这一家公司在牛客上的每一篇面经我都会看一遍,每个问题都总结成了我自己的文字。但是从上个星期开始,我看不下去了,因为基本上都看过了,八股文的每一股好像都看完了,但是我还没有工作,我不知道现在该做什么了。短时间内能够提高的内容我都学会了,需要长时间的系统的学习的东西我现在开始似乎又没有什么意义。我一直寻找的原因找不到了。那一天晚上崩溃了,在学校操场上,边跑步边哭,还好比较黑没人看得到。跑完想通了,为什么我会把唯一一个offer拒掉,因为他让我转前端,因为我不喜欢,我去干这个以后可能心里永远有刺,以后可能永远以混口饭吃的心态学习。那我还不如考公务员。家里的一直想让我考公务员,我又是个坚信自己能改变世界的啥b,所以一直嗤之以鼻。那天晚上我觉得我只能考公务员了。
下面是一些我整理的面经,都不太好,按我自己的语言习惯写的,不太甘心就这样丢掉了,在这里保留一份。废话还挺多的,两万多字。唉,两万多字的面试笔记都找不到工作。应该不能给谁提供什么帮助,如果谁看的话,多少算是能够提供一些知识点相关的搜索关键词吧。
设计模式
分三类
创建型模式:将对象的部分创建工作延迟到子类或者其他对象,从而应对需求变化为对象创建具体类型的实现引来的冲击
结构性模式:通过类继承或者对象组合获得更灵活的结构,从而应对需求变化为对象结构带来的冲击
行为型模式:通过类继承或者对象组合来划分类于对象间的职责,从而应对需求变化为多个交互对象带来的冲击
- 创建型模式
将对象的部分创建工作延迟到子类,从而用对需求变化为对象创建具体类型的实现带来的冲击 - 结构型模式
通过类继承或对象组合获得更灵活的结构,从而应对需求变化对对象结构带来的冲击 - 行为型模式
通过类继承或对象组合来划分对象间的职责
23种
单例模式
public class Singleton { private static volatile Singleton instance ; private Singleton(){} public Singleton getInstance(){ if (instance==null){ synchronized (Singleton.class){ if (instance==null){ instance=new Singleton(); } } } return instance; } }
观察者模式
代理模式
一个类代表另一个类的功能。
工厂方法
通过"对象"创建模式绕开new,来避免通过new关键字创建对象时依赖于具体类的紧耦合,从而支持对象创建的稳定,是接口抽象以后的第一步工作。
抽象工厂
提供一个接口,让该接口负责创建一系列"相关或者相互依赖的对象",无需指定他们具体的类。
工厂方法提供松耦合,抽象工厂提供高内聚。
计算机网络
osi七层网络模型
osi | 功能 | 协议 | tcp |
---|---|---|---|
应用层 | 文件传输、电子邮件 | HTTP、FTP | 应用层 |
表示层 | 编码转换、数据解析 | 应用层 | |
会话层 | 网络中端对端的通信 | DNS | 应用层 |
传输层 | 定义传输协议和端口,以及流量控制 | TCP、UDP | 传输层 |
网络层 | 控制子网的运行。 路由选择最小单位:分组报文 | ip | 网络层 |
数据链路层 | 对物理层传输的数据流包装,保证数据的可靠性。 最小传输单位:帧 | 数据链路层 | |
物理层 | 定义物理设备的标准 最小传输单位:位(比特流) | 数据链路层 |
tcp三次握手,四次挥手
seq | sequence | n.顺序 |
ack | acknowledge | v.确认 |
syn | synchronous | adj.同步的 |
fin | finish | 结束 |
psh | push | 传送 |
urg | urgent | 紧急的 |
TCP是面向连接的、可靠的、基于字节流的传输层通信协议
三次握手
- 客户端随机初始化序列号,置于TCP报文头部的Sequence Number,并把SYN控制位设置为1,将这个SYN报文发送给服务端,该报文不包含应用层数据。此时客户端处于SYN_SEND状态。
- 服务端收到客户端SYN报文后,服务端将客户端发送的客户端序列号+1后填入Acknowledgement Number,并同样也初始化自己的序列号填入SequenceNumber中,并把报文的ACK和SYN控制位都置1,再将此报文发送给客户端,此时服务端处于SYN_RCVD
- 客户端在接收到服务端的报文后,还需要向服务端回应最后一个报文,该报文的ACK控制位为1,Acknowledgement为服务端发送的SequenceNumber+1,并且该报文可以携带客户端到服务端的数据。此时客户端处于established,服务端在接收到该报文后也改为established状态,此时客户端和服务端的连接就已经建立完成
为什么需要三次握手
三次握手可以阻止历史重复连接的初始化
加入客户端发送到服务端的多个SYN报文中,旧报文比新报文更早到达了服务端,客户端就可以根据服务端返回的SYN报文中的Acknowledgement以及自身的上下文,判断这是一个历史连接,这是客户端就会向服务端发送一个RST控制位为1的报文给服务端表示中止这一次连接。
原因2是TCP通信协议双方可以借三次握手同步双方的初始化序列号。序列号是可靠传输的一个关键因素
- 接收方可以去除重复数据
- 接收方可以根据数据包序号按序接受
- 发送方可以标识发送出去的数据包中已经被接受到的。
原因三是可以避免服务端多次建立无效连接的资源浪费。
四次挥手
跟握手不同,挥手也可以是服务端先发起,发起的一端称为主动关闭方,另一端称为被动关闭方。
- 客户端准备关闭连接,会发送一个FIN标志位为1的报文,并由established进入FIN_WAIT_1状态
- 服务端接收到客户端的FIN报文后面会向客户端发送ACK应答报文,并进入CLOSED_WAIT状态
- 客户端接收到服务端的ACK报文后进入FIN_WAIT_2状态。
- 等待服务端处理完数据后,服务端会向客户端发送一个FIN报文并进入LAST_ACK状态
- 客户端接收到服务端发送的FIN报文后,向服务端发送应答报文并进入TIME_WAIT状态,并在2MSL的时间后进入CLOSE状态
- 服务端接收到客户端的ACK报文后进入CLOSE状态。
为什么需要四级挥手
- 客户端向服务端发送FIN时,仅表示客户端不再发送数据了,但是还是能接收数据
- 服务端接收到客户端的FIN报文时,先回一个ACK报文,服务端还可以继续进行数据的处理和发送,待不再发送数据时才发送FIN数据包表示同意现在关闭连接。
为什么TIME_WAIT等待时间是2MSL
https://mp.weixin.qq.com/s/lolcZPM3_pnNMlBI6cTVFw
msl是Maximum Segment Lifetime,意为报文最大生存时间,是任何报文在网络上存在的最长时间,超过这个时间的报文将被丢弃。
为什么需要TIME_WAIT状态
- 防止客户端接收旧连接的数据包,2MSL的时间客户端仍然可以接收数据包,使得再一次建立连接时再出现的数据包一定都是新建立连接产生的。
- 等待足够的时间以确保客户端最后发送的ACK报文能够让服务端接收,以帮助其正常关闭。
浏览器输入URL后
- URL解析
- DNS查询
- 建立TCP连接
- 处理请求
- 接收响应
- 渲染页面
http和https的区别
- http默认为80端口,https默认443端口
- http***l/tls加密数据包
同一个网段下的主机如何通信 不同网段下的主机如何通信
答:两台同网段主机通信;必须知道双方的mac地址就可以了。当主机A想给主机B发送数据,如何才能知道主机B的mac地址呢?通过一个叫ARP的网络协议,广播询问同网段下的主机,对应主机单播回应;对应主机收到回应后会将该MAC地址加入ARP缓存表; 不同网段下的主机如何通信? 答:获取ip-》路由器寻址
TCP和UDP的区别
- TCP面向连接,UDP面向无连接
- TCP保证数据包正确性和顺序,UDP不保证
- TCP是面向字节流的,UDP是基于数据包的
TCP的拆包和粘包
UDP基于数据包,而TCP是面向流的,没有消息保护边界,通讯的一段发送的多个数据包可能会被TCP协议打包成一个TCP报文发出去,
Http1.0 、Http1.1、Http2.0、Http3.0
http1.0相比http1.1
- http1.1支持长连接,在http1.0中浏览器的每次请求都需要和服务器建立一次TCP连接,请求完成后即断开。而http1.1中则支持一个TCP连接中传送和响应多个请求。
- 请求头中添加了主机名 hostname
http2.0相比http1.1
- 基于二进制解析
- 多路复用
http3.0
udp
Cookie Session Token
由于Http是一种无状态协议
滑动窗口
接收方根据自己的接收缓存的大小,动态的调整发送方的发送窗口的大小,即接收窗口的rwnd(接收方通过设置给发送方发送的确认报文中的窗口字段将rwnd通知给对方)。发送方的发送窗口取接收方的接收窗口和拥塞窗口的最小值
TCP流量控制
TCP通过滑动窗口实现流量控制
TCP拥塞控制
防止过多的数据注入到网络中
拥塞窗口:发送方根据自己估算的网络拥塞程度而设置的窗口值,反应网络当前容量。
拥塞控制是一个全局性的问题,流量控制是点对点的问题
慢开始->拥塞避免
HTTPS加密过程
- 客户端向服务端请求,获取服务端公钥
- 客户端生成随机对称加密的密钥,并利用服务端发送的公钥对自己生成的密钥进行非对称加密,并把结果发送给服务端。
- 服务端通过私钥进行非对称解密,获得了客户端的密钥。
- 之后的每次交换数据都是用客户端生成的对称密钥。
操作系统
进程调度算法
- 先来先服务
- 短作业优先
- 最短剩余时间
- 高响应比优先
- 优先级
- 时间片轮转
进程间通信的方式
- 匿名管道:单工,只能在父子进程间使用
- 流管道:同上但是为半双工
- 有名管道通信:半双工,允许无亲缘关系的进程间通信
- 消息队列
- 信号量,用于实现进程间的互斥与同步,常作为锁机制
- 共享内存
映射一段能够被其他线程访问的内存,这段共享内存有一个进程创建,但是多个进程都可以访问。 - 套接字
可以用于不同机器间的通信。
- 全双工:指两台通讯设备之间,允许有双向的资料传输
- 半双工:不能同时发送和接收
- 单工:只能单向传输
并发和并行
- 并发指一个处理器同时处理多个任务
- 并行指多个处理器或多个核心同时处理不同的任务。
并发是逻辑上的同时发生,并行是物理上的真实的同时发生
进程和线程的区别
进程是资源分配的最小单位,线程是cpu调度的最小单位。
相比进程,线程是更轻量级的执行单位,进程中的每个线程可以共享进程的资源,又可以独立调度。线程是Java中处理器调度的最小单位。
递归是什么,递归过多会怎样,在哪使用过递归
程序调用自身。
StackOverflowError
二叉树
队列使用过吗?队列在哪使用的比较多?平时有没有用过?
用户态和内核态
操作系统的进程的两种运行级别。用户态指特权级3级,内核态指特权级0级。
用户态到内核态的切换方式
- 系统调用。
- 异常
- 外设中断。
特权级:指操作系统中访问不同权限的资格等级。分为四级,0,1,2,3
物理内存和虚拟内存有什么区别
多次性(多次调入):基于局部性原理,在程序装入时,可以将程序中可能短时间内会用到的部分装入内存,暂时用不到的部分留在外存,就可以让程序开始执行。
对换性:在程序执行过程中,如果需要访问的信息不在内存中,由操作系统负责将所需内存从外存调入内存,然后继续执行程序。当内存空间不足时,由操作系统负责将内存中暂时用不到的信息换出到外存。
虚拟性:在操作系统的管理下,在用户看来似乎有一个比实际内存大得多的内存,这就是虚拟内存。
局部性原理
- 时间局部性:如果执行了程序中的某条指令,那么短时间内这条指令很有可能再次执行。如果某个数据被访问过,短时间内该数据很有可能再次被访问。(程序中存在大量的循环)
- 空间局部性:一旦程序访问了某个存储单元,在不久后,其附近的存储单元也很有可能再次被访问。(数据在内存中连续存放,程序的指令在内存中连续的存放)
命令
整机:top
load average:平均负载,cpu工作量的度量,进程队列的长度,一段时间内系统的平均负载。
三个值分别为1分钟、5分钟、15分钟
查看内存使用
free -m
-m表示显示结果按MB显示,无参数默认为KB
硬盘空间使用情况
df
disk free
Java
jvm
类加载的过程
java虚拟机中类加载需要加载、验证、准备、解析和初始化共5个阶段
加载
加载阶段虚拟机需要完成3件事情
- 通过一个类的全限定名来获取此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时结构
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法去这个类的各种数据的访问入口
Class对象比较特殊,存放在方法区中
连接
1.验证
验证是连接阶段的第一步,这一阶段的目的是确保Class文件中的字节流包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
验证阶段会完成四个校验动作
- 文件格式校验
- 元数据校验
- 字节码校验
- 符号引用校验
2.准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量使用的内存都将在方法区进行分配。
此阶段进行内存分配的仅包括类变量(static修饰的变量)
3.解析
解析阶段是虚拟机将常量池中的符号引用替换为直接引用的过程。
初始化
前面的类加载过程中,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段才真正开始真正执行类中定义的java程序代码。
初始化阶段是执行类构造器<clinit>()方法的过程。
<clinit>()是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的,并且编译器收集的顺序是语句在原文件中出现的顺序所决定的。即在静态语句块中只能访问在静态语句块之前定义的变量,而在静态语句块之后定义的变量能够赋值但无法访问。
在子类的<clinit>()方法执行之前,虚拟机会保证父类的<clinit>()已经执行完毕
虚拟机会保证类构造器方法<clinit>()在多线程环境下能够正确的加锁和同步,如果有多个线程同时去初始化一个类,那么只有一个线程去执行<clinit>()。
双亲委派机制
- 如果一个类加载器收到了类加载的请求,并不会马上自己去加载而是把这个请求委托给父类的加载器去执行,重复此过程直到请求到达顶层的启动类加载器。
- 如果父类加载器可以完成类加载任务则成为返回,如父类无法加载则子类加载再尝试自己去加载。
优势:
- 避免类的重复加载
- 保护程序安全,防止核心api被篡改。
两个class对象是否为同一个类
- 完成类名一致
- 类加载器一致
Program Counter Register 程序计数寄存器
线程私有,生命周期与线程保持一致
没有垃圾回收,也是唯一一个没有任何OOM情况的区域
用来存储指向下一条指令的地址,即将要执行的指令代码。由执行引擎读取下一条指令
cpu在并发切换线程时需要知道上一次该线程执行的位置。
java虚拟机栈
线程私有,生命周期和线程保持一致
保存方法的局部变量,部分结果,参与方法的调用和返回
没有垃圾回收
使用-Xss选项设置线程最大栈空间。
- 当采用固定大小的java虚拟机栈时,每一个线程的java虚拟机栈可以在线程创建的时候独立选定,如果线程请求分配的栈容量超过java虚拟机栈允许的最大容量则抛出StackOverflowError。
- 当java虚拟机栈可以动态扩展,并且在尝试扩展的时候无法获得足够的内存或在创建新线程时没有足够的内存去创建对应的虚拟机栈,java虚拟机抛出oom。
栈帧
每个方法对应一个栈帧,栈帧是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息。
栈帧内部数据结构:
- Local Variables 局部变量表
- Operand Stack 操作数栈 表达式栈
- Dynamic Linking 动态链接 指向运行时常量池的方法应用
- Return Address 方法正常退出或异常退出的定义
- 一些附加信息
方法区 元空间
java虚拟机栈中的本地变量表中存储实例的引用,引用指向线程共有的java堆中的实例,实例由方法区中的对象类型数据描述。
方法区和堆一样在jvm启动是被创建,并且实际的物理内存都可以是不连续的。
方法区的大小决定了系统中可以保存多少个类。
元空间不在虚拟机设置的内存中,而是使用本地内存,这也是元空间和永久代最大的区别。
堆
- 一个JVM实例只存在一个堆内存
- 堆在JVM启动时创建,并且在此过程中确定大小。
- 堆在物理上可以基于不连续的内存空间,但是在逻辑上视为连续的
- 堆是线程间共享的,可以划分线程私有的缓冲区。
反射
java的反射是指在程序运行状态中可以构造任意一个类的对象,可以了解任意一个对象所属的类。可以了解任意一个类的成员变量和方法,可以调用一个对象的属性和方法。
反射的三种方法获得Class对象
- 通过 Object类的getClass()方法
- 通过对象实例 .class
- 通过Class.forName("全类名")
GC 垃圾收集算法
如何判断一个对象是不是垃圾
1.引用计数算法
在对象中添加一个引用计数器,每当有一个地方引用他时,计数器加1,引用失效时,计数器减一。任何时刻计数器为0时对象就是不可能在被使用的。
难以解决对象之间相互循环引用的问题。
2.可达性分析算法
通过一系列称为"GC Roots"的根对象作为起始节点,从这些结点开始,根据引用关系向下搜索,搜索过程中所走过的路径称为"引用链"。如果某个对象到GC Roots之间没有任何引用链就认为整个对象不可达,即整个对象不可能再被引用。
如果一个对象被判定为不可达的对象,他的回收还需要至少经历两次标记的过程。
- 如果在可达性分析中一个对象发现没有与GC Roots相关联的引用链,那么整个对象将被第一次标记。
- 标记后java虚拟机还会对标记的对象进行一次筛选,判断对象是否有必要执行finalize()方法,如果对象没有覆盖finalize()方法,或该方法已经被虚拟机调用过,那么虚拟机将这两种情况都视为"没有必要执行"。
- 如果对象被判定为有必要执行finalize()方法,那么该对象将会被放置在一个队列中,收集器将对队列中的对象进行第二次的标记,然后再由一个低调度优先级的线程去执行这些对象的finalize()方法。(finalize()方法只会被系统自动调用一次)
GC Roots
固定可以作为GC Roots的对象
在虚拟机栈的栈帧中的本地变量表中引用的对象。
方法区中类静态属性引用的对象。
方法区中常量引用的对象,例如字符串常量池中的引用。
本地方法栈引用的对象
java虚拟机内部的引用,基本数据类型对应的Class对象,常驻的异常对象,系统类加载器。
所有被同步锁持有的对象(synchronized关键字)
两个分代假说
- 弱分代假说:绝大多数对象都是朝生夕死的。
- 强分代假说:熬过越多次垃圾回收过程的对象就越难以消亡。
标记-清除算法
首先标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象。也可以反过来,标记所有存活的对象,统一回收所有未被标记的对象。
缺点:
- 执行效率不稳定,标记和清除两个过程的执行效率都随对象数量增长而降低
- 标记清除之后会产生大量不连续的内存碎片,空间碎片过多可能导致当以后在程序运行过程中需要分配较大对象是无法找到足够的连续内存而不得不提前触发另一次的垃圾收集动作。
标记-复制算法
将可用内存划分为大小相等的两块,每次只使用其中一块,这一块内存用完了,就将还存活着的对象复制到另一块上面,再把已使用过的内存空间一次清理掉。
缺点:
- 如果内存中大多数对象都是存活的,这种算法将会产生大量的内存复制的开销。
- 造成空间的浪费。
标记-整理算法
标记处所有需要回收的对象,让所有的对象都往内存的一端移动,然后直接清理掉边界以外的内存。
特点:
- 不再需要额外的空间
- 但是移动存活对象并更新所有引用是一种极为负重的操作,并且必须全程暂停用户应用程序才能进行。
垃圾收集器
Serial收集器
- 单线程,进行垃圾回收时,必须暂停其他所有的工作。
- 新生代垃圾收集器
- 新生代使用标记-复制算法,老年代使用标记-整理算法
Serial adj. 顺序的
ParNew收集器
- Serial收集器的多线程并行版本。
- 新生代垃圾收集器
- 新生代使用标记-复制算法,老年代使用标记-整理算法
- 只有ParNew才能和CMS搭配使用
parallel adj.平行的
Parallel Scavenge 收集器
- 新生代垃圾收集器
- 基于标记-复制算法
- 目标是达到一个可控制的吞吐量,因此也被称为 吞吐量优先收集器
吞吐量 = 运行用户代码时间 / (运行用户代码时间+运行垃圾收集时间)
CMS 收集器
Concurrent Mark Sweep 并行标记-清除收集器
- 目标是尽可能的缩短垃圾回收的停顿时间
- 基于标记-清除算法
- 老年代的垃圾收集器
运作过程分为四个步骤,初始标记和重新标记两个步骤都需要暂停其他的所有线程。
- 初始标记
标记GC Roots能够直接关联到的对象,速度很快。 - 并发标记
从GC Roots的直接关联对象开始遍历整个对象图,耗时较长但是不需要停顿用户线程。 - 重新标记
修正并发标记期间,因用户程序继续运作而导致标记变动的一部分对象的标记。 - 并发清除
清理删除掉标记阶段判断已经死亡的对象,由于不需要移动存活对象,所以整个阶段也是可以与用户线程并发的。
CMS收集器的缺点:
- 在并发阶段CMS虽然不会导致用户线程停顿,但是由于占用了一部分内存会导致应用程序变慢,降低吞吐量。
- CMS无法清理浮动垃圾。在CMS并发标记和并发清除期间还会有新的垃圾对象产生,但是由于这一类垃圾是在标记结束后出现的,CMS无法在当次垃圾收集中处理他们。
- 基于标记清除算***产生内存的空间碎片。
GarBage First收集器 G1
- 面向堆内存的任何部分来组成回收集进行回收,而不再属于哪个分代。把连续的堆内存划分为多个大小相等的独立区域,每一个区域都可以根据需要扮演新生代的Eden空间、Survivor空间或老年代空间,收集器也能采用不同的策略去处理。
- G1收集器回去跟踪每一个Region里垃圾的回收所获得的空间大小和回收所需的时间,以维护一个优先级列表,优先回收价值收益最大的Region。
两个特点保证了G1收集器能够在有限的时间内获取尽可能高的收集效率。
停顿时间模型:在指定M毫秒的时间片段内,消耗在垃圾收集上的时间大概率不能超过N毫秒。
逃逸分析
- 方法逃逸:分析对象的动态作用域,当一个对象在方法里被定义后,他可能被外部方法所引用,如作为调用的参数传递到其他方法中。
- 线程逃逸:可能被外部线程访问到,例如赋值给可以在其他线程中访问的实例变量。
从不逃逸、方法逃逸、线程逃逸,称为对象由低到高的不同逃逸程度。
如果对象的逃逸程度较低,可以为这个对象采取优化:
- 栈上分配:如果确定一个对象不会逃逸出线程之外,可以让这个对象在栈上分配,对象所占内存空间就随着栈帧的出栈而销毁。栈上分配支持方法逃逸,不能支持线程逃逸。
- 标量替换:把一个对象拆散,根据程序访问的情况,将其用到的成员变量恢复为原始类型来访问,这个过程称为标量替换。不允许对象逃逸出方法范围。
- 同步消除:如果逃逸分析能够确定一个对象不会逃逸出线程,即无法被其他线程所访问。那么这个变量的读写也就不可能有存在线程间的竞争,对这个变量试试的同步措施就可以安全的消除掉。
空间担保机制
进行Minor GC之前jvm会检查老年代的最大可用连续内存空间是否能够容纳新生代所有的对象。如果判断通过则直接进行MinorGC,如果不够虚拟机会检查参数是否允许,如果允许则判断老年代的最大可用连续内存空间是否大于历次晋升老年代对象的平均大小,如果不允许则进行FullGC。
新生代为什么要判断老年代?
当出现Minor GC后大量对象依然存活时,需要老年代进行分配担保,存放survivor空间无法容纳的对象。
java杂项
排序算法
基本数据类型
基本类型 | 大小(字节) | 取值范围 |
---|---|---|
int | 4 | -2^31~2^31-1 |
char | 2 | |
byte | 1 | -2^7~2^7-1 |
short | 2 | -2^15~2^15-1 |
long | 8 | -2^63~2^63-1 |
float | 4 | |
double | 8 | |
boolean | 1 |
优先队列
都继承了实现了Queue接口,Queue接口继承了Collection接口
PriorityQueue
优先队列,线程不安全
ConcurrentLinkedQueue
上面的优先队列的线程安全版本
阻塞队列
ArrayBlockingQueue :由数组结构组成的有界阻塞队列
LinkedBlockingQueue :有链表结构组成的有界阻塞队列
PriorityBlockingQueue :一个由优先级堆支持的无界优先级队列。
DelayQueue :一个由优先级堆支持的、基于时间的调度队列。
SynchronousQueue :单个元素的队列
Java三大特性
- 封装
将类的信息隐藏在类内部,不允许外部程序直接访问,而是通过该类的方法实现对隐藏信息的操作和访问。 - 继承
子类继承父类中可以访问的属性和方法 - 多态
多态是同一个行为具有多个不同表现形式或形态的能力。
初始化时等号左边是接口或父类,等号右边是实现的类或子类
Java五大原则
- 单一职责原则
一个类最好只做一件事 - 开放封闭原则
对扩展开放,对修改封闭 - 里氏替换原则
子类必须能够替换基类,即任何基类出现的地方子类一定可以出现 - 依赖倒置原则
高层模块不能依赖底层模块,二者都应该依赖于抽象。抽象不能依赖实现细节,实现细节应该依赖于抽象。 - 接口隔离原则
接口应该尽可能的小而完备
依赖:A依赖B,A编译时B必须存在。
HashMap
红黑树
- 根节点是黑色的
- 所有叶子结点是黑色的
- 不能有两个相连的红色结点
- 从任一结点到其每个叶子结点的路径都包含相同数目的黑色点
数据结构为数组加链表
一个数组坐标上的链表长度大于8并且数组长度大于64时链表转为红黑树,当红黑树大小小于6时还原成链表
如果添加新节点后的map的总容量大于数组长度与负载因子的乘积时则进行扩容,扩容后的长度总是为2的幂次方数。这样扩容的好处是,结点迁移时
要么在原位置,要么在新的数组坐标为,原数组的长度+原坐标。
1.7 头插
1.8 尾插
Hash Key的具体对应规则
哈希扰动
- 首先计算key的hash值,并将hashCode异或右移的16位hashCode,让高位影响低位。
- 然后再把上一步的结果与数组长度进行与运算,以获得真正的数组坐标。
ConcurrentHashMap
- hashMap线程不安全,多线程操作下容易产生循环链表。
- hashTable线程安全,但是实现方式是在读写方法上添加synchronized关键字,效率低下
- ConcurrentHashMap
- 实现方式在1.8之前是添加分段锁,由Segement和HashEntry两种数据结构组成,Segement继承了ReetrantLock,同时也是一个子哈希表。
- 和HashMap不同,键值不允许为空。
- 如果添加的key计算的hash值坐标为空,通过cas添加
- 如果将要添加的位置有值的话,对链表的头节点或整个红黑树锁定。
- 添加完成后对map大小进行添加,首先尝试cas添加,如果添加失败,则每个线程把自己需要添加的值存放在一个数组中,map的最终大小即为原大小加上数组上的每个数。
- 如果添加过程中正在扩容,多线程协助扩容,协助的方式是每个线程负责一部分的数据迁移,任务量最少为16。
Hash冲突
- 拉链法
- 开放地址法
Java String、StringBuffer 和 StringBuilder
String是不可变的,StringBuffer和StringBuilder是可变的
StringBuffer线程安全,StringBuilder线程不安全
synchronized关键字
Java创建对象的方式
- new 构造方法创建
- 使用clone(),当前类需要实现Cloneable接口,实现clone()方法
- 反射
- Class类的newInstance()方法,只能调用无参数的构造方法,且构造方法的权限必须是public
- Constructor的newInstace()方法,对构造方法的参数数量和权限没有限制
- 使用反序列化。
创建对象时代码块的执行顺序
父类静态块——>子类静态块——>父类代码块——>父类构造器——>子类代码块——>子类构造器
静态代码块是在类加载的时候进行的。不需要对类进行实例化就会进行调用。父类先于子类执行。
子类初始化的过程中,父类的初始化方***先于子类执行。
构造代码块会先于构造方法执行。
接口和抽象类的区别
- 接口不能有私有的方法和变量,抽象类可以有
- 抽象类可以有方法的实现,接口不能有
线程池
参数
public ThreadPoolExecutor(int corePoolSize, //核心线程池大小 int maximumPoolSize, //线程池最大线程数 long keepAliveTime, //空闲线程存活时间 TimeUnit unit, //keepAliveTime时间单位 BlockingQueue<Runnable> workQueue, //进入线程池的阻塞队列 ThreadFactory threadFactory, //线程工厂 RejectedExecutionHandler handler) {
线程池阻塞队列拒绝策略 RejectedExecutionHandler
- AbortPolicy: 丢弃任务并抛出RejectedExecutionException异常
- DiscardPolicy : 丢弃任务但是不抛出异常
- DiscardOldestPolicy : 丢弃最早未处理的请求,然后重新执行。此外,如调用方法的线程关闭,则丢弃该任务。
- CallerRunsPolicy:直接在调用方法的线程中执行被拒绝的任务,如调用方法的线程已关闭则丢弃该任务。
执行流程
/** * Executes the given task sometime in the future. The task * may execute in a new thread or in an existing pooled thread. * * If the task cannot be submitted for execution, either because this * executor has been shutdown or because its capacity has been reached, * the task is handled by the current {@code RejectedExecutionHandler}. * * @param command the task to execute * @throws RejectedExecutionException at discretion of * {@code RejectedExecutionHandler}, if the task * cannot be accepted for execution * @throws NullPointerException if {@code command} is null */ public void execute(Runnable command) { if (command == null) throw new NullPointerException(); /* * Proceed in 3 steps: * * 1. If fewer than corePoolSize threads are running, try to * start a new thread with the given command as its first * task. The call to addWorker atomically checks runState and * workerCount, and so prevents false alarms that would add * threads when it shouldn't, by returning false. * * 2. If a task can be successfully queued, then we still need * to double-check whether we should have added a thread * (because existing ones died since last checking) or that * the pool shut down since entry into this method. So we * recheck state and if necessary roll back the enqueuing if * stopped, or start a new thread if there are none. * * 3. If we cannot queue task, then we try to add a new * thread. If it fails, we know we are shut down or saturated * and so reject the task. */ int c = ctl.get(); //如核心线程数未满,新建线程执行任务 if (workerCountOf(c) < corePoolSize) { if (addWorker(command, true)) return; c = ctl.get(); } //核心线程已满,尝试在阻塞队列中添加任务 if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) reject(command); else if (workerCountOf(recheck) == 0) addWorker(null, false); } //队列已满,尝试新建新建线程执行任务。 else if (!addWorker(command, false)) //线程池中线程数等于最大线程数无法创建线程,则执行拒绝策略。 reject(command); }
submit()和execute()区别
都是线程池提交任务的方法。其中submit可以提交实现了callable接口的线程以获取Future
Java内存模型
JVM运行程序的主体是线程,每个线程创建时JVM都会为其创建一个工作内存,工作内存是每个线程的私有数据区域。
Java内存模型规定所有的变量都存储在主内存,主内存是共享内存的区域,所有线程都可以访问。
线程无法直接操作主内存中的共享变量。线程对变量的操作必须先将共享变量从主内存中拷贝至本线程的工作内存中再对变量进行操作,最后将结果写回主内存中。各个线程的工作内存中存储的是准内存中的共享变量的副本拷贝。
线程无法访问其他线程的工作内存,线程间的通信只能够通过主内存来实现。
CAS 比较并交换 CompareAndSwap
判断内存中某个为的值是否为预期值,如是则更改为新的值,整个过程保证原子性。调用的是CPU并发原语,原语的执行必须是连续的且不允许被中断。
原子变量中通过调用Unsafe类的方法对变量进行赋值来保证操作的原子性。Unsafe可以直接操作特定内存中的数据。内部的变量value用volatile关键字修饰,保证了内存可见性。
public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; }
一例:
- 当两个线程A,B同时开始对变量value执行getAndAddInt方法,根据JMM模型A,B两线程在各自的工作内存中都有一个value的副本。
- 设value变量的初始值为1,A,B都通过 this.getIntVolatile(var1, var2); 得到的value的值。
- 假设A线程刚拿到值就被挂起,B则线程继续执行并调用 compareAndSwapInt(var1, var2, var5, var5 + var4) 方法将value变量改为4,由于value变量被volatile修饰,value为4立即写回主内存。B线程结束执行。
- A线程重新被唤醒,和B一样也调用 compareAndSwapInt 将本线程的工作内存中的值与主内存中的值进行对比,发现不一致,则A本次修改失败,再次循环重新获取value的值,此时再次调用 compareAndSwapInt 方法成功。
java 的引用类型
强引用、软引用、弱引用、虚引用
- 强引用
强应用指向的对象在任何时候都不会被垃圾回收器回收,如即将内存溢出则oom - 软应用
软引用指向的对象不会被jvm很快回收,当堆使用率接近阈值时才会去回收软引用的对象 - 弱引用
当垃圾回收器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象 - 虚引用
最弱的一种引用关系,一个实例是否有虚引用的存在完全不会对其生存时间产生影响。
无法通过虚引用获取该引用的实例
虚引用关联的唯一作用是,这个实例被收集器收集时会收到一个系统通知。
ThreadLocal
每个线程是一个Thread的实例,每个Thread的实例内部维护着一个ThreadLocalMap的属性成员
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; /* * InheritableThreadLocal values pertaining to this thread. This map is * maintained by the InheritableThreadLocal class. */ ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
可以通过在Thread中实例化ThreadLocal设置Thread实例的私有变量
Thread本身不是容器,存储线程私有变量的容器是ThreadLocalMap,ThreadLocal作为map结构中的key。每个线程对应一个ThreadLocalMap,可以通过实例化多个ThreadLocal设置多个线程私有变量。
但是对私有变量的操作其实是拐了一个弯,调用操作私有变量的方法的实例还是ThreadLocal,但是调用时首先获得的还是该线程实例对应的ThreadLocalMap。
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }
ThreadLocal的一个重要的问题是关于内存泄漏的,并且集中在这个弱引用的身上
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
内存泄漏的原因是,ThreadLocalMap中的ThreadLocal作为key是弱引用,如果不存在外部其他的强引用,必然会在一次GC中被收集器收集,而value还存在着强引用,这就导致了key为null,而value还存在。并且ThreadLocalMap的线程周期与Thread的生命周期一致,只有Thread死亡,value的强引用才会消失。所以每次使用ThreadLocal都需要对调用remove清除数据。
总结:每个线程为一个Thread类的实例,实例中含有ThreadLocalMap的实例属性,这个实例存储了一组以ThreadLcoal.threadLocalHashCode为键,以本地变量为值的k-v键值,ThreadLocal对象就是当前线程的ThreadLocalMap的访问入口,每一个ThreadLocal都包含了一个独一无二的threadLocalHashCode用于在线程k-v键值对中找回对应的本地线程变量。
内存溢出和内存泄漏
- 内存泄漏指已经被申请的内存空间没有被正确释放,导致后续程序中这块内存被永远占用。
- 内存溢出指存储的数据超出的指定内存空间的大小。
sql注入
{}可以防止sql注入,是预编译处理,替换为?,调用preparedStatement的set方法进行赋值,${}是字符串替换
preparedStatement调用set方法设置参数,并且还会对符号进行转义,并且不同类型的参数调用不同的set方法。
线程生命周期
- 新建
- 就绪
- 运行
- 阻塞
- 死亡
String
拼接
字符串的拼接分为两种情况:
字面量的拼接
编译器优化,直接视为拼接后的结果存在变量的拼接
实际调用的是StringBuilder
不在存储在StringPool中而是直接在堆中创建新的String实例。
String a=new String("aa") 创建了几个对象?
创建了两个,因为new在堆空间中创建了一个,并且在字符串常量池中也创建了一个。
new String("a") + new String("b") 创建了几个对象?
创建了六个对象
- 第一个对象是StringBuilder
- new
- 字符串常量池中的"a"
- 同上
- 同上
- 在由StringBuilder完成字符串变量拼接完成后返回的 toString()方法中还会创建一个新的对象
值得注意的是在后面这个问题中并没有在字符串常量池中新建字符串常量 "ab"
@Override public String toString() { // Create a copy, don't share the array return new String(value, 0, count); }
再继续上面的问题
String a=new String("1")+new String("1"); a.intern(); String b="11"; System.out.println(a==b);
输出的其实是true。
- 变量a指向堆空间中的字符串对象的实例
- a执行intern方法将字符串"11"加入到字符串常量池中,返回的字符串常量池中字符串常量的地址无变量接收
- 变量b指向字符串常量池中的字符串常量"11"
看似没什么问题应该输出false。但是实际输出true的原因是:
- 在jdk7及以后由于放弃了永久代的概念改为了元空间,并且将字符串常量池存储在了堆中
- 变量a调用intern方法时,由于StringPool和变量a同处于堆中,jvm进行了空间的优化,不再在字符串常量池中创建字符串常量,而是在字符串常量池中添加了变量a的引用。
- 变量b确实是在字符串常量池中找到了字面量,但是其内容其实是堆空间中实例的引用。
- 值得注意的是,实例a调用的intern方法至关重要,如果不执行,返回的就是false,变量b的创建和以前一样在字符串常量池中找不到"11"的字面量,故此新建一个并返回。想想其实也合理,毕竟若不是实例直接调用intern方法时方便获得实例的引用,jvm总不能在每一个字符串添加进常量池之前都在偌大的堆空间中找找有没有相同内容的字符串。
StringPool
字符串常量池是一个固定大小的HashTable,不会存储内容相同的字符串。
字面量声明的字符串会直接存储到StringPool。
StringPool存储在堆空间中。
intern() 在StringPool中查询该字符串,如不存在则将该字符串存入StringPool中并返回StringPool中该字符串的引用,如已经存在则直接返回。
对象创建的步骤
- 判断对象对应的类是否以及加载、链接、初始化
如果没有则在双亲委派机制下进行加载生成对应的Class类对象 - 为其分配内存空间,由于堆空间线程共享的特性,会有并发的安全问题。主要有两种解决方式,CAS和为每个线程预先分配空间
- 初始化分配的到的内存空间,并为所有属性设置默认值
- 设置对象的对象头(元数据信息)
- 对象属性的初始化(属性显示初始化、代码块中初始化、构造器中初始化)
对象的内存布局
动态定义的数据结构:
1.对象头 Header
对象头分为两部分
- Mark Word :第一部分用于存储对象自身运行时的数据
- 第二部分用于存储指向方法区对象类型数据的指针
2.实例数据 Instance Data
3.对齐填充 Padding
一些关键字
final
- final修饰类的时候,这个类不可被继承,并且所有的成员方法都会隐式的制定为final方法
- final修饰方法时。该方法无法被子类重写。private修饰方法时会隐式的指定为final
- 修饰变量时。
- 如修饰的是基本变量,则该变量初始化后就无法更改。
- 如果是引用类型的变量,则不能指向另外一个对象
final修饰的变量由几种赋值方式呢?3种
- 直接赋值
- 构造代码块中赋值
- 构造方法中赋值。
值得注意的是,如果final修饰的变量同时还被static修饰,将无法在构造方法中进行赋值。我们都知道static修饰的变量和方法都属于类而不属于实例。每个实例的初始化都会调用构造方法,违反了final初始化后无法更改的思想。
static
static描述的是类的信息,不需要类的实例就可以获得被static描述的属性
Error和Exception
- 都是实现了Throwable接口
- Exception是程序中可预测可恢复的问题
- Error表示程序中比较严重,表示运行时JVM中出现的问题,StackOverflowError,OutOfMemoryError
java位运算
& 与运算
都1为1
| 或运算
有1为1
^ 异或
相同为0,不同为1
Java运行参数
-Xms 堆内存的初始大小
-Xmx 堆内存的最大大小
异常分类
java的异常分为Exception和Error,都是Throwable的子类。
Error为java虚拟机内部的异常,如oom,栈溢出
Exception分为两种RuntiomeException和除了这个的子类之外的。
如果不是RuntimeException的子类,直接继承Exception则称为CheckedException,必须显示的处理异常如try catch代码块。
BIO NIO AIO
BIO Blocking IO 同步阻塞IO
应用程序发起read调用后,会一直阻塞,直到内核把数据拷贝到用户空间。
NIO IO多路复用
线程首先发起select调用,询问内核数据是否已经准备就绪,等内核把数据准备好了,再发起阻塞的read请求。
Java的NIO,有一个selector多路复用器,可以管理多个客户端的连接,等客户端的数据到了才为其服务。
select:内核提供的系统调用,支持一次查询多个系统调用的可用状态。
epoll:是select的增强版本,优化了IO的执行效率
泛型
把数据类型当作参数传递,避免了强制类型转换
java-web
Servlet的生命周期
- init()方法
调用方法对servlet对象进行初始化,只有init()方法执行后,servlet才会处于服务状态。 - service()
Servlet容器调用service方法处理客户端的请求 - destory()
Servlet回收Servlet对象
Bean的生命周期
aop
动态代理
分为两种
- 基于jdk的动态代理
代理的类和方法必须实现接口中的方法 - 基于CGILB的动态代理
可以不用实现接口
jdk动态代理
适用于由接口的业务方法。
通过newProxyInstance获取同样实现了接口的代理对象。
TestService testService = (TestService) Proxy.newProxyInstance(TestServiceImpl.class.getClassLoader(), new Class[]{TestService.class}, new TestHandler(new TestServiceImpl()));
实现InvocationHandler接口通过重写invoke方法实现增强。
public class TestHandler implements InvocationHandler { private final Object object; public TestHandler(Object object) { this.object=object; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(method.getName()+"方法执行前"); Object invoke = method.invoke(object,args); System.out.println(method.getName()+"方法执行后\t 方法返回值:"+invoke); return invoke; } }
spring 用到的设计模式
- spring容器管理的bean是单例模式和工厂模式。
- AOP利用的是代理模式
IOC
就是把Bean交给spring容器管理,通过依赖注入的方式实现外部资源的获取。
引入第三方ioc容器,利用依赖注入的方式,实现对象之间的解偶,将原本程序中手动创建对象的控制权,交给spring框架来管理。
springBoot 和 springMVC的区别
- 内置tomcat服务器
- 简化配置
spring中的事务传播行为
propagation n.传播;扩展;宣传;培养
什么是事务的传播?
多个事务方法相互调用时,这个过程中事务是如何进行管理的。
/** * Enumeration that represents transaction propagation behaviors for use * with the {@link Transactional} annotation, corresponding to the * {@link TransactionDefinition} interface. * * @author Colin Sampaleanu * @author Juergen Hoeller * @since 1.2 */ public enum Propagation { /** * Support a current transaction, create a new one if none exists. * Analogous to EJB transaction attribute of the same name. * <p>This is the default setting of a transaction annotation. */ //事务注释的默认设置 //表示当前方法必须运行在事务中,如果当前事务存在,方法将会在该事务中运行,否则将创建一个新的事务 REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED), /** * Support a current transaction, execute non-transactionally if none exists. * Analogous to EJB transaction attribute of the same name. * <p>Note: For transaction managers with transaction synchronization, * {@code SUPPORTS} is slightly different from no transaction at all, * as it defines a transaction scope that synchronization will apply for. * As a consequence, the same resources (JDBC Connection, Hibernate Session, etc) * will be shared for the entire specified scope. Note that this depends on * the actual synchronization configuration of the transaction manager. * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization */ //如果存在事务则在当前事务中执行,没有则非事务的执行 SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS), /** * Support a current transaction, throw an exception if none exists. * Analogous to EJB transaction attribute of the same name. */ //必须在当前事务中执行,当前无事务则抛出异常 MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY), //mandatory adj.强制的;法定的;义务的 /** * Create a new transaction, and suspend the current transaction if one exists. * Analogous to the EJB transaction attribute of the same name. * <p><b>NOTE:</b> Actual transaction suspension will not work out-of-the-box * on all transaction managers. This in particular applies to * {@link org.springframework.transaction.jta.JtaTransactionManager}, * which requires the {@code javax.transaction.TransactionManager} to be * made available to it (which is server-specific in standard Java EE). * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager */ //将会开启一个新的事务,如果一个事务已经存在,则先将这个事务挂起 REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW), /** * Execute non-transactionally, suspend the current transaction if one exists. * Analogous to EJB transaction attribute of the same name. * <p><b>NOTE:</b> Actual transaction suspension will not work out-of-the-box * on all transaction managers. This in particular applies to * {@link org.springframework.transaction.jta.JtaTransactionManager}, * which requires the {@code javax.transaction.TransactionManager} to be * made available to it (which is server-specific in standard Java EE). * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager */ //总是非事务的执行,如果存在一个事务,则挂起任何存在的事务 NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED), /** * Execute non-transactionally, throw an exception if a transaction exists. * Analogous to EJB transaction attribute of the same name. */ //总是非事务的执行,如存在任何事务则直接抛出异常 NEVER(TransactionDefinition.PROPAGATION_NEVER), /** * Execute within a nested transaction if a current transaction exists, * behave like {@code REQUIRED} otherwise. There is no analogous feature in EJB. * <p>Note: Actual creation of a nested transaction will only work on specific * transaction managers. Out of the box, this only applies to the JDBC * DataSourceTransactionManager. Some JTA providers might support nested * transactions as well. * @see org.springframework.jdbc.datasource.DataSourceTransactionManager */ //如果已经存在一个事务,这个方法将在嵌套事务中执行,嵌套的事务可以独立与当前的事务进行单独的提交或回滚。 NESTED(TransactionDefinition.PROPAGATION_NESTED); //nested v.嵌套 }
- 默认。必须在事务中执行,如当前存在事务则在当前事务中运行,当前不存在事务则新建一个事务执行。
- 必须在事务中执行,当前没有事务则报错
- 总会开启一个新的事务,如已经存在事务则将这个新创建的事务挂起
- 当前存在事务则在当前事务中执行,当前不存在事务则非事务的执行。
- 总是非事务的执行,ru'dang'qian
网关的作用
拦截所有的请求,分发到各个微服务上。
可以实现日志拦截,权限控制、解决跨域、限流、熔断、负载均衡、隐藏服务端真实请求路径,黑名单与白名单的拦截和授权。
创建Bean的几种方式
- 构造器创建
- 调用静态工厂方法创建
- 调用实例工厂方法创建
锁
线程安全
当多个线程同时访问同一对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或在调用方法时进行任何其他的协调工作,调用这个对象的行为都可以获得正确的结果,那么就称这个对象是线程安全的。
可重入锁
可重入锁也叫递归锁,指在同一线程中,外层函数获得锁后,内存函数自动获得锁。或同一个线程再次进入同步代码时可以使用自己已经获得的锁。
公平锁和非公平锁
- 公平锁
指多个线程在等待同一个锁的时候,必须按照申请锁的时间顺序来依次获得锁。ReentrantLock使用公平锁将导致性能下降。但是可以保证每一个线程都可以获得锁。 - 非公平锁
在锁被释放时,任何一个等待锁的线程都有机会获得锁。
自旋锁
尝试获取锁对象但没有成功的线程,暂时不放弃cpu时间,进行一个忙循环,认为持有锁的线程很快就会释放锁。
自适应自旋:自旋的时间不再固定。虚拟机认为自旋很有可能成功时会允许自旋持续更长的时间。
CAS
ABA问题:一个线程对变量的初次读取为A值,修改时检查仍为A值,无法保证过程中变量曾经没有被修改过。
自旋锁和cas的区别:cas是利用操作系统指令完成的功能,自旋锁使用cas完成。
ReentrantLock
ReentrantLock和Synchronized的区别:
- synchronized是语法层面的同步。
- ReentrantLock是Api层面的互斥
ReentrantLock默认为非公平锁。通过内部继承的AbstractQueuedSynchronizer实现锁和线程同步。
ReentrantLock相比synchronized提供了一些高级的功能
- 等待可中断
当持有锁的线程长期不释放锁的时候,等待的线程可以放弃等待改为处理其他事情。
tryLock() - 公平锁
- 一个ReentrantLock绑定多个Condition对象,分别唤醒和阻塞。
抽象队列同步器 AQS
- 通过CLH队列和资源状态量state实现,state为0表示锁没有被任何线程持有,为1表示被1个线程持有
- 每个线程被包装成一个node结点,每个node节点中还有一个整型属性waitStatus表示当前线程等待的状态。
- 非公平锁再入队前还会通过cas尝试获取锁,如恰好锁资源被其他线程释放且恰好被本线程获取则中断入队
- 队列中的第一个结点内部的线程是空的,每个新的线程获取到锁资源后前一个空的结点出队,同时本结点中的线程置空并把本结点作为队列中的头结点。
- 最后没能获取锁并不会一直自旋,而是通过LockSupport阻塞
LockSupport
- 基于许可证
- 每个线程的许可证初始为0,最大为1
- 当线程的许可证数量为0时执行park方法,线程阻塞
- 线程许可证数量为1时执行park方法,线程许可证数量减一,继续执行
- 线程许可证数量的初始化在线程就绪过程中完成
Synchronized
非公平锁,并且也是可重入锁
加在static方法上时,锁的时Class对象
加在普通方法上作为关键字时与synchronized(this)相同,锁对象为当前的实例。
分为重量级锁、轻量级锁、偏向锁,他们的作用:
重量级锁
可以认为直接对底层的操作系统的互斥量mutex操作。这种同步方式的成本非常高,包括系统调用引起的用户态和内核态的切换、线程阻塞造成的线程切换等。java线程是映射到操作系统中的原生内核线程上的,阻塞和唤醒线程需要操作系统的协助,这就无可避免的陷入用户态和内核态间的转换中轻量级锁
在没有多线程竞争的前提下,减少传统重量级锁使用操作系统互斥量产生的性能消耗。
当一个线程访问同步代码块并获取锁的时候,会在对象头和栈帧中的锁记录中存储偏向锁的线程id
以后在该线程进入和完成同步代码块的时候不需要进行CAS操作来加锁或解锁,只需要校验对象头中的MarkWord中的线程ID是否为当前线程ID。
轻量级锁的工作过程:
- 在代码即将进入同步块时,如果同步对象还有被锁定(标志位为01),虚拟机将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用来存储锁对象目前Mark Word的拷贝。
- 轻量级锁的加锁和解锁
- 然后虚拟机将使用CAS尝试把对象的MarkWord更新为指向LockRecord的指针,如果更新成功则表示线程拥有了这个对象的锁,并且对象的MarkWord的锁标志位将转变为“00”,即表示对象处于轻量级锁定状态。
- 如果更新操作失败了。那么意味着还有其他线程尝试获取该对象的锁,虚拟机将会检查对象的MarkWord是否指向当前线程的栈帧,如果是则表明该线程已经拥有了这个对象的锁,否则说明对象被其他线程抢占。轻量级锁不再有效,将会膨胀为重量级锁
- 解锁的过程同样通过CAS完成,如果对象的MarkWord仍然指向线程的锁记录,虚拟机就会尝试利用CAS替换回来。如果成功替换则代表同步过程顺利完成,失败则代表其他线程尝试获取过该锁对象,此时需要再释放锁的同时唤醒被挂起的线程。
- 加锁操作同2.1 此外设置偏向模式为1。一旦出现另外一个线程去尝试获取这个锁,偏向模式会立即结束,根据锁对象目前是否处于锁定状态决定是否撤销偏向,撤销后恢复至“未锁定”或“轻量级锁定”
锁升级过程
- 一个对象刚开始实例化,没有任何线程访问他的时候,他就是是可偏向的。也就是说,他现在认为只会有一个线程会访问他,因此当第一个线程访问他时,锁定的对象会偏向这个线程,此时代表着对象持有偏向锁。
- 当第二个线程访问锁定对象时,MarkWord中的线程ID指向的并不是本线程,本线程B通过CAS尝试获取锁,如果获取锁成功则将锁定对象的MarkWord中的线程ID设置为当前线程的线程ID。如果CAS失败,则表示对于该锁定的对象存在线程之间的竞争关系。当到达全局安全点的时候会首先暂停只有偏向锁的线程A,并检查线程是否存活。如果持有锁的A线程不处于活动状态,则将对象头设置为无锁状态并重新偏向新的线程。如果A线程依然处于存活状态则撤销偏向锁膨胀为轻量级锁。
- 轻量级锁认为锁竞争存在,但是竞争的程度不激烈,当一个线程的自旋超过了一定的次数,或竞争锁的线程过多,轻量级锁就会膨胀为重量级锁。
锁粗化
当一系列连续的或在循环中进行的操作都会频繁的加锁解锁,Jvm将自动扩大锁同步的范围,只需要加锁一次
public String concatString(String s1, String s2, String s3) { StringBuffer sb = new StringBuffer(); sb.append(s1); sb.append(s2); sb.append(s3); return sb.toString(); }
锁消除
jvm对同步代码块进行逃逸分析,如果同步代码块中的堆上的数据不会逃逸到其他线程中,那么就可以认为他们是线程私有的,无需进行同步加锁。
死锁样例
Object lock1 = new Object(); Object lock2 = new Object(); Thread threadA=new Thread(()->{ synchronized (lock1){ System.out.println("threadA get the lock1"); synchronized (lock2){ System.out.println("threadA get the lock2"); } } },"A"); Thread threadB=new Thread(()->{ synchronized (lock2){ System.out.println("threadA get the lock1"); synchronized (lock1){ System.out.println("threadA get the lock2"); } } },"B"); threadA.start(); threadB.start();
JUC
cyclicBarrier
public static void main(String[] args) { CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> System.out.println("cyclicBarrier completed")); for (int i = 0; i < 7; i++) { new Thread(()->{ System.out.println(Thread.currentThread().getName()+"\tstart"); try { Thread.sleep(1000); cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"\tend"); },"T"+i).start(); } }
Semaphore
争车位,线程单位的令牌桶
线程安全
可以分为五类
- 不可变
- 绝对线程安全。
当多个线程同时访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果。
快照访问。 - 相对线程安全
对这个对象的单次操作是线程安全的。 - 线程兼容
对象本身不是线程安全的,但是可以通过调用端正确的使用同步手段来保证对象在线程并发环境中可以安全的使用。 - 线程对立
数据结构
跳表
中间件
MQ
rabbitMQ消息确认机制
分为三种
消息到达Broker消息代理时
发送端消息抵达队列时
消息被成功消费时
1.和2.通过RabbitTemplate设置,并在Configuration中通过@PostConstruct注解设置
消费端确认可以通过消费者手动确认模式。
- 消息没被消费时消息的状态为Ready
- 消息被接收但是没有被确认时为Unacked状态
- 默认为自动消息确认,接收即确认后消息从RabbitMq中删除,
- 如果开启手动确认模式,在没有手动确认时,即使消息已经被接收变成了Unacked状态,这时假如服务器宕机,消息会重新变成Ready状态被其他消费者消费。
- 也可以通过消费者Rabbithandler中的参数Channel的方法对不同的消息进行不同的操作,(确认,不确认并废弃消息,不确认并将消息重新投递)
MySql
数据库的四种隔离级别
- 读取未提交
- 读取已提交
- 可重复读(行锁)
- 串行化(表锁)
hash索引和B+树索引的优劣
hash索引:
- hash在数据量小时进行等值索引可能更快,如果数据量大发生大量hash碰撞就不一定,并且性能不可预测。
- 无法进行返回查询,模糊查询,因为hash值不可预测。
b+树索引
- 支持范围查询
- 查询效率稳定,都是从根节点查询到叶子结点。
B树和B+树的区别
- B树的每个结点都存储key和data,叶子结点指向null
- B+树只有叶子结点会存储key和data,非叶子结点只存储key。叶子结点包含了全部关键字的信息。叶子节点之间两两相连,可在范围查询中发挥作用。
那叶子节点上的data是什么?
如果是聚集索引,叶子节点上存储的是整条记录。普通索引的叶子结点存放的是主键索引的值。
回表表述的就是这样一个过程,当查询的是普通索引时,先通过普通索引找到主键索引的值,再通过主键索引找到行记录。
MyISAM和InnoDB存储索引的区别
- MyISAM结点中的data存放的是数据地址索引放在 *.MYI中,数据放在 *.MYD中,称为非聚簇索引
- InnoDB中结点中的data存放是的数据本身,数据和索引的文件称为 *.IDB
同一个网段下的主机如何通信 不同网段下的主机如何通信 答:两台同网段主机通信;必须知道双方的mac地址就可以了。当主机A想给主机B发送数据,如何才能知道主机B的mac地址呢?通过一个叫ARP的网络协议,广播询问同网段下的主机,对应主机单播回应;对应主机收到回应后会将该MAC地址加入ARP缓存表; 不同网段下的主机如何通信? 答:获取ip-》路由器寻址
MyIsam和InnoDB的区别
- InnoDB支持事务,如没有组成事务,innoDB会将每一条sql默认封装城事务,自动提交。MyIsam不支持事务
- MyIsam不支持外键
- InnoDB是聚集索引,MyIsam是非聚集索引
- Inno支持表锁,默认行锁。MyIsam只支持表锁
选择:大多数是查询操作可以选择MyIsam。
mysql的几种索引
- 普通索引
- 唯一索引
允许空值,索引值必须唯一,如果是组合索引,则组合的值必须唯一 - 主键索引
不允许空值的唯一索引 - 组合索引
最左前缀 - 全文索引
覆盖索引
覆盖索引不是索引的类型,而是查询索引的行为,表示索引已经覆盖了查询的需求,不必进行回表。
mysql日志系统
binlog、 redo log 、undo log
bin log是mysql server层的日志记录,而redo log 和 undo log是存储引擎层的的日志记录。
- bin log记录的是事务的操作,在事务最终提交前写入,redo log和undo log在事务的执行过程中会不断的写入。
- redo log提供前滚操作,将多次的io操作打包成一次redo log以提高性能,通过redo log的持久化来保证事务的持久性
- undo log记录的是事务操作前的数据信息,可以在事务失败时进行回滚保证事务的原子性。
acid
原子性 atomicity
一个事务中对事务的操作要么全部执行成功,要么全部执行失败。实现基于redo log和undo log。
一致性 Consistent
一致性指执行事务前后的状态要一致,可以理解为数据一致性。
隔离性 isolation
指事务之间相互隔离,不受影响。四种隔离级别。
持久性 Durable
一个事务提交后,这个事务的状态会被持久化到数据库中,即事务的提交。
慢查询
- 可以通过explain查看查询命中索引的信息。
- 开启慢查询日志,超过时间阈值的都会添加至慢查询日志中。
共享锁和排他锁
共享锁
- 用于不更改数据的操作。
- 当一个事务对数据添加共享锁后,其他事务只能够对该数据添加共享锁,不能添加排他锁。
排他锁
- 可以更改数据
- 当一个事务对数据添加排他锁后,其他事务不能再对数据添加任何锁。
三大范式
- 每一列都是不可再分的原子数据项。
- 实体的属性必须完全依赖于主键,即行记录必须可以被唯一的区分。
- 每列都和主键值直接相关。
MVCC
多版本并发控制,使用一致性视图,用于读取已提交和可重复读的实现。
在可重复读的隔离级别,在事务开始时创建一致性视图,之后在这个事务中的查询中都使用这个一致性视图。
读取已提交每次查询都会创建一个新的视图。
两个事务并发操作
事务1和事务2同时对一条记录进行更新操作,如果事务1先取得行锁,事务2就会阻塞,如果事务1长时间没有释放锁,事务2就会超时异常。
Redis
redis分布式锁
- 过期时间
- 删除时,只能删除自己的,并且删除必须只能原子操作。
持久化
redis提供了两种持久化方案
- RDB redis database
在不同的时间点上将redis中存储的数据以快照的形式持久化
redis会单独创建一个子线程,去往一个临时文件中写入redis中的数据,写入完成后在用这一个临时文件替换上一次的持久化好的文件。
性能较高,但是不保证数据恢复的完整性。 - AOF Append Only File
只允许追加的文件。
将redis执行过的写指令存储下来,在数据恢复时在按顺序执行一遍。
缓存穿透、缓存雪崩、缓存击穿
- 缓存穿透指查找一个不存在的数据时,每次找都不存在,每次都查数据库
- 缓存雪崩指大量key同一时间过期。
- 缓存击穿,数据在缓存中过期时,刚好并发过多同时查询了数据库
底层数据结构
简单动态字符串 simple dynamic string sds
数据结构中有专门存储长度的变量。
哨兵模式
哨兵是一个独立的进程,哨兵通过发送命令,等待redis服务器响应,从而监控多个redis实例,包括主机和从机
当哨兵检测到主机宕机,会自动将一个从机切换为新的主机。然后通过发布订阅模式通知其他的从机,修改配置文件,切换新的主机
如何设置哨兵结点
- 修改从服务器配置文件,指定主机ip端口号和密码
- 修改哨兵配置文件,设置redis主机ip、端口、quorum,redis主机服务器密码。
哨兵结点如何监控其他的结点
- 每个哨兵结点每隔十秒向主机和从机发送info命令获取最新的拓扑结构
- 每个哨兵结点每个两秒向redis数据结点的指定频道上发送哨兵结点对于主节点的判断以及当前哨兵结点的信息,每个哨兵结点也会订阅该频道,来了解其他哨兵结点的信息以及对主节点的判断。publish/subscribe
- 心跳检测:每隔一秒每个哨兵结点会向所有节点包括主机、从机和所有哨兵结点发送ping命令
选举过程
1.主观下线
如果结点没有回复哨兵结点的ping,则该结点被此哨兵结点主观下线
2.客观下线
如果超过一定数量的哨兵结点都认为该结点主观下线则该结点客观下线。
3.哨兵结点选举leader
察觉主观下线的哨兵结点请求其他哨兵结点将自己选举为leader,其他哨兵结点可以同意或拒绝,如果同意的数量同时大于等于quorum和哨兵结点数除以2加1,则该哨兵结点成为哨兵leader
4.哨兵leader决定新的主节点
CAP理论
- 一致性 Consistency
在分布式系统中所有的数据备份在同一时刻是否为同样的值。 - 可用性 Availability
任何客户端的请求都能够得到服务器非错误的响应。 - 分区容错性 Partition tolerance
系统中任意信息的丢失或失败不会影响系统的继续运作
BASE理论
- BA :基本可用 Basically Avaliablity
系统中出现了不可预知的故障,但还是能用,相比较正常的系统而言会有响应时间和功能上的损失。 - S:Soft State 软状态 状态可以有一段时间不同步
允许系统中的数据存在中间状态,并认为该状态不影响系统的整体可用性(允许系统在多个不同结点的数据副本存在数据延时) - E :Eventually Consistent 最终一致
在软状态的期限过后应当保证所有副本保持数据一致性。
Redis淘汰策略
- 直接报错
- 删除最近最少使用(全部key或只删除设置了过期时间的key)
- 随机删除(全部key或只删除设置了过期时间的key)
- 只删除设计了过期时间的key,优先删除ttl短的key
通过设置配置文件中的 maxmemory-policy 设置
数据结构
String sds
list 双向链表
hash 数组加链表
set 数组加链表
zset 跳表
杂项
https://segmentfault.com/a/1190000003063859
Linux IO模式
- 阻塞I/O
- 非阻塞I/O
- I/O多路复用
- 信号驱动I/O
- 异步I/O
I/O 多路复用之select、poll、epoll
select,poll,epoll都是IO多路复用的机制
项目中遇到的问题
- 项目创建时勾选的jar包版本不匹配,openFeign运行提示缺少一个负载均衡的包
- 手动添加依赖,还是不行
- 修改至合适的版本,发现可行
- 然后发现其实不太适合(使用频率低、启动慢),改为restTemplate
快速排序
快速排序的两种交换方式
第一种:
- 从右边开始,当遇到第一个小于锚点数,把右指针指向的数赋值给左指针(因为第一个左指针是锚点数,所以不会丢失。此时数组上有两个右指针的值,右指针额外占据的值被锚点值记录)。
- 左指针开始移动,寻找第一个大于锚点的值(因为此时左指针指向的值是右指针刚刚赋值的,所以一定小于锚点值,所以第一次循环必定触发)。找到第一个大于锚点值时,如果没有越界,直接赋值给右指针,此时右指针是有两份的,赋值不会丢失此时右指针的值,另一份就是左指针这一次循环的起点。
- 循环结束的情况之一是,右指针已经把值赋给左指针后,左指针开始向右移动准备寻找一个大于锚点值的值赋值给左指针,但是只能遍历到右指针处,结束循环。
- 此时直接把锚点值赋值给左右指针同时指向的值,因为这个值在循环时已经给其他地方赋值过,所以有两份,左指针最后结束遍历的话,这个值在此时指针的左边,右指针结束遍历的话这个值在指针右边。
public void quickSort(int[] arr,int l,int r){ if (l>=r){ return; } int poi=arr[l]; int low=l; int high=r; while (low<high){ while (low<high&&arr[high]>=poi){ high--; } if (low<high){ arr[low]=arr[high]; low++; } while (low<high&&arr[low]<=poi){ low++; } if (low<high){ arr[high]=arr[low]; high--; } } arr[low]=poi; quickSort(arr,l,low-1); quickSort(arr,low+1,r); }
这种是一种显示的交换方式
- 每次右指针开始向右移动,寻找第一个小于锚点值的坐标,找到后先不进行操作,待左指针找到第一个大于锚点值时,一次性交换左右指针。
- 结束循环的情况有两种:1.右指针找到最后一个小于锚点值的坐标,结束循环,左指针开始向右移动尝试找到大于锚点值的左边,但是没有找到,遇到了右指针结束循环。2.上一次的交换完成后右指针开始向左移动尝试寻找小于锚点值的数,没有找到遇到了左指针,此时左指针还没有移动,这个数是上一次自己交换出去的数。
- 交换锚点所在的坐标。
注意右指针必须先移动
public void quickSort(int[] arr,int l,int r){ if (l>=r){ return; } int low=l; int high=r; while (low<high){ while (low<high&&arr[high]>=arr[l]){ high--; } while (low<high&&arr[low]<=arr[l]){ low++; } if (low<high){ swap(arr,low,high); } } swap(arr,l,low); quickSort(arr,l,low-1); quickSort(arr,low+1,r); }
面试笔记
2021.03.29 金蝶 java 春招
spring中事务的传播方式(不会)
事务的回滚机制(不会)
mybatis防止sql注入
项目中的redis分布式锁是怎么用的(没答好,项目中用的redisson,直接回答一个自己手动实现的应该会好很多)
redis分布式锁的优缺点(不会)
redis的基本数据结构
redis为什么是高可用的(不会)
redis的哨兵模式是啥,选举从机作为主机的过程(只说了是什么)
springBoot相比ssm的优点
项目中的注册中心和配置中心(我说用的是nacos,面试官让我说eureka????)
实习经历,问我做一个什么样的项目,取得了什么成果,有什么收获
tcp的三次握手,挥手为什么四次,time_wait的状态
tcp报文过长拆包的时候,怎么知道报文什么时候传完
进程间通讯的方式
知道socket吗?
进程和线程的区别
平衡二叉树,平衡二叉树出现的背景(猜对了哈哈哈),关于平衡二叉树的算法
深度优先和广度优先
java中error和exception的区别
hashmap底层的数据结构(我只说了jdk1.8的又追问了我1.7的,但是没往深了问)
jdk1.8的新特性(说了流式)
synchronized在jdk7和8中的区别(这个不知道,隐约记得jdk6的轻量级锁和偏向锁,就说了这个,但是面试官好像不太满意没让我继续说)
双亲委派机制
GC算法
jvm还有哪些了解的知识(说了synchronized实现的原理,没让我说,问我还有其他的吗,我说没有了)
线程池的核心线程数和最大线程数
线程池的拒绝策略,实际生产中用哪个?
如何获取异步线程的返回结果(不会,只说了实现callable接口可以获得结果,他让我说说还有没有其他的方法,又说了动态代理)
联合索引
sql去重
我:建立唯一索引
面试官:表一开始有重复数据呢?
我:建立新表建唯一索引,从旧表中插入
面试官:如果不建新表呢?不会了
(淦,我以为他的意思是让我空间复杂为O(1)的在mysql中去重,现在回看的时候发现当时听漏了,他只说在查询中去重。。。。)聚簇索引和非聚簇索引
innodb中索引的数据结构
索引查找数据的过程
悲观锁和乐观锁,什么情况下用什么?数据强一致性用什么?
上一个答了ABA,问我知道雪崩吗?(只说了redis的缓存雪崩)
不用递归如何遍历二叉树
osi七层网络模型
TCP和UDP是哪层
应用层有那些协议?
数据链路层有哪些协议?
我有什么问题?
看金蝶去年秋招的时候的面经有的说过了一面当天会发测评,我到现在还没收到,估计是凉了。总的来说面试的体验还行,小哥还挺有亲和力,会引导。看着录像回顾整理了一遍,自己也吓一跳,四十分钟怎么问了这么多问题,估计是有太多不会的直接就过下一个问题了。之前的复习主要都集中在java上,这次面试没有问多少,感觉有力没使出来。
2021.03.31 伴鱼 java 春招 一面
手写算法
第一题是按层次遍历二叉树,第一层从左到右第二层从右到左,以此类推。二叉树利用数组输入。
我的想法是既然是用数组输入,那么可以当作完全二叉树处理,为0就跳过。不为0就视为当前结点存在,完全二叉树的话就可以通过当前深度确定每一层的边界即最左结点和最右结点,第一层循环对深度遍历,第二层循环对这一层的每一个结点遍历,通过深度对2取余的结果作为第二层循环是++还是--。但是面试官说我的想法有问题,让我用队列做,没想出来,就换了一道题。
第二题是leetcode上的原题,空间复杂度o(1)判断链表是否为回文链表。
面试的规则是半小时内写出来一道题,不会可以换题,但时间不会重置。公司效率很高,面完不到10分钟,还没跟舍友吐槽完,hr的电话就来了。
2021.04.15 神策 java 春招 一面
面试官一来自己先自我介绍,呜呜呜莫名感动。然后说了一下面试的流程,分为两部分,先写算法,再问基础。
- 自我介绍
- 屏幕共享,层次遍历二叉树,写完自己运行。
太尴尬了,因为之前刷链表二叉树的题都是直接复制到内部类,这个树的类结构要自己写,二叉树的类发现ide报错不知道哪里写错了,人都傻了,问了一下面试官能不能复制之前的,因为上午刚刷一道二叉树的题。写完之后让我运行,因为是树是内部类,静态的main方法不能直接创建,我又把二叉树专门创建了一个类(其实直接改成静态内部类就行)。 - 怎么去限制java服务占用的最大内存。我说了个堆内存的,问我还有吗。没了
- Map容器,按put顺序取出。LinkedHashMap
- 对 “线程安全的理解”
- 经典之经典HashMap、HashTable、ConcurrentHashMap。
- 能不能在创建HashMap的时候通过某些设置减少HashMap中哈希冲突的概率。
我说构造方法传整数让数组大一点。
然后问我还有吗。我答不会,说是装载因子。 - 如何判断对象生存还是死亡。
- 垃圾回收器
- 知道深拷贝、浅拷贝不?
知道,Object的方法是浅拷贝
那怎么实现深拷贝
重写clone
其他方法?
(唉,当时没想到,网上搜一圈,从序列化里面生成对象就是深拷贝,之前实习的干的最多的就是从json里面解析对象,现在想来就是这个意思,哭了) - 有没有用过ThreadLocal
唉,确实没用过,八股也没背全,只记得数据结构没记使用场景 - 二叉树有几种遍历?问我中序遍历怎么实现
又尴尬了,没表达好,讲着讲着自己晕了讲成先序遍历。
因为我讲的的是递归的方法,又问我如果不用递归呢?我说递归是隐式的栈,不递归就创建一个栈,问我具体流程,不会。 - 堆。使用场景,答了个top K,问我具体的流程,不会,呜呜呜怎么每个问多几次就不会了。
- 经典之经典TCP、UDP
- 经典之经典进程和线程
- 经典之经典进程间通信
- 反问
面试通知邮件上写的预计面试市场45分钟,实际40分钟,不敢想代表什么。。。
2021.04.15 唯品会 java 春招 一面
虽然没拿到几个offer,但是也面试过好多次了,还是第一次见到手机小程序面试的,并且这个小程序电脑端的微信打不开,只能用手机面试。手机的信号太垃圾,加上宿舍信号不好导致面试过程一直因为网络问题卡顿,还好面试官看起来没有不耐烦。
- 经典Java的集合了解吗(不太常规啊,我说完线程不安全的,没追问线程安全)
- 重载
- 线程的实现方式
- wait和sleep的区别
- 为什么上面两个方法必须要在try catch中?
- java的锁
- synchronized偏向锁、轻量级锁、重量级锁
- 可以重入吗?
- 讲讲spring
- springMVC执行流程
- springMVC常用的注解
- 数据库的三范式
- 数据库事务的隔离级别
- select * from xxx where xxx group by xxx 几个关键字的执行顺序,加个having呢?
- 怎么检查一个索引语句是否生效
- 如果一个查询语句比较慢,从什么地方开始着手解决。
- 数据结构平时有了解吗?
- B树和B+树
- B+树叶子节点存储的是索引还是真正的数据
- 数组有什么特点
- 索引为什么从 零角标 开始吗?(不知道是不是这个 lingJiaoBiao 从来没听说过)
- 你们现在还没毕业是吧?
- 稍微微问了一下实习,实习的内容和使用的技术栈
- 简历上的个人项目(唯品会投的很早,笔试之后又等了很久才面试,久到我已经把简历上的项目给换了,结果就是面试官手上我的简历里的项目都快忘光了)
- 你这个商品后台的商品有什么基础信息?
- 上面讲到了redis分布式锁,reids数据结构,底层实现
- 有看什么书吗?嗨呀,这个问题我还真有话说,我这人比较浮躁估计有好几年没看过课本外的书了。最近面试老有面试官问我java的垃圾回收,以前遇到问题我都是直接搜索引擎搜索。但是这个问题好像不太管用,因为这东西也看不见摸不着,jdk和一些框架我还能按住ctrl和鼠标左键点进去看看底层的代码,这东西不知道在哪看,我老怕写这些技术博客的人骗我,毕竟写这东西的门槛太低了,我都能写,对吧?我确实也对陌生人缺少信任,以前搜问题,我都是一次性把搜索引擎第一页的搜索结果一次性全打开互相印证,一旦发现哪个不懂装懂我还会在心里骂他,全部看完再挑一个看起来还不错的试试他的测试的例子,我才能相信这些人说的是真的。不过这些技术博客的质量也确实迫切的需要一个可以量化的标准以及排行榜,哔哩哔哩挑个视频还能点个按收藏数排序呢。扯远了,为了获取更权威的内容我就买了本书,就是那本著名的紫皮的深入理解java虚拟机,写书还是有门槛的,我就姑且相信他是对的,一看我也很震惊,我竟然看的下去,不知道是不是计算机组成原理那一系列黑皮书给我整成心理阴影了。
- 看到了书里垃圾回收器那一章了吗?然后我就巴拉巴拉讲垃圾回收器,毕竟我这水平也就是看前几章的水平。
- 近两年的工作规划。
- 反问。
全程不到四十分钟。我从来没有想过,面试也能是个体力活,整一个面试过程一直卡,为了能找个信号好的地方跑了一整层宿舍楼,为啥不能电脑面试啊。。。一般我都习惯当天面试,当天回看录像,一边看录像,一边像这样记问题,因为时间上没有间隔太久,面试的结果不太能这么快出来,所以每次都是虔诚的怀抱希望,一边在标题上敲下凉了,一边心理求神拜佛希望进下一轮面试。但是这次有点事,没有当晚回看,时间已经过了一天了,呜呜呜不会真凉了吧。
全部评论
(7) 回帖