图解学习网站:https://xiaolincoding.com
大家好,我是小林。
今天就来分享 Java 同学面试滴滴后端开发的面经,主要是问了Java+MySQL+系统+网络+算法,都是比较经典面试题,不算难。
可惜最后同学还是挂了,挂了没关系,重在复盘每一次的面经,针对面试中不理解或者不明白的问题看书继续加强理解,针对已经知道,但是讲不清楚的问题,自己对着镜子面前练习。
考察的知识内容,我帮大家罗列了一下:
一个完整的进程状态的变迁如下图:
进程五种状态的变迁 再来详细说明一下进程的状态变迁:
IO多路复用是一种高效的IO处理方式,它允许单个进程或线程同时监视多个文件描述符,如网络连接或文件句柄。当这些描述符中的任何一个就绪时,比如有数据可读或可写,多路复用机制就能够通知应用程序进行相应的读写操作。这种机制的核心优势在于,它可以在不增加额外线程或进程的情况下,处理大量的并发连接,从而显著地提高系统的并发性和响应能力。常见的IO多路复用技术包括select、poll和epoll等。这些技术各有特点,但核心思想都是通过一个线程来管理多个连接,减少系统资源的消耗,并提高程序运行的效率。select 实现多路复用的方式是,将已连接的 Socket 都放到一个文件描述符集合,然后调用 select 函数将文件描述符集合拷贝到内核里,让内核来检查是否有网络事件产生,检查的方式很粗暴,就是通过遍历文件描述符集合的方式,当检查到有事件产生后,将此 Socket 标记为可读或可写, 接着再把整个文件描述符集合拷贝回用户态里,然后用户态还需要再通过遍历的方法找到可读或可写的 Socket,然后再对其处理。所以,对于 select 这种方式,需要进行 2 次「遍历」文件描述符集合,一次是在内核态里,一个次是在用户态里 ,而且还会发生 2 次「拷贝」文件描述符集合,先从用户空间传入内核空间,由内核修改后,再传出到用户空间中。select 使用固定长度的 BitsMap,表示文件描述符集合,而且所支持的文件描述符的个数是有限制的,在 Linux 系统中,由内核中的 FD_SETSIZE 限制, 默认最大值为 1024,只能监听 0~1023 的文件描述符。poll 不再用 BitsMap 来存储所关注的文件描述符,取而代之用动态数组,以链表形式来组织,突破了 select 的文件描述符个数限制,当然还会受到系统文件描述符限制。但是 poll 和 select 并没有太大的本质区别,都是使用「线性结构」存储进程关注的 Socket 集合,因此都需要遍历文件描述符集合来找到可读或可写的 Socket,时间复杂度为 O(n),而且也需要在用户态与内核态之间拷贝文件描述符集合,这种方式随着并发数上来,性能的损耗会呈指数级增长。poll 通过两个方面,很好解决了 select/poll 的问题。
从下图你可以看到 epoll 相关的接口作用:
epoll 的方式即使监听的 Socket 数量越多的时候,效率不会大幅度降低,能够同时监听的 Socket 的数目也非常的多了,上限就为系统定义的进程打开的最大文件描述符个数。因而,epoll 被称为解决 C10K 问题的利器。
下面是一个HTTP请求的请求头:
GET /home.html HTTP/1.1
Host: developer.mozilla.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101 Firefox/50.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: https://developer.mozilla.org/testpage.html
Connection: keep-alive
Upgrade-Insecure-Requests: 1
If-Modified-Since: Mon, 18 Jul 2016 02:36:04 GMT
If-None-Match: "c561c68d0ba92bbeb8b0fff2a9199f722e3a621a"
Cache-Control: max-age=0
常见的请求字段如下表所示:
字段名 | 说明 | 示例 |
---|---|---|
Accept | 能够接受的回应内容类型(Content-Types) | Accept: text/plain |
Accept-Charset | 能够接受的字符集 | Accept-Charset: utf-8 |
Accept-Encoding | 能够接受的编码方式列表 | Accept-Encoding: gzip, deflate |
Authorization | 用于超文本传输协议的认证的认证信息 | Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== |
Cache-Control | 用来指定在这次的请求/响应链中的所有缓存机制 都必须 遵守的指令 | Cache-Control: no-cache |
Connection | 该浏览器想要优先使用的连接类型 | Connection: keep-alive Connection: Upgrade |
Cookie | 服务器通过 Set- Cookie (下文详述)发送的一个 超文本传输协议Cookie | Cookie: $Version=1; Skin=new; |
Content-Length | 以 八位字节数组 (8位的字节)表示的请求体的长度 | Content-Length: 348 |
Content-Type | 请求体的 多媒体类型 | Content-Type: application/x-www-form-urlencoded |
Host | 服务器的域名(用于虚拟主机 ),以及服务器所监听的传输控制协议端口号 | Host: en.wikipedia.org:80 Host: en.wikipedia.org |
User-Agent | 浏览器的浏览器身份标识字符串 | User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:12.0) Gecko/20100101 Firefox/21.0 |
Origin | 发起一个针对 跨来源资源共享 的请求 | Origin: http://www.example-social-network.com |
五大类 HTTP 状态码
重定向状态码如下,301 和 302 都会在响应头里使用字段 Location,指明后续要跳转的 URL,浏览器会自动重定向新的 URL。
重定向是指将一个URL请求转发到另一个URL的过程。重定向的作用包括:
List是有序的Collection,使用此接口能够精确的控制每个元素的插入位置,用户能根据索引访问List中元素。常用的实现List的类有LinkedList,ArrayList,Vector,Stack。
Set不允许存在重复的元素,与List不同,set中的元素是无序的。常用的实现有HashSet,LinkedHashSet和TreeSet。
Map 是一个键值对集合,存储键、值和之间的映射。Key 无序,唯一;value 不要求有序,允许重复。Map 没有继承于 Collection 接口,从 Map 集合中检索元素时,只要给出键对象,就会返回对应的值对象。主要实现有TreeMap、HashMap、HashTable、LinkedHashMap、ConcurrentHashMap
调整新生代和老年代的比例、线程池、减少GC
SQL 标准提出了四种隔离级别来规避这些现象,隔离级别越高,性能效率就越低,这四个隔离级别如下:
按隔离水平高低排序如下:
针对不同的隔离级别,并发事务时可能发生的现象也会不同。
也就是说:
脏读
如果一个事务「读到」了另一个「未提交事务修改过的数据」,就意味着发生了「脏读」现象。
举个栗子。
假设有 A 和 B 这两个事务同时在处理,事务 A 先开始从数据库中读取小林的余额数据,然后再执行更新操作,如果此时事务 A 还没有提交事务,而此时正好事务 B 也从数据库中读取小林的余额数据,那么事务 B 读取到的余额数据是刚才事务 A 更新后的数据,即使没有提交事务。
因为事务 A 是还没提交事务的,也就是它随时可能发生回滚操作,如果在上面这种情况事务 A 发生了回滚,那么事务 B 刚才得到的数据就是过期的数据,这种现象就被称为脏读。
幻读
在一个事务内多次查询某个符合查询条件的「记录数量」,如果出现前后两次查询到的记录数量不一样的情况,就意味着发生了「幻读」现象。
举个栗子。
假设有 A 和 B 这两个事务同时在处理,事务 A 先开始从数据库查询账户余额大于 100 万的记录,发现共有 5 条,然后事务 B 也按相同的搜索条件也是查询出了 5 条记录。
接下来,事务 A 插入了一条余额超过 100 万的账号,并提交了事务,此时数据库超过 100 万余额的账号个数就变为 6。
然后事务 B 再次查询账户余额大于 100 万的记录,此时查询到的记录数量有 6 条,发现和前一次读到的记录数量不一样了,就感觉发生了幻觉一样,这种现象就被称为幻读。
可以通过二分查找的方式来查找并删除一个数。
首先,将数组从中间分成两部分,比较中间元素和要查找的数的大小关系。如果中间元素等于要查找的数,则删除该元素并将数组整体向前移动一位。如果中间元素大于要查找的数,则在左半部分继续进行二分查找。如果中间元素小于要查找的数,则在右半部分继续进行二分查找。
重复以上步骤,直到找到要删除的数或者确定该数不在数组中。如果找到要删除的数,则将该数删除并将数组整体向前移动一位。
以下是java实现代码:
public class DeleteNumber {
public static void main(String[] args) {
int[] arr = new int[100];
for (int i = 0; i < 100; i++) {
arr[i] = i + 1;
}
int target = 50; // 要删除的数
int index = binarySearch(arr, target);
if(index != -1){
deleteNumber(arr, index);
System.out.println("删除成功!");
}else{
System.out.println("未找到该数!");
}
}
public static int binarySearch(int[] arr, int target) {
int left = 0;
int right = arr.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target) {
return mid;
} else if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
public static void deleteNumber(int[] arr, int index) {
for (int i = index; i < arr.length - 1; i++) {
arr[i] = arr[i + 1];
}
}
}
在上面的代码中,假设要删除的数是50,然后调用binarySearch方法查找50的索引,如果找到则调用deleteNumber方法删除该数。最后打印删除成功的消息。