《揭秘互联网大厂Java岗面试:从核心知识到热门框架与中间件的层层考验》

61 阅读14分钟

《揭秘互联网大厂Java岗面试:从核心知识到热门框架与中间件的层层考验》

在一间明亮的会议室里,一场重要的Java程序员面试正在紧张进行着。面试官神情严肃,经验丰富,对每一个前来应聘的求职者都有着高标准的考察。而坐在对面的求职者王铁牛,虽说有点基础,但面对复杂的技术问题,心里还是有点发虚。

第一轮面试提问:

面试官: 首先,我先问几个Java核心知识相关的基础问题啊。 第一个问题,Java中基本数据类型有哪些? 第二个问题,说说==和equals方法的区别吧。 第三个问题,在Java里,静态变量和实例变量有啥不同?

王铁牛: 嗯,Java的基本数据类型有byte、short、int、long、float、double、char、boolean这些。 然后,==比较的是两个对象的地址是否相等,equals方法在没重写之前也是比较地址,不过很多类会重写它来比较对象的内容是否相等。 静态变量是属于类的,只有一份,在类加载的时候就初始化了;实例变量是属于每个对象的,每个对象都有自己独立的一份实例变量,创建对象的时候才会初始化。

面试官:嗯,不错,这些基础点回答得挺清晰的。

第二轮面试提问:

面试官: 接下来,我们深入一点啊。 第一个问题,讲讲JVM的内存结构吧,主要说说堆和栈的区别。 第二个问题,在多线程环境下,你怎么保证线程安全地使用HashMap? 第三个问题,ArrayList和LinkedList有哪些区别,在什么场景下会优先选择LinkedList?

王铁牛: 呃,JVM内存结构嘛,有堆、栈、方法区这些吧。堆是用来存放对象的,栈是用来存放局部变量那些的。区别嘛,堆内存大一些,栈内存小一些,栈是线程私有的,堆是线程共享的吧,好像是这样。 那个多线程用HashMap保证安全,嗯,我想想啊,好像可以用Collections的那个同步方法把它变成同步的HashMap,就可以安全用了吧。 ArrayList和LinkedList的区别啊,ArrayList查询快,因为它是数组实现的;LinkedList增删快,因为它是链表实现的。优先选LinkedList的场景,嗯,就是经常要在中间插入删除元素的时候吧。

面试官:嗯,关于JVM内存结构的回答还需要更准确细致些,多线程使用HashMap那部分回答得不太完整,ArrayList和LinkedList区别这块还凑合。

第三轮面试提问:

面试官: 好,那我们再进一步考察下其他方面的知识。 第一个问题,说说Spring框架里的IOC和AOP的核心概念以及它们的作用。 第二个问题,在SpringBoot项目中,如何配置和使用自定义的配置文件? 第三个问题,简单说下MyBatis的工作原理,以及它是怎么实现数据库交互的?

王铁牛: Spring的IOC啊,就是控制反转嘛,把对象的创建和管理交给Spring容器去做了,不用我们自己在代码里new对象了。AOP嘛,就是面向切面编程,能在不修改原来代码的基础上,给代码添加一些通用的功能,比如日志记录啥的。 SpringBoot配置自定义配置文件啊,这个,我记得好像是在那个resources目录下创建个配置文件,然后在代码里用注解去读取里面的值吧,具体咋弄我有点记不太清了。 MyBatis的工作原理啊,就是先写那些SQL语句在XML文件里或者注解里,然后MyBatis会根据这些SQL去数据库里执行查询、插入这些操作,它会把数据库返回的数据映射成Java对象啥的,具体咋实现的我也不是特别清楚。

面试官:嗯,IOC和AOP的概念回答得还算可以,但不够深入。SpringBoot配置文件那部分回答得比较模糊,MyBatis原理这块也没说清楚。行吧,今天的面试就先到这儿吧,你先回去等通知,我们后续会综合评估下你的情况再做决定。

以下是上述问题的详细答案:

