Java面经学习1

背景:

暑期实习招聘还没完全结束,有些提前批现在都已经开了,算法刷题的时候八股也要积累:)

源自:

  1. https://www.nowcoder.com/feed/main/detail/16cfeb34d1274fae8c226dfd1d3d7ca4
  2. https://www.nowcoder.com/discuss/619478954549813248

1.

一面(技术面):
怎么判断这个sql执行的好坏?
索引失效的场景?
mybatis的一个#和$的区别?
mybatis的缓存有了解吗?
mybatis的动态sql的几种写法,有什么优缺点?
线程池的一个流程?
redis的缓存三件套?
redis的基本数据类型?
1.怎么判断sql的好坏?

根据执行的性能,是否使用到了索引,避免全文搜索

2.索引失效的场景

使用通配符
不使用索引列查询
对索引列进行表达式操作
OR多个条件,索引不一定能同时用到

3.MyBatis的#和$的区别

#传递的参数是经过预编译之后的sql语句接受
$传递的参数直接拼接到sql,进行执行
所以#传递参数更安全,$可能会导致sql注入.
预编译:sql语句先进行分析编译,之后把占位符的参数绑定.

4.MyBatis的缓存

一级缓存:会话级别的缓存,如果有相同的sql,直接会从缓存中获取结果返回–默认开启
二级缓存:mapper级别缓存,同一个mapper接口得到的结果如果在缓存中直接返回–需要配置
会相应引起并发性问题:数据库数据更新
@CacheEvict 使缓存失效

5.MyBatis的动态sql有几种写法,有什么有缺点
  1. xml文件中写:与代码分离,清晰,但是需要额外配置.适合复杂sql
  2. 注解写:方便,适合简单sql,但是写动态sql可读性差
  3. 动态方法写:方便,适合简单sql
6.线程池的一个流程

创建线程池,指定核心线程数,最大线程数,任务队列,拒绝策略,非核心线程超时
创建一个任务,

  1. 如果线程池的线程数小于核心线程数,那么就直接开一个新线程执行任务.
  2. 如果核心线程数上限,那么就把任务放到等待任务队列中.
  3. 如果任务队列满了,那么就开新线程来执行任务.
  4. 如果线程数到达最大线程数,那么新任务就执行拒绝策略
  5. 如果线程数大于核心线程数,非核心线程空闲时间超过非核心线程超时之后就会被销毁
    线程执行完任务后回到线程池
7.redis的缓存三件套

缓存键
缓存值
缓存过期时间

8.redis的基本数据类型

String
Set
List
Hash:键值对
sorted Set : 通过添加score进行排序
bitmap
geospatial index : 地理空间索引

JVM
堆里面主要存放什么对象?存放什么东西
栈里面存放什么
“堆对象放多了会发生什么?
“那对象都放堆里,GC 很频繁对吧?一些特定场景下,对象存活时间很短,引用很少,可能会放在栈里……减少堆压力减少 GC”
“什么样的对象会放入老年代?”——年龄阈值 15;大对象。“别的情况?”——不知道了,查了下还有动态年龄判断和主动 GC - “为什么是 15?算了我一会问你,你也应该知道”——汗流浃背,只能想到以为是概率统计结论
“什么情况会发生 Full GC?”——Minor GC 还不够,老年代要满
“行,你 JVM 相关是在哪里学的?”——《深入理解 Java 虚拟机》,老圣经了。“那不应该看出来是这个结果吧哈哈?”——再次汗流浃背,说刚开始看还没看多少
“其实这里有三种情况,还有个空间分配的担保机制”——只能附和对对对;查了下主动调用也行,元空间不够也行
“G1 回收阶段什么时候 STW?”——除了并发标记以外都有
“CMS 有什么缺点?”——标记清除的内存碎片;浮动垃圾(应该还有个清除时并发 CPU 资源抢占,降低程序吞吐量)

