zoukankan      html  css  js  c++  java
  • Java NIO中的Channel接口

    1、

    Channel  通道,可以将指定文件的部分或全部直接映射成Buffer。

    不能直接读写Channel中的数据,Channel只能与ByteBuffer交互。

    读数据时,把Channel中的数据映射到ByteBuffer中取出数据使用。

    写数据时,把数据放到Buffer中,再把ByteBuffer中的数据写到Channel中。

    Channel是一个接口,常用的实现类有:

    • FileChannel    用于文件读写
    • DatagramChannel    用于UDP通信的Channel
    • ServerSocketChannel、SocketChannel    用于TCP通信的Channel

     这里只介绍FileChannel,用于UDP、TCP通信的Channel在写网络编程时再介绍。

    2、Channel常用方法:

    • 字节流对象.getChannel()      //获取对应类型的Channel对象。只有字节流对象才有getChannel()方法。
    • Channel对象.read(ByteBuffer  buffer)      //从Channel对应的输入流中读取数据到buffer中,buffer只能是ByteBuffer类型,不能是其他Buffer类型
    • Channel对象.write(ByteBuffer buffer)    //将buffer中的数据写到channel对应的流中
    • Channel对象.position()    //返会Channel中记录指针的位置,返回值是long型
    • Channel对象.position(long  index)     //将Channel中的记录指针调整到指定的位置
    • Channel对象.map(映射模式,起始下标,长度)    //将文件的部分/全部映射到一个MappedByteBuffer对象中,返回该MappedByteBuffer对象。

    3、

     示例:读文件,一次读完

     1         //创建Channel
     2         File file=new File("./1.txt");
     3         FileInputStream in=new FileInputStream(file);
     4         FileChannel channel=in.getChannel();  //通过文件输入流对象获取Channel
     5 
     6         //创建Buffer
     7         ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
     8 
     9         //将Channel中的数据读取到Buffer中。Channel只能与ByteBuffer交互,不能与其它Buffer交互
    10         channel.read(byteBuffer);
    11 
    12         //调用ByteBuffer的flip()方法,调整指针,做好数据使用准备
    13         byteBuffer.flip();
    14 
    15         //对ByteBuffer进行解码,转换为CharBuffer。因为ByteBuffer不能直接转换为String,通过toString()转换得到String不是文件内容。要把ByteBuffer转换为CharBuffer。
    16         Charset charset=Charset.forName("GBK");    //创建Charset对象。Windows创建的文本文件默认以GBK编码,即默认的编码字符集为GBK。
    17         //这里使用的编码字符集要与文件的编码字符集对应。如果我们在创建文件时指定字符集为UTF-8,或者在资源管理器中将文件的编码修改为UTF-8,则此处使用UTF-8.
    18         CharsetDecoder decoder=charset.newDecoder();   //创建解码器
    19         CharBuffer charBuffer=decoder.decode(byteBuffer);   //使用解码器对ByteBuffer解码,得到CharBuffer
    20 
    21         //此处CharBuffer不能调用flip(),调用了反而没有数据
    22 
    23         //可以通过get()获取char,也可以通过get(char[] arr)读取到一个char[]中,或者使用toString()转换为String
    24         System.out.println(charBuffer.get());  //获取第一个字符
    25         System.out.println(charBuffer.toString());  //把CharBuffer剩余部分转换为String输出。注意是剩余部分的数据。
    26         
    27         //sout输出一个对象时,会自动调用这个对象的toString()方法,将对象转换为String输出。
    28         //所以也可以写为   System.out.println(charBuffer);
    29         
    30         byteBuffer.clear();
    31         charBuffer.clear();
    32         channel.close();
    33         in.close();

    示例:读文件,循环读取

     1         //创建Channel
     2         FileInputStream in=new FileInputStream("./1.txt");
     3         FileChannel channel=in.getChannel();
     4 
     5         //创建Buffer
     6         ByteBuffer byteBuffer=ByteBuffer.allocate(102);
     7         CharBuffer charBuffer;
     8 
     9         //创建解码器
    10         CharsetDecoder decoder=Charset.forName("GBK").newDecoder();
    11 
    12         //循环读取数据
    13         while (channel.read(byteBuffer)!=-1){  //read()后,Channel中的指针会自动后移。没数据可读时返回-1。
    14             byteBuffer.flip();   //做好数据使用准备
    15             charBuffer=decoder.decode(byteBuffer);   //解码
    16             System.out.println(charBuffer);
    17             byteBuffer.clear();   //清空,准备下次使用。必须清空byteBuffer。
    18             /*
    19             因为channel.read(byteBuffer)的机制是把channel的数据读取到byteBuffer中,返回byteBuffer中的内容长度,不是返回从channel中读取的数据长度。
    20             如果不清空byteBuffer,第一次循环后,记录指针指到byteBuffer末尾,再次执行channel.read(byteBuffer)时,因为byteBuffer是满的,没有剩余空间,
    21             不会从Channel中读取新数据,而返回的byteBuffer的内容长度不等于-1,会再次执行循环(使用第一次byteBuffer中的数据)。
    22             会一直使用第一次读取到的数据,陷入死循环。
    23             */
    24             charBuffer.clear();   //这个可缺省,因为下一次的值会自动覆盖上一次的。
    25         }
    26 
    27         channel.close();
    28         in.close();

    示例:写文件

     1         //创建Channel
     2         FileOutputStream out=new FileOutputStream("./2.txt");
     3         FileChannel channel=out.getChannel();
     4 
     5         //创建Buffer
     6         ByteBuffer buffer=ByteBuffer.allocate(1024);
     7 
     8         //将要写的数据放入Buffer中
     9         buffer.put("hello world!".getBytes());
    10 
    11         //要调整好指针,标明可用数据。否则Buffer中的可用数据为空
    12         buffer.flip();
    13 
    14         //将Buffer中的数据写入Channel。会同步写入到文件中。
    15         channel.write(buffer);
    16 
    17         buffer.clear();
    18         channel.close();
    19         out.close();

    上面的三个例子,都使用了Buffer,本质和传统IO流的缓冲是一样的,读写速度都很快,但都没有使用通道映射。

    4、

    示例:使用通道映射读写文件

     1 File inFile=new File("./1.txt");
     2         FileChannel inChannel=new FileInputStream(inFile).getChannel();
     3         FileChannel outChannel=new FileOutputStream("./2.txt").getChannel();
     4 
     5         /*
     6         把文件输入流的Channel映射到Buffer中,输入流的Channel只能映射为只读。
     7         映射的是整个文件。把inFile单独作为一个对象,就是为了获取文件长度
     8         */
     9         MappedByteBuffer buffer=inChannel.map(FileChannel.MapMode.READ_ONLY,0,inFile.length());
    10 
    11         //把Buffer中的内容写到输出流的Channel中,会同步写到输出文件中。这就实现了文件复制。
    12         outChannel.write(buffer);
    13 
    14         inChannel.close();
    15         outChannel.close();

    示例:使用通道映射读文件

     1 //创建Channel
     2         File inFile=new File("./1.txt");
     3         FileChannel inChannel=new FileInputStream(inFile).getChannel();
     4 
     5         //映射到Buffer中
     6         MappedByteBuffer buffer=inChannel.map(FileChannel.MapMode.READ_ONLY,0,inFile.length());
     7 
     8         //创建解码器。MappedByteBuffer是ByteBuffer的子类,要转换为CharBuffer使用。
     9         CharsetDecoder decoder=Charset.forName("GBK").newDecoder();
    10         
    11         //通道映射得到的MappedByteBuffer,使用MappedByteBuffer中的数据之前不必flip()调整指针,指针已经调整好了。
    12         //当然   buffer.flip();  写上也行
    13         
    14         //转换为CharBuffer
    15         CharBuffer charBuffer=decoder.decode(buffer);
    16 
    17         //使用charBuffer中的数据
    18         System.out.println(charBuffer);
    19 
    20         buffer.clear();
    21         charBuffer.clear();
    22         inChannel.close();

    使用通道映射是最快的。但如果映射的文件很大,比如1,2个G,一次性映射整个文件会占用很大的内存,反而会引起性能的下降,此时可以使用循环依次映射读取。

    也可以只使用缓冲、不使用通道映射,利用循环依次读取,但是速度要慢些。

    如果不用把数据(内容)转化为String,就不必使用解码器。

    5、

    RandomAccessFile类也可以使用通道映射:

     1      //创建Channel
     2         File file = new File("./1.txt");
     3         RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");  //使用RandomAccessFile可以指定文件打开方式
     4         FileChannel channel = randomAccessFile.getChannel();
     5 
     6         /*
     7         将Channel映射到Buffer中。
     8         以r只读打开,只能映射为只读;以rw读写方式打开,不管映射模式指定为只读、还是读写,都会映射为读写。
     9         就是说,以rw方式打开的文件,进行通道映射后,Channel既可以读,又可以写(会同步到文件中)
    10          */
    11         MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, file.length());
    12 
    13         channel.position(file.length()); //将记录指针移到Channel末尾
    14         channel.write(buffer);   //将buffer写到Channel末尾,即复制内容追加到末尾
    15 
    16         channel.close();
    17         randomAccessFile.close();

    使用RandomAccessFile类进行通道映射的优点:

    可以指定文件打开方式,以读写方式打开进行映射后,Channel既可以读,又可以写,适用于要同时进行读写的文件。

    6、注意点

    使用ByteBuffer中的数据之前,要先flip()调整好指针位置。

    如果后续还要使用ByteBuffer,要先调用clear()将ByteBuffer清空后再使用(在循环读取数据时,往往要用到)。

    File不用关闭,但File对应的流要关闭。

    RandomAccessFile、Channel和流很相似,都需要关闭。

    Buffer只是一个容器,不是流,不用关闭。

  • 相关阅读:
    分享知识-快乐自己:Spring整合定时器
    自定义响应结构 AjaxResult()
    日期工具类 DateUtils(继承org.apache.commons.lang.time.DateUtils类)
    处理json的工具类({本类为处理json的工具类})
    单机版 RedisUtils({基本操作封装工具类})【三】
    单机版 JedisUtil({基本操作封装工具类})【二】
    单机版 RedisPoolUtil({基本操作封装工具类})【一】
    序列化工具类({对实体Bean进行序列化操作.},{将字节数组反序列化为实体Bean.})
    分享知识-快乐自己:遍历Map集合
    自己的新闻后台管理系统
  • 原文地址:https://www.cnblogs.com/chy18883701161/p/10928979.html
Copyright © 2011-2022 走看看