NIO详解

NIO

前言

NIO即New IO,这个库是在JDK1.4中才引入的。NIO和IO有相同的做用和目的,但实现方式不一样,NIO主要用到的是块,因此NIO的效率要比IO高不少。在Java API中提供了两套NIO,一套是针对标准输入输出NIO,另外一套就是网络编程NIO。编程

IO与NIO的区别

  • IO流是面向流的,属于阻塞IO,没有选择器。
  • NIO流是面向块(缓冲区),属于非阻塞IO有选择器。
    其中最大的区别就是一个面向流,一个面向缓冲区。
    • 面向流:IO流每次读取一个字节或者多个,直到读完为止。不能对流中的数据进行操做。
    • 面向缓冲区:NIO流是将数据放到一个缓冲区,须要时能够对缓冲区中的数据进行操做,这样就能够更加灵活的操做数据了。(就好像IO流是一根水管,而NIO是一个运水车)

Buffer(缓冲区)

Buffer是一个抽象类;
子类有:ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer
核心类(经常使用类):ByteBufferCharBuffer 其中ByteBuffer有一个子类MappedByteBufferMappedByteBuffer类可以将文件直接映射到内存中,那么这样咱们就能够像访问内存同样访问文件,很是方便)网络

建立Buffer

由于Buffer都是抽象类,没法直接实例化。建立缓冲区要调用XxxBuffer allocate(int capacity),XxxBuffer allocateDirect(int capacity),参数是缓冲区容量。app

eg:获取ByteBuffer
static ByteBuffer allocate(int capacity)
分配一个新的字节缓冲区(普通Buffer)
static ByteBuffer allocateDirect(int capacity)
分配新的直接字节缓冲区(直接Buffer)dom

两者的区别:异步

  1. 建立普通Buffer成本低,读写的效率不高
  2. 由于建立直接Buffer成本高,因此咱们通常用在Buffer生存周期较长的时候使用
  3. 只有ByteBuffer才可以建立直接Buffer,其余的Buffer对象是不可以建立
  4. 若是建立了直接Buffer可是我又想要使用其余Buffer的功能,能够将ByteBuffer转换成其余Buffer
Buffer参数
  • capacity(容量):缓冲区的容量,不能够为负数,一旦建立了就不可以改变
  • limit(界限):是缓冲区读写数据的终止点,limit以后的区域没法访问
  • position(起始指针):是缓冲区读写数据的起始点,初始值为0。position随着数据的加入而改变,例如读取2个数据到Buffer中,则position = 2
  • mark(标记):该索引可以用于下次读取或者写入,mark在0~position之间,设置该值就会把position移动到mark处

Buffer方法
  • flip():读取模式;肯定缓冲区数据的起始点和终止点,为输出数据作准备(即写入通道), 将limit的值改成postion的值,同时将postion归0
    • 特色: 就是为下一次数据的读取作好准备
  • clear():写入模式;缓冲区初始化,准备再次接收新数据到缓冲区,将limit改成capacity的值,同时将postion归0
    • 特色: 就是为下一次数据的写入作好准备
  • get()和put():获取元素和存放元素。使用clear()以后,没法直接使用get()获取元素,须要使用get(int index)根据索引值来获取相应元素
  • hasRemaining():判断postion到limit之间是否还有元素。
public static  void main(String[] args) {
        CharBuffer buffer = CharBuffer.allocate(8);
        // Buffer已经准备好了向Buffer中写数据    写模式
        System.out.println("capacity:" + buffer.capacity()); // 8
        System.out.println("limit:" + buffer.limit()); // 8
        System.out.println("position:" + buffer.position()); // 0   
        buffer.put('a');
        buffer.put('b');
        buffer.put('c');
        System.out.println("------------------------"); 
        System.out.println("capacity:" + buffer.capacity()); // 8
        System.out.println("limit:" + buffer.limit()); // 8
        System.out.println("position:" + buffer.position()); // 3   
        System.out.println("------------------------");
        // 切换模式  ,limit变为position的位置而后将position变为0
        buffer.flip();
        System.out.println("capacity:" + buffer.capacity()); // 8
        System.out.println("limit:" + buffer.limit()); // 3
        System.out.println("position:" + buffer.position()); // 0
        System.out.println("------------------------");
            System.out.println("------------------");
        buffer.clear(); // 将postion 清 0 ,将limit = capacity  
        System.out.println("capacity:" + buffer.capacity()); // 8
        System.out.println("limit:" + buffer.limit()); // 8
        System.out.println("position:" + buffer.position()); // 0
        // 注意: 调用clear方法只是将读模式改成写模式,并不会清空缓冲区的数据

    }

Channel(通道)

