NIO就这些知识吗?

时间:2022-09-05 15:21:12
Java关于NIO的整理
Java NIO(Non-Blocking IO)
与IO有同样的作用和目的,但是使用的方式完全不同,NIO是支持面向缓冲
区的、基于通道的IO操作。相对IO来时更高效


与IO的主要区别
IO是面向流:单向的;NIO是面向缓冲区:通道可以单向也可以双向
IO是阻塞IO;NIO是非阻塞IO
NIO支持选择器 Selectors

NIO中也有几个类、接口的使用需要注意

Path、Paths
Path接口,代表一个平台无关的平台路径,描述了目录结构中文件的位置
,可以把它看做File类的升级版本
Path path = Paths.get("我的文件", "伊修" ,"笔记.txt");
这样可以填写多个目录,也可以只填写一个,比较灵活
这样就能得到一个文件路径的对象了,就可以对其进行一系列的操作了
startsWith(String path) 判断是否以path路径开始的
endsWith(String path)
getParent() 返回这个对象的父目录的Path对象,根据path对象中指定的,
getRoot() 返回调用Path对象的根路径, 只有绝对路径才有根;好比C:/ D:/
getFileName() 返回path对象最子层的名称
getName(int index) 返回指定位置的名称
getNameCount() 返回一共有几层
toAbsolutePath() 得到绝对路径的Path对象
reslove(Path path) 可以组合两个路径;不一定存在
toFile() 把Path对象转成File对象

Files工具类
Files工具类中有太多的方法了,分为很多类型功能的,比较繁琐,所以有
该方面需要的时候,一定要能想到来这里查找
用于操作文件或目录的方法
Path createDiretory(Path path)
Path createDirectories(Path dir) 这两个类似于mkdir()和mkdirs()
Path createFile(Path path) 创建文件,指定目录必须存在;文件如果存在的
话,会报异常
Path createLink(Path link,Path existing)
这个分为硬链接和软链接;硬链接删除了源文件依旧可以打开,软链接不
可以
void delete(Path path) 删除指定文件,不存在会报异常
void deleteIfExists(Path path) 这个如果存在才会删除
Path copy(Path src,Path dest,CopyOption)
复制文件到指定路径,可以指定复制的方式,类似如下
StandardCopyOption.REPLACE_EXISTING
Path move(Path src,Path dest)
移动,这个也可以指定方式
long size(Path path) 返回path指定文件的大小

用于判断的方法
exists(Path path)
notExists(Path path)
isDirectory(Path path)
isRegularFile(Path path) 判断是否是文件
isHidden(Path path) 是否是隐藏文件
isReadable(Path path) 是否可读
isWritable(Path path) 是否可写

用来操作内容的方法
SeekableByteChannel newByteChannel(Path path) 获取与指定文件的连接
DirectoryStream<Path> new DirectoryStream(Path path) 打开Path指定的
目录,可以使用Iterator迭代,获取目录下的各个Path对象
InputStream newInputStream(Path path) 获取InputStream对象
OutputStream newOutputStream(Path path) 获取OutputStream对象
List<String> readAllLines(Path path,Charset charset) 读取所有的内容,并
把每一行都存到一个List中
byte[] readAllBytes(Path path)

关于缓冲区(Buffer)和通道(Channel)
Java NIO系统的核心在于:通道和缓冲区

通道表示IO源 到 IO设备的连接(比如文件或者套接字Socket)
使用NIO时,需要获取 用于连接IO设备的通道 以及 用于容纳数据的缓冲区
;然后对缓冲区进行操作,对数据进行处理;通道负责传输,缓冲区负责
存储数据

IO中,输入流中只能读取数据,输出流中只能写入数据,所以是阻塞的;
意味着一个线程调用read()或write()时,该线程被阻塞,直到有一些数据被
读取,或数据完全写入。该线程在此期间不能再干任何事情了。

缓冲区就好比是“地铁车厢”,通道就好比“地铁线路”,“地铁线路”
负责运输数据,而缓冲区负责存储(承载)数据,即装“乘客”。地铁车
厢在通道中来回走,在任何一端都可以上人和下车。

缓冲区(Buffer)
是一个用于特定基本数据类型(除boolean型外)的容器,底层使用数组存储

由 java.nio 包定义的。
Java NIO 中的 Buffer 主要用于与 NIO 通道(Channel)进行交互,数据是从
通道读入缓冲区,从缓冲区写入通道中的。
1、缓冲区类型
所有缓冲区都是 Buffer 抽象类的子类。根据数据类型不同(boolean 除外)
,有以下 Buffer 常用子类:
ByteBuffer  <--  MappedByteBuffer
IntBuffer
LongBuffer
FloatBuffer
DoubleBuffer
CharBuffer
上述缓冲区的管理方式几乎一致,通过 allocate() 获取缓冲区。
2、缓冲区存取数据的两个核心方法:
 (1)put() : 存入数据到缓冲区中
 (2)get() : 获取缓冲区中的数据
