当前位置: 首页 > article >正文

Java面试黄金宝典2

1. 什么是 Concurrent 包

java.util.concurrent(简称 Concurrent 包)是 Java 5 引入的一个用于并发编程的工具包。它提供了一系列用于处理多线程编程的类和接口,帮助开发者更方便、安全地进行并发编程。

  • 原理

该包基于 Java 的多线程机制和锁机制,采用了诸如 CAS(Compare - And - Swap)、AQS(AbstractQueuedSynchronizer)等底层技术。

  1. CAS:是一种无锁算法,其核心思想是先比较内存中的值是否和预期值一致,如果一致则将新值更新到内存中,这个操作是原子性的。例如,在 AtomicInteger 类中,就使用了 CAS 来实现原子的自增操作。在多线程环境下,多个线程同时尝试修改一个 AtomicInteger 的值时,只有一个线程能够成功,其他线程会不断重试,避免了传统锁带来的线程阻塞和上下文切换开销。
  2. AQS:是一个用于构建锁和同步器的框架,许多并发工具类都基于它实现。AQS 内部维护了一个 FIFO 队列,用于管理等待获取锁的线程。当一个线程尝试获取锁失败时,会被放入队列中阻塞等待;当持有锁的线程释放锁时,会从队列中唤醒一个等待的线程。

  • 要点

  • 线程池
    1. ThreadPoolExecutor:是线程池的核心实现类,开发者可以通过它灵活地配置线程池的参数,如核心线程数、最大线程数、线程空闲时间等。例如,在一个 Web 服务器中,可以根据请求的并发量来调整线程池的大小,提高系统的处理能力。
    2. ScheduledThreadPoolExecutor:用于执行定时任务和周期性任务。它可以在指定的时间点执行任务,或者按照一定的周期重复执行任务,适用于定时数据备份、定时任务调度等场景。
  • 并发集合
    1. ConcurrentHashMap:在多线程环境下是线程安全的哈希表。它采用分段锁(Java 7 及以前)或 CAS + synchronized(Java 8 及以后)的方式来保证并发操作的安全性,避免了传统 HashMap 在并发访问时可能出现的数据不一致问题,如死循环和数据丢失。
    2. CopyOnWriteArrayList:是一个线程安全的动态数组。它的写操作(如 addremove 等)会创建一个新的数组副本,将原数组中的元素复制到新数组中,并在新数组上进行修改,最后将引用指向新数组。读操作则直接在原数组上进行,不需要加锁,因此在读多写少的场景下性能较好。
  • 同步工具类
    1. CountDownLatch:用于协调多个线程的执行顺序。它通过一个计数器来实现,主线程可以等待计数器的值减为 0 后再继续执行。例如,在一个多线程的任务中,主线程需要等待所有子线程完成任务后再进行汇总操作,就可以使用 CountDownLatch
    2. CyclicBarrier:可以让一组线程在到达某个屏障点时进行同步。当所有线程都到达屏障点后,屏障会打开,所有线程可以继续执行。它可以重复使用,适用于多个线程需要反复进行同步的场景。
    3. Semaphore:用于控制对有限资源的访问权限。它通过一个许可证计数器来实现,线程在访问资源前需要获取许可证,访问完后释放许可证。例如,在一个数据库连接池中,可以使用 Semaphore 来控制同时使用的连接数。

  • 应用

在高并发的 Web 应用中,使用 ConcurrentHashMap 可以替代传统的 HashMap,避免在多线程环境下出现死循环和数据丢失的问题。线程池可以根据业务需求调整线程数量,提高系统的吞吐量。例如,在电商系统的促销活动期间,并发访问量会大幅增加,可以适当增加线程池的最大线程数,以应对高并发请求。

2. 什么是面向对象,有哪 4 种特性

面向对象编程(OOP)是一种编程范式,它将数据和操作数据的方法封装在一起,形成对象。通过对象之间的交互来完成程序的功能。

  • 原理

