Java NIO中的Channel接口
Posted chy18883701161
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java NIO中的Channel接口相关的知识,希望对你有一定的参考价值。
Channel 通道,可以将指定文件的部分或全部直接映射成Buffer。
不能直接读写Channel中的数据,Channel只能与ByteBuffer交互。
读数据时,把Channel中的数据映射到ByteBuffer中取出数据使用。
写数据时,把数据放到Buffer中,再把ByteBuffer中的数据写到Channel中。
Channel是一个接口,常用的实现类有:
- FileChannel 用于文件读写
- DatagramChannel 用于UDP通信的Channel
- ServerSocketChannel、SocketChannel 用于TCP通信的Channel
这里只介绍FileChannel,用于UDP、TCP通信的Channel在写网络编程时再介绍。
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对象。
示例:读文件,一次读完
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流的缓冲是一样的,读写速度都很快,但都没有使用通道映射。
示例:使用通道映射读写文件
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,就不必使用解码器。
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既可以读,又可以写,适用于要同时进行读写的文件。
注意:
使用ByteBuffer中的数据之前,要先flip()调整好指针位置。
如果后续还要使用ByteBuffer,要先调用clear()将ByteBuffer清空后再使用(在循环读取数据时,往往要用到)。
File不用关闭,但File对应的流要关闭。
RandomAccessFile、Channel和流很相似,都需要关闭。
Buffer只是一个容器,不是流,不用关闭。
以上是关于Java NIO中的Channel接口的主要内容,如果未能解决你的问题,请参考以下文章