IO是同步阻塞的。
NIO是同步非阻塞。
NIO2(AIO)异步非阻塞。
NIO三个主要组成部分:
Buffer(缓冲区)、Channel(通道)、Selector(选择器)
/** * 缓冲区 * Buffer是一个对象,它对基本数据类型的数组进行了封装。 * Buffer数组是NIO读写数据的中转池。 */
0 <= mark <= position(容量位置) <= limit(限制) <= capacity(容量)
往缓冲区存数据,是存在position和limit之间
常用方法:
public final int capacity()
获取当前容量
public final int limit()
获取此缓冲区的限制
public final Buffer limit(int newLimit)
设置此缓冲区的限制
public final int position()
获取容量位置
public final Buffer position(int newPosition)
设置容量位置
public final Buffer mark()
在某一个位置调用mark方法,会记录当前的position位置
public final Buffer reset()
调用reset方法,将此缓冲区重置到以前的标记,简单点说就是mark相当于设置一个新的标记起点,然后添加元素后,标记点会后移,这个时候调用reset方法,就会回到mark设置的标记点
public final Buffer clear()
还原缓冲区,将position设置成0,将limit设置成capacity,如果有mark标记会丢弃
public final Buffer flip()
将limit设置成当前position,将position容量位置设置成0,丢弃mark标记
我们从文件读取数据后,需要filp方法,然后再写数据到文件中。再次读的时候我们需要clear还原一下缓冲区
/** * ByteBuffer类 继承Buffer类 * 内部封装了一个byte[]数组,并且可以通过一些方法对这个数组进行操作 * 创建ByteBuffer缓冲区的几种方法 * public static ByteBuffer allocate(int capacity) * 在堆中创建缓冲区 * public static ByteBuffer allocateDirect(int capacity) * 在系统内存中创建缓冲区 * public static ByteBuffer wrap(byte[] array) * public static ByteBuffer wrap(byte[] array, * int offset, int length) * 通过数组创建缓冲区 * * 参数int capacity:初始容量 * * 在堆中创建的缓冲区称为:间接缓冲区 * 在系统内存创建的缓冲区称为:直接缓冲区 * 间接缓冲区的创建和销毁效率要高于直接缓冲区 * 间接缓冲区的工作效率要低于直接缓冲区 */
public class TestByteBuffer {
/**
* java.nio.ByteBuffer
*
* public final byte[] array()
* 把ByteBuffer对象转换成byte[]数组
*
* 添加方法
* public abstract ByteBuffer put(byte b);
*
* ByteBuffer put(byte[] src)
* 相对大容量 put方法 (可选操作) 。
* ByteBuffer put(byte[] src, int offset, int length)
* 相对大容量 put方法 (可选操作) 。
* ByteBuffer put(ByteBuffer src)
* 相对大容量 put方法 (可选操作) 。
* abstract ByteBuffer put(int index, byte b)
* 绝对 put方法 (可选操作) 。
*/
/**
*
*
*/
public static void main(String[] args) {
//初始化 10个容量
ByteBuffer bb = ByteBuffer.allocate(10);
bb.put((byte)10);
bb.put((byte)20);
bb.put((byte)30);
//打印
System.out.println(Arrays.toString(bb.array()));
//获取数组的容量
int capacity = bb.capacity();
//初始化10个容量。中途没有改变,所以还是10
System.out.println(capacity);
//设置ByteBuffer缓冲区的限制
bb.limit(5);
//初始化10个容量。设置成5,限制打印出来所以是5
System.out.println(bb.capacity());
//容量虽然还是10,但是限制是5,所以再次添加至6个元素会报异常
//bb.put((byte)10);
//bb.put((byte)20);
//bb.put((byte)30);
//打印
//System.out.println(Arrays.toString(bb.array()));
//获取容量
System.out.println(bb.position());
//设置容量为5
bb.position(5);
//这时限制是5,容量时5,所以添加不了新的数据了
//bb.put((byte) 6);
System.out.println(bb.position());//5
bb.mark();
System.out.println(bb.position());//5
bb.reset();
System.out.println(bb.position());//5
bb.limit(8);
bb.put((byte)15 );
System.out.println(bb.position());//6
//[10, 20, 30, 0, 0, 15, 0, 0, 0, 0]
System.out.println(Arrays.toString(bb.array()));
//重置一下,会发现position又会变成5
bb.reset();
//[10, 20, 30, 0, 0, 15, 0, 0, 0, 0]
System.out.println(Arrays.toString(bb.array()));
System.out.println(bb.position());//5
bb.put((byte) 51);
//重置过后添加元素,你会发现是从5开始,覆盖了原来的元素
//[10, 20, 30, 0, 0, 51, 0, 0, 0, 0]
System.out.println(Arrays.toString(bb.array()));
//clear方法,将position设置成0,limit设置成capacity,丢弃mark标记
bb.clear();
System.out.println(bb.position());//0
System.out.println(bb.limit());//10
System.out.println(bb.capacity());//10
//[10, 20, 30, 0, 0, 51, 0, 0, 0, 0]数据还在
System.out.println(Arrays.toString(bb.array()));
}
}
Channel(通道)java.nio.channels
Channel是一个通道,是双向的,可以通过它读和写数据。Channel是一个对象,既可以调用读的方法也可以调用写的方法。
在java NIO中Channel主要有以下几种类型:
FileChannel:从文件中读取数据
DatagramChannel:读写UDP网络协议数据,相当于IO DategramPacket
SocketChannel:读写TCP网络协议数据,相当于IO Socket
ServerSocketChannel:可以监听TCP连接,相当于IO SocketServer
FileChannel使用案例
public class TestFileChannel {
/**
* FileChannel是一个抽象类
* 可以通过以下三种方法得到
* FileInputStream.getChannel()
* FileOutputStream.getChannel()
* RandomAccessFile.getChannel()
*/
public static void main(String[] args) throws IOException {
FileInputStream fileInputStream = new FileInputStream("NIO案例\\file\\1.json");
FileOutputStream fileOutputStream = new FileOutputStream("NIO案例\\file\\1_copy.json");
//创建通道
FileChannel fileInputStreamChannel = fileInputStream.getChannel();
FileChannel fileOutputStreamChannel = fileOutputStream.getChannel();
//读数据
ByteBuffer byteBuffer = ByteBuffer.allocate(8192);
while (fileInputStreamChannel.read(byteBuffer)!= -1 ){//把数据读到position与limit之间,position位置发生了改变
//重置,limit设置成position的值,position设置成0
byteBuffer.flip();
//写数据,position到limit之间的数据
fileOutputStreamChannel.write(byteBuffer);
//还原缓冲区
byteBuffer.clear();
}
fileOutputStreamChannel.close();
fileInputStreamChannel.close();
fileOutputStream.close();
fileInputStream.close();
}
}
FileChannel结合ByteBuffter实现管道读写数据效率并不是最高的,ByteBuffter有个抽象子类,MappedByteBuffer它可以将文件直接映射到内存中,,把从硬盘读写数据变成从内存读写数据,极大的提高了读写效率。
案例:MappedByteBuffer映射缓冲数组只适合拷贝2GB以下的文件,最大就是2GB。大于2GB就分块拷贝。
public class TestMappedByteBuffer {
/**
* MappedByteBuffer
* MappedByteBuffer map(MapMode mode,long position,long size)
*/
public static void main(String[] args) throws IOException {
//RandomAccessFile可以获取指定的映射模式的Channel
RandomAccessFile r = new RandomAccessFile("NIO案例\\file\\1.json", "r");
RandomAccessFile rw = new RandomAccessFile("NIO案例\\file\\copy_1.json", "rw");
//只读模式的channel
FileChannel channel = r.getChannel();
//写模式的channel
FileChannel channel1 = rw.getChannel();
//获取文件字节大小
long size = channel.size();
//把数据源文件中的所有字节映射到缓冲数组中
MappedByteBuffer map = channel.map(FileChannel.MapMode.READ_ONLY, 0, size);
//得到一个映射缓冲数组,写
MappedByteBuffer map1 = channel1.map(FileChannel.MapMode.READ_WRITE, 0, size);
//拷贝文件
for (long i = 0; i < size; i++) {
//获取一个map缓冲数组中的一个字节
byte b = map.get();
//把获取到的一个字节写入到map1
map1.put(b);
}
rw.close();
r.close();
channel1.close();
channel.close();
}
}
SocketChannel和ServerSocketChannel类
public class Test {
/**
* 客户端SocketChannel类
*/
public static void main(String[] args) throws IOException {
//1,先调用open方法,打开通道
SocketChannel sc = SocketChannel.open();
//2,调用connect方法连接服务器
sc.connect(new InetSocketAddress("127.0.0.1",6666));
//读或写数据
sc.write(ByteBuffer.wrap("服务器你好6".getBytes(StandardCharsets.UTF_8)));
//关闭
sc.close();
}
}
public class Test2 {
/**
* ServerSocketChannel
*/
public static void main(String[] args) throws IOException {
ServerSocketChannel ssc = ServerSocketChannel.open();
//绑定端口号
ssc.bind(new InetSocketAddress(6666));
//接受请求,建立连接
SocketChannel accept = ssc.accept();
//接受客户端传过来的数据
ByteBuffer bb = ByteBuffer.allocate(8192);
int len;
while ((len = accept.read(bb))!= -1){
//重置
bb.flip();
//打印数据
System.out.println(new String(bb.array(),0,len));
//还原缓冲区
bb.clear();
}
}
}
Selector(选择器)
Selector也被称为多路复用器,,可以把多个Channel(通道)注册到一个Selector(选择器)上,那么就可以通过一条线程来处理多个Channel(通道)上发生的事件,并且能够根据事件的情况来决定选择器的读写。
作用:一个选择器可以监听多个通道发生的事件,减少了系统负担,提高了程序的执行效率。因为我线程之间的切换对操作系统来说是需要付出一定的代价,并且每个线程都会占用一定的资源,所以对于操作系统来说,线程越少越好。
IO入门002之SelectionKey详解_zhuhaoyu6666的博客-CSDN博客
public class TestSelector {
/**
* Selector常用方法
* select方法,服务器等待客户端连接的方法
*/
public static void main(String[] args) throws IOException {
ServerSocketChannel ssc666 = ServerSocketChannel.open();
ssc666.bind(new InetSocketAddress(6666));
ServerSocketChannel ssc777 = ServerSocketChannel.open();
ssc777.bind(new InetSocketAddress(7777));
ServerSocketChannel ssc888 = ServerSocketChannel.open();
ssc888.bind(new InetSocketAddress(8888));
//注册之前必须设置非阻塞
ssc666.configureBlocking(false);
ssc777.configureBlocking(false);
ssc888.configureBlocking(false);
//获取选择器
Selector selector = Selector.open();
//把通道注册到选择器上的方法
//Channel的register方法来实现注册
//第一个参数:选择器
//第二个参数:注册监听事件https://blog.csdn.net/zhuhaoyu6666/article/details/100114183
//在注册ServerSocketChannel时只能使用SelectionKey.OP_ACCEPT
ssc666.register(selector, SelectionKey.OP_ACCEPT);
ssc777.register(selector, SelectionKey.OP_ACCEPT);
ssc888.register(selector, SelectionKey.OP_ACCEPT);
//获取被注册的通道
Set<SelectionKey> keys = selector.keys();
System.out.println(keys.size());
//获取所有被连接的通道
Set<SelectionKey> selectionKeys = selector.selectedKeys();
while (true) {
//服务器等待客户端连接,
selector.select();//阻塞,获取到客户端连接后,就会把通道放入selectionKeys
/**
* 不能使用增强for循环,因为不能删除set里面的元素
* 用完通道后需要从set集合中删除,不然就会空指针异常
* 遍历集合的时候不能增加或者删除
*/
/* for (SelectionKey selectionKey : selectionKeys) {
//拿到对应的服务器通道
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
SocketChannel socketChannel = serverSocketChannel.accept();//接收请求
//接下来就正常按照通道处理数据就行了
}*/
//使用迭代器,迭代器remove方法可以删除
Iterator<SelectionKey> iterator = selectionKeys.iterator();
//itit
while (iterator.hasNext()){
SelectionKey selectionKey = iterator.next();
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
SocketChannel socketChannel = serverSocketChannel.accept();//接收请求
//接下来就正常按照通道处理数据就行了
ByteBuffer bb = ByteBuffer.allocate(8192);
int len = socketChannel.read(bb);
System.out.println(new String(bb.array(),0,len));
iterator.remove();
}
}
}
}