Java NIO包结构简介

发布时间 2023-11-02 20:26:16作者: 尹如何
layout: post
read_time: true
show_date: true
title:  Java NIO包结构
date:   2023-07-09 10:12:10 -0600
description: Java NIO包结构简述.
img: posts/java-nio/cover.png
tags: [java, IO, Java NIO, Channel IO, Buffer, thread safty, multiplexer, charset]
author: 尹康
github: yinkang-sh/yinkang-blog
mathjax: yes

1. input/output

来自 wiki

1.1 概述

1.1.1 什么是 I/O?

输入输出(I/O)就是一个信息处理系统(information processing system)和外部的之间的通信。外部系统可以是另一个信息系统或者人类。其中输入(Input)就是系统接收到的信号或者数据,输出(Output)就是由该系统发出信号或者数据。其中IO操作具体指的是输入操作或者输出操作

关于输入(Input)和输出(Output)的定义首先它是一个相对概念,是该系统的输入但是可能是另一个系统的输出。同时对于某个系统,任何和该系统进行交互(Interaction)的设备就是输入,该系统对交互所作出的反应(reaction)就是输出。

在冯诺依曼体系的计算机中,可以将CPU和主存(Main Memory)理解为系统内部,其他的部分全部是外部。同时IO操作指的就是CPU/主存和其外部的交互(数据交换)。该体系之下,CPU和其所支持的电路应该具有以下的功能:

  1. IO设备和CPU之间的数据传输: 这个也叫编程IO(Programmed input-output,PIO),包括的技术有内存映射IO(Memory-mapped IO,MMIO)端口映射IO(Port-mapped IO,PMIO)

  2. IO设备和主存之间的数据传输:(I/O Channel):直接内存访问技术(Direct Memory Access,DMA)。

所谓的IO算法指的是:一种设计用来与辅助存储设备(如磁盘驱动器)进行高效地交换数据的局部性算法。

1.1.2 什么是I/O设备?

是人类或者其他设备用于和该系统通信的硬件部分。比如键盘鼠标是负责人和信息系统通信的,比如人机交互领域。调制解调器(Modems)和网卡等是负责系统之间通信的IO设备。

1.1.3 IO接口(IO Interface)

当处理处理器驱动某个IO设备的时候都会调用相应的IO接口。也就是说处理器可以通过IO接口和IO设备进行通信。一般来说,处理器和IO设备通过总线进行通信。作为一个IO接口,应该提供以下的功能:

  1. 翻译处理器生成的设备地址。

  2. 实现握手功能(Handshaking),用于处理器探查IO设备是否在线。

  3. 如果处理器和设备数据格式不一致,还需要能进行数据的转换。

  4. 为了防止处理器在IO操作中一直处于空闲状态,还需要设计一套产生中断的机制,比如中断的具体类型编码等。

其中为了实现处理器和IO设备的通信,主要由两种方式:

  1. 使用内存映射IO(Memory-mapped IO),计算机可以使用普通访问主存的指令来访问设备。

  2. 设计专门针对IO操作的新指令。

在IO接口的设计中,考虑到输入输出方在数据传输率方面的巨大差异,让IO设备在不需要CPU持续协助下直接访问内存是必要的。否则,由于CPU和设备数据传输率的巨大差异,在IO操作过程中,CPU将大量处于空闲状态。这就是所谓的IO瓶颈(IO Bound),也成为冯诺依曼瓶颈。


1.2 Channel I/O

是一种高性能的IO架构,具有多种具体的实现。下面先介绍其架构,然后介绍几种具体的实现。其中该架构尤其适合于大型计算机(mainframe computer)。它的具体名称有:通道(Channel),I/O处理器(I/O Processor),I/O控制器(I/O Controller),I/O同步器(I/O Synchronizer),DMA控制器(DMA Controller)。

该架构设计的初衷主要是解决I/O Bound。也就是解决IO设备和CPU的频繁交互从而导致的CPU闲置问题。具体的做法是将I/O任务的操作从CPU中卸载出来,使用专门的逻辑处理任务。

1.2.1 架构

首先,Channel IO是一个拥有自身逻辑指令和内存的设备,可以独立处理IO任务。也就是说Channel IO中是具有独立的处理器的硬件设备。在某些计算机架构中,IO Channel还可以作为辅助处理器进行使用。

