java NIO的认识

1. 简介

        Java NIO(New IO)是从JDK1.4版本开始引入的一个新的IO API,可以替代标准的java IO API,NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。
与IO的主要区别:

IONIO
面向流(Stream Oriented)面向缓冲区(Buffer  Oriented)
阻塞IO(Blocking IO)非阻塞IO(Non Blocking IO)
选择器(Selectors)

        Java NIO系统的核心在于:通道(Channel)和缓冲区(Buffer)。通道表示打开到IO设备(例如;文件、套接字)的连接。若需要使用NIO系统,需要获取用于连接IO设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区,对数据进行处理。

        简而言之,channel负责传输,buffer负责存储

2. 缓冲区——Buffer

2.1 缓冲区        

        一个用于特定基本数据类型的容器,由java.nio包定义的,所有缓冲区都是Buffer抽象类的子类。

        Java NIO中的Buffer主要用于与NIO通道进行交互,数据是从通道读入缓冲区,从缓冲区写入通道中的。

2.2 Java NIO 缓冲区

        在 Java NIO 中负责数据的存取。缓冲区就是数组。用于存储不同数据类型的数据,根据数据类型不同(boolean 除外),提供了相应类型的缓冲区:ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer。

        上述缓冲区的管理方式几乎一致,通过 allocate() 获取缓冲区的。

static XxxBuffer allocate(int capacity):创建一个容量为capacity的XXXBuffer对象。

2.3 缓冲区的基本属性

1)容量(capacity):表示buffer最大数据容量,缓冲区容量不能为负,并且创建后不能更改

2)限制(limit):第一个不应该读取或写入的数据的索引,即位于limit后的数据不可读写。缓冲区的限制不能为负,并且不能大于其容量。

3)位置(position):下一个要读取或写入的数据的索引。缓冲区的位置不能为负,并且不能大于其限制

4)标记(Mark)与重置(reset):标记是一个索引,表示记录当前 position 的位置。可以通过 reset() 恢复到 mark 的位置

标记、位置、限制、容量遵守以下不变式:0 <= mark <= position <= limit <= capacoty

2.4 缓冲区存取数据的两个核心方法

1)put() : 存入数据到缓冲区中

2)get() : 获取缓冲区中的数据

例如:

public void test1(){
	String str = "abcde";
	
	//1. 分配一个指定大小的缓冲区
	ByteBuffer buf = ByteBuffer.allocate(1024);
	
	System.out.println("-----------------allocate()----------------");
	System.out.println(buf.position());
	System.out.println(buf.limit());
	System.out.println(buf.capacity());
	
	//2. 利用 put() 存入数据到缓冲区中
	buf.put(str.getBytes());
	
	System.out.println("-----------------put()----------------");
	System.out.println(buf.position());
	System.out.println(buf.limit());
	System.out.println(buf.capacity());
	
	//3. 切换读取数据模式
	buf.flip();
	
	System.out.println("-----------------flip()----------------");
	System.out.println(buf.position());
	System.out.println(buf.limit());
	System.out.println(buf.capacity());
	
	//4. 利用 get() 读取缓冲区中的数据
	byte[] dst = new byte[buf.limit()];
	buf.get(dst);
	System.out.println(new String(dst, 0, dst.length));
	
	System.out.println("-----------------get()----------------");
	System.out.println(buf.position());
	System.out.println(buf.limit());
	System.out.println(buf.capacity());
	
	//5. rewind() : 可重复读
	buf.rewind();
	
	System.out.println("-----------------rewind()----------------");
	System.out.println(buf.position());
	System.out.println(buf.limit());
	System.out.println(buf.capacity());
	
	//6. clear() : 清空缓冲区. 但是缓冲区中的数据依然存在,但是处于“被遗忘”状态
	buf.clear();
	
	System.out.println("-----------------clear()----------------");
	System.out.println(buf.position());
	System.out.println(buf.limit());
	System.out.println(buf.capacity());
	
	System.out.println((char)buf.get());
	
}

2.5 直接缓冲区与非直接缓冲区

1)直接缓冲区:通过 allocateDirect() 方法分配直接缓冲区,将缓冲区建立在物理内存中,可以提高效率。直接缓冲区还可以通过FileChannel的map()方法将文件区域直接映射到内存中来创建,该方法返回MappedByteBuffer

