第一轮面试 面试官:先来聊聊Java基础,ArrayList和HashMap在存储数据结构上有什么不同? 王铁牛:ArrayList是基于数组实现的,有序可重复;HashMap是基于哈希表,无序且键唯一。 面试官:不错。那HashMap在什么情况下会发生哈希冲突,怎么解决的? 王铁牛:当不同的键计算出相同的哈希值时就会冲突,好像是用链地址法解决的。 面试官:回答得可以。那说说在高并发场景下,使用HashMap会有什么问题? 王铁牛:嗯……好像会数据丢失或者死循环,具体不太清楚。
第二轮面试 面试官:接下来谈谈多线程和线程池。线程池有哪些核心参数,分别有什么作用? 王铁牛:有核心线程数、最大线程数、阻塞队列,核心线程数是线程池一开始就创建的线程数量,最大线程数是能创建的最大线程数,阻塞队列用来存放任务。 面试官:很好。那在实际业务中,如何根据业务场景合理配置线程池参数? 王铁牛:嗯……看任务类型吧,计算密集型就少点线程,I/O密集型就多点线程,具体怎么配不太确定。 面试官:那说说线程池中的拒绝策略有哪些? 王铁牛:有AbortPolicy、CallerRunsPolicy,还有……不太记得了。
第三轮面试 面试官:聊聊框架相关,Spring的IOC和AOP是什么,在项目中有什么实际应用? 王铁牛:IOC是控制反转,把对象创建和管理交给Spring容器;AOP是面向切面编程,比如可以在方法前后加日志。 面试官:不错。那Spring Boot相比于Spring,在项目开发中有哪些优势? 王铁牛:它简化了配置,能快速搭建项目,内置了很多组件。 面试官:那MyBatis在数据库操作方面,和原生JDBC相比有什么优点? 王铁牛:它封装了JDBC,操作更简单,SQL语句写在XML文件里,便于维护。 面试官:最后说说Dubbo、RabbitMq、xxl - job、Redis在项目中的作用。 王铁牛:Dubbo是分布式服务框架,RabbitMq用来做消息队列,xxl - job是分布式任务调度框架,Redis用来缓存数据。
面试官:今天的面试就到这里,你回去等通知吧。从今天的面试来看,你对一些基础的知识点掌握得还可以,但对于一些稍微复杂、需要结合实际业务场景深入分析的问题,回答得不是很理想。在实际的项目开发中,不仅要知道技术点本身,更要明白如何在不同的业务场景下合理运用这些技术,希望你回去之后能进一步深入学习和思考。
问题答案:
- ArrayList和HashMap在存储数据结构上有什么不同:
- ArrayList:基于动态数组实现。它在内存中是连续存储的,所以支持快速的随机访问,通过索引可以直接定位到元素。它有序,即按照元素添加的顺序存储,并且允许元素重复。例如在一个学生成绩管理系统中,如果需要按学生录入顺序记录成绩,ArrayList就比较合适。
- HashMap:基于哈希表实现。它通过哈希函数将键映射到一个哈希值,然后根据哈希值确定元素在哈希表中的存储位置。它无序,即元素的存储顺序和插入顺序无关,并且键是唯一的。比如在一个用户信息管理系统中,以用户ID作为键来快速查找用户信息,HashMap就很适用。
- HashMap在什么情况下会发生哈希冲突,怎么解决的:
- 哈希冲突情况:当不同的键通过哈希函数计算出相同的哈希值时,就会发生哈希冲突。例如,两个不同的字符串“abc”和“cba”,经过某些简单哈希函数计算可能得到相同哈希值。
- 解决方法 - 链地址法:在HashMap中,当发生哈希冲突时,会使用链地址法解决。即每个哈希桶(数组中的一个位置)实际上是一个链表的头节点。当有新元素因为哈希冲突要放入同一个哈希桶时,会将新元素作为链表的新节点插入到链表头部(JDK 1.7及之前)或尾部(JDK 1.8及之后)。在JDK 1.8中,如果链表长度超过8且哈希表容量大于64,链表会转化为红黑树,以提高查找效率。
- 在高并发场景下,使用HashMap会有什么问题:
- 数据丢失:在高并发环境下,多个线程同时对HashMap进行put操作时,可能会出现数据丢失的情况。因为HashMap不是线程安全的,在多线程操作时,可能会导致部分数据覆盖或丢失。例如,线程A和线程B同时向HashMap中put不同的键值对,但由于并发操作,可能会导致其中一个线程的操作被覆盖。
- 死循环:在JDK 1.7及之前,当HashMap进行扩容时,如果多个线程同时操作,可能会形成环形链表,导致死循环。这是因为在扩容过程中,线程A和线程B可能会同时对链表进行操作,改变链表结构,最终形成环形链表。当后续访问该链表时,就会陷入死循环。
- 线程池有哪些核心参数,分别有什么作用:
- 核心线程数(corePoolSize):线程池在初始化后,会创建corePoolSize个线程等待任务到来。这些线程即使在空闲状态下也不会被销毁(除非设置了allowCoreThreadTimeOut为true)。例如在一个订单处理系统中,如果预计平均每秒有一定数量的订单任务,就可以根据这个数量设置合适的核心线程数,让这些线程一直处于工作状态处理订单。
- 最大线程数(maximumPoolSize):线程池允许创建的最大线程数量。当任务队列已满,且当前线程数小于最大线程数时,线程池会创建新的线程来处理任务。但如果线程数达到最大线程数后,任务队列又已满,就会触发拒绝策略。比如在电商大促期间,订单量剧增,线程池可能会创建更多线程直到最大线程数来处理订单。
- 阻塞队列(workQueue):用于存放等待执行的任务。当核心线程都在忙碌时,新的任务会被放入阻塞队列。常见的阻塞队列有ArrayBlockingQueue(有界队列)、LinkedBlockingQueue(无界队列,默认容量为Integer.MAX_VALUE)等。例如在一个消息处理系统中,消息任务先进入阻塞队列,等待线程池中的线程来处理。
- 在实际业务中,如何根据业务场景合理配置线程池参数:
- 计算密集型任务:这类任务主要消耗CPU资源,线程执行时间短,但CPU使用率高。由于CPU核心数有限,过多的线程会导致线程上下文切换开销增大,降低性能。因此,核心线程数一般设置为CPU核心数 + 1(多一个线程防止某个线程偶尔的页缺失等情况影响整体性能)。例如科学计算、数据加密等任务。
- I/O密集型任务:这类任务大部分时间在等待I/O操作完成,如文件读写、网络请求等。CPU利用率相对较低,所以可以设置较多的线程数,一般为CPU核心数 * 2。这样可以充分利用CPU资源,在一个线程等待I/O时,其他线程可以继续工作。比如在一个文件上传下载系统中,就适合这种配置。
- 线程池中的拒绝策略有哪些:
- AbortPolicy:默认的拒绝策略。当任务无法提交到线程池(队列已满且线程数达到最大线程数)时,会抛出RejectedExecutionException异常。例如在一个资源有限的系统中,如果任务过多无法处理,直接抛出异常可以让调用者知道任务处理失败。
- CallerRunsPolicy:当任务被拒绝时,会将任务返回给调用者线程来执行。这样可以降低新任务的提交速度,减轻线程池的压力。比如在一个简单的单线程调用线程池的场景中,调用者线程可以在拒绝时处理任务,避免任务丢失。
- DiscardPolicy:直接丢弃被拒绝的任务,不做任何处理。适用于对任务可靠性要求不高,允许部分任务丢失的场景,如一些日志记录任务,偶尔丢失几条日志不影响系统主要功能。
- DiscardOldestPolicy:丢弃队列中最老的任务(即将队列头部的任务丢弃),然后尝试将新任务提交到队列中。例如在一个实时数据处理系统中,如果新数据更重要,就可以丢弃旧数据对应的任务,优先处理新任务。
- Spring的IOC和AOP是什么,在项目中有什么实际应用:
- IOC(控制反转):
- 概念:将对象的创建和管理从应用程序代码中转移到Spring容器中。在传统编程中,对象的创建和依赖关系管理由开发者在代码中手动完成,而IOC通过容器来负责这些工作。例如在一个电商项目中,商品服务类可能依赖于商品数据访问层类,在IOC模式下,Spring容器会创建并管理这两个类,并将商品数据访问层类注入到商品服务类中。
- 实际应用:解耦组件之间的依赖关系,提高代码的可维护性和可测试性。比如在一个大型企业级应用中,不同模块之间的依赖关系复杂,使用IOC可以方便地管理这些依赖,当某个模块需要替换实现类时,只需要在Spring配置文件或注解中修改,而不需要在大量代码中查找和修改。
- AOP(面向切面编程):
- 概念:将一些与业务逻辑无关但又贯穿于多个业务模块的功能(如日志记录、事务管理、权限控制等)抽取出来,形成一个独立的切面,然后通过动态代理等技术将这些切面功能织入到目标业务逻辑中。例如在一个订单处理模块中,在订单创建、修改、删除等操作前后都需要记录日志,使用AOP可以将日志记录功能作为一个切面,统一织入到这些订单操作方法中。
- 实际应用:提高代码的复用性,减少重复代码。在一个系统中,多个业务模块可能都需要权限控制功能,通过AOP可以将权限控制逻辑抽取出来,统一应用到需要权限控制的方法上,而不需要在每个方法中重复编写权限控制代码。
- IOC(控制反转):
- Spring Boot相比于Spring,在项目开发中有哪些优势:
- 简化配置:Spring Boot采用了约定大于配置的原则,默认提供了很多合理的配置,大大减少了Spring项目中大量的XML配置或Java配置类。例如在Spring项目中配置一个数据库连接,可能需要编写大量的XML配置来配置数据源、事务管理器等,而在Spring Boot中只需要在application.properties或application.yml文件中简单配置数据库连接信息,Spring Boot就能自动配置好相关组件。
- 快速搭建项目:Spring Boot内置了很多常用的依赖和插件,如Tomcat、Jetty等Web服务器,以及各种数据库连接驱动等。开发者可以通过Spring Initializr快速生成一个包含基本依赖的Spring Boot项目骨架,然后直接在这个基础上开发业务功能,大大缩短了项目的搭建时间。
- 内置监控功能:Spring Boot Actuator提供了对应用程序的健康检查、指标监控等功能。可以方便地查看应用程序的运行状态、性能指标等信息,便于运维和调试。例如可以通过访问特定的端点查看应用程序的内存使用情况、线程状态等。
- MyBatis在数据库操作方面,和原生JDBC相比有什么优点:
- 简化操作:原生JDBC需要开发者手动编写大量代码来获取数据库连接、创建Statement或PreparedStatement、执行SQL语句、处理结果集等。而MyBatis通过映射文件(XML或注解)将SQL语句和Java代码分离,开发者只需要编写SQL语句和定义映射关系,MyBatis会自动处理数据库连接、参数设置、结果集映射等操作。例如在查询用户信息时,在MyBatis中只需要编写简单的SQL语句和结果映射,而JDBC则需要更多的代码来完成相同功能。
- SQL语句维护方便:MyBatis的SQL语句写在XML文件中,与Java代码分离。当SQL语句需要修改时,只需要修改XML文件,而不需要修改大量的Java代码。在大型项目中,SQL语句可能会经常根据业务需求调整,这种分离方式使得SQL语句的维护更加容易。
- 支持动态SQL:MyBatis提供了丰富的标签(如if、choose、when、otherwise、foreach等)来实现动态SQL。可以根据不同的条件生成不同的SQL语句,提高了SQL语句的灵活性。例如在一个查询用户列表的功能中,可以根据用户输入的不同条件(如用户名、年龄范围等)动态生成SQL语句,而在原生JDBC中实现动态SQL则相对复杂。
- Dubbo、RabbitMq、xxl - job、Redis在项目中的作用:
- Dubbo:是一个高性能的分布式服务框架。在大型分布式系统中,不同的服务可能部署在不同的服务器上,Dubbo可以实现服务的注册与发现,让服务提供者将自己的服务注册到注册中心(如Zookeeper),服务消费者可以从注册中心获取服务提供者的地址信息,并调用其提供的服务。它还支持负载均衡、容错等功能,提高了分布式系统的可靠性和性能。例如在一个电商系统中,商品服务、订单服务等可以通过Dubbo进行分布式部署和调用。
- RabbitMq:是一个消息队列中间件。它可以实现应用程序之间的异步通信。在项目中,当一个任务不需要立即处理或者处理过程比较耗时,可以将任务封装成消息发送到RabbitMq队列中,由消费者从队列中取出消息并处理。例如在一个订单处理系统中,订单创建成功后,发送短信通知用户的任务可以通过消息队列异步处理,避免影响订单创建的响应时间。同时,消息队列还可以起到削峰填谷的作用,在高并发情况下,将大量请求放入队列,慢慢处理,防止系统因瞬间高流量而崩溃。
- xxl - job:是一个分布式任务调度框架。在分布式系统中,可能有一些定时任务或批量任务需要在不同的服务器上执行。xxl - job提供了可视化的任务管理界面,可以方便地创建、调度、监控任务。它支持任务的分片执行,即可以将一个任务拆分成多个子任务在不同的服务器上并行执行,提高任务执行效率。例如在一个数据统计系统中,每天凌晨需要统计前一天的业务数据,就可以使用xxl - job来调度这个任务。
- Redis:是一个高性能的键值对存储数据库,常被用作缓存。在项目中,对于一些不经常变化且访问频繁的数据,可以将其存储在Redis中,当应用程序需要获取这些数据时,优先从Redis中读取,减少对数据库的访问压力,提高系统的响应速度。例如在一个新闻网站中,新闻的标题、摘要等信息可以缓存到Redis中,用户访问新闻列表时直接从Redis获取数据,而不需要每次都查询数据库。此外,Redis还支持多种数据结构(如字符串、哈希、列表、集合、有序集合等),可以满足不同的业务需求,如使用Redis的列表结构实现简单的消息队列,使用有序集合实现排行榜功能等。