Java 基础
“老生常谈,HashMap,在 1.7 会发生死循环,什么情况下发生?”——扩容、头插法、循环链表,细节忘了
“ConcurrentHashMap 1.7 怎么加锁的”——分段加锁,1.8 是锁链表头
“1.8 再说详细些,比如 put()”——CAS 或 synchronized。“……可以再看看源码,里面有4 个 if-else,可能还有数组初始化……”,又讲好多,学习了
“了解扩容原理吗?”——硬着头皮说了解过,硬扯 CopyOnWrite 搬石砸脚。“那我们不聊这个了,它支持多线程扩容你知道吗?……”然后认真跟我讲了扩容机制,根据 CPU 核数算出来合适线程数之类的(后来看源码确实是这样,之前真没注意),学到了
“synchronized 锁升级过程了解吗?”——说意向锁被 JDK 15 被废除了,所以基本上只剩轻量级升重量级了(想以此逃避细节拷打)
“锁标识存在哪里?”——Mark Word 对象头。“Mark Word 有多少位?”——32 位是吗?。“32 或 64,看操作系统”
“4 个 bit 能表示多少数字?”——2^4=16
"对象头里面有 4 位留给谁了你知道吗?"——?有点懵,确实没反应上来。“比如留给对象年龄,刚刚说的 15 就是这么来的”,牛蛙!记住了,确实也有印象对象头里有对象年龄 GC 用,这下串起来了,赞!然后又讲了讲对象头其他部分大概是什么
“线程池的非核心线程是什么时候创建的?”——设置可以创建;阻塞队列已满
“什么时候销毁?”——空闲时间超过存活时间 keepAliveTmie
“处理完任务队列满了的额外任务等待 keepAliveTime 就销毁吗?”——觉得应该继续处理队列中的任务,肯定了,然后又细讲了讲机制
“你还有哪块比较熟悉?”——最近也在看 MySQL 相关的东西 “OK 那就 MySQL”——大哥真洒脱随性真好啊!

MySQL
“MySQL 底层索引用的什么数据结构?”——B+树
“B+树和 b 树区别?”——老生常谈。“B+树叶子结点之间是什么数据结构?”——双向链表
“隔离级别的可重复读是什么意思?”——举例 AB 事务布拉布拉
“数据库这块还有哪比较熟?”——最近在看事务和锁。“锁是吧,行”
“用 SQL 实现一个死锁,举个例子”——大概描述就是 AB 事务锁资源顺序导致死锁
“具体 SQL 语句?”——select for update,结合 SQL 再具体描述了一下上面的过程(还好吸取了上次不会写SQL的经验教训,这些天看了看锁的SQL怎么实现,没有两次在同一个地方翻车就是进步就是win!)
30 min

算法
LC 426(会员题,或剑指 LCR 155 免费同题) 二叉搜索树转排序双向链表
看了看,有点怂,没做过,感觉左孩子右孩子遍历改起来会自己绕进去,于是换题(哎,菜还得多练)
LC 53 最大子数组和,看似 mid 实为 easy 的 dp,但是好久没做 dp 想着滑窗绕进去了……面试官提醒我考虑 dp,结果推状态转移方程还是考虑了滑窗的思想半天推不出来。面试官又提醒怎么考虑状态转移,我能听懂意思,也能写方程,但脑子里受滑窗影响觉得这个方程不对,然后又犹豫了会选择相信面试官直接敲代码,然后过了……我真傻,真的。甚至中途收集最大值我直接 return 了 dp 末尾,面试官又提醒我然后我才补上,巨尴尬
+15 min——45 min

反问
先问我“平常怎么学习的?”——网课、书、博客,后面打算研究源码(感觉以后不应该提课了,书+文档+博客+源码够够的了)
哪方面还要着重提高?——JVM 书看完;博客看全看详细;算法加强常见题得熟,没做过也得有思路
如果能去,技术栈准备?——实习生基础好就行,框架会用就行;基础扎实已经很耗费时间了,我们也没指望能一上来就深入框架源码(与牛友面经阿里上来就问看过 Spring 源码吗形成鲜明对比)
“对,还有一点,希望你回答问题时能更自信一些,经常用“可能”“或者”,让我感觉你对你的回答不自信,即使是对的。我们搞程序的肯定是要严谨一些”——确实有点代入生活习惯,不喜欢把话说满,觉得不确定就直接查,面试没法查(bushi);但是有时也确实是不太确定答案习惯性带个可能,确实对于面试来说不是个好习惯,受教了!
52 min 结束

2.