面向对象的核心思想是将现实世界中的事物抽象成对象,每个对象都有自己的属性和行为。对象之间通过消息传递来进行交互,从而实现程序的功能。例如,在一个汽车模拟程序中,汽车可以抽象成一个对象,它有颜色、型号等属性,还有启动、加速、刹车等行为。不同的汽车对象之间可以通过消息传递来模拟交通场景。

  • 要点

  • 封装
    • 定义:将数据和操作数据的方法捆绑在一起,隐藏对象的内部实现细节,只对外提供必要的接口。通过访问修饰符(如 privateprotectedpublic)来控制对类成员的访问,提高了代码的安全性和可维护性。
    • 示例:在一个银行账户类中,账户余额是一个敏感数据,应该被封装起来。可以将余额属性设置为 private,并提供 getBalancedepositwithdraw 等方法来访问和修改余额,这样可以避免外部代码直接修改余额,保证数据的安全性。
  • 继承
    • 定义:子类可以继承父类的属性和方法,从而实现代码的复用。Java 只支持单继承,即一个子类只能有一个直接父类,但可以实现多个接口。子类可以对父类的方法进行重写,以实现自己的功能。
    • 示例:在一个动物类层次结构中,狗类可以继承动物类的属性和方法,同时可以重写动物类的 makeSound 方法,实现狗叫的功能。
  • 多态
    • 定义:同一个行为具有多个不同表现形式或形态的能力。通过继承和接口实现,父类引用可以指向子类对象,在运行时根据实际对象类型调用相应的方法,提高了代码的灵活性和可扩展性。
    • 示例:在一个图形绘制程序中,有一个抽象的图形类,以及圆形、矩形等具体的图形子类。可以定义一个绘制图形的方法,接收一个图形类的引用作为参数,在方法内部根据实际传入的对象类型调用相应的绘制方法,实现多态。
  • 抽象
    • 定义:将一类对象的共同特征总结出来,形成抽象类或接口。抽象类不能实例化,它可以包含抽象方法,子类必须实现这些抽象方法。接口是一种特殊的抽象类,只包含抽象方法和常量。
    • 示例:在一个交通工具系统中,可以定义一个抽象的交通工具类,包含抽象的移动方法。不同的交通工具子类(如汽车、飞机等)需要实现这个移动方法,以实现自己的移动方式。

  • 应用

在大型项目开发中,面向对象的编程思想可以使代码结构更加清晰,易于维护和扩展。例如,在游戏开发中,可以将不同的角色抽象成类,通过继承和多态实现不同角色的不同行为。在软件开发中,使用设计模式(如单例模式、工厂模式等)也是基于面向对象的思想,进一步提高代码的可维护性和可扩展性。

3. 什么是 String、StringBuffer、StringBuilder、hashCode、equals,有什么区别

  • String:是不可变的字符序列。一旦创建,其内容不能被修改。每次对 String 对象进行修改时,实际上是创建了一个新的 String 对象。
  • StringBuffer:是可变的字符序列,线程安全。它的方法大多是同步的,因此在多线程环境下可以安全使用,但性能相对较低。
  • StringBuilder:也是可变的字符序列,非线程安全。它的方法没有进行同步处理,因此在单线程环境下性能比 StringBuffer 高。
  • hashCode:是 Object 类的一个方法,用于返回对象的哈希码。哈希码是一个整数,通常用于哈希表(如 HashMapHashSet)中,提高查找效率。
  • equals:是 Object 类的一个方法,用于比较两个对象是否相等。默认情况下,比较的是对象的引用是否相等,子类可以重写该方法来实现自定义的相等比较逻辑。

  • 原理

  1. String:类内部使用 final char[] 数组来存储字符序列,由于 final 修饰,数组的引用不能被修改,因此 String 是不可变的。当对 String 进行拼接操作时,会创建一个新的 String 对象,原对象的内容不会改变。
  2. StringBuffer 和 StringBuilder:内部使用可变的字符数组来存储字符序列,通过 appendinsert 等方法可以修改字符序列。StringBuffer 的方法使用了 synchronized 关键字进行同步,保证了线程安全;StringBuilder 没有进行同步处理,因此性能更高。
  3. hashCode:方法根据对象的内容计算哈希码,不同的对象可能有相同的哈希码,但相同的对象哈希码一定相同。在 String 类中,哈希码的计算是基于字符串的内容,因此相同内容的 String 对象哈希码相同。
  4. equals:方法在 String 类中被重写,用于比较两个字符串的内容是否相等。它会遍历两个字符串的每个字符,只有当所有字符都相等时,才认为两个字符串相等。

  • 要点

  1. 性能方面:在单线程环境下,频繁修改字符序列时,StringBuilder 性能最好;在多线程环境下,使用 StringBuffer 更安全。例如,在一个单线程的字符串拼接任务中,使用 StringBuilder 可以显著提高性能;而在一个多线程的日志记录系统中,使用 StringBuffer 可以保证数据的一致性。
  2. 不可变性String 的不可变性使得它在字符串常量池等场景中非常有用,可以提高内存的使用效率。字符串常量池是 JVM 中的一块特殊区域,用于存储常量字符串。当创建一个常量字符串时,JVM 会先在常量池中查找是否已经存在相同内容的字符串,如果存在则直接返回引用,避免了重复创建对象。
  3. 哈希码和相等比较:在使用哈希表时,需要确保对象的 hashCodeequals 方法的实现是一致的,即如果两个对象 equals 比较结果为 true,那么它们的 hashCode 必须相同。否则,会导致哈希表的查找和插入操作出现错误。

  • 应用

