【字节跳动面经】Java后台研发2020面试真题(商业变现部门一面)

前言

2020是个不平凡的一年,疫情、海啸、中高考延期。对于互联网人也是不平凡的一年,内卷、各大小公司资金链断裂破产等等。
这一年,对我而言也是有特别的意义。先是因为疫情,一个人滞留在工作城市半个月,之后又因为老东家组织架构调整,未来发展方向与自己期望不符,所以即使刚满一年多点的工作经验,也决心离开。后来认真准备,经历了一个多月的密集面试,大大小小的公司offer也拿到好几个,最终选择了开水团。
年底是跳槽的高峰,故以此系列,记录阿里,字节跳动,快手等大厂的面试的问题及回答要点,希望对大家有帮助。同是打工人,祝各位朋友顺心顺利。

面试开始

什么叫死锁,手写一个MySQL死锁的案例?

死锁:在两个或多个并发进程中,如果每个进程持有某种资源而又都等待别的进程释放它或它们现在保持着的资源,在未改变这种状态之前都不能向前推进,称这一组进程产生了死锁
通俗地讲,就是两个或多个进程被无限期地阻塞、相互等待的一种状态

产生死锁的必要条件

  • 互斥(mutualexclusion),一个资源每次只能被一个进程使用
  • 不可抢占(nopreemption),进程已获得的资源,在未使用完之前,不能强行剥夺
  • 占有并等待(hold andwait),一个进程因请求资源而阻塞时,对已获得的资源保持不放
  • 环形等待(circularwait),若干进程之间形成一种首尾相接的循环等待资源关系。
T1:
begin;
select * from user u where u.id=1 for update; 
update user u set u.name='Carson' where id=2;

T2:
begin;
delete from user u where u.id=2;
delete from user u where u.id=1;

如上述代码,启动了两个事务,当事务T1为id=1加悲观锁,但是第二条语句尚未执行时,事务T2也开启了并删除了id=2的行数据,此时无论是T1还是T2想执行接下来的语句都会发现相关的id行已经被另一个事务持有并且互不释放,此时就发生了死锁。

什么是倒排索引,MySQL倒排索引的实现原理?

倒排索引是将存储于数据库中的整本书或整篇文章中的任意内容信息查找出来的技术。简单来说,假设现在有若干个文档,现在想要查询关键字keyword出现在了哪几个文档里,如果没有倒排索引,你能想到方法或许只有完整遍历,暴力获取了,很显然这是一种很低效的方式。所以这就有了倒排索引,什么意思呢,就是把这些关键字keyword出现在了哪些文档里记录下来,诸如以下的形式:

textdocuments
keyword1,6
code2,6

这样一看,是不是方便多了呢,想要查询关键字,就能通过该索引定位到对应的文档里,然后直接返回给业务端。事实上Google、百度等搜索引擎就是通过该技术实现的。
那么MySQL又是如何实现的呢?
这里有两个重要的概念:Auxiliary Table(辅助表)FTS Index Cache(全文检索索引缓存)
辅助表就是将倒排索引关键字及对应的索引文档存放的位置。
全文检索索引缓存主要用来提高全文索引的性能。

手写:三个线程对同一个变量进行累加十次,主线程等待三个子线程累加完成,打印出30。

【Java多线程手写代码】(四):字节跳动面试官让我手写代码实现启动三个线程累加数字到30

ThreadLocal的应用及原理?

1 原理:
ThreadLocal它是线程的局部变量,这些变量只能在这个线程内被读写,在其他线程内是无法访问的。 首先 ThreadLocal 是一个泛型类,保证可以接受任何类型的对象。

因为一个线程内可以存在多个 ThreadLocal 对象,所以其实是 ThreadLocal 内部维护了一个 Map ,这个 Map 不是直接使用的 HashMap ,而是ThreadLocal实现的一个叫做 ThreadLocalMap的静态内部类。而我们使用的 get()、set() 方法其实都是调用了这个 ThreadLocalMap类对应的 get()、set() 方法。例如下面的 set 方法:

 	public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

调用 ThreadLocal的 set 方法时,首先获取到了当前线程,然后获取当前线程维护的 ThreadLocalMap对象,最后在ThreadLocalMap实例中添加上。如果 ThreadLocalMap 实例不存在则初始化并赋初始值。