第一轮问题答案:

  • Java中基本数据类型有哪些?

    • Java的基本数据类型分为四类八种。
    • 整数类型:byte(占1个字节,范围是-128到127)、short(占2个字节,范围是-32768到32767)、int(占4个字节,范围是-2147483648到2147483647)、long(占8个字节,范围是-9223372036854775808到9223372036854775807,定义时需在数值后加L或l)。
    • 浮点类型:float(占4个字节,有效数字大概是6 - 7位,定义时需在数值后加F或f)、double(占8个字节,有效数字大概是15位左右)。
    • 字符类型:char(占2个字节,用来表示单个字符,如'a'、'1'等)。
    • 布尔类型:boolean(占1位,只有true和false两个值)。
  • ==和equals方法的区别?

    • ==:
      • 对于基本数据类型,比较的是值是否相等。例如:int a = 5; int b = 5; 那么a == b的结果是true。
      • 对于引用数据类型,比较的是对象的引用地址是否相等,也就是判断两个对象是否是同一个对象。例如:String s1 = new String("hello"); String s2 = new String("hello"); s1 == s2的结果是false,因为它们是两个不同的对象实例,虽然内容相同,但地址不同。
    • equals:
      • 它是Object类中的一个方法,在Object类中,equals方法的默认实现就是==,也就是比较对象的引用地址。
      • 但是很多类会重写equals方法,比如String类,重写后的equals方法用于比较对象的内容是否相等。例如:String s1 = new String("hello"); String s2 = new String("hello"); s1.equals(s2)的结果是true,因为String类重写了equals方法来比较字符串的内容。
  • 静态变量和实例变量有啥不同?

    • 静态变量:
      • 属于类本身,不属于类的任何一个具体实例。在类加载时就会被初始化,只有一份内存空间,被类的所有实例共享。
      • 可以通过类名直接访问,例如:class MyClass { static int staticVar = 10; } 可以通过MyClass.staticVar来访问静态变量。
    • 实例变量:
      • 属于类的每一个具体实例对象,每个对象都有自己独立的一份实例变量。
      • 在创建对象时才会被初始化,只能通过对象来访问,例如:class MyClass { int instanceVar; } MyClass obj = new MyClass(); obj.instanceVar来访问实例变量。

第二轮问题答案:

  • JVM的内存结构吧,主要说说堆和栈的区别?

    • JVM内存结构主要包括:程序计数器、虚拟机栈、本地方法栈、堆、方法区(在Java 8及以后,方法区的实现称为元空间)等部分。
    • 堆:
      • 是JVM管理的最大的一块内存区域,用于存放对象实例以及数组等数据。
      • 是线程共享的内存区域,在Java中所有的对象实例和数组都在堆上分配内存。
      • 堆内存又可以细分为新生代(Eden区、Survivor区)和老年代,新生代用于存放新创建的对象,老年代用于存放经过多次垃圾回收后仍然存活的对象。
    • 栈:
      • 主要指虚拟机栈,每个线程都有自己独立的一个虚拟机栈,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
      • 是线程私有的内存区域,随着线程的创建而创建,随着线程的结束而销毁。
      • 局部变量表中存放着方法参数、局部变量等数据,在方法调用时入栈,方法执行结束后出栈。
  • 在多线程环境下,你怎么保证线程安全地使用HashMap?

    • HashMap本身是非线程安全的,在多线程环境下直接使用可能会导致数据不一致等问题。以下是几种常见的保证线程安全使用HashMap的方法:
      • 使用Collections.synchronizedMap方法:
        • 例如:Map<String, Integer> hashMap = new HashMap<>(); Map<String, Integer> synchronizedMap = Collections.synchronizedMap(hashMap);
        • 这种方法会返回一个线程安全的Map,它通过在所有修改操作(如put、remove等)上添加同步锁来实现线程安全,但这种方式在高并发场景下性能可能不太好,因为每次操作都需要获取锁。
      • 使用ConcurrentHashMap:
        • ConcurrentHashMap是Java提供的一个线程安全的哈希表实现,它采用了更先进的并发控制机制,如分段锁等。
        • 例如:Map<String, Integer> concurrentHashMap = new ConcurrentHashMap<>();
        • 在多线程环境下,它可以高效地支持并发读写操作,性能比使用Collections.synchronizedMap要好很多。
  • ArrayList和LinkedList有哪些区别,在什么场景下会优先选择LinkedList?

    • 区别:
      • 数据结构:
        • ArrayList是基于动态数组实现的,它在内存中是连续的存储空间,通过索引可以快速访问元素,查询效率高,时间复杂度为O(1)(在不考虑扩容等情况时)。
        • LinkedList是基于双向链表实现的,每个节点包含数据和指向前一个节点以及后一个节点的指针,它的插入和删除操作相对灵活,在链表中间插入或删除一个元素的时间复杂度为O(1)(只需要修改指针即可)。
      • 内存占用:
        • ArrayList在初始化时会分配一定的初始容量,如果元素数量超过容量需要扩容,扩容时可能会涉及到数组的复制等操作,会占用一定的额外内存。
        • LinkedList每个节点除了存储数据外,还需要存储前后节点的指针,相对来说内存占用会多一些。
      • 性能特点:
        • ArrayList的查询性能好,但插入和删除性能较差,尤其是在中间位置插入或删除元素时,需要移动后面的所有元素,时间复杂度为O(n)。
        • LinkedList的插入和删除性能好,但查询性能较差,需要从链表头或链表尾开始遍历查找元素,时间复杂度为O(n)。
    • 优先选择LinkedList的场景:
      • 当需要频繁地在列表中间进行插入和删除操作时,比如实现一个双向链表的数据结构,或者在一个队列、栈等数据结构中,需要频繁地在头部或尾部进行插入和删除操作,此时LinkedList会更合适。

