小林求职记(二):说好的问基础,为啥我感觉一点也不基础呢?

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: 小林求职记(二):说好的问基础,为啥我感觉一点也不基础呢?

在上一轮的面试中,小林在mysql方面因为作答不够完善,被面试官吊打了一番。经过两天的自我复习之后,新的一轮面试又开始了。



面试官:你好,请简单介绍下自己吧。


小林:你好,我是xxxxxx,之前在深圳的xxx公司负责了xxx系统的研发设计。


面试官:嗯嗯,那我先来问你一些基础问题吧。


小林:嗯嗯,好的。


面试官:你了解arraylist吧,请说下内部的一些特性。


小林此时心里一下子乐开了花,这个简单啊。


小林:arraylist的底层主要是由数组组成,它和普通数组不太一样,arraylist具有自动扩容的功能。每次当我们add一个元素到队列里面的时候,都会有一步确认容量的机制判断(对应源码里面的ensureCapacityInternal函数)如果当数组内部的元素达到了数组阈值的时候,就会以1.5倍的体积去做扩容,底层是调用了才做系统内部的一个System.arraycopy方法。


又由于arraylist是采用数组存储的,在读取数据的时候可以借助数组位的下标去快速定位,写数据的时候需要涉及到挪动数组,所以读的性能平均要比写的性能更高一些。


面试官:嗯嗯,回答地挺全面的。那你觉得在使用arraylist的时候一般会注意些什么吗?


小林:嗯嗯,有的。一般我会根据代码的上下文给arraylist附一个初始值来定位这个数组的大小,防止其做过多不必要的扩容操作。另外在循环中进行删除操作的时候需要注意会有坑,一般建议采用迭代器的模式来处理。


ps:


如果使用以下这种方式进行元素的移除可能会导致出现删除元素不完整的情况:


public static void main(String[] args) {
    ArrayListApplication arrayListApplication = new ArrayListApplication();
    List<String> list = new ArrayList(3);
    list.add("a");
    list.add("c");
    list.add("c");
    list.add("d");
    list.add("e");
    System.out.println(list);
    System.out.println("==========");
    removeV1(list, "c");
    System.out.println(list);
}
public static void removeV1(List<String> list, String deleteItem) {
    for (int i = 0; i < list.size(); i++) {
        String item = list.get(i);
        if (item.equals(deleteItem)) {
            list.remove(item);
        }
    }
}


打印结果:


[a, c, c, d, e]
==========
[a, c, d, e]


此时由于删除掉list里面元素之后,list的size值也减少了,随之导致了数组元素的前移,因此会出现被删除元素的后一位直接绕开了if判断,没有被“命中”。如果采用foreach的方式删除,则会抛出一段异常信息,声明删除失败:


Exception in thread "main" java.util.ConcurrentModificationException
 at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
 at java.util.ArrayList$Itr.next(ArrayList.java:859)


面试官:嗯嗯,你刚刚有说到迭代器删除,那么你有了解过迭代器模式吗?


小林:额,迭代器模式,让我思考一下.... 背了知识点,但是不记得了....


image.png


面试官:好吧...


ps:其实迭代器模式的好处在于,直接帮我们屏蔽了具体的实现细节,通过采用迭代器的方式来帮助我们编写一些可以复用的类。例如说arraylist,其实内部也有自己迭代器的具体实现,vector也有自己迭代器模式的具体实现。非常的方便,并且有助于减少代码里面的对于具体实现的强依赖性。


面试官:那我们切入下一个话题吧,能说下自己对于幻读的理解吗?


小林:嗯嗯,可以的。我在上家公司工作的时候,公司内部的事务隔离级别设置为了可重复读级别,这样能够保证当前事务读取的数据不会受到其他事务提交的影响,但是这种隔离级别会在事务提交完毕之后查询数据的时候出现幻读的场景,如果需要解决幻读的情况需要将事务的隔离级别提升为串行化等级。


面试官:哦,那你在工作中有试过提升为串行化吗?


小林:没有,因为串行化是强行在mysql层加锁,使得事务得排队执行,容易产生堵塞的情况,性能不佳。


面试官:那你是怎么解决幻读的情况呢?


小林:嗯....别的同事帮我解决的.....


