Java NIO

欢迎访问:我的个人网站

NIO

NIO(Non Blocking IO / New IO)于JDK1.4引入,可用于代替标准的JavaIO API, NIO与原来的IO有着相同的作用于目的,但是二者在使用上以及特点上还是有较大区别。NIO相较于传统的IO能进行更加高效的读写操作。NIO与传统IO的区别主要体现在以下几个地方:

  • 传统IO是面向流(Stream)的, 但是NIO是面向缓冲区的(Buffer Oriented)。
  • 传统IO是阻塞的,但是NIO是非阻塞的。
  • NIO在使用上多了个选择器(Selectors)的概念
  • 传统IO是单向的(例:文件输入流,仅表示一个输入的过程),但是NIO的数据传送可以是双向(打开一个通道,以读写的模式打开)的。

在使用NIO的时候,涉及到两个概念:通道, 缓存区 , 它们可以视作为NIO的核心。通道表示一个从应用程序打开到IO设备(文件,网络套接字)的连接。而缓冲区则视作为在通道上装载数据的区域,我们可以操作位于通道上的缓冲区,进行对数据进行处理。形象化理解:通道可以理解为是连接两地(应用程序&IO设备)的铁路。而缓存区是位于这个铁路上的火车。火车可以装载货物在两地往返(数据传送&双向)。简而言之,通道负责进行传输工作,而缓存区负责进行数据的临时存储。下面就这两个概念进行进一步的展开:

1.缓存区

在Java NIO中负责进行数据的存取,缓存区的底层还是使用数据进行实现的,以存储不同类似的数据进行传递,根据我们使用的数据类型,JDK提供了与之对应的类进行装载,示例(部分):

  • ByteBuffer
  • CharBuffer
  • ShortBuffer
  • IntBuffer
  • LongBuffer

1.1相关属性

以上部分实现类均为抽象类Buffer的子类,Buffer类为其子类实现了一组通用的操作,使得各个具体类在使用上具有较强的通用性。就缓存区而言,它具有几个需要注意的属性:

  • capacity: 容量
  • limit: 可操作范围索引
  • position: 操作位置索引
  • mark: 标记索引

以上几个属性满足关系:0<=mark<=position<=limit<=capacity

capacity

前面说过,缓存区实际上还是使用数组结构作为其底层实现的,那么既然是数组,就必须要又一个固定的大小,而决定这个固定数组大小的值,就是capacity。这个值是在我们申请缓存区空间的时候进行指定的,在指定之后就无法再次进行改变,后续所要容纳的数据量也不能操作该大小,否则将导致java.nio.BufferOverflowException

DDDDDDDDDDDDDDDDDDDDD	capacity示例图  ddddddddddddddddddddddd

limit

针对一个缓冲区而言,该数据指示了一个缓冲区可以被操作的区域大小 , 换句话说,limit指定了缓存区的一个索引位置,在该索引位置之后的数据是禁止读写操作的。**当我们进行写操作的时候,该值与capacity的大小一致,因为写操作模式下,我们最多可以操作capacity大小的空间,limit也就理所当然的与其一致了。但是在写操作模式下,该值等于我们已经存储的数据长度。 ** 该值在切换模式时自动转换无需我们操心。

position

该数据是在对缓存区数据进行存取操作时,用于指示操作的索引位置, 具体如下:当写模式下,该值等于下一个即将被存储数据的索引位置,此时索引指向的位置是不包含数据的。在读模式下,该值等于准备被读取数据的索引位置,此时指向的位置是存在数据的。

mark

可以标记一个position的值到mark中,后续我们可以再使用相关方法恢复position的值而无需关心在此期间position做了何种变化。简单地说,mark在某个时间记录了position的值,在之后的某个时间可以将position恢复到之前标记的位置。

1.2相关方法

下面将就结合上面介绍的属性,说明针对缓存区操作的一些方法:

分配缓存区空间

ByteBuffer allocate(int capacity):
使用该方法可以建立一个指定大小的缓存区,该方法为其具体缓存区类型中的静态方法,所分配的空间仍属于JVM管理的内存范围

