Channel(管道)
1、简介
基本上,所有的 IO 在NIO 中都从一个Channel 开始。Channel 有点象流。 数据可以从Channel读到Buffer中,也可以从Buffer 写到Channel中。这里有个图示:
JAVA NIO中的一些主要Channel的实现:
- FileChannel
- DatagramChannel
- SocketChannel
- ServerSocketChannel
2、FileChannel
Java NIO中的FileChannel是一个连接到文件的通道。可以通过文件通道读写文件。FileChannel无法设置为非阻塞模式,它总是运行在阻塞模式下。
(1)创建 FileChannel:
在使用FileChannel之前,必须先创建它。创建方式有两种:
- 第一种:使用一个InputStream、OutputStream或RandomAccessFile来获取一个FileChannel实例。
- 第二种:FileChannel.open()方法,JDK1.7之后才能使用。
(2)向Channel写入数据:
使用FileChannel.write()方法向FileChannel写数据,该方法的参数是一个Buffer 。
while (buf.hasRemaining()){
channel.write(buf);
}
注意FileChannel.write()是在while循环中调用的。因为无法保证write()方法一次能向FileChannel写完所有字节,因此需要重复调用write()方法,直到Buffer中已经没有尚未写入通道的字节。
(3)关闭Channel:
用完FileChannel后必须将其关闭。如:
channel.close();
(4)案例一:
使用channel把数据写入文件:
package basis.StuNIO;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class StuChannel {
public static void main(String[] args) throws Exception{
//使用普通流获取FileChannel实例,权限要可写
RandomAccessFile raf = new RandomAccessFile("info.txt","rw");
FileChannel channel = raf.getChannel();
String data = "suxing你好\r\nsuxing你好\nsuxing你好\nsuxing你好\nsuxing你好\nsuxing你好\nsuxing你好\nsuxing你好\n";
//创建Buffer,分配空间,大小要能放得下 data
ByteBuffer buf = ByteBuffer.allocate(1024);//间接内存
buf.put(data.getBytes());
//切换到读模式,一定
buf.flip();
//循环写入数据
while (buf.hasRemaining()){
channel.write(buf);
}
//关闭
channel.close();
raf.close();
}
}
上述代码有几点注意事项:
- 使用RandomAccessFile读写数据,文件权限必须是RW。
- 创建的Buffer大小要能放得下要写入的数据。
- 数据放入Buffer中之后,一定要flip,否则只能向文件内写入空数据。
- channel.write(buf)要放入循环中。
(5)案例二:
使用FileChannel 类的静态方法open() 打开一个通道,并使用FileChannel 的read()方法读取一个文件中的内容。
package basis.StuNIO;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class ChannelRead {
public static void main(String[] args) throws Exception{
//使用open()方法打开通道
FileChannel channel = FileChannel.open(Paths.get("info.txt"), StandardOpenOption.READ);
ByteBuffer buf = ByteBuffer.allocate(1024);//间接内存
while (channel.read(buf)>0){//把硬盘中的数据读出来写入缓冲区。
buf.flip();
String data = new String(buf.array(),0,buf.limit());
System.out.println(data);
}
channel.close();
}
}
(6)FileChannel.open()源码
public static FileChannel open(Path path,
Set<? extends OpenOption> options,
FileAttribute<?>... attrs)
throws IOException
{
FileSystemProvider provider = path.getFileSystem().provider();
return provider.newFileChannel(path, options, attrs);
}
/**
* @since 1.7
*/
public static FileChannel open(Path path, OpenOption... options)
throws IOException
{
Set<OpenOption> set = new HashSet<OpenOption>(options.length);
Collections.addAll(set, options);
return open(path, set, NO_ATTRIBUTES);
}
两个方法均接受一个Path接口 类型的参数作为将要读取文件的路径。
3、Path接口、Paths工具类和StandardOpenOption
(1)Path:
Path 可以用于在文件系统中定位文件的对象。它通常代表一个系统依赖的文件路径。Path对象是一组目录名称的序列,后面可以跟文件名。
package java.nio.file;
public interface Path
extends Comparable<Path>, Iterable<Path>, Watchable
{}
Path对象获取的方式有三种:
//第一种
FileSystems.getDefault().getPath();
//第二种
new File("d:/a.txt").toPath();
//第三种
Paths.get("d:", "a.txt");//在d盘找a.txt文件
Paths.get("a.txt"); //在项目根目录找a.txt文件
(2)Paths:
Paths 这类由专门的静态方法返回一个Path通过转换路径字符串或URI。
Paths里面一共只有两个静态方法get(),用于由给定的参数获取Path对象。
package java.nio.file;
/**
* @since 1.7
*/
public final class Paths {
private Paths() { }
public static Path get(String first, String... more) {
return FileSystems.getDefault().getPath(first, more);
}
public static Path get(URI uri) {
}
}
第一个Path的get()方法接受String类型的不定参数,该参数由 StandardOpenOption 类的静态常量提供,表示对文件操作的权限或模式(只读、写、创建等)。
StandardOpenOption:
StrandarOpenOption是一个枚举,提供了Path对文件的操作的标准打开操作方式。
package java.nio.file;
/**
* Defines the standard open options.
* @since 1.7
*/
public enum StandardOpenOption implements OpenOption {
READ,//读模式
WRITE,//写模式
APPEND,//追加
TRUNCATE_EXISTING,
//如果文件不存在,创建新的,如果存在,不创建。
CREATE,
CREATE_NEW,//创建新的,如果文件存在则报错
DELETE_ON_CLOSE,
SPARSE,
SYNC,
DSYNC;
}
4、使用FileChannel 复制图片文件
直接缓冲区的使用,可以提高读写的速度。但是直接缓冲区的创建和销毁的开销比较大,一般大文件操作或能显著提高读写性能时使用。
代码:
package basis.StuNIO;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class FileChannelCopy {
public static void main(String[] args) throws Exception{
//创建读写通道
FileChannel inChannel = FileChannel.open(Paths.get("e:\\111.jpg"), StandardOpenOption.READ);
FileChannel outChannel = FileChannel.open(Paths.get("e:\\test\\haha.jpg"),StandardOpenOption.WRITE,StandardOpenOption.CREATE);
//创建直接缓冲区
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
int len = 0;
//复制
while ((len = inChannel.read(buffer))>0){
buffer.flip();
outChannel.write(buffer);
buffer.clear();
}
inChannel.close();
outChannel.close();
System.out.println("文件复制完毕。");
}
}
5、使用内存映射文件复制大文件
(1)MappedByteBuffer 复制大文件
内存映射文件也属于直接缓冲区。
代码:
package basis.StuNIO;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
public class StuMappedChannel {
public static void main(String[] args) throws Exception{
//创建通道
FileChannel inChannel = new RandomAccessFile("e:\\111.jpg","r").getChannel();
FileChannel outChannel = new RandomAccessFile("e:\\test\\test.jpg","rw").getChannel();
//使用内存映射缓冲区(直接缓冲区)
MappedByteBuffer map = inChannel.map(FileChannel.MapMode.READ_ONLY,0,inChannel.size());
outChannel.write(map);
//关闭
inChannel.close();
outChannel.close();
System.out.println("文件复制完毕。");
}
}
注意:如果文件超过2G,需要分多个文件映射。
(2)MappedByteBuffer:
java io操作中通常采用BufferedReader,BufferedInputStream等带缓冲的IO类处理大文件,不过java nio中引入了一种基于MappedByteBuffer操作大文件的方式,其读写性能极高。FileChannel.map()方法可以获取该抽象类实例。
继承关系和类声明:
java.lang.Object
|___java.nio.Buffer
|___java.nio.ByteBuffer
|___java.nio.MappedByteBuffer
package java.nio;
/**
* @since 1.4
*/
public abstract class MappedByteBuffer extends ByteBuffer{}
从继承结构上看,MappedByteBuffer继承自ByteBuffer,内部维护了一个逻辑地址address。FileChannel提供了map方法把文件映射到虚拟内存,通常情况可以映射整个文件,如果文件比较大,可以进行分段映射。
FileChannel 中 的内部类 MapMode类为MappedByteBuffer 提供内存映像文件访问的方式:
MapMode mode:
- MapMode.READ_ONLY:只读,试图修改得到的缓冲区将导致抛出异常。
- MapMode.READ_WRITE:读/写,对得到的缓冲区的更改最终将写入文件;但该更改对映射到同一文件的其他程序不一定是可见的。
- MapMode.PRIVATE:私用,可读可写,但是修改的内容不会写入文件,只是buffer自身的改变,这种能力称之为”copy on write”。
FileChannel.MapMode:
public static class MapMode {
public static final MapMode READ_ONLY
= new MapMode("READ_ONLY");
public static final MapMode READ_WRITE
= new MapMode("READ_WRITE");
public static final MapMode PRIVATE
= new MapMode("PRIVATE");
private final String name;
private MapMode(String name) {
this.name = name;
}
public String toString() {
return name;
}
}