Java--NIO(一)

Java传统的IO是阻塞式的,而NIO则提供了非阻塞式的IO.

NIO即New IO, java从jdk1.4开始引入,用于解决阻塞式的处理,所以也可以说是No block IO.

NIO主要是用来处理Socket中阻塞的问题。

1、NIO组件

Java NIO 由以下几个核心部分组成:

  • Channels
  • Buffers
  • Selectors

Channel

所有的 IO 在NIO 中都从一个Channel 开始。Channel 有点象流。 数据可以从Channel读到Buffer中,也可以从Buffer 写到Channel中。

Channel和Buffer有好几种类型。下面是JAVA NIO中的一些主要Channel的实现:

  • FileChannel
  • DatagramChannel
  • SocketChannel
  • ServerSocketChannel

正如你所看到的,这些通道涵盖了UDP 和 TCP 网络IO,以及文件IO。

Buffer

以下是Java NIO里关键的Buffer实现:

  • ByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

这些Buffer覆盖了你能通过IO发送的基本数据类型:byte, short, int, long, float, double 和 char。

selector

Selector允许单线程处理多个 Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便。例如,在一个聊天服务器中。

这是在一个单线程中使用一个Selector处理3个Channel的图示:

img

要使用Selector,得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新连接进来,数据接收等。

2、Channel、Buffer

Channel和Buffer在一起使用,channel连接一个目的地,从Buffer中进行读写。

1、FileChannel、ByteBuffer

Java NIO中的FileChannel是一个连接到文件的通道。可以通过文件通道读写文件。

FileChannel无法设置为非阻塞模式,它总是运行在阻塞模式下。

Buffer可以创建对应大小的缓存区。

打开FileChannel、创建ByteBuffer

通过使用一个InputStream、OutputStream或RandomAccessFile来获取一个FileChannel实例.

RandomAccessFile file=new RandomAccessFile("./a.txt","rw");
        
FileChannel fChannel= file.getChannel();

Buffer没有公开的构造方法,使用allocate方法创建实例,传入一个数字作为容量。

ByteBuffer buffer=ByteBuffer.allocate(48);

Buffer中关于数据的读写起始位置,可以看做指针:

Buffer有三个属性:

  • capacity:容量,即创建时指定的数值
  • position:定位,即当前缓冲中指向的位置
  • limit:极限:即当前Buffer中读写所处的极限位置。
操作Buffer和Channel

Channel和Buffer之间进行数据读写,和普通IO基本一样,通过write和read方法。

在对Buffer进行读写,可以使用get,put方法。

Buffer进行读写之前需要对Buffer中的三个属性进行调整,以便数据读写正确。

向Buffer中写数据,需要使用clear()与compact()方法,如果调用的是clear()方法,position将被设回0,limit被设置成 capacity的值。

如果Buffer中仍有未读的数据,且后续还需要这些数据,但是此时想要先先写些数据,那么使用compact()方法。

从Buffer中读数据,需要使用flip方法。

关闭Channel

Buffer不需要关闭,jvm会自动处理。

Channel关闭使用close方法即可。

从文件中读写数据
RandomAccessFile file=new RandomAccessFile("./a.txt","rw");
        
        FileChannel fChannel= file.getChannel();//获取通道

        ByteBuffer buffer=ByteBuffer.allocate(48);//get Buffer

        int len=fChannel.read(buffer);//返回长度

        buffer.flip();//进入读模式

        byte[] b=new byte[64];

        buffer.get(b,0,len);//读到b中len个字节

        System.out.println(new String(b,0,len));//输出

        buffer.clear();//全部读完,进入写模式

        String data="
now is: "+System.currentTimeMillis();
        buffer.put(data.getBytes());//向通道中写入
        
        buffer.flip();//进入读模式
        
        fChannel.write(buffer);//从Buffer中读出

        file.close();

2、SocketChannel、ServerSocketChannel

Socket才是NIO真正使用的地方。

ServerSocketChannel可以设置为非阻塞式,这种情况下的网络Io即不会阻塞。

configureBlocking(false);
打开/关闭Channel

Socket通过open打开连接,需要一个Socket地址。关闭使用close,非阻塞情况下连接可以自动关闭,无需显示调用。

SocketChannel sChannel=SocketChannel.open();
 sChannel.connect(new InetSocketAddress("127.0.0.1", 8001));

操作

操作基本一样,如下:

使用客户端向服务端发送数据,服务端返回数据。

客户端
SocketChannel sChannel=SocketChannel.open();

        sChannel.connect(new InetSocketAddress("127.0.0.1", 8001));

        ByteBuffer buffer=ByteBuffer.allocate(1024);

        buffer.put("hello world".getBytes());
        buffer.flip();

        sChannel.write(buffer);


        buffer.clear();
        int len=sChannel.read(buffer);

        buffer.flip();
        byte[] b=new byte[1024];
        buffer.get(b, 0, len);

        System.out.println(new String(b,0,len));
服务端
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        serverSocketChannel.socket().bind(new InetSocketAddress("127.0.0.1",8001));

        serverSocketChannel.configureBlocking(false);
        ByteBuffer buffer=ByteBuffer.allocate(1024);
        while(true){
			
            //这里不再阻塞,直接向下执行
            SocketChannel socketChannel =
                    serverSocketChannel.accept();

            if(socketChannel!=null){
                int len=socketChannel.read(buffer);

                buffer.flip();

                byte[] b=new byte[1024];

                buffer.get(b,0,len);
                System.out.println(new String(b,0,len));

                buffer.clear();

                buffer.put("yes".getBytes());

                buffer.flip();
                socketChannel.write(buffer);

                break;

            }

3、Scatter|Gather

分散与聚集,通道可以与多个Buffer建立关联。

写入多个Buffer时,如果某个Buffer满了,则转入下个Buffer。

同样,读取时,某个Buffer被读完,则从其他Buffer读。

file="b.txt"

abcdefghijkhello world
 RandomAccessFile rFile=new RandomAccessFile(file, "rw");

        ByteBuffer buffer1=ByteBuffer.allocate(10);
        ByteBuffer buffer2=ByteBuffer.allocate(10);
        ByteBuffer buffer3=ByteBuffer.allocate(10);

        ByteBuffer[] buffers={buffer1,buffer2,buffer3};

        FileChannel fc= rFile.getChannel();

        long l=fc.read(buffers);//读到buffers中,buffer1满则读到buffer2
        System.out.println("buffers "+l);
        buffer3.flip();
        buffer2.flip();
        buffer1.flip();
        System.out.println((char)buffer1.get());
        System.out.println((char)buffer2.get());
        System.out.println((char)buffer3.get());


        buffer1.clear();
        buffer2.clear();
        buffer3.clear();
      
        buffer1.put("scatter".getBytes());
        buffer2.put("gatter".getBytes());

        buffer3.flip();
        buffer2.flip();
        buffer1.flip();
       
        fc.write(buffers);//从buffers中写出,buffer1写完,则从buffer2中写。
       
原文地址:https://www.cnblogs.com/cgl-dong/p/13856179.html