「Java」Java面试宝典:全面覆盖常见问题和难点解析

Java面试宝典:全面覆盖常见问题和难点解析

  • 1. 怎么理解Spring MVC?
  • 2. 为什么说ConcurrentHashMap是线程安全的?
  • 3. 为什么说HashMap是线程不安全的?
  • 4. 遍历map的方法
  • 5. HashSet怎样处理重复的数据?
  • 6. HashSet 具备哪些特点?
  • 7. 什么是哈希表
  • 8. Java有哪些线程安全的类
  • 9. 为什么说StringBuffer是线程安全的,StringBuilder是线程不安全的
  • 10. JVM 是由哪几部分组成的?
  • 11. 什么叫回表?
  • 12. 什么是索引覆盖?
  • 13. 简要描述nacos的心跳机制
  • 14. nacos具备哪些功能?
  • 15. 一个主线程,两个子线程,如何让两个子线程运行完毕再继续执行主线程?有哪些方式?
  • 16. 为什么说一个文件里面最多只有一个被public修饰的类?
  • 17. 说一说你对Java访问权限的了解
  • 18. 介绍一下Java的数据类型
  • 19. 为什么被static修饰的成员变量在方法区,未被static修饰的在堆内存里面?
  • 20. 为什么说Java中没有真正的全局变量?
  • 21. 为什么要有包装类?
  • 22. 自动装箱、自动拆箱的应用场景
  • 23. 如何对Integer和Double类型判断相等?

1. 怎么理解Spring MVC?

是Java的Web应用程序开发框架,通过不同组件来实现松散耦合的架构。

理解MVC的相关概念:

  1. 模型(Model):表示应用程序的数据和业务逻辑。

  2. 视图(View):负责呈现数据给用户

  3. 控制器(Controller):控制器接收来自用户的请求,

Spring MVC的工作流程如下:

  1. 用户发送请求到前端控制器(Front Controller),通常是通过URL访问应用程序。

  2. 前端控制器(Dispatcher Servlet)拦截请求,并将其传递给相应的处理器(Handler)。

  3. 处理器根据请求的URL找到对应的处理器映射器(Handler Mapping),确定使用哪个控制器处理请求。

  4. 控制器(Controller)接收请求,并处理业务逻辑。通常它会调用相应的服务层或数据访问层。

  5. 控制器根据请求的处理结果选择合适的视图,并将模型数据传递给视图。

  6. 视图负责呈现模型数据,生成用户可以理解的内容。它将响应返回给前端控制器。

  7. 前端控制器最终将响应返回给用户。

通过使用Spring MVC,开发人员可以更好地组织和管理Web应用程序的不同层次,实现松散耦合、易于测试和维护的代码结构。它提供了丰富的功能和灵活的扩展性,使得开发Web应用程序变得更加简便和高效。

2. 为什么说ConcurrentHashMap是线程安全的?

因为它使用了一种称为分段锁(Segment)加锁机制来保证多线程环境下的线程安全。具体而言,ConcurrentHashMap 将哈希表分成多个小的哈希表每个小哈希表被称为“段”,每个段都有自己的锁。当一个线程访问 ConcurrentHashMap 的某个段时,只会锁住这个段,而不会锁住整个哈希表,从而实现对不同段的访问操作之间的并发执行。

3. 为什么说HashMap是线程不安全的?

因为它不提供同步机制来保护多线程对其进行并发访问时的数据一致性。如果多个线程同时修改 HashMap 中的内容,可能会导致数据丢失、数据错乱或死循环等问题。

4. 遍历map的方法

  1. 使用 EntrySet 遍历:
Map<KeyType, ValueType> map = new HashMap<>();
// 添加键值对到 map

for (Map.Entry<KeyType, ValueType> entry : map.entrySet()) {
    KeyType key = entry.getKey();
    ValueType value = entry.getValue();
    // 对键值对进行操作
}
  1. 使用 KeySet 遍历:
Map<KeyType, ValueType> map = new HashMap<>();
// 添加键值对到 map

for (KeyType key : map.keySet()) {
    ValueType value = map.get(key);
    // 对键值对进行操作
}
  1. 使用 Iterator 遍历:
Map<KeyType, ValueType> map = new HashMap<>();
// 添加键值对到 map

Iterator<Map.Entry<KeyType, ValueType>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
    Map.Entry<KeyType, ValueType> entry = iterator.next();
    KeyType key = entry.getKey();
    ValueType value = entry.getValue();
    // 对键值对进行操作
}