流程:

  1. 当程序请求对某个设备进行IO任务的时候,CPU指定某块内存作为通道的内存使用区域。

  2. 然后CPU将通道程序发送到Channel IO中,这样Channel IO中的处理器就可以在没有CPU干预的情况下执行通道程序的逻辑,从而完成IO任务。

  3. 通道通过中断和CPU进行通信,比如任务中出现错误或者任务完成的时候,通道需要和CPU进行通信。

从上面的流程中可以看出,Channel IO本质上是位于CPU和IO设备之间的一层硬件设备,该硬件设备具有独立的处理器,将本来需要CPU执行的逻辑委托给Channel IO中的处理器,从而将CPU在IO Bound中解放出来。同时在具体执行之前,CPU将为通道分配一片内存区域作为通道程序运行过程中的工作区域。

1.2.2 实现

第一个Channel IO的实现是IBM 709中的Model 766 Data Synchronizer。这是世界上第一个通道控制器。


1.3 Memory-mapped IO

首先该技术旨在实现设备和CPU之间的通信,简单说就是该技术将主存和IO设备的内存空间统一到相同的地址空间中,这样CPU就可以使用访问主存的指令去直接访问IO设备的内存。这样有助于降低CPU指令的复杂性。通过地址内存映射的方法,CPU就可以直接使用普通指令去访问设备。

1.4 Port-mapped IO

CPU使用专门为IO操作设计的指令去访问设备,比如在x86架构中的inout指令。IO设备和主存之间是独立的地址空间,设备要么连接到CPU上的物理引脚或者直接连接到专用的IO总线中。

本质上是通过特殊指令将IO设备中的数据和CPU中的专用寄存器进行数据交换,由此来实现CPU和设备之间的通信。

1.5 Direct Memory Access

DMA是计算机系统中的一个功能特性技术,该功能为允许某些硬件子系统在不适用CPU辅助的情况下直接和主存进行通信。使用了DMA功能的系统,在发起CPU在发起IO操作的时候,首先由CPU初始化操作,然后CPU便可以处理其他的任务了,当IO操作完成的时候,CPU会受到DMA控制器发出的中断信号,由此通知到CPU相关的信息。

包括硬盘驱动控制器,图形卡,网卡,声卡等设备中都使用了DMA技术,而且在一些多核处理器中,各个核心之间的通信也是使用DMA技术实现的。

该技术本质上是将内存之间,内存和设备之间的IO费时操作从CPU中剥离出来,从而将相关的任务交由专门的DMA引擎进行处理。其中DMA功能的一个具体实现就是IO加速技术(I/O Acceleration Technology),DMA功能一般在network-on-chipin-memory computing架构中有比较大的应用。

DMA功能由主要两种形式:

  1. 标准DMA: 该形式中使用了一个DMA控制器,中间由一些寄存器来控制IO操作的状态。

    1. 流程:

      1. 在执行输入/输出/内存之间(memory-memory operation)的操作的时候,首先主处理器使用传输数据大小和工作内存地址初始化DMA控制器。

      2. 然后,CPU将命令外部设备初始化IO操作。

      3. DMA控制器然后为工作内存提供地址和读/写的控制总线。

      4. 传输过程中是外部设备直接和内存之间的数据交换。DMA控制器只是控制这一过程。

    2. 从上述流程中可以看出,DMA控制器主要是通过提供操作地址和控制线(Control line)来控制IO操作的流程,从而解放CPU。

  2. 总线型DMA: 外部设备和CPU均可以获取内存总线的控制权,该方式固然简单粗暴,但是需要提供一些手段来解决包含总线冲突在内的一系列问题。

数据传输过程中的三种模式:

  • Burst mode 每次直接将整块数据依次传输完成。这个过程中设备会长时间占用内存总线从而导致CPU无法访问主存。

  • Cycle stealing mode 这个是每次传输一个字节的数据,然后就释放总线的控制权,并且开始堵塞等待CPU释放总线。这个模式比较适合实时的数据监控。其中这个模式主要是使用了两个信号,BR(Bus Request)BG(Bus Grant)来实现的。

  • Transparent mnode 目前位置系统吞吐量最大的一种模式,DMA控制器只在CPU不使用内存总线的时候占有总线。也就是说抓住一切机会去执行IO操作。优势是该模式下,CPU一刻也不会闲置。劣势是硬件需要额外实现判断CPU是否在使用总线这一功能,这是比较复杂的,该模式也成为“隐式DMA数据传输模式,Hidden DMA data transfer mode”。

注意:对于有多级缓存的CPU,DMA功能可能导致缓存不一致的问题。

2. java.nio包基本结构

