- 当前位置:
- 首页
- VIP_八股文
- aaaaVIP_面试突击专题
- 正文
Java IO + Java8新特性突击八股文
JavaIO+Java8新特性学习指南
这块考的可深可浅,一般也是互联网中大厂才会问的了
需要掌握的知识概括:
1、BIO + NIO + AIO + IO多路复用 + epoll + poll + select
2、零拷贝技术
3、Java8新特性(主要是lambda)
参考学习文章以及资料
我会在对应的面试题那里,补充对应的文章,专栏,视频和书籍,是一个持续补充的过程,大家有看到好的文章也可以发我,然后我也会给大家推荐对应的书籍 + 咱们训练营的专栏,作为一个进阶补充,有时间你就都看。
系统资料推荐:IO 这块,目前是没有什么系统的书籍,而且这块可深可浅,目前学习方式主要是文章,大家基于我下面提供的文章 + 面试学习即可,其他无需特意学习。
正文
【IO专题】同步 异步 阻塞 非阻塞概念问题 + 两两组合后的易混淆问题
PS:这部分的话,主要就是要理解,千万不能记忆,很多人会把 同步/异步 和阻塞/非阻塞 之间搞混淆。问题的关键,就是要先理解「字面意思」。
1、解释一下 同步 异步 阻塞 非阻塞 的概念?
2、同步/异步 和 阻塞/非阻塞 有什么区别?
3、解释下我们常说的 IO 是指什么?
4、什么是 阻塞IO和非阻塞IO?
5、什么是 同步IO和同步阻塞IO?
6、那你再说一下什么是 异步IO和异步阻塞/非阻塞IO?
【参考文章以及资料补充】:
【面试】迄今为止把同步/异步/阻塞/非阻塞/BIO/NIO/AIO讲的这么清楚的好文章(快快珍藏)
参考回答:
1、解释一下 同步 异步 阻塞 非阻塞 的概念?🌟🌟🌟🌟🌟
同步就是指多个事件不能同时进行,需要按照一定的次序一个一个进行。异步就是指多个事件能够同时进行,互不干扰。
阻塞就是某个事件由于某些原因不能进行,需要停下来等待,但在等待的期间可以干其他事情。非阻塞就是事件的进行畅通无阻,没有阻碍。
2、同步/异步 和 阻塞/非阻塞 有什么区别?
同步/异步关心的是事件之间能不能同时进行,阻塞/非阻塞关心的是当前事件能不能够继续进行。
也可以把它们两两组合,以线程为例子:
同步阻塞,相当与同一时刻只能运行一个线程,并且此时还在等待。
同步非阻塞,相当与一个线程在正常运行。
异步阻塞,相当与同一时刻可以运行多个线程,但此时某些线程在等待。
异步非阻塞,相等于多个线程都在同时正常运行。
3、解释下我们常说的 IO 是指什么?
IO 是读/写数据的过程,以及等待数据进行读/写的过程。拿网络 IO 来说,等待的过程就是数据从网络到网卡再到内核空间,真正读写的过程是数据从内核空间到用户空间的相互拷贝。
4、什么是 阻塞IO和非阻塞IO?
阻塞IO 是指程序在发出 IO 请求时,如果数据没有准备好或无法立即进行读取或写入,程序将会被阻塞,也就是暂停执行,直到操作完成或者有数据可用,才能进行下一步操作。
非阻塞 IO 则是相反的概念。非阻塞 IO 是指程序发出 IO 请求时,如果数据没有准备好或无法立即进行读取或写入,程序不会被阻塞,而是立即返回,并允许程序去执行其他任务。程序可以定期轮询 IO 操作的状态,来确定何时数据已经准备好,或者可以使用回调函数或事件通知等机制来接收通知。
5、什么是 同步IO和同步阻塞IO?
同步IO 就是同步阻塞 IO,都属于阻塞IO,表示程序发起 IO 请求后,必须拿到 IO 的数据后才能继续执行,并且在等待数据或者拷贝数据的期间,程序都是阻塞状态。
6、那你再说一下什么是 异步IO和异步阻塞/非阻塞IO?
异步IO 就是程序发起 IO 请求后,不用拿到数据就可以继续执行,也就是在等待数据的过程中,程序是可以继续执行的。但根据拷贝数据时程序是否阻塞,又可以分为异步阻塞/非阻塞IO。
也就是说,等待数据过程中,程序继续执行,但拷贝数据时程序阻塞,是异步阻塞IO。等待过程中,程序继续执行,拷贝数据时程序也继续执行,是异步非阻塞IO。
【IO专题】select, poll, epoll🌟🌟🌟🌟
PS:这几个属于问的挺多的高频题,如果有条件,select, epoll 可以深入了解一下
1、I/O多路复用讲一下,有什么优缺点?
2、介绍一下select,poll和epoll,他们区别知道嘛?讲一下,它们算同步还是异步io?
3、关于 select, epoll 的应用,你了解哪些?(就是哪些工具用了这些机制)
【参考文章以及资料补充】:
补充:零拷贝没有弄上去,感兴趣可以学:
参考回答:
1、I/O多路复用讲一下,有什么优缺点?
I/O 多路复用是指,用一个线程同时监听多个文件描述符。它的主要思想是将多个 I/O 事件注册到一个统一的事件循环中,然后通过调用系统调用,常见的有 select,poll,epoll,等待其中任意一个事件发生。一旦有事件发生,系统调用就会返回并告诉应用程序哪些事件已经就绪,应用程序可以根据返回的结果进行相应的处理。
它的优点很明显,使用单线程就可以监听多个 IO 事件,避免了线程切换的开销。同时利用了操作系统提供的系统调用,效率很高。但它不能充分利用多核处理器,并且使用不同的系统调用,优缺点也不同。
2、介绍一下select,poll和epoll,他们区别知道嘛?讲一下,它们算同步还是异步io?
select 就是将在用户态对文件描述符数组的遍历操作,交给操作系统在内核态去进行。操作系统会筛选出可以进行读写的文件描述符并做个标记,然后将其「个数」通过函数返回值告知用户层,用户层此时再遍历这个文件描述符数组,找出做过标记的文件描述符即可。
select 最早实现了一个线程处理多个文件描述符的功能,减少了系统调用的开销。但是整个过程会发生文件描述符数组从用户态到内核态的来回拷贝,高并发场景下资源消耗多。并且 select 最多只能监听 1024 个文件描述符。
poll 相较于 select,主要是取消了 select 只能监听 1024 个文件描述符的限制,也就是无限制。
epoll 针对 select,poll 的缺点做了相对应的改进。比如 epoll 会在内核态中保存文件描述符的集合,避免每次调用时都拷贝;并且 epoll 只会将发生 IO 事件的文件描述符返回给用户,省去了用户层的遍历操作;除此之外,epoll 将原先对文件描述符集合的轮询优化为了基于 IO 事件的回调,整体效率不会随着文件描述符的增多而降低,效率远高于 select 和 poll。不过 epoll 只能在 linux 下使用。
它们都算是同步 IO,因为程序在执行这三种系统调用函数时,都需要阻塞等待直到 I/O 操作完成。
3、关于 select, epoll 的应用,你了解哪些?(就是哪些工具用了这些机制)
比如 Redis 是单线程却还那么快,原因就是 Redis 借用了 Linux 的 IO 多路复用机制。该机制中的内核会有多个监听套接字和已连接套接字,同时内核会一直监听这些套接字的连接请求或数据请求,一旦有请求到达,就会将其交给 Redis 线程处理。
其它的比如 Nginx,Netty 这种高性能网络应用框架,底层都会用到 IO 多路复用来提高性能。
4、demo
select 使用示例:
结构定义
// 数据结构 (bitmap)
typedef struct {
unsigned long fds_bits[__FDSET_LONGS];
} fd_set;
// API
FD_ZERO(int fd, fd_set* fds) // 清空集合
FD_SET(int fd, fd_set* fds) // 将给定的描述符加入集合
FD_ISSET(int fd, fd_set* fds) // 判断指定描述符是否在集合中
FD_CLR(int fd, fd_set* fds) // 将给定的描述符从文件中删除
案例
int main() {
/*
* 这里进行一些初始化的设置,
* 包括socket建立,地址的设置等,
*/
fd_set read_fs, write_fs;
struct timeval timeout;
int max = 0; // 用于记录最大的fd,在轮询中时刻更新即可
// 初始化比特位
FD_ZERO(&read_fs);
FD_ZERO(&write_fs);
int nfds = 0; // 记录就绪的事件,可以减少遍历的次数
while (1) {
// 阻塞获取
// 每次需要把fd从用户态拷贝到内核态
nfds = select(max + 1, &read_fd, &write_fd, NULL, &timeout);
// 每次需要遍历所有fd,判断有无读写事件发生
for (int i = 0; i <= max && nfds; ++i) {
if (i == listenfd) {
--nfds;
// 这里处理accept事件
FD_SET(i, &read_fd);//将客户端socket加入到集合中
}
if (FD_ISSET(i, &read_fd)) {
--nfds;
// 这里处理read事件
}
if (FD_ISSET(i, &write_fd)) {
--nfds;
// 这里处理write事件
}
}
}
poll 使用示例
结构定义
// 数据结构
struct pollfd {
int fd; // 需要监视的文件描述符
short events; // 需要内核监视的事件
short revents; // 实际发生的事件
};
案例
// 先宏定义长度
#define MAX_POLLFD_LEN 4096
int main() {
/*
* 在这里进行一些初始化的操作,
* 比如初始化数据和socket等。
*/
int nfds = 0;
pollfd fds[MAX_POLLFD_LEN];
memset(fds, 0, sizeof(fds));
fds[0].fd = listenfd;
fds[0].events = POLLRDNORM;
int max = 0; // 队列的实际长度,是一个随时更新的,也可以自定义其他的
int timeout = 0;
int current_size = max;
while (1) {
// 阻塞获取
// 每次需要把fd从用户态拷贝到内核态
nfds = poll(fds, max+1, timeout);
if (fds[0].revents & POLLRDNORM) {
// 这里处理accept事件
connfd = accept(listenfd);
//将新的描述符添加到读描述符集合中
}
// 每次需要遍历所有fd,判断有无读写事件发生
for (int i = 1; i < max; ++i) {
if (fds[i].revents & POLLRDNORM) {
sockfd = fds[i].fd
if ((n = read(sockfd, buf, MAXLINE)) <= 0) {
// 这里处理read事件
if (n == 0) {
close(sockfd);
fds[i].fd = -1;
}
} else {
// 这里处理write事件
}
if (--nfds <= 0) {
break;
}
}
}
}
epoll 使用示例
结构定义
// 数据结构: 每一个epoll对象都有一个独立的eventpoll结构体, 用于存放通过epoll_ctl方法向epoll对象中添加进来的事件
struct eventpoll {
/* 红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件 */
struct rb_root rbr;
/* 双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件 */
struct list_head rdlist;
};
// API
int epoll_create(int size); // 内核中间加一个 ep 对象,把所有需要监听的 socket 都放到 ep 对象中
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); // 负责把 socket 增加、删除到内核红黑树
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout); // 负责检测可读队列,没有可读 socket 则阻塞进程
案例
int main(int argc, char* argv[])
{
/*
* 在这里进行一些初始化的操作,
* 比如初始化数据和socket等。
*/
// 内核中创建ep对象
epfd = epoll_create(256);
// 需要监听的socket放到ep中
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
while(1) {
// 阻塞获取
nfds = epoll_wait(epfd,events,20,0);
for(i=0;i < nfds;++i) {
if(events[i].data.fd==listenfd) {
// 这里处理accept事件
connfd = accept(listenfd);
// 接收新连接写到内核对象中
epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
} else if (events[i].events&EPOLLIN) {
// 这里处理read事件
read(sockfd, BUF, MAXLINE);
//读完后准备写
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
} else if(events[i].events&EPOLLOUT) {
// 这里处理write事件
write(sockfd, BUF, n);
//写完后准备读
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
}
}
}
return 0;
}
【Java8新特性】Java8新特性相关面试题
PS:这个一般也不会有什么连环炮,面试官就想看看你是否了解Java8的一些新特性,因为在工作中,这些新特性还用的挺多的。
1、关于Java8的新特性,你了解多少?可以讲几个吗?(PS:讲3~4个即可,Lambda 和 stream 必须包含)
2、Lambda 表达式解决了什么问题?Lambda 真正的好处是什么?
【参考文章以及资料补充】:
参考回答:Java8的新特性与案例解析(要是问起来,简单介绍几个即可)
1、关于Java8的新特性,你了解多少?可以讲几个吗?(PS:讲3~4个即可,Lambda 和 stream 必须包含)
下面说几个比较了解的:
第一个是 Lambda 表达式,它可以直接作为参数传递给方法或存储在变量中,可以在任何需要函数式接口的地方使用。并且以前使用匿名内部类的地方现在都可以用 Lambda 表达式来化简。
第二个是 Stream API,不同于集合关注的是对数据的存储,它关注的是对数据的运算处理,比如可以对集合进行过滤,映射,排序等等,使用起来也十分简洁。
第三个是新增的 Optional 类,它主要是为了解决 Java 中的空指针而生的。它可以包装一个对象,该对象可能为空,通过一系列的方法操作该对象,而无需显示地进行 null 检查。
还有方法引用,通过一对冒号 ‘ :: ‘ 操作符来表示。对于静态方法引用,可以用 ‘类名::静态方法名’ 表示,对于示例方法引用,可以用 ‘类名::方法名’ 表示等等。
2、Lambda 表达式解决了什么问题?Lambda 真正的好处是什么?
Lambda 主要解决的是代码的冗余问题。Java 8 之前,为了传递功能到方法中,必须使用匿名内部类,导致代码非常冗余且难以阅读,使用 lambda 表达式可以以一种非常简洁的方式替代它,同时也为 Java 引入了函数式编程的能力。
Lambda 的好处在于它能以更紧凑的方式表示代码逻辑,写出来的代码简洁清晰,可读性好。结合 Stream API 可以简洁高效的对集合进行操作。
3、demo
public class LambdaStreamExample {
public static void main(String[] args) {
List numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 使用Lambda和Stream API过滤和映射集合
List result = numbers.stream()
.filter(n -> n % 2 != 0) // 过滤掉偶数
.map(n -> n * 2) // 将剩下的数字加倍
.toList();
// 输出结果
System.out.println(result); // 输出: [2, 6, 10, 14, 18]
}
}