java nio
nio
Java NIO(New IO)是一个可以替代标准Java IO API的IO API(从Java 1.4开始),Java NIO提供了与标准IO不同的IO工作方式
标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。
Channel
Channel
- FileChannel
- DatagramChannel
- SocketChannel
ServerSocketChannel
FileInputStream fis=new FileInputStream("123");
FileChannel channel=fis.getChannel();
Channel是与Buffer可以进行双向的读写操作的结构,关于如何读写可以看下面的Buffer
Buffer
- Buffer
- ByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
- MappedByteBuffer
缓冲区可以进行读写操作,最大大小的开辟是确定的
IntBuffer ib=IntBuffer.allocate(20);
Buffer里面的三个重要属性
- capacity 表示buffer的容量,是定值
- position 表示当前的读写指针
- limit 表示当前读写的最大的位置,就是说position不能超过limit
不论读还是写,都是从postion位置开始操作,limit相当于一个postion的阈值,Buffer的主要函数都是通过修改position和limit的值来实现功能的
- flip()
从写状态变为读状态,limit=position,position=0
- rewind()
将position变成0,主要是用于你读数据的时候,可以回过头重新读一遍
- clear()
完全清空,position=0,limit=max,变为写状态,但是实际上原来的数据没有抹掉
- compact()
方法将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面。limit属性依然像clear()方法一样,设置成capacity。现在Buffer准备好写数据了,但是不会覆盖未读的数据。
- mark()与reset()方法
通过调用Buffer.mark()方法,可以标记Buffer中的一个特定position。之后可以通过调用Buffer.reset()方法恢复到这个position,用来标记一个position位置的作用
- equals()与compareTo()方法
比较的是从 position到limit之间的元素,他们之间的元素才是程序承认的有效元素
- hasremaining 还有没有有效数据
读写方法
从Channel写到Buffer
int bytesRead = inChannel.read(buf); //从channel读到buffer
通过put方法写Buffer的例子:
buf.put(127);// 这就比较简单了,直接put,有很多的复写的方法,大体类似
从Buffer读取数据到Channel的
int bytesWritten = inChannel.write(buf);
使用get()方法从Buffer中读取数据
byte aByte = buf.get();
Scatter/Gather
Scatter/Gather是针对于Channel的一个特性,可以把Channel数据分发给多个Buffer,或者把多个Buffer合并到一个Channel
他的价值在于,可以把固定的包头给单独的拿出来放到某一个Buffer中处理,注意在分发时,只有第一个BUffer填满了,才会去填第二个Buffer,特别要主要position和limit的值的变化
//Scatter ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
ByteBuffer[] bufferArray = { header, body };
channel.read(bufferArray);
//Gather
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
//write data into buffers
ByteBuffer[] bufferArray = { header, body };
channel.write(bufferArray);
通道之间的数据传输
FileChannel有两个方法transferTo/transferFrom,来完成通道之间数据的传递,参数可以是任意通道。
long position = 0; long count = fromChannel.size();//最大传输的数据量,实际上可能没有这么大,有多少传多少
fromChannel.transferTo(position, count, toChannel);
long position = 0;
long count = fromChannel.size();////最大传输的数据量,实际上可能没有这么大,有多少接收多少
toChannel.transferFrom(position, count, fromChannel);
FileChannel
可以使用FileChannel.truncate()方法截取一个文件。截取文件时,文件将中指定长度后面的部分将被删除。如:
channel.truncate(1024);
FileChannel实例的size()方法将返回该实例所关联文件的大小。如:
long fileSize = channel.size();
FileChannel.force()方法将通道里尚未写入磁盘的数据强制写到磁盘上。出于性能方面的考虑,操作系统会将数据缓存在内存中,所以无法保证写入到FileChannel里的数据一定会即时写到磁盘上。要保证这一点,需要调用force()方法。
force()方法有一个boolean类型的参数,指明是否同时将文件元数据(权限信息等)写到磁盘上。
下面的例子同时将文件数据和元数据强制写到磁盘上:
channel.force(true);
Pipe
Pipe是2个线程之间的单向数据连接。Pipe有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取
Pipe p=Pipe.open();//打开Pipe //Pipe入口
Pipe.SinkChannel sinkChannel=p.sink();
//Pipe出口
Pipe.SourceChannel sourceChannel=p.source();
//入口端写入
String s="Write to Pipe ......";
ByteBuffer bf=ByteBuffer.allocate(2000);
bf.put(s.getBytes());
bf.flip();
while (bf.hasRemaining()) {
sinkChannel.write(bf);
}
//出口端写出
ByteBuffer bf1=ByteBuffer.allocate(2000);
sourceChannel.read(bf1);
System.out.println(new String(bf1.array()));
SocketChannel
SocketChannel socketChannel = SocketChannel.open();socketChannel.connect(new InetSocketAddress("IP",port));
读数据
ByteBuffer buf = ByteBuffer.allocate(48);int bytesRead = socketChannel.read(buf);
写数据
ByteBuffer buf = ByteBuffer.allocate(48); buf.clear();
buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining()) {
channel.write(buf);
}
非阻塞模式
socketChannel.configureBlocking(false);
非阻塞模式与选择器搭配会工作的更好,通过将一或多个SocketChannel注册到Selector,可以询问选择器哪个通道已经准备好了读取,写入等
ServerSocketChannel
- socket()返回对应的Socket对象
- accept()接受到此通道套接字的连接。返回的是一个SocketChannel,在分阻塞模式的时候,accept没有连接就会返回null
Selector
Selector相当于一个事件管理器,可以向它注册不同连接的多个事件,并以一定间隔询问Selector是否有注册的时间发生,再进行后续的处理。这样只需要一个阻塞的线程,就可以管理所有的连接了。
Selector注册的Chnannel必须是非阻塞的,因此FileChannel肯定不能用Selector了。主要应用还是Socket。
Selector管理的事件包含两个要素:
1. 事件所对应的连接,以SocketChannel作为描述。
2. 事件的类型,SelectionKey.OP_* 常量之一。
两个要素唯一确定一个发生的事件。
主要的逻辑包括
- 将Channel注册到Selector上,并且说明我这个Channel要监听的事件的类型
- 调用int n = selector.select(),如果n>0说明有监听的事件发生了
- 调用selector.selectedKeys()返回一个集合,每一个事件对应一个 SelectionKey 对象。通过 SelectionKey 获取对应的 SocketChannel 以及事件的类型,就能对SocketChannel做你想要做的必须的操作。
其他的知识点
事件的类型
1、SelectionKey.OP_CONNECT
2、SelectionKey.OP_ACCEPT
3、SelectionKey.OP_READ
4、SelectionKey.OP_WRITE
select()方法会阻塞,直到至少有一个channel的注册事件已就绪。
select(long timeout)和select()一样,但阻塞时间最大为timeout 毫秒。
selectNow()不会阻塞,不管有没有channel就绪,都立刻返回。
示例代码
以上是 java nio 的全部内容, 来源链接: utcz.com/z/392040.html