一、 java NIO概述
1.1 NIO的基本作用
- 替代java io的一个操作
- 面向缓冲区也可以基于通道操作
- 更高效的进行文件的读写操作
1.2 阻塞 IO
读或者写数据的时候,会阻塞直到数据能够正常的读或者写入在传统的方法中,服务器为客户端建立一个线程,这种模式如果线程增加,大量线程会造成服务器的开销,为了解决这种问题,采用了线程池,并设置线程池的上限,但超出线程池的上限的线程就会访问不上
1.3 非阻塞 IO(NIO)
非阻塞指的是 IO 事件本身不阻塞,是获取 IO 事件的 select()方法是需要阻塞等待的,区别是阻塞的 IO 会阻塞在 IO 操作上, NIO 阻塞在事件获取上,没有事件就没有 IO,select()阻塞的时候 IO 还没有发生,何谈 IO 的阻塞。本质是延迟io操作,真正发生io的时候才执行,而不是发生的时候再阻塞。用Selector负责去监听多个通道,注册感兴趣的特定 I/O 事件,之后系统进行通知.
当有读或写等任何注册的事件发生时,可以从 Selector 中获得相应的 SelectionKey,同时从 SelectionKey 中可以找到发生的事件和该事件所发生的具体的 SelectableChannel,以获得客户端发送过来的数据。
IO | NIO |
---|---|
面向流 | 面向缓冲区 |
阻塞IO | 非阻塞IO |
无 | 选择器 |
1.4 NIO 概述
java NIO 由以下几个核心部分组成,还有其他组件(pipe、filelock)
- Channel(双向的,既可以用来进行读操作,又可以用来进行写操作)
主要有如下:
FileChannel(IO)、DatagramChannel(UDP )、
SocketChannel (TCP中Server )和 ServerSocketChannel(TCP中Client) - Buffer
主要有如下:
ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer,
IntBuffer, LongBuffer, ShortBuffer - Selector(处理多个 Channel)
二、Channel
- 可以进行读取和写入,或者进行读写操作,全双工
- 操作的数据源可以多种,比如文件、网络socket
- Channel 用于在字节缓冲区和位于通道另一侧的实体(通常是一个文件或套接字)之间有效地传输数据
- 从通道读取数据到缓冲区,从缓冲区写入数据到通道
Java NIO 的通道类似流,但又有些不同:
- 既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的。
- 通道可以异步地读写。
- 通道中的数据总是要先读到一个 Buffer,或者总是要从一个 Buffer 中写入
主要是接口实现,不同操作系统不同接口实现,通过代码也可以看到其代码为接口
public interface Channel extends Closeable {
/**
* Tells whether or not this channel is open.
*
* @return {@code true} if, and only if, this channel is open
*/
public boolean isOpen();
/**
* Closes this channel.
*
* <p> After a channel is closed, any further attempt to invoke I/O
* operations upon it will cause a {@link ClosedChannelException} to be
* thrown.
*
* <p> If this channel is already closed then invoking this method has no
* effect.
*
* <p> This method may be invoked at any time. If some other thread has
* already invoked it, however, then another invocation will block until
* the first invocation is complete, after which it will return without
* effect. </p>
*
* @throws IOException If an I/O error occurs
*/
public void close() throws IOException;
}
实现接口主要有以下几个常用类:
- FileChannel 从文件中读写数据。
- DatagramChannel 能通过 UDP 读写网络中的数据。
- SocketChannel 能通过 TCP 读写网络中的数据。
- ServerSocketChannel 可以监听新进来的 TCP 连接,像 Web 服务器那样。对每一个新进来的连接都会创建一个 SocketChannel
2.1 FileChannel
主要是文件IO也是最常用的一个类,以下是FileChannel类的核心方法和主要的作用:
Buffer 通常的操作
- 将数据写入缓冲区
- 调用 buffer.flip() 反转读写模式
- 从缓冲区读取数据
- 调用 buffer.clear() 或 buffer.compact() 清除缓冲区内容
部分步骤代码展示:
先打开文件,无法直接打开一个
FileChannel,需要通过使用一个 InputStream、OutputStream 或RandomAccessFile 来获取一个 FileChannel
//创建FileChannel RandomAccessFile aFile = new RandomAccessFile("b://1.txt","rw"); FileChannel channel = aFile.getChannel();
创建Buffer
ByteBuffer buf = ByteBuffer.allocate(1024);
从 FileChannel 读取数据
read()方法返回的 int 值表示了有多少字节被读到了 Buffer 中。如果返回-1,表示到了文件末尾
int bytesRead = channel.read(buf);
FileChannel.write()方法向 FileChannel 写数据,该方法的参数是一个 Buffer。在 while 循环中调用的。因为无法保证 write()方法一次能向 FileChannel 写入多少字节,因此需要重复调用 write()方法,直到 Buffer 中已经没有尚未写入通道的字节。
读数据主要的代码思路步骤是:
- 创建一个FileChannel
- 创建一个数据缓冲区
- 读取数据到缓冲区中
- 判断数据是否有,如果有,则取出,判断的依据是获取到的数据是否为-1,取出的数据要先反转读写操作,之后如果数据缓冲区还有,则取出,最后清除数据缓冲区后在判断是否缓冲区还有数据。关闭FileChannel
完整代码展示
public class FileChannelDemo1 {
//FileChannel读取数据到buffer中
public static void main(String[] args) throws Exception {
//创建FileChannel
RandomAccessFile aFile = new RandomAccessFile("d://opencoder.txt","rw");
FileChannel channel = aFile.getChannel();
//创建Buffer
ByteBuffer buf = ByteBuffer.allocate(1024);
//读取数据到buffer中
int bytesRead = channel.read(buf);
while(bytesRead != -1) {
System.out.println("读取了:"+bytesRead);
buf.flip();
while(buf.hasRemaining()) {
System.out.println((char)buf.get());
}
buf.clear();
bytesRead = channel.read(buf);
}
aFile.close();
System.out.println("结束了");
}
}
写数据主要代码思路是:
- 创建一个FileChannel
- 创建一个数据缓冲区
- 创建要写入的数据对象,以及清空以下缓冲区(防止出错)
- 要写入的数据写入到缓冲区中
- 缓冲区读写反转
- 判断缓冲区是否有数据,将数据一个一个写入到FileChannel
- 关闭FileChannel
完整代码展示:
//FileChanne写操作
public class FileChannelDemo2 {
public static void main(String[] args) throws Exception {
// 打开FileChannel
RandomAccessFile aFile = new RandomAccessFile("d://opencoder.txt","rw");
FileChannel channel = aFile.getChannel();
//创建buffer对象
ByteBuffer buffer = ByteBuffer.allocate(1024);
String newData = "manongyanjiuseng";
buffer.clear();
//写入内容
buffer.put(newData.getBytes());
buffer.flip();
//FileChannel完成最终实现
while (buffer.hasRemaining()) {
channel.write(buffer);
}
//关闭
channel.close();
}
}
2.2 其他常用方法
position方法
需要在 FileChannel 的某个特定位置进行数据的读/写操作。可以通过调用position()方法获取 FileChannel 的当前位置。也可以通过调用 position(long pos)方法设置 FileChannel 的当前位置
注意这样设置会造成两个后果:
位置如果设置在文件结束符之后,读取数据的文件结束标志返回-1,而且写入数据的时候前面会有间隙,导致文件空洞long pos = channel.position(); channel.position(pos +404);
size 方法
返回该实例所关联文件的大小
truncate 方法
截取一个文件。截取文件时,文件将中指定长度,后面的部分将被删除,而且截取的数据长度是以字节截取
force 方法
尚未写入磁盘的数据强制写到磁盘上
transferTo 和 transferFrom 方法
进行通道之间的传输注意一个To与From的区别,一个主动一个被动。
以下是transferFrom 的完整代码:
//通道之间数据传输 public class FileChannelDemo3 { //transferFrom() public static void main(String[] args) throws Exception { // 创建两个fileChannel RandomAccessFile aFile = new RandomAccessFile("d://opencoder.txt","rw"); FileChannel fromChannel = aFile.getChannel(); RandomAccessFile bFile = new RandomAccessFile("d://opencoder2.txt","rw"); FileChannel toChannel = bFile.getChannel(); //fromChannel 传输到 toChannel long position = 0; long size = fromChannel.size(); toChannel.transferFrom(fromChannel,position,size); aFile.close(); bFile.close(); System.out.println("over!"); } }
以下是transferTo的完整代码:
//通道之间数据传输 public class FileChannelDemo4 { //transferTo() public static void main(String[] args) throws Exception { // 创建两个fileChannel RandomAccessFile aFile = new RandomAccessFile("d://opencoder1.txt","rw"); FileChannel fromChannel = aFile.getChannel(); RandomAccessFile bFile = new RandomAccessFile("d://opencoder2.txt","rw"); FileChannel toChannel = bFile.getChannel(); //fromChannel 传输到 toChannel long position = 0; long size = fromChannel.size(); fromChannel.transferTo(0,size,toChannel); aFile.close(); bFile.close(); System.out.println("over!"); } }
总结
今天主要给大家介绍的是NIO的基本的概念以及Channel中常用的FileChannel的基本的用法,算是对Channel有一个简单的介绍。下一篇文章我们将详细的为大家介绍其他的常用Channel。