在一间明亮却略显严肃的面试房间里,一位神色专注的面试官正准备对面前的求职者展开一场关于Java技术的深度考察。
面试官:“第一轮面试开始。先问几个基础问题,Java中ArrayList和HashMap的底层数据结构分别是什么?”
王铁牛:“ArrayList底层是数组,HashMap底层是数组加链表,JDK1.8 后引入了红黑树。”
面试官:“回答得不错。那HashMap在什么情况下会发生扩容?”
王铁牛:“当HashMap中的元素个数达到负载因子(默认0.75)乘以当前容量时,就会发生扩容。”
面试官:“很好。那说说ArrayList在添加元素时,如果容量不够了会怎样?”
王铁牛:“ArrayList会进行扩容,新容量大概是原容量的1.5倍,然后将原数组内容复制到新数组。”
面试官:“非常好,第一轮表现不错。接下来第二轮。谈谈Spring框架中IOC和AOP的概念分别是什么?”
王铁牛:“IOC是控制反转,就是把对象创建和对象之间的依赖关系交给Spring容器管理。AOP是面向切面编程,能把一些通用功能抽取出来,比如日志、事务,在需要的地方切入。”
面试官:“那Spring中如何实现AOP的?”
王铁牛:“嗯……好像是通过动态代理,有JDK动态代理和CGLIB代理两种方式。”
面试官:“好。Spring Boot相比于Spring,最大的优势是什么?”
王铁牛:“Spring Boot能快速搭建项目,它有很多默认配置,简化了Spring项目的开发。”
面试官:“第二轮回答得也还行。进入第三轮。MyBatis中#{}和${}的区别是什么?”
王铁牛:“#{}是预编译处理,${}是字符串替换,#{}能防止SQL注入。”
面试官:“那在MyBatis中,如何实现一对多的映射关系?”
王铁牛:“呃……可以用collection标签,在resultMap里配置。”
面试官:“Dubbo是什么,在微服务架构中有什么作用?”
王铁牛:“Dubbo是一个高性能的Java RPC框架,在微服务里可以做服务治理,像服务注册、发现、调用这些。”
面试官:“最后一个问题,RabbitMQ在项目中如何保证消息不丢失?”
王铁牛:“嗯……好像要开启确认机制,还有持久化什么的。”
面试官微微点头,说道:“今天的面试就到这里,你对很多基础知识点掌握得还可以,但对于一些稍微深入的问题,回答得不是特别清晰。我们后续会综合评估所有面试者的情况,你回家等通知吧,无论结果如何,我们都会在一周内给你回复。”
问题答案:
- Java中ArrayList和HashMap的底层数据结构分别是什么?
- ArrayList:底层是数组结构。数组可以快速地通过索引访问元素,适合随机访问场景。例如,在需要频繁根据下标获取元素的场景,如分页查询结果集的获取,使用ArrayList比较合适。
- HashMap:JDK1.8之前,底层是数组加链表。数组的每个位置是一个链表的头节点,通过哈希算法计算键的哈希值来确定元素在数组中的位置。如果发生哈希冲突(不同键的哈希值相同),则以链表的形式存储在该位置。JDK1.8 后,当链表长度大于8且数组容量大于64时,链表会转化为红黑树,以提高查找效率。在需要快速根据键获取值的场景,如用户信息根据用户ID快速查找,HashMap很适用。
- HashMap在什么情况下会发生扩容?
- 当HashMap中的元素个数(size)达到负载因子(loadFactor,默认值为0.75)乘以当前容量(capacity)时,就会触发扩容。例如,当前HashMap的容量为16,负载因子为0.75,当元素个数达到16 * 0.75 = 12时,就会进行扩容。扩容的目的是为了减少哈希冲突,提高HashMap的性能。新容量一般是原容量的2倍。
- ArrayList在添加元素时,如果容量不够了会怎样?
- ArrayList会进行扩容。具体过程是,先计算新的容量,新容量大概是原容量的1.5倍(原容量右移一位再加上原容量)。然后创建一个新的更大的数组,将原数组的内容复制到新数组中,最后将新元素添加到新数组的合适位置。例如,原ArrayList容量为10,添加第11个元素时,会扩容到15(10 * 1.5),创建新数组并复制原数组内容,再添加新元素。
- Spring框架中IOC和AOP的概念分别是什么?
- IOC(Inversion of Control)控制反转:传统的Java应用中,对象的创建和对象之间的依赖关系由应用程序自身负责管理。而在Spring框架中,IOC把对象创建和对象之间的依赖关系交给Spring容器管理。例如,一个Service类依赖另一个Dao类,在传统方式下,Service类内部会自己创建Dao类的实例。而使用IOC,Spring容器会创建Dao类实例,并注入到Service类中,Service类不需要自己创建Dao实例,实现了控制权的反转。
- AOP(Aspect - Oriented Programming)面向切面编程:它是一种编程范式,能把一些通用功能(如日志记录、事务管理、权限控制等)从业务逻辑中抽取出来,形成一个个切面。这些切面可以在需要的地方切入到业务逻辑中。比如,在一个电商系统中,订单处理的业务逻辑中,在下单、支付等关键节点都需要记录日志,使用AOP可以把日志记录功能抽取出来,在这些业务方法执行前后切入日志记录逻辑,而不需要在每个业务方法中都写重复的日志记录代码。
- Spring中如何实现AOP的?
- Spring实现AOP主要通过动态代理,有两种方式:
- JDK动态代理:JDK动态代理是基于接口的代理。如果目标对象实现了至少一个接口,Spring会使用JDK动态代理创建代理对象。JDK动态代理通过反射机制在运行时创建代理类,代理类实现了目标对象的接口,并在代理方法中调用InvocationHandler的invoke方法,在invoke方法中可以实现切面逻辑,如在目标方法执行前后添加日志记录等。
- CGLIB代理:如果目标对象没有实现接口,Spring会使用CGLIB代理。CGLIB代理是通过继承目标类,生成目标类的子类作为代理对象。CGLIB代理通过字节码增强技术,在运行时生成代理类的字节码,在代理类的方法中调用MethodInterceptor的intercept方法,在intercept方法中实现切面逻辑。
- Spring实现AOP主要通过动态代理,有两种方式:
- Spring Boot相比于Spring,最大的优势是什么?
- Spring Boot最大的优势是能快速搭建项目,简化了Spring项目的开发。Spring项目在搭建时,需要进行大量的配置,如配置数据源、事务管理器、各种Bean等。而Spring Boot有很多默认配置,采用“约定大于配置”的原则,开发者只需要很少的配置就能快速搭建一个可用的项目。例如,使用Spring Boot开发一个Web应用,只需要引入相关的starter依赖,Spring Boot就能自动配置好Tomcat服务器、Spring MVC等,开发者可以专注于业务逻辑的开发,大大提高了开发效率。
- MyBatis中#{}和${}的区别是什么?
- #{}:#{}是预编译处理,MyBatis在处理#{}时,会将SQL中的#{}替换为?占位符,然后使用PreparedStatement进行数据库操作。这种方式能有效防止SQL注入攻击。例如,SQL语句为“SELECT * FROM user WHERE username = #{username}”,实际执行时会变为“SELECT * FROM user WHERE username =?”,然后通过PreparedStatement设置参数值。
- **{}是字符串替换,MyBatis在处理{}中的内容替换到SQL中。例如,SQL语句为“SELECT * FROM user WHERE username = {}时要特别小心,一般用于传入表名、列名等,且要确保传入的值是安全的。
- 在MyBatis中,如何实现一对多的映射关系?
- 在MyBatis中,可以使用collection标签在resultMap里配置一对多的映射关系。例如,有一个订单(Order)类和订单项(OrderItem)类,一个订单对应多个订单项。在SQL查询时,可以使用连接查询获取订单和订单项的相关数据。在MyBatis的映射文件中,定义如下:
这里的collection标签表示一对多关系,property指定订单类中存储订单项的属性名,ofType指定订单项的类型。通过这种配置,MyBatis就能将查询结果正确映射为包含订单项列表的订单对象。<resultMap id="orderResultMap" type="Order"> <id property="orderId" column="order_id"/> <result property="orderName" column="order_name"/> <collection property="orderItems" ofType="OrderItem"> <id property="itemId" column="item_id"/> <result property="itemName" column="item_name"/> </collection> </resultMap> - Dubbo是什么,在微服务架构中有什么作用?
- Dubbo:是一个高性能的Java RPC(Remote Procedure Call,远程过程调用)框架,由阿里巴巴开源。它致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。
- 在微服务架构中的作用:
- 服务注册与发现:Dubbo提供了服务注册中心(如Zookeeper、Redis等),服务提供者将自己的服务注册到注册中心,服务消费者从注册中心获取服务提供者的地址信息,实现服务的动态发现。例如,在一个电商微服务系统中,商品服务作为服务提供者将商品查询服务注册到Zookeeper,订单服务作为消费者从Zookeeper获取商品服务地址来调用商品查询接口。
- 负载均衡:当有多个服务提供者提供相同的服务时,Dubbo能实现负载均衡,将请求均匀分配到各个服务提供者上,提高系统的整体性能和可用性。比如,有多个商品服务实例,Dubbo可以根据不同的负载均衡策略(如随机、轮询等)将商品查询请求分配到不同的实例上。
- 服务治理:Dubbo支持对服务进行治理,如服务的降级、容错、限流等。在高并发场景下,如果某个服务出现故障,Dubbo可以进行服务降级,返回默认数据,保证系统的基本可用性;还可以对服务进行限流,防止过多请求压垮服务。
- RabbitMQ在项目中如何保证消息不丢失?
- 生产者确认机制:RabbitMQ提供了两种确认模式,confirm模式和return模式。
- confirm模式:生产者将信道设置为confirm模式后,当消息成功到达Broker,Broker会给生产者发送一个确认消息。生产者可以通过监听确认消息来判断消息是否成功发送。例如,使用Spring AMQP时,可以通过实现ConfirmCallback接口来处理确认消息。
- return模式:当消息无法路由到合适的队列时,RabbitMQ会将消息返回给生产者。生产者可以通过实现ReturnCallback接口来处理返回的消息,进行相应的处理,如记录日志、重新发送等。
- 消息持久化:
- 队列持久化:通过将队列声明为持久化队列,即使RabbitMQ服务器重启,队列依然存在。在声明队列时,将durable参数设置为true,如“channel.queueDeclare("myQueue", true, false, false, null);”。
- 消息持久化:将消息的deliveryMode属性设置为2,表示消息持久化。这样消息会被写入磁盘,即使服务器重启,消息也不会丢失。例如,在Spring AMQP中,可以通过设置MessageProperties.PERSISTENT_TEXT_PLAIN来实现消息持久化。
- 消费者确认机制:消费者在接收到消息并处理完成后,向RabbitMQ发送确认消息。RabbitMQ只有在收到确认消息后才会将消息从队列中删除。如果消费者在处理消息过程中出现异常,没有发送确认消息,RabbitMQ会认为消息没有被成功处理,会将消息重新投递给其他消费者(如果有多个消费者)或在一定时间后再次投递给该消费者。在Spring AMQP中,可以通过设置acknowledgeMode来选择不同的确认模式,如AcknowledgeMode.MANUAL表示手动确认,消费者需要在处理完消息后手动调用channel.basicAck方法发送确认消息。