Java面试黄金宝典2
1. 什么是 Concurrent 包
java.util.concurrent
(简称 Concurrent 包)是 Java 5 引入的一个用于并发编程的工具包。它提供了一系列用于处理多线程编程的类和接口,帮助开发者更方便、安全地进行并发编程。
-
原理
该包基于 Java 的多线程机制和锁机制,采用了诸如 CAS(Compare - And - Swap)、AQS(AbstractQueuedSynchronizer)等底层技术。
- CAS:是一种无锁算法,其核心思想是先比较内存中的值是否和预期值一致,如果一致则将新值更新到内存中,这个操作是原子性的。例如,在
AtomicInteger
类中,就使用了 CAS 来实现原子的自增操作。在多线程环境下,多个线程同时尝试修改一个AtomicInteger
的值时,只有一个线程能够成功,其他线程会不断重试,避免了传统锁带来的线程阻塞和上下文切换开销。 - AQS:是一个用于构建锁和同步器的框架,许多并发工具类都基于它实现。AQS 内部维护了一个 FIFO 队列,用于管理等待获取锁的线程。当一个线程尝试获取锁失败时,会被放入队列中阻塞等待;当持有锁的线程释放锁时,会从队列中唤醒一个等待的线程。
- 要点
- 线程池:
ThreadPoolExecutor
:是线程池的核心实现类,开发者可以通过它灵活地配置线程池的参数,如核心线程数、最大线程数、线程空闲时间等。例如,在一个 Web 服务器中,可以根据请求的并发量来调整线程池的大小,提高系统的处理能力。ScheduledThreadPoolExecutor
:用于执行定时任务和周期性任务。它可以在指定的时间点执行任务,或者按照一定的周期重复执行任务,适用于定时数据备份、定时任务调度等场景。
- 并发集合:
ConcurrentHashMap
:在多线程环境下是线程安全的哈希表。它采用分段锁(Java 7 及以前)或 CAS + synchronized(Java 8 及以后)的方式来保证并发操作的安全性,避免了传统HashMap
在并发访问时可能出现的数据不一致问题,如死循环和数据丢失。CopyOnWriteArrayList
:是一个线程安全的动态数组。它的写操作(如add
、remove
等)会创建一个新的数组副本,将原数组中的元素复制到新数组中,并在新数组上进行修改,最后将引用指向新数组。读操作则直接在原数组上进行,不需要加锁,因此在读多写少的场景下性能较好。
- 同步工具类:
CountDownLatch
:用于协调多个线程的执行顺序。它通过一个计数器来实现,主线程可以等待计数器的值减为 0 后再继续执行。例如,在一个多线程的任务中,主线程需要等待所有子线程完成任务后再进行汇总操作,就可以使用CountDownLatch
。CyclicBarrier
:可以让一组线程在到达某个屏障点时进行同步。当所有线程都到达屏障点后,屏障会打开,所有线程可以继续执行。它可以重复使用,适用于多个线程需要反复进行同步的场景。Semaphore
:用于控制对有限资源的访问权限。它通过一个许可证计数器来实现,线程在访问资源前需要获取许可证,访问完后释放许可证。例如,在一个数据库连接池中,可以使用Semaphore
来控制同时使用的连接数。
- 应用
在高并发的 Web 应用中,使用 ConcurrentHashMap
可以替代传统的 HashMap
,避免在多线程环境下出现死循环和数据丢失的问题。线程池可以根据业务需求调整线程数量,提高系统的吞吐量。例如,在电商系统的促销活动期间,并发访问量会大幅增加,可以适当增加线程池的最大线程数,以应对高并发请求。
2. 什么是面向对象,有哪 4 种特性
面向对象编程(OOP)是一种编程范式,它将数据和操作数据的方法封装在一起,形成对象。通过对象之间的交互来完成程序的功能。
- 原理
面向对象的核心思想是将现实世界中的事物抽象成对象,每个对象都有自己的属性和行为。对象之间通过消息传递来进行交互,从而实现程序的功能。例如,在一个汽车模拟程序中,汽车可以抽象成一个对象,它有颜色、型号等属性,还有启动、加速、刹车等行为。不同的汽车对象之间可以通过消息传递来模拟交通场景。
- 要点
- 封装:
- 定义:将数据和操作数据的方法捆绑在一起,隐藏对象的内部实现细节,只对外提供必要的接口。通过访问修饰符(如
private
、protected
、public
)来控制对类成员的访问,提高了代码的安全性和可维护性。 - 示例:在一个银行账户类中,账户余额是一个敏感数据,应该被封装起来。可以将余额属性设置为
private
,并提供getBalance
和deposit
、withdraw
等方法来访问和修改余额,这样可以避免外部代码直接修改余额,保证数据的安全性。
- 定义:将数据和操作数据的方法捆绑在一起,隐藏对象的内部实现细节,只对外提供必要的接口。通过访问修饰符(如
- 继承:
- 定义:子类可以继承父类的属性和方法,从而实现代码的复用。Java 只支持单继承,即一个子类只能有一个直接父类,但可以实现多个接口。子类可以对父类的方法进行重写,以实现自己的功能。
- 示例:在一个动物类层次结构中,狗类可以继承动物类的属性和方法,同时可以重写动物类的
makeSound
方法,实现狗叫的功能。
- 多态:
- 定义:同一个行为具有多个不同表现形式或形态的能力。通过继承和接口实现,父类引用可以指向子类对象,在运行时根据实际对象类型调用相应的方法,提高了代码的灵活性和可扩展性。
- 示例:在一个图形绘制程序中,有一个抽象的图形类,以及圆形、矩形等具体的图形子类。可以定义一个绘制图形的方法,接收一个图形类的引用作为参数,在方法内部根据实际传入的对象类型调用相应的绘制方法,实现多态。
- 抽象:
- 定义:将一类对象的共同特征总结出来,形成抽象类或接口。抽象类不能实例化,它可以包含抽象方法,子类必须实现这些抽象方法。接口是一种特殊的抽象类,只包含抽象方法和常量。
- 示例:在一个交通工具系统中,可以定义一个抽象的交通工具类,包含抽象的移动方法。不同的交通工具子类(如汽车、飞机等)需要实现这个移动方法,以实现自己的移动方式。
- 应用
在大型项目开发中,面向对象的编程思想可以使代码结构更加清晰,易于维护和扩展。例如,在游戏开发中,可以将不同的角色抽象成类,通过继承和多态实现不同角色的不同行为。在软件开发中,使用设计模式(如单例模式、工厂模式等)也是基于面向对象的思想,进一步提高代码的可维护性和可扩展性。
3. 什么是 String、StringBuffer、StringBuilder、hashCode、equals,有什么区别
- String:是不可变的字符序列。一旦创建,其内容不能被修改。每次对
String
对象进行修改时,实际上是创建了一个新的String
对象。 - StringBuffer:是可变的字符序列,线程安全。它的方法大多是同步的,因此在多线程环境下可以安全使用,但性能相对较低。
- StringBuilder:也是可变的字符序列,非线程安全。它的方法没有进行同步处理,因此在单线程环境下性能比
StringBuffer
高。 - hashCode:是
Object
类的一个方法,用于返回对象的哈希码。哈希码是一个整数,通常用于哈希表(如HashMap
、HashSet
)中,提高查找效率。 - equals:是
Object
类的一个方法,用于比较两个对象是否相等。默认情况下,比较的是对象的引用是否相等,子类可以重写该方法来实现自定义的相等比较逻辑。
- 原理
- String:类内部使用
final char[]
数组来存储字符序列,由于final
修饰,数组的引用不能被修改,因此String
是不可变的。当对String
进行拼接操作时,会创建一个新的String
对象,原对象的内容不会改变。 - StringBuffer 和 StringBuilder:内部使用可变的字符数组来存储字符序列,通过
append
、insert
等方法可以修改字符序列。StringBuffer
的方法使用了synchronized
关键字进行同步,保证了线程安全;StringBuilder
没有进行同步处理,因此性能更高。 - hashCode:方法根据对象的内容计算哈希码,不同的对象可能有相同的哈希码,但相同的对象哈希码一定相同。在
String
类中,哈希码的计算是基于字符串的内容,因此相同内容的String
对象哈希码相同。 - equals:方法在
String
类中被重写,用于比较两个字符串的内容是否相等。它会遍历两个字符串的每个字符,只有当所有字符都相等时,才认为两个字符串相等。
- 要点
- 性能方面:在单线程环境下,频繁修改字符序列时,
StringBuilder
性能最好;在多线程环境下,使用StringBuffer
更安全。例如,在一个单线程的字符串拼接任务中,使用StringBuilder
可以显著提高性能;而在一个多线程的日志记录系统中,使用StringBuffer
可以保证数据的一致性。 - 不可变性:
String
的不可变性使得它在字符串常量池等场景中非常有用,可以提高内存的使用效率。字符串常量池是 JVM 中的一块特殊区域,用于存储常量字符串。当创建一个常量字符串时,JVM 会先在常量池中查找是否已经存在相同内容的字符串,如果存在则直接返回引用,避免了重复创建对象。 - 哈希码和相等比较:在使用哈希表时,需要确保对象的
hashCode
和equals
方法的实现是一致的,即如果两个对象equals
比较结果为true
,那么它们的hashCode
必须相同。否则,会导致哈希表的查找和插入操作出现错误。
- 应用
在处理大量字符串拼接时,如果是单线程环境,优先使用 StringBuilder
;如果是多线程环境,则使用 StringBuffer
。在自定义类中,为了能正确使用哈希表,需要重写 hashCode
和 equals
方法。例如,在一个自定义的用户类中,用户的 id
是唯一标识,那么在重写 hashCode
和 equals
方法时,应该基于 id
来进行计算和比较。
4. 什么是文件读取,如何实现
文件读取是指从文件系统中读取文件的内容到程序中进行处理。在 Java 中,可以使用不同的类和方法来实现文件读取。
- 原理
Java 通过输入流(InputStream
和 Reader
)来实现文件读取。输入流是一个抽象概念,它表示从数据源(如文件、网络等)读取数据的通道。InputStream
是字节输入流的基类,用于以字节为单位读取数据;Reader
是字符输入流的基类,用于以字符为单位读取数据。
- 要点
- 字节流:
FileInputStream
用于以字节为单位读取文件,适用于读取二进制文件(如图片、视频等)。它直接从文件中读取字节数据,不会对数据进行任何编码转换。 - 字符流:
FileReader
用于以字符为单位读取文件,适用于读取文本文件。它会根据系统默认的字符编码将字节数据转换为字符数据。为了指定特定的字符编码,可以使用InputStreamReader
包装FileInputStream
。 - 缓冲流:
BufferedInputStream
和BufferedReader
可以提高文件读取的效率,它们内部有一个缓冲区,减少了与磁盘的交互次数。当调用读取方法时,会先从缓冲区中读取数据,如果缓冲区中没有数据,则从文件中读取一批数据到缓冲区中。
- 实现示例
java
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class FileReadingExample {
public static void main(String[] args) {
try (BufferedReader br = new BufferedReader(new FileReader("test.txt"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 应用
可以使用 RandomAccessFile
类实现随机文件读取,即可以在文件的任意位置进行读写操作。RandomAccessFile
内部维护了一个文件指针,可以通过 seek
方法移动指针的位置,然后进行读写操作。在处理大文件时,可以采用分块读取的方式,避免一次性将整个文件加载到内存中。例如,可以每次读取固定大小的字节块,处理完后再读取下一块。
5. 什么是反射,如何实现
反射是 Java 的一种机制,它允许程序在运行时获取类的信息(如类的属性、方法、构造函数等),并可以动态地创建对象、调用方法、访问属性等。
- 原理
Java 的反射机制基于 Class
类和 java.lang.reflect
包中的类。Class
类是 Java 反射的核心,每个类在 JVM 中都有一个对应的 Class
对象,通过这个对象可以获取类的各种信息。java.lang.reflect
包中包含了 Method
、Field
、Constructor
等类,用于表示类的方法、属性和构造函数,可以通过这些类来动态地操作类的成员。
- 要点
- 获取
Class
对象:Class.forName()
:通过类的全限定名来获取Class
对象。这种方式适用于在运行时根据类名动态加载类。类名.class
:在编译时就确定要获取的类,直接通过类名获取Class
对象。这种方式比较简洁,适用于已知类名的情况。对象.getClass()
:通过对象实例获取其对应的Class
对象。这种方式适用于在运行时根据对象获取其类的信息。
- 创建对象:
Class
对象的newInstance()
方法(Java 9 及以后已弃用):该方法会调用类的无参构造函数来创建对象。如果类没有无参构造函数,会抛出异常。Constructor
类的newInstance()
方法:可以通过Class
对象的getConstructor
或getDeclaredConstructor
方法获取构造函数对象,然后调用其newInstance()
方法来创建对象。这种方式可以调用带参数的构造函数。
- 调用方法:通过
Method
类的invoke()
方法来调用对象的方法。需要先通过Class
对象的getMethod
或getDeclaredMethod
方法获取方法对象,然后传入对象实例和方法参数来调用方法。 - 访问属性:通过
Field
类的get()
和set()
方法来访问和修改对象的属性。需要先通过Class
对象的getField
或getDeclaredField
方法获取属性对象,然后传入对象实例来获取或修改属性值。
- 实现示例
java
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
class MyClass {
public void printMessage() {
System.out.println("Hello, Reflection!");
}
}
public class ReflectionExample {
public static void main(String[] args) throws Exception {
// 获取 Class 对象
Class<?> clazz = Class.forName("MyClass");
// 创建对象
Constructor<?> constructor = clazz.getConstructor();
Object obj = constructor.newInstance();
// 获取方法
Method method = clazz.getMethod("printMessage");
// 调用方法
method.invoke(obj);
}
}
- 应用
反射在框架开发中应用广泛,如 Spring 框架通过反射实现依赖注入和 AOP 功能。在 Spring 中,通过反射机制可以动态地创建对象、调用方法和设置属性,实现对象之间的依赖关系。但反射会带来一定的性能开销,因为它需要在运行时进行类的查找和方法的调用,因此在性能敏感的场景中应谨慎使用。
6. 什么是 JDK、NDK、JRE、JNI,有什么区别
- JDK(Java Development Kit):是 Java 开发工具包,它包含了 JRE 和一系列开发工具(如编译器
javac
、调试器jdb
等)。JDK 是开发 Java 程序的基础,提供了编译、运行 Java 程序所需的环境。 - NDK(Native Development Kit):是 Android 提供的一个工具集,用于在 Android 应用中使用 C 和 C++ 代码。通过 NDK,可以将部分性能敏感的代码用 C 或 C++ 实现,提高应用的性能。
- JRE(Java Runtime Environment):是 Java 运行时环境,它包含了 JVM(Java Virtual Machine)和 Java 核心类库。JRE 是运行 Java 程序的最小环境,用户只需要安装 JRE 就可以运行 Java 程序。
- JNI(Java Native Interface):是 Java 提供的一种机制,允许 Java 代码与本地代码(如 C、C++ 代码)进行交互。通过 JNI,可以在 Java 程序中调用本地方法,也可以在本地代码中调用 Java 方法。
- 区别要点
- 功能用途:
- JDK:用于开发 Java 程序,提供了编译、调试、打包等开发工具。
- NDK:用于在 Android 应用中使用本地代码,提高应用的性能,特别是在处理图形、音频、视频等计算密集型任务时。
- JRE:用于运行 Java 程序,用户只需要安装 JRE 就可以运行已编译好的 Java 程序。
- JNI:用于实现 Java 代码和本地代码的交互,使得 Java 程序可以利用本地代码的高性能和丰富的库资源。
- 包含内容:
- JDK:包含 JRE 和开发工具,如编译器
javac
、调试器jdb
、打包工具jar
等。 - NDK:包含一系列用于编译和链接本地代码的工具和库,如 GCC 编译器、Android 系统库等。
- JRE:包含 JVM 和核心类库,如
java.lang
、java.util
等。 - JNI:是 Java 提供的一组接口和规范,没有具体的工具和库,需要开发者根据规范编写代码。
- JDK:包含 JRE 和开发工具,如编译器
- 应用
在开发 Android 应用时,如果需要使用一些性能敏感的算法或调用一些现有的 C/C++ 库,可以使用 NDK 和 JNI 技术。例如,在游戏开发中,可以使用 C++ 实现游戏的物理引擎,然后通过 JNI 在 Java 层调用。在开发 Java 桌面应用或服务器端应用时,只需要安装 JDK 即可进行开发和运行。
7. 什么是 static 和 final,有什么区别
- static:是 Java 的一个关键字,用于修饰类的成员(成员变量、成员方法、代码块)。被
static
修饰的成员属于类,而不属于某个对象。可以通过类名直接访问,不需要创建对象。 - final:也是 Java 的一个关键字,用于修饰类、方法和变量。被
final
修饰的类不能被继承;被final
修饰的方法不能被重写;被final
修饰的变量一旦赋值,就不能再被修改。
- 原理
- static:成员在类加载时就会被分配内存,并且只分配一次,所有对象共享同一个
static
成员。类加载是 JVM 启动时将类的字节码文件加载到内存中的过程,static
成员在这个过程中被初始化。 - final:关键字主要是为了保证数据的不可变性和类、方法的安全性。对于
final
变量,编译器会在编译时进行检查,确保其值不会被修改;对于final
类和方法,编译器会禁止子类继承和重写。
- 要点
- 修饰变量:
static
变量:是类变量,所有对象共享。它可以在类加载时进行初始化,也可以在静态代码块中进行初始化。final
变量:是常量,一旦赋值不能改变。如果是基本数据类型,其值不能被修改;如果是引用类型,其引用不能被修改,但对象的内容可以被修改。static final
修饰的变量:是类常量,通常用于定义全局常量。它在类加载时被初始化,并且只能初始化一次。
- 修饰方法:
static
方法:可以通过类名直接调用,不能使用this
和super
关键字。因为static
方法属于类,而不是对象,this
和super
关键字是与对象相关的。final
方法:不能被重写,主要用于防止子类修改该方法的实现。例如,在一个工具类中,为了保证某些方法的实现不被修改,可以将这些方法声明为final
。
- 修饰类:
static
不能修饰类,但可以修饰内部类。静态内部类属于外部类,而不属于外部类的对象,可以通过外部类名直接访问。final
修饰的类:不能被继承,用于保证类的安全性和完整性。例如,String
类就是一个final
类,防止其他类继承并修改其行为。
- 应用
在工具类中,通常会使用 static
方法,方便直接调用。例如,Math
类中的所有方法都是 static
方法,可以直接通过类名调用。在定义常量时,使用 static final
可以提高代码的可读性和可维护性。在设计框架时,为了防止子类意外修改某些方法的实现,可以将这些方法声明为 final
。
8. 什么是 map、list、set,有什么区别
- Map:是一种键值对的集合,用于存储具有映射关系的数据。每个键是唯一的,一个键对应一个值。常见的实现类有
HashMap
、TreeMap
、LinkedHashMap
等。 - List:是一种有序的集合,允许存储重复的元素。可以通过索引访问元素。常见的实现类有
ArrayList
、LinkedList
等。 - Set:是一种不允许存储重复元素的集合。元素没有特定的顺序。常见的实现类有
HashSet
、TreeSet
、LinkedHashSet
等。
- 原理
- Map:基于哈希表或红黑树实现,通过键的哈希码来快速定位值。
HashMap
基于哈希表实现,通过哈希函数将键映射到哈希表的某个位置;TreeMap
基于红黑树实现,键会按照自然顺序或指定的比较器进行排序。 - List:基于数组或链表实现,
ArrayList
基于动态数组,LinkedList
基于双向链表。ArrayList
在随机访问元素时性能较好,因为可以通过索引直接访问数组中的元素;LinkedList
在插入和删除元素时性能较好,因为只需要修改链表的指针。 - Set:基于
Map
实现,HashSet
基于HashMap
,TreeSet
基于TreeMap
。HashSet
利用HashMap
的键的唯一性来保证元素的唯一性;TreeSet
利用TreeMap
的键的排序功能来保证元素的有序性。
- 要点
- 存储方式:
- Map:存储键值对,通过键来查找值。
- List:按顺序存储元素,可以通过索引访问元素。
- Set:存储不重复的元素,没有直接的索引访问方式。
- 访问方式:
- Map:通过键访问值,时间复杂度为 O (1)(
HashMap
)或 O (log n)(TreeMap
)。 - List:通过索引访问元素,时间复杂度为 O (1)(
ArrayList
)或 O (n)(LinkedList
)。 - Set:通常通过迭代器遍历元素,时间复杂度为 O (n)。
- Map:通过键访问值,时间复杂度为 O (1)(
- 元素特性:
- Map:键必须唯一,值可以重复。
- List:允许元素重复,元素有顺序。
- Set:不允许元素重复,元素没有特定的顺序(
TreeSet
除外)。
- 应用
在需要存储具有映射关系的数据时,使用 Map
;在需要按顺序存储元素,并且可能需要频繁访问元素时,使用 List
;在需要存储不重复的元素时,使用 Set
。在多线程环境下,可以使用 ConcurrentHashMap
、CopyOnWriteArrayList
、ConcurrentSkipListSet
等线程安全的集合类。例如,在一个电商系统中,可以使用 Map
存储商品的 ID 和商品信息的映射关系;使用 List
存储用户的订单列表;使用 Set
存储用户的收藏商品列表。
9. 什么是 Session 和 COOKIE,有什么区别
- Session:是服务器端的会话机制,用于跟踪用户的会话状态。服务器为每个用户创建一个唯一的会话对象,存储用户的相关信息。会话对象通常存储在服务器的内存中,也可以持久化到磁盘。
- COOKIE:是客户端的会话机制,服务器将一些信息以文本形式发送给客户端浏览器,浏览器将这些信息存储在本地。下次浏览器向服务器发送请求时,会将这些信息一起发送给服务器。
- 原理
- Session:服务器在创建会话对象时,会为每个会话分配一个唯一的会话 ID,并将该 ID 以 COOKIE 的形式发送给客户端。客户端下次请求时,会携带该会话 ID,服务器根据会话 ID 找到对应的会话对象。如果客户端浏览器禁用了 COOKIE,也可以通过 URL 重写的方式将会话 ID 传递给服务器。
- COOKIE:服务器通过响应头
Set - Cookie
将 COOKIE 信息发送给客户端,客户端浏览器将 COOKIE 存储在本地,并在下次请求时通过请求头Cookie
将 COOKIE 信息发送给服务器。COOKIE 可以设置不同的属性,如名称、值、过期时间、路径、域名等。
- 要点
- 存储位置:
- Session:信息存储在服务器端,客户端只保存会话 ID。
- COOKIE:信息存储在客户端,包括用户的一些信息(如用户名、偏好设置等)。
- 安全性:
- Session:相对安全,因为信息存储在服务器端,客户端无法直接访问和修改。
- COOKIE:安全性较低,因为信息存储在客户端,可能会被窃取或篡改。例如,攻击者可以通过中间人攻击获取用户的 COOKIE 信息,从而冒充用户登录系统。
- 数据大小限制:
- Session:没有数据大小限制,因为服务器可以根据需要分配内存来存储会话信息。
- COOKIE:有数据大小限制,一般不超过 4KB。如果需要存储大量数据,建议使用 Session。
- 有效期:
- Session:有一定的有效期,过期后会自动销毁。可以通过设置会话的超时时间来控制有效期。
- COOKIE:可以设置不同的有效期,包括会话级 COOKIE(浏览器关闭即失效)和持久化 COOKIE(可以设置过期时间)。
- 应用
在 Web 应用中,通常会结合使用 Session
和 COOKIE
。例如,使用 COOKIE
存储一些不敏感的信息(如用户的偏好设置),使用 Session
存储用户的登录状态等敏感信息。在分布式系统中,需要使用分布式会话管理技术来管理 Session
,如使用 Redis 等缓存服务器来存储会话信息,以保证不同服务器之间的会话一致性。
10. 什么是 IO、NIO、BIO、AIO、select、epoll,有什么区别
- IO(Input/Output):是 Java 中用于处理输入输出的基本机制,分为字节流和字符流。传统的 IO 是阻塞式的,即当进行读写操作时,线程会一直阻塞直到操作完成。
- BIO(Blocking IO):是传统的阻塞式 IO 模型,在进行读写操作时,线程会被阻塞,直到数据读写完成。在高并发场景下,需要为每个连接创建一个线程,会导致线程资源的浪费。
- NIO(Non - Blocking IO):是 Java 1.4 引入的非阻塞式 IO 模型,它基于通道(
Channel
)和缓冲区(Buffer
)进行操作。线程可以在进行读写操作时不被阻塞,而是可以继续处理其他任务。通过选择器(Selector
)可以实现一个线程管理多个通道。 - AIO(Asynchronous IO):是 Java 7 引入的异步 IO 模型,它是基于事件和回调机制的。当进行读写操作时,线程不会阻塞,而是在操作完成后通过回调函数通知线程。
- select:是一种传统的多路复用机制,用于监控多个文件描述符的状态变化。它有文件描述符数量的限制,一般为 1024。
- epoll:是 Linux 内核提供的一种高效的多路复用机制,它克服了
select
的一些缺点,没有文件描述符数量的限制,并且性能更高。
- 区别要点
- 阻塞与非阻塞:
- BIO:是阻塞式的,线程在进行读写操作时会被阻塞,直到操作完成。
- NIO:是非阻塞式的,线程可以在进行读写操作时继续处理其他任务。
- AIO:是异步非阻塞式的,线程在进行读写操作时不会阻塞,操作完成后通过回调函数通知线程。
- 多路复用机制:
- NIO:基于
select
或epoll
等多路复用机制,一个线程可以管理多个通道。通过选择器可以监控多个通道的状态变化,当某个通道有数据可读或可写时,线程才会进行相应的操作。 - AIO:基于事件和回调机制,不需要使用多路复用器。当读写操作完成时,操作系统会自动触发回调函数。
- NIO:基于
- 应用场景:
- BIO:适用于连接数较少且固定的场景,如一些传统的单机应用。
- NIO:适用于连接数较多但连接时间较短的场景,如 Web 服务器、即时通讯系统等。
- AIO:适用于连接数较多且连接时间较长的场景,如文件服务器、数据库服务器等。
- 应用
在开发高性能的网络应用时,如 Web 服务器、即时通讯系统等,通常会使用 NIO
或 AIO
模型。在 Linux 系统中,epoll
是实现 NIO
的首选多路复用机制。在使用 NIO
时,需要注意选择器的使用和缓冲区的管理,避免出现死锁和内存泄漏等问题。在使用 AIO
时,需要处理好回调函数的异常情况,确保程序的稳定性。
友情提示:本文已经整理成文档,可以到如下链接免积分下载阅读
https://download.csdn.net/download/ylfhpy/90493963