2)非直接缓冲区:通过 allocate() 方法分配缓冲区,将缓冲区建立在 JVM 的内存中

可以调用其isDirect()方法来确定缓冲区是什么缓冲区

2.6 Buffer的常用方法

方法描述
Buffer clear()清空缓冲区并返回对缓冲区的引用
Buffer flip()将缓冲区的界限设置为当前位置,并将当前位置重置为0
int capacity()返回 Buffer 的 capacity 大小
boolean hasRemaining()判断缓冲区是否还存在元素
int limit()返回Buffer的界限(limit)的位置
Buffer limit(int n)设置缓冲区界面(limit)为n,并返回一个具有新的界限的Buffer对象
Buffer mark()对缓冲区设置标记
int position()返回缓冲区的当前位置position
Buffer position(int n)设置缓冲区的当前位置为n,并返回修改后的Buffer对象
int remaining()返回position和limit之间的元素个数
Buffer reset()将位置position转到以前设置的mark所在的位置
Buffer rewind()将位置设为0,取消设置的mark

2.7 缓冲区的数据操作

Buffer所有子类提供了两个用于数据操作的方法:get()与put()

2.7.1 获取Buffer中的数据

1)get():读取单个字节

2)get(byte[] dest):批量读取多个字节到dest字节数组中

3)get(int index):读取指定索引位置的字节(不会移动position)

2.7.2 放入数据到Buffer中

1)put(byte b):将给定的单个字节写入到缓冲区的当前位置

2)put(byte[] src):将src中的字节写入缓冲区的当前位置

3)put(int index,byte b):将指定字节写入到缓冲区的索引位置(不会移动position)

3. 通道——Channel

        由java.nio.channels包定义的,Channel表示IO源与目标打开的连接,Channel;类似于传统的流,只不过Channel本身不能直接访问数据,Channel只能与Buffer进行交互。Java为Channel接口提供了如下几个重要的实现类:

1)FileChannel:用于读取、写入、映射和操作文件的通道

2)DatagramChannel:通过UDP读写网络中的数据通道

3)SocketChannel:通过TCP读写网络中的数据

4)ServerSocketChannel:可以监听新进来的TCP连接,对每一个新进来的连接都会创建一个SocketChannel

3.1 获取通道

3.1.1 getChannel获取

        Java 针对支持通道的类提供了 getChannel() 方法,

1)本地 IO:FileInputStream/FileOutputStream、RandomAccessFile

2)网络IO:Socket、ServerSocket、DatagramSocket

public void test01() {
	try (
			FileInputStream fis = new FileInputStream("1.jpg");
			FileOutputStream fos = new FileOutputStream("2.jpg");
	){
		// 1. 获取通道
		FileChannel inChannel = fis.getChannel();
		FileChannel outChannel = fos.getChannel();
		
		// 2. 分配指定大小的缓冲区
		ByteBuffer buffer = ByteBuffer.allocate(1024);
		// 3. 将通道中的数据存入缓冲区中
		while(inChannel.read(buffer) != -1) {
			buffer.flip(); 					// 切换为读模式
			outChannel.write(buffer); 		// 将缓冲区中的数据写入通道中
			buffer.clear();					// 清空缓冲区
		}
	} catch (Exception e) {
		e.printStackTrace();
	}
}

3.1.2 通道的静态方法 open()

在 JDK 1.7 中的 NIO.2 针对各个通道提供了静态方法 open()

//使用直接缓冲区完成文件的复制(内存映射文件)
@Test
public void test2() throws IOException{//2127-1902-1777
	long start = System.currentTimeMillis();
	
	FileChannel inChannel = FileChannel.open(Paths.get("d:/1.mkv"), StandardOpenOption.READ);
	FileChannel outChannel = FileChannel.open(Paths.get("d:/2.mkv"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);
	
	//内存映射文件
	MappedByteBuffer inMappedBuf = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
	MappedByteBuffer outMappedBuf = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());
	
	//直接对缓冲区进行数据的读写操作
	byte[] dst = new byte[inMappedBuf.limit()];
	inMappedBuf.get(dst);
	outMappedBuf.put(dst);
	
	inChannel.close();
	outChannel.close();
	
	long end = System.currentTimeMillis();
	System.out.println("耗费时间为:" + (end - start));
}

