【Java进阶】NIO(三)<Channel>

一、Channel概述

1. 介绍

  • Channel是一种新的IO的访问方式,用于在字节缓冲区与通道另一侧的实体(可以是文件,也可以是Socket)之间进行传输数据
  • Channel可以双向读写数据,也可以实现异步读写
  • 程序不能直接访问Channel,Channel只能与Buffer缓冲区进行交互,即把通道中的数据读到Buffer缓冲区中,程序从缓冲区中读取数据;在写操作时,程序把数据写入Buffer缓冲区中,再把缓冲区的数据写入到Channel中

2. 常见的Channel

  • FileChannel:读写文件的通道
  • SocketChannel/ServerSocketChannel:读写Socket套接字中的数据
  • DatagramChannel:通过UDP读写网络数据
  • 总之
    ① 不管是哪个Channel,都不能通过构造器创建Channel
    通道不能重复使用,打开一个通道,表示一个特定的IO服务来建立一个连接,通道关闭时,连接就没了
    通道可以阻塞或者非阻塞方式运行

二、Scatter/Gather

1. 介绍

  • Scatter(发散)、Gather(聚焦)是通道提供的一个重要功能(有时也称为矢量IO)

  • Scatter、Gather是指在多个缓冲区中实现一个简单的IO操作

  • Scatter是指从Channel通道中读取数据,把这些数据按照顺序分散写入到多个Buffer缓冲区中
    在这里插入图片描述

  • Gather是指在写操作时,将多个Buffer缓冲区的数据写入到同一个Channel通道
    在这里插入图片描述

  • Scatter、Gather经常用于需要将传输的数据分开处理的场景
    在这里插入图片描述

三、FileChannel

1. 介绍

  • FileChannel通过RandomAccessFile、FileInputStream、FileOutputStream对象调用getChannel()方法获取
  • FileChannel虽然是双向的,既可以读也可以写,但是从FileInputStream流中获得的通道只能读不能写,如果进行写操作或抛出异常。从FileOutputStream流中获得的通道只能写不能读
  • 如果访问的文件是只读的,也不能执行写操作
  • FileChannel是线程安全的,但是并不是所有的操作都是多线程的,如影响通道位置或者影响文件大小的操作都是单线程的

2. 内存映射文件

  • FileChannel的map()方法可以把磁盘上的文件映射到计算机的虚拟内存中,把这块虚拟内存包装为一个MapedByteBuffer对象
  • 当把一个文件映射到虚拟内存中,文件的内容通常不会从硬盘中读取到内存
package channel;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;

/**
 * 内存映射文件:将文件中的数据映射到虚拟内存中,这种方式访问文件效率比较高
 *
 * @author swaggyhang
 * @create 2023-06-11 11:24
 */
public class FileChannelMapDemo {
    public static void main(String[] args) {
        // 目标:把buffer/BufferCreateDemo.java源文件以内存映射的方式读取到out.txt文件中

        // 1 创建当前文件的File对象
        File file = new File("E:\\swaggyhang\\wlb4ssh\\NIO\\src\\buffer\\BufferCreateDemo.java");

        // 从jdk 1.7开始,资源可以自动关闭
        try (
                // 2 获取输入和输出通道
                FileChannel inChannel = new FileInputStream(file).getChannel();
                FileChannel outChannel = new FileOutputStream("out.txt").getChannel()
        ) {
            // 3 将inChannel中的数据映射到虚拟内存中
            MappedByteBuffer buf = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, file.length());
            // 把虚拟内存中的缓冲区中数据输出到outChannel中
            outChannel.write(buf);

            // 4 可以将buf中的内容打印出来
            buf.flip(); // 翻转,切换为读模式
            Charset charset = Charset.defaultCharset();
            CharBuffer decode = charset.decode(buf);
            System.out.println(decode);
        } catch (Exception e) {
            // todo
        }
    }
}

3. FileChannel的双向传输

  • 通过RandomAccessFile获得的通道是双向传输的,既可以读也可以写
package channel;

import java.io.File;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

/**
 * 使用RandomAccessFile获得的通道是双向传输的
 *
 * @author swaggyhang
 * @create 2023-06-11 18:19
 */
