《互联网大厂 Java 求职者面试:从核心知识到热门框架》

47 阅读10分钟

以下是一篇满足要求的文章:

《互联网大厂 Java 求职者面试:从核心知识到热门框架》

在互联网大厂的面试室里,面试官坐在桌前,神情严肃,而求职者王铁牛则略显紧张地坐在对面。

第一轮: 面试官:“首先,说说你对 Java 核心知识的理解,比如面向对象的三大特性是什么?” 王铁牛:“面向对象的三大特性是封装、继承和多态。封装可以将数据和操作封装在一个类中,对外提供特定的接口;继承可以让子类继承父类的属性和方法,实现代码的复用;多态则可以让不同的对象对同一消息做出不同的响应。” 面试官:“不错,理解得很到位。那你再说说 Java 中的基本数据类型有哪些?” 王铁牛:“Java 中的基本数据类型有 byte、short、int、long、float、double、char 和 boolean 。” 面试官:“很好,继续。你知道 Java 中的自动装箱和拆箱吗?” 王铁牛:“知道,自动装箱就是把基本数据类型自动转换成对应的包装类型,拆箱则是把包装类型自动转换成基本数据类型。”

第二轮: 面试官:“接下来谈谈 JUC 相关的知识,你了解线程池吗?它的作用是什么?” 王铁牛:“线程池可以提高线程的复用性,减少创建和销毁线程的开销。它可以管理线程的生命周期,控制线程的数量,避免线程过多导致系统资源耗尽。” 面试官:“嗯,说得不错。那你说说线程池的几种创建方式?” 王铁牛:“有三种创建方式,分别是通过 Executors 工具类的静态方法创建,通过 ThreadPoolExecutor 构造函数创建,以及通过自定义线程工厂创建。” 面试官:“再说说线程池的工作原理吧。” 王铁牛:“线程池中有核心线程池和线程队列等组件,当有任务提交时,会先判断核心线程池是否已满,如果未满则创建新线程执行任务,否则将任务放入线程队列中等待执行。如果线程队列已满,则会根据线程池的拒绝策略来处理任务。”

第三轮: 面试官:“接着谈谈 JVM 方面的知识,你知道 JVM 的内存结构吗?” 王铁牛:“JVM 的内存结构主要包括堆、栈、方法区、本地方法栈和程序计数器。堆用于存储对象实例,栈用于存储局部变量和方法调用栈,方法区用于存储类信息、常量池等,本地方法栈用于存储本地方法的调用信息,程序计数器用于记录当前线程执行的字节码地址。” 面试官:“对,理解得很准确。那你说说 Java 中的垃圾回收机制是怎样的?” 王铁牛:“垃圾回收机制主要是通过标记-清除、复制、标记-整理等算法来回收不再被引用的对象所占用的内存。当内存空间不足时,垃圾回收器会自动触发回收操作。” 面试官:“最后,说说 HashMap 和 ArrayList 的区别吧。” 王铁牛:“HashMap 是基于哈希表实现的,用于存储键值对,插入和查找速度快;ArrayList 是基于数组实现的,用于存储一组元素,随机访问速度快。”

面试官:“今天的面试就到这里,你可以回去等通知了。”

