2023最新java面试题库
Java平台
Java基础
JVM主题
JDK、JRE、JVM 定义及它们之间的关系?
JVM(Java Virtual Machine,Java 虚拟机)是 Java 程序的运行环境,它在计算机中负责将 Java 代码转换成计算机可执行的指令,实现了 Java 代码跨平台的特性。JVM 主要由类加载器、Java 核心类库、字节码校验器、解释器、即时编译器等多个组件构成。
扩展阅读请参阅下文
JRE(Java Runtime Environment,Java 运行时环境)是 Java 程序的运行环境,包括了 JVM 和 Java 核心类库等组件。在安装 JRE 后,用户可以直接运行 Java 程序,但无法进行 Java 程序的开发和编译。
扩展阅读请参阅下文
JDK(Java Development Kit,Java 开发工具包)是 Java 程序的开发环境,包括了 JRE 和开发工具,如编译器、调试器、文档生成器、性能分析器等。JDK 可以用于开发、编译和调试 Java 程序,是 Java 程序员必备的开发工具。
因此,JDK 包含了 JRE 和开发工具,而 JRE 包含了 JVM 和 Java 核心类库。在进行 Java 程序的开发和编译时,需要安装 JDK。在运行 Java 程序时,只需要安装 JRE 即可,JVM 将在 JRE 中被自动安装。
什么是Java 类加载双亲委派模型
双亲委派模型(Parent-Delegation Model),是指在 Java 类加载器的加载过程中,如果一个类加载器收到了类加载的请求,它首先会把这个请求委派给它的父类加载器去完成。如果父类加载器还存在父类加载器,则会一直向上委托,直到最顶层的父类加载器。只有当父类加载器无法完成类加载请求时,子类加载器才会尝试自己去加载这个类。这样做目的是为了保证 Java 类库的安全性和稳定性的机制。Java 类库中的核心类库(比如 java.lang 和 java.util 等)都是由 Bootstrap ClassLoader 加载的。这些类库是 Java 运行环境的一部分,其它的类库都依赖于它们。因此,当一个类需要加载一个类库时,它首先委托给它的父类加载器去加载。如果父类加载器找不到这个类库,它会继续向上委托,直到 Bootstrap ClassLoader。如果 Bootstrap ClassLoader 也找不到这个类库,那么它会回到子类加载器,看看子类是否能够找到这个类库。如果子类加载器找到了这个类库,那么它就会把这个类库加载进来,否则就会抛出 ClassNotFoundException 异常。双亲委派模型可以避免类库的重复加载和冲突,同时也可以提供一种安全机制,防止不同的类库之间的干扰。例如,如果一个应用程序需要使用某个类库,但是这个类库已经被另一个应用程序加载了,那么这个应用程序可以使用双亲委派模型,从而避免重复加载这个类库。另外,双亲委派模型还可以防止恶意代码的注入,保障 Java 应用程序的安全性。
Java中堆栈和堆有什么区别?
JVM 存储所有变量和对象的地方被分成两个部分,第一部分称为堆栈,第二部分称为堆。
堆栈是 JVM 为局部变量和其他数据保留块的地方。堆栈是后进先出(后进先出)结构。这意味着每当调用方法时,都会为局部变量和对象引用保留一个新块。每个新方法调用都会保留下一个块。当方法完成执行时,块将以启动时相反的方式释放。
每个新线程都有自己的堆栈。
我们应该知道,堆栈的内存空间比堆少得多。当堆栈已满时,JVM将抛出StackOverflowError。当有一个错误的递归调用并且递归太深时,可能会发生这种情况。
每个新对象都是在用于动态分配的 Java heap 上创建的。有一个garbage收集器,负责擦除未使用的物体。对堆的内存访问比对堆栈的访问慢。当堆已满时,JVM 会抛出内存不足错误。
你可以在 Java 中的堆栈内存和堆空间、VM规范定义运行时数据区详解-Java快速进阶教程及JVM 垃圾收集器-Java快速进阶教程 三篇文中找到更多详细信息。
动态绑定和静态绑定有什么区别?
Java 中的绑定是将方法调用与正确的方法主体相关联的过程。我们可以区分Java中的两种绑定类型:静态和动态。
静态绑定和动态绑定之间的主要区别在于静态绑定发生在编译时,动态绑定发生在运行时。
静态绑定使用类信息进行绑定。它负责解析私有或静态的类成员以及最终的方法和变量。此外,静态绑定绑定重载方法。
另一方面,动态绑定使用对象信息来解析绑定。这就是为什么它负责解析虚拟方法和被覆盖的方法
什么是JIT处理?
JIT 全称Java Intime Compiler。它是在运行时运行的 JRE 组件,可提高应用程序的性能。具体来说,它是一个在程序启动后运行的编译器。
这与常规的Java编译器不同,后者在应用程序启动之前就编译代码。JIT 可以通过不同的方式加快应用程序的速度。
例如,JIT 编译器负责动态将字节码编译为本机指令以提高性能。此外,它可以针对目标 CPU 和操作系统优化代码。
此外,它还可以访问许多运行时统计信息,这些统计信息可用于重新编译以获得最佳性能。这样,它还可以进行一些全局代码优化或重新排列代码以提高缓存利用率。
什么是类加载器?
类加载器是 Java 中最重要的组件之一。它是 JRE 的一部分。
简单地说,类加载器负责将类加载到 JVM 中。我们可以区分三种类型的类加载器:
Bootstrap classloader – 它加载核心 Java 类。它们位于 <JAVA_HOME>/jre/lib 目录中
扩展类加载器 – 它加载位于 <JAVA_HOME>/jre/lib/ext 或 java.ext.dirs 属性定义的路径中的类
系统类加载器 – 它在应用程序的类路径上加载类
类装入器“按需”加载类。这意味着类在程序调用后加载。更重要的是,类加载器只能加载一次具有给定名称的类。但是,如果同一个类由两个不同的类装入器装入,则这些类在相等性检查中失败。
什么是Java内存泄漏?
内存泄漏是指堆中存在不再使用的对象,但垃圾回收器无法从内存中删除它们。因此,它们被不必要地维护。
内存泄漏是不好的,因为它会阻塞内存资源并随着时间的推移降低系统性能。如果不处理,应用程序最终将耗尽其资源,最终以致命的java.lang.OutOfMemoryError终止。
有两种不同类型的对象驻留在堆内存中,引用和未引用。引用的对象是指在应用程序中仍具有活动引用的对象,而未引用的对象没有任何活动引用的对象。
垃圾回收器会定期删除未引用的对象,但它从不收集仍在引用的对象;这是可能发生内存泄漏的地方。
内存泄漏的症状
1)当应用程序长时间连续运行时,性能严重下降
2)应用程序中的内存不足错误、堆错误
3)自发和奇怪的应用程序崩溃
4)应用程序偶尔会用完连接对象。
扩展阅读请参阅下文
Java 中的内存泄漏剖析
类、接口定义相关主题
什么是 Java 中的反射?
反射在Java中是一种非常强大的机制。反射是Java语言的一种机制,它使程序员能够在运行时检查或修改程序的内部状态(属性,方法,类等)。java.lang.reflect 包提供了使用反射所需的所有组件。
使用此功能时,我们可以访问类定义中包含的所有可能的字段、方法、构造函数。无论它们的访问修饰符如何,我们都可以访问它们。这意味着例如,我们能够访问私人成员。要做到这一点,我们不必知道他们的名字。我们所要做的就是使用一些 Class 的静态方法。
值得一提的是,有可能通过反射来限制访问。为此,我们可以使用 Java 安全管理器和 Java 安全策略文件。它们允许我们向类授予权限。
从 Java 9 开始使用模块时,我们应该知道,默认情况下,我们无法对从另一个模块导入的类使用反射。要允许其他类使用反射来访问包的私有成员,我们必须授予“反射”权限。
扩展阅读请参阅下文
为什么在Java中不支持运算符重载?
Java不支持运算符重载主要是为了保持代码的简洁性和可读性,以及避免出现一些不确定的行为。如果Java支持运算符重载,那么不同的程序员可能会给运算符赋予不同的含义,这样可能会导致程序的行为不确定性,同时也会增加代码的复杂性和难度。此外,Java的设计目标之一是保持代码的可读性和可维护性,因此不支持运算符重载也有助于提高代码的可读性和可维护性。
相比之下,C++等其他语言支持运算符重载是因为它们的设计目标不同。C++等语言更加注重程序员的灵活性和效率,因此支持运算符重载可以让程序员更加灵活地定义自己的类型和操作,同时也能提高代码的效率。但是,这种灵活性和效率是以代码的复杂性和难度为代价的。
总之,Java不支持运算符重载是为了保持代码的简洁性和可读性,以及提高代码的可维护性和可靠性。
为什么 wait,notify 和 notifyAll 是在 Object 类中定义的而不是在 Thread 类中定义?
wait,notify和notifyAll是在Object类中定义的,因为它们是用于控制线程同步和协作的基本机制。
线程是由操作系统调度的,而Java中的线程是由Java虚拟机管理的。因此,Java中的线程和操作系统中的线程之间存在一定的差异。在Java中,线程是对象,它们必须依赖于对象来进行同步和协作。
wait,notify和notifyAll是用于线程之间的通信的基本机制,它们必须与对象一起使用。因此,它们被定义在Object类中,而不是Thread类中。
此外,wait,notify和notifyAll是与锁密切相关的。在Java中,每个对象都有一个锁,线程可以通过获取对象的锁来进行同步和协作。因此,wait,notify和notifyAll必须与锁一起使用,而锁是与对象相关的,因此它们被定义在Object类中。
在Java中如何避免死锁?
在Java中,死锁是多线程编程中的一个常见问题,可以通过以下几种方法来避免死锁:
1. 避免嵌套锁:尽量避免在持有一个锁的情况下去请求另一个锁,这样可能会导致死锁的产生。如果确实需要使用多个锁,可以尝试使用统一的锁对象来避免嵌套锁的情况。
2. 避免循环依赖:如果有多个线程需要使用多个锁,尽量避免出现循环依赖的情况,这样也容易导致死锁的产生。可以尝试使用不同的锁顺序来避免循环依赖。
3. 使用定时锁:如果线程在持有锁的情况下被阻塞,可以使用定时锁来避免死锁的产生。Java中的ReentrantLock类提供了tryLock(long time, TimeUnit unit)方法,可以在指定时间内尝试获取锁,如果在指定时间内无法获取锁,则放弃获取锁。
4. 使用线程池:使用线程池可以避免由于线程过多而导致的死锁问题。
5. 使用多个锁对象:尽量避免多个线程同时竞争同一个锁对象,可以使用多个锁对象来避免这种情况。例如,可以为不同的资源分配不同的锁对象,这样可以避免线程之间的互相等待。
总之,在Java中,避免死锁需要注意避免嵌套锁、避免循环依赖、使用定时锁、使用线程池和使用多个锁对象等方法。
扩展阅读
java中的哲学家用餐问题 Java 线程死锁和活锁 Locks使用指南 死锁:它是什么,如何检测、处理和预防 死锁、活锁和饥饿 什么是互斥体 什么是信号量
静态类加载和动态类加载有什么区别?
静态类加载发生在编译时有可用的源类时。我们可以通过使用 new 关键字创建对象实例来利用它。动态类加载是指我们无法在编译时提供类定义的情况。但是,我们可以在运行时执行此操作。比如要创建一个类的实例,我们可使用 Class.forName() 方法来处理:
Class.forName("oracle.jdbc.driver.OracleDriver")
可序列化接口的用途是什么?
我们可以使用 Serializable 接口来启用类的可序列化性,使用 Java 的序列化 API。序列化是一种将对象状态保存为字节序列的机制,而反序列化是一种从字节序列还原对象状态的机制。序列化输出保存对象的状态以及有关对象类型及其字段类型的一些元数据。
我们应该知道可序列化类的子类型也是可序列化的。但是,如果我们想使一个类可序列化,但它的超类型是不可序列化的,我们必须做两件事:
1)实现可序列化接口
2)确保超类中存在无参数构造函数
扩展阅读
什么是 NullPointerException?
NullPointerException可能是Java世界中最常见的异常。这是一个未经检查的异常,因此扩展了运行时异常。当我们尝试访问变量或调用 null 引用的方法时,会抛出此异常,例如:
1)调用空引用的方法
2)设置或获取空引用的字段
3)检查空数组引用的长度
4)设置或获取空数组引用的项
4)抛出null值
为什么Java中类不支持多重继承,而接口却支持多重继承?
Java中类不支持多重继承主要是为了避免多种继承带来的复杂性和不确定性。当一个类继承自多个类时,可能会出现不同类中具有相同方法名和参数列表的方法,这就会造成冲突。此外,多重继承还会增加代码的复杂性和难度,因为一个类可能会继承多个类中的方法和属性,这会增加类的复杂性和难度。
相比之下,接口支持多重继承是因为接口的方法只有方法名和参数列表的定义,没有具体实现。因此,当一个类实现多个接口时,不会出现方法名和参数列表相同的方法,也不会因为继承多个接口而增加类的复杂性。此外,接口的多重继承提供了更大的灵活性,可以让类实现多个接口,从而获得更多的功能。
为什么String类型在 Java 中是不可变的?
在Java中,String是不可变的,这意味着一旦创建一个String对象,它的值就不能被改变。这是因为Java中的String类被设计为不可变的,这样可以带来以下优点:
1. 线程安全性:由于String是不可变的,所以多线程环境下使用String是安全的,不需要额外的同步措施。
2. 安全性:如果String是可变的,那么在传递参数时,可能会对原始数据进行修改,这可能会导致数据安全性问题。不可变的String可以保证数据的安全性。
3. 性能:由于String是不可变的,所以它的hashcode可以在第一次计算后缓存起来,这样可以提高String的性能。
4. 缓存:由于String是不可变的,所以可以被缓存起来,这样可以提高程序的性能。
5. 易于重用:由于String是不可变的,所以可以被重用。在Java中,String常量池就是一个很好的例子,相同的String常量只会被存储一次,这样可以节省内存空间。
总之,Java中的String是不可变的,这是为了保证线程安全性、安全性、性能、缓存和易于重用等方面的优点。
为什么在Java中char 数组类型比String 类型更适合存储密码?
在Java中,char数组类型比String类型更适合存储密码,这是因为char数组是可变的,而String类型是不可变的。以下是一些原因:
1. Security:由于char数组是可变的,可以通过覆盖数组中的数据来擦除密码信息。而String类型是不可变的,一旦创建,其值就不能被更改,这就意味着密码信息可能会被留在内存中,从而导致安全风险。
2. Mutable:由于char数组是可变的,可以使用Arrays.fill()方法或循环遍历方法在使用后立即擦除密码信息。而String类型是不可变的,无法直接更改其值,因此无法使用这种方法擦除密码信息。
3. 归零操作:在Java中,char数组可以通过填充null或0值来归零。这样可以确保密码信息不会在内存中残留。但String类型不能被归零,因为其是不可变的。
总之,char数组类型比String类型更适合存储密码,因为它们是可变的,并且可以通过覆盖或归零来保护密码信息的安全性。而String类型是不可变的,一旦创建,其值就不能被更改,可能会导致密码信息在内存中残留,存在安全风险。
Java 中有哪些访问修饰符可用,它们的用途是什么?
Java 中有四个访问修饰符:
- private-私有
- default-默认(包)
- default-保护
- public-公共
私有修饰符可确保在类外部无法访问类成员。它可以应用于方法、属性、构造函数、嵌套类,但不能应用于顶级类本身。
与私有修饰符不同,可以将默认修饰符应用于所有类型的类成员和类本身。可以通过根本不添加任何访问修饰符来应用默认可见性。如果使用默认可见性,我们的类或其成员将只能在类的包中访问。 我们应该记住,默认访问修饰符与默认关键字没有任何共同之处。
与默认修饰符类似,一个包中的所有类都可以访问受保护的成员。更重要的是,受保护的修饰符允许子类访问超类的受保护成员,即使它们不在同一包中。我们不能将此访问修饰符应用于类,只能应用于类成员。公共修饰符可以与 class 关键字和所有类成员一起使用。它使类和类成员可以在所有包和所有类中访问。我在另一篇文章对该主题作了深入分析:Java 中的访问修饰符详解-Java快速入门教程
Java 中除了常规访问修饰符外还有哪些其他修饰符可用,它们的目的是什么?
Java 中还有其他五个可用的修饰符:
- static-静态的
- final-最终
- abstract-抽象
- synchronized-同步
- volatile不稳定的
这些不控制可见性。
首先,我们可以将 static 关键字应用于字段和方法。静态字段或方法是类成员,而非静态字段或方法是对象成员。类成员不需要调用实例。使用类名而不是对象引用名调用它们。本文将更详细地介绍静态关键字。
然后,我们有final关键字。我们可以将其与字段、方法和类一起使用。 当在字段上使用 final 时,这意味着无法更改字段引用。因此,无法将其重新分配给另一个对象。当 final 应用于类或方法时,它向我们保证该类或方法不会被扩展或覆盖。 本文将更详细地解释final关键字。
下一个关键字是抽象的。这个可以描述类和方法。当类是抽象的时,它们不能被实例化。 相反,它们应该是子类的。当方法抽象时,它们没有实现,并且可以在子类中重写。
同步关键字可能是最高级的。我们可以将其与实例以及静态方法和代码块一起使用。当我们使用这个关键字时,我们让 Java 使用监视器锁来提供给定代码片段的同步。有关同步的详细信息,请参阅本文。
在 Java 中,数据是通过引用还是按值传递的?
虽然这个问题的答案很简单,但这个问题可能会让初学者感到困惑。首先,让我澄清一下问题是什么:
- 按值传递 – 意味着我们将对象的副本作为参数传递到方法中。
- 通过引用传递 – 意味着我们将对对象的引用作为参数传递到方法中。
要回答这个问题,我们必须分析两个案例。它们表示我们可以传递给方法的两种类型的数据:基元和对象。
当我们将基元(原始数据类型)传递给方法时,其值被复制到一个新变量中。当涉及到对象时,引用的值被复制到一个新变量中。因此,我们可以说Java是一种严格的按值传递语言。我在另一篇文章对该主题作了深入分析:Java 中的原始数据类型值传递及引用类型对象的引用传递分析
在Java中导入和静态导入有什么区别?
可以使用常规导入来导入特定类或不同包中定义的所有类:
import java.util.ArrayList; //specific class import java.util.*; //all classes in util package
还可以使用它们来导入封闭类的公共嵌套类:
import com.jack.yang.A.*
但是应该知道是上面的导入本身并不导入 A 类。
还有一些静态导入使我们能够导入静态成员或嵌套类:
import static java.util.Collections.EMPTY_LIST;
效果是,我们可以EMPTY_LIST使用静态变量,而无需在完全限定的类名前面加上前缀,即就好像它是在当前类中声明的一样。
抽象类和接口有什么区别?
抽象类和接口是 Java 中两种实现抽象化的机制,它们的主要区别如下:
抽象类可以包含具体方法的实现,而接口只能包含抽象方法的定义;
抽象类可以包含成员变量和构造方法,而接口不能包含成员变量和构造方法;
一个类只能继承一个抽象类,但可以实现多个接口;
抽象类的子类必须实现抽象类中所有的抽象方法,而接口实现类必须实现接口中所有的方法;
接口可以被任意多个类实现,并且可以在不同的类中实现相同的接口,而抽象类只能被单一的子类继承。
总的来说,如果需要定义一些通用的行为和属性,可以使用抽象类;如果需要定义一些行为规范,可以使用接口。
Java类的重载和重写的区别?
重载(Overloading)和重写(Overriding)是两个常用的面向对象编程中的概念,它们的定义及区别如下:
定义:
重载(Overloading):在同一个类中,可以定义多个同名但参数类型或个数不同的方法。
重写(Overriding):在子类中重新定义一个与父类中同名、同参的方法,实现对父类方法的覆盖。
参数:
重载(Overloading):参数类型或个数不同,方法名相同。
重写(Overriding):参数类型和个数必须与被重写的方法完全相同。
返回值:
重载(Overloading):返回值类型可以相同也可以不同,但不会以此作为区分重载的标准。
重写(Overriding):返回值类型必须与被重写的方法相同或为其子类。
作用:
重载(Overloading):为了提高代码复用性和灵活性,可以根据不同的参数类型和个数来调用不同的方法。
重写(Overriding):为了实现多态性,在子类中重新定义一个与父类中同名、同参的方法,可以实现对父类方法的覆盖,从而根据对象的实际类型来执行相应的方法。
实现:
重载(Overloading):在同一个类中,可以定义多个同名但参数类型或个数不同的方法。
重写(Overriding):在子类中重新定义一个与父类中同名、同参的方法,并使用 @Override 注解来标识。
综上所述,重载和重写是两个不同的概念。重载是在同一个类中定义多个同名方法,根据参数类型和个数的不同来区分不同的方法,实现代码复用性和灵活性;重写是在子类中重新定义一个与父类中同名、同参的方法,实现多态性,根据对象的实际类型来执行相应的方法。
谈谈你在Java 中创建对象的常用方法有那些?
以下简单介绍一下Java 中创建对象的五种常见方式如下:
使用 new 关键字创建对象
MyObject obj = new MyObject();
使用 Class 类的 newInstance() 方法创建对象
MyObject obj = (MyObject) Class.forName("com.example.MyObject").newInstance();
或
MyObject obj = MyObject.class. newinstance ();
使用 Constructor 类的 newInstance() 方法创建对象
Constructor<MyObject> constructor = MyObject.class.getConstructor();
MyObject obj = constructor.newInstance();
使用 clone() 方法创建对象
MyObject obj1 = new MyObject();
MyObject obj2 = obj1.clone();
使用反序列化创建对象
ObjectInputStream in = new ObjectInputStream(new FileInputStream("object.ser"));
MyObject obj = (MyObject) in.readObject(); in.close();
Java 中是否可以重写一个 private 或者 static 方法?
Java 中不可以重写一个 private 或者 static 方法。原因如下:
private 方法只能在本类中被访问,子类无法访问到该方法,因此子类也无法重写该方法。
static 方法属于类,不属于对象,子类无法继承该方法,因此也无法重写该方法。 因此,如果一个方法被声明为 private 或 static,就不可以被重写。如果在子类中定义了一个与父类中 private 或 static 方法同名、同参的方法,不会被认为是重写,而是在子类中定义了一个新的方法。
简单谈一下Java中的Vector 和 ArrayList 的区别?
Vector 是线程安全的,而 ArrayList 是非线程安全的。
Vector 是使用 synchronized 关键字来实现同步的,而 ArrayList 则没有。
在执行 add、remove 操作时,Vector 和 ArrayList 的表现不同。Vector 在执行 add 操作时,如果容量不足,会自动扩容一倍;而 ArrayList 则是增加一定的容量。在执行 remove 操作时,Vector 会自动减少容量,而 ArrayList 不会。
Vector 的迭代器是同步的,而 ArrayList 的迭代器是非同步的。
简单谈一下Java中的StringBuilder 和 StringBuffer 的区别?
线程安全性:StringBuffer是线程安全的,而StringBuilder则不是线程安全的。因为StringBuffer的每个方法都被synchronized修饰,而StringBuilder则没有。
性能:由于StringBuffer的线程安全性,它的性能通常比StringBuilder差一些。因为在多线程环境下,每个线程都需要获取锁才能使用StringBuffer,而StringBuilder则不需要。
API兼容性:StringBuilder是在Java 5中引入的,而StringBuffer则是在Java 1.0中就已经存在了。因此,StringBuilder的API相对较新,它提供了一些在StringBuffer中没有的方法。
综上所述,如果在单线程环境下需要进行字符串处理,建议使用StringBuilder;如果需要在多线程环境下进行字符串处理,建议使用StringBuffer。
简单谈一下Java中的HashMap 和 Hashtable 的区别?
Hashtable 是线程安全的,而 HashMap 是非线程安全的。
Hashtable 是使用 synchronized 关键字来实现同步的,而 HashMap 则没有。
Hashtable 不允许键或值为 null,而 HashMap 则可以。
在执行 put 操作时,如果 Hashtable 的容量不足,会自动扩容一倍;而 HashMap 则是增加一定的容量。
Hashtable 的迭代器是同步的,而 HashMap 的迭代器是非同步的。
总的来说,Hashtable 的性能都比HashMap 差,但是它具有线程安全的特点,适合在多线程环境中使用。而 HashMap 的性能较好,适合在单线程环境中使用。在使用时,应根据实际情况选择合适的类。
BIO、NIO、AIO 有什么区别?
BIO、NIO、AIO 是 Java 中用于网络编程的三种不同的 I/O 模型,它们之间的区别如下:
BIO(Blocking I/O,阻塞 I/O):在传统的 I/O 模型中,当应用程序向操作系统请求 I/O 操作时,操作系统会将应用程序的线程阻塞,直到 I/O 操作完成,应用程序的线程才能继续执行。这种模型通常称为阻塞 I/O(BIO),它的主要特点是编程模型简单,但并发处理能力较弱。
NIO(Non-Blocking I/O,非阻塞 I/O):Java NIO 是在 Java 1.4 中引入的一种新的 I/O 模型,它可以实现非阻塞 I/O 操作。在 NIO 中,应用程序不需要等待 I/O 操作完成,而是将 I/O 请求注册到多路复用器上,当操作系统完成 I/O 操作后,多路复用器会通知应用程序。这种模型通常称为非阻塞 I/O(NIO),它的主要特点是并发处理能力较强,适用于高并发、吞吐量大的场景。
AIO(Asynchronous I/O,异步 I/O):Java NIO 2.0 中引入了一种更加高效的异步 I/O 模型,也称为 AIO。在 AIO 中,应用程序不需要等待 I/O 操作完成,而是通过回调函数的方式来处理 I/O 操作的结果。这种模型通常称为异步 I/O(AIO),它的主要特点是操作系统可以在完成 I/O 操作后主动通知应用程序,避免了轮询的开销,适用于高并发、吞吐量大、响应时间要求低的场景。
综上所述,BIO、NIO、AIO 是三种不同的 I/O 模型,BIO 的主要特点是编程模型简单,但并发处理能力较弱;NIO 的主要特点是并发处理能力较强,适用于高并发、吞吐量大的场景;AIO 的主要特点是操作系统可以在完成 I/O 操作后主动通知应用程序,避免了轮询的开销,适用于高并发、吞吐量大、响应时间要求低的场景。
Java中字节流和字符流有什么区别?
字节流和字符流是Java中I/O操作的两种基本流,它们的主要区别如下:
处理的数据类型不同 字节流以字节为单位读写数据,适用于处理二进制数据,如图片、音频、视频等。而字符流以字符为单位读写数据,适用于处理文本数据,如文本文件、XML文件、HTML文件等。
处理的方式不同 字节流以字节为单位直接读写数据,不会对数据进行处理,而字符流以字符为单位读写数据,会对数据进行编码和解码,可以自动将数据从本地编码转换为Unicode编码,或者将Unicode编码转换为本地编码。
使用的类不同 Java中的字节流主要由InputStream和OutputStream两个抽象类及其子类实现,而字符流主要由Reader和Writer两个抽象类及其子类实现。
缓冲的方式不同 字节流可以使用BufferedInputStream和BufferedOutputStream这两个缓冲流对数据进行缓冲处理,减少对磁盘的访问次数,提高读写效率。字符流也可以使用BufferedReader和BufferedWriter这两个缓冲流对数据进行缓冲处理,但是字符流的缓冲是按行缓冲的,即只有读到行末时才进行数据的处理。
总的来说,如果需要处理二进制数据,应该使用字节流;如果需要处理文本数据,应该使用字符流。需要注意的是,字符流可以处理字节流可以处理的所有数据,但是字节流不能处理字符流可以处理的所有数据。
持久化框架
MyBatis主题(20问)
1.什么是 MyBatis,它的几个核心模块是什么?
MyBatis 是一款基于 Java 的持久层框架,它封装了 JDBC 操作数据库的细节,让我们可以通过简单的 XML 或注解配置来映射数据库中的数据到 Java 对象中,从而实现数据的持久化。 MyBatis 的几个核心模块包括:
SqlSessionFactoryBuilder:用于创建 SqlSessionFactory 实例。
SqlSessionFactory:用于创建 SqlSession 实例。
SqlSession:对数据库操作的封装,提供了对数据库的增删改查等操作方法。
Mapper:MyBatis 的 Mapper 接口,用于定义 SQL 操作的方法。
2.MyBatis 的优点和缺点是什么?
优点:
简化了 JDBC 的代码量,使得代码更加简洁易读。
提供了动态 SQL 功能,使得 SQL 语句的构建更加灵活。
支持注解和 XML 两种方式配置 SQL 语句,提供了更加灵活的配置方式。
提供了一级缓存和二级缓存机制,可以有效地提高查询效率。
可以通过插件机制扩展 MyBatis 的功能。
缺点:
对于复杂 SQL 语句的支持不够强大,需要手动编写 SQL 语句。
基于 XML 的配置方式相对繁琐,容易出错。
需要手动管理 SQL 语句的参数,容易出现参数不匹配的问题。
映射关系需要手动维护,容易出现错误。
3.MyBatis 中的 resultMap 和 resultType 有什么区别?
resultMap 是将查询结果映射为 Java 对象的配置方式,它可以灵活地对查询结果进行转换和处理。
resultMap 需要手动编写 SQL 语句,并将查询结果映射为 Java 对象。
resultType 则是将查询结果直接映射为 Java 类型的配置方式,它只能将查询结果映射为简单的 Java 类型,例如 int、String 等。
resultType 不需要手动编写 SQL 语句,但需要确保查询结果和 Java 类型匹配。
4.MyBatis 中的 #{} 和 ${} 的区别是什么?
#{} 和 ${} 都是 MyBatis 中用于表示 SQL 参数的方式,但它们有着不同的作用:
#{} 表示一个占位符,用于表示一个参数,MyBatis 会将参数值以占位符的形式插入 SQL 语句中。#{} 可以防止 SQL 注入攻击。
${} 表示一个字符串替换,用于表示一个表名、列名或其他 SQL 片段,MyBatis 会将 ${} 中的内容直接替换为对应的值。${} 不能防止 SQL 注入攻击。
5.MyBatis 是如何进行分页的?
6.MyBatis 中的一级缓存和二级缓存有什么区别?如何配置二级缓存?
一级缓存是指在同一个 SqlSession 中,对同一个查询进行的缓存,当查询多次相同的 SQL 语句时,会从缓存中获取结果,而不是再次查询数据库。
一级缓存是默认开启的,不需要额外配置。 二级缓存是指在多个 SqlSession 中对同一个查询进行的缓存。二级缓存需要手动配置,可以在 XML 文
件中配置,也可以通过注解方式进行配置。 在 XML 文件中配置二级缓存:
通过注解方式配置二级缓存:
7.MyBatis 中的动态 SQL 是什么?有哪些动态 SQL 标签?
动态 SQL 是指在 MyBatis 中根据条件动态生成 SQL 语句的功能,它可以根据不同的条件生成不同的 SQL 语句,使得 SQL 语句更加灵活。
MyBatis 中提供了如下动态 SQL 标签:
if:用于在 SQL 语句中添加条件判断语句。
choose、when、otherwise:用于在 SQL 语句中添加多个条件判断语句。
trim、where、set、foreach:用于在 SQL 语句中添加动态的 SQL 片段。
bind:用于绑定一个变量,可以在 SQL 语句中引用这个变量。
8.MyBatis 中如何进行事务管理?
MyBatis 中的事务管理和 JDBC 中的事务管理类似,它可以通过编程式和声明式两种方式实现。 编程式事务管理需要手动在代码中开启、提交、回滚事务:
声明式事务管理可以通过 Spring 框架的事务管理机制实现,它可以通过注解或 XML 配置来实现。
9.MyBatis 中的插件是什么?如何实现自定义插件?
插件是 MyBatis 提供的一种扩展机制,可以对 MyBatis 的核心功能进行增强或修改。插件可以在 SQL 执行前、执行后或结果返回前进行拦截,
可以用于 增强 SQL 的性能、修改 SQL 的执行逻辑等。 自定义插件需要实现 Interceptor 接口,并在插件类上使用 @Intercepts 注解来指定拦
截的方法和时机。例如,下面的插件实现了 SQL 执行前打印 SQL 语句的功能:
10.MyBatis 中的懒加载是什么?如何配置懒加载?
懒加载是一种优化查询性能的方式,它可以延迟加载对象的属性,只有在访问属性时才会进行查询。MyBatis 中的懒加载可以通过配置来实现,
它可以在 XML 文件中配置,也可以通过注解方式进行配置。 在 XML 文件中配置懒加载:
通过注解方式配置懒加载:
11.MyBatis 中的批处理如何实现?有哪些注意事项?
MyBatis 中的批处理可以通过 SqlSession 的 batch 方法实现。batch 方法可以接收一个 StatementType 参数和一个 ExecutorType 参数,用于指定执行的 SQL 语句类型和执行器类型。 注意事项:
在批处理中,所有的 SQL 语句必须是相同的类型,并且返回的结果集必须是同一类型的。
执行批处理时,需要将所有的 SQL 语句一次性提交到数据库中,因此在执行大量 SQL 语句时,可能会占用大量的内存和网络资源。
12.什么是MyBatis的SqlSource?
SqlSource 是 MyBatis 中负责解析 SQL 语句的接口,它的主要作用是将 Mapper 中定义的 SQL 语句转化为可执行的 SQL 语句。SqlSource 接口中只有一个方法 getBoundSql,它的返回值是 BoundSql 对象。
13.什么是MyBatis的SqlNode?
SqlNode 是 MyBatis 中负责解析 SQL 语句的节点类,它是 SqlSource 接口的实现类之一。在解析 Mapper 中定义的 SQL 语句时,MyBatis 会将 SQL 语句拆分成多个 SqlNode 对象,每个 SqlNode 对象负责解析 SQL 语句的一部分,并将其转化为可执行的 SQL 片段。
14.什么是MyBatis的ParameterMapping
ParameterMapping 是 MyBatis 中负责绑定参数的类,它用于将 Java 对象中的属性值绑定到 SQL 语句中的参数上。ParameterMapping 中包含了参数名称、Java 类型、JDBC 类型等信息。
15.什么是MyBatis的BoundSql
BoundSql 是 MyBatis 中负责绑定 SQL 语句和参数的类,它包含了 SQL 语句、参数列表以及参数类型等信息。BoundSql 类中有两个重要的方法:getSql 和 getParameterMappings。getSql 方法用于获取可执行的 SQL 语句,getParameMappings 方法用于获取参数的信息。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix