通道
第三课 通道
1 通道(Channel)
- 通道(Channel):用于源节点与目标节点的连接。在Java NIO中负责缓冲区中数据的传输。Channel本身不存储数据,因此需要配合缓冲区进行传输。现代计算机中通道集成于专用的处理器中,专门用于处理IO请求,提高效率。
2 通道的主要实现类
java.nio.channels.Channel
FileChannel
SocketChannel
ServerSocketChannel
DatagramChannel
3 获取通道
-
Java针对支持通道的类提供了
getChannel()
方法- 本地IO
FileInputStream
/FileOutputStream
RandomAccessFile
- 网络IO
Socket
ServerSocket
DatagramSocket
- 本地IO
-
在 JDK1.7 中的 NIO.2 针对各个通道提供了静态方法
open()
-
在 JDK1.7 中的 NIO.2 的
Files
工具类的newByteChannel()
方法获取通道
package NIO;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class FileChannelTest {
public static void main(String[] args) {
try {
test02();
} catch (Exception e) {
e.printStackTrace();
}
}
/*
* 非直接缓冲区
*/
public static void test01() throws Exception {
// 输入文件流
FileInputStream fis = new FileInputStream("D:\\WorkSpace\\JavaBaseStu\\Notes\\99 Pic\\IO总结.jpg");
// 输出文件流
FileOutputStream fos = new FileOutputStream("D:\\WorkSpace\\JavaBaseStu\\Notes\\99 Pic\\复制.jpg");
// 输入流管道
FileChannel inChannel = fis.getChannel();
// 输出流管道
FileChannel outChannel = fos.getChannel();
// 配合缓冲区进行文件的读写
ByteBuffer dst = ByteBuffer.allocate(1024);
// 循环读入写入文件
while (inChannel.read(dst) != -1) { // 从输入管道中获取缓冲区,并读取其中的数据
dst.flip(); // 转换成写入模式
outChannel.write(dst); // 写入缓冲区,并加载到输出管道中
dst.clear(); // 清空缓冲区中的数据,重新从输入管道中缓冲区获取数据
}
// 关闭资源
fis.close();
fos.close();
inChannel.close();
outChannel.close();
}
/*
* 直接缓冲区(内存映射文件,不需要缓冲区)
*/
public static void test02() throws Exception {
long start = System.currentTimeMillis();
// 获取输入管道
FileChannel inChannel = FileChannel.open(Paths.get("E:\\Yorick\\01 学习\\01 基础阶段\\01 Java基础\\课件笔记源码资料.zip"), StandardOpenOption.READ);
// 获取输出管道
FileChannel outChannel = FileChannel.open(Paths.get("E:\\Yorick\\01 学习\\01 基础阶段\\01 Java基础\\复制.zip"), StandardOpenOption.READ, StandardOpenOption.WRITE,
StandardOpenOption.CREATE);
MappedByteBuffer inMap = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
MappedByteBuffer outMap = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());
byte[] dst = new byte[inMap.limit()];
inMap.get(dst);
outMap.put(dst);
inChannel.close();
outChannel.close();
long end = System.currentTimeMillis();
System.out.println(end = start);
}
}
4 通道之间的数据传输(直接缓冲区)
- 通道与通道之间的数据传输主要有以下两个方法
transferForm()
transferTo()
/*
* 缓冲区间通信
*/
public static void test03() throws Exception {
FileChannel inChannel = FileChannel.open(Paths.get("D:\\WorkSpace\\JavaBaseStu\\Notes\\99 Pic\\IO总结.jpg"),
StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("D:\\WorkSpace\\JavaBaseStu\\Notes\\99 Pic\\复制.jpg"),
StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
outChannel.transferFrom(inChannel, 0, inChannel.size());
}
5 分散(Scatter)与聚集(Gather)
- 分散读取(Scattering Reads):将通道中的数据分散到多个缓冲区中,按照缓冲区的顺序,从Channel中读取的数据一次将Buffer填满
- 聚集写入(Gathering Writes):将多个缓冲区中的数据聚集到通道中,按照缓冲区的顺序,写入position和limit之间的数据到Channel
/*
* 分散读取及聚集写入
*/
public static void test04() throws Exception {
FileChannel inChannel = FileChannel.open(Paths.get("D:\\1.txt"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("D:\\2.txt"), StandardOpenOption.READ, StandardOpenOption.WRITE,
StandardOpenOption.CREATE);
// 分散读取
ByteBuffer buffer_1 = ByteBuffer.allocate(100);
ByteBuffer buffer_2 = ByteBuffer.allocate(1024);
ByteBuffer[] buffers = { buffer_1, buffer_2 };
// 将数据读取到两个缓冲区中
inChannel.read(buffers);
// 转换缓冲区的读写模式
for (ByteBuffer buffer : buffers) {
buffer.flip();
}
byte[] dst = new byte[buffer_2.limit()];
buffer_2.get(dst);
System.out.println(new String(dst));
// 聚集写入
outChannel.write(buffers);
inChannel.close();
outChannel.close();
}
6 字符集(Charset)
- 编码和解码的使用
public static void test06() throws Exception {
Charset charSetGBK = Charset.forName("GBK");
// 编码器
CharsetEncoder GBKEncoder = charSetGBK.newEncoder();
// 解码器
CharsetDecoder GBKDecoder = charSetGBK.newDecoder();
// 创建缓冲区
CharBuffer inBuffer = CharBuffer.allocate(100);
// 加载字符串
inBuffer.put("编码器编码的字符串");
// 将缓冲区(inBuffer)转换为读取模式
inBuffer.flip();
// 编码,此时编码缓冲区(encodeBuffer)为写入模式
ByteBuffer encodeBuffer = GBKEncoder.encode(inBuffer);
// 输出编码结果
System.out.print("编码结果:");
for (int i = 0; i < encodeBuffer.limit(); i++) {
System.out.print(encodeBuffer.get() + "\t");
}
System.out.println();
// 将编码缓冲区(encodeBuffer)转换为读取模式,并进行解码
encodeBuffer.flip();
CharBuffer decoderBuffer = GBKDecoder.decode(encodeBuffer);
for (int i = 0; i < decoderBuffer.limit(); i++) {
System.out.print(decoderBuffer.get());
}
System.out.println();
// 用UTF-8对编码缓冲区进行解码
encodeBuffer.flip();
Charset UTF8Encoder = Charset.forName("UTF-8");
CharBuffer decoderBufferUTF8 = UTF8Encoder.decode(encodeBuffer);
for (int i = 0; i < decoderBufferUTF8.limit(); i++) {
System.out.print(decoderBufferUTF8.get());
}
}
- 查看支持的字符集
/*
* Charset支持的字符集
*/
public static void test05() {
SortedMap<String, Charset> availableCharsetMap = Charset.availableCharsets();
Set<String> keySet = availableCharsetMap.keySet();
for (String tmp : keySet) {
Charset charset = availableCharsetMap.get(tmp);
System.out.println(tmp + " = " + charset);
}
}
7 FileChannel的常用方法
方法 | 描述 |
---|---|
int read(ByteBuffer dst) | 从Channel 中读取数据到ByteBuffer |
longread(ByteBuffer[] dsts) | 将Channel 中的数据“分散”到ByteBuffer[] |
intwrite(ByteBuffer src) | 将ByteBuffer 中的数据写入到Channel |
long write(ByteBuffer[] srcs) | 将ByteBuffer[] 中的数据“聚集”到Channel |
longposition() | 返回此通道的文件位置 |
FileChannelposition(long p) | 设置此通道的文件位置 |
long size() | 返回此通道的文件的当前大小 |
FileChanneltruncate(long s) | 将此通道的文件截取为给定大小 |
voidforce(boolean metaData) | 强制将所有对此通道的文件更新写入到存储设备中 |
8 通道的原理(了解)
8.1 早期应用程序与计算机底层通信原理
应用程序和计算机底层硬件之间不能直接进行通信,需要使用底层硬件向上层操作系统提供的IO接口进行读写操作,应用程序将数据写入用户地址空间,并从用户地址空间中读取底层硬件运算好的数据。
用户地址空间会和内核地址空间进行数据交互,内核地址空间再和底层硬件进行交互,从而完成数据的读取操作。如下图展示的早期计算机,
这样做的缺点是:由CPU统一管理底层硬件的IO接口,这样会使得CPU的占用路较高,本能很好的发挥CPU的运算效率。
8.2 近代计算机应用程序与计算机底层通信原理
近代计算机不再使用CPU统一管理IO接口,加入了DMA总线(直接存储器)。DMA总线辅助CPU管理IO接口并与CPU等底层硬件进行数据传输
当应用程序向底层硬件发出读写请求后,内存会向CPU发出申请建立DMA总线,该总线一旦建立成功,则所有的IO操作全权由DMA总线进行控制。
这样做的缺点是:当有大量的读写请求发送到底层硬件时,内存会大量向CPU申请DMA总线,申请需要时间,从而造成了总线拥堵的情况,DMA总线依旧需要依赖CPU的指示。
8.3 现代计算机应用程序与计算机底层通信原理
现代计算机引入了通道的概念,是一个完全独立的处理器,专门用于处理IO请求,不再需要向CPU申请权限了,减少了与CPU之间的通信,提高了IO读取的性能