通道和缓冲区

  • 通道
通常来说NIO中的所有IO都是从Channel开始的。Channel和流有点类似。通过Channel,我们即可以从Channel把数据写到Buffer中,也可以把数据从Buffer写入到Channel,下图是一个示意图: 

 

通道可以理解成一种连接,根据连接对象的不同,可以分为下面这些类型

文件连接:FileChannel,用于文件的读写

UDP连接:DatagramChannel,用于UDP数据的读写

Socket客户端:SocketChannel,用于TCP数据的读写

Socket服务端:ServerSocketChanel,用于监听TCP连接,每个请求都会生成一个SocketChannel

通道的作用相当于基本IO中的流,但有如下几点区别:

1、通道可以读也可以写,而流只能读或者写

2、通道可以异步读写,而流在读或写的时候,都是阻塞的

3、通道总是基于缓冲区Buffer来读写的,而流的读写是基于虚拟机内存的

demo01:使用通道来进行文件的读取


 1     /**
 2      * 使用通道读文件数据
 3      */
 4     public void readFile() {
 5         Path path = Paths.get("/Users/sherry/WorkPath/Git/LinkTest/zln-learning/testDir/content.html");
 6         Charset charset = Charset.forName("UTF-8");
 7         CharsetDecoder decoder = charset.newDecoder();//解码器
 8 
 9         try (FileChannel fileChannel = FileChannel.open(path, StandardOpenOption.READ)) {
10             ByteBuffer byteBuffer = ByteBuffer.allocate((int) fileChannel.size());
11             CharBuffer charBuffer = CharBuffer.allocate((int) fileChannel.size());
12             logger.debug("缓冲区大小:"+(int) fileChannel.size());//371
13             if ((fileChannel.read(byteBuffer)) != -1) {//因为缓存容量的缘故,其实只需要读取一次  对于大文件,需要循环
14                 logger.debug("ByteBuffer limit:"+byteBuffer.limit());//371
15                 byteBuffer.flip();//limit到当前位置  pos到0
16                 decoder.decode(byteBuffer, charBuffer, true);//将读取到的字节按照指定方式进行解码,写入到字符缓冲区中
17                 logger.debug("CharBuffer limit:"+charBuffer.limit());//371
18                 charBuffer.flip();
19                 System.out.print(charBuffer);
20 
21                 byteBuffer.clear();
22                 charBuffer.clear();
23             }
24         } catch (IOException e) {
25             logger.error(e.getMessage(), e);
26         }
27     }

用到了如下知识

  1、Path表示路径

  2、使用CharserDecoder进行解码

  3、ByteBuffer到CharBuffer之间使用解码器进行转换

  4、使用TWR实现资源的安全关闭

  5、缓冲区的flip与clear操作

demo02:使用通道实现文件的写入

 1     /**
 2      * 打开并写数据
 3      *
 4      * @throws IOException
 5      */
 6     public void openAndWrite() throws IOException {
 7         Charset charset = Charset.forName("UTF-8");
 8         CharsetEncoder encoder = charset.newEncoder();
 9         //已有的File流,一般也提供了 getChannel 方法,用于获取 FileChannel 实例
10         try (FileChannel fileChannel = FileChannel.open(Paths.get("/Users/sherry/WorkPath/Git/LinkTest/zln-learning/testDir/fileChannel.txt")
11                 , StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)) {
12             ByteBuffer byteBuffer = ByteBuffer.allocate(1024 * 4);
13             CharBuffer charBuffer = CharBuffer.allocate(1024 * 4);
14             for (int i = 0; i < 100; i++) {
15                 String writeStr = "张柳宁 - " + UUID.randomUUID().toString() + System.lineSeparator();//待写入的数据
16                 charBuffer.put(writeStr);
17                 charBuffer.flip();
18                 encoder.encode(charBuffer, byteBuffer, true);
19                 byteBuffer.flip();
20                 fileChannel.write(byteBuffer);
21                 byteBuffer.clear();
22                 charBuffer.clear();
23             }
24 
25         }
26     }
  • 缓冲区 

缓冲区根据缓存的内容不同,可以分为如下几类

ByteBuffer
CharBuffer
DoubleBuffer
FloatBuffer
IntBuffer
LongBuffer 
ShortBuffer 
MappedBytesBuffer  - 用于映射内存与文件
 

Buffer本质是就是一块内存区,可以用来读写数据,这块内存被NIO包装起来,对外提供了一系列便于开发的接口

先来说说Buffer的共性

1、capacity,所有的缓冲区都有一个初始大小,它代表了这块缓冲区的容量,不可改变

2、limit,读写操作允许的最大位置。刚开始创建的时候,limit等于capacity

3、position,当前读写位置。初始化时为0.一旦写数据,写一个就往后移动一个单元,最大值为capacity-1

Buffer提供的很多方式都是在对以上三个属性进行操作

举例:

  读缓冲区前:调用flip,limit设为当前的position,position设为0.这样在读取的时候才能将有效数据全部读取出来

  读缓冲区后:如果全部读完了,调用clear,对Buffer重新初始化,compact 方法会将已经读取到的数据清除出缓冲区,未读取的数据通通往前移

  重复读取:rewind,将position设为0,limit保持不变

其他操作

  mark,记录当前position

  reset,恢复到mark时候的position

其他方法详见API

关于limit,Buffer在读和写的时候含义有所区别,见下图

  • 单通道,多缓冲区读写

1、将一个通道中的数据一次性写入到多个缓冲区中

ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body   = ByteBuffer.allocate(1024);
ByteBuffer[] bufferArray = { header, body };
channel.read(bufferArray);

在写入缓冲区数组过程中,按照顺序去填充缓冲区,适合报文长度固定的情况。

2、多个缓冲区的数据写入到一个通道中

ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body   = ByteBuffer.allocate(1024);
//write data into buffers
ByteBuffer[] bufferArray = { header, body };
channel.write(bufferArray);

按照顺序,从position到limit之间的数据会被写入到channel中

  • 文件传输
    public void copyFile() {
        try (FileChannel srcChannel = FileChannel.open(Paths.get("/Users/sherry/WorkPath/Git/LinkTest/zln-learning/testDir/content.html"), StandardOpenOption.READ);
             FileChannel destChannel = FileChannel.open(Paths.get("/Users/sherry/WorkPath/Git/LinkTest/zln-learning/testDir/content_bak.html"), StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {
//            srcChannel.transferTo(0, srcChannel.size(), destChannel);//等效
            destChannel.transferFrom(srcChannel, 0, srcChannel.size());//等效
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        }
    }
原文地址:https://www.cnblogs.com/sherrykid/p/6009378.html