MappedByteBuffer的使用

一、IO与NIO

    NIO(JDK1.4)模型是一种同步非阻塞IO,主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(多路复用器)。

1、阻塞与非阻塞

阻塞与非阻塞是描述进程在访问某个资源时,数据是否准备就绪的的一种处理方式。当数据没有准备就绪时:

  • 阻塞:线程持续等待资源中数据准备完成,直到返回响应结果。
  • 非阻塞:线程直接返回结果,不会持续等待资源准备数据结束后才响应结果。

2、同步与异步

    同步与异步是指访问数据的机制

  • 同步一般指主动请求并等待IO操作完成的方式。
  • 异步则指主动请求数据后便可以继续处理其它任务,随后等待IO操作完毕的通知。

3.IO与NIO的区别

a) IO是面向流的,基于字节流和字符流进行操作.NIO是面向缓冲区的,基于Channel和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。

b) IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。

二、什么是MappedByteBuffer

相对于java io操作中通常采用BufferedReader,BufferedInputStream等带缓冲的IO类处理大文件,java nio中引入了一种基于MappedByteBuffer操作大文件的方式,其读写性能极高

三、MappedByteBuffer作用

把文件映射到虚拟内存

现在以及知道了MappedByteBuffer作用是把文件映射到虚拟内存,那么为什么需要映射到虚拟内存呢?先来了解下虚拟内存的概念

1.什么是虚拟内存和物理内存

  • 物理内存:物理内存(Physical memory)是相对于虚拟内存而言的。物理内存指通过物理内存条而获得的内存空间,而虚拟内存则是指将硬盘的一块区域划分来作为内存。
  • 虚拟内存:虚拟内存计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换

2.为什么需要虚拟内存

    如果正在运行的一个进程,它所需的内存是有可能大于内存条容量之和的,如内存条是256M,程序却要创建一个2G的数据区,那么所有数据不可能都加载到内存(物理内存),必然有数据要放到其他介质中(比如硬盘),待进程需要访问那部分数据时,再调度进入物理内存。

四、MappedByteBuffer使用

1.map过程

FileChannel提供了map方法把文件映射到虚拟内存,通常情况可以映射整个文件,如果文件比较大,可以进行分段映射。

2.FileChannel中的几个变量:

  • MapMode mode:内存映像文件访问的方式,共三种:
    1. MapMode.READ_ONLY:只读,试图修改得到的缓冲区将导致抛出异常。
    2. MapMode.READ_WRITE:读/写,对得到的缓冲区的更改最终将写入文件;但该更改对映射到同一文件的其他程序不一定是可见的。
    3. MapMode.PRIVATE:私用,可读可写,但是修改的内容不会写入文件,只是buffer自身的改变,这种能力称之为”copy on write”。
  • position:文件映射时的起始位置。
  • allocationGranularity:需要映射的文件大小

下面粘贴一个demo,案例很简单,一个线程写文件,另一个线程读文件并输出文件内容

写线程:WriteThread

package nio;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * @Package: nio
 * @ClassName: WriteThread
 * @Author: tanp
 * @Description: ${description}
 * @Date: 2020/9/3 15:43
 */
public class WriteThread extends Thread {

    private int threadId;

    private LinkedBlockingQueue fileNameList;

