Java NIO之通道Channel

一、通道介绍

通道表示与诸如硬件设备,文件,网络套接字socket或程序组件之类的实体的开放连接的实体,该实体能够执行一个或多个不同的I/O操作(例如,读取或写入)。

通常,通道旨在确保多线程安全访问,如扩展和实现此接口的接口和类的规范中所述。

通常来说NIO中的所有IO都是从 Channel(通道) 开始的。

  • 从通道进行数据读取 :创建一个缓冲区,然后请求通道读取数据。

  • 从通道进行数据写入 :创建一个缓冲区,填充数据,并要求通道写入数据。

数据读取和写入操作图示:

Java NIO 通道Channel和流Stream非常相似,主要有以下几点区别:

  • 通道可以读write也可以写read,即通道是双向通行,流一般来说是单向的(只能读或者写,所以之前我们用流进行IO操作的时候需要分别创建一个输入流和一个输出流)。
  • 通道可以异步读写。
  • 通道总是基于缓冲区Buffer来读写,通道是必须和buffer结合使用的,流可以和buffer配套,也可以不用。

Java NIO中几个重要的Channel实现:

  • FileChannel: 用于文件的数据读写,该类是抽象类,需要在支持通道的文件对象或流中获取,如在RandomAccessFile,FileInputStream,FileOutputStream中的getChannel()方法,该方法返回连接到相同基础文件的文件通道。
  • DatagramChannel: 通过UDP读取网络中数据,该类是抽象类,通过该类的静态方法open()创建一个通道
  • SocketChannel: 通过TCP读写网络中数据,用来建立TCP连接(connect),该类是抽象类,通过该类的静态方法open()创建一个通道
  • ServerSocketChannel: 可以监听新进来的TCP连接,对每个新进来的连接都会创建SocketChannel ,一般在服务端使用。该类是抽象类,通过该类的静态方法open()创建一个通道

 类层次结构

二、通道使用例子 

RandomAccessFile

本地有一个raf.txt文件内容如下图,我们通过例子将该文件内容读取到内存中并打印,并在该文件中新添加一行内容

程序

package com;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class FileChannelRaf {
	public static void main(String args[]) throws IOException {
		// 1.创建一个RandomAccessFile(随机访问文件)对象
		RandomAccessFile randomAccessFile = new RandomAccessFile("D:\\raf.txt", "rw");
		// 2.通过RandomAccessFile对象的getChannel()方法获取它的FileChannel通道
		FileChannel inChannel = randomAccessFile.getChannel();
		// 3.创建一个数据缓冲区对象
		ByteBuffer buf = ByteBuffer.allocate(1024);
		// 4.从通道中读取数据到缓冲区
		int bytesRead = 0;
		while ((bytesRead = inChannel.read(buf)) != -1) {
			// System.out.println("Read " + bytesRead);

			// The limit is set to the current position and then the position is set to
			// zero. If the mark is defined then it is discarded.
			buf.flip();
			// Tells whether there are any elements between the current position and the
			// limit.
			while (buf.hasRemaining()) {
				System.out.print((char) buf.get());
			}
			// 清空缓冲区
			// The position is set to zero, the limit is set to the capacity, and the mark
			// is discarded.
			buf.clear();
		}
		// 5.清空缓冲区,向缓冲区put数据,将缓冲区数据写到通道
		buf.clear();
		buf.put("\r\nnewWriteString".getBytes());
		buf.flip();
		inChannel.write(buf);
		// 6.关闭通道
		inChannel.close();
		// 7.关闭RandomAccessFile对象
		randomAccessFile.close();
	}
}

运行结果:文件中的内容被读取到内存中并打印出来;在raf文件中多了一行数据,内容为newWriteString 

       

File FileInputStream FileOutputStream

本地有一个fileIn.txt文件内容如下图,我们通过例子将该文件内容读取到内容为空的fileOut.txt中,并在fileOut.txt文件中新添加一行内容

程序

package com;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class FileChannelStream {
	public static void main(String args[]) throws IOException {
		// 1.创建两个File对象
		File fileIn = new File("D:" + File.separator + "fileIn.txt");
		File fileOut = new File("D:" + File.separator + "fileOut.txt");
		FileInputStream fileInputStream = new FileInputStream(fileIn);
		FileOutputStream fileOutputStream = new FileOutputStream(fileOut);
		// 2.通过FileInputStream/FileOutputStream对象的getChannel()方法获取它的FileChannel通道
		FileChannel inChannel = fileInputStream.getChannel();
		FileChannel outChannel = fileOutputStream.getChannel();
		// 3.创建一个数据缓冲区对象
		ByteBuffer buf = ByteBuffer.allocate(1024);
		// 4.使用inChannel.read(dst)将inChannel中数据读取缓冲区,并使用outChannel.write(src)将缓冲区数据写到outChannel中
		int bytesRead = 0;
		while ((bytesRead = inChannel.read(buf)) != -1) {
			// The limit is set to the current position and then the position is set to
			// zero. If the mark is defined then it is discarded.
			buf.flip();
			// Writes a sequence of bytes to this channel from the given buffer.
			outChannel.write(buf);
			// 清空缓冲区
			// The position is set to zero, the limit is set to the capacity, and the mark
			// is discarded.
			buf.clear();
		}
		// 5.清空缓冲区,向缓冲区put数据,将缓冲区数据写到通道
		buf.clear();
		buf.put("\r\nnewWriteString".getBytes());
		buf.flip();
		outChannel.write(buf);
		// 6.关闭通道
		inChannel.close();
		outChannel.close();
		// 7.关闭Stream
		fileInputStream.close();
		fileOutputStream.close();
	}
}

 运行结果