Channel原理相似于传统的流对象,区别在于:
1.Channel可以将指定的部分或者所有文件映射到内存中
2.程序若是想要读取Channel中的数据,不可以直接读写,必须通过Buffer
简单来讲:Channel经过Buffer(缓冲区)进行读写操做。read()表示读取通道数据到缓冲区,write()表示把缓冲区数据写入到通道。ide

Channel实现类
  • FileChannel 和文件相关的通道
  • DatagramChannel 和UDP协议传输数据相关的通道
  • SocketChannel 和TCP协议相关的数据传输通道
  • ServerSocket 和TCP协议相关的数据传输通道
  • Pipe.SinkChannel、Pipe.SourceChannel //线程通讯管道传输数据
Channel经常使用方法
  • read() : 将Channel中的数据读取到Buffer中
  • write() : 向Buffer中写入数据
  • map(): 将channel中的数据所有或者部分映射到Buffer中(MappedByteBuffer,本质也是一个ByteBuffer),map()方法参数(读写模式,映射起始位置,数据长度)。
    • inChannel.map(mode, position, size)
    • MappedByteBuffer mappBuffer = inChannel.map(MapMode.READ_ONLY, 0, srcFile.length());
public static void main(String[] args) throws IOException {
        File srcFile = new File("nio-a.txt");
        FileInputStream fis = new FileInputStream(srcFile);
        FileOutputStream fos = new FileOutputStream(new File("nio-b.txt"));
        
        // 获取Channel对象
        FileChannel inChannel = fis.getChannel();
        FileChannel outChannel = fos.getChannel();
        // 获取MapByteBuffer对象
        MappedByteBuffer mapBuffer = inChannel.map(MapMode.READ_ONLY, 0, srcFile.length());
        //字符集解码器    
        Charset charset = Charset.forName("GBK");
        outChannel.write(mapBuffer);
        CharsetDecoder decoder = charset.newDecoder();
        CharBuffer charBuffer = decoder.decode(mapBuffer);
        System.out.println(charBuffer);
        
        
    }

通道能够异步读写,异步读写表示通道执行读写操做时,也能作别的事情,解决线程阻塞。若是使用文件管道(FileChannel),建议用RandomAccessFile来建立管道,由于该类支持读写模式以及有大量处理文件的方法。post

public static void main(String[] args) throws Exception {
        File f = new File("nio-a.txt");
        
        RandomAccessFile raf = new RandomAccessFile(f, "rw");
        
        FileChannel channel = raf.getChannel();
        
        MappedByteBuffer mapBuffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, f.length());
        
//      raf.seek(f.length());
        channel.position(f.length());
        
        channel.write(mapBuffer);
    }

Charset(字符集)

理解为现实生活的编码表对象
当使用NIO来获取文件内容时,若是是文本数据,那么须要进行转码,才能查看正确内容,这就须要解码器。 若是要把字符数据写入文件,须要将CharBuffer转码成ByteBuffer,这就须要编码器。编码

  • 包含了字节和 Unicode 字符之间转换的 charset,还定义了用于建立解码器和编码器以及获取与 charset 关联的各类方法
  • CharsetDecoder(解码器):把字节转成字符,例如查看文本数据,须要转成字符才能查看,若是是字节,就看不懂了。
  • CharsetEncoder(编码器):把字符转成字节,才能被计算机理解。 由于字节是计算机最小的存储单位,因此Channel的IO操做都与ByteBuffer有关
  • 解码器和编码器都不能直接建立,须要一个Charset对象来建立对应的解码器和编码器。
Charset经常使用方法
  • forName():根据传入的字符集得到对应的字符集对象。
  • defaultCharset():得到当前使用的默认字符集。
  • availableCharsets():得到全部有效的字符集。

NIO遍历文件

public static void main(String[] args) throws IOException {
            //匿名子对象实现FileVisitor接口
        FileVisitor<Path> visitor = new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException {
                System.out.println("正在访问" + path + "文件");
                if(path.endsWith("NIODemo.java")){
                    System.out.println("恭喜您找到Java");
                    return FileVisitResult.CONTINUE;
                }
                return FileVisitResult.CONTINUE;
            }
            
            @Override
            public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes attrs) throws IOException {
                System.out.println("准备访问" + path + "文件");
                return FileVisitResult.CONTINUE;
            }
            
            @Override
            public FileVisitResult visitFileFailed(Path path, IOException exc) throws IOException {
                System.out.println("准备访问" + path + "文件失败");
                System.out.println(exc.getMessage());
                return FileVisitResult.CONTINUE;
            }
        };
            //访问文件树
        Files.walkFileTree(Paths.get("D:\\JavaSE"), visitor);
        
    }

以上线程

@Fzxey

相关文章
相关标签/搜索