这里看到 set 方法的第一个参数是 thisthis即指的是当前的ThreadLocal对象,会看上看的代码就是指的 mLocal 这个对象。而在ThreadLocalMap的 set 方法中会根据当前ThreadLocal对象实例,做一些操作和判断,最终实现赋值操作(具体参考源码)。
2 典型应用:
保存session。
3 内存泄漏问题:
实际上ThreadLocalMap中使用的 key 为ThreadLocal的弱引用,弱引用的特点是,如果这个对象只存在弱引用,那么在下一次垃圾回收的时候必然会被清理掉

所以如果 ThreadLocal没有被外部强引用的情况下,在垃圾回收的时候会被清理掉的,这样一来 ThreadLocalMap中使用这个 ThreadLocal的 key 也会被清理掉。但是,value 是强引用,不会被清理,这样一来就会出现 key 为 null 的 value。

ThreadLocalMap实现中已经考虑了这种情况,在调用 set()、get()、remove() 方法的时候,会清理掉 key 为 null 的记录。如果说会出现内存泄漏,那只有在出现了 key 为 null 的记录后,没有手动调用 remove() 方法,并且之后也不再调用 get()、set()、remove() 方法的情况下。

sessionId存在哪里?

sessionId是一个会话的key,浏览器第一次访问服务器会在服务器端生成一个session,有一个sessionId和它对应。tomcat生成的sessionId叫做jsessionId。jetty为sessionId。
sesssion内容存储在服务器内存中,sessionId保存在客户端cookie中。当然也可以持久化到数据库,redis里。

HTTPS加解密过程?

HTTPS在传统的HTTP和TCP之间加了一层用于加密解密的SSL/TLS层(安全套接层Secure Sockets Layer/安全传输层Transport Layer Security)层。使用HTTPS必须要有一套自己的数字证书(包含公钥和私钥)。
HTTPS加解密过程:

  • 客户端请求服务器获取证书公钥
  • 客户端(SSL/TLS)解析证书(无效会弹出警告)
  • 生成随机值
  • 公钥加密随机值生成密钥
  • 客户端将秘钥发送给服务器
  • 服务端用私钥解密秘钥得到随机值
  • 将信息和随机值混合在一起进行对称加密
  • 将加密的内容发送给客户端
  • 客户端用秘钥解密信息

详细过程可以参考下篇博文👇
HTTPS原理,以及加密、解密的原理

Nginx的请求过程?

这个问题要是细说起来能说半个小时,在面试中就抓住重点过程说下就可以了。不难,纯粹记忆的玩意,非Nginx中间件开发的我相信没多少人能说得清清楚楚、一步不落。这里我把核心流程贴出来以供参考。

  • Read Request Headers:解析请求头。
  • Identify Configuration Block:识别由哪一个location 进行处理,匹配 URL。
  • Apply Rate Limits:判断是否限速。例如可能这个请求并发的连接数太多超过了限制,或者 QPS 太高。
  • Perform Authentication:连接控制,验证请求。例如可能根据 Referrer 头部做一些防盗链的设置,或者验证用户的权限。
  • Generate Content:生成返回给用户的响应。为了生成这个响应,做反向代理的时候可能会和上游服务(Upstream Services)进行通信,然后这个过程中还可能会有些子请求或者重定向,那么还会走一下这个过程(Internal redirects and subrequests)。
  • Response Filters:过滤返回给用户的响应。比如压缩响应,或者对图片进行处理。
  • Log:记录日志。

死循环能否导致死机?

这个问题其实不难,重在考验求职者对问题的思考。可以参考少侠当时的回答:
这要看实际场景,如果死循环里会产生大对象或有对象一直累积无法及时回收,那么很快就会死机。如果死循环就是空转,不执行复杂逻辑和生产大对象,仅仅是CPU会上升,少侠亲测过,死循环连续执行一个小时,CPU使用率在60%左右,但不会死机。

redo log和undo log的作用?

重做日志(redo log):

  • 确保事务的持久性。
  • 防止在发生故障的时间点,尚有脏页未写入磁盘,在重启mysql服务的时候,根据redo log进行重做,从而达到事务的持久性这一特性。

回滚日志(undo log):

  • 保存了事务发生之前的数据的一个版本,可以用于回滚,同时可以提供多版本并发控制下的读(MVCC),也即快照读。

关于MySQL事务和日志可请阅读👇
 深入理解MySQL中的事务【超详细配图版】
 必须了解的mysql三大日志-binlog、redo log和undo log