注意:StandardOpenOption.CREATE_NEW与StandardOpenOption.CREATE的区别,StandardOpenOption.CREATE_NEW当文件以及存在的时候,会报错,而StandardOpenOption.CREATE不会。

3.1.3 Files的静态方法newByteChannel()

在 JDK 1.7 中的 NIO.2 的 Files 工具类提供了静态方法 newByteChannel(),用于获取通道

3.2 通道之间的数据传输

3.2.1 写数据

1)通过write方法

int bytesWritten = inChannel.write(buf)

2)通过transferFrom方法

outChannel.transferFrom(inChannel, 0, inChannel.size());

3)使用transferTo方法

inChannel.transferTo(0, inChannel.size(), outChannel);

3.2.2 读数据

例如:int bytesRead = inChannel.read(buf)

while(inChannel.read(buf) != -1){
	buf.flip(); //切换读取数据的模式
	//④将缓冲区中的数据写入通道中
	outChannel.write(buf);
	buf.clear(); //清空缓冲区
}

4. 分散与聚集

4.1 分散读取

分散读取(Scattering Reads)是指从Channel中读取的数据“分散”到多个Buffer中。

注意:按照缓冲区的顺序,从channel中读取的数据依次将Buffer填满

public void test02() throws IOException {
	//1. 获取通道
	// RandomAccessFile channel = new RandomAccessFile("1.txt", "rw");
	FileChannel channel = new FileInputStream("1.txt").getChannel();
	ByteBuffer b1 = ByteBuffer.allocate(100); 
	ByteBuffer b2 = ByteBuffer.allocate(1024); 
	ByteBuffer[] bs = {b1,b2};
	
	channel.read(bs);
	
	for(ByteBuffer b : bs) {
		b.flip();
		System.out.println(new String(b.array(), 0, b.limit()));
		System.out.println("---------------");
	}
	
	FileChannel channel2 = new FileOutputStream("2.txt").getChannel();
	channel2.write(bs);
}

4.2 聚集写入

聚集写入(Gathering Writes)是指将多个Buffer中的数据“聚集”到channel

注意:按照缓冲区的顺序,写入position个limit之间的数据到channel

FileChannel channel2 = new FileOutputStream("2.txt").getChannel();
channel2.write(bs);

5. 字符集——Charset

1)编码:字符串 -> 字节数组
2)解码:字节数组  -> 字符串

//字符集
@Test
public void test6() throws IOException{
	Charset cs1 = Charset.forName("GBK");
	
	//获取编码器
	CharsetEncoder ce = cs1.newEncoder();
	
	//获取解码器
	CharsetDecoder cd = cs1.newDecoder();
	
	CharBuffer cBuf = CharBuffer.allocate(1024);
	cBuf.put("你好世界!");
	cBuf.flip();  // 切换为读模式
	
	//编码
	ByteBuffer bBuf = ce.encode(cBuf);
	
	//解码
	bBuf.flip(); // 切换为读模式
	CharBuffer cBuf2 = cd.decode(bBuf);
	System.out.println(cBuf2.toString());
	
	System.out.println("------------------------------------------------------");
	
	Charset cs2 = Charset.forName("GBK");
	bBuf.flip(); // 切换为读模式
	CharBuffer cBuf3 = cs2.decode(bBuf);
	System.out.println(cBuf3.toString());
}

6. 阻塞与非阻塞

        传统的IO流都是阻塞式的,也就是说,当一个线程调用read()或者write()时,该线程被阻塞,直到有一些数据被读取或写入,该线程在此期间不能执行其他任务。因此,在完成网络通信进行IO操作时,由于线程会阻塞,所以服务器端必须为每个客户端都提供一个独立的线程进行处理,当服务器端需要处理大量客户端时,性能急剧下降。
        java NIO是非阻塞模式的,当线程从某通道进行读写数据时,若没有数据可用,该线程可以进行其他任务,线程通常将非阻塞IO的空闲时间用于在其他通道上执行IO操作,所以单独的线程可以管理多个输入和输出通道,因此,NIO可以让服务器端使用一个或有限几个线程来同时处理连接到服务器端的所有客户端。