5. HashSet怎样处理重复的数据?

hashSet 是基于 HashMap 实现的,它使用哈希表的方式来存储数据,并且不允许重复元素存在。当我们向 HashSet 中添加元素时,HashSet 会通过元素的 hashCode 方法计算该元素的哈希值,并将元素存储到对应的哈希桶中。如果哈希桶中已经存在相同哈希值的元素,那么 HashSet 会使用 equals 方法判断两个元素是否相等。如果相等,HashSet 将不会添加重复的元素。

6. HashSet 具备哪些特点?

  1. 无序性。
  2. 不允许重复元素。
  3. 基于哈希表实现:HashSet 内部使用 HashMap 实现,每个元素都是作为 HashMap 的键存储在哈希桶中,而值则是一个常量。
  4. 高效的插入、删除和查找操作:基于哈希表实现的,所以具有高效性能。这些操作的时间复杂度为 O(1)。
  5. 允许使用 null 元素。
  6. 非线程安全:可以通过 Collections工具类提供的 synchronizedSet 方法来获取一个线程安全的 HashSet。

7. 什么是哈希表

什么是哈希表?

8. Java有哪些线程安全的类

  1. StringBuffer和StringBuilder:StringBuffer和StringBuilder类用于可变的字符串操作。StringBuffer是线程安全的,可以在多个线程中使用,而StringBuilder则不是线程安全的。

  2. Vector:Vector是一个动态数组,类似于ArrayList,但它是线程安全的。多个线程可以同时对Vector进行读取和写入操作。

  3. Hashtable:Hashtable是一个传统的哈希表实现,它是线程安全的。它提供了基本的键值对存储和检索,多个线程可以同时对Hashtable进行操作,但性能上相对较差。

  4. ConcurrentHashMap:ConcurrentHashMap是Java 5引入的线程安全的哈希表实现。它在多线程环境下提供了更好的性能,允许多个线程同时读取和更新其中的数据。

  5. ConcurrentLinkedQueue:ConcurrentLinkedQueue是一个线程安全的无界队列实现。它适用于多个线程同时进行元素插入和删除操作的场景。

  6. AtomicInteger和AtomicLong:AtomicInteger和AtomicLong是线程安全的整型和长整型原子变量类。它们提供了一系列的原子操作方法,可以在多线程环境下安全地进行数值更新操作。

9. 为什么说StringBuffer是线程安全的,StringBuilder是线程不安全的

因为:都是用于处理可变字符串的类,类中的方法用synchronized关键词修饰,确保操作的原子性和顺序性,如果多个线程同时对同一个StringBuilder实例进行操作,就会导致竞态条件和数据不一致的问题。

10. JVM 是由哪几部分组成的?

由:类加载器、执行引擎、运行时数据区域、方法区、本地方法接口、类文件格式

  1. 类加载器:负责加载 Java 字节码文件(.class 文件)。

  2. 执行引擎:负责执行 JVM 中的字节码指令。它有两种主要的执行方式:解释执行和即时编译执行。解释执行逐条解释执行字节码指令,效率较低;即时编译执行将整个字节码转换为本地机器代码,以提高执行效率。

  3. 运行时数据区域:包含了 JVM 在执行程序时所需的各种内存区域。

    • Method Area(方法区):用于存储已被加载的类的信息、常量、静态变量等数据。
    • Heap(堆):用于存储对象实例,包括所有的实例变量。
    • Java Stack(Java栈):每个线程在执行 Java 方法时都会创建一个栈帧,用于保存局部变量、方法参数、返回值等信息。
    • Native Method Stack(本地方法栈):与Java栈类似,但用于执行Native方法(非Java语言编写的方法)。
  4. 本地方法接口:提供了与 Native 代码(非Java语言编写的代码)进行交互的接口,在 Java 代码中可以调用 Native 方法。

  5. 类文件格式:约定了 Java 字节码文件的结构,包括常量池、方法区、字段信息等。

11. 什么叫回表?

通俗的讲:如果索引的列在 select 所需获得的列中或者根据一次索引查询就能获得记录就不需要回表,如果 select 所需获得列中有大量的非索引列,索引就需要到表中找到相应的列的信息,这就叫回表。
参考:在Mysql中,什么是回表,什么是覆盖索引,索引下推?

12. 什么是索引覆盖?

