NIO与BIO的区别
NIO是jdk1.4引入的新的IO api,可以替代原来的标准java io(以下简称BIO,阻塞IO),NIO是面向缓冲区,基于通道的非阻塞io操作,BIO是面向流的阻塞性io操作。nio可以做到更加高效的文件读写操作。
BIO | NIO |
面向流 | 面向缓冲区 |
阻塞 | 非阻塞 |
无 | 选择器 |
NIO三大核心概述
1、缓冲区
缓冲区(buffer):主要是用来存储数据。所有缓冲区都是Buffer抽象类的子类。
NIO中的缓冲区主要是用于跟通道进行交互完成数据传输。数据总是从通道读取到缓冲区,然后从缓冲区写入通道中。
2、通道
通道表示打开到 IO 设备(例如:文件、 套接字)的连接。通道必须依赖缓冲区才能传输数据。
3、选择器
非阻塞IO的核心,通过将选择器注册到通道,选择器可以监听通道的事件(读,写,连接,接收),当选择器监听到某个通道准备好时,才会去执行IO操作,能够让一个单独的线程管理多个通道,所以选择器也被称为多路复用器。
缓冲区详解
根据数据类型不同(boolean 除外),主要有以下缓冲区:
-| ByteBuffer
-| CharBuffer
-| ShortBuffer
-| IntBuffer
-| LongBuffer
-| FloatBuffer
-| DoubleBuffer
1、缓冲区基本属性
position:下一个可读写操作位置
limit:最大读写限制
capacity:buffer总容量
mark()和reset():标记当前位置,通过reset将position重置到mark位置
结论: 0 <= mark <= position <= limit <= capacity
2、常用方法和数据存取操作:
@Test
public void test1() {
// 1.分配指定大小的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
System.out.println(buffer.position()); // 0,初始化时,position为零
System.out.println(buffer.limit()); // 1024,初始化时,limit跟容量保持一致
System.out.println(buffer.capacity()); // 1024,初始化的容量
// 2.通过put()往缓冲区存放数据
String str = "abc";
buffer.put(str.getBytes()); // 往缓冲区存放长度为3的字符串
System.out.println(buffer.position()); // 3,position保持跟缓冲区数据长度增长一致
System.out.println(buffer.limit()); // 1024,最大存放限制跟容量一致
System.out.println(buffer.capacity()); // 1024
// 3.切换到读取模式
buffer.flip();
System.out.println(buffer.position()); // 0,将position重置到开始的位置
System.out.println(buffer.limit()); // 3, 切换到读取模式时,将limit设定成缓冲区数据的长度
System.out.println(buffer.capacity()); // 1024
// 4.读取缓冲区数据到byte数组中
byte[] dest = new byte[buffer.limit()]; // 切换到读取模式时,limit切换成缓冲区已经插入数据的长度
buffer.get(dest);
System.out.println(new String(dest, 0, dest.length));
System.out.println(buffer.position()); // 3,读取完将position保持数据长度一致
System.out.println(buffer.limit()); // 3,读取完将limit保持数据长度一致
System.out.println(buffer.capacity()); // 1024
// 5.将缓冲区设置为可重复读
buffer.rewind();
System.out.println(buffer.position()); // 0,重置
System.out.println(buffer.limit()); // 3,设定为已插入数据的长度
System.out.println(buffer.capacity()); // 1024
// 6.清空缓冲区
buffer.clear();
System.out.println(buffer.position()); // 0,重置到初始化状态
System.out.println(buffer.limit()); // 1024,重置到初始化状态
System.out.println(buffer.capacity()); // 1024,重置到初始化状态
System.out.println((char) buffer.get(0)); // a: 清空只是将重置缓冲区的属性到初始状态,并未真正删除数据
}
直接缓冲区和非直接缓冲区
非直接缓冲区主要是在JVM堆内存,直接缓冲区是直接建立在物理内存中,能够提升数据传输效率。
1)通过ByteBuffer.allocateDirect(int)直接分配到物理内存。
2)FileChannel的map()方法返回MappedByteBuffer,
3)改缓冲区直接分配在内存中。可以通过isDirect()方法判断缓冲区是否分配到内存中。
通道详解
1、获取通道的方式,直接代码演示
创建通道的方式
1)通过支持通道的对象
-| FileInputStream
-| FileOutputStream
-| RandomAccessFile
-| Socket
-| ServerSocket
-| DatagramSocket
2)通道类的open方法
-| FileChannel
-| SocketChannel
-| ServerSocketChannel
-| DatagramChannel
3)通过Files的静态方法newByteChannel()获取字节通道
-| Files
@Test
public void test3() throws IOException {
// 1、通过支持通道的对象,调用getChannel()获取
FileInputStream fis = new FileInputStream("1.txt");
FileChannel channel1 = fis.getChannel();
FileOutputStream fos = new FileOutputStream("2.txt");
FileChannel channel2 = fos.getChannel();
RandomAccessFile randomAccessFile = new RandomAccessFile("a.txt", "rw");
FileChannel channel3 = randomAccessFile.getChannel();
Socket socket = new Socket();
SocketChannel channel4 = socket.getChannel();
ServerSocket serverSocket = new ServerSocket();
ServerSocketChannel channel5 = serverSocket.getChannel();
DatagramSocket datagramSocket = new DatagramSocket();
DatagramChannel channel6 = datagramSocket.getChannel();
// 2、通道类的open方法
FileChannel channel7 = FileChannel.open(Paths.get("1.txt"), StandardOpenOption.READ);
FileChannel channel8 = FileChannel.open(Paths.get("1.txt"), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW);
SocketChannel channel9 = SocketChannel.open();
ServerSocketChannel channel10 = ServerSocketChannel.open();
DatagramChannel channel11 = DatagramChannel.open();
// 3、通过Files的静态方法newByteChannel()获取
SeekableByteChannel channel12 = Files.newByteChannel(Paths.get("1.txt"), StandardOpenOption.WRITE);
}
2、 通道配合缓冲区完成文件复制
@Test
public void test4() {
FileChannel inChannel = null;
FileChannel outChannel = null;
try {
// 1、创建直接缓冲区(在堆外,物理内存)
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
// 2、创建通道
inChannel = FileChannel.open(Paths.get("D:/file.txt"), StandardOpenOption.READ);
// 可读写,没有该文件就创建,有则报错,也已直接用StandardOpenOption.NEW没有就不创建,不会报错
outChannel = FileChannel.open(Paths.get("D:/dest.txt"), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW);
while (inChannel.read(buffer) != -1) {
buffer.flip(); // 切换缓冲区为读取模式
outChannel.write(buffer); // 将缓冲区数据写入通道
buffer.clear(); // 清空缓冲区
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (inChannel != null) {
try {
inChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outChannel != null) {
try {
outChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
选择器详解
阻塞:传统的IO流都是阻塞的,当一个线程调用read()或write()方法读取或写入数据时,该线程会进入阻塞状态,每个读写请求都要分配一个线程去处理,当处理大量客户请求时,服务器性能就会急剧下降。
非阻塞:nio是非阻塞模式,当线程从某通道读写数据时,若没有数据可用时,改线程可以去处理其他通道的io操作,所以服务可以使用一个或少量的线程来管理服务器的所有客户端。NIO的非阻塞io主要是选择器来实现的。而且主要用于网络io。
选择器:多路复用器,selector可以监控多个通道的io状况,能够让一个单独线程管理多个通道。selector是非阻塞io的核心。
SelectableChannel是可以使用选择器的通道抽象父类,主要子类是:
-| SocketChannel
-| ServerSocketChannel
-| DatagramChannel
在选择器注册到通道时候可以配置具体的监听事件,主要有:
读 : SelectionKey.OP_READ
写 : SelectionKey.OP_WRITE
连接 : SelectionKey.OP_CONNECT
接收 : SelectionKey.OP_ACCEPT
只有选择器上具体的监听事件就绪时,线程才会去处理该通道上操作,而不会一直阻塞等待。