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

9 篇文章 0 订阅
8 篇文章 0 订阅

面试文章

1. 2024最强秋招八股文

1、2024最强秋招八股文

2. MyBatis常见面试题汇总(超详细回答)

2、MyBatis常见面试题汇总(超详细回答)

3. 25个最常见的MyBatis面试题

3、25个最常见的MyBatis面试题

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的方法,转换后即可比较

24.HashMap底层实现

HashMap 是 Java 中常用的集合类之一,它基于哈希表实现,用于存储键值对。在 Java 8 中,HashMap 的底层实现主要由数组和链表(或红黑树)组成。

在 Java 8 中,HashMap 使用“拉链法”解决哈希冲突,具体的底层实现如下:

  1. 数组:HashMap 内部维护了一个 Node 类型的数组 table,用于存储实际的键值对。数组的长度通常会随着元素的增加而进行扩容操作。

  2. 哈希函数:HashMap 使用键的 hashCode 值来确定键值对的存储位置。通过对键的 hashCode 值进行再次哈希运算(hash & (length-1)),确定键值对在数组中的存储位置。

  3. 链表或红黑树:当多个键值对经过哈希函数计算后存储到数组的同一个位置时,它们会被存储为一个链表或红黑树结构。在 Java 8 中,当链表长度超过阈值(默认为 8)时,链表会被转换为红黑树,以保证插入、删除和查找等操作的更高效率。

  4. 负载因子:HashMap 会根据负载因子来判断是否需要进行扩容操作。当元素个数达到数组长度与负载因子的乘积时,HashMap 将进行扩容操作,以减少哈希冲突的概率。

  5. 扩容:在扩容时,HashMap 会创建一个新的数组,并将原数组中的键值对重新计算哈希位置后存储到新数组中,从而实现扩容。

25. volatile是什么?有什么用?

“volatile” 是一个关键字,用于修饰变量。在多线程编程中,使用volatile关键字可以确保被声明的变量具有可见性、禁止重排序和防止指令重排、不保证原子性。

public class VolatileTest {
    boolean flag = true;

    public void updateFlag() {
        this.flag = false;
        System.out.println("修改flag值为:" + this.flag);
    }

    public static void main(String[] args) {
        VolatileTest test = new VolatileTest();
        new Thread(() -> {
            while (test.flag) {
            }
            System.out.println(Thread.currentThread().getName() + "结束");
        }, "Thread1").start();

        new Thread(() -> {
            try {
                Thread.sleep(2000);
                test.updateFlag();
            } catch (InterruptedException e) {
            }
        }, "Thread2").start();

    }
}

26. cookie和session的区别

Cookie和Session是用于在Web应用中跟踪用户状态的机制,它们之间有一些关键的区别:

  1. 存储位置不同:cookie存储在客户端、session存储在服务端
  2. 安全性不同:cookie可以被更改、session不能被更改
  3. 大小不同:单个域名下的cookie数量有限、大小有限,session没有
  4. 生命周期不同:cookie和session都会随游览器的关闭而失效,也可以对他们进行时间的控制

27. 重写equals为什么要重写hashcode

在Java中,当我们重写一个类的equals方法时,通常也需要同时重写hashCode方法。这是因为在Java中,hashCode方法和equals方法是相关联的:

  • hashCode方法用于计算对象的哈希值,哈希值是一个整数,用于快速判断对象在哈希表中的位置。
  • equals方法用于比较两个对象是否相等。在哈希表中存储对象时,首先使用对象的哈希值来确定存储位置,然后再使用equals方法来判断是否有冲突的对象。如果两个对象的equals方法返回true,它们的哈希值必须相同;反之,如果哈希值相同,对象的equals方法不一定相等。

因此,为了保持一致性,当我们重写equals方法时,也需要同时重写hashCode方法,确保相等的对象具有相同的哈希值,避免在哈希表中出现错误的行为。

注意:在重写hashCode方法时,要保证相等的对象具有相同的哈希值,但相同的哈希值不一定代表相等的对象。

28. 什么是多态?有什么用?

多态是面向对象的一个重要概念,指的是同一个方法在不同的对象里面有不同的表现形式。具体而言,就是父类引用指向子类对象,在运行的时候根据实际对象的类型调用对应的方法。增加了代码的灵活性和扩展性

29. 什么是反射?反射的常用方法有哪些?

什么是反射:反射是指在程序运行时动态地获取类的信息、调用类的方法和修改类的属性等操作。
在Java中,反射提供了一系列方法来实现这些功能。以下是一些常用的反射方法:

  1. getFields(): 获取类的public字段,包括父类的字段。

  2. getDeclaredFields(): 获取类声明的所有字段,但不包括父类的字段。

  3. getMethods(): 获取类的public方法,包括父类的方法。

  4. getDeclaredMethods(): 获取类声明的所有方法,但不包括父类的方法。

  5. getMethod(String name, Class… parameterTypes): 获取指定名称和参数类型的方法。

  6. getConstructor(Class… parameterTypes): 获取指定参数类型的构造函数。

  7. newInstance(): 通过类的无参构造函数创建对象。

  8. getMethod(String name, Class… parameterTypes).invoke(Object obj, Object… args): 调用指定对象的方法。

  9. getField(String name): 获取指定字段。

  10. setAccessible(true): 设置访问私有成员的权限。

  11. set(Object obj, Object value): 设置字段的值。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

技术路上的探险家

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值