Java网络编程(6)NIO - Channel详解

前言

NIO的三个核心组件:Buffer、Channel、Selector
Java网络编程(4)NIO的理解与NIO的三个组件完成了大概的了解
Java网络编程(5)NIO - Buffer详解详细了解了Buffer

现在开始详细的学习Channel

目录

  1. Channel概念
  2. Channel类继承结构
  3. FileChannel
    3.1. 常用方法
    3.2. 案例
  4. 分散读取与聚集写入
  5. 通道复制数据
    5.1. 案例
  6. ServerSocketChannel、SocketChannel
  7. 总结

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

常用方法

  1. 实例化
    FileChannel是抽象类,不能通过实例化创建,可以通过FileInputStream、FileOutputStream、RandomAccessFile 获得FileChannel

因为NIO是在IO的基础上搭建的,所以Channel可以通过IO流获得
在这里插入图片描述

在这里插入图片描述
IO流的getChannel()方法可以打开通道;
jdk1.7后有open()方法,Files工具类的newByteChannel()也可以

  1. 读取数据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

  1. 写入数据Write
    在这里插入图片描述
    和读操作相对应

  2. 关闭close:Channel自带的方法,关闭通道
    不过一般通过关闭流就能关闭通道,后面也可以通过关闭选择器关闭所有的通道、流

  3. isOpen():Channel自带的方法,告诉这个通道是否打开

  4. transferFrom(ReadableByteChannel src,long position, long count):从目标通道复制数据到当前通道

  5. 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详解中实践

总结

  1. Channel通道是在实体与缓冲区之间传输数据的媒介,类似与流,但区别在与Channel可以读写双向传输,Channel必须得和Buffer交换数据
  2. Channel有许多子类,可以分为文件通道与套接字通道(网络通道),常用的有FileChannel、SockeChannel、ServerSockeChannel、DatagramChannel
  3. 套接字通道需要结合多路复用器,这里仅详细介绍FileChannel
  4. FileChannel主要用于读写本地文件,通过流得到FileChannel
  5. Buffer通过read方法读取Channel中的数据,通过write方法写入Channel
  6. 分散读取:Channel数据分散读取到多个Buffer;聚集写入:多个Buffer中的数据聚集写入到一个Channel
  7. TransferForm、TransferTo实现通道交流数据
发布了95 篇原创文章 · 获赞 25 · 访问量 4192

猜你喜欢

转载自blog.csdn.net/key_768/article/details/104673158
今日推荐