2.1 java.nio和java.io的区别

  1. 设计模式

Java IO采用流式设计模式(Stream-oriented),数据是顺序地,一个接一个地读取或者写入。

Java NIO采用缓冲区设计模式(Buffer-oriented),数据可以被写入/输出缓冲区然后批量以块为单位读出/读取。这位非堵塞和批量操作提供了可能。

  1. 堵塞和非堵塞

Java IO是堵塞模式的,当一个线程执行流的IO操作时,该线程会一直堵塞直到操作完成。

Java NIO提供非堵塞操作,一个线程可以发起多个IO任务,同时继续执行其他的任务。

  1. 通道和缓冲区

Java IO 输入输出是在流中进行的,没有缓冲区这个概念,因此对于批量的操作支持比较少。

Java NIO 引入通道和缓冲区概念,通道负责连接,缓冲区负责数据存取。

  1. 选择器

Java NIO引入选择器概念,本质上是一个多路复用器,使得一个选择器可以同时管理多个通道的状态,也就是一个线程管理多个通道。

  1. 性能和拓展性

Java IO由于其堵塞特性,不太适合于高并发,高性能的场景。

Java NIO由于其非堵塞和选择器的特性,更适合高并发,高性能的场景。也就是可以利用到Channel IO的部分特性,来提高程序的性能。

2.2 通道(Channel)

通道(Channel):表示能执行IO操作的实体之间的抽象连接。这些实体包括文件/套接字等。通道的状态只能是open或者closed,同时所有的通道都是可中断的和异步可关闭的。

异步可关闭(Asynchronously closed): 如果一个线程正堵塞在该通道的IO操作中,另一个线程调用了close方法,那么堵塞线程会直接抛出AsynchronousCloseException然后退出。

可中断(Interruptible): 如果一个线程正堵塞在该通道的IO操作中,另一个线程中断了该线程,那么将导致通道被关闭,被堵塞线程抛出ClosedByInterrupException异常。

通道的基本接口为:java.nio.channels.Channel。在该接口之下还有关于具体操作通道子类型有两个:

  1. java.nio.channels.ReadableByteChannel

  2. java.nio.channels.WritableByteChannel

其他类型的通道有三个:

  1. java.nio.channels.InterruptibleChannel

  2. java.nio.channels.channels.AsynchronousChannel

  3. java.nio.channels.NetworkChannel

后续所有的具体通道,都是以上通道特性的组合。

2.2.1 文件通道

和文件进行连接的通道有两种:java.nio.channels.FileChanneljava.nio.channels.AsynchronousFileChannel

二者的区别为是否支持非堵塞操作,FileChannel就是普通的堵塞通道,而AsynchronousFileChannel就是异步通道。其中的API设计也是支持非堵塞操作的。

在结构方面,FileChannelInterruptibleChannelSeekableByteChannel的组合。也即是说支持中断和随机访问功能。

AsynchronousFileChannelAsynchronousChannel的子类,因此支持非堵塞操作。该包中非堵塞操作的基本模式在java.nio.channels.AsynchronousFileChannel的Specification中进行了规范。

2.2.2 网络通道

首先该报中的网络通道均是基于套接字模型的,而套接字有两种类型:

  1. SOCKET_DGRAM 数据报套接字,提供无连接的不可靠的数据传输服务。

  2. SOCK_STREAM提供面向连接,可靠的字节流服务。

网络通道分为以下三种类型:

  1. 数据报通道:java.nio.channels.DatagramChannel,这是面向数据报的通道,也就是进行数据报传输的通道。

  2. 套接字通道:java.nio.channels.SocketChanneljava.nio.channels.ServerSocketChannel

  3. 异步套接字通道:java.nio.channels.AsynchronousSocketChanneljava.nio.channels.AsynchronousServerSocketChannel

首先,DatagramChannel 实现的接口是MulticastChannelByteChannelGatheringByteChannelScatteringByteChannel,并且直接继承于java.nio.channels.spi.AbstractSelectableChannel。因此该通道也是一个可选择的通道,可以被选择器复用。

其次,套接字通道均实现了NetWorkChannel接口,同时继承于java.nio.channels.spi.AbstractSelectableChannel,也就是二者均为可选择通道,可以被选择其进行复用。其中SocketChannel(客户端) 额外实现了ByteChannelGatheringByteChannelScatteringByteChannel这三个接口。ServerSocketChannel表示服务端的通道。