答案:

  • 面向对象的三大特性
    • 封装:将数据和操作封装在一个类中,通过访问修饰符控制对类内部成员的访问,对外提供特定的接口,隐藏了内部实现细节,提高了代码的安全性和可维护性。
    • 继承:子类继承父类的属性和方法,子类可以在父类的基础上进行扩展和修改,实现了代码的复用,提高了开发效率。
    • 多态:不同的对象对同一消息做出不同的响应,通过方法重写和向上转型实现。多态可以使代码更加灵活和可扩展,提高了代码的可维护性和可扩展性。
  • Java 中的基本数据类型
    • byte:占 1 个字节,范围是 -128 到 127 ,用于存储整数。
    • short:占 2 个字节,范围是 -32768 到 32767 ,用于存储整数。
    • int:占 4 个字节,范围是 -2147483648 到 2147483647 ,用于存储整数。
    • long:占 8 个字节,范围是 -9223372036854775808 到 9223372036854775807 ,用于存储整数。
    • float:占 4 个字节,用于存储单精度浮点数。
    • double:占 8 个字节,用于存储双精度浮点数。
    • char:占 2 个字节,用于存储字符,使用 Unicode 编码。
    • boolean:占 1 个字节,用于存储布尔值,只有 true 和 false 两个值。
  • 自动装箱和拆箱
    • 自动装箱:在 Java 5 及以后的版本中,基本数据类型可以自动转换成对应的包装类型,例如将 int 类型转换成 Integer 类型。自动装箱是通过调用包装类型的 valueOf() 方法实现的。
    • 自动拆箱:与自动装箱相反,包装类型可以自动转换成对应的基本数据类型,例如将 Integer 类型转换成 int 类型。自动拆箱是通过调用包装类型的 xxxValue() 方法实现的,其中 xxx 是基本数据类型的名称。
  • 线程池的作用和几种创建方式
    • 作用:提高线程的复用性,减少创建和销毁线程的开销;可以管理线程的生命周期,控制线程的数量,避免线程过多导致系统资源耗尽;提供了一种线程管理的机制,方便对线程进行统一的调度和管理。
    • 创建方式
      • 通过 Executors 工具类的静态方法创建:例如 Executors.newFixedThreadPool(int nThreads) 创建固定大小的线程池,Executors.newCachedThreadPool() 创建可缓存的线程池,Executors.newSingleThreadExecutor() 创建单线程的线程池。
      • 通过 ThreadPoolExecutor 构造函数创建:可以通过自定义线程池的参数,如核心线程数、最大线程数、线程队列容量等,来创建线程池。
      • 通过自定义线程工厂创建:可以创建自定义的线程工厂,用于设置线程的名称、优先级等属性。
  • 线程池的工作原理
    • 线程池中有核心线程池和线程队列等组件。当有任务提交时,首先会判断核心线程池是否已满,如果未满则创建新线程执行任务;如果核心线程池已满,则将任务放入线程队列中等待执行。
    • 如果线程队列已满,则会根据线程池的拒绝策略来处理任务。拒绝策略有 AbortPolicy(直接抛出异常)、CallerRunsPolicy(由调用者所在的线程来执行任务)、DiscardPolicy(直接丢弃任务)、DiscardOldestPolicy(丢弃队列中最旧的任务,并尝试重新提交当前任务)。
    • 当线程池中的线程数量超过核心线程池的大小且线程队列已满时,会创建新的线程来执行任务,直到线程数量达到最大线程数。如果线程数量达到最大线程数后仍然有任务提交,则会根据拒绝策略来处理任务。
  • JVM 的内存结构
    • :是 JVM 管理的最大的一块内存区域,用于存储对象实例和数组。堆分为新生代和老年代,新生代又分为 Eden 区、From Survivor 区和 To Survivor 区。对象首先在 Eden 区分配内存,当 Eden 区满时,进行 Minor GC(新生代垃圾回收),将存活的对象复制到 Survivor 区,经过若干次 Minor GC 后,如果对象仍然存活,则将其移动到老年代。
    • :用于存储局部变量、方法参数和方法调用栈。每个线程都有自己的栈,栈的大小是固定的,随着方法的调用和返回,栈帧会在栈中进行压栈和出栈操作。
    • 方法区:用于存储类信息、常量池、静态变量等。方法区是共享的内存区域,被所有线程共享。
    • 本地方法栈:用于存储本地方法的调用信息。本地方法是用其他语言(如 C、C++)实现的方法,通过 JNI(Java Native Interface)调用。
    • 程序计数器:用于记录当前线程执行的字节码地址,字节码解释器通过程序计数器来确定下一条要执行的字节码指令。程序计数器是线程私有的,每个线程都有自己的程序计数器。
  • Java 中的垃圾回收机制
    • 垃圾回收机制主要是通过标记-清除、复制、标记-整理等算法来回收不再被引用的对象所占用的内存。
    • 标记-清除算法:首先标记出所有需要回收的对象,然后统一回收这些对象所占用的内存。该算法的缺点是标记和清除过程效率较低,并且会产生内存碎片。
    • 复制算法:将内存分为两个相等的区域,每次只使用其中一个区域。当对象内存满时,将存活的对象复制到另一个区域,然后清除当前区域的所有对象。复制算法的优点是效率高,不会产生内存碎片,但需要两倍的内存空间。
    • 标记-整理算法:首先标记出所有需要回收的对象,然后将存活的对象向一端移动,最后清理掉边界以外的内存。标记-整理算法既可以解决标记-清除算法的内存碎片问题,又可以提高效率。
    • Java 的垃圾回收器会根据不同的情况选择合适的垃圾回收算法来进行内存回收。在新生代中,通常使用复制算法;在老年代中,通常使用标记-整理算法或标记-清除算法。
  • HashMap 和 ArrayList 的区别
    • 数据结构
      • HashMap:基于哈希表实现,通过哈希函数将键映射到哈希表中的位置,从而实现快速的插入、删除和查找操作。哈希表是由数组和链表组成的,数组用于存储桶,链表用于解决哈希冲突。
      • ArrayList:基于数组实现,数组的长度是固定的,当数组满时需要进行扩容。ArrayList 可以根据索引快速访问元素,但插入和删除元素的效率较低,因为需要移动数组中的元素。
    • 遍历方式
      • HashMap:遍历键值对时,可以通过迭代器或增强 for 循环来遍历。迭代器可以遍历所有的键值对,增强 for 循环可以遍历所有的键或值。
      • ArrayList:遍历元素时,可以通过迭代器或增强 for 循环来遍历。迭代器可以遍历所有的元素,增强 for 循环可以遍历所有的元素。
    • 线程安全性
      • HashMap:不是线程安全的,在多线程环境下可能会出现数据不一致的问题。如果需要在多线程环境下使用 HashMap ,可以使用 ConcurrentHashMap 。
      • ArrayList:也不是线程安全的,在多线程环境下可能会出现并发修改异常。如果需要在多线程环境下使用 ArrayList ,可以使用 Vector 或 CopyOnWriteArrayList 。
    • 适用场景
      • HashMap:适用于存储键值对,需要快速的插入、删除和查找操作,例如缓存、数据库连接池等。
      • ArrayList:适用于存储一组元素,需要随机访问元素,例如列表展示、数据存储等。