public class RandomAccessFileDemo {
    public static void main(String[] args) {
        // 目标:将out.txt文件中的内容复制,追加到该文件的最后面

        // 1 创建File对象
        File file = new File("E:\\swaggyhang\\wlb4ssh\\out.txt");
        System.out.println("file.length() = " + file.length()); // file.length() = 1724

        try (
                // 2 创建双向传输通道,可读可写
                RandomAccessFile raf = new RandomAccessFile(file, "rw");
                FileChannel channel = raf.getChannel()
        ) {
            // 3 将双向通道的数据映射到虚拟内存中
            MappedByteBuffer mapbuf = channel.map(FileChannel.MapMode.READ_WRITE, 0, file.length());
            System.out.println("mapbuf.capacity() = " + mapbuf.capacity()); // mapbuf.capacity() = 1724

            // 4 设置通道的position为File对象的最大长度
            System.out.println("channel.position() = " + channel.position());   // channel.position() = 0
            channel.position(file.length());

            // 5 将mapbuf缓冲区中的数据写入到通道中
            channel.write(mapbuf);
        } catch (Exception e) {
            // todo
        }
    }
}

4. 设置缓冲区为固定大小

package channel;

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

/**
 * 读写文件时,设置缓冲区为固定大小
 *
 * @author swaggyhang
 * @create 2023-06-11 19:12
 */
public class BufferFixSizeDemo {
    public static void main(String[] args) {
        // 目标:把out.txt文件的内容复制到out2.txt文件中
        try (
                // 1 创建输入和输出通道
                FileChannel inChannel = new FileInputStream("E:\\swaggyhang\\wlb4ssh\\out.txt").getChannel();
                FileChannel outChannel = new FileOutputStream("E:\\swaggyhang\\wlb4ssh\\out2.txt").getChannel()
        ) {
            // 2 定义固定大小为48的字节缓冲区
            ByteBuffer buf = ByteBuffer.allocate(48);

            // 3 从inChannel中读取数据到buf中
            int readBytes = inChannel.read(buf);

            // 4 当readBytes不为-1时,表示没有读取到文件末尾
            while (readBytes != -1) {
                // 5 将缓冲区写入到输出通道中
                buf.flip(); // 翻转,切换为读模式
                outChannel.write(buf);

                // 6 继续从输入通道读取数据到缓冲区中
                buf.clear();    // 清空buf
                readBytes = inChannel.read(buf);
            }
        } catch (Exception e) {
            // todo
        }
    }
}

5. Channel到Channel的传输

  • 经常需要把文件从一个位置批量传输到另一个位置,可以直接使用通道到通道之间的传输,不需要之间缓冲区传递数据
  • 注意:只有FileChannel支持通道到通道之间的传输
  • 通道到通道的传输非常快速,有的操作系统可以不使用用户空间,直接传递数据
package channel;

import java.io.File;
import java.io.FileOutputStream;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;

/**
 * 通道与通道之间的传输
 *
 * @author swaggyhang
 * @create 2023-06-11 19:22
 */
public class Channel2ChannelDemo {
    public static void main(String[] args) {
        // 目标:将out.txt文件复制到out3.txt文件中

        File file = new File("E:\\swaggyhang\\wlb4ssh\\out.txt");
        try (
                RandomAccessFile raf = new RandomAccessFile(file, "rw");
                FileChannel inChannel = raf.getChannel();
                FileChannel outChannel = new FileOutputStream("E:\\swaggyhang\\wlb4ssh\\out3.txt").getChannel()
        ) {
            // 将输入通道从0开始的所有字节传输到输出通道中
            //inChannel.transferTo(0, file.length(), outChannel);
            outChannel.transferFrom(inChannel, 0, file.length());
        } catch (Exception e) {
            // todo
        }
    }
}

6. Gather代码演示

  • 把文件的属性和内容,通过Gather形式写到另外一个文件中
package channel;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URLConnection;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 把文件的属性和文件的内容,通过Gather形式写到另外一个文件中
 *
 * @author swaggyhang
 * @create 2023-06-11 19:43
 */
