《互联网大厂面试:Java核心知识、框架与中间件全方位考察》

互联网大厂面试:Java核心知识、框架与中间件全方位考察

王铁牛怀揣着紧张与期待,坐在了互联网大厂的面试房间里,对面坐着表情严肃的面试官,一场关于Java技术的严峻考验即将拉开帷幕。

第一轮提问 面试官:“首先问几个基础问题。Java里的多态是如何实现的?” 王铁牛:“多态主要通过继承、接口实现和方法重写来实现。子类继承父类,重写父类的方法,这样在调用时可以根据实际对象类型调用不同的方法;实现接口也是类似,不同的类实现同一个接口,重写接口里的方法。” 面试官:“回答得不错。那说说ArrayList和LinkedList的区别。” 王铁牛:“ArrayList是基于数组实现的,它的优点是随机访问速度快,通过索引可以直接访问元素;缺点是插入和删除操作效率低,因为可能需要移动大量元素。LinkedList是基于双向链表实现的,插入和删除操作效率高,但是随机访问速度慢,需要从头或尾开始遍历链表。” 面试官:“很好。那HashMap的底层数据结构是什么?” 王铁牛:“HashMap在JDK 1.8之前是数组 + 链表的结构,JDK 1.8及以后是数组 + 链表 + 红黑树。当链表长度超过8且数组长度大于64时,链表会转化为红黑树,以提高查找效率。”

第二轮提问 面试官:“看来基础还挺扎实。那聊聊多线程吧。创建线程有几种方式?” 王铁牛:“有四种方式,分别是继承Thread类、实现Runnable接口、实现Callable接口并结合FutureTask使用,还有使用线程池。” 面试官:“不错。那线程池的核心参数有哪些,分别有什么作用?” 王铁牛:“线程池的核心参数有corePoolSize(核心线程数)、maximumPoolSize(最大线程数)、keepAliveTime(线程空闲存活时间)、unit(时间单位)、workQueue(任务队列)、threadFactory(线程工厂)和handler(拒绝策略)。核心线程数是线程池一直保持的线程数量,最大线程数是线程池最多能容纳的线程数量。任务队列用于存放等待执行的任务,当核心线程都在工作时,新任务会放入队列。线程空闲存活时间是指当线程空闲超过这个时间就会被销毁。线程工厂用于创建线程,拒绝策略是当任务队列满了且线程数达到最大线程数时,对新任务的处理方式。” 面试官:“回答得很全面。那JUC里的CountDownLatch是做什么用的?” 王铁牛:“CountDownLatch可以让一个或多个线程等待其他线程完成操作。通过构造函数设置一个初始计数值,当线程完成任务时调用countDown()方法将计数值减1,等待的线程调用await()方法,当计数值减到0时,等待的线程才会继续执行。”

第三轮提问 面试官:“多线程掌握得不错。接下来谈谈框架。Spring的IOC和AOP是什么?” 王铁牛:“IOC是控制反转,它把对象的创建和依赖关系的管理交给Spring容器,而不是由对象本身来控制,这样降低了代码的耦合度。AOP是面向切面编程,它可以在不修改原有代码的情况下,对程序进行增强,比如实现日志记录、事务管理等功能。” 面试官:“理解得还可以。那Spring Boot和Spring有什么区别和联系?” 王铁牛:“Spring Boot是基于Spring的,它简化了Spring应用的开发。Spring需要大量的配置文件,而Spring Boot通过自动配置和starter依赖,减少了配置的工作量,让开发人员可以更快速地搭建项目。它们的核心功能是相通的,Spring Boot本质上还是使用了Spring的IOC和AOP等功能。” 面试官:“有点意思。那MyBatis的一级缓存和二级缓存是怎么回事?” 王铁牛:“呃……这个嘛,好像是和数据的缓存有关。一级缓存是在一个SqlSession里的缓存,二级缓存是全局的缓存,但是具体怎么实现和使用我有点不太清楚。” 面试官:“看来这方面掌握得不太好。那Dubbo和RabbitMQ分别有什么作用?” 王铁牛:“Dubbo好像是个分布式服务框架,能实现服务的调用和管理;RabbitMQ是个消息队列,能实现异步通信,但是具体细节我不太能说清楚。” 面试官:“那xxl - job和Redis你了解多少?” 王铁牛:“xxl - job好像是个分布式任务调度框架,Redis是个内存数据库,不过它们具体的使用场景和原理我就不太明白了。”

面试官:“通过这几轮的提问,我能看出你对Java的一些基础核心知识掌握得还可以,像多态、集合类、多线程和线程池这些方面回答得比较准确,对Spring和Spring Boot也有一定的理解。但是在一些中间件和框架的深入应用上,比如MyBatis的缓存、Dubbo、RabbitMQ、xxl - job和Redis等方面,你的知识储备还不够完善,回答得不够清晰和准确。回去之后你可以再深入学习一下这些内容。你先回家等通知吧。”

