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只是一个容器,不是流,不用关闭。

 

posted @ 2019-05-28 11:58  chy_18883701161  阅读(639)  评论(0编辑  收藏  举报