使用 NIO 完成网络通信的三个核心

1)通道(Channel):负责连接

java.nio.channels.Channel 接口:
	|--SelectableChannel
		|--SocketChannel
		|--ServerSocketChannel
		|--DatagramChannel
		|--Pipe.SinkChannel
		|--Pipe.SourceChannel

2)缓冲区(Buffer):负责数据的存取

3)选择器(Selector):是 SelectableChannel 的多路复用器。用于监控 SelectableChannel 的 IO 状况

7. 阻塞IO

使用NIO完成网络通信的例子(阻塞式)

例1:

package com.bjc.nio.block;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

import org.junit.Test;

public class TestBlockingNIO {
	
	/** 将本地文件发送给服务端
	 * @throws Exception
	 */
	@Test
	public void client() throws Exception {
		// 1. 创建网络连接
		SocketChannel open = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999));
		
		// 2. 创建文件通道
		FileChannel fileChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
		
		// 3. 创建缓冲区
		ByteBuffer byteBuffer = ByteBuffer.allocate(100);
		
		// 4. 发送数据
		while(fileChannel.read(byteBuffer) != -1) {
			byteBuffer.flip(); // 切换为读模式
			open.write(byteBuffer);
			byteBuffer.clear();
		}
		
		// 5. 关闭管道
		fileChannel.close();
		open.close();
	}
	
	/**  接收客户端的数据
	 * @throws Exception 
	 */
	@Test
	public void server() throws Exception {
		// 1. 获取通道
		ServerSocketChannel open = ServerSocketChannel.open();
		// 2. 绑定端口号
		open.bind(new InetSocketAddress(9999));
		// 3. 获取客户端连接通道
		SocketChannel socketChannel = open.accept();

		// 4. 缓冲区
		ByteBuffer buffer = ByteBuffer.allocate(100);
		
		// 5. 写入磁盘的文件通道
		FileChannel fileChannel = FileChannel.open(Paths.get("3.jpg"), StandardOpenOption.WRITE,StandardOpenOption.CREATE);
		
		while(socketChannel.read(buffer) != -1) {
			buffer.flip();
			fileChannel.write(buffer);
			buffer.clear();
		}
		
		// 6. 关闭通道
		fileChannel.close();
		open.close();
		
	}
	
}

例2:带返回消息

package com.bjc.nio.block;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

import org.junit.Test;

public class TestBlockingNio2 {
	
	/** 客户端发送数据
	 * @throws Exception 
	 */
	@Test
	public void client() throws Exception {
		SocketChannel client = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999));
		FileChannel fileChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
		ByteBuffer buffer = ByteBuffer.allocate(1024);
		while(fileChannel.read(buffer) != -1) {
			buffer.flip();
			client.write(buffer);
			buffer.clear();
		}
		// 关闭客户端连接
		client.shutdownOutput();
		
		// 读取服务端返回的数据
		int len = 0;
		while((len = client.read(buffer)) != -1) {
			buffer.flip();
			System.out.println(new String(buffer.array(),0,len));
			buffer.clear();
		}
		
		// 关闭连接
		fileChannel.close();
		client.close();
	}
	
	@Test
	public void server() throws Exception {
		ServerSocketChannel server = ServerSocketChannel.open();
		server.bind(new InetSocketAddress(9999));
		SocketChannel socketChannel = server.accept();
		ByteBuffer buffer = ByteBuffer.allocate(1024);
		FileChannel fileChannel = FileChannel.open(Paths.get("6.jpg"), StandardOpenOption.WRITE,StandardOpenOption.CREATE);
		while(socketChannel.read(buffer)!= -1) {
			buffer.flip();
			fileChannel.write(buffer);
			buffer.clear();
		}
		
		// 然后给客户端回传消息
		buffer.put("服务器接收数据成功!".getBytes());
		buffer.flip();
		socketChannel.write(buffer);
		
		// 关闭连接
		socketChannel.close();
		fileChannel.close();
	}
	
}

8 非阻塞式IO

8.2 选择器——Selector

8.2.1简介

        选择器是SelectableChannel对象的多路复用器,Selector可以同时监控多个SelectableChannel的IO状况,也就是说,利用Selector可使一个单独的线程管理多个Channel。Selector是非阻塞IO的核心。

