JAVA NIO 之Channel

缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。Channel 通道就是将数据传输给 ByteBuffer 对象或者从 ByteBuffer 对象获取数据进行传输。
    Channel 用于在字节缓冲区和位于通道另一侧的实体(通常是一个文件或套接字)之间有效地传输数据。常用Channel有FileChannel、SocketChannel、DatagramChannel、ServerSocketChannelSocket 可以通过socket 通道的工厂方法直接创建。但是FileChannel 对象却只能通过在一个打开的 RandomAccessFile、FileInputStream 或 FileOutputStream对象上调用 getChannel( )方法来获取。通道可以以阻塞(blocking)或非阻塞(nonblocking)模式运行。只有面向流的(stream-oriented)的通道,如 sockets 和 pipes 才能使用非阻塞模式。

1.打开通道

a.打开FileChannel,以RandomAccessFile为例 :
            RandomAccessFile aFile = new RandomAccessFile("G:\\we.txt", "rw");
            FileChannel inChannel = aFile.getChannel();

b.打开SocketChannel:

SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("127.0.0.1",9011));

c.打开ServerSocketChannel

ServerSocketChannel ssc = ServerSocketChannel.open( );
ssc.socket( ).bind (new InetSocketAddress (9011));

 2.使用通道

a.使用FileChannel从指定文件中读取数据:
/**
 * @author tangquanbin
 * @date 2018/10/18 21:00
 */
public class ChannelTest {