最后,关于异步套接字通道,二者均实现了NetworkChannel接口,但是客户端的AsynchronousSocketChannel实现的异步接口是AsynchronousByteChannel。服务端的AsynchronousServerSocketChannel实现的异步接口是AsynchronousChannel

2.2.3 管道通道

管道通道只有两种类型,源头通道(source)和目标通道(sink)。二者均继承了java.nio.channels.spi.AbstractSelectableChannel类,因此是可选择通道,可以被选择器复用。

源头通道(source):也就是往管道中写入数据的通道为java.nio.channels.Pipe.SinkChannel,该通道实现了GatheringByteChannel接口,支持同时将多个缓冲按照顺序依次写入管道中。

目标通道(sink):也就是从管道中读取数据,java.nio.channels.Pipe.SourceChannel实现了ScatteringByteChannel接口,支持将数据按照顺序从通道中写入到多个主存缓冲中。

2.3 选择器(Selector)

选择器(Selector):可选择通道的多路复用器,或者成为数据选择器。

所对应的类是:java.nio.channels.Pipe.SourceChannel

类声明:public abstract class Selector implements Closeable

2.3.1 什么是Selector?

Selector是可选择通道的多路复用器,也可以成为数据选择器。同时在Channel IO架构中也成为通道控制器。接下来我们从多路复用器的角度来理解选择器。

首先多路复用器的概念是来自于电子电器领域中的复用器。所谓多路复用器即使在多个模拟输入信号或者数字输入信号中选择某个信号,然后将其转发到相应的输出线中的设备。多路复用器可以让多个输入信号共享同一个设备或者资源。同时多路复用器可以认为是multiple-input <-> single-output或者single-input <-> multiple-output模式中的枢纽。

选择器的工作流程:首先选择器管理那些注册了的通道,并且在每个轮询中查询各个通道感兴趣操作的准备情况,如果某个通道的某个操作就绪了,那么该IO操作将在另一个线程中异步执行。

从以上描述中可以看到,其核心操作时轮询操作select操作(也叫选择操作),也就是选择复用的通道,然后执行任务。

接下来我们将首先介绍选择器的创建,可选择通道的注册/注销,选择操作以及选择器的并发问题。

2.3.2 选择器的创建

选择器只能通过java.nio.channels.spi.SelectorProvider对象的openSelector方法进行创建。Selector类中静态方法open可以使用系统默认的SelectorProvider创建选择器。同时也可以使用自定义的 provider 去创建。一般来说,JVM中都有一个系统级的默认Provider。选择器在close方法调用完成之后,均处于打开状态。新创建的选择器三个令牌集合都是空的。

2.3.3 通道的注册/注销

一个可选择通道在选择器中注册之后,将以SelectionKey对象的方式存在。一个选择器维护以下三个令牌集合(key set):

  1. key set 调用方法keys()就可以得到。表示当前所有已经注册的通道令牌。注意该集合不能被直接修改(用户手动修改,特指增删操作)。

  2. selected-key set 调用selectedKeys()方法就可以获取。表示在上一次的选择操作中那些至少就绪了一个操作的令牌集合。其中该集合是ket set的子集。

    1. 向该集合中添加令牌:不能手动直接向其中添加令牌,而是通过调用选择操作来添加令牌。

    2. 删除集合中的令牌:可以直接操作集合。如removeclear等方法。

  3. cancelled-key set 表示key set中那些已经cancelled但是还没有注销(deregister)的令牌。该集合不能直接获取和操作,并且总是key set的子集。

首先是关于通道的注册操作register,该操作会生成一个相应的令牌(SelectionKey)并且添加到key set中。如果一个令牌被取消(可能是通道被关闭或者手动调用cancel方法)那么首先直接将该令牌添加到cancelled-key set中,然后在下一次的选择操作中注销通道并且将从所有选择器的key set中删除。

2.3.4 选择操作

行为:向底层操作系统查询已经注册通道及其相关IO操作的就绪状态,并且更新就绪状态。有两种形式:

就绪令牌添加到selected-key set

select()/select(long)/selectNow() 这些形式中参数指定的是堵塞时间。无参数指的是一直堵塞到查询到就绪令牌为止。

