Netty学习笔记_6(Channel的基本使用)

15 篇文章 0 订阅

1、基本介绍

1、NIO通道类似于Java的输入输出流,有所区别:

      通道可以同时进行读写,流只能读或写;

      通道可以实现异步读写数据;

      通道可以从缓冲区读数据,也可以写数据到缓冲区。

2、BIO中stream是单向的,例如FileInputStream对象只能进行读取数据的操作,而NIO的 通道(Channel)是双向的可以读操作,也可以写操作。

3、Channnel在NIO中是一个接口:public interface Channel extends Closeable{}

4、常用的Channnel类有:FileChannel、DatagramChannel、ServerSocketChannel和SocketChannel。【ServerSocketChannel类似于ServerSocket,SocketChannel类似于Socket】

【注】在NIO模式下,当客户端向服务器请求连接时,客户端程序先访问服务器主线 程的ServerSocketChannel类,若服务器同意连接,则为客户端分配SocketChannel。

5、FileChannel用于文件数据读写,DatagramChannel用于UDP数据读写,ServerSocketChannel 和SocketChannel用于TCP数据读写。

2、FileChannel类:主要用于对本地文件进行IO操作

  1. Public int read(ByteBuffer dst); 从通道中读取数据到缓冲区中
  2. Public int write(ByteBuffer src); 把缓冲区中的数据写入到通道中
  3. Public long transferFrom(ReadableByteChannel src,long position,long count);从目标通道中复制数据到当前通道
  4. Public long transferTo(long position,long count,WriteableByteChannel target);把数据从当前通道复制给目标通道

2-1通道应用案例1 本地文件写数据

要求:1)使用ByteBuffer(缓冲)和FileChannel(通道),将“你好,Netty”写入到file01.txt中

           2)若文件不存在则创建文件

设计思路:1)首先定义一个String类型的变量,赋值为“你好,Netty”

                  2)创建一个文件输出流FileOutputStream内置FileChannel

                  3)创建一个ByteBuffer缓冲区,将String变量按照字节形式存入缓冲区

                  4)将缓冲区中的数据通过channel输出到指定文件

                                     【注】使用FileChannel进行数据输入输出时,其底层使用的仍然是java.io中的输入输出流

 

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

public class NIOFileChannel01 {
    public static void main(String[] args) throws Exception {
        String str = "你好,Netty";

        //创建一个输出流包裹到Channel中
        FileOutputStream fileOutputStream = new FileOutputStream("d:\\file01.txt");

        //通过fileOutputStream获取对应的FileChannel
        //该FileChannel的真实类型是 FileChannelImpl
        FileChannel fileChannel = fileOutputStream.getChannel();

        //创建一个缓冲区 ByteBuffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

        //将str放入到ByteBuffer中
        byteBuffer.put(str.getBytes());

        //对byteBuffer进行反转
        byteBuffer.flip();

        //将byteBuffer中的数据写入到fileChannel中
        fileChannel.write(byteBuffer);
        fileOutputStream.close();
    }
}
  • 创建缓冲区ByteBuffer时,设置缓冲区大小为1024,此时缓冲区中无内容,底层数组各下标位置值为0
  • 将String类型的数据写入缓冲区时,按照中文字符占3个字节,英文字符占1个字节,给ByteBuffer的0~13位置赋值【3*3+5-1=13】;写入结束后,ByteBuffer的limit=1024,position=14
  • 将ByteBuffer反转(状态切换)flip()方法,此时limit=14,position=0,准备从0开始读取缓冲区数据,写入通道。
  • 调用FlieChannel的write()方法,将数据写入通道,再由输出流OutputStream输出到指定文件

2-2通道应用案例2 本地文件读数据 

要求:1)使用ByteBuffer和FileChannel,将file01.txt中的数据读入到程序,并显示在控制台屏幕

           2)假定文件已经存在

设计思路:1)先创建文件的输入流FileInputStream获取对应的FileChannel

                   2)将file01.txt中的内容读入通道

                   3)创建一个字节缓冲区ByteBuffer,通过FileChannnel.read()方法将通道中的数据读入缓冲区

                   4)调用ByteBuffer底层的字节数组,将数组中的内容转换为字符串类型并定义变量接收

                   5)将字符串输出至控制台显示