SocketChannel ServerSocketChannel 

我们利用SocketChannel和ServerSocketChannel实现客户端与服务器端简单通信:在客户端输入一个加法算式发送到服务器端,服务器计算得到结果,将算式及结果再发送到客户端

程序

package com.socket2;

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

public class Server {
	public static void main(String args[]) {
		// 1.通过ServerSocketChannel 的open()方法创建一个ServerSocketChannel对象,open方法的作用:打开套接字通道
		try (ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()) {
			// 2.通过ServerSocketChannel绑定ip地址和port(端口号)
			serverSocketChannel.socket().bind(new InetSocketAddress("127.0.0.1", 8080));
			// 通过ServerSocketChannelImpl的accept()方法创建一个SocketChannel对象用户从客户端读/写数据
			SocketChannel socketChannel = serverSocketChannel.accept();
			// 3.创建数据的缓存区对象
			ByteBuffer byteBuffer = ByteBuffer.allocate(128);
			while (true) {
				System.out.println("等待客户端的消息……");
				// 4.从通道中读取客户端传送的数据到缓冲区中
				byteBuffer.clear();
				socketChannel.read(byteBuffer);
				StringBuilder stringBuffer = new StringBuilder();
				// 5.从缓冲区中get数据并进行解析
				byteBuffer.flip();
				while (byteBuffer.hasRemaining()) {
					stringBuffer.append((char) byteBuffer.get());
				}
				String formulaStr = stringBuffer.toString();
				System.out.println("客户端发送公式:" + formulaStr);
				if (stringBuffer.toString().equals("end")) {
					System.out.println("结束通信");
					break;
				}
				if (!stringBuffer.toString().contains("+")) {
					byteBuffer.clear();
					byteBuffer.put("不正确的加法公式".getBytes());
					byteBuffer.flip();
					socketChannel.write(byteBuffer);

				} else {
					// 6.进行计算,并将结果放入到缓冲区中
					String stra = formulaStr.substring(0, formulaStr.indexOf("+"));
					String strb = formulaStr.substring(formulaStr.indexOf("+") + 1, formulaStr.length());
					int a = Integer.parseInt(stra);
					int b = Integer.parseInt(strb);
					int sum = a + b;
					byteBuffer.clear();
					System.out.println("计算结果:" + formulaStr + "=" + sum);
					byteBuffer.put(String.valueOf(formulaStr + "=" + sum).getBytes());
					// 7.从缓冲区中读取数据到通道发向客户端
					byteBuffer.flip();
					socketChannel.write(byteBuffer);
				}

			} // while
			socketChannel.close();
			serverSocketChannel.close();
		} catch (

		Exception e) {
			System.out.println("异常:" + e);
		}
	}
}
package com.socket2;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class Client {
	public static void main(String[] args) {
		String str;
		// 1.通过SocketChannel的静态方法open()创建一个SocketChannel对象
		try (SocketChannel socketChannel = SocketChannel.open()) {
			// 使用本地IP地址和指定端口创建一个Socket对象
			InetSocketAddress addr = new InetSocketAddress("127.0.0.1", 8080);
			// 2.连接到远程服务器(连接此通道的socket)
			socketChannel.connect(addr);

			// 3.创建数据缓存区对象
			ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
			/*
			 * in是System的一个字段 “标准”输入流。 此流已打开并准备提供输入数据。 通常,此流对应于键盘输入或者由主机环境或用户指定的另一个输入源
			 */
			InputStreamReader userisr = new InputStreamReader(System.in);
			BufferedReader userin = new BufferedReader(userisr);
			while (true) {
				System.out.println("发送加法公式:");
				// 4.获取用户从终端输入的字符串并放到缓冲区
				str = userin.readLine();
				byteBuffer.clear();
				byteBuffer.put(str.getBytes());
				// 5.读取缓冲区中的数据到通道中,发向服务端
				byteBuffer.flip();
				socketChannel.write(byteBuffer);
				if (str.equals("end")) {
					System.out.println("结束通信");
					break;
				}
				System.out.println("等待服务器端消息……");
				// String 字符串常量,不可变;StringBuffer 字符串变量(线程安全),可变;StringBuilder 字符串变量(非线程安全),可变
				StringBuilder stringBuffer = new StringBuilder();
				// 6.读取通道中从服务器端来的数据到缓冲区中,并从缓冲区读取出来
				byteBuffer.clear();
				socketChannel.read(byteBuffer);
				byteBuffer.flip();
				while (byteBuffer.hasRemaining()) {
					stringBuffer.append((char) byteBuffer.get());
				}
				System.out.println("服务器端计算结果:" + stringBuffer);
				if (str.equals("end")) {
					System.out.println("结束通信");
					break;
				}
			} // while
			socketChannel.close();
		} catch (Exception e) {
			System.out.println("异常:" + e);
		}
	}
}

 运行结果:

           

参考https://www.cnblogs.com/snailclimb/p/9086335.html

发布了243 篇原创文章 · 获赞 138 · 访问量 138万+

猜你喜欢

转载自blog.csdn.net/ystyaoshengting/article/details/104139768