所需的列都包含在索引中,无需回表操作即可获取所有需要的数据,速度更快。
参考:在Mysql中,什么是回表,什么是覆盖索引,索引下推?

13. 简要描述nacos的心跳机制

Nacos的心跳机制是指在分布式环境中,Nacos通过定期发送心跳消息来检测实例的存活状态。

14. nacos具备哪些功能?

Nacos具备服务注册与发现配置管理服务健康监测动态路由与负载均衡服务配置共享集群部署与高可用性权限管理等功能。

15. 一个主线程,两个子线程,如何让两个子线程运行完毕再继续执行主线程?有哪些方式?

  1. 使用Thread的join()方法:在主线程中分别调用两个子线程的join()方法,这会使得主线程等待直到两个子线程都执行完毕。
public class MainThread {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                // 子线程1的任务逻辑
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                // 子线程2的任务逻辑
            }
        });

        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();

        // 这里是主线程的任务逻辑,只有在两个子线程都执行完毕后才会执行到这里
    }
}
  1. 使用CountDownLatch:在主线程中创建一个CountDownLatch,设置计数器为2,然后两个子线程执行完毕后分别调用CountDownLatch的countDown()方法减小计数器,主线程调用await()方法等待计数器归零。
import java.util.concurrent.CountDownLatch;

public class MainThread {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(2);

        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                // 子线程1的任务逻辑
                latch.countDown();
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                // 子线程2的任务逻辑
                latch.countDown();
            }
        });

        thread1.start();
        thread2.start();

        latch.await();

        // 这里是主线程的任务逻辑,只有在两个子线程都执行完毕后才会执行到这里
    }
}
  1. 使用ExecutorService,通过调用awaitTermination()方法等待所有线程执行完毕,可以使用ThreadPoolExecutor来创建线程池。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MainThread {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(2);

        executorService.submit(new Runnable() {
            @Override
            public void run() {
                // 子线程1的任务逻辑
            }
        });

        executorService.submit(new Runnable() {
            @Override
            public void run() {
                // 子线程2的任务逻辑
            }
        });

        executorService.shutdown();
        executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);

        // 这里是主线程的任务逻辑,只有在两个子线程都执行完毕后才会执行到这里
    }
}

16. 为什么说一个文件里面最多只有一个被public修饰的类?

如果一个源文件中包含多个被public修饰的类,那么就无法通过文件名与类名进行唯一的对应,导致编译器无法确定如何生成字节码文件

17. 说一说你对Java访问权限的了解

private:类内部成员
default:同一包,类内部成员
protected:同一包,子类,类内部成员
public:任意类

18. 介绍一下Java的数据类型

基本数据类型有8个,可以分为4个小类,分别是整数类型(byte/short/int/long)、浮点类型(float/double)、字符类型(char)、布尔类型(boolean)

1字节 (8位):byte(0), boolean(false)
2字节 (16位):short(0), char(‘\u0000’)
4字节 (32位):in(0)t, float(0.0L)
8字节 (64位):long(0L), double(0.0)

19. 为什么被static修饰的成员变量在方法区,未被static修饰的在堆内存里面?

因为方法区用于存储类的结构信息、常量池、静态变量等数据。由于静态变量属于类本身,不是对象的一部分,未被static修饰的成员变量属于对象级别的变量,在对象被创建时,它们会被存储在堆内存中的对象实例中

20. 为什么说Java中没有真正的全局变量?

因为变量作用域受限于声明位置,强调封装和对象独立性。

21. 为什么要有包装类?

包装类的存在是为了将基本数据类型转换为对象类型,以便进行更多的操作和满足特定的编程需求。

22. 自动装箱、自动拆箱的应用场景

自动装箱:可以把一个基本类型的数据直接赋值给对应的包装类型;
自动拆箱:可以把一个包装类型的对象直接赋值给对应的基本类型;
可以大大简化基本类型变量和包装类对象之间的转换过程

23. 如何对Integer和Double类型判断相等?

  • 不能用==进行直接比较,因为它们是不同的数据类型;
  • 不能转为字符串进行比较,因为转为字符串后,浮点值带小数点,整数值不带,这样它们永远都不相等;
  • 不能使用compareTo方法进行比较,虽然它们都有compareTo方法,但该方法只能对相同类型进行比较。
  • 整数、浮点类型的包装类,都继承于Number类型,而Number类型分别定义了将数字转换为byte、short、int、long、float、double的方法,转换后即可比较

你可能感兴趣的:(面试,java,面试,开发语言)