在处理大量字符串拼接时,如果是单线程环境,优先使用 StringBuilder;如果是多线程环境,则使用 StringBuffer。在自定义类中,为了能正确使用哈希表,需要重写 hashCodeequals 方法。例如,在一个自定义的用户类中,用户的 id 是唯一标识,那么在重写 hashCodeequals 方法时,应该基于 id 来进行计算和比较。

4. 什么是文件读取,如何实现

文件读取是指从文件系统中读取文件的内容到程序中进行处理。在 Java 中,可以使用不同的类和方法来实现文件读取。

  • 原理

Java 通过输入流(InputStreamReader)来实现文件读取。输入流是一个抽象概念,它表示从数据源(如文件、网络等)读取数据的通道。InputStream 是字节输入流的基类,用于以字节为单位读取数据;Reader 是字符输入流的基类,用于以字符为单位读取数据。

  • 要点

  1. 字节流FileInputStream 用于以字节为单位读取文件,适用于读取二进制文件(如图片、视频等)。它直接从文件中读取字节数据,不会对数据进行任何编码转换。
  2. 字符流FileReader 用于以字符为单位读取文件,适用于读取文本文件。它会根据系统默认的字符编码将字节数据转换为字符数据。为了指定特定的字符编码,可以使用 InputStreamReader 包装 FileInputStream
  3. 缓冲流BufferedInputStreamBufferedReader 可以提高文件读取的效率,它们内部有一个缓冲区,减少了与磁盘的交互次数。当调用读取方法时,会先从缓冲区中读取数据,如果缓冲区中没有数据,则从文件中读取一批数据到缓冲区中。

  • 实现示例

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 包中包含了 MethodFieldConstructor 等类,用于表示类的方法、属性和构造函数,可以通过这些类来动态地操作类的成员。

  • 要点

  • 获取 Class 对象
    1. Class.forName():通过类的全限定名来获取 Class 对象。这种方式适用于在运行时根据类名动态加载类。
    2. 类名.class:在编译时就确定要获取的类,直接通过类名获取 Class 对象。这种方式比较简洁,适用于已知类名的情况。
    3. 对象.getClass():通过对象实例获取其对应的 Class 对象。这种方式适用于在运行时根据对象获取其类的信息。
  • 创建对象
    1. Class 对象的 newInstance() 方法(Java 9 及以后已弃用):该方法会调用类的无参构造函数来创建对象。如果类没有无参构造函数,会抛出异常。
    2. Constructor 类的 newInstance() 方法:可以通过 Class 对象的 getConstructorgetDeclaredConstructor 方法获取构造函数对象,然后调用其 newInstance() 方法来创建对象。这种方式可以调用带参数的构造函数。
  • 调用方法:通过 Method 类的 invoke() 方法来调用对象的方法。需要先通过 Class 对象的 getMethodgetDeclaredMethod 方法获取方法对象,然后传入对象实例和方法参数来调用方法。
  • 访问属性:通过 Field 类的 get()set() 方法来访问和修改对象的属性。需要先通过 Class 对象的 getFieldgetDeclaredField 方法获取属性对象,然后传入对象实例来获取或修改属性值。

  • 实现示例

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 方法。

  • 区别要点

  • 功能用途
    1. JDK:用于开发 Java 程序,提供了编译、调试、打包等开发工具。
    2. NDK:用于在 Android 应用中使用本地代码,提高应用的性能,特别是在处理图形、音频、视频等计算密集型任务时。
    3. JRE:用于运行 Java 程序,用户只需要安装 JRE 就可以运行已编译好的 Java 程序。
    4. JNI:用于实现 Java 代码和本地代码的交互,使得 Java 程序可以利用本地代码的高性能和丰富的库资源。
  • 包含内容
    1. JDK:包含 JRE 和开发工具,如编译器 javac、调试器 jdb、打包工具 jar 等。
    2. NDK:包含一系列用于编译和链接本地代码的工具和库,如 GCC 编译器、Android 系统库等。
    3. JRE:包含 JVM 和核心类库,如 java.langjava.util 等。
    4. JNI:是 Java 提供的一组接口和规范,没有具体的工具和库,需要开发者根据规范编写代码。

  • 应用

在开发 Android 应用时,如果需要使用一些性能敏感的算法或调用一些现有的 C/C++ 库,可以使用 NDK 和 JNI 技术。例如,在游戏开发中,可以使用 C++ 实现游戏的物理引擎,然后通过 JNI 在 Java 层调用。在开发 Java 桌面应用或服务器端应用时,只需要安装 JDK 即可进行开发和运行。

7. 什么是 static 和 final,有什么区别

  • static:是 Java 的一个关键字,用于修饰类的成员(成员变量、成员方法、代码块)。被 static 修饰的成员属于类,而不属于某个对象。可以通过类名直接访问,不需要创建对象。
  • final:也是 Java 的一个关键字,用于修饰类、方法和变量。被 final 修饰的类不能被继承;被 final 修饰的方法不能被重写;被 final 修饰的变量一旦赋值,就不能再被修改。

  • 原理

  1. static:成员在类加载时就会被分配内存,并且只分配一次,所有对象共享同一个 static 成员。类加载是 JVM 启动时将类的字节码文件加载到内存中的过程,static 成员在这个过程中被初始化。
  2. final:关键字主要是为了保证数据的不可变性和类、方法的安全性。对于 final 变量,编译器会在编译时进行检查,确保其值不会被修改;对于 final 类和方法,编译器会禁止子类继承和重写。

  • 要点

  • 修饰变量
    1. static 变量:是类变量,所有对象共享。它可以在类加载时进行初始化,也可以在静态代码块中进行初始化。
    2. final 变量:是常量,一旦赋值不能改变。如果是基本数据类型,其值不能被修改;如果是引用类型,其引用不能被修改,但对象的内容可以被修改。
    3. static final 修饰的变量:是类常量,通常用于定义全局常量。它在类加载时被初始化,并且只能初始化一次。
  • 修饰方法
    1. static 方法:可以通过类名直接调用,不能使用 thissuper 关键字。因为 static 方法属于类,而不是对象,thissuper 关键字是与对象相关的。
    2. final 方法:不能被重写,主要用于防止子类修改该方法的实现。例如,在一个工具类中,为了保证某些方法的实现不被修改,可以将这些方法声明为 final
  • 修饰类
    1. static 不能修饰类,但可以修饰内部类。静态内部类属于外部类,而不属于外部类的对象,可以通过外部类名直接访问。
    2. final 修饰的类:不能被继承,用于保证类的安全性和完整性。例如,String 类就是一个 final 类,防止其他类继承并修改其行为。

  • 应用

在工具类中,通常会使用 static 方法,方便直接调用。例如,Math 类中的所有方法都是 static 方法,可以直接通过类名调用。在定义常量时,使用 static final 可以提高代码的可读性和可维护性。在设计框架时,为了防止子类意外修改某些方法的实现,可以将这些方法声明为 final