//源码, 在堆上建立一个缓存区
public static ByteBuffer allocate(int capacity) {
        if (capacity < 0)
            throw createCapacityException(capacity);
        return new HeapByteBuffer(capacity, capacity);
}

ByteBuffer allocateDirect(int capacity):
该方法也是用于分配缓存区空间,但是该方法是在直接内存 区域进行划分的,直接内存区域不属于JVM管理,在JVM内存模型之外。介于此特点可知,使用这个方法进行缓存区划分的话,将不再受制于堆大小的限制,但是仍然会受制于物理主机的实际内存大小以及分页,最大寻址空间等的限制。

无论使用何种方式进行申请,在后续的使用上几乎是没区别的,在申请一个缓存区之后,默认的各项属性如下:

ByteBuffer byteBuffer = ByteBuffer.allocate(10);
ByteBuffer byteBuffer = ByteBuffer.allocate(10);  
System.out.println("capacity :" +byteBuffer.capacity());  
System.out.println("limit : "+byteBuffer.limit());  
System.out.println("mark : "+byteBuffer.mark());  
System.out.println("position :" + byteBuffer.position());

//输出:
capacity :10
limit : 10
mark : java.nio.HeapByteBuffer[pos=0 lim=10 cap=10]
position :0
存数据:put()

将数据存入一个缓存区中,该方法提供了多种的重载形式,可以直接传入另一个缓存区对象,或者一个与之对应类型的数组,如下当我们存入数据之后,缓存区属性发生变化:
GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG

FloatBuffer floatBuffer = FloatBuffer.allocate(10);
byteBuffer.put("bestbigkk".getBytes());
System.out.println("position :" +byteBuffer.position());
System.out.println("limit :"+byteBuffer.limit());

//输出:
position :9
limit :10
读写切换:flip()

更改对一个缓存区的操作模式,更改实质上是对缓存区相关属性的调整:

ByteBuffer byteBuffer = ByteBuffer.allocate(10);
//写模式
byteBuffer.put("bestbigkk".getBytes());
System.out.println("position :" +byteBuffer.position());
System.out.println("limit :"+byteBuffer.limit());

//切换为读模式
byteBuffer.flip();
        
System.out.println("position :" +byteBuffer.position());
System.out.println("limit :"+byteBuffer.limit());

//输出
position :9
limit :10
position :0
limit :9

KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK

读取数据 get()

在正确切换读写模式的前提下,对数据进行读取操作, 方法仍旧提供了几种重载的形式,根据需要进行选择:

		ByteBuffer byteBuffer = ByteBuffer.allocate(10);
        //写模式
        byteBuffer.put("bestbigkk".getBytes());

        //切换为读模式
        byteBuffer.flip();

        System.out.println("position :" +byteBuffer.position());
        System.out.println("limit :"+byteBuffer.limit());

        //读取
        byte[] buff = new byte[byteBuffer.limit()];
        byteBuffer.get(buff);
        System.out.println(new String(buff));

        System.out.println("position :" +byteBuffer.position());
        System.out.println("limit :"+byteBuffer.limit());

		//输出
		position :0
		limit :9
		bestbigkk
		position :9
		limit :9

YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY

复位读取位置 rewind()

调用该方法将导致position重新指向数据的开始位置,以便进行再次的读取操作

清空缓存区 clear()

调用该方法以清空缓存区中的内容,需要注意的是该方法仅仅将缓存区的几个属性重新置为默认状态,数组中存在的数据实际上并没有被清空,只是允许后续对该数据进行覆盖操作:

//源码
public Buffer clear() {
        position = 0;
        limit = capacity;
        mark = -1;
        return this;
}
标记mark() / 复位 reset()

标记一个position的位置,并在后续使用reset()进行恢复,如果当前缓存区已经被清空,则无法进行标记操作.

获取可读取数据大小

可读取数据,也就是position与limit的差。如果positin小于limit则认为有数据还未读取

1.3 直接缓存区,非直接缓存区