SelectableChannel的结构如图:

8.2.2 常用API

1)创建Selector

Selector selector = Selector.open();

2)向选择器注册通道

serverChannel.register(sel, SelectionKey.OP_ACCEPT);

注意:

2.1 SelectionKey是一个抽象类,选择器对通道的监听事件就是通过该值来决定的。

2.2 SelectionKey有4个常量,分别是:

      SelectionKey.OP_READ(读)、

      SelectionKey.OP_WRITE(写)、

      SelectionKey.OP_CONNECT(连接)、

      SelectionKey.OP_ACCEPT(接收)

2.3 若注册的监听器不止一个,可以使用“位或”操作符连接

serverChannel.register(sel, SelectionKey.OP_ACCEPT | SelectionKey.OP_CONNECT);

3)其他API

方法描述
Set<SelectionKey> keys()所有的SelectionKey集合,代表注册在Selector上的channel
selectedKeys()被选择的SelectionKey集合,返回此Selector的已选择键集
int select()监控所有注册的channel,当它们中间有需要处理的IO操作时,该方法返回,并将对应的SelectionKey加入被选择的SelectionKey集合中,该方法返回这些Channel的数量
int select(long timeout)可以设置超时时长的select()操作
int selectNow()执行一个立即返回的select()操作,该方法不会阻塞线程
Selector wakeup()使一个还未返回的select()方法立即返回
void close() 关闭该选择器

8.2.3 SelectionKey

        SelectionKey表示SelectableChannel和selector之间的注册关系,每次向选择器注册通道时,就会选择一个事件(选择键)。选择键包含两个表示为整数值的操作集。操作集的每一位都表示该键的通道所支持的一类可选择操作

SelectionKey有四个常量

      SelectionKey.OP_READ(读)、

      SelectionKey.OP_WRITE(写)、

      SelectionKey.OP_CONNECT(连接)、

      SelectionKey.OP_ACCEPT(接收)

SelectionKey的方法

方法描述
int interestOps() 获取感兴趣事件集合
int readyOps()获取通道以及准备就绪的操作的集合
SelectableChannel channel()获取注册通道
Selector selector()返回选择器
Boolean isReadable()检测Channel中读事件是否就绪
Boolean isWritable()检测Channel中写事件是否就绪
Boolean isConnectable()检测Channel中连接是否就绪
Boolean isAcceptable()检测Channel中接收是否就绪

8.3 SocketChannel

        SocketChannel是一个连接到TCP网络套接字的通道。Java NIO中的ServerSocketChannel是一个可以监听新进来的TCP连接的通道,就像标准IO中的ServerSocket一样

操作步骤:

1)打开SocketChannel
2)读写数据
3)关闭SocketChannel

例如:

package com.bjc.nio.block;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

import org.junit.Test;

public class TestNoneBlockingNIO {
	
	@Test
	public void client() throws Exception{
		// 1. 获取管道
		SocketChannel clientChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999));
		// 2. 切换为非阻塞
		clientChannel.configureBlocking(false);
		// 3. 定义缓冲区
		ByteBuffer buf = ByteBuffer.allocate(1024);
		// 4. 写数据
		buf.put("你好服务器!".getBytes());
		buf.flip();
		clientChannel.write(buf);
		clientChannel.close();
	}
	
	@Test
	public void server() throws Exception {
		// 1. 获取服务端管道
		ServerSocketChannel serverChannel = ServerSocketChannel.open();
		// 2. 绑定监听端口号
		serverChannel.bind(new InetSocketAddress(9999));
		// 3. 切换为非阻塞
		serverChannel.configureBlocking(false);
		// 4. 获取选择器
		Selector sel = Selector.open();
		// 5. 将通道注册到选择器上, 并且指定“监听接收事件”
		serverChannel.register(sel, SelectionKey.OP_ACCEPT);
		
		//6. 轮询式的获取选择器上已经“准备就绪”的事件
		while(sel.select() > 0) {
			// 7. 获取当前选择器中所有注册的“选择键(已就绪的监听事件)”
			Iterator<SelectionKey> keyIterator = sel.selectedKeys().iterator();
			while(keyIterator.hasNext()) {
				//8. 获取准备“就绪”的是事件
				SelectionKey key = keyIterator.next();
				//9. 判断具体是什么事件准备就绪
				if(key.isAcceptable()) { 
					//9.1 若“接收就绪”,获取客户端连接
					SocketChannel accept = serverChannel.accept();
					// 9.2 切换为非阻塞
					accept.configureBlocking(false);
					// 9.3 将该通道注册到选择器上
					accept.register(sel, SelectionKey.OP_READ);
				} else if(key.isReadable()) {
					//9.4. 获取当前选择器上“读就绪”状态的通道
					SocketChannel channel = (SocketChannel) key.channel();
					// 读取数据
					ByteBuffer buf = ByteBuffer.allocate(1024);
					int len = 0;
					// 读取数据
					while((len = channel.read(buf)) > 0) {
						buf.flip();
						String content = new String(buf.array(),0,len);
						System.out.println(content);
						buf.clear();
					}
				}
				// 8. 取消选择键 SelectionKey
				keyIterator.remove();
			}
		}
	}

}