    public static void main(String[] args) {

        //RandomAccessFile、FileInputStream 或 FileOutputStream的close()会同时关闭chanel。

        try (RandomAccessFile aFile = new RandomAccessFile("F:\\test.txt", "rw")) {
            //1.FileChannel 对象却只能通过在一个打开的 RandomAccessFile、FileInputStream 或 FileOutputStream对象上调用 getChannel( )方法来获取
            //2.三个 socket 通道类:SocketChannel、ServerSocketChannel 和 DatagramChannel有可以直接创建新 socket 通道的工厂方法
            //随机流, mode 的值可选 "r":可读,"w" :可写,"rw":可读写;
            FileChannel inChannel = aFile.getChannel();

            ByteBuffer buf = ByteBuffer.allocate(48);

            int bytesRead = inChannel.read(buf);
            while (bytesRead != -1) {
                //通过flip()方法将Buffer从写模式切换到读模式
                buf.flip();
                while (buf.hasRemaining()) {
                    System.out.print((char) buf.get());
                }
                //两种方式能清空缓冲区:调用clear()或compact()方法,clear()方法会清空整个缓冲区。compact()方法只会清除已经读过的数据
                buf.clear();
                bytesRead = inChannel.read(buf);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 b.在两个通道中复制数据:

/**
 * 在通道之间复制数据
 * @author tangquanbin
 * @date 2018/10/18 21:12
 */
public class ChannelCopy {

    public static void main(String[] argv) throws IOException {
        FileInputStream inputStream = new FileInputStream("G:\\test.txt");
        FileChannel inChanel = inputStream.getChannel();
        FileOutputStream outputStream = new FileOutputStream("G:\\copyTest.txt");
        FileChannel outChanel = outputStream.getChannel();
        //channelCopy1 channelCopy2方法都是两个通道复制数据
        channelCopy1(inChanel, outChanel);
//        channelCopy2 (inChanel, outChanel);
        inChanel.close();
        outChanel.close();
    }

    /**
     *
     * @param inChanel
     * @param outChanel
     * @throws IOException
     */
    private static void channelCopy1(FileChannel inChanel, FileChannel outChanel) throws IOException {
        ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024);

        //将数据从输入通道读入缓冲区
        while (inChanel.read(buffer) != -1) {
            // buffer切换到读模式
            buffer.flip();

            while (buffer.hasRemaining()) {
                //如果buffer中有数据就将数据写入输出通道
                outChanel.write(buffer);
            }
            // buffer切换到写模式,确保缓冲区为空,准备继续填充数据
            buffer.clear();
        }
    }

    private static void channelCopy2(FileChannel inChanel, FileChannel outChanel) throws IOException {
        ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024);
        while (inChanel.read(buffer) != -1) {
            // Prepare the buffer to be drained
            buffer.flip();
            // Write to the channel; may block
            outChanel.write(buffer);
            // If partial transfer, shift remainder down
            // If buffer is empty, same as doing clear( )
            buffer.compact();
        }
        // EOF will leave buffer in fill state
        buffer.flip();
        // Make sure that the buffer is fully drained
        while (buffer.hasRemaining()) {
            outChanel.write(buffer);
        }
    }

 c.注意:一个连接到只读文件的 Channel 实例不能进行写操作,即使该实例所属的类可能有 write( )方法;FileChannel 实现 ByteChannel

下面是ByteChannel的源码:

public interface ByteChannel
    extends ReadableByteChannel, WritableByteChannel
{

}

可以看到ByteChannel其实什么也不做,这样设计起到了聚集的作用。

FileChannel 接口既有write也有read方法,为何说不能写呢?那是当使用只读流获取通道时:

/**
 * @author tangquanbin
 * @date 2018/10/18 21:15
 */
public class ChannelOnlyReadTest {
    public static void main(String[] args) {
        try {
            ByteBuffer buffer = ByteBuffer.allocate(48);
            // 但是从 FileInputStream 对象的getChannel( )方法获取的 FileChannel 对象是只读的,因为 FileInputStream 对象总是以 read-only 的权限打开文件
            FileInputStream input = new FileInputStream ("G:\\we.txt");
            FileChannel channel = input.getChannel( );
            // 抛出 java.nio.channels.NonWritableChannelException
            channel.write (buffer);
            //FileInputStream的close同时会关闭chanel
            input.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 上面的代码运行会报错:java.nio.channels.NonWritableChannelException

因为:从 FileInputStream 对象的getChannel( )方法获取的 FileChannel 对象是只读的,因为 FileInputStream 对象总是以 read-only 的权限打开文件。所以没有写权限。

之前我们用ServerSocket创建服务器的栗子中服务器是阻塞的。那NIO怎么实现非阻塞呢?

上面讲的FileChannel 是非阻塞的,因为没有依靠SelectableChannel 超类。全部 socket 通道类(DatagramChannel、 SocketChannel 和 ServerSocketChannel) 都可以设置成非阻塞的。
  这里就要用到SelectableChannel 的方法

public abstract SelectableChannel configureBlocking(boolean block)
        throws IOException;

  通道设置configureBlocking方法为false就表示非阻塞模式。

下面是一个客服端和服务器代码说明;

客服端:

/**
 * @author tangquanbin
 * @date 2018/10/18 21:08
 */
public class ScoketChannelClient {

    public static void main(String[] args) {

        SocketChannel socketChannel = null;

        try {
            ByteBuffer buffer = ByteBuffer.wrap ("客服端".getBytes());
            //获取SocketChannel
            socketChannel = SocketChannel.open();
            //设置通道为非阻塞
            socketChannel.configureBlocking(false);
            //连接
            socketChannel.connect(new InetSocketAddress("127.0.0.1",9011));

            //connect(InetSocketAddress) 等价于 open(InetSocketAddress)
            //SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9010));

            //一些判断
            if (!socketChannel.isBlocking()){
                System.out.println("非阻塞");
            }
            while (!socketChannel.finishConnect()){
                System.out.println("连接没有建立");
            }
            System.out.println("连接已建立");
            if (socketChannel.isConnected()){
                System.out.println("连接已建立");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                assert socketChannel != null;
                socketChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

 服务器:

package com.nio.java;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

/**
 * ServerSocketChannel
 * @author tangquanbin
 * @date 2018/10/18 21:29
 */
public class ChannelAccept
{
    public static final String HELLO = "Hello I am server.\r\n";
    public static void main (String [] argv)throws Exception{
        int port = 9011;
        if (argv.length > 0) {
            port = Integer.parseInt (argv [0]);
        }
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open( );
        serverSocketChannel.socket( ).bind (new InetSocketAddress (port));
        //非阻塞模式
        serverSocketChannel.configureBlocking (false);
        while (true) {
            System.out.println ("Waiting for connections");
            //如果以非阻塞模式被调用,当没有传入连接在等待时,ServerSocketChannel.accept( )会立即返回 null
            SocketChannel sc = serverSocketChannel.accept( );
            if (sc == null) {
                // 没有连接。。。睡觉
                Thread.sleep (2000);
            } else {
                System.out.println ("Incoming connection from: "+ sc.socket().getRemoteSocketAddress( ));
                //wrap buffer的另一种创建方式
                ByteBuffer buffer = ByteBuffer.wrap (HELLO.getBytes( ));
                //可以使用 rewind()后退,重读已经被flip()的缓冲区中的数据。
                buffer.rewind( );
                sc.write (buffer);
            }
        }
    }
}

 Scatter/Gather( ScatteringByteChannel 和 GatheringByteChannel
    矢量化的 I/O 使您可以在多个缓冲区上自动执行一个 I/O 操作。使用多个而不是单个缓冲区来保存数据的读写方法。处理复杂数据结构时用。

原文地址:https://www.cnblogs.com/tangquanbin/p/9809907.html

时间: 2024-08-01 18:15:47

JAVA NIO 之Channel的相关文章

java NIO-Channel

基本简介 Java NIO(New IO)是一个可以替代标准Java IO API的IO API(从Java 1.4开始),Java NIO提供了与标准IO不同的IO工作方式. Java NIO: Channels and Buffers(通道和缓冲区) 标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中. Java NIO: Non-blocking IO(非阻塞IO) Java

Java NIO (2) Channel

Java NIO Channel Java NIO Channels are similar to streams with a few differences: You can both read and write to a Channels. Streams are typically one-way (read or write). Channels can be read and written asynchronously. Channels always read to, or w

java nio之 channel通道(二)

java nio 通道上一篇文章里就讲述过,channel总是写数据的时候,要先把数据写入到bytebuffer,读数据的时候总是要先从channel中读入到bytebuffer.如下图,这个图是好多知名博客常用的图,很好理解这个channel. channel分为一下几种: FileChannel SocketChannel ServerSocketChannel DatagramChannel FileChannel: 经常说的FileChannel都是拿下面的例子说事 代码如下: pack

Java NIO(1)----Channel 和 Buffer

Java NIO 由以下几个核心部分组成: Channels Buffers Selectors 虽然Java NIO 中除此之外还有很多类和组件,但在我看来,Channel,Buffer 和 Selector 构成了核心的API.其它组件,如Pipe和FileLock,只不过是与三个核心组件共同使用的工具类.因此,在概述中我将集中在这三个组件上.其它组件会在单独的章节中讲到. Channel 和 Buffer 基本上,所有的 IO 在NIO 中都从一个Channel 开始.Channel 有点

Java NIO (5) Channel to Channel Transfers

Java NIO Channel to Channel Transfers In Java NIO you can transfer data directly from one channel to another, if one of the channels is a FileChannel. The FileChannel class has a transferTo() and a transferFrom() method which does this for you. trans

java nio之channel

一.通道(Channel):由 java.nio.channels 包定义的.Channel 表示 IO 源与目标打开的连接.Channel 类似于传统的"流".只不过 Channel本身不能直接访问数据,Channel 只能与Buffer 进行交互. 二.Channel重要实现 FileChannel:操作文件的读写 SocketChannel:通过TCP读写网络数据 ServerSocketChannel:监听TCP连接,你能利用它创建一个最简单的Web服务器 DatagramCh

JAVA NIO(二)Channel通道

Channel概述 Channel是一个通道,可以通过它读取和写入数据,通道与流的不同之处在于通道是双向的,流是单向的:NIO中通过channel封装了对数据源的操作,通过channel 我们可以操作数据源,但又不必关心数据源的具体物理结构.在大多数应用中,channel与文件描述符或者socket是一一对应的.Channel用于在字节缓冲区和位于通道另一侧的实体(通常是一个文件或套接字)之间有效地传输数据.channel中所有数据都通过 Buffer 对象来处理.您永远不会将字节直接写入通道中

《JAVA NIO》Channel

1.类图 Channle主要分为两类:File操作对应的FIleChannel和Stream操作对应的socket的3个channel.问题:stream操作除了socket还有其他的吧?例如串口等 3.1.1打开通道通道可以以多种方式创建.Socket 通道有可以直接创建新socket 通道的工??方法.但是一个FileChannel 对象却只能通过在一个打开的RandomAccessFile.FileInputStream 或 FileOutputStream对象上调用getChannel(

java NIO中的buffer和channel

缓冲区(Buffer):一,在 Java NIO 中负责数据的存取.缓冲区就是数组.用于存储不同数据类型的数据 根据数据类型不同(boolean 除外),提供了相应类型的缓冲区:ByteBufferCharBufferShortBufferIntBufferLongBufferFloatBufferDoubleBuffer 上述缓冲区的管理方式几乎一致,通过 allocate() 获取缓冲区 二.缓冲区存取数据的两个核心方法:put() : 存入数据到缓冲区中get() : 获取缓冲区中的数据