《互联网大厂 Java 面试:从基础到进阶的核心技术大考察》

31 阅读2分钟

第一轮面试 面试官:首先问几个基础问题。Java中ArrayList和HashMap的底层数据结构分别是什么? 王铁牛:ArrayList底层是数组,HashMap底层是数组加链表,JDK 1.8 后引入了红黑树。 面试官:不错,回答得很准确。那ArrayList在扩容时是如何操作的? 王铁牛:当ArrayList的元素个数达到容量阈值时,会创建一个新的数组,新数组容量是原数组的1.5倍,然后将原数组的元素复制到新数组。 面试官:很好。接着,HashMap在put元素时,具体的流程是怎样的? 王铁牛:先计算key的哈希值,通过哈希值找到对应的数组位置,如果该位置为空,直接放入;如果不为空,再判断key是否相同,相同则覆盖,不同则以链表或红黑树的形式存储。

第二轮面试 面试官:接下来问些多线程相关的。讲讲Java多线程中线程池的作用是什么? 王铁牛:线程池可以复用线程,减少线程创建和销毁的开销,提高系统性能,还能控制线程数量,避免资源耗尽。 面试官:那线程池有哪些核心参数? 王铁牛:有核心线程数、最大线程数、存活时间、阻塞队列等。 面试官:很好。那在高并发场景下,如何合理设置线程池的参数? 王铁牛:嗯……就是,根据任务类型和数量来,嗯,具体怎么算我不太清楚了。

第三轮面试 面试官:最后问些框架相关的。Spring框架中IOC和AOP的概念分别是什么? 王铁牛:IOC是控制反转,把对象创建和管理的控制权交给Spring容器;AOP是面向切面编程,在不修改原有代码的基础上,对业务进行增强。 面试官:不错。Spring Boot相比于Spring,有哪些优势? 王铁牛:Spring Boot简化了Spring的配置,能快速搭建项目,内置了很多插件,方便开发。 面试官:那MyBatis框架中,#{}和{}的区别是什么? **王铁牛**:#{}是预编译处理,{}是字符串替换,#{}能防止SQL注入,${}可能会有SQL注入风险。 面试官:好的,今天的面试就到这里。你对自己今天的表现感觉怎么样?整体来看,你对一些基础知识掌握得还不错,但在一些进阶问题上,比如高并发场景下线程池参数设置,回答得不是很清晰。回去等通知吧,我们会综合评估所有候选人后,再做决定。

