java nio

java

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的值来实现功能的

  1. flip()

    从写状态变为读状态,limit=position,position=0

  2. rewind()

    将position变成0,主要是用于你读数据的时候,可以回过头重新读一遍

  3. clear()

    完全清空,position=0,limit=max,变为写状态,但是实际上原来的数据没有抹掉

  4. compact()

    方法将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面。limit属性依然像clear()方法一样,设置成capacity。现在Buffer准备好写数据了,但是不会覆盖未读的数据。

  5. mark()与reset()方法

    通过调用Buffer.mark()方法,可以标记Buffer中的一个特定position。之后可以通过调用Buffer.reset()方法恢复到这个position,用来标记一个position位置的作用

  6. equals()与compareTo()方法

    比较的是从 position到limit之间的元素,他们之间的元素才是程序承认的有效元素

  7. hasremaining 还有没有有效数据

读写方法

  1. 从Channel写到Buffer
    int bytesRead = inChannel.read(buf); //从channel读到buffer

  2. 通过put方法写Buffer的例子:
    buf.put(127);// 这就比较简单了,直接put,有很多的复写的方法,大体类似

  3. 从Buffer读取数据到Channel的
    int bytesWritten = inChannel.write(buf);

  4. 使用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

回到顶部