import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
import java.lang.reflect.Field;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Stream;
import com.alibaba.fastjson.JSON;
import com.sun.istack.internal.NotNull;
public class NioFileOperateUtils {
/**
* 获取最大堆内存,单位byte
* @return
*/
public long getMaxJvmSpace() {
MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
MemoryUsage heapMemory= memoryMXBean.getHeapMemoryUsage();
long maxHeap = heapMemory.getMax();
return maxHeap;
}
/**
* 获取最大堆外内存(单位byte),没有设置过默认和堆内大小一样
* @return
*/
public long getMaxDirectMemory(){
try {
//不能直接获取类对象,只能通过反射
Class<?> c = Class.forName("java.nio.Bits");
Field maxMemory = c.getDeclaredField("maxMemory");
maxMemory.setAccessible(true);
Field reservedMemory = c.getDeclaredField("reservedMemory");
reservedMemory.setAccessible(true);
//最大堆外内存
Long maxMemoryValue = (Long)maxMemory.get(null);
//已分配的堆外内存大小
// Object reservedMemoryValue = reservedMemory.get(null);
// System.out.println(reservedMemoryValue);
return maxMemoryValue;
}catch(Exception e) {
e.printStackTrace();
}
return 0;
}
/**
* java8 nio文件流读取(基于io的Reader转BufferedReader封装实现),依赖堆内存
* @param filePath
* @return
*/
public List<String> readTextFile(String filePath) {
Stream<String> stream=null;
try {
//默认utf-8编码
stream=Files.lines(Paths.get(filePath));
List<String> result=new ArrayList<>();
stream.forEach(str->result.add(str));
//stream.forEach(result::add);等同于上面
return result;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
if(null!=stream) {
stream.close();
}
}
return null;
}
/**
* 通过MappedByteBuffer映射分段读取文件数据
* @param filePath 文件地址
* @param threadNums 分段数量
* @return
*/
public ConcurrentLinkedDeque<String> readBigTextFile(String filePath,int threadNums){
File file =new File(filePath);
if(!file.exists()) {
return null;
}
//大文件转字节码存储容器
RandomAccessFile randomAccessFile=null;
//文件通道
FileChannel fileChannel=null;
//线程池
ExecutorService executor=null;
//线程安全队列
ConcurrentLinkedDeque<String> contents=new ConcurrentLinkedDeque<String>();
try {
randomAccessFile=new RandomAccessFile(file, "rw");
fileChannel=randomAccessFile.getChannel();
//不分页处理
if(threadNums<2) {
getChanelFileContent(contents, fileChannel, 0, file.length(),file.length());
return contents;
}
//分页采用多线程处理
executor=Executors.newFixedThreadPool(threadNums);
//发令枪,并行执行
CountDownLatch latch=new CountDownLatch(threadNums);
// 每线程应该读取的字节数
long numPerThread = file.length() / threadNums;
// 整个文件整除后剩下的余数
long left = file.length() % threadNums;
for(int i=0;i<threadNums;i++) {
if (i == threadNums - 1 ) {
executor.execute(new ReadRunable(fileChannel, i*numPerThread, numPerThread+left,file.length(), contents,latch));
}else {
executor.execute(new ReadRunable(fileChannel, i*numPerThread, numPerThread,file.length(), contents,latch));
}
//发令枪减数量,直到减为0执行时,所有阻塞的线程同时并行执行
latch.countDown();
}
//需要线程等待会,不然FileLock文件锁可能还没释放会报错
Thread.sleep(500);
}catch(Exception e) {
e.printStackTrace();
}finally {
if(null!=executor) {
executor.shutdown();
}
if(null!=fileChannel) {
try {
fileChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(null!=randomAccessFile) {
try {
randomAccessFile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return contents;
}
class ReadRunable implements Runnable {
private FileChannel fileChannel;//文件通道
private long position;//字节码起始位置
private long size;//预定义读取字节码的个数
private long fileSize;//文件总字节码大小
private CountDownLatch latch;//线程并行执行发令枪
private ConcurrentLinkedDeque<String> contents;
public ReadRunable(FileChannel fileChannel, long position, long size,long fileSize, ConcurrentLinkedDeque<String> contents,CountDownLatch latch) {
super();
this.fileChannel = fileChannel;
this.position = position;
this.size = size;
this.contents = contents;
this.fileSize=fileSize;
this.latch=latch;
}
@Override
public void run() {
try {
latch.await();//当前线程等待发令枪唤醒
} catch (InterruptedException e) {
e.printStackTrace();
}
getChanelFileContent(contents, fileChannel, position, size,fileSize);
}
}
/**
*
* @param contents 接收文件每行的数据
* @param file 文件
* @param position 起始字符位置
* @param size 读取的字符长度范围
*/
private void getChanelFileContent(@NotNull ConcurrentLinkedDeque<String> contents,@NotNull FileChannel fileChannel,long position,long size,long fileSize){
FileLock lock=null;
boolean isLast= position+size >= fileSize;
//真的读取的数据,字符是\n后的下一字节位置开始读
long realPosition=position;
//真的读取数据的大小,在遇到\r\n后的字节位置
long realSize=size;
//开始位置和结束位置是否为换行符号
boolean newLine=false;
try {
MappedByteBuffer mapbuffer=null;
byte line="\n".getBytes()[0];
byte getLine;
//到下一个\n字节还有几个长度
int addNum=0;
if(position > 0) {
addNum=0;
newLine=false;
//开始字节的前一位是否是换行符
mapbuffer=fileChannel.map(FileChannel.MapMode.READ_ONLY, position-1, 1);
getLine=mapbuffer.get();
if(line==getLine) {
newLine=true;
mapbuffer.clear();
}
while(!newLine) {
mapbuffer=fileChannel.map(FileChannel.MapMode.READ_ONLY, position+addNum, 1);
getLine=mapbuffer.get();
if(line==getLine) {
mapbuffer.clear();
break;
}else if("\r".getBytes()[0]==getLine) {
addNum=addNum+1;
mapbuffer.clear();
break;
}
addNum++;
}
//开始字节码位置在\n后+1
realPosition=realPosition+addNum+1;
}
//是否为换行符号
newLine=false;
//从新赋值0
addNum=0;
while(!isLast && !newLine) {
if(null!=mapbuffer) {
mapbuffer.clear();
}
//结尾字节码判断起始位置,不是最后一页采用上面计算后真实的开始位置+原始长度;
mapbuffer=fileChannel.map(FileChannel.MapMode.READ_ONLY, realPosition+size+addNum, 1);
getLine=mapbuffer.get();
if(line==getLine) {
mapbuffer.clear();
break;
}else if("\r".getBytes()[0]==getLine) {
addNum=addNum+1;
mapbuffer.clear();
break;
}
addNum++;
}
//结束字节码位置在\n
realSize=isLast ? fileSize-realPosition : realSize+addNum;
//锁定文件位置范围
lock=fileChannel.lock(realPosition, realSize, false);
mapbuffer=fileChannel.map(FileChannel.MapMode.READ_ONLY, realPosition, realSize);
String strcontens = Charset.forName("UTF-8").decode(mapbuffer).toString();
//根据换行\r\n符号获取每行的数据
StringTokenizer token = new StringTokenizer(strcontens,"\r\n");
while(token.hasMoreTokens()) {
contents.add(token.nextToken());
}
mapbuffer.clear();
}catch(Exception e) {
e.printStackTrace();
}finally {
if(null!=lock) {
try {
lock.release();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
//ByteBuffer.allocateDirect(10 * 1024 * 1024);
NioFileOperateUtils test=new NioFileOperateUtils();
// System.out.println("堆内存:"+(test.getMaxJvmSpace()/(1024*1024*1024)));
// System.out.println("堆外内存:"+test.getMaxDirectMemory());
// System.out.println("start###"+System.currentTimeMillis());
// List<String> strList=test.readTextFile("F:/test.txt");
// System.out.println(JSON.toJSONString(strList));
// System.out.println("end###"+System.currentTimeMillis());
System.out.println("start###"+System.currentTimeMillis());
ConcurrentLinkedDeque<String> list=test.readBigTextFile("F:/test.txt",8);
System.out.println(JSON.toJSONString(list));
System.out.println("end###"+System.currentTimeMillis());
}
}