简单三分钟自我介绍
自我介绍这里一笔带过,给对面介绍自己内在 + 外在 + 校园经历 + 校园项目 + 意向岗位
一面
1. Java基础
1.1 序列化和反序列化
首先了解一下序列化和反序列化的概念
序列化:将Java对象以二进制即字节码的形式保存在磁盘文件中,可以说是保存Java对象状态的过程,序列化可以实现对象保存的持久化;
反序列化:将保存在磁盘文件中的Java字节码重新转换为Java对象的过程;
其他特点:一般RPC
框架底层协议通信就是通过序列化和反序列化在网络上传输Java对象。
序列化和反序列化的实现主要有两种,准确来说有三种方法:
采用默认的序列化方式,即通过ObjectOutPutStream
类的writeObject(OutputStream out)
方法来序列化到输出流中
输出流可以选择文件流、也可以选择管道流,甚至是二进制流
文件流是直接写到文件中再读取转成对象,管道流是通过管道缓存数据,然后再通过输入流连接管道读取转成对象
序列化的对象必须实现Seriablized
接口,才能完成序列化和反序列化操作
1 | class Student implements Serializable { |
- 第一种实现(采用默认)
1 | //FileOutputStream fos = new FileOutputStream("D:\\student.txt"); |
- 第二种实现(自定义)
1 | // 自定义协议 比如我可以将加入对象头 cafe babe |
- 第三种(自定义)
实现接口Externalizable
并重写方法,其实跟第二种差别不大,只是第二种有默认的私有方法
1 |
|
解决反序列化破坏单例
- 在单例模式中,序列化可以破坏单例,这是需要重写
readResolve()
方法,将之前的单例实例对象返回即可保证单例。
解决反射破坏单例
反射创建对象根本也是要调用构造方法,而且可以无视构造方法的访问修饰符(public
、private
)
- Java规定反射不能破坏枚举类型,采用枚举构造单例
- 构造方法执行前,单例实例逻辑上不应该创建而且只执行一次,可判断是否已创建来避免反射创建多个实例,前提单例已先于反射
1.2 说一说数据类型有哪些
Java有八种基本数据类型 + String
1个位的bit
,布尔类型的boolean
,2个字节的short
,4个字节的int
、char
,8个字节的long
、double
、float
其中基本类型中的包装类型常用的有Integer、Long
其中Integer
会自动进行拆封装处理,也就是可以直接跟int
类型比较数值上的大小
底层在-128-127
之间会做缓存,在这之间通过Integer.valueOf(int)
创建的对象都是同一个,使用了享元设计模式
1 | System.out.println(new Integer(1) == 1); |
结果
1 | true |
1.3 数据结构
给你你个浏览器,要求设计前进和后退的数据结构,优先考虑性能
那么由于浏览器前进后退访问,是一种FIFO
的结构,比如你连续点击前进几个页面,最先进的页面最后返回,即LIFO
那么可以考虑栈的结构设计,栈结构设计要考虑性能,首先我们可以分析到,浏览器页面跳转没有涉及到页面的修改,即用于查询
那么优先考虑数组而不考虑链表
我们可以设计两个栈数组,一个用于入栈已前进的页面,一个用于入栈已后退的页面,取其中一个栈顶元素作为当前页面
2. MySQL
2.1 索引
在MySQL中,直接影响索引类型的是数据库的存储引擎
使用MyIsam存储引擎,数据文件和索引是分开的,索引会另外存储在另一个文件中
使用InnoDB存储引擎,数据和索引都存储在同一个文件中,而且是以B+数的数据结构存储,非叶子节点存储索引,叶子节点存储索引和索引对应的数据
就MyIsam引擎来说,索引中最主要的是聚簇索引,也就是主键的默认索引
单值索引和多值索引,这里是指组合索引,好的组合索引可以达到覆盖索引,可以做到避免回表,这里是因为叶子节点通过覆盖索引带了数据,因为B+树只有叶子节点带有数据,非叶子节点都是索引
而多个组合索引下,有效索引要做到左匹配,也就是必须从左顺序匹配到右查询,否则索引将失效
2.2 口述 sql
给你一张表,有三个字段,id、产品id、备注信息,现在需要你查询相同产品id的记录,然后id值相同的记录数大于等于5的产品id
首先呢,我们可以先定义这张表为S
因为涉及相同字段的记录,可以考虑直接分组,使用聚合函数
1 | select pro_id from S |
3. JVM
3.1 线程的死锁了解过吗?
比如有两个线程t1和t2,t1线程有资源r1,t2有资源r2,t1线程执行代码中需要资源r2,不过这段代码需要t2线程把资源r2释放才能执行,此时t2线程也执行代码中,释放资源前需要获取资源r1,但t2又需要r2,此时处于相互等待的状态,就导致了死锁。
从Java角度来说,资源相当于Java的锁对象,是互斥的,一个线程获取锁对象后,另一个线程只能等待,t1线程获取锁对象r1,也就是占有了锁r1,这时线程t2也是占有了锁对象r2,而线程t1需要获取锁对象r2才能往下执行代码,使得线程t1阻塞,而线程t2处于阻塞等待锁对象r1释放,才能释放锁对象r2,这样就导致死锁。(互相等待)
4. JUC
4.1 线程池有了解过吗?
线程池主要有核心线程数、救急线程和队列,队列分为阻塞队列和非阻塞队列。
目前线程池主要可以分为几类:
只有核心线程、无救急线程的线程池,此时等待队列中有要执行的任务,而核心线程在轮询地执行等待队列中的任务,如果队列满或者队列是无界队列,可能导致内存溢出问题。
一般这种线程池的做法就是使用了拒绝策略,拒绝策略可分为直接丢弃新任务、异常抛出(主动逻辑处理)、丢弃等待队列头结点、提交任务线程执行。
最后一个是推荐使用的
- 不会造成数据丢失,也就不会出现业务损失;
- 提交任务的线程被占用,新的任务不可提交,减缓任务提交的速度,相当于负反馈,能给到线程池一定的缓冲期;
只有救急线程的线程池,这种的话有线程池newCachedThreadPool
,它能无限创建救急线程,队列采用SynchronousQueue
,是一个没有容量的队列,只有线程取任务时才能提交任务;
最后是一种核心线程数只有1的线程池,没有救急线程,任务队列无界,一般作为单线程任务,这样就不会有CPU的轮询切换,任务的执行效率最高,不过请求数太多的情况下,也是容易导致内存溢出。
非阻塞队列实现的有CurrentLinkedQueue
,它是通过CAS
无锁化机制的线程池队列,每个线程通过for(;;)
执行,是一个单向且通过GC
自动回收出队节点的,利用可达性算法分析,将next
指向自己,即可触发延迟回收,利用元素的不可重用性,规避ABA
问题
5. Linux
给你一个日志文件,要求你查询最近一天内中,匹配到关键字的所有记录数据
一般日志实时查询可以使用该命令,它会实时更新,而且会处于fg模式,tail打印文件末尾记录,也就是最近
1 | tail -100f catalina.log | grep "关键字" |
而使用cat是查询历史,只能打印日志到屏幕
比如按照tail
或head
来查询关键字为20:
的后5条数据并显示行号
1 | cat -n log.log4j | grep "20:" | tail -n 5 |
时间段查询
1 | grep '2022-08-21 20:1[1-9]' log.log4j |