0%

Java NIO 详解:非阻塞 IO 与多路复用技术

Java NIO(Non-blocking IO,非阻塞 IO)是 JDK 1.4 引入的全新 IO 模型(JDK 1.7 补充 NIO.2),旨在解决传统 IO(BIO)在高并发场景下的性能瓶颈。NIO 基于 “通道(Channel)” 和 “缓冲区(Buffer)” 实现,通过 “多路复用(Selector)” 机制支持单线程处理多个 IO 操作,显著提升系统吞吐量。本文将全面解析 NIO 的核心原理、组件及实践。

NIO 核心概念与优势

阻塞 vs 非阻塞

  • 阻塞 IO(BIO):线程调用 read()write() 时会被挂起,直到操作完成才能继续执行。为处理多个客户端,需为每个连接创建独立线程,导致线程资源耗尽(上下文切换开销大)。
  • 非阻塞 IO(NIO):线程发起 IO 操作后无需阻塞,可继续处理其他任务;若操作未完成,仅返回 “未就绪” 状态,通过定期轮询或事件通知获取结果。单线程可管理多个 IO 通道,减少线程数量。

NIO 与传统 IO 的核心区别

特性 传统 IO(BIO) NIO
数据操作单位 字节流 / 字符流(Stream) 缓冲区(Buffer)
传输方向 单向(输入流 / 输出流分离) 双向(通道 Channel 可读写)
阻塞模式 阻塞(线程挂起) 非阻塞(线程可并发处理多任务)
并发处理 多线程(一个连接一个线程) 单线程 / 少线程(多路复用)
核心模型 流模型 通道 - 缓冲区模型
适用场景 低并发、简单 IO 操作 高并发、大流量场景(如网络服务器)

NIO 核心组件

NIO 的核心由三大组件构成:缓冲区(Buffer)通道(Channel)选择器(Selector),三者协同实现非阻塞 IO 操作。

缓冲区(Buffer):数据的容器

Buffer 是一块内存区域,用于存储 IO 操作的数据。所有 NIO 数据读写都必须通过 Buffer 完成(Channel 仅负责传输,不存储数据)。

(1)核心 Buffer 类型

NIO 为每种基本数据类型提供了对应的 Buffer 实现(除 boolean):

类型 描述 示例
ByteBuffer 字节缓冲区(最常用) 网络数据、文件二进制数据
CharBuffer 字符缓冲区 文本数据(自动处理编码)
IntBuffer/LongBuffer 基本类型缓冲区 结构化数据(如整数数组)
(2)Buffer 的核心变量

Buffer 通过三个核心变量控制数据读写,其关系为:0 ≤ mark ≤ position ≤ limit ≤ capacity

阅读全文 »

Java 函数式编程:核心思想与实践

函数式编程(Functional Programming)是一种以函数为核心的编程范式,强调不可变数据无副作用,从根本上解决了并发编程中的数据竞争问题。Java 8 引入 Lambda 表达式和函数式接口,正式支持函数式编程风格,大幅简化了代码并提升了可读性。本文将从核心思想出发,详解 Lambda 表达式、函数式接口、方法引用等关键特性。

函数式编程的核心思想

函数式编程的两大支柱:

  1. 不可变数据(Immutability)
    数据一旦创建就不可修改,任何操作都只会生成新数据,不会改变原始值。这避免了多线程环境下的数据竞争(多个线程同时修改同一数据)。
  2. 无副作用(No Side Effects)
    函数的执行不会影响外部状态(如全局变量、输入参数),相同输入始终产生相同输出。这种 “纯函数” 特性让代码更易测试和推理。

函数式接口:Lambda 表达式的载体

函数式接口是函数式编程的基础,指仅包含一个抽象方法的接口(可包含默认方法或静态方法)。Java 8 提供 @FunctionalInterface 注解用于标记此类接口,编译器会检查其是否符合规范。

1
2
3
4
5
6
7
8
9
10
11
12
@FunctionalInterface // 编译器验证:仅含一个抽象方法
public interface Calculator {
int compute(int a, int b); // 唯一抽象方法

default void printResult(int result) { // 默认方法(允许)
System.out.println("结果:" + result);
}

static void log() { // 静态方法(允许)
System.out.println("计算完成");
}
}

关键:Lambda 表达式本质是函数式接口的匿名实现,简化了接口实现的代码。

Lambda 表达式:简洁的函数实现

Lambda 表达式是函数式接口的简写形式,语法为:

阅读全文 »

Java 集合框架详解:从基础到实践

Java 集合框架(Java Collections Framework)是处理对象集合的核心工具,位于 java.util 包下,提供了一套统一的接口和实现类,用于存储、操作和管理一组对象。与数组相比,集合能自动调整大小,支持泛型(编译期类型检查),并提供了丰富的操作方法(如添加、删除、查找等)。本文将系统讲解集合框架的体系结构、核心实现类及常见问题解决方案。

集合框架体系概览

Java 集合框架主要分为两大体系:Collection(单列集合)Map(双列集合)

1. Collection 接口:独立元素的序列

Collection 是所有单列集合的根接口,存储一组独立元素,主要子接口包括:

  • List:有序、可重复(如动态数组);
  • Set:无序、不可重复(如数学中的 “集合”);
  • Queue:按特定规则排序(如先进先出的队列)。

2. Map 接口:键值对的映射

Map 存储 “键值对”(key-value),通过键(key)查找值(value),键不可重复,值可重复。主要实现类包括 HashMapTreeMapLinkedHashMap 等。