    WriteThread(int threadId) {
        this.threadId = threadId;
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            long time = System.currentTimeMillis();
            String testMsg = "线程" + threadId + "测试写入数据"+i+"时间为"+  time;
            byte[] bytes = testMsg.getBytes();
            String fileName = threadId + "-" +i+"-"+ time;
            FileChannel fChannel;
            RandomAccessFile file;
            long fileLength = 0L;
            MappedByteBuffer buffer;
            try {
                file = new RandomAccessFile("./MT/" + fileName + ".tmp", "rw");
                fChannel = file.getChannel();
                //文件内存映射
                buffer = fChannel.map(FileChannel.MapMode.READ_WRITE, fileLength, bytes.length);
                System.gc();
                buffer.put(bytes);
                file.close();
                fChannel.close();
                fileNameList.add("./MT/" + fileName + ".tmp");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public void setFileNameList(LinkedBlockingQueue fileNameList) {
        this.fileNameList = fileNameList;
    }
}

读线程:ReadThread

package nio;

import java.io.*;
import java.lang.reflect.Method;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * @Package: nio
 * @ClassName: ReadThread
 * @Author: tanp
 * @Description: ${description}
 * @Date: 2020/9/3 15:13
 */
public class ReadThread extends Thread {

    private LinkedBlockingQueue fileNameList = null;
    private int thredId;

    ReadThread(int thredId) {
        this.thredId = thredId;
    }

    @Override
    public void run() {
        RandomAccessFile raf;
        FileChannel fChannel;
        MappedByteBuffer byteBuffer;
        int delCount = 0;
        try {
            while (true) {
                String pathname = (String) fileNameList.poll();
                if (null != pathname) {
                    File file = new File(pathname);
                    if (!file.exists()) {
                        for (int i = 0; i < 3; i++) {
                            System.out.println("读不到文件:" + file.getName());
                            Thread.sleep(1000);
                            file = new File(pathname);
                        }
                    }
                    raf = new RandomAccessFile(pathname, "r");
                    fChannel = raf.getChannel();
                    byteBuffer = fChannel.map(FileChannel.MapMode.READ_ONLY, 0,
                            raf.length());
                    // 用UTF-8进行解码为字符串
                    String bufString = Charset.forName("UTF-8")
                            .decode(byteBuffer).toString();
                    //文件内存映射清理,以防止文件无法删除
                    clean(byteBuffer);
                    System.gc();
                    //文件解析执行
                    System.out.println(bufString);
                    //文件关闭
                    raf.close();
                    fChannel.close();
                    File delFile = new File(pathname);
                    if (!file.exists()) {
                        continue;
                    }
                    if (!delFile.delete()) {
                        delCount++;
                        System.out.println(delFile.getName() + "删除不成功,已有" + delCount + "个文件删除不成功");
                    }else{
                        System.out.println(delFile.getName() + "删除成功");
                    }
                } else {
                    Thread.sleep(3000);
                }
            }
        } catch (IOException e) {
            System.out.println(thredId + ":" + getName());
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void clean(final MappedByteBuffer buffer) {
        if (null == buffer) {
            return;
        }
        AccessController.doPrivileged(new PrivilegedAction() {
            @Override
            public Object run() {
                try {
                    Method getCleanerMethod = buffer.getClass().getMethod(
                            "cleaner");
                    getCleanerMethod.setAccessible(true);
                    sun.misc.Cleaner cleaner = (sun.misc.Cleaner) getCleanerMethod
                            .invoke(buffer, new Object[0]);
                    cleaner.clean();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return null;
            }
        });

    }

    public void setFileNameList(LinkedBlockingQueue fileNameList) {
        this.fileNameList = fileNameList;
    }
}

主线程

package nio;

import java.util.concurrent.LinkedBlockingQueue;

/**
 * @Package: nio
 * @ClassName: NioTestMain
 * @Author: tanp
 * @Description: ${description}
 * @Date: 2020/9/3 15:46
 */
public class NioTestMain {

    public static void main(String[] args) {
        int num = 3;
        LinkedBlockingQueue[] fileNameList = new LinkedBlockingQueue[num];
        for (int i = 0; i < fileNameList.length; i++) {
            fileNameList[i] = new LinkedBlockingQueue<String>();
        }
        WriteThread[] writeThreads = new WriteThread[num];
        for (int i = 0; i < writeThreads.length; i++) {
            writeThreads[i] = new WriteThread(i);
            writeThreads[i].setFileNameList(fileNameList[i]);
            writeThreads[i].start();
        }
        ReadThread[] readThreads = new ReadThread[num];
        for (int i = 0; i < fileNameList.length; i++) {
            readThreads[i] = new ReadThread(i);
            readThreads[i].setFileNameList(fileNameList[i]);
            readThreads[i].start();
        }
    }
}

从上面的代码可以看到,在主线程里创建了文件名队列数组,读线程数组,写线程数值,这样可以保证写线程1的数据永远被读线程1消费,当然在本案例中这样写线程组没有什么多大的意义,只是为了在模拟数据量多的情况

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值