JVM引用类型有哪些?强引用,弱引用,有哪些应用场景?

强引用:
在 Java 中,我们默认声明的时候,使用的是强引用,比如:

A a = new A();

只要使用了强引用,垃圾回收器将永远不会回收被引用的对象。由于内存大小是一定的,当内存不足时,JVM 会直接抛出 OutOfMemoryError,因为没办法回收去释放空间。
如果想中断强引用与对象之间的关系,可以显示将其赋值为 null,这样一来JVM 就可以在适当的时候进行垃圾回收了。

A a = null;

软引用:
软引用比强引用的程度减弱一些,表示是一些非必需但仍有用的对象。它的表现形式如下:
(1)内存足够的时候,软引用对象不会被回收。
(2)内存不足时,系统则会回收软引用对象。
如果软引用对象被回收之后,仍然没有足够的内存空间,然后会抛出内存溢出异常。
由于软引用的这种特性,非常适合实现缓存技术,比如网页缓存,图片缓存等等。
在 JDK1.2 之后,用java.lang.ref.SoftReference类来表示软引用。
弱引用:
弱引用的强度还要更弱一些,它最主要的特点是:无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收。

在 JDK1.2 之后,用 java.lang.ref.WeakReference 来表示弱引用。
一个经典的例子就是ThreadLocal,见上面ThreadLocal的问题。
虚引用:
虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收。
在 JDK1.2 之后,用PhantomReference类来表示。

如何设计一个短链接服务,如微博的短链接。

短链接服务就是把普通网址,转换成比较短的网址。
当我们在浏览器里输入 http://t.cn/RlB2PdD

  1. DNS首先解析获得 http://t.cn 的 IP 地址
  2. 当 DNS 获得 IP 地址以后(比如:74.125.225.72),会向这个地址发送 HTTP GET 请求,查询短码RlB2PdD
  3. http://t.cn 服务器会通过短码 RlB2PdD获取对应的长 URL。短码和长URL的对应关系保存在MySQL里。
  4. 请求通过 HTTP 301 转到对应的长 URL https://m.helijia.com
    这里有个小的知识点,为什么要用 301 跳转而不是 302 呐?

301 是永久重定向,302 是临时重定向。短地址一经生成就不会变化,所以用 301 是符合 http 语义的。同时对服务器压力也会有一定减少。
但是如果使用了 301,我们就无法统计到短地址被点击的次数了。而这个点击次数是一个非常有意思的大数据分析数据源。能够分析出的东西非常非常多。所以选择302虽然会增加服务器压力,但是我想是一个更好的选择。

短码的生成用什么算法呢?

  1. 自增序列算法 也叫永不重复算法;2. MD5算法。

手写:二叉树的后序遍历(非递归)

LeetCode 145题,用栈实现。

public List<Integer> postorderTraversal(TreeNode root) {
        LinkedList<Integer> ans = new LinkedList<>();
        Stack<TreeNode> stack = new Stack<>();
        if (root == null) return ans;

        stack.push(root);
        while (!stack.isEmpty()) {
            TreeNode cur = stack.pop();
            ans.addFirst(cur.val);
            if (cur.left != null) {
                stack.push(cur.left);
            }
            if (cur.right != null) {
                stack.push(cur.right);
            }
        }
        return ans;
    }

手写:整数开根号,精确到m位小数。

用二分法,代码如下。

    /**
     * 用二分法将正整数n开方
     *
     * @param n         输入的正整数
     * @param m 保留的小数精度
     * @return
     */
    public static double sqrt(int n, int m) {
        double lower = 0;
        double high = n;
        double mid = 0;
        double threshold = Math.pow(10, -m);
        do {
            mid = lower + (high - lower) / 2;
            if (mid * mid > n) {
                high = mid;
            } else {
                lower = mid;
            }

        } while (Math.abs(mid * mid - n) > threshold);

        return new BigDecimal(mid).setScale(m, BigDecimal.ROUND_DOWN).doubleValue();
    }

结语

之前考虑换工作的时候,也是经过一番挣扎以及比较费心的准备。怎么说呢,感觉互联网挺难的,技术不能太菜,年龄又不能太大。记录下大厂的笔经面经,既是自己后续学习的资料,也希望能帮到正在求职的你。二面的问题答案正在梳理中。祝各位早早的上岸。

我是少侠露飞,咱们下期见。

点点关注,不会迷路

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值