前言
NIO的三个核心组件:Buffer、Channel、Selector
Java网络编程(4)NIO的理解与NIO的三个组件完成了大概的了解
Java网络编程(5)NIO - Buffer详解详细了解了Buffer
现在开始详细的学习Channel
目录
- Channel概念
- Channel类继承结构
- FileChannel
3.1. 常用方法
3.2. 案例 - 分散读取与聚集写入
- 通道复制数据
5.1. 案例 - ServerSocketChannel、SocketChannel
- 总结
Channel概念
首先先知道Channel位于NIO编程的位置
通道Channel是在实体和缓冲区之间有效传输数据的媒介
在jdk1.4之前的BIO通信中,使用流进行传输数据
在NIO中,通道Channel类似与流,但有一些区别:
- 既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的。
- 通道可以异步地读写。
- 通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入
Channel不能直接和程序交换数据,而是通过Buffer,这与流不同
Channel类继承结构
可以看出Channel是总接口,它有许多继承接口、子类
当然,其中可分为文件通道和套接字通道
这里只需要了解一些常用的通道
-
FileChannel:用于读取、写入、映射和操作文件的通道
FileChannel是抽象类,真正的实现是FileChannelImpl实现类 -
DatagramChannel:读写UDP通信的数据,对应DatagramSocket类
-
SocketChannel:读写TCP通信的数据,对应Socket类
-
ServerSocketChannel:监听新的TCP连接,并且会创建一个可读写的SocketChannel,对应ServerSocket类(服务器)
这几个套接字通道在NetworkChannel接口,当然实现类还是Impl类 -
ScatteringByteChannel和GatheringByteChannel:分散聚集通道,由操作系统完成
-
WritableByteChannel和ReadableByteChannel:接口提供读写API
读写通道
这些通道都有各自的一些特性和操作,这里先了解FileChannel
FileChannel
FileChannel用于读取、写入、映射和操作文件的通道,主要用于读写本地的文件File
常用方法
- 实例化
FileChannel是抽象类,不能通过实例化创建,可以通过FileInputStream、FileOutputStream、RandomAccessFile 获得FileChannel
因为NIO是在IO的基础上搭建的,所以Channel可以通过IO流获得
IO流的getChannel()方法可以打开通道;
jdk1.7后有open()方法,Files工具类的newByteChannel()也可以
- 读取数据Read
read方法有四种:
- read(ByteBuffer) : ByteBuffer读取Channel中的数据
- read(ByteBuffer dst, long position):ByteBuffer读取Channel中的数据,从指定position开始
- read(ByteBuffer[] dsts, int offset, int length):将通道中的数据读入多个Buffer中,offset是第一个缓冲区(字节传输到该缓冲区中)在缓冲区数组中的偏移量,length要访问的最大缓冲区数
- read(ByteBuffer[] dsts) :read(ByteBuffer[] dsts, int offset, int length)的简化
读取的字节数,可能为零,如果该通道已到达流的末尾,则返回 -1
-
写入数据Write
和读操作相对应 -
关闭close:Channel自带的方法,关闭通道
不过一般通过关闭流就能关闭通道,后面也可以通过关闭选择器关闭所有的通道、流 -
isOpen():Channel自带的方法,告诉这个通道是否打开
-
transferFrom(ReadableByteChannel src,long position, long count):从目标通道复制数据到当前通道
-
transferTo(long position, long count, WritableByteChannel target):把数据从当前通道复制到目标通道
案例
这是一个简单的往文件写入字符串,然后读出的程序
package com.company.Channel;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class BasicFileChannel {
public static void main(String[] args) throws Exception {
outputFile();
inputfile();
}
public static void inputfile() throws Exception{
//创建输入流
FileInputStream fileInputStream = new FileInputStream("BasicFileChannel");
//从流中实例化一个文件通道
FileChannel fileChannel = fileInputStream.getChannel();
//实例化一个Buffer
ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
//将文件的数据读出到Buffer
fileChannel.read(byteBuffer);
//将Buffer数组转换为字符串
System.out.println(new String(byteBuffer.array()));
fileInputStream.close();
}
public static void outputFile() throws Exception{
String string="TO be or not to be,that's a question";
//创建文件输出流
FileOutputStream fileOutputStream = new FileOutputStream("BasicFileChannel");
//从流中实例化一个文件通道
FileChannel fileChannel = fileOutputStream.getChannel();
//创建一个ByteBuffer存储字符串
//getBytes()获得字符串所有字符
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put(string.getBytes());
//翻转,读模式
byteBuffer.flip();
//写入文件
fileChannel.write(byteBuffer);
fileOutputStream.close();
}
}
分散读取与聚集写入
前面看到了read方法、write方法还有两种:
read(ByteBuffer[] dsts, int offset, int length)
write(ByteBuffer[] srcs, int offset, int length)
这就是用来分散读取和聚集写入的
分散读取
将通道中的数据读取的多个Buffer(read方法)
聚集写入
将多个缓冲区的数据写入到单个通道中(write方法)
FileChannel 可以实现聚集写入与分散读出
有两个专门的接口就是实现聚集写入与分散读出:ScatteringByteChannel和GatheringByteChannel
FileChannel实现了这两个接口
package com.company.ScatterGather;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Channel;
import java.nio.channels.FileChannel;
import java.nio.channels.GatheringByteChannel;
import java.nio.channels.ScatteringByteChannel;
public class ScatterGatherIO {
//聚集写入
public static void Gather(String data) throws FileNotFoundException {
//创建两个ByteBuffer存数据
ByteBuffer byteBuffer1=ByteBuffer.allocate(20);
ByteBuffer byteBuffer2=ByteBuffer.allocate(400);
//把整数放入byteBuffer1
byteBuffer1.asIntBuffer().put(1024);
//把输入的String变量放入byteBuffer2
byteBuffer2.asCharBuffer().put(data);
//GatheringByteChannel接口允许委托操作系统完成任务
//CreatChanner使用文件写入流
GatheringByteChannel gatherChannel=CreatChanner("TestOut.txt",true);
//聚集写入通道
try {
//write只允许一个ByteBuffer
gatherChannel.write(new ByteBuffer[]{byteBuffer1,byteBuffer2});
} catch (IOException e) {
e.printStackTrace();
}
}
//分散写出
public static void Scatter() throws FileNotFoundException {
//创建两个ByteBuffer存数据
ByteBuffer byteBuffer1=ByteBuffer.allocate(20);
ByteBuffer byteBuffer2=ByteBuffer.allocate(400);
//读取文件通道
ScatteringByteChannel scatterChannel=CreatChanner("TestOut.txt",false);
try {
scatterChannel.read(new ByteBuffer[]{byteBuffer1,byteBuffer2});
} catch (IOException e) {
e.printStackTrace();
}
//buffer位置置0
byteBuffer1.rewind();
byteBuffer2.rewind();
System.out.println(byteBuffer1.asIntBuffer().get());
System.out.println(byteBuffer2.asCharBuffer().toString());
}
//输入文件地址和输入方向,决定通道方向
public static FileChannel CreatChanner(String fileUrl,boolean out) throws FileNotFoundException {
FileChannel fileChannel=null;
if (out){
fileChannel=new FileOutputStream(fileUrl).getChannel();
}
else
fileChannel=new FileInputStream(fileUrl).getChannel();
return fileChannel;
}
public static void main(String[] args) throws FileNotFoundException {
String data="hello,welcome to ScatterGatherIO";
Gather(data);
Scatter();
}
}
通道复制数据
transferFrom(ReadableByteChannel src,long position, long count):从目标通道复制数据到当前通道
transferTo(long position, long count, WritableByteChannel target):把数据从当前通道复制到目标通道
(在Linux下一次transferTo可以传输完文件,在Windows下一次只能传输8M文件,太大要分段)
案例
package com.company.Channel;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;
public class ChannelTransfer {
public static void main(String[] args) throws Exception {
//创建输入输出流
FileInputStream fileInputStream = new FileInputStream("1.jpg");
FileOutputStream fileOutputStream = new FileOutputStream("clone.jpg");
FileOutputStream fileOutputStream1 = new FileOutputStream("toClone.jpg");
//从流中得到Channel
FileChannel inputStreamChannel = fileInputStream.getChannel();
FileChannel outputStreamChannel = fileOutputStream.getChannel();
FileChannel fileOutputStream1Channel = fileOutputStream1.getChannel();
//transferFrom是通道间的数据传输
outputStreamChannel.transferFrom(inputStreamChannel,0,inputStreamChannel.size());
//transferTo
inputStreamChannel.transferTo(0,inputStreamChannel.size(),fileOutputStream1Channel);
inputStreamChannel.close();
outputStreamChannel.close();
}
}
复制成功:
ServerSocketChannel、SocketChannel
ServerSocketChannel是服务器端套接字通道,和BIO中ServerSocket类似
ServerSocketChannel是一个抽象类,实现类是ServerSocketChannelImpl
SocketChannel是客户端端套接字通道,和BIO中Socket类似
SocketChannel也是个抽象类,实现类是SocketChannelImpl
操作
ServerSocketChannel大部分操作和FileChannel、ServerSocket类似
- 实例化:通过open()获得
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
- 返回一个ServerSocket :socket()
- 绑定端口号:bind(new InetSocketAddress)
serverSocketChannel.socket().bind(new InetSocketAddress(6666));
- 设置非阻塞:configureBlocking(boolean),false为非阻塞,一般NIO网络编程都是要设置非阻塞(文件通道不是非阻塞的),不然会报错
serverSocketChannel.configureBlocking(false);
- 接受连接:和ServerSocket一样用accept()方法,返回的是一个SocketChannel(与返回Socket类似)
- 注册到选择器:register()
SocketChannel与FileChannel、Socket操作类似:
- open()获得对象
- configureBlocking(false)设置非阻塞
- connect(new InetSocketAddress):连接指定套接字地址:IP地址+端口号
- finishConnect:如果connet连接失败,为了确定后续IO操作正常进行需等待连接的建立,可以使用该方法阻塞到连接建立好
- write :从缓冲区往通道里写数据
- read:从通道里读取数据到缓冲区
- register():注册方法
SocketChannel和ServerSocketChannel主要还是要配合Selector使用,在后续Selector详解中实践
总结
- Channel通道是在实体与缓冲区之间传输数据的媒介,类似与流,但区别在与Channel可以读写双向传输,Channel必须得和Buffer交换数据
- Channel有许多子类,可以分为文件通道与套接字通道(网络通道),常用的有FileChannel、SockeChannel、ServerSockeChannel、DatagramChannel
- 套接字通道需要结合多路复用器,这里仅详细介绍FileChannel
- FileChannel主要用于读写本地文件,通过流得到FileChannel
- Buffer通过read方法读取Channel中的数据,通过write方法写入Channel
- 分散读取:Channel数据分散读取到多个Buffer;聚集写入:多个Buffer中的数据聚集写入到一个Channel
- TransferForm、TransferTo实现通道交流数据