8. 什么是 map、list、set,有什么区别

  • Map:是一种键值对的集合,用于存储具有映射关系的数据。每个键是唯一的,一个键对应一个值。常见的实现类有 HashMapTreeMapLinkedHashMap 等。
  • List:是一种有序的集合,允许存储重复的元素。可以通过索引访问元素。常见的实现类有 ArrayListLinkedList 等。
  • Set:是一种不允许存储重复元素的集合。元素没有特定的顺序。常见的实现类有 HashSetTreeSetLinkedHashSet 等。

  • 原理

  1. Map:基于哈希表或红黑树实现,通过键的哈希码来快速定位值。HashMap 基于哈希表实现,通过哈希函数将键映射到哈希表的某个位置;TreeMap 基于红黑树实现,键会按照自然顺序或指定的比较器进行排序。
  2. List:基于数组或链表实现,ArrayList 基于动态数组,LinkedList 基于双向链表。ArrayList 在随机访问元素时性能较好,因为可以通过索引直接访问数组中的元素;LinkedList 在插入和删除元素时性能较好,因为只需要修改链表的指针。
  3. Set:基于 Map 实现,HashSet 基于 HashMapTreeSet 基于 TreeMapHashSet 利用 HashMap 的键的唯一性来保证元素的唯一性;TreeSet 利用 TreeMap 的键的排序功能来保证元素的有序性。

  • 要点

  • 存储方式
    1. Map:存储键值对,通过键来查找值。
    2. List:按顺序存储元素,可以通过索引访问元素。
    3. Set:存储不重复的元素,没有直接的索引访问方式。
  • 访问方式
    1. Map:通过键访问值,时间复杂度为 O (1)(HashMap)或 O (log n)(TreeMap)。
    2. List:通过索引访问元素,时间复杂度为 O (1)(ArrayList)或 O (n)(LinkedList)。
    3. Set:通常通过迭代器遍历元素,时间复杂度为 O (n)。
  • 元素特性
    1. Map:键必须唯一,值可以重复。
    2. List:允许元素重复,元素有顺序。
    3. Set:不允许元素重复,元素没有特定的顺序(TreeSet 除外)。

  • 应用

在需要存储具有映射关系的数据时,使用 Map;在需要按顺序存储元素,并且可能需要频繁访问元素时,使用 List;在需要存储不重复的元素时,使用 Set。在多线程环境下,可以使用 ConcurrentHashMapCopyOnWriteArrayListConcurrentSkipListSet 等线程安全的集合类。例如,在一个电商系统中,可以使用 Map 存储商品的 ID 和商品信息的映射关系;使用 List 存储用户的订单列表;使用 Set 存储用户的收藏商品列表。

9. 什么是 Session 和 COOKIE,有什么区别

  • Session:是服务器端的会话机制,用于跟踪用户的会话状态。服务器为每个用户创建一个唯一的会话对象,存储用户的相关信息。会话对象通常存储在服务器的内存中,也可以持久化到磁盘。
  • COOKIE:是客户端的会话机制,服务器将一些信息以文本形式发送给客户端浏览器,浏览器将这些信息存储在本地。下次浏览器向服务器发送请求时,会将这些信息一起发送给服务器。

  • 原理

  1. Session:服务器在创建会话对象时,会为每个会话分配一个唯一的会话 ID,并将该 ID 以 COOKIE 的形式发送给客户端。客户端下次请求时,会携带该会话 ID,服务器根据会话 ID 找到对应的会话对象。如果客户端浏览器禁用了 COOKIE,也可以通过 URL 重写的方式将会话 ID 传递给服务器。
  2. COOKIE:服务器通过响应头 Set - Cookie 将 COOKIE 信息发送给客户端,客户端浏览器将 COOKIE 存储在本地,并在下次请求时通过请求头 Cookie 将 COOKIE 信息发送给服务器。COOKIE 可以设置不同的属性,如名称、值、过期时间、路径、域名等。

  • 要点

  • 存储位置
    1. Session:信息存储在服务器端,客户端只保存会话 ID。
    2. COOKIE:信息存储在客户端,包括用户的一些信息(如用户名、偏好设置等)。
  • 安全性
    1. Session:相对安全,因为信息存储在服务器端,客户端无法直接访问和修改。
    2. COOKIE:安全性较低,因为信息存储在客户端,可能会被窃取或篡改。例如,攻击者可以通过中间人攻击获取用户的 COOKIE 信息,从而冒充用户登录系统。
  • 数据大小限制
    1. Session:没有数据大小限制,因为服务器可以根据需要分配内存来存储会话信息。
    2. COOKIE:有数据大小限制,一般不超过 4KB。如果需要存储大量数据,建议使用 Session。
  • 有效期
    1. Session:有一定的有效期,过期后会自动销毁。可以通过设置会话的超时时间来控制有效期。
    2. COOKIE:可以设置不同的有效期,包括会话级 COOKIE(浏览器关闭即失效)和持久化 COOKIE(可以设置过期时间)。

  • 应用