Collection 子接口及实现类

List:有序可重复的动态数组

List 以插入顺序保存元素,允许重复,支持通过索引访问(类似数组)。核心实现类:ArrayListLinkedListVector

(1)ArrayList:基于数组的动态列表
  • 底层结构:数组(Object[]),默认初始容量为 10,当元素满时自动扩容(默认扩容为原容量的 1.5 倍)。
  • 特性:
    • 优势:随机访问效率高(通过索引直接访问,时间复杂度 O(1));
    • 劣势:中间插入 / 删除元素效率低(需移动后续元素,时间复杂度 O(n));
    • 非线程安全(多线程环境需手动同步,或使用 CopyOnWriteArrayList)。
(2)LinkedList:基于双向链表的列表
  • 底层结构:双向链表(每个节点包含 prevnext 指针和元素值)。
  • 特性:
    • 优势:中间插入 / 删除元素效率高(只需修改指针,时间复杂度 O(1));
    • 劣势:随机访问效率低(需从表头 / 表尾遍历,时间复杂度 O(n));
    • 非线程安全,可作为栈、队列、双向队列使用(提供 addFirst()removeLast() 等方法)。
(3)Vector:线程安全的动态数组(已过时)
  • 底层结构:数组,与 ArrayList 类似,但所有方法均被 synchronized 修饰(线程安全)。
  • 问题:同步导致性能低下,推荐用 CopyOnWriteArrayList(并发包中的线程安全列表)替代。
List 实现类对比
特性 ArrayList LinkedList Vector(过时)
底层结构 数组 双向链表 数组
随机访问 快(O(1) 慢(O(n) 快(O(1)
插入 / 删除(中间) 慢(O(n) 快(O(1) 慢(O(n)+ 同步开销)
线程安全 是(低效)
适用场景 频繁查询,少量增删 频繁增删,少量查询 无(推荐 CopyOnWriteArrayList

Set:无序不可重复的集合

Set 不允许存储重复元素(通过 equals()hashCode() 判断重复),无序(元素存储位置与插入顺序无关)。核心实现类:HashSetLinkedHashSetTreeSet

阅读全文 »

Java 内部类详解:类型、特性与应用

内部类是定义在另一个类(外部类)中的类,作为外部类的一部分,它可以将逻辑相关的类组织在一起,并通过访问控制隐藏实现细节。内部类不仅能访问外部类的所有成员(包括私有成员),还能对其他类隐藏自身,是 Java 面向对象设计中灵活且强大的特性。

内部类的核心作用

  1. 访问外部类私有成员:内部类可直接访问外部类的所有成员(包括 private 修饰的属性和方法),无需特殊权限。
  2. 实现隐藏:内部类可被声明为 privateprotected,仅外部类或其子类可见,对其他类隐藏。
  3. 逻辑内聚:将仅被外部类使用的辅助类定义为内部类,增强代码的模块化和可读性。
  4. 多实现灵活性:一个类可通过多个内部类实现同一接口的不同版本,解决单继承限制。

内部类的四种类型

根据定义位置和修饰符的不同,内部类分为四类:成员内部类局部内部类匿名内部类静态内部类,各有不同的特性和适用场景。

成员内部类(Member Inner Class)

成员内部类是定义在外部类的成员位置(与属性、方法同级)的内部类,可视为外部类的 “成员变量”,具有以下特性:

核心特性:
  • 依赖外部类实例:必须通过外部类实例才能创建成员内部类对象,无法独立存在。
  • 访问权限修饰符:可被 publicprotectedprivate 或默认修饰符修饰,控制其可见性。
  • 无静态成员:成员内部类中不能定义静态属性、静态方法或静态内部类(编译报错)。
  • 访问外部类成员:可直接访问外部类的所有成员(包括私有成员),通过隐式引用关联外部类实例。
代码示例:基本用法
阅读全文 »

Java 数组详解:基础结构与核心操作

数组是 Java 中最基础的数据结构之一,用于存储相同数据类型的元素集合。其核心特点是固定长度随机访问(通过索引快速定位元素),是许多高级集合类(如 ArrayList)的底层存储结构。本文将从数组的初始化、多维数组、特性及应用场景等方面,全面解析 Java 数组的本质。

数组的基本概念

  • 定义:数组是相同数据类型元素的有序集合,每个元素通过唯一索引(下标)访问,索引从 0 开始。
  • 本质:数组在内存中是连续的存储空间,因此支持 O(1) 时间复杂度的随机访问。
  • 长度固定:数组一旦初始化,长度不可修改(若需动态调整,需手动复制到新数组)。

一维数组的初始化

数组的初始化分为静态初始化动态初始化,核心区别在于是否在初始化时直接指定元素值。

静态初始化

初始化时直接为每个元素赋值,数组长度由元素个数自动确定。

语法:
1
2
3
4
5
6
// 方式1:简化语法(声明时直接赋值)
数据类型[] 数组名 = {元素1, 元素2, ..., 元素n};

// 方式2:完整语法(可用于声明后赋值)
数据类型[] 数组名;
数组名 = new 数据类型[]{元素1, 元素2, ..., 元素n};
示例:
1
2
3
4
5
6
// 静态初始化整数数组
int[] numbers = {1, 2, 3, 4};

// 静态初始化字符串数组(声明后赋值)
String[] names;
names = new String[]{"Alice", "Bob", "Charlie"};

动态初始化

初始化时仅指定数组长度,系统为元素分配默认值,后续再手动赋值。

语法:
阅读全文 »