NIO(JDK1.4)模型是一种同步非阻塞IO,主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(多路复用器)。
阻塞与非阻塞是描述进程在访问某个资源时,数据是否准备就绪的的一种处理方式。当数据没有准备就绪时:
阻塞:线程持续等待资源中数据准备完成,直到返回响应结果。非阻塞:线程直接返回结果,不会持续等待资源准备数据结束后才响应结果。同步与异步是指访问数据的机制
同步一般指主动请求并等待IO操作完成的方式。异步则指主动请求数据后便可以继续处理其它任务,随后等待IO操作完毕的通知。a) IO是面向流的,基于字节流和字符流进行操作.NIO是面向缓冲区的,基于Channel和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。
b) IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。
相对于java io操作中通常采用BufferedReader,BufferedInputStream等带缓冲的IO类处理大文件,java nio中引入了一种基于MappedByteBuffer操作大文件的方式,其读写性能极高
把文件映射到虚拟内存
现在以及知道了MappedByteBuffer作用是把文件映射到虚拟内存,那么为什么需要映射到虚拟内存呢?先来了解下虚拟内存的概念
如果正在运行的一个进程,它所需的内存是有可能大于内存条容量之和的,如内存条是256M,程序却要创建一个2G的数据区,那么所有数据不可能都加载到内存(物理内存),必然有数据要放到其他介质中(比如硬盘),待进程需要访问那部分数据时,再调度进入物理内存。
FileChannel提供了map方法把文件映射到虚拟内存,通常情况可以映射整个文件,如果文件比较大,可以进行分段映射。
下面粘贴一个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消费,当然在本案例中这样写线程组没有什么多大的意义,只是为了在模拟数据量多的情况