第三轮问题答案:

  • 说说Spring框架里的IOC和AOP的核心概念以及它们的作用?

    • IOC(Inversion of Control,控制反转):

      • 核心概念:传统的程序设计中,对象的创建和依赖关系的管理通常是由程序员在代码中手动完成的,比如通过new关键字来创建对象。而在Spring的IOC模式下,对象的创建和依赖关系的管理不再由程序员手动完成,而是交给Spring容器来完成。Spring容器会根据配置文件(如XML配置文件或基于注解的配置)来创建对象,并管理它们之间的依赖关系。
      • 作用:
        • 降低代码的耦合度:因为对象的创建和依赖关系由容器管理,所以在代码中不需要再到处去new对象,当需要修改某个对象的实现时,只需要在容器配置中进行修改,而不需要在大量的代码中去查找和修改相关的new语句。
        • 提高代码的可维护性和可扩展性:便于对代码进行单元测试,因为可以很容易地通过容器获取到需要测试的对象,并且可以方便地替换对象的实现来进行不同情况的测试。
    • AOP(Aspect Oriented Programming,面向切面编程):

      • 核心概念:AOP是一种编程范式,它主要关注的是横切关注点(Cross-cutting Concerns),也就是那些在多个业务逻辑中普遍存在但又不属于业务逻辑本身的功能,比如日志记录、事务管理、权限控制等。AOP通过将这些横切关注点从业务逻辑中分离出来,形成独立的切面(Aspect),然后在合适的时机将切面织入到业务逻辑中。
      • 作用:
        • 分离关注点:将业务逻辑和非业务逻辑(如日志记录、事务管理等)分开,使得业务逻辑更加清晰,便于理解和维护。
        • 提高代码的复用性:因为这些横切关注点被提取出来形成了独立的切面,所以可以在多个不同的业务逻辑中复用这些切面,而不需要在每个业务逻辑中都重复编写相关的功能代码。
  • 在SpringBoot项目中,如何配置和使用自定义的配置文件?

    • SpringBoot默认会读取application.properties或application.yml等配置文件来获取项目的配置信息。如果要使用自定义的配置文件,可以按照以下步骤:
      • 创建自定义配置文件:在src/main/resources目录下创建一个自定义的配置文件,比如myconfig.properties或myconfig.yml等,文件格式根据自己的需求选择。
      • 定义配置项:在自定义配置文件中定义需要的配置项,例如:my.custom.property=value。
      • 在Java代码中读取配置:
        • 如果使用properties文件,可以通过@Value注解来读取配置项的值,例如:
          • @Component
          • public class MyConfigClass { @Value("${my.custom.property}") private String myCustomProperty; }
        • 如果使用yml文件,可以通过@ConfigurationProperties注解来读取配置项的值,例如:
          • @Component
          • @ConfigurationProperties(prefix = "my.custom")
          • public class MyConfigClass { private String myCustomProperty; }
        • 还可以通过Environment对象来读取配置项的值,例如:
          • @Component
          • public class MyConfigClass { @Autowired private Environment environment; private String myCustomProperty = environment.getProperty("my.custom.property"); }
  • 简单说下MyBatis的工作原理,以及它是怎么实现数据库交互的?

    • 工作原理:
      • MyBatis首先会读取配置文件(可以是XML配置文件或者基于注解的配置),配置文件中包含了数据库连接信息、映射文件的位置等内容。
      • 然后根据映射文件(XML文件或者基于注解的映射)中的SQL语句与Java对象的映射关系,将Java对象中的数据转换为SQL语句中的参数,或者将数据库返回的结果集转换为Java对象。
      • 当执行一个数据库操作(如查询、插入、删除等)时,MyBatis会根据配置文件和映射文件的信息,找到对应的SQL语句,然后通过JDBC驱动程序与数据库进行连接并执行SQL语句。
      • 最后,将数据库返回的结果(如果是查询操作)按照映射关系转换为Java对象并返回给调用者。
    • 实现数据库交互:
      • 配置文件:通过编写XML配置文件或者使用注解来配置MyBatis,包括配置数据库连接池、映射文件的位置等。
      • 映射文件:在映射文件中(XML格式或者基于注解)编写具体的SQL语句,并定义SQL语句与Java对象之间的映射关系,比如通过resultMap来定义结果集与Java对象的映射,通过parameterMap(或者在注解中直接指定参数)来定义Java对象与SQL语句参数的映射。
      • JDBC驱动:MyBatis底层是通过JDBC驱动来实现与数据库的连接和交互的,它会根据配置文件中的数据库连接信息,加载相应的JDBC驱动,然后利用JDBC的相关API来执行SQL语句、获取结果集等操作。