【注】将数据读入缓冲区之后不能直接输出,因为此时缓冲区存放的并不是原始字符串,而是字符串对应的字节文件,必须将字节转换为字符串才符合要求

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class NIOFileChannel02 {
    public static void main(String[] args) throws IOException {
        //创建文件的输入流
        File file = new File("d:\\file01.txt");
        FileInputStream fileInputStream = new FileInputStream(file);

        //通过fileInputStream获取对应的FileChannel -> 实际类型:FileChannelImpl
        FileChannel fileChannel = fileInputStream.getChannel();

        //创建缓冲区
        ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());
        
        //将通道数据读入缓冲区中
        fileChannel.read(byteBuffer);

        //将byteBuffer中的字节数据转换为字符串输出
        System.out.println(new String(byteBuffer.array()));

        fileInputStream.close();
    }
}

2-3通道应用案例3 使用一个buffer完成文件读取

要求:1)使用FileChannel(通道)和方法 read,write,完成文件拷贝

           2)拷贝一个文本文件1.txt,放在项目下即可

数据流动过程如图:

 

设计思路:1)创建文件输入流输出流对象,并获取对应的FileChannel

                   2)创建ByteBuffer缓冲区

                   3)调用输入流通道的read()方法,将1.txt的内容循环读入缓冲区

                   4)每次读入缓冲区后,将缓冲区数据通过输出流通道的write()方法写入到2.txt

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class NIOFileChannel03 {
    public static void main(String[] args) throws IOException {
        //创建文件输入流对象
        File f1 = new File("1.txt");
        FileInputStream fileInputStream = new FileInputStream(f1);

        //获取对应的FileChannel
        FileChannel inputStreamChannel = fileInputStream.getChannel();

        //创建文件输出流对象以及对应的FileChannel
        FileOutputStream fileOutputStream = new FileOutputStream("2.txt");
        FileChannel outputStreamChannel = fileOutputStream.getChannel();

        //创建缓冲区ByteBuffer
        ByteBuffer byteBuffer = ByteBuffer.allocate((int) f1.length());

        while(true){
            //循环读取文件内容,输入到缓冲区
            byteBuffer.clear(); //清空buffer 重要
            int read = inputStreamChannel.read(byteBuffer);
            System.out.println("read = "+read);
            if (read == -1){ //表示读取完毕
                break;
            }

            //将buffer中的数据写入到outputStreamChannel中 -> 2.txt
            byteBuffer.flip();
            outputStreamChannel.write(byteBuffer);
        }

        //关闭流
        fileInputStream.close();
        fileOutputStream.close();

    }
}

【注】1)每次进行循环读入前,必须调用ByteBuffer.clear()方法,将所有标志位重置。否则,若待读取文件不能一次读入缓冲区时,第二                次开始,limit和position的值一直相等,会导致读取内容为0。

            2)将Buffer中的数据写入通道前,需要调用flip()方法将缓冲区反转。

2-4通道应用案例4 使用transferFrom方法拷贝文件

要求:1)使用FileChannel和方法transferFrom,完成文件拷贝

           2)拷贝一张图片

设计思路:1)新建输入流和输出流,并分别获取对应的通道

                  2)调用通道的transferFrom()方法,将输入通道中的数据复制到输出通道中

import java.io.*;
import java.nio.channels.FileChannel;

public class NIOFlieChannel04 {
    public static void main(String[] args) throws IOException {
        File f1 = new File("1.jpg");
        FileInputStream fileInputStream = new FileInputStream(f1);
        FileChannel inputStreamChannel = fileInputStream.getChannel();

        FileOutputStream fileOutputStream = new FileOutputStream("2.jpg");
        FileChannel outputStreamChannel = fileOutputStream.getChannel();

        outputStreamChannel.transferFrom(inputStreamChannel,0,f1.length());

        fileInputStream.close();
        fileOutputStream.close();


    }
}