3、缓冲区中的四个核心属性:
容量 (capacity) :表示 Buffer 最大数据容量,一旦声明后,不能更改。
通过Buffer中的capacity()获取。缓冲区capacity不能为负。
限制 (limit):第一个不应该读取或写入的数据的索引,即位于 limit 后的
数据不可读写。通过Buffer中的limit()获取。缓冲区的limit不能为负,并且
不能大于其capacity。
位置 (position):当前要读取或写入数据的索引。通过Buffer中的
position()获取。缓冲区的position不能为负,并且不能大于其limit。
标记 (mark):标记是一个索引,通过 Buffer 中的 mark() 方法将mark标
记为当前position位置。 之后可以通过调用 reset() 方法将 position恢复到
标记的mark处。
4、其他的方法
flip()  将limit设置为当前position将position设为0,mark设置为-1
rewind()  将position设为为0,mark设为-1。可重复读
clear()  将limit设为capacity,将position设为0,并将mark设为-1。数据没有
清空
hasRemaining()  判断缓冲区中是否还有元素
remaining()  返回position和limit之间的元素个数
array()  返回底层的xxx数组(xxx类型)
5、直接缓冲区与非直接缓冲区
 非直接缓冲区:通过xxxBuffer类的静态方法allocate(int) 分配缓冲区,将
缓冲区建立在 JVM 的内存中
 直接缓冲区:可以提高效率
(1)通过xxxBuffer类的静态方法allocateDirect(int)分配直接缓冲区,将缓
冲区建立在物理内存中。
(2)还可以通过 FileChannel对象的map() 方法 将文件区域直接映射到内
存中来创建,该方法返回ByteBuffer的子类:MappedByteBuffer 。

字节缓冲区要么是直接的,要么是非直接的。如果为直接字节缓冲区,则
Java 虚拟机会尽最大努力直接在此缓冲区上执行本机 I/O 操作。也就是说
,在每次调用基础操作系统的一个本机 I/O 操作之前(或之后),虚拟机
都会尽量避免将缓冲区的内容复制到中间缓冲区中(或从中间缓冲区中复
制内容)。
直接字节缓冲区可以通过调用ByteBuffer的 allocateDirect() 工厂方法来创
建。此方法返回的缓冲区进行分配和取消分配所需成本通常高于非直接缓
冲区。直接缓冲区的内容可以驻留在常规的垃圾回收堆之外,因此,它们
对应用程序的内存需求量造成的影响可能并不明显。所以,建议将直接缓
冲区主要分配给那些易受基础系统的本机 I/O 操作影响的大型、持久的缓
冲区。一般情况下,最好仅在直接缓冲区能在程序性能方面带来明显好处
时分配它们。
Java 平台的实现有助于通过 JNI 从本机代码创建直接字节缓冲区。如果以
上这些缓冲区中的某个缓冲区实例指的是不可访问的内存区域,则试图访
问该区域不会更改该缓冲区的内容,并且将会在访问期间或稍后的某个时
间导致抛出不确定的异常。
字节缓冲区是直接缓冲区还是非直接缓冲区可通过调用其 isDirect() 方法来
确定。提供此方法是为了能够在性能关键型代码中执行显式缓冲区管理。

通道(Channel)
由 java.nio.channels 包定义的。
Channel 表示 IO 源与目标节点打开的连接。
Channel 类似于传统的“流”。只不过 Channel 本身不能直接存储数据,
Channel 只能与 Buffer 进行交互。

Java 为 Channel 接口提供的最主要实现类如下:
FileChannel:用于读取、写入、映射和操作文件的通道
SocketChannel:通过 TCP 读写网络中的数据
ServerSocketChannel:可以监听新进来的 TCP 连接,对每一个新进来    
的连接都会创建一个SocketChannel
DatagramChannel:通过 UDP 读写网络中的数据通道

1、获取通道的几种方式
(1)获取通道的一种方式:对支持通道的对象调用 getChannel() 方法。
(2)获取通道的其他方式:(JDK 1.7新增)
 通过XxxChannel的静态方法 open() 打开并返回指定的XxxChannel。
(3)使用 Files 工具类的静态方法(JDK1.7新增)
 newByteChannel() 获取字节通道
2、FileChannel的常用方法
int read(ByteBuffer dst) 从Channel中读入数据到ByteBuffer
int write(ByteBuffer src)  将ByteBuffer中的数据写入到Channel
MappedByteBuffer map(MapMode mode, long position, long size)
将Channel对应的部分或全部数据映射到ByteBuffer
long position() 返回此通道的文件位置
FileChannel position(long p) 设置此通道的文件位置
long size() 返回此通道的文件的当前大小

MappedByteBuffer fbb = from.map(MapMode.READ_ONLY,0,from.size());
MappedByteBuffer tbb = to.map(MapMode.READ_WRITE,0,from.size());
tbb.put(fbb)  可以直接这样

转换通道
to.transferFrom(from, 0 , from.size())
from.transferTo(0 , from.size() ,to)

3、管道
Java NIO管道是2个线程之间的单项数据连接。Pipe有一个source通道和一
个sink通道。数据会被写到sink通道,从source通道读取。

关于字符编码Charset
在java.nio.charset包*提供了Charset。向ByteBuffer中存放数据时需要考
虑字符集的编码方式,从中读取时需要考虑字符集的解码。要读和写文本
需要分别使用CharsetDecoder(解码器)和CharsetEncoder(编码器)。

Charset charset = Charset.forName("编码方式");
得到一个CharSet实例后,我们需要创建一个编码器和一个解码器,使用下
面方法进行创建:
       CharsetDecoder decoder = cs.newDecoder();
       CharsetEncoder encoder = cs.newEncoder();
接着我们把ByteBuffer传递给decoder进行编码,返回一个CharBuffer:
       CharBuffer cb = decoder.decode(inputData);
然后我们可以使用encoder进行解码返回一个ByteBuffer:
       ByteBuffer outputData = encoder.encode(cb);