文章目录
-
-
- 1.概述
- 2.缓冲区Buffer定义
- 3.Buffer内部结构
- 4.Buffer常用的操作
-
- 4.1 allocate
- 4.2 wrap
- 4.3 put
- 4.4 flip
- 4.5 get
- 4.6 mark
- 4.7 reset
- 4.8 clear
- 4.9 rewind
- 4.10 remaining
- 5. chanel
-
- 5.1 FileChannel 文件管道
- 5.2 DatagramChannel UDP套接字管道
- 5.3 TCP套接字管道
- 6.后续
-
1.概述
在BIO API中是通过InputStream 与outPutStream 两个流进行输入输出。而NIO 使用一个双向通信的管道代替了它俩。管道(Channel)必须依赖缓冲区(Buffer)实现通信。
管道对比流多了一些如:非阻塞、堆外内存映射、零拷贝等特性。
2.缓冲区Buffer定义
管道要依赖于缓冲区,所以先来介绍一下缓冲区的概念和使用。
缓冲区内部维护了一个数组来存储数据,缓存区并不支持存储的任意的数据类型,只能存储一些基本数据类型。
具备读写、清空的功能。不具备线程安全,需要自行的控制线程安全问题。
3.Buffer内部结构
拿ByteBuffer举例,它是Buffer的一个子类,用于存储Byte类型的数据,其他的缓冲区差不多,只是存储的数据类型不同。
内部维护了一个byte数组,用来存储数据的。
父类Buffer有4个重要的属性:
-
capacity
表示数组的容量大小
-
limit
限制Buffer的可读和可写的范围,默认等于capacity
-
position
当前读写的位置,默认是0,每读取一位或写一位,则+1.
-
mark
做标记,用于reset将position设置到这个位置。默认是-1
4者的位置关系:mark <= position <= limit <= capacity
4.Buffer常用的操作
4.1 allocate
分配缓冲区的存储空间,初始化所有的属性。例如:
得到的结果如下:
4.2 wrap
基于数组包装一个Buffer,position为0,limit为容量值
测试代码如下:
buffer结果如下:
4.3 put
写入缓冲区,测试代码如下:
在put的过程中,会改变position,也就是说每写入一个数字,就会将position+1.当第7次写入的时候,position会超过limit的限制,此时会抛出异常BufferOverflowExeception
4.4 flip
为读取做好准备,在put完之后,执行flip可以重置position,设置成0.
之后的读取操作就从position的位置开始。
测试代码如下:
执行完put 5 之后 buffer的结果如下:
接着执行flip,结果如下:
根据结果就可以看出flip的作用,将position设置成0
并且将limit设置成了原先position的位置,此时的limit就限制了读的范围。
4.5 get
读取缓冲区的内容,每读取一个,position位置后移动一位
测试代码如下:
当读取到第5个的时候,此时的buffer里面的内容如下:
可以发现此时的position已经到了limit的限制边界。如果再次读取就会报错,读取越界异常BufferUnderflowException
4.6 mark
设置标记位,记录下当前的位置
测试代码如下:
执行完mark后,将mark属性设置成position,也就是做了个标记
4.7 reset
reset的作用就是将position的值设置成mark,用于修改缓冲区中的一段数据。或者重复的读取缓冲区中的一段数据。
测试代码如下:
执行完reset之后,buffer结果如下
在记录mark标记后为,读取了两个数字,并将者两个数字修改了。reset之后,position又回到了mark记录的位置。此时再进行写操作,就可以修改缓冲区中的那两个数字。结果如下
4.8 clear
重置缓冲区的属性,但是并不会清空缓冲区里的数据
将position的位置设为0,mark设置为-1.limit设置为capacity
测试代码如下:
执行完clear之后,缓冲区的结果如下:
4.9 rewind
为重新读取做准备。将position设置为0,limit不变。mark设置为-1
测试代码如下:
执行完rewind之后,buffer结果如下:
4.10 remaining
返回还有多少可读取的范围
也就是limit-position
测试代码如下:
此时limit等于5 读取了两次之后,执行remaining方法,返回的结果是3,表示剩余可读取的内容
5. chanel
管道用于连接文件、网络Socket等。它可同时执行读取和写入两个I/O 操作,固称双向管道,它有连接和关闭两个状态,在创建管道时处于打开状态,一但关闭 在调用I/O操作就会报ClosedChannelException
。通过管道的isOpen
方法可判断其是否处于打开状态。
5.1 FileChannel 文件管道
固名思议它就是用于操作文件的,除常规操作外它还支持以下特性:
-
支持对文件的指定区域进行读写
-
堆外内存映射,进行大文件读写时,可直接映射到JVM声明内存之外,从面提升读写效率。
-
零拷贝技术,通过
transferFrom
或transferTo
直接将数据传输到某个通道,极大提高性能。 -
锁定文件指定区域,以阻止其它程序员进行访问
打开FileChannel目前只能通过流进行间打开,如inputStream.getChannel() 和outputStream.getChannel() ,通过输入流打开的管道只能进行取,而outputStream打开的只能写。否则会分别抛出NonWritableChannelException与NonReadableChannelException异常。
如果想要管道同时支持读写,必须用RandomAccessFile
读写模式才可以。
下面的测试代码是文件管道的基本使用
//1. 打开文件管道
FileChannel channel = new RandomAccessFile(file_name,"rw").getChannel();
ByteBuffer buffer=ByteBuffer.allocate(1024); // 声明1024个空间
// 从文件中 读取数据并写入管道 再写入缓冲
channel.read(buffer);
buffer.flip();//上面学的,写完之后,需要将position归0,为了后面的读取做准备
byte[] bytes= new byte[buffer.remaining()];
int i =0;
while (buffer.hasRemaining()){
bytes[i++]= buffer.get();
}
System.out.println(new String(bytes));
// 把缓冲区数据写入到管道
channel.write(ByteBuffer.wrap("森林大帅哥".getBytes()));
channel.close();
5.2 DatagramChannel UDP套接字管道
UDP是无连接的协议,DatagramChannel就是为这个协议提供服务,以接收客户端发来的消息。
DatagramChannel的基本用法如下:
public void test1() throws IOException {
DatagramChannel channel=DatagramChannel.open();
// 绑定端口
channel.bind(new InetSocketAddress(8080));
ByteBuffer buffer=ByteBuffer.allocate(8192);
while (true){
buffer.clear(); // 清空还原
channel.receive(buffer); // 阻塞
buffer.flip();
byte[] bytes=new byte[buffer.remaining()];
buffer.get(bytes);
System.out.println(new String(bytes));
}
}
使用命令nc - uv 127.0.0.1 8080 可以向指定的ip端口号 发送udp
idea控制台就会输出如下:
5.3 TCP套接字管道
TCP是一个有连接协议,须建立连接后才能通信。这就需要下面两个管道:
-
**ServerSocketChannel :**用于与客户端建立连接
-
**SocketChannel :**用于和客户端进行消息读写
测试代码如下:
@Test
public void test1() throws IOException {
// 用于与客户端建立连接
ServerSocketChannel channel = ServerSocketChannel.open();
channel.bind(new InetSocketAddress(8080));
while (true) { //循环接收请求,分配子线程去执行请求
// 用于和客户端进行消息读写
SocketChannel socketChannel = channel.accept();
handle(socketChannel);
}
}
public void handle(final SocketChannel socketChannel) throws IOException {
// 2.通信
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
ByteBuffer buffer = ByteBuffer.allocate(8192);
while (true) {
try {
buffer.clear();
socketChannel.read(buffer);
// 从buffer 当中读出来
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
String message = new String(bytes);
System.out.println(message);
// 写回去
buffer.rewind();
socketChannel.write(buffer);
if (message.trim().equals("exit")) {
break;
}
} catch (Exception e) {
e.printStackTrace();
}
}
try {
socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
});
thread.start();
}
可通过命令进行测试TCP服务 telnet 127.0.0.1 8080
控制台输出了tcp的消息,并将消息写回了管道,命令行界面也输出了返回来的hello结果。
6.后续
下一篇博客,介绍NIO中的另一个重要组件Selector