NIO中的Channel(通道)是一个接口,有以下几种实现类:FileChannel、SocketChannel、ServerSocketChannel、DatagramChannel等。
可以使用以下方法获取Channel对象
FileInputStream
FileOutStream
RandomAccessFile
这三个属于本地IO,这些类中毒提供了FileChannel getChannel()方法,可以用于获取FileChannel对象
Socket
ServerSocket
DatagramSocket
这三个属于网络型IO类,这些类提供了XXXChannel getChannel()方法,可以用于获取不同类型的Channel对象
FileChannel等各个Channel实现类
Channel实现类中,都提供了用于获取Channel的open()方法
范例1:文件复制,在非直接缓冲区中,借助Channel实现文件复制
public static void main(String[] args) throws IOException {
long start=System.currentTimeMillis();
FileInputStream input=new FileInputStream("C:\\Users\\peng\\Desktop\\cdbf6c81800a19d86524639732fa828ba61e4679.jpg");
FileOutputStream out=new FileOutputStream("C:\\Users\\peng\\Desktop\\1.jpg");
FileChannel channel = input.getChannel();
FileChannel channel1 = out.getChannel();
// ByteBuffer buffer=ByteBuffer.allocateDirect(1024);//直接缓冲区
ByteBuffer buffer=ByteBuffer.allocate(1024);//非直接缓冲区
while(channel.read(buffer)!=-1){
buffer.flip();
channel1.write(buffer);
buffer.clear();
}
long end =System.currentTimeMillis();
System.out.println("复制文件用时(毫秒)"+(end-start));
//依次关闭
channel1.close();
channel.close();
out.close();
input.close();
}
allocateDirect在源码中实际调用的是new DirectByteBuffer(capacity)来创建直接缓冲区对象,DirectByteBuffer标识堆外内存,也就是程序中使用的直接缓冲区
上文提到Buffer有一个long address属性,address就是Buffer使用的堆外内存的地址,并且address只会在直接缓冲区中被使用。
DirectByteBuffer类的类定义class DirectByteBuffer extends MappedByteBUffer...,由此可知MappedByteBUffer是堆外内存的父类,因此可以使用MappedByteBuffer来操作堆外内存,即操作直接缓冲区,MappedByteBuffer也成为内存映射文件
在支持零拷贝的操作系统上,可以使用MappedByteBuffer类和FileChannel中的transferFrom()/transferTo()方法进行文件的零拷贝。
范例2:直接缓冲区的文件复制
public void test2() throws IOException{
long start=System.currentTimeMillis();
//文件的输入通道
FileChannel in = FileChannel.open(Paths.get("C:\\Users\\peng\\Desktop\\cdbf6c81800a19d86524639732fa828ba61e4679.jpg"), StandardOpenOption.READ);
//文件的输出通道
FileChannel out = FileChannel.open(Paths.get("C:\\Users\\peng\\Desktop\\1.jpg"), StandardOpenOption.WRITE, StandardOpenOption.READ,StandardOpenOption.CREATE);
//输入通道和输出通道之间的内存映射文件
MappedByteBuffer inmap = in.map(FileChannel.MapMode.READ_ONLY, 0, in.size());
MappedByteBuffer outmap = out.map(FileChannel.MapMode.READ_WRITE, 0, in.size());
byte[] bytes = new byte[inmap.limit()];
inmap.get(bytes);
outmap.put(bytes);
in.close();
out.close();
long end =System.currentTimeMillis();
System.out.println("复制文件用时(毫秒)"+(end-start));
}
直接缓冲区中的内存映射文件MappedByteBuffer本身代表了磁盘上对应的物理文件,如果修改了MapperByteBuffer,磁盘上的物理文件也会随之修改(实际上,这种同步操作是由操作系统完成的),因此在开发的时候,应用程序只需要关心内存中的数据。建议将直接缓冲区分配给那些持久的,经常重用的数据使用。
范例3:零拷贝
public void test3() throws IOException{
long start=System.currentTimeMillis();
//文件的输入通道
FileChannel in = FileChannel.open(Paths.get("C:\\Users\\peng\\Desktop\\cdbf6c81800a19d86524639732fa828ba61e4679.jpg"), StandardOpenOption.READ);
//文件的输出通道
FileChannel out = FileChannel.open(Paths.get("C:\\Users\\peng\\Desktop\\1.jpg"), StandardOpenOption.WRITE, StandardOpenOption.READ,StandardOpenOption.CREATE);
//输入通道和输出通道之间的内存映射文件
in.transferTo(0,in.size(),out);
in.close();
out.close();
long end =System.currentTimeMillis();
System.out.println("复制文件用时(毫秒)"+(end-start));
}
范例4:资源加锁
在FileChannel中也提供了Lock的加锁方式
public static void main(String[] args) throws IOException, InterruptedException {
RandomAccessFile rw = new RandomAccessFile("C:\\Users\\peng\\Desktop\\cdbf6c81800a19d86524639732fa828ba61e4679.jpg", "rw");
FileChannel channel = rw.getChannel();
//第三个参数是否是共享锁
FileLock lock = channel.lock(2, 4, false);
System.out.println("主线程将文件锁定3S");
new Thread(()->{
byte[] bs=new byte[8];
try {
// rw.read(bs);
if(lock.isValid()){
rw.write("ccc".getBytes(),0,8);
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
Thread.sleep(3000);
System.out.println("3s结束");
lock.release();
}
范例5 管道pipe
两个线程之间单项传递数据时,可以使用Pipe规范数据的读写操作
可以使用Pipe.open()创建管道
想向管道写数据可以访问SinkChannel管道,读数据访问SourceChannel管道
public static void testPipe() throws IOException{
//创建管道
Pipe pipe = Pipe.open();
ByteBuffer buf = ByteBuffer.allocate(1024);
//通过SinkChannel,向Pipe中写数据
Pipe.SinkChannel sinkChannel = pipe.sink();
buf.put("helloworld".getBytes());
buf.flip();
sinkChannel.write(buf);
// 通过SourceChannel,从Pipe中读取数据
Pipe.SourceChannel sourceChannel = pipe.source();
buf.flip();
int len = sourceChannel.read(buf);
System.out.println( new String(buf.array(),0,len));
sourceChannel.close();
sinkChannel.close();
}











网友评论