问题答案

  1. ArrayList和HashMap的底层数据结构
    • ArrayList:底层是数组结构,它可以动态扩容。数组的特点是可以根据索引快速访问元素,但是插入和删除元素在非尾部位置时,需要移动大量元素,时间复杂度较高。
    • HashMap:JDK 1.7及之前,底层是数组加链表。数组的每个位置是一个链表头节点,通过哈希值计算数组索引,将元素放入对应的链表。这种结构在哈希冲突严重时,链表会很长,查询效率降低。JDK 1.8引入了红黑树,当链表长度达到8且数组容量大于等于64时,链表会转换为红黑树,红黑树的查询、插入、删除操作平均时间复杂度为O(logn),提高了查询效率。
  2. ArrayList扩容操作
    • ArrayList有一个容量(capacity)和实际元素个数(size)。当size达到capacity时,会触发扩容。扩容时,会创建一个新的数组,新数组容量为原数组容量的1.5倍(原容量右移一位再加原容量)。然后通过System.arraycopy方法将原数组的元素复制到新数组。例如,原数组容量为10,当第11个元素加入时,会创建一个容量为15的新数组,并将原数组10个元素复制过去。
  3. HashMap put流程
    • 首先计算key的哈希值,通过哈希值与数组长度减1做与运算(hash & (length - 1))得到数组索引位置。
    • 如果该位置为空,直接将键值对放入该位置。
    • 如果该位置不为空,说明发生了哈希冲突。此时会判断该位置的第一个元素(链表头节点或红黑树节点)的key与要插入的key是否相同(通过equals方法),如果相同则覆盖value。
    • 如果key不同,且该位置是链表结构,则将新元素插入链表尾部(JDK 1.7是头插法)。如果链表长度达到8且数组容量大于等于64,链表会转换为红黑树。
    • 如果该位置是红黑树结构,则按照红黑树的插入规则插入新元素。
  4. 线程池作用
    • 复用线程:避免频繁创建和销毁线程带来的开销。线程的创建和销毁需要与操作系统进行交互,开销较大。线程池可以让线程执行完任务后不被销毁,而是回到线程池中等待下一个任务。
    • 控制线程数量:通过设置核心线程数和最大线程数,可以控制系统中同时运行的线程数量,避免过多线程导致系统资源耗尽,如CPU过度占用、内存溢出等问题。
    • 提高响应速度:当有新任务到来时,无需等待线程创建,直接从线程池中获取线程执行任务,提高了系统的响应速度。
  5. 线程池核心参数
    • 核心线程数(corePoolSize):线程池中会一直存活的线程数量,即使这些线程处于空闲状态,也不会被销毁(除非设置了allowCoreThreadTimeOut为true)。
    • 最大线程数(maximumPoolSize):线程池中允许存在的最大线程数量。当任务队列已满且核心线程都在忙碌时,会创建新的线程,直到线程数量达到最大线程数。
    • 存活时间(keepAliveTime):当线程数量超过核心线程数时,多余的空闲线程存活的最长时间。超过这个时间,多余的线程会被销毁。
    • 阻塞队列(workQueue):用于存放等待执行的任务。当核心线程都在忙碌时,新任务会被放入阻塞队列。常见的阻塞队列有ArrayBlockingQueue(有界队列)、LinkedBlockingQueue(无界队列,默认容量为Integer.MAX_VALUE)、SynchronousQueue(不存储任务,直接交给线程处理)等。
  6. 高并发场景下线程池参数设置
    • 核心线程数:如果任务是CPU密集型(如大量计算任务),核心线程数一般设置为CPU核心数或CPU核心数 + 1。因为CPU密集型任务主要消耗CPU资源,过多线程会导致线程上下文切换开销增大。如果是I/O密集型任务(如网络请求、文件读写),核心线程数可以设置为2倍CPU核心数,因为I/O操作会使线程等待,此时可以利用更多线程来提高系统利用率。
    • 最大线程数:一般根据系统资源和任务特性来设置。如果任务队列是有界队列,最大线程数可以适当设置大一些,以应对突发的高并发任务。但如果任务队列是无界队列,最大线程数的设置意义不大,因为任务会一直放入队列,不会创建新线程直到队列满。
    • 存活时间:根据任务的频率来设置。如果任务频率较高,存活时间可以设置短一些,避免过多空闲线程占用资源;如果任务频率较低,存活时间可以设置长一些,减少线程创建和销毁的开销。
  7. Spring中IOC和AOP概念
    • IOC(控制反转):传统的Java开发中,对象的创建和管理由应用程序自己负责,这导致对象之间的耦合度较高。IOC将对象的创建和管理控制权交给Spring容器。例如,在一个业务类中需要使用另一个类的实例,如果没有IOC,需要在业务类中通过new关键字创建实例;而使用IOC后,Spring容器会创建并注入所需的实例,业务类只需要声明依赖即可,降低了对象之间的耦合度。
    • AOP(面向切面编程):AOP是一种编程范式,它将横切关注点(如日志记录、事务管理、权限控制等)从业务逻辑中分离出来,形成独立的切面。在不修改原有业务代码的基础上,通过动态代理等技术将切面逻辑织入到目标方法的执行过程中。例如,在一个电商系统中,订单处理方法可能需要添加事务管理和日志记录功能,通过AOP可以将这些功能封装成切面,在订单处理方法执行前后自动执行相关逻辑。
  8. Spring Boot优势
    • 简化配置:Spring Boot采用约定大于配置的原则,很多Spring框架的配置都有默认值,开发者无需像传统Spring项目那样编写大量的XML配置文件或Java配置类。例如,Spring Boot集成数据库时,只需要在配置文件中简单配置数据库连接信息,就可以自动配置数据源、JdbcTemplate等相关组件。
    • 快速搭建项目:Spring Boot提供了大量的Starter依赖,开发者只需要引入相关的Starter,就可以快速集成各种功能,如Web开发、数据库访问、消息队列等。例如,引入spring - boot - starter - web依赖,就可以快速搭建一个Spring MVC的Web项目。
    • 内置服务器:Spring Boot内置了Tomcat、Jetty等服务器,无需像传统Web项目那样将项目部署到外部服务器,直接通过命令行或IDE就可以启动项目,方便开发和测试。
  9. MyBatis中#{}和${}区别
    • #{}:是预编译处理。MyBatis在处理#{}时,会将SQL中的#{}替换为?占位符,然后使用PreparedStatement进行数据库操作。这种方式可以有效防止SQL注入攻击,因为参数是作为数据传递的,而不是直接拼接到SQL语句中。例如,SQL语句为“SELECT * FROM user WHERE username = #{username}”,实际执行时会变为“SELECT * FROM user WHERE username =?”,然后将参数值作为数据传递给PreparedStatement。
    • **:是字符串替换。MyBatis在处理{}**:是字符串替换。MyBatis在处理{}时,会直接将中的内容替换为实际的值,然后拼接到SQL语句中。这种方式如果使用不当,容易导致SQL注入攻击。例如,SQL语句为“SELECTFROMuserWHEREusername={}中的内容替换为实际的值,然后拼接到SQL语句中。这种方式如果使用不当,容易导致SQL注入攻击。例如,SQL语句为“SELECT * FROM user WHERE username = {username}”,如果用户输入“' OR '1'='1”,则实际执行的SQL语句会变为“SELECT * FROM user WHERE username = ' OR '1'='1”,这样会导致非法查询。