步骤:

  1. key set中属于cancelled-key set的令牌删除,并且将相应的通道注销(deregister),最后将cancelled-key set清空。

  2. 通过查询底层操作系统来更新剩余被注册通道中的就绪令牌,对于某个准备好进行某个IO操作的通道:

    1. 如果该通道之前不在selected-key set中,那么将令牌加入其中,并且将令牌相应的操作掩码修改为当前就绪的IO操作掩码。任何之前在准备集(ready set)中记录的准备信息将被丢弃,也就是系统将不再保留通道之前的状态信息,只关注当前的状态。

    2. 否则。将令牌相应的操作掩码修改为当前就绪的IO操作掩码。之前记录在准备集中的信息都将被保留。最后由底层返回的准备集(ready set)会被位解析(bitwise-disjoin)到令牌的当前操作掩码。

  3. 如果在第二步运行的过程中有任何的令牌被加入到了cancelled-key set中,那他们将在下一个选择操作中的第一步处理。

就绪令牌不添加到selected-key set

select(Comsumer)/select(Consumer,long)/selectNow(Consumer) 这些方法不会将IO操作已经就绪的通道令牌加入selected-key set,而是对于就绪的令牌执行相关的动作。

步骤:

  1. key set中属于cancelled-key set的令牌删除,并且将相应的通道注销(deregister),最后将cancelled-key set清空。

  2. 通过查询底层操作提供来更新甚于通道中IO任务的就绪情况,对于某个就绪的令牌,首先将令牌的操作掩码修改为当前就绪的操作。丢弃所有之前在准备集中记录的信息,然后调用相应的action来消费通道的令牌(Channel key)。

    1. 如果当前就绪令牌的操作掩码包含超过一种操作,,那么action将被调用多次。同时在多次的调用中令牌的操作掩码将被修改成其就绪操作掩码的子集,同时为了防止漏操作和重复操作,在相同的选择操作中,令牌修改的操作掩码将绝不会包含之前已经使用过的操作位。

  3. 如果在第二步运行的过程中有任何的令牌被加入到了cancelled-key set中,那他们将在下一个选择操作中的第一步处理。

2.3.5 异步

本质上是如何在使用该对象时保证线程安全。

  • 选择器本身和其key set是线程安全的。也就是说多个线程同时访问/修改这些对象的时候,程序不会出现不一致和难以预知的问题。

  • selected-key setcancelled-key set不是线程安全的。也就是说多个线程同时直接访问/修改这些对象的时候,可能出现安全性或者不一致问题。需要使用额外的同步机制来保证这对两个对象的并发操作。

  • 选择操作是线程安全的。选择操作(轮询)是通过依次同步选择器本身,selected-key setcancelled-key set来保证线程安全的。因此,内部已经实现了相关的同步机制。

  • 选择器的关闭操作(close)是线程安全的,依次通过选择器本身,selected-key setcancelled-key set来保证线程安全的。这种方式可以保证选择器被安全的关闭。


  • 在选择操作执行过程中,如果修改某个注册令牌的操作掩码是不会影响当前的选择操作,而是会在下一次选择操作中生效。

  • 用户应该格外注意在程序可能并发关闭通道或者取消令牌的情况下,手动实现相关的同步机制和检查机制来保证访问的通道和令牌是有效的。

  • 一个线程中断另外一个堵塞在选择操作中的线程有以下三种情况(特殊设定):

    • 调用了选择器的wakeup方法。

      • 如果当前有线程堵塞在该对象上,那么直接返回。

      • 否则,下一次的选择操作将直接返回。

    • 调用了选择器的close方法。

    • 该线程调用了堵塞线程的interrupt方法,这种情况下该线程被设置位中断状态,并且调用选择器的wakeup方法。

2.4 缓冲(Buffer)

线程不安全!

2.4.1 java.nio.Buffer

实现了Comparable<Buffer>接口,并且是所有Buffer基类,作为基本数据类型的容器(数据缓冲区)。下面介绍Buffer中的基本参数。

  1. capacity 缓冲区的容量,在创建之初指定的,后续无法修改。

  2. position 表示当前读写位置。

  3. limit 从该位置开始的后续位置是不能访问的,大概也表示缓冲区的最大可用区域,但是具体的可用空间要使用remaining()方法获得。

  4. mark 标识缓冲区中的某些位置,方便后续的倒置reset

核心的操作:

  1. flip() 将limit设置为position,position设置为零。这可以理解为切换模式。

  2. rewind() 倒带操作,将position设置为零。模式不改变,但是从头开始读写数据。

  3. remaining()返回当前缓冲区中有效的数据个数。limit-position

  4. get/put 基本上所有的Buffer都支持这两种读写方案。

    1. 相对读写操作:直接在当前位置上进行读写。

    2. 绝对读写操作:直接读写参数索引上的数据。

  5. mark()/reset() 标识位置和复位,在格式化数据解析中比较有用。

  6. slice() 创建当前Buffer的切片视图,其内容只包括当前Buffer中的有效内容。也就是起始位置是原来的position,容量是原来的remaining。

  7. duplicate() 创建当前Buffer的全景视图。和原Buffer完全一样,