在建立缓存区的时候,可以选择在JVM管理的堆上建立,此时的缓存区成为非直接缓存区,数据的交互仍旧要通过中间缓存空间进行,存在复制行为。同样的,我们也可以在直接内存建立缓存区,此时该区域将不再由JVM进行管理,因此使用直接缓存区对应用程序内存空间造成的影响很小。

在这里插入图片描述
使用直接直接缓存区可以通过FileChannel的map()方法将一个文件映射到内存中进行处理,该方法会返回一个MappedByteBuffer对象(内存映射文件),我们在这个区域操作对象就可以视为直接操作了磁盘的文件。

如果要在直接缓存区中创建一个内存映射文件,只有ByteBuffer支持。

2.通道

可以视为程序与设备(本地,网络套接字)所建立起的一个连接,与传统IO的流相似,通道与Buffer进行数据交互,因为其本身是不存取数据的,该工作由缓存区承担。通道存在几个主要的实现类,以上除FileChannel外,其余的通道均应用于网络IO环境下:

  • FileChannel
  • SocketChannel
  • ServerSocketChannel
  • DatagramChannel

2.1 获取通道

(1)JDK允许从支持通道的类中获取,使用getChannel()方法即可,包含:FileInputStream,FileOutputStream,RandomAccessFile,Socket, ServerSocket,DatagramSocket。

	    FileInputStream fis = new FileInputStream("d:\\waterPic.png");
        FileOutputStream fos = new FileOutputStream("d:\\1.png");
        //获取通道
        FileChannel inChannel = fis.getChannel();
        FileChannel outChannel = fos.getChannel();
        //分配缓存区
        ByteBuffer buff = ByteBuffer.allocate(1024);
        //将通道的数据存入缓存区
        while (inChannel.read(buff) != -1) {
            buff.flip();
            outChannel.write(buff);
            buff.clear();
        }
        inChannel.close();
        outChannel.close();
        fos.close();
        fis.close();

(2)JDK1.7之后,针对1中的各个类提供了一个静态方法open(Path path, OpenOption… options)来获取 。

path表示建立通道的文件路径,可以使用Paths类进行生成,可变参数options表示对这个文件进行的操作方式,StandardOpenOption类下定义了关于此操作的一些常量。CREATE常量表示:不存在就创建,存在就覆盖,与之对应的还有一个CREATE_NEW,表示如果文件存在就报错,如果不存在就创建文件。

需要注意的是,在指定映射模式MapModel的时候,必须被它通道本身的操作所支持,例如:针对outChannel,它支持读操作,写操作,新建操作。那么针对这个通道建立缓存区的时候,MapMode指定了READ_WRITE, 表示要进行读写操作,这两种操作在outChannel中是被支持的。反之则会抛出java.nio.channels.NonReadableChannelException (取消了outChannel对读操作的支持)

	    FileChannel inChannel = FileChannel.open(Paths.get("d:\\waterPic.png"), StandardOpenOption.READ);
        FileChannel outChannel = FileChannel.open(Paths.get("d:\\1.png"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE_NEW);

        //建立缓存区,与之前的allocate()获取到的内存空间是相同的,同样表示把一个文件的数据加载到内存空间中
        MappedByteBuffer inMappedByteBuffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
        MappedByteBuffer outMappedByteBuffer = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());

        //将缓存区的数据传递到另一个通道的缓存区中
        byte[] dst = new byte[inMappedByteBuffer.limit()];
        inMappedByteBuffer.get(dst);
        outMappedByteBuffer.put(dst);

        inChannel.close();
        outChannel.close();

(3)JDK1.7之后,使用工具类Files中的newByteChannel()获取通道。其余操作类似,这里不再赘述

FileChannel inChannel = Files.newByteChannel(Paths.get("d:\\waterPic.png"), StandardOpenOption.CREATE);

以上三种方式均可以进行文件的传输工作,通过指定一个较大的缓存区,我们可以在进行文件传输工作的时候,明显的观察到内存占用情况的改变,这里演示拷贝一个1.5GB的视频由E盘至D盘的情况,可以明显观察到:伴随着磁盘0的使用率上升,内存使用量也在增加,在全部读取到内存之后,开始进行复制工作,此时磁盘2使用率开始上升:
PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP

另外在两个通道转移数据的时候,可以使用更加较为便捷的方法:transferTo(int position, long count, WritableByteChannel target),transferFrom(ReadableByteChannel src, long position, long count)
这两个方法内部实际上与上面的原理类似,不过是对外而言隐藏了这些细节。

		FileChannel inChannel = FileChannel.open(Paths.get("e:\\Videos\\猩球崛起3.mp4"), StandardOpenOption.READ);
        FileChannel outChannel = FileChannel.open(Paths.get("d:\\1.mp4"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE_NEW);

        //方式1.将一个通道内的数据转移到另一个通道中,其内部还是使用了缓存区,简化了流程
        inChannel.transferTo(0, inChannel.size(), outChannel);
        //方式2.获取一个通道内的数据到本通道
        outChannel.transferFrom(inChannel, 0, inChannel.size());

        inChannel.close();
        outChannel.close();

3.分散(Scatter)与聚集(Gather)

简单而言,分散就是将一个通道所指向文件的数据存在多个缓存区中,这个分散的过程是按照缓存区的顺序进行的。聚集与之相反,将多个缓存区内的数据按照顺序存到一个通道中。这里仅仅使用一些代码演示:

3.1 分散

	    //文件内容: www.bestbigkk.com
        FileChannel inChannel = new RandomAccessFile("d:\\text.txt","rw").getChannel();

        ByteBuffer[] buffers = {ByteBuffer.allocate(2), ByteBuffer.allocate(2), ByteBuffer.allocate(2)};

        inChannel.read(buffers);

        buffers[2].flip();
        System.out.println(new String(buffers[2].array(), StandardCharsets.UTF_8));

        inChannel.close();
        //输出:be

3.2 聚集

		//约定此时buffers中已经按照3.1的代码读入了数据
		for (var b : buffers) {
            b.flip();
        }

        FileChannel outChannel = new RandomAccessFile("d:\\copy.txt", "rw").getChannel();
        outChannel.write(buffers);

        outChannel.close();
        inChannel.close();
        //copy.txt文件中的内容:www.be

4.编码解码

在使用其他Buffer类型的时候,有时候会遇到乱码的问题,此时可以使用相关操作更改解码编码方式:

		CharBuffer buffer = CharBuffer.allocate(32);
        buffer.put("最棒的大开开".toCharArray());
        buffer.flip();

        Charset charset = Charset.forName("UTF16");
        CharsetDecoder decoder = charset.newDecoder();
        CharsetEncoder encoder = charset.newEncoder();;

        ByteBuffer byteBuffer = encoder.encode(buffer);// UTF16
        CharBuffer charBuffer = decoder.decode(byteBuffer); // 还原为默认编码

        System.out.println(new String(byteBuffer.array(), 0, byteBuffer.limit(), StandardCharsets.UTF_16)); //以UTF16解码
        System.out.println(new String(charBuffer.array(), 0, charBuffer.limit()));// 以默认编码解码

5.选择器

选择器是实现NIO的一个核心组件,是SelectableChannel的多路复用器,用于监控SelectableChannel的IO状况,在它下面有以下集合子类:

  • SocketChannel
  • ServerSocketChannel
  • DatagramChannel
  • Pipe.SinkChannel
  • Pipd.SourceChannel

我们可以调用以上类的register(Selector sel, int ops)方法将一个通道注册到指定的选择器上面,选择器可使用以下方式获取:

Selector selector = Selector.open();

在绑定一个通道到选择器上面的时候,还需要指定要进行监听的操作类型,该操作类型通过register的第二个参数进行指定,有以下几个选择:

  • 读: SelectionKey.OP_READ = 1
  • 写: SelectionKey.OP_WRITE = 4
  • 连接: Selection.OP_CONNECT = 8
  • 接收: Selection.OP_ACCEPT = 16

如果要同时进行多种操作类型的监听,可以使用或运算符进行实现:

int opType = SelectionKey.OP_READ | SelectionKey.OP_ACCEPT;

使用选择器之前,需要使用configureBlocking(false);方法配置当前通道为非阻塞的。

6.阻塞与非阻塞式IO

Java NIO使我们可以进行非阻塞IO操作。比如说,单线程中从通道读取数据到buffer,同时可以继续做别的事情,当数据读取到buffer中后,线程再继续处理数据。写数据也是一样的。另外,非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。而Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情。

6.1 阻塞式的IO示例

如果一个IO的传输过程是阻塞式的,那么操作会在一个线程上等待直至其处理完成,这里写了一个简单的例子来说明这个问题。客户端向服务器发送数据,发送完成之后服务器进行一次响应以确认上传结果。在这里如果客户端发送完数据之后没有调用shutdowmOutput() 方法。则服务器会一直在线程上等待数据的继续传递而不会进行响应操作。

	@Test
    public void client() throws Exception{
        //建立本地文件通道
        FileChannel fileChannel = FileChannel.open(Paths.get("D:\\waterPic.png"), StandardOpenOption.READ);
        //获取远程连接通道
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 1314));
        System.out.println("已连接到服务器...");
        //获取本地文件内存映射
        MappedByteBuffer buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size());
        //写入到远程连接通道
        socketChannel.write(buffer);
        //主动终止输出, 如果不加这句,则服务器会在线程上一直等待数据传递而不会进行相应
        socketChannel.shutdownOutput();
        System.out.println("数据发送完成,等待回应...");

        //从远程连接通道读取服务器的回应
        ByteBuffer byteBuffer = ByteBuffer.allocate(128);
        while (socketChannel.read(byteBuffer) != -1) {
            byteBuffer.flip();
            System.out.println(byteBuffer.mark());
            System.out.println(new String(byteBuffer.array(), 0, byteBuffer.limit()));
            byteBuffer.clear();
        }

        socketChannel.close();
        fileChannel.close();
    }

	@Test
    public void server() throws Exception{
        //获取本地文件通道
        FileChannel fileChannel = FileChannel.open(Paths.get("D:\\copy.png"), StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW);
        //获取远程连接通道
        ServerSocketChannel socketChannel = ServerSocketChannel.open();
        socketChannel.bind(new InetSocketAddress(1314));
        System.out.println("监听中..");
        SocketChannel acceptChannel = socketChannel.accept();
        System.out.println("客户端已连接...");

        ByteBuffer buffer = ByteBuffer.allocate(512);
        //从远程连接通道读取数据到本地文件通道
        while (acceptChannel.read(buffer) != -1) {
            buffer.flip();
            fileChannel.write(buffer);
            buffer.clear();
        }

        //回应
        buffer.put("接收完成".getBytes());
        buffer.flip();
        acceptChannel.write(buffer);
        acceptChannel.shutdownOutput();

        fileChannel.close();
        acceptChannel.close();
        socketChannel.close();
    }