8.4 DatagramChannel

        Java NIO中的DatagramChannel是一个能收发UDP包的通道
操作步骤:
1)打开DatagramChannel
2)接收/发送数据

例如:

package com.bjc.nio.block;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.time.LocalDateTime;
import java.util.Iterator;
import java.util.Scanner;

import org.junit.Test;

public class TestNoneBlockingNIO2 {
	
	/**发送端
	 * @throws Exception
	 */
	@Test
	public void send() throws Exception{
		DatagramChannel dc = DatagramChannel.open();
		ByteBuffer buf = ByteBuffer.allocate(1024);
		dc.configureBlocking(false);
		Scanner sc = new Scanner(System.in);
		while(sc.hasNext()) {
			String str = sc.next();
			buf.put(("发送时间:" + LocalDateTime.now().toString() + "   " + str).getBytes());
			buf.flip();
			// 发送消息
			dc.send(buf, new InetSocketAddress("127.0.0.1", 9999));
			buf.clear();
		}
	}
	
	/**接收端
	 * @throws Exception
	 */
	@Test
	public void receive() throws Exception {
		DatagramChannel dc = DatagramChannel.open();
		dc.configureBlocking(false);
		dc.bind(new InetSocketAddress(9999));
		Selector selector = Selector.open();
		dc.register(selector, SelectionKey.OP_READ);
		while(selector.select() > 0) {
			Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
			while(keys.hasNext()) {
				SelectionKey sk = keys.next();
				if(sk.isReadable()) {
					ByteBuffer buf = ByteBuffer.allocate(1024);
					// 接收消息
					dc.receive(buf);
					buf.flip();
					System.out.println(new String(buf.array(), 0, buf.limit()));
					buf.clear();
				}
			}
			keys.remove();
		}
	}

}

9. 管道——Pipe

        Java NIO管道是2个线程之间单向数据连接,Pipe有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取。

9.1 向管道写数据

//1. 获取管道
Pipe pipe = Pipe.open();

//2. 创建缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);

// 3. 定义sink管道
Pipe.SinkChannel sinkChannel = pipe.sink();
buf.put("通过单向管道发送数据".getBytes());
buf.flip();

// 4. 通过write方法写数据
sinkChannel.write(buf);

9.2 从管道读数据

//3. 从管道读数据需要获取source通道
Pipe.SourceChannel sourceChannel = pipe.source();
buf.flip();

// 通过read方法读数据
int len = sourceChannel.read(buf);
System.out.println(new String(buf.array(), 0, len));

完整的案例:

@Test
public void test1() throws IOException{
	//1. 获取管道
	Pipe pipe = Pipe.open();
	
	//2. 将缓冲区中的数据写入管道
	ByteBuffer buf = ByteBuffer.allocate(1024);
	
	Pipe.SinkChannel sinkChannel = pipe.sink();
	buf.put("通过单向管道发送数据".getBytes());
	buf.flip();
	sinkChannel.write(buf);
	
	//3. 读取缓冲区中的数据
	Pipe.SourceChannel sourceChannel = pipe.source();
	buf.flip();
	int len = sourceChannel.read(buf);
	System.out.println(new String(buf.array(), 0, len));
	
	sourceChannel.close();
	sinkChannel.close();
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值