问题答案详解

  1. Java里的多态是如何实现的

    • 多态是指同一个行为具有多个不同表现形式或形态的能力。在Java中,多态主要通过以下几种方式实现:
      • 继承:子类继承父类,并重写父类的方法。例如,有一个父类Animal,有一个子类Dog,Animal类有一个方法叫sound(),Dog类重写了这个方法,当通过父类引用指向子类对象时,调用sound()方法会根据实际对象类型调用Dog类的sound()方法。
      class Animal {
          public void sound() {
              System.out.println("Animal makes a sound");
          }
      }
      
      class Dog extends Animal {
          @Override
          public void sound() {
              System.out.println("Dog barks");
          }
      }
      
      public class Main {
          public static void main(String[] args) {
              Animal animal = new Dog();
              animal.sound();
          }
      }
      
      • 接口实现:不同的类实现同一个接口,并重写接口中的方法。例如,有一个接口Shape,有两个类Circle和Rectangle实现了这个接口,并重写了接口中的area()方法,通过接口引用指向不同的实现类对象,调用area()方法会根据实际对象类型调用相应类的area()方法。
      • 方法重写:是实现多态的关键,在子类中对父类的方法进行重新定义,要求方法名、参数列表和返回值类型都相同。
  2. ArrayList和LinkedList的区别

    • 数据结构:ArrayList是基于动态数组实现的,而LinkedList是基于双向链表实现的。
    • 随机访问:ArrayList支持随机访问,通过索引可以直接访问元素,时间复杂度为O(1);LinkedList不支持随机访问,需要从头或尾开始遍历链表,时间复杂度为O(n)。
    • 插入和删除操作:ArrayList在中间插入或删除元素时,需要移动大量元素,时间复杂度为O(n);LinkedList在插入和删除元素时,只需要修改节点的指针,时间复杂度为O(1)。
    • 内存占用:ArrayList的内存占用主要是数组的空间,可能会有一定的空间浪费;LinkedList的内存占用除了存储元素外,还需要存储节点的指针,相对占用更多的内存。
  3. HashMap的底层数据结构是什么

    • 在JDK 1.8之前,HashMap的底层数据结构是数组 + 链表。数组是HashMap的主体,每个数组元素是一个链表的头节点,当发生哈希冲突时,新的元素会插入到链表中。
    • 在JDK 1.8及以后,HashMap的底层数据结构是数组 + 链表 + 红黑树。当链表长度超过8且数组长度大于64时,链表会转化为红黑树,以提高查找效率。红黑树是一种自平衡的二叉搜索树,查找、插入和删除操作的时间复杂度为O(log n)。
  4. 创建线程有几种方式

    • 继承Thread类:创建一个类继承Thread类,并重写run()方法,然后创建该类的对象并调用start()方法启动线程。
    class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("Thread is running");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            MyThread thread = new MyThread();
            thread.start();
        }
    }
    
    • 实现Runnable接口:创建一个类实现Runnable接口,并重写run()方法,然后将该类的对象作为参数传递给Thread类的构造函数,最后调用start()方法启动线程。
    class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("Runnable is running");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            MyRunnable runnable = new MyRunnable();
            Thread thread = new Thread(runnable);
            thread.start();
        }
    }
    
    • 实现Callable接口并结合FutureTask使用:创建一个类实现Callable接口,并重写call()方法,该方法有返回值。将该类的对象作为参数传递给FutureTask类的构造函数,再将FutureTask对象作为参数传递给Thread类的构造函数,最后调用start()方法启动线程。可以通过FutureTask的get()方法获取线程执行的结果。
    import java.util.concurrent.Callable;
    import java.util.concurrent.FutureTask;
    
    class MyCallable implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            return 1 + 2;
        }
    }
    
    public class Main {
        public static void main(String[] args) throws Exception {
            MyCallable callable = new MyCallable();
            FutureTask<Integer> futureTask = new FutureTask<>(callable);
            Thread thread = new Thread(futureTask);
            thread.start();
            System.out.println(futureTask.get());
        }
    }
    
    • 使用线程池:通过Executors工具类创建不同类型的线程池,将实现了Runnable或Callable接口的任务提交给线程池执行。
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("Task is running");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            ExecutorService executorService = Executors.newFixedThreadPool(2);
            MyRunnable runnable = new MyRunnable();
            executorService.submit(runnable);
            executorService.shutdown();
        }
    }
    
  5. 线程池的核心参数有哪些,分别有什么作用

    • corePoolSize(核心线程数):线程池一直保持的线程数量,当提交的任务数小于核心线程数时,线程池会创建新的线程来执行任务。
    • maximumPoolSize(最大线程数):线程池最多能容纳的线程数量,当任务队列满了且线程数达到核心线程数时,线程池会创建新的线程,直到达到最大线程数。
    • keepAliveTime(线程空闲存活时间):当线程空闲超过这个时间就会被销毁,前提是线程数大于核心线程数。
    • unit(时间单位):keepAliveTime的时间单位,如秒、毫秒等。
    • workQueue(任务队列):用于存放等待执行的任务,常用的任务队列有ArrayBlockingQueue、LinkedBlockingQueue等。
    • threadFactory(线程工厂):用于创建线程,可以自定义线程的名称、优先级等。
    • handler(拒绝策略):当任务队列满了且线程数达到最大线程数时,对新任务的处理方式。常用的拒绝策略有AbortPolicy(直接抛出异常)、CallerRunsPolicy(由调用线程执行任务)、DiscardPolicy(直接丢弃任务)、DiscardOldestPolicy(丢弃队列中最老的任务)。
  6. JUC里的CountDownLatch是做什么用的

    • CountDownLatch是一个同步工具类,它可以让一个或多个线程等待其他线程完成操作。通过构造函数设置一个初始计数值,当线程完成任务时调用countDown()方法将计数值减1,等待的线程调用await()方法,当计数值减到0时,等待的线程才会继续执行。
    import java.util.concurrent.CountDownLatch;
    
    public class CountDownLatchExample {
        public static void main(String[] args) throws InterruptedException {
            int n = 3;
            CountDownLatch latch = new CountDownLatch(n);
    
            for (int i = 0; i < n; i++) {
                new Thread(() -> {
                    System.out.println(Thread.currentThread().getName() + " is working");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " has finished");
                    latch.countDown();
                }).start();
            }
    
            latch.await();
            System.out.println("All threads have finished, main thread can continue");
        }
    }
    
  7. Spring的IOC和AOP是什么

    • IOC(控制反转):是一种设计原则,它把对象的创建和依赖关系的管理交给Spring容器,而不是由对象本身来控制。通过依赖注入(DI)的方式,将对象的依赖关系注入到对象中,降低了代码的耦合度。例如,有一个UserService类依赖于UserDao类,在传统的方式中,UserService类需要自己创建UserDao对象,而在Spring中,通过配置或注解的方式,将UserDao对象注入到UserService类中。
    • AOP(面向切面编程):是一种编程范式,它可以在不修改原有代码的情况下,对程序进行增强。AOP的核心概念包括切面(Aspect)、连接点(Join Point)、切点(Pointcut)、通知(Advice)等。切面是一个包含了通知和切点的模块,连接点是程序执行过程中的某个点,切点是一组连接点的集合,通知是在切点处执行的代码,包括前置通知、后置通知、环绕通知等。例如,在进行日志记录或事务管理时,可以使用AOP来实现,将这些通用的功能从业务逻辑中分离出来。
  8. Spring Boot和Spring有什么区别和联系

    • 联系:Spring Boot是基于Spring的,它继承了Spring的核心功能,如IOC和AOP等。Spring Boot本质上还是使用了Spring的框架来构建应用程序。
    • 区别:Spring需要大量的配置文件,如XML配置文件或Java配置类,开发人员需要手动配置各种组件和依赖关系。而Spring Boot通过自动配置和starter依赖,减少了配置的工作量。Spring Boot提供了默认的配置,当开发人员引入相应的starter依赖时,Spring Boot会自动配置相关的组件,让开发人员可以更快速地搭建项目。
  9. MyBatis的一级缓存和二级缓存是怎么回事

    • 一级缓存:是基于SqlSession的缓存,在同一个SqlSession中,执行相同的查询语句时,会先从缓存中查找,如果缓存中有则直接返回,否则从数据库中查询,并将查询结果存入缓存。当SqlSession关闭或执行了增删改操作时,一级缓存会被清空。
    • 二级缓存:是全局的缓存,多个SqlSession可以共享二级缓存。需要在MyBatis的配置文件中开启二级缓存,并在Mapper.xml文件中配置相应的缓存策略。当执行查询操作时,会先从二级缓存中查找,如果缓存中有则直接返回,否则从数据库中查询,并将查询结果存入二级缓存。二级缓存的作用范围更大,能提高查询效率,但需要注意缓存的一致性问题。
  10. Dubbo和RabbitMQ分别有什么作用

    • Dubbo:是一个高性能的分布式服务框架,主要用于解决分布式系统中的服务调用和管理问题。它提供了服务注册与发现、远程调用、负载均衡、集群容错等功能。通过Dubbo,服务提供者可以将自己的服务注册到注册中心,服务消费者可以从注册中心获取服务提供者的地址,并进行远程调用。Dubbo支持多种协议,如Dubbo协议、HTTP协议等,能提高分布式系统的开发效率和性能。
    • RabbitMQ:是一个开源的消息队列中间件,主要用于实现异步通信和系统解耦。生产者将消息发送到RabbitMQ的交换器,交换器根据路由规则将消息路由到相应的队列,消费者从队列中获取消息并进行处理。RabbitMQ支持多种消息模型,如点对点模型、发布 - 订阅模型等,能提高系统的吞吐量和可靠性,同时降低系统之间的耦合度。
  11. xxl - job和Redis你了解多少

    • xxl - job:是一个分布式任务调度框架,它提供了简单易用的任务调度功能。可以通过Web界面进行任务的配置、管理和监控,支持任务的定时调度、分片广播、失败重试等功能。xxl -
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值