1.jvm
  1. 堆里存放什么对象:Java类对象,实例对象,数组,字符串对象,匿名对象,基元包装类对象
  2. 栈里存放什么对象:方法内局部变量,方法调用地址(栈帧)
  3. 什么样的对象会放到老年代:大对象,年龄超过阈值的对象,预置是jvm参数配置,每经历一次新生代GC年龄加一,最大15好像是因为对象头信息中保存对象年龄使用了4位
  4. 什么情况会fullgc:老年代空间不足,显式调用,系统空闲时间
  5. 空间分配的担保机制:当新生代存活下来的对象无法存放到survivor区域,那么就直接晋升到老年代,这会导致老年代满,导致fullgc
  6. 元空间满:在1.8之后元空间满不会触发fullgc,而是会触发专门的元空间gc
  7. G1的回收过程中什么会导致STW(stop the world),除了并发标记都会,初始标记->并发标记->最终标记->回收清理. 初始标记:记录根对象直接关联的初始对象. 并发标记遍历堆对象,标记所有存货对象.最终标记,标记并发标记中变化的对象.
  8. CMS的缺点:STW时间较长,空间碎片问题,浮动垃圾,cpu竞争,浮动垃圾:在并发标记和清除过程中产生的垃圾只有在下次垃圾回收才能回收.并发的标记会竞争走一部分程序的cpu
2.Java基础
  1. HashMap1.7可能会出现死循环的问题:多线程插入的时候,如果一个线程在进行扩容,另外一个线程在进行hash碰撞后的链表插入.可能会因为next指针指向已被移动到新map的元素,导致遍历不能正常结束
  2. concurrentHashMap:1.7之前是内部分多个segment,锁segment. 1.8之后使用synchronized,CAS,volatile完成的
  3. synchronized锁升级:无锁->偏向锁->轻量锁->重量锁:偏向锁:加上id,如果访问资源的线程id相同,则直接准入(可重入),如果不同线程产生了竞争,则升级为轻量锁.轻量锁线程如果没得到锁,会自旋等待.如果等待自旋超过一定次数,或者在等待中又有另外一个线程想要获取锁,则升级到重量级锁.重量级锁会用操作系统层面将线程阻塞,需要用户态和内核态切换,所以性能较差.
  4. 锁标识信息存放在markword中,markword根据操作系统不同,位64位或者32位
  5. 4bit可以表示16个数字
  6. 非核心线程处理完任务队列溢出的任务之后会继续协助处理任务
3.MySQL
  1. MySQL底层索引用的B+树实现
  2. b+树和b树的区别是什么:b树又称为b-树,b+树只有叶子节点存储data,其他节点存储key.b-树所有节点都存储data.遍历的时候是进行二分查找. b+树叶子节点之间通过双向链表相连,每个节点的大小较小,可以存放更多节点,查询效率稳定
  3. 隔离级别:读未提交,读已提交,可重复读,串行化
  4. SQL死锁:一个事务读A表id=1的值,更新B表id=1的值,另外一个读B表id=1的值,更新A表id=1的值
-- 下面的执行死锁不住,我不知道为什么,在8.0数据库里面尝试了很多种网上所说的sql,都没法形成死锁.
-- 先执行这一段,在sleep期间执行下面一段
BEGIN;
SELECT * FROM user WHERE id = 2 FOR UPDATE;
-- 等待一段时间,模拟会话1持有锁
SELECT SLEEP(100);
UPDATE user SET phone = '21' WHERE id = 3;
COMMIT;

-- 因为select要想update获取锁,但是上面sleep占有了锁
BEGIN;
SELECT * FROM user WHERE id = 3 FOR UPDATE;
UPDATE user SET phone = '32' WHERE id = 2;
COMMIT;
4.算法
  1. 二叉搜索树转化为双向链表
    核心思想:我自己写的代码思路是对的,也每a出来,因为没处理好tail和head的变量,报错null.
class Solution {
    Node head = null;
    Node tail = null;

    public Node treeToDoublyList(Node root) {
        if (root == null)
            return null;

        helper(root);

        // 连接首尾节点
        head.left = tail;
        tail.right = head;

        return head;
    }

    private void helper(Node node) {
        if (node == null)
            return;

        // 中序遍历
        helper(node.left);

        if (tail == null) {
            // 链表为空,当前节点为头尾节点
            head = node;
            tail = node;
        } else {
            // 连接当前节点到链表尾部
            tail.right = node;
            node.left = tail;
            tail = node;
        }

        helper(node.right);
    }
}
  1. 最大子数组和
    核心思想:比较简单的题,做了好多遍了
class Solution {
    public int maxSubArray(int[] nums) {
        int max = nums[0];
        // 如果和小于0了就置为1
        int[] dp = new int[nums.length+1];
        dp[0] = nums[0];
        for(int i = 1;  i < nums.length ; i ++){
            dp[i] = Math.max(dp[i-1],0) + nums[i];
            if(dp[i] > max){
                max = dp[i];
            }
        }
        return max;
    }
}
  • 24
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值