一、通道介绍
通道表示与诸如硬件设备,文件,网络套接字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);
}
}
}
运行结果: