不学无数——JAVA中NIO再深刻

JAVA中NIO再深刻

在上一章节的JAVA中的I/O和NIO咱们学习了如何使用NIO,接下来再深刻了解一下关于NIO的知识。html

缓冲器内部的细节

Buffer数据能够高效地访问及操做这些数据的四个索引组成。这四个索引是java

  • mark:标记,就像游戏中设置了一个存档同样,能够调用reset()方法进行回归到mark标记的地方。
  • position:位置,其实缓冲器实际上就是一个美化过的数组,从通道中读取数据就是放到了底层的数组。因此其实就像索引同样。因此positon变量跟踪已经写了多少数据。
  • limit:界限,即代表还有多少数据须要取出,或者还有多少空间可以写入。
  • capacity:容量,代表缓冲器中能够存储的最大容量。

在缓冲器中每个读写操做都会改变缓冲器的状态,用于反应所发生的变化。经过记录和跟踪这些变化,缓冲器就可以内部地管理本身的资源。下面是用于设置和复位索引以及查询其索引值的方法sql

方法名 解释
capacity() 返回缓冲器的容量
clear() 清空缓冲器,将position设置为0,limit设置容量。调用此方法复写缓冲器
flip() 将limit设置为position,position设置为0.此方法用于准备从缓冲区读取已经写入的数据
limit() 返回limit值
limit(int lim) 设置limit值
mark() 将mark设置为positon
position() 返回position的值
position(int pos) 设置postion的值
remaining() 返回limit-position的值

接下来咱们写个例子模拟这四个索引的变化状况,例若有一个字符串BuXueWuShu。咱们交换相邻的字符。编程

private static void symmetricScranble(CharBuffer buffer){
        while (buffer.hasRemaining()){
            buffer.mark();
            char c1 = buffer.get();
            char c2 = buffer.get();
            buffer.reset();
            buffer.put(c2).put(c1);
        }
    }

复制代码
public static void main(String[] args) {
        char [] data = "BuXueWuShu".toCharArray();
        ByteBuffer byteBuffer = ByteBuffer.allocate(data.length*2);
        CharBuffer charBuffer = byteBuffer.asCharBuffer();
        charBuffer.put(data);
        System.out.println(charBuffer.rewind());
        symmetricScranble(charBuffer);
        System.out.println(charBuffer.rewind());
        symmetricScranble(charBuffer);
        System.out.println(charBuffer.rewind());
    }

复制代码

rewind()方法是将position设为0 ,mark设为-1数组

在刚进入symmetricScranble ()方法时的各个索引以下图所示缓存

而后第一次调用了mark()方法之后就至关于给mark赋值了,至关于在此设置了一个回档点。此时索引以下所示bash

而后每次调用get()方法Position索引都会改变,在第一次调用了两次get()方法之后,各个索引以下app

而后调用了reset()方法另Position=Mark,此时的索引以下dom

而后每次调用put()方法也会改变Position索引的值,工具

注意此时前两个字符已经互换了位置。而后在第二轮while开始再次改变了Mark索引的值,各个索引以下

此时咱们应该就知道前面咱们说的调用clear()方法并不会清除缓冲器里面的数据的缘由了,由于只是将其索引变了而已。

内存映射文件

内存映射文件不是Java引入的概念,而是操做系统提供的一种功能,大部分操做系统都支持。

内存映射文件容许咱们建立和修改那些由于太大而不能放入内存的文件。有了内存映射文件,咱们就能够假定整个文件都放在内存中,并且能够彻底将其视为很是大的数组进行访问。因此对于文件的操做就变为了对于内存中的字节数组的操做,而后对于字节数组的操做会映射到文件中。这种映射能够映射整个文件,也能够只映射文件中的一部分。何时字节数组中的的操做会映射到文件上呢?这是由操做系统内部决定的。

内存放不下整个文件也没关系,操做系统会自动进行处理,将须要的内容读到内存,将修改的内容保存到硬盘,将再也不使用的内存释放。

如何用NIO将文件映射到内存中呢?下面有个小例子表示将文件的前1024个字节映射到内存中。

FileChannel fileChannel = new FileInputStream("").getChannel();
MappedByteBuffer map = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
复制代码

建立一个内存映射文件只须要在通道中调用map()方法便可,MapMode有如下三个参数

  • READ_ONLY:建立一个只读的映射文件
  • READ_WRITE:建立一个既能读也能写的映射文件
  • PRIVATE:建立一个写时拷贝(copy-on-write)的映射文件

咱们能够简单的对比一下用内存映射文件对文件进行读写操做和用缓存Buffer对文件进行读写操做的速度比较。

public static void main(String[] args) throws IOException {
        String fileName="/Users/hupengfei/Downloads/a.sql";
        long t1=System.currentTimeMillis();
        FileChannel fileChannel = new RandomAccessFile(fileName,"rw").getChannel();
        IntBuffer map = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, fileChannel.size()).asIntBuffer();
        map.put(0);
        for (int i = 1; i < 50590; i++) {
            map.put(map.get(i-1));
        }
        fileChannel.close();
        long t=System.currentTimeMillis()-t1;
        System.out.println("Mapped Read/Write:"+t);

        long t2=System.currentTimeMillis();
        RandomAccessFile randomAccessFile = new RandomAccessFile(new File(fileName),"rw");
        randomAccessFile.writeInt(1);
        for (int i = 0 ; i<50590;i++){
            randomAccessFile.seek(randomAccessFile.length()-4);
            randomAccessFile.writeInt(randomAccessFile.readInt());
        }
        randomAccessFile.close();
        long t22=System.currentTimeMillis()-t2;
        System.out.println("Stream Read/Write:"+t22);
    }

复制代码

发现打印以下

Mapped Read/Write:29
Stream Read/Write:2439
复制代码

文件越大,那么这个差别会更明显。

文件加锁

在JDK1.4中引入了文件加锁的机制,它容许咱们同步的访问某个做为共享资源的文件。对于同一文件竞争的两个线程多是来自于不一样的操做系统,也多是不一样的进程,也多是相同的进程,例如Java中两个线程对于文件的竞争。文件锁对于其余的操做系统的进程是可见的,由于文件加锁是直接映射到了本地操做系统的加锁工具。

下面举了一个简单的关于文件加锁的例子

public static void main(String[] args) throws IOException, InterruptedException {
        FileOutputStream fileOutputStream = new FileOutputStream("/Users/hupengfei/Downloads/a.sql");
        FileLock fileLock = fileOutputStream.getChannel().tryLock();
        if (fileLock != null){
            System.out.println("Locked File");
            TimeUnit.MICROSECONDS.sleep(100);
            fileLock.release();
            System.out.println("Released Lock");
        }
        fileLock.close();
    }

复制代码

经过对FileChannel调用tryLock()或者lock()方法,就能够得到整个文件的FileLock

  • tryLock():是非阻塞的,若是不能得到锁,那么他就会直接从方法调用中返回
  • lock():是阻塞的,它要阻塞进程直到锁能够得到为止

调用FileLock.release()能够释放锁。

固然也能够经过如下的方式对于文件的部分进行上锁

tryLock(long position,long size,boolean shared)
lock(long position,long size,boolean shared)
复制代码

对于加锁的区域是经过positionsize进行限定的,而第三个参数指定是否为共享锁。无参数的加锁方法会对整个文件进行加锁,甚至文件变大之后也是如此。其中锁的类型是独占锁仍是共享锁能够经过FileLock.isShared()进行查询。

参考文章