6.2 非阻塞式IO

	@Test
    public void client() throws Exception{
        //建立本地文件通道
        FileChannel fileChannel = FileChannel.open(Paths.get("D:\\waterPic.png"), StandardOpenOption.READ);
        MappedByteBuffer buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fileChannel.size());
        //获取远程连接通道
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 1314));
        System.out.println("已连接到服务器...");
        //切换为非阻塞模式
        socketChannel.configureBlocking(false);
        //传递数据到远程连接的通道中
        socketChannel.write(buffer);

        socketChannel.close();
        fileChannel.close();
    }

    @Test
    public void server() throws Exception{
        //获取远程连接通道
        ServerSocketChannel socketChannel = ServerSocketChannel.open();
        System.out.println("监听中..");
        socketChannel.bind(new InetSocketAddress(1314));
        //切换为非阻塞模式
        socketChannel.configureBlocking(false);
        //获取选择器
        Selector selector = Selector.open();
        //将远程连接通道注册到选择器上, 监听该通道的连接状态, 并指定选择键为“接收“
        socketChannel.register(selector, SelectionKey.OP_ACCEPT);
        //轮询获取该选择器上面已经就绪的通道, 这里的“就绪”指的是存在通道满足了选择器的一个或多个监听类型(读,写,可连接,可接收)
        while (selector.select() > 0) {
            System.out.println("Selectable:"+selector.select());
            //获取这个选择器中所有注册的选择键
            Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
            while (keyIterator.hasNext()) {
                //获取这个准备就绪的选择键
                SelectionKey selectionKey = keyIterator.next();
                //判断键类型,
                //是“可接收类型”
                if (selectionKey.isAcceptable()) {
                    SocketChannel acceptChannel = socketChannel.accept();
                    System.out.println("客户端已连接...");
                    acceptChannel.configureBlocking(false);
                    //将这个新建立的通道注册到选择器上面,并监听其“读”状态
                    acceptChannel.register(selector, SelectionKey.OP_READ);
                }else if(selectionKey.isReadable()){//是“可读”状态
                    System.out.println("传递数据");
                    //获取这个可读取的通道
                    SocketChannel channel = (SocketChannel) selectionKey.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(512);
                    //从远程连接通道读取数据到本地文件通道
                    int len = 0;
                    //获取本地文件通道
                    FileChannel fileChannel = FileChannel.open(Paths.get("D:\\"+UUID.randomUUID().toString()+".png"), StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW);
                    while ((len = channel.read(buffer))  > 0) {
                        buffer.flip();
                        fileChannel.write(buffer);
                        buffer.clear();
                    }
                    channel.close();
                    fileChannel.close();
                }
                keyIterator.remove();
            }
        }
        socketChannel.close();
    }

