目录
- 3.1 Java NIO基本介绍
- 3.2 BIO 和 NIO的比较
- 3.3 NIO三大核心 selector、channel、buffer之间的关系
- 3.4 缓冲区(Buffer)
- 3.4.1 基本介绍
- 3.4.2 Buffer类及其子类
- 3.5 通道
- 3.5.0 channel基本介绍
- 3.5.1 FileChannel 类
- 3.5.2 应用实例1 - 本地文件写数据
- 3.5.3 应用实例2 - 本地文件读数据
- 3.5.4 应用实例3 - 使用一个buffer完成文件的读取写入
- 3.5.5 应用实例4 - 拷贝文件 transferFrom方法
- 3.5.6 Buffer和Channel的注意事项和细节
- 3.6 Selector(选择器)
- 3.6.1 Selector 基本介绍
- 3.6.2 Selector的特点
- 3.6.3 Selector的常用方法
3.1 Java NIO基本介绍
3.2 BIO 和 NIO的比较
3.3 NIO三大核心 selector、channel、buffer之间的关系
一张图描述 NIO 的 Selector 、 Channel 和 Buffer 的关系:
3.4 缓冲区(Buffer)
3.4.1 基本介绍
Buffer(缓冲区): 缓冲区本质上是一个可以读写数据的内存块,可以理解成一个容器,该容器提供了一组方法,可以更轻松的使用内存块。缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变换。
Channel提供从文件、网络读取数据的渠道,但是读取或者写入的数据都必须经过Buffer
3.4.2 Buffer类及其子类
3.5 通道
3.5.0 channel基本介绍
-
NIO的通道类似于流,但又有些区别
- 通道可以同时进行读写,而流只能读或者写
通道可以从缓冲读数据,也可以写数据到缓冲
-
Channel 是 NIO中的一个接口 public interface Channel extends Closeable 、
-
常用的channel类有:FileChannel,DatagramChannel,ServerSocketChannel,SocketChannel。【FileChannel用于文件数据的读写,DatagramChannel用于UDP数据的读写,ServerScoketChannel SocketChannel用于TCP数据的读写】
3.5.1 FileChannel 类
FileChannel 主要是用来对本地文件进行IO操作,常见的方法有:
3.5.2 应用实例1 - 本地文件写数据
【实例要求】
1、使用前面学习的ByteBuffer,FileChannel 将“hello,尚硅谷” 写入到file01.txt中
2、文件不存在则创建文件
public class NIOFileChannel01 {
public static void main(String[] args) throws IOException {
String str= "hello,尚硅谷";
FileOutputStream fileOutputStream = new FileOutputStream("E://file01.txt");
// channel的真是类型是 FileChannelImpl
FileChannel channel = fileOutputStream.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
// 将str扔到byteBuffer中
byteBuffer.put(str.getBytes());
// 对 byteBuffer 进行反转
byteBuffer.flip();
// 对于channel 来说是将 buffer中的数据写入到channel中
channel.write(byteBuffer);
// 关闭流
fileOutputStream.close();
}
}
3.5.3 应用实例2 - 本地文件读数据
public class NIOFileChannel02 {
public static void main(String[] args) throws IOException {
File file = new File("E://file01.txt");
FileInputStream fileInputStream = new FileInputStream(file);
// channel的真是类型是 FileChannelImpl
FileChannel channel = fileInputStream.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());
channel.read(byteBuffer);
System.out.println(new String(byteBuffer.array()));
// 关闭流
fileInputStream.close();
}
}
3.5.4 应用实例3 - 使用一个buffer完成文件的读取写入
public class NIOFileChannel03 {
public static void main(String[] args) throws IOException {
FileInputStream fileInputStream = new FileInputStream("1.txt");
FileChannel fileChannel01 = fileInputStream.getChannel();
FileOutputStream fileOutputStream = new FileOutputStream("2.txt");
FileChannel fileChannel02 = fileOutputStream.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(512);
while (true) { //循环读取
//这里有一个重要的操作,一定不要忘了
/*
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
*/
byteBuffer.clear(); //清空buffer
int read = fileChannel01.read(byteBuffer);
System.out.println("read =" + read);
if(read == -1) { //表示读完
break;
}
//将buffer 中的数据写入到 fileChannel02 -- 2.txt
byteBuffer.flip();
fileChannel02.write(byteBuffer);
}
//关闭相关的流
fileInputStream.close();
fileOutputStream.close();
}
}
3.5.5 应用实例4 - 拷贝文件 transferFrom方法
public class NIOFileChannel04 {
public static void main(String[] args) throws Exception {
//创建相关流
FileInputStream fileInputStream = new FileInputStream("d:\\a.jpg");
FileOutputStream fileOutputStream = new FileOutputStream("d:\\a2.jpg");
//获取各个流对应的filechannel
FileChannel sourceCh = fileInputStream.getChannel();
FileChannel destCh = fileOutputStream.getChannel();
//使用transferForm完成拷贝
destCh.transferFrom(sourceCh,0,sourceCh.size());
//关闭相关通道和流
sourceCh.close();
destCh.close();
fileInputStream.close();
fileOutputStream.close();
}
}
3.5.6 Buffer和Channel的注意事项和细节
1)ByteBuffer支持类型化的put和get,put放入的是什么数据类型,get就需要使用相应的数据类型来取出,否则就可能有 BufferUnderflowException 异常
2)可以将一个普通Buffer转换成只读Buffer
//得到一个只读的Buffer
ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();
3)NIO还提供了MapperedByteBuffer,可以让文件直接在内存中(堆外的内存)进行修改,而如何同步到文件由NIO来完成
4)前面我们讲的读写操作,都是通过一个Buffer完成的,NIO还支持通过多个Buffer(即Buffer数组)来完成读写操作,即Scattering 和 Gathering 【分散和聚合】
3.6 Selector(选择器)
3.6.1 Selector 基本介绍
1)Java的NIO,用的非阻塞的IO方式。可以用一个线程来处理多个客户端的连接,此时就会用到Selector选择器
2)多个Channel以事件的方式注册到同一个Selector,Selector可以检测到多个注册的channel上是否有事件发生,
如如果有事件发生,便去获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道。
3)只有在连接真正有读写事件发生时,才会进行读写,大大减少了系统的开销。并且不会为每一个连接都创建一个线程,不用去维护多个线程
4)避免了多线程之间的上下文切换导致的开销