public class GatherChannelDemo {
    public static void main(String[] args) throws Exception {
        // 目标:把out.txt文件的属性及其内容写入到新文件中

        // 1 获取文件的属性,将文件属性存储在header缓冲区中
        File file = new File("E:\\swaggyhang\\wlb4ssh\\out.txt");
        String path = file.getAbsolutePath();
        long lastModified = file.lastModified();
        Date lastModifiedDate = new Date(lastModified);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String headerText = "filename:" + path + "\n" + "last modified:" + sdf.format(lastModifiedDate) + "\n";
        ByteBuffer header = ByteBuffer.wrap(headerText.getBytes());

        // 2 将文件内容类型存储在contentBuf中
        ByteBuffer contentBuf = ByteBuffer.allocate(128);
        String contentType = "unknown";
        long contentLength = -1;

        // 3 创建一个缓冲区数组,用于存放文件属性和文件内容缓冲区
        ByteBuffer[] gather = {header, contentBuf, null};

        try {
            FileChannel fc = new FileInputStream(file).getChannel();
            // 3 把文件内容映射到虚拟内存中
            MappedByteBuffer fileData = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
            // 将文件内容缓冲区保存到gather中
            gather[2] = fileData;

            // 4 获取文件内容大小和文件内容类型
            contentLength = fc.size();
            contentType = URLConnection.guessContentTypeFromName(file.getAbsolutePath());
        } catch (IOException e) {
            ByteBuffer buffer = ByteBuffer.allocate(128);
            String msg = "文件访问异常:" + e + "\n";
            buffer.put(msg.getBytes());
            buffer.flip();
            gather[2] = buffer;
        }

        // 5 组装文件长度和文件内容类型的缓冲区
        StringBuilder sb = new StringBuilder();
        sb.append("Content-Length:").append(contentLength).append("\n");
        sb.append("Content-Type:").append(contentType).append("\n");
        contentBuf.put(sb.toString().getBytes());
        contentBuf.flip();

        // 6 将缓冲区数组gather写入到输出通道中
        FileChannel outChannel = new FileOutputStream("E:\\swaggyhang\\wlb4ssh\\out4.txt").getChannel();
        long writeLength = outChannel.write(gather);
        // 防止gather缓冲区数组过大,一次性写不完
        while (writeLength > 0) {
            writeLength = outChannel.write(gather);
        }
        outChannel.close();
    }
}

四、SocketChannel和ServerSocketChannel

1. 介绍

  • ServerSocketChannel可以监听新进来的TCP通道
  • SocketChannel是一个连接到TCP网络套接字的通道

2. ServerSocketChannel代码演示

package channel;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;

/**
 * @author swaggyhang
 * @create 2023-06-11 21:11
 */
public class ServerSocketChannelDemo {
    public static void main(String[] args) throws IOException, InterruptedException {
        // 1 建立一个未绑定ServerSocket服务器的通道
        ServerSocketChannel ssc = ServerSocketChannel.open();
        // ServerSocketChannel没有bind()绑定方法,需要先通过socket()方法获取ServerSocket对象,再进行绑定端口号
        ssc.socket().bind(new InetSocketAddress(1234));

        // 2 设置通道为非阻塞模式,当没有传入连接时,accept()方法返回null
        ssc.configureBlocking(false);

        // 3 服务器端一直监听
        while (true) {
            System.out.println("我是ServerSocket服务器,已经准备好了,就等你来了");

            // 4 调用accept()方法,返回接收到的客户端socketChannel
            SocketChannel sc = ssc.accept();
            // 如果没有连接进来,那么sc为null
            if (sc == null) {
                Thread.sleep(1000);
            } else {
                // 5 如果客户端连接进来了,先给客户端发送一个问候
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                buffer.put("hello, I am from socketServer".getBytes());
                buffer.flip();
                sc.write(buffer);

                // 6 再读取客户端中发送来的内容
                System.out.println("from Socket Client:" + sc.socket().getRemoteSocketAddress());
                buffer.clear();
                sc.read(buffer);
                buffer.flip();
                Charset charset = Charset.defaultCharset();
                CharBuffer decode = charset.decode(buffer);
                System.out.println(decode);

                sc.close();
            }
        }
    }
}

3. SocketChannel代码演示

package channel;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;

/**
 * @author swaggyhang
 * @create 2023-06-11 21:27
 */
public class SocketChannelDemo {
    public static void main(String[] args) throws IOException {

        // 1 创建一个未连接的客户端SocketChannel
        SocketChannel sc = SocketChannel.open();
        // 客户端与服务器建立连接
        sc.connect(new InetSocketAddress("localhost", 1234));

        // 2 TCP连接需要一定时间,两个连接的建立需要进行包对话
        // 调用finishConnect()方法来完成连接过程
        while (!sc.finishConnect()) {
            System.out.println("等待连接过程中......");
        }
        System.out.println("连接成功");

        // 3 向服务器发送消息
        ByteBuffer buffer = ByteBuffer.wrap("hello, I am from client socket".getBytes());
        while (buffer.hasRemaining()) {
            sc.write(buffer);
        }

        buffer.clear();
        int readLength = sc.read(buffer);
        while (readLength != -1) {
            readLength = sc.read(buffer);
        }
        buffer.flip();
        Charset charset = Charset.defaultCharset();
        CharBuffer decode = charset.decode(buffer);
        System.out.println(decode);
    }
}

五、DatagramChannel

1. 介绍

  • DatagramChannel是对UDP无连接用户数据报协议的通道

2. 服务端代码演示

3. 客户端代码演示

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值