在 Web 应用中,通常会结合使用 SessionCOOKIE。例如,使用 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 的一些缺点,没有文件描述符数量的限制,并且性能更高。

  • 区别要点

  • 阻塞与非阻塞
    1. BIO:是阻塞式的,线程在进行读写操作时会被阻塞,直到操作完成。
    2. NIO:是非阻塞式的,线程可以在进行读写操作时继续处理其他任务。
    3. AIO:是异步非阻塞式的,线程在进行读写操作时不会阻塞,操作完成后通过回调函数通知线程。
  • 多路复用机制
    1. NIO:基于 selectepoll 等多路复用机制,一个线程可以管理多个通道。通过选择器可以监控多个通道的状态变化,当某个通道有数据可读或可写时,线程才会进行相应的操作。
    2. AIO:基于事件和回调机制,不需要使用多路复用器。当读写操作完成时,操作系统会自动触发回调函数。
  • 应用场景
    1. BIO:适用于连接数较少且固定的场景,如一些传统的单机应用。
    2. NIO:适用于连接数较多但连接时间较短的场景,如 Web 服务器、即时通讯系统等。
    3. AIO:适用于连接数较多且连接时间较长的场景,如文件服务器、数据库服务器等。

  • 应用

在开发高性能的网络应用时,如 Web 服务器、即时通讯系统等,通常会使用 NIOAIO 模型。在 Linux 系统中,epoll 是实现 NIO 的首选多路复用机制。在使用 NIO 时,需要注意选择器的使用和缓冲区的管理,避免出现死锁和内存泄漏等问题。在使用 AIO 时,需要处理好回调函数的异常情况,确保程序的稳定性。

 友情提示:本文已经整理成文档,可以到如下链接免积分下载阅读

https://download.csdn.net/download/ylfhpy/90493963


http://www.kler.cn/a/591340.html

相关文章:

  • 学习知识的心理和方法杂记-04
  • 强化学习(赵世钰版)-学习笔记(8.值函数方法)
  • MySQL--DDL
  • Python教学:lambda表达式的应用-由DeepSeek产生
  • 技术路线图ppt模板_流程图ppt图表_PPT架构图
  • 优选算法合集————双指针(专题四)
  • django如何配置使用asgi
  • 【LInux进程六】命令行参数和环境变量
  • 【大模型基础_毛玉仁】2.6 非 Transformer 架构
  • SSH后判断当前服务器是云主机、物理机、虚拟机、docker环境
  • 谈谈 TypeScript 中的模块系统,如何使用 ES Modules 和 CommonJS 模块?
  • STM32---FreeRTOS内存管理实验
  • 剑指 Offer II 109. 开密码锁
  • 【go】soilid与设计模式
  • 【Leetcode】203.移除链表元素
  • MyBatis 如何创建 SqlSession 对象的?
  • 手抖护理指南:全方位守护患者生活
  • 华为ISC+战略规划项目数字化转型驱动的智慧供应链革新(169页PPT)(文末有下载方式)
  • 为什么Django能有效防御CSRF攻击?
  • 爬虫基础之爬取猫眼Top100 可视化