此时,面试官脸上渐渐露出了诡异的笑容。


image.png


ps:其实幻读这种情况在工作中偶尔还是会遇到的,举个具体场景:


假设某一时刻,同时有两个事务访问了数据库,需要先从库里面查询订单是否存在,然后再插入新的订单记录。


a连接


mysql> select @@global.tx_isolation,@@tx_isolation;
+-----------------------+-----------------+
| @@global.tx_isolation | @@tx_isolation  |
+-----------------------+-----------------+
| REPEATABLE-READ       | REPEATABLE-READ |
+-----------------------+-----------------+
1 row in set, 2 warnings (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t_order_1 where id =100;
Empty set (0.00 sec)


b连接


mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t_order_01 where id =100;
ERROR 1146 (42S02): Table 'test-db01.t_order_01' doesn't exist
mysql> select * from t_order_1 where id =100;
Empty set (0.00 sec)


假设在两边事务都开启的一刻,a连接中的事务往数据库插入了一条id为100的数据,然后commit。a连接


mysql> INSERT INTO `test-db01`.`t_order_1` ( `id`, `order_no`, `product_id`, `user_id`, `create_time`, `update_time` )
    -> VALUES
    -> ( 100, 2, 2, 2, now(), now());
Query OK, 1 row affected (0.00 sec)
mysql> select * from t_order_1 where id=100;
+-----+----------+------------+---------+---------------------+---------------------+
| id  | order_no | product_id | user_id | create_time         | update_time         |
+-----+----------+------------+---------+---------------------+---------------------+
| 100 |        2 |          2 |       2 | 2020-07-14 22:57:37 | 2020-07-14 22:57:37 |
+-----+----------+------------+---------+---------------------+---------------------+
1 row in set (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)


当a事务提交结束了,此时b事务开始执行select 查询校验的操作,判断不存在id为100的数据,此时打算执行插入数据的操作:


mysql> select * from t_order_1 where id=100;
Empty set (0.00 sec)
mysql> INSERT INTO `test-db01`.`t_order_1` ( `id`, `order_no`, `product_id`, `user_id`, `create_time`, `update_time` )
    -> VALUES
    -> ( 100, 2, 2, 2, now(), now());
ERROR 1062 (23000): Duplicate entry '100' for key 'PRIMARY'


结果出现了异常,这种情况我们通常称之为幻读。那么该如何解决这种场景的问题呢?其实mysql内部提供了一种叫做next-key-lock的加锁机制,可以供我们处理这类特殊情况:


mysql> select * from t_order_1 where id=100 for update;
+-----+----------+------------+---------+---------------------+---------------------+
| id  | order_no | product_id | user_id | create_time         | update_time         |
+-----+----------+------------+---------+---------------------+---------------------+
| 100 |        2 |          2 |       2 | 2020-07-14 23:06:03 | 2020-07-14 23:06:03 |
+-----+----------+------------+---------+---------------------+---------------------+
1 row in set (0.00 sec)
借助 **for update **语句,我们可以在应用程序的层面手工实现数据加锁保护操作。就是那些需要业务层面数据独占时,可以考虑使用** for update**。


其实 for update 可以理解为一把悲观锁,每次获取数据的时候,都担心会有其他线程修改当前的数据,因此在拿数据的时候就会加入一把锁,其他试图改写数据的请求将会处于堵塞情况。(读数据的请求不会堵塞)


面试官:那你知道mysql里面的mvcc机制吗?


小林:mvcc是啥?mvc我倒知道,之前工作中有使用过springmvc框架 blabla(希望把面试官绕开引到自己熟系的话题方向)


面试官满脸微笑地看着小林,似乎想缓解下尴尬的气氛。


面试官:好吧,你之前有对缓存了解过吗?


小林:嗯嗯,我在工作中一般喜欢使用redis作为缓存。当查询数据的时候先去redis中查询,如果redis没有再去mysql中读取数据。


面试官:嗯嗯,那你有了解过redis里面的哪些数据结构吗?


小林:嗯嗯,我在工作中有使用过string,list,hash,zset,set这几类数据结构,它们各自都有自己的特点,在使用的时候需要结合实际的业务场景来使用。



String类型可以用于存储一些简单的键值对数据,例如数字,字符串之类的。


List结构一般是采用了双端队列的结构,这类结构通常使用的命令有lpush,lpop,rpop等,如果想移除某个节点的前置和后置节点就比较简单(复杂度就是O(1)),但是搜索比较复杂。适合用于存储一些列表类型的数据信息,例如说用户的留言和评论信息。


Set 是一个无顺序的集合,比较常见的例如说交集查询,用于搜索两个好友之间共同阅读过的图书。或者两个人之间的共同好友等。使用命令sinter key [key...] 即可实现。


image.png


Hash是包含键值对的无序散列表,常用命令有:hget,hgetall等。


ZSet则是一套有序集合,通常会根据分值范围或者成员来获取元素并且计算一个键的排名。


面试官:嗯嗯,你讲的这些东西都只是停留在了表层,关于其内部的构造有去做过一些深入的了解吗?


小林:额,没有....


此时小林又一次被面试官打击了自信心


面试官:好吧,今天的一面主要只是问问基础,那么就先这样吧,我去找下我老大询问下。


小林:嗯嗯,好的。(似乎感觉还有戏)


小林一个人在前台坐着等待着下一场面试的到来,不仅内心感叹道,自己过去的工作中过多地安逸于写crud,很多java的基础问题也都记得不太清楚了,每天下班之后也没怎么学习,虽然一面只是问了些基础问题,结果却暴露了自己这么多的知识盲区。


过了不久一个陌生的男人慢慢走了过来,天啊,这家伙真的是聪明“绝顶” 了。小林一下子慌了,脑袋一片空白,二面似乎来了一位资深的大佬.....


二面面试官:你好,我是你的二面面试官,下边我可能会针对你的项目做一些询问。

(未完待续....)

END

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
3月前
|
前端开发 安全 Java
Java后端面试必问:十八道面试题及答案最新整理(速看速藏)
Java后端面试必问:十八道面试题及答案最新整理(速看速藏)
172 0
|
4月前
|
运维 Java 中间件
离谱!阿里面试官嫌面试者技术差,竟彻夜怒肝524页面试通关手册
又到了“金三银四”面试求职高峰期,在金三银四时也参与过不少面试,2021都说工作不好找,也是对开发人员的要求变高。前段时间自己有结合GitHub高频面试资料整理了一些Java后端开发面试常问的高频考点问题做成一份PDF文档(1000道高频题),同时也整理一些图文解析及笔记,今天在这免费分享给大家,希望大家在即将的十月面试做好复习,长期的积累和短期的突击让自己能找到一个满意的工作!
|
Web App开发 存储 前端开发
【番外01】吐血整理5万字100道高频基础面试题 无名面试集《烂俗前端》
【番外01】吐血整理5万字100道高频基础面试题 无名面试集《烂俗前端》
161 0
|
算法 数据安全/隐私保护 芯片
面试:第七章:冷门面试题
面试:第七章:冷门面试题
112 0
面试:第七章:冷门面试题
|
缓存 小程序 测试技术
建议收藏!初级软件测试面试题及题库答案,你肯定用得上
软件测试的面试过程中,面试官往往都会根据你面试的职位,提问一些相关的软件测试知识,而很多人为了能够提高的自己在面试当中的通过率,都会在面试前做好充足的准备。
451 0
|
存储 安全 NoSQL
问遍了身边的面试官朋友,我整理出这份 Java 集合高频面试题(2021年最新版)
今天我们继续下一个重要的面试内容:集合框架。HashMap作为 Java 中最靓的仔,毋庸置疑将是本文的主角。
139 0
问遍了身边的面试官朋友,我整理出这份 Java 集合高频面试题(2021年最新版)
|
存储 缓存 网络协议
(2.6w字)网络知识点灵魂拷问(下)——前端面试必问
(2.6w字)网络知识点灵魂拷问(下)——前端面试必问
(2.6w字)网络知识点灵魂拷问(下)——前端面试必问
|
域名解析 存储 缓存
(2.6w字)网络知识点灵魂拷问(上)——前端面试必问
(2.6w字)网络知识点灵魂拷问(上)——前端面试必问
(2.6w字)网络知识点灵魂拷问(上)——前端面试必问