3、关于Buffer和Channel的注意事项和细节

  1. ByteBuffer支持类型化的put和get,put放入什么数据类型,get就应该使用相应的数据类型来取出,否则可能会产生ByteUnderflowException异常。
  2. 可以将一个普通的Buffer转换为只读的Buffer:asReadOnlyBuffer()方法。
    import java.nio.ByteBuffer;
    
    public class ReadOnlyBuffer {
        public static void main(String[] args) {
            ByteBuffer buffer = ByteBuffer.allocate(64);
    
            for (int i = 0; i < 64; i++){
                buffer.put((byte) i);
            }
    
            buffer.flip();
    
            //得到一个只读的buffer
            ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();
            System.out.println(readOnlyBuffer.getClass());
    
            while (readOnlyBuffer.hasRemaining()){
                System.out.println(readOnlyBuffer.get());
            }
    
            readOnlyBuffer.put((byte) 122);
        }
    }
    
    【注】此时,向只读buffer中放入数据会产生异常。
  3. NIO提供了MapperByteBuffer,可以让文件直接在内存(堆外内存)中进行修改,而如何同步到文件由NIO来完成。
    import java.io.FileNotFoundException;
    import java.io.IOException;
    import java.io.RandomAccessFile;
    import java.nio.MappedByteBuffer;
    import java.nio.channels.FileChannel;
    
    /*
    * 说明
    * 1、MappedByteBuffer可以直接让文件在内存(堆外内存)中修改,操作系统不需要拷贝一次
    *
    * */
    public class MappedByteBufferTest {
        public static void main(String[] args) throws IOException {
            RandomAccessFile randomAccessFile = new RandomAccessFile("1.txt", "rw");
    
            //获取对应的文件通道
            FileChannel channel = randomAccessFile.getChannel();
    
            MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);
            /**
             * 参数1:FileChannel.MapMode.READ_WRITE 使用的是读写模式
             * 参数2:0 可以直接修改的起始位置
             * 参数3:5 映射到内存的大小(不是索引位置),即将文件的多少个字节映射到内存中
             *                          可以直接修改的范围是0~5
             * 实际类型是 DirectByteBuffer
             * */
    
    
            mappedByteBuffer.put(0, (byte) 'H');
            mappedByteBuffer.put(3, (byte) '9');
            mappedByteBuffer.put(5, (byte) 'D');
    
            randomAccessFile.close();
    
    
        }
    }
    

     

  4. NIO还支持通过多个Buffer(即Buffer数组)完成读写操作,即Scattering(分散)和Gathering(聚集)。

     Scattering(分散):在向缓冲区写入数据时,可以使用buffer数组依次写入,一个buffer数组写满后,继续写入下一个buffer数组。

     Gathering(聚集):从缓冲区读取数据时,可以依次读取,读完一个buffer再按顺序读取下一个。

import java.io.IOException;
import java.lang.reflect.Array;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Arrays;

/**
 * scattering[分散]:将数据写入到buffer时,可以采用buffer数组,依次写入
 *                 一个buffer满了,写入下一个buffer
 * gathering[聚集]:从buffer中读数据时,可以采用buffer数组,依次读取
 * */

public class ScatteringANDGathering {
    public static void main(String[] args) throws IOException {

        //使用ServerSocketChannel和SocketChannel 网络
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        InetSocketAddress inetSocketAddress = new InetSocketAddress(7000);

        //绑定端口到socket并启动
        serverSocketChannel.socket().bind(inetSocketAddress);

        //创建buffer数组
        ByteBuffer[] byteBuffers = new ByteBuffer[2];
        byteBuffers[0] = ByteBuffer.allocate(5);
        byteBuffers[1] = ByteBuffer.allocate(3);

        //等待客户端连接(telnet)
        SocketChannel socketChannel = serverSocketChannel.accept();
        int messageLength = 8; //假定从客户端接收8个字节

        //循环读取
        while (true){
            int byteRead = 0;

            while (byteRead < messageLength ){
                long l = socketChannel.read(byteBuffers);
                byteRead += l; //累计读取到在字节数
                System.out.println("byte = "+byteRead);
                //使用流打印,输出当前buffer的position和limit
                Arrays.asList(byteBuffers).stream().map(buffer ->
                        "position="+buffer.position()+",limit="+buffer.limit()).forEach(System.out::println);
            }

            //将所有的buffer进行一次反转
            Arrays.asList(byteBuffers).forEach(buffer -> buffer.flip());

            //将数据读出显示到客户端
            long byteWrite = 0;
            while (byteWrite < messageLength){
                long l = socketChannel.write(byteBuffers);//
                byteWrite += l;
            }

            //将所有的buffer进行复位(clear)
            Arrays.asList(byteBuffers).forEach(buffer -> {
                buffer.clear();
            });

            System.out.println("byteRead = "+ byteRead + " byteWrite = "+ byteWrite + "messageLength = "
            + messageLength);
        }

    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值