目录
2.4 FileChannel结合MappedByteBuffer实现读写
Non-blocking IO,NIO包含三个部分,分别是Buffer缓冲区、Channel通道、Selector选择器,NIO又叫做非阻塞IO,可以提高高并发情况下程序的效率。
1Buffer缓冲区
1.1概述
Buffer叫缓冲区数组,能够替换之前的普通数组byte[]。用来在后面的NIO的读写中提高效率。
分类:
ByteBuffer byte[]
CharBuffer char[]
DoubleBuffer double[]
FloatBuffer float[]
IntBuffer int[]
LongBuffer long[]
ShortBuffer short[]
1.2ByteBuffer的创建方式
代码演示:
//在堆中创建缓冲区:allocate(int capacity)
ByteBuffer buffer1 = ByteBuffer.allocate(10);
//在系统内存创建缓冲区:allocatDirect(int capacity)
ByteBuffer buffer2 = ByteBuffer.allocateDirect(10);
//通过数组创建缓冲区:wrap(byte[] arr)
byte[] arr = new byte[1024];
ByteBuffer buffer3 = ByteBuffer.wrap(arr);
1.3常用方法
方法 | 说明 |
put(byte b) | 给数组添加元素 |
capacity() | 获取容量 |
limit() | 限制 |
position() | 位置 |
mark() | 标记 |
clear() | 还原缓冲区的状态 |
flip() | 切换读写状态 |
1.3.1给数组添加元素
//创建缓冲区
ByteBuffer buffer = ByteBuffer.allocate(10);
//给数组添加元素
buffer.put((byte) 11);
buffer.put((byte) 22);
buffer.put((byte) 33);
buffer.put((byte) 44);
byte[] arr = {55,66,77};
buffer.put(arr);
buffer.put(arr,0,2);
buffer.put(9, (byte) 99);
//把byteBuffer转成普通数组
byte[] array = buffer.array();
System.out.println(Arrays.toString(array));//[11, 22, 33, 44, 55, 66, 77, 55, 66, 99]
1.3.2获取容量
容量是一个定值。
//获取容量
int capacity = buffer.capacity();
System.out.println(capacity);//10
1.3.3限制
limit可以指定一个索引,从limit开始后面的位置不能操作。
//获取限制(不带参是获取)
int limit = buffer.limit();
System.out.println(limit); //10
//赋值限制(带参数是赋值)
buffer.limit(6); //从5索引开始就不能操作了
1.3.4位置
位置代表将要存放的元素的索引,每次添加position往后移动。位置不能小于0,也不能大于limit。
//创建对象
ByteBuffer buffer = ByteBuffer.allocate(10);
//添加
buffer.put((byte)11);
buffer.put((byte)22);
//position范围 [0,limit] 不带参数是获取范围
int position = buffer.position();
System.out.println(position);//2
//带参数是指定范围
buffer.position(5);
buffer.put((byte)33);
System.out.println(Arrays.toString(buffer.array()));//[11, 22, 0, 0, 0, 33, 0, 0, 0, 0]
System.out.println(buffer); //[pos=2 lim=10 cap=10]
1.3.5标记
当调用缓冲区的reset()重置方法时,会将缓冲区的position位置重置为mark的位置。
//创建对象
ByteBuffer buffer = ByteBuffer.allocate(10);
//添加
buffer.put((byte)11);
buffer.put((byte)22);
//position范围 [0,limit]
System.out.println(buffer); //[pos=2 lim=10 cap=10]
//设置mark标记
buffer.mark(); //把此时索引标记2
//添加
buffer.put((byte)33);
buffer.put((byte)44);
//重置(此时position会被重置到mark的位置)
buffer.reset();
buffer.put((byte)55);
System.out.println(Arrays.toString(buffer.array()));//[11, 22, 55, 44, 0, 0, 0, 0, 0, 0]
1.3.6还原缓冲区的状态
将position设置为:0
将限制limit设置为容量capacity
丢弃标记mark
//创建对象
ByteBuffer buffer = ByteBuffer.allocate(10);
//添加
buffer.put((byte)11);
buffer.put((byte)22);
//mark标记
buffer.mark(); //2
//limit限制
buffer.limit(3);
System.out.println(buffer); //[pos=2 lim=3 cap=10]
//clear 还原
buffer.clear();
System.out.println(buffer); //[pos=0 lim=10 cap=10]
//数组的内容
System.out.println(Arrays.toString(buffer.array())); //[11, 22, 0, 0, 0, 0, 0, 0, 0, 0]
1.3.7切换读写状态
在读写数据之间要调用这个方法。
将limit设置为当前position位置
将当前position位置设置为0
丢弃标记
//创建对象
ByteBuffer buffer = ByteBuffer.allocate(10);
//添加
buffer.put((byte)11);
buffer.put((byte)22);
System.out.println(buffer);//[pos=2 lim=10 cap=10]
//flip切换
buffer.flip();
System.out.println(buffer);//[pos=0 lim=2 cap=10]
//数组的内容
System.out.println(Arrays.toString(buffer.array()));
//[11, 22, 0, 0, 0, 0, 0, 0, 0, 0]
2Channel通道
2.1概述
Channel叫通道,通道相当于之前的流对象。流是用来传输数据的,通道也是用来传输数据的,并且通道既可以读也可以写。流可以使用普通数组作为数据的载体,通道需要使用缓冲区数组作为数据的载体。
2.2分类
- FileChannel:从文件读取数据
- DatagramChannel:读写UDP网络协议数据
- SocketChannel:读写TCP网络协议数据
- ServerSocketChannel:可以监听TCP连接
2.3FileChannel基本使用
使用FileChannel完成文件的复制
//输入流
FileInputStream fis = new FileInputStream(new File("src\\study\\NIO\\Test07.png"));
//输出流
FileOutputStream fos = new FileOutputStream(new File("src\\study\\NIO\\Test07输出.png"));
//获取通道
FileChannel inChannel = fis.getChannel();
FileChannel outChannel = fos.getChannel();
//创建缓冲区数组
ByteBuffer buffer = ByteBuffer.allocate(1024);
//循环 读文件
while (inChannel.read(buffer) != -1){
//读了1024字节的文件,开始写,写之前需要进行切换
buffer.flip();
//输出
outChannel.write(buffer);
//重置,下一轮读取
buffer.clear();
}
//关闭资源
fis.close();
fos.close();
inChannel.close();
outChannel.close();
2.4 FileChannel结合MappedByteBuffer实现读写
MappedByteBuffer是ByteBuffer的子类。他可以完成数据读写。能够把硬盘中的数据一次映射到内存中。可以减少硬盘和内存之间的IO次数,能够提高效率。
使用MappedByteBuffer完成文件复制
//输入流
RandomAccessFile in = new RandomAccessFile("src\\study\\NIO\\Test08.png","r");
//输出流
RandomAccessFile out = new RandomAccessFile("src\\study\\NIO\\Test08输出.png","rw");
//获取通道
FileChannel inChannel = in.getChannel();
FileChannel outChannel = out.getChannel();
//获取文件字节大小
long size = inChannel.size();
//创建缓冲数组
MappedByteBuffer buffer1 = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, size);
MappedByteBuffer buffer2 = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, size);
//将buffer1的字节移入到buffer2中
for (long i = 0; i < size; i++) {
byte b = buffer1.get();
buffer2.put(b);
}
//关流
in.close();
out.close();
inChannel.close();
outChannel.close();
一次性将文件读取到内存中,从内存中将输入缓冲区的数据写到输出缓冲区中,这个时候对于内存的压力很大,如果文件很大,内存可能不够用,所以在传递大文件时,我们需要分块读写。
/**
* 大文件 需要分块复制
*/
//输入流
RandomAccessFile in = new RandomAccessFile("src\\study\\NIO\\Test09大文件.zip","r");
//输出流
RandomAccessFile out = new RandomAccessFile("src\\study\\NIO\\Test09复制大文件.zip","rw");
//获取通道
FileChannel inChannel = in.getChannel();
FileChannel outChannel = out.getChannel();
//文件总大小
long size = inChannel.size();//字节
//每次最多读取多少
long every = 500*1024*1024;//500MB
//需要读取几次
int count = (int) Math.ceil(size*1.0/every);
//每次从哪里开始读
long start;
//实际读取多少
long actual;
//定义缓冲区
MappedByteBuffer inBuffer;
MappedByteBuffer outBuffer;
//循环读取
for (int i = 0; i < count; i++) {
start = i * every;
if (i == count -1) {
actual = size - start;
} else {
actual = every;
}
inBuffer = inChannel.map(FileChannel.MapMode.READ_ONLY,start,actual);
outBuffer = outChannel.map(FileChannel.MapMode.READ_WRITE,start,actual);
}
//关流
in.close();
out.close();
inChannel.close();
outChannel.close();
2.5网络编程收发信息
服务端:
//服务端
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//绑定端口
serverSocketChannel.bind(new InetSocketAddress(9999));
//获取连接
SocketChannel accept = serverSocketChannel.accept();
//缓冲区数组
ByteBuffer buffer = ByteBuffer.allocate(1024);
//读取数据
int read = accept.read(buffer);
//打印
System.out.println(new String(buffer.array(),0,read));
//关流
serverSocketChannel.close();
accept.close();
客户端:
//客户端
SocketChannel socketChannel = SocketChannel.open();
//连接服务器端口
socketChannel.connect(new InetSocketAddress("localhost", 9999));
//创建数组
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("HelloWorld".getBytes());
//切换
buffer.flip();
//发送
socketChannel.write(buffer);
//关流
socketChannel.close();
2.6accept阻塞
accept方法是阻塞方法,调用accept方法时,如果没有连接,则不会往下进行。
设置非阻塞:
//服务端
ServerSocketChannel ssc = ServerSocketChannel.open();
//绑定端口
ssc.bind(new InetSocketAddress(9999));
//设置非阻塞
ssc.configureBlocking(false);
while (true) {
//获取连接
SocketChannel accept = ssc.accept();
if (accept != null){
//有连接了
//创建缓冲数组
ByteBuffer buffer = ByteBuffer.allocate(1024);
//接收信息
int read = accept.read(buffer);
System.out.println(new String(buffer.array(),0,read));
break;
}else {
//没有连接
System.out.println("没人连我");
}
}
3Selector选择器
3.1多路复用概念
多路复用就是把多个服务器端口交给同一个Selector去管理。
3.2 Selector介绍
Selector是一个选择器,可以用一个线程处理多个事件。可以注册到多个Channel上,帮多个Channel去处理事件。用一个线程处理了之前多个线程的事务,就给系统减轻负担,提高效率。
select() :Selector的连接方法,代替之前的accept()方法等待客户端的连接
阻塞问题:
在没有客户端连接的时候,是阻塞状态
在有客户端连接并还没有处理的时候,是非阻塞状态
在客户处理结束之后,又会恢复到阻塞状态
selectedKeys() :获取一个集合,被连接的服务端对象会被放在集合中keys() :获取一个集合,用于存放被Selector管理的服务对象
3.3Selector选择器的使用
//客户端
ServerSocketChannel ssc = ServerSocketChannel.open();
//绑定端口
ssc.bind(new InetSocketAddress(9999));
//设置非阻塞
ssc.configureBlocking(false);
//创建Selector
Selector selector = Selector.open();
//把selector注册到channel上,并把accept这个方法交给Selector去管理
ssc.register(selector, SelectionKey.OP_ACCEPT);
//会把被连接的服务对象放在集合中
Set<SelectionKey> selectionKeys= selector.selectionKeys();
while (true) {
//等待连接
System.out.println("等待连接");
selector.select();
System.out.println("连接了");
for (SelectionKey key : selectionKeys) {
//获取被连接的对象
ServerSocketChannel channel = (ServerSocketChannel)key.channel();
//获取客户端对象
SocketChannel accept = channel.accept();
//创建缓冲数组
ByteBuffer buffer = ByteBuffer.allocate(1024);
//接受数据
int read = accept.read(buffer);
System.out.println(new String(buffer.array(),0,read));
accept.close();
}
}
3.4Selector管理多个服务端
//创建Selector
Selector selector = Selector.open();
//客户端
ServerSocketChannel ssc = ServerSocketChannel.open();
//绑定端口
ssc.bind(new InetSocketAddress(9999));
//客户端
ServerSocketChannel ssc2 = ServerSocketChannel.open();
//绑定端口
ssc2.bind(new InetSocketAddress(8888));
//设置非阻塞
ssc.configureBlocking(false);
ssc2.configureBlocking(false);
//把selector注册到channel上,并把accept这个方法交给Selector去管理
ssc.register(selector, SelectionKey.OP_ACCEPT);
ssc2.register(selector, SelectionKey.OP_ACCEPT);
//会把被连接的服务对象放在集合中
Set<SelectionKey> selectionKeys = selector.selectedKeys();
//被管理的客户端对象
Set<SelectionKey> keys = selector.keys();
System.out.println("被连接的"+selectionKeys);
System.out.println("被管理的"+keys);
while (true) {
//等待连接
selector.select();
System.out.println("被连接的"+selectionKeys);
System.out.println("被管理的"+keys);
//for (SelectionKey key : selectionKeys) {//被连接的对象放进去没有删掉,所以我们要删掉集合中删除元素要使用迭代器删除
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
//获取被连接的对象
ServerSocketChannel channel = (ServerSocketChannel)key.channel();
//获取客户端对象
SocketChannel accept = channel.accept();
//创建缓冲数组
ByteBuffer buffer = ByteBuffer.allocate(1024);
//接受数据
int read = accept.read(buffer);
System.out.println(new String(buffer.array(),0,read));
accept.close();
//删除已经操作完的服务端对象
iterator.remove();
}
}
以上就是部分NIO的知识点啦,后续会继续补充。各位大佬如发现有知识点错误或者有不同的建议与意见,欢迎评论、私信指正,本人才疏学浅还需向各位大佬学习,还请不吝赐教!在此感谢各位的观看!谢谢!