7. DatagramChannel

用于接收UDP包的一个通道,使用上与上面的其他通道基本类似,不同的是数据的发送以及接收对应的方法名为:send() / receive() 这里只做出一个简单的使用演示说明即可:

客户端多次发送数据到服务器,服务器接收并显示

	@Test
    public void client() throws Exception{
        while (true) {
            out.println("发送");
            DatagramChannel channel = DatagramChannel.open();
            channel.configureBlocking(false);
            ByteBuffer buffer = ByteBuffer.allocate(128);
            buffer.put((UUID.randomUUID().toString() +"\n").getBytes());
            buffer.flip();
            channel.send(buffer, new InetSocketAddress("127.0.0.1", 1314));
            buffer.clear();
            channel.close();
            Thread.sleep(2000);
        }
    }
	
	@Test
    public void server() throws Exception{
        DatagramChannel channel = DatagramChannel.open();
        channel.configureBlocking(false);
        channel.bind(new InetSocketAddress(1314));

        Selector selector = Selector.open();
        channel.register(selector, SelectionKey.OP_READ);

        ByteBuffer buffer = ByteBuffer.allocate(128);
        while (selector.select() > 0) {
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                if (selectionKey.isReadable()) {
                    DatagramChannel readChannel = (DatagramChannel) selectionKey.channel();
                    readChannel.receive(buffer);
                    buffer.flip();
                    out.println(new String(buffer.array(), 0, buffer.limit()));
                }
            }
            iterator.remove();
        }
        channel.close();
    }

8. Pipe

这种通道可以用于两个线程之间的通信,Pipe对象包含了两种通道类型:SinkChannel / SoureceChannel, 我们可以向SinkChannel通道写入信息,另一个线程从SourceChannel通道进行读取工作,以达到线程间通信的目的。 下面是对这个管道的简单使用:

public class Main {
    Pipe pipe;
    Pipe.SinkChannel sinkChannel;
    Pipe.SourceChannel sourceChannel;

    @Before
    public void before() throws Exception{
        pipe = Pipe.open();
        sinkChannel = pipe.sink();
        sourceChannel = pipe.source();
    }

    @Test
    public void pipe() throws InterruptedException {

        //sender
        new Thread(()->{
            String newData = "另一个线程, 你好!";
            ByteBuffer buf = ByteBuffer.allocate(48);
            buf.put(newData.getBytes());
            buf.flip();
            while(buf.hasRemaining()) {
                try {
                    sinkChannel.write(buf);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        Thread.sleep(1000);
        
        //recevier
        new Thread(()->{
            try {
                ByteBuffer buf = ByteBuffer.allocate(48);
                int bytesRead = sourceChannel.read(buf);
                System.out.println(new String(buf.array(), 0, bytesRead));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();

    }
}

enter image description here

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值