一、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:内存映像文件访问的方式,共三种:
- MapMode.READ_ONLY:只读,试图修改得到的缓冲区将导致抛出异常。
- MapMode.READ_WRITE:读/写,对得到的缓冲区的更改最终将写入文件;但该更改对映射到同一文件的其他程序不一定是可见的。
- 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消费,当然在本案例中这样写线程组没有什么多大的意义,只是为了在模拟数据量多的情况