Java基础学习总结:NIO之(二)Channel

Source
版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/Vaingloryss/article/details/99536240

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之前,必须先创建它。创建方式有两种:

  1. 第一种:使用一个InputStream、OutputStream或RandomAccessFile来获取一个FileChannel实例。
  2. 第二种: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();


    }
}

上述代码有几点注意事项:

  1. 使用RandomAccessFile读写数据,文件权限必须是RW。
  2. 创建的Buffer大小要能放得下要写入的数据。
  3. 数据放入Buffer中之后,一定要flip,否则只能向文件内写入空数据。
  4. 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

  1. MapMode.READ_ONLY:只读,试图修改得到的缓冲区将导致抛出异常。
  2. MapMode.READ_WRITE:读/写,对得到的缓冲区的更改最终将写入文件;但该更改对映射到同一文件的其他程序不一定是可见的。
  3. 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;
        }
    }