缓冲的视图:顾名思义是某个Buffer的视图,他们共享同一片内存区域,只是一个只读的Buffer而已。他们具有独立的状态参数(position/limit/mark),创建视图不涉及内存复制。

每种Buffer都有普通Buffer和直接Buffer两种,同时还有只读Buffer。

如何创建Buffer?

每个具体的Buffer都有相应的静态方法来构造非直接Buffer/只读Buffer。并且在构造的时候指定容量。通过静态方法wrap可以高效初始化Buffer。

Buffer中的大部分方法都支持链式调用。

2.4.2 直接缓冲(Direct Buffer)

直接缓冲是在原生的操作系统内存中,而不是在JVM中。

优势:

  1. 不会被GC线程自动回收,适合那些容量较大,长时间存在缓冲。

  2. 直接操作操作系统本地内存,避免了JVM堆内存和本地内存之间的数据复制。提高了IO操作的效率,特别是在大量数据传输场景下。

劣势:

  1. 使用的过程中需要特别主要内存泄漏问题。

  2. 分配空间和回收空间成本更高。

除了具体Buffer都有对应的直接缓冲之外,还有一个特殊的Buffer:MappedByteBuffer这是ByteBuffer的子类,是一个直接缓冲。表示直接将文件部分区域和Buffer映射起来。

如何创建各种类型的直接缓冲?

  1. ByteBuffer.allocateDirect 即可创建一个之间缓冲。

  2. 其他类型的直接缓冲,则可以在Direct ByteBuffer的基础上的asXXXBuffer来创建各种类型的视图获得。

2.4.3 字节缓冲

java.nio.ByteBuffer

类声明:public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer>

和其他类型的Buffer相比,ByteBuffer首先充当的就是一个桥梁作用,能在ByteBuffer的基础上创建出其他各种基础类型的Buffer视图。也能将各种类型的数据输入其中,也可以以各种类型读取数据。

指的注意的是该Buffer还支持简单的内存对齐功能:alignmentOffsetalignedSlice

有一个直接子类:java.nio.MappedByteBuffer 是文件内存映射,可以将一个直接内存映射到文件的部分区域,也就是锁部分文件。这对大型文件的读取和分析比较有用。通过FileChannel.map方法能创建该Buffer。

2.4.4 字符缓冲

java.nio.CharByte

实现的接口: java.lang.Readablejava.lang.CharSequencejava.lang.Appendable

该Buffer的独特之处在于虽然是字符缓冲区,但是也可以直接读写字符序列(字符数组/String/CharBuffer)等数据。其本质上是一个可追加地可读字符序列。

2.4.5 基本数据类型缓冲

其他基本数据类型的缓冲API基本上就是:

  1. 静态创建方法:allocate/wrap

  2. 相对/绝对的读写方法。

  3. 创建视图方法:slice/duplicate

  4. 字节序:order

  5. 比较:mismatch

  6. 收拢操作:compact

  7. 只读视图:asReadOnlyBuffer

其他基本数据类型Buffer有:

DoubleBuffer/FloatBuffer/ShortBuffer/IntBuffer/LongBuffer

2. 5 编码集(Charset)

核心类:

java.nio.charset.Charset

java.nio.charset.CharsetDecoder

java.nio.charset.CharsetEncoder

辅助类:

java.nio.charset.CoderResult

编码器和解码器的编解码结果。

java.nio.charset.CodingErrorAction

遇到编解码错误时可以采用的操作,比如报告错误/忽略错误/替换错误。

2.5.1 Charset

实现接口:java.lang.Comparable<T>

简单来说,Charset就是16为Unicode码点单元和字节序列的映射对象。该对象中除了一些静态创建方法之外,还有创建编码器和解码器,编码方法,解码方法,以及获取基本信息等方法。

其中比较有用的是静态方法:defaultCharset 返回当前JVM使用的字符集。

可以通过SPI(Service-provider interface)的方法添加字符集。

2.5.2 CharsetDecoder

依附于某个字符集对象,依照该字符集提供解码服务。

2.5.3 CharsetEncoder

依附于某个字符集对象,依照该字符集提供编码服务。