深入浅出MappedByteBuffer多线程 Map映射 快速处理大文件
前言
java io操作中通常采用BufferedReader,BufferedInputStream等带缓冲的IO类处理大文件,不过java nio中引入了一种基于MappedByteBuffer操作大文件的方式,其读写性能极高,本文会介绍其性能如此高的内部实现原理。
内存管理
在深入MappedByteBuffer之前,先看看计算机内存管理的几个术语:
- MMU:CPU的内存管理单元。
- 物理内存:即内存条的内存空间。
- 虚拟内存:计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。
- 页面文件:操作系统反映构建并使用虚拟内存的硬盘空间大小而创建的文件,在windows下,即pagefile.sys文件,其存在意味着物理内存被占满后,将暂时不用的数据移动到硬盘上。
- 缺页中断:当程序试图访问已映射在虚拟地址空间中但未被加载至物理内存的一个分页时,由MMC发出的中断。如果操作系统判断此次访问是有效的,则尝试将相关的页从虚拟内存文件中载入物理内存。
为什么会有虚拟内存和物理内存的区别?
如果正在运行的一个进程,它所需的内存是有可能大于内存条容量之和的,如内存条是256M,程序却要创建一个2G的数据区,那么所有数据不可能都加载到内存(物理内存),必然有数据要放到其他介质中(比如硬盘),待进程需要访问那部分数据时,再调度进入物理内存。
什么是虚拟内存地址和物理内存地址?
假设你的计算机是32位,那么它的地址总线是32位的,也就是它可以寻址00xFFFFFFFF(4G)的地址空间,但如果你的计算机只有256M的物理内存0x0x0FFFFFFF(256M),同时你的进程产生了一个不在这256M地址空间中的地址,那么计算机该如何处理呢?回答这个问题前,先说明计算机的内存分页机制。
计算机会对虚拟内存地址空间(32位为4G)进行分页产生页(page),对物理内存地址空间(假设256M)进行分页产生页帧(page frame),页和页帧的大小一样,所以虚拟内存页的个数势必要大于物理内存页帧的个数。在计算机上有一个页表(page table),就是映射虚拟内存页到物理内存页的,更确切的说是页号到页帧号的映射,而且是一对一的映射。
问题来了,虚拟内存页的个数 > 物理内存页帧的个数,岂不是有些虚拟内存页的地址永远没有对应的物理内存地址空间?不是的,操作系统是这样处理的。操作系统有个页面失效(page fault)功能。操作系统找到一个最少使用的页帧,使之失效,并把它写入磁盘,随后把需要访问的页放到页帧中,并修改页表中的映射,保证了所有的页都会被调度。
现在来看看什么是虚拟内存地址和物理内存地址:
- 虚拟内存地址:由页号(与页表中的页号关联)和偏移量(页的小大,即这个页能存多少数据)组成。
举个例子,有一个虚拟地址它的页号是4,偏移量是20,那么他的寻址过程是这样的:首先到页表中找到页号4对应的页帧号(比如为8),如果页不在内存中,则用失效机制调入页,接着把页帧号和偏移量传给MMC组成一个物理上真正存在的地址,最后就是访问物理内存的数据了。
MappedByteBuffer是什么
从继承结构上看,MappedByteBuffer继承自ByteBuffer,内部维护了一个逻辑地址address。
示例
通过MappedByteBuffer读取文件
public class MappedByteBufferTest {
public static void main(String[] args) {
File file = new File("D://data.txt");
long len = file.length();
byte[] ds = new byte[(int) len];
try {
MappedByteBuffer mappedByteBuffer = new RandomAccessFile(file, "r")
.getChannel()
.map(FileChannel.MapMode.READ_ONLY, 0, len);
for (int offset = 0; offset < len; offset++) {
byte b = mappedByteBuffer.get();
ds[offset] = b;
}
Scanner scan = new Scanner(new ByteArrayInputStream(ds)).useDelimiter(" ");
while (scan.hasNext()) {
System.out.print(scan.next() + " ");
}
} catch (IOException e) {}
}
}
map过程
FileChannel提供了map方法把文件映射到虚拟内存,通常情况可以映射整个文件,如果文件比较大,可以进行分段映射。
-
FileChannel中的几个变量
-
MapMode mode
内存映像文件访问的方式,共三种:
- MapMode.READ_ONLY:只读,试图修改得到的缓冲区将导致抛出异常。
- MapMode.READ_WRITE:读/写,对得到的缓冲区的更改最终将写入文件;但该更改对映射到同一文件的其他程序不一定是可见的。
- MapMode.PRIVATE:私用,可读可写,但是修改的内容不会写入文件,只是buffer自身的改变,这种能力称之为”copy on write”。
-
position:文件映射时的起始位置。
-
allocationGranularity:Memory allocation size for mapping buffers,通过native函数initIDs初始化。
-
接下去通过分析源码,了解一下map过程的内部实现。
- 通过RandomAccessFile获取FileChannel。
public final FileChannel getChannel() {
synchronized (this) {
if (channel == null) {
channel = FileChannelImpl.open(fd, path, true, rw, this);
}
return channel;
}
}
上述实现可以看出,由于synchronized ,只有一个线程能够初始化FileChannel。
- 通过FileChannel.map方法,把文件映射到虚拟内存,并返回逻辑地址address,实现如下
**只保留了核心代码**
public MappedByteBuffer map(MapMode mode, long position, long size) throws IOException {
int pagePosition = (int)(position % allocationGranularity);
long mapPosition = position - pagePosition;
long mapSize = size + pagePosition;
try {
addr = map0(imode, mapPosition, mapSize);
} catch (OutOfMemoryError x) {
System.gc();
try {
Thread.sleep(100);
} catch (InterruptedException y) {
Thread.currentThread().interrupt();
}
try {
addr = map0(imode, mapPosition, mapSize);
} catch (OutOfMemoryError y) {
// After a second OOME, fail
throw new IOException("Map failed", y);
}
}
int isize = (int)size;
Unmapper um = new Unmapper(addr, mapSize, isize, mfd);
if ((!writable) || (imode == MAP_RO)) {
return Util.newMappedByteBufferR(isize,
addr + pagePosition,
mfd,
um);
} else {
return Util.newMappedByteBuffer(isize,
addr + pagePosition,
mfd,
um);
}
}
上述代码可以看出,最终map通过native函数map0完成文件的映射工作。
- 如果第一次文件映射导致OOM,则手动触发垃圾回收,休眠100ms后再次尝试映射,如果失败,则抛出异常。
- 通过newMappedByteBuffer方法初始化MappedByteBuffer实例,不过其最终返回的是DirectByteBuffer的实例,实现如下:
static MappedByteBuffer newMappedByteBuffer(int size, long addr, FileDescriptor fd, Runnable unmapper) {
MappedByteBuffer dbb;
if (directByteBufferConstructor == null)
initDBBConstructor();
dbb = (MappedByteBuffer)directByteBufferConstructor.newInstance(
new Object[] { new Integer(size),
new Long(addr),
fd,
unmapper }
return dbb;
}
// 访问权限
private static void initDBBConstructor() {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
Class<?> cl = Class.forName("java.nio.DirectByteBuffer");
Constructor<?> ctor = cl.getDeclaredConstructor(
new Class<?>[] { int.class,
long.class,
FileDescriptor.class,
Runnable.class });
ctor.setAccessible(true);
directByteBufferConstructor = ctor;
}});
}
由于FileChannelImpl和DirectByteBuffer不在同一个包中,所以有权限访问问题,通过AccessController类获取DirectByteBuffer的构造器进行实例化。
DirectByteBuffer是MappedByteBuffer的一个子类,其实现了对内存的直接操作。
get过程
MappedByteBuffer的get方法最终通过DirectByteBuffer.get方法实现的。
public byte get() {
return ((unsafe.getByte(ix(nextGetIndex()))));
}
public byte get(int i) {
return ((unsafe.getByte(ix(checkIndex(i)))));
}
private long ix(int i) {
return address + (i << 0);
}
map0()函数返回一个地址address,这样就无需调用read或write方法对文件进行读写,通过address就能够操作文件。底层采用unsafe.getByte方法,通过(address + 偏移量)获取指定内存的数据。
- 第一次访问address所指向的内存区域,导致缺页中断,中断响应函数会在交换区中查找相对应的页面,如果找不到(也就是该文件从来没有被读入内存的情况),则从硬盘上将文件指定页读取到物理内存中(非jvm堆内存)。
- 如果在拷贝数据时,发现物理内存不够用,则会通过虚拟内存机制(swap)将暂时不用的物理页面交换到硬盘的虚拟内存中。
性能分析
从代码层面上看,从硬盘上将文件读入内存,都要经过文件系统进行数据拷贝,并且数据拷贝操作是由文件系统和硬件驱动实现的,理论上来说,拷贝数据的效率是一样的。
但是通过内存映射的方法访问硬盘上的文件,效率要比read和write系统调用高,这是为什么?
- read()是系统调用,首先将文件从硬盘拷贝到内核空间的一个缓冲区,再将这些数据拷贝到用户空间,实际上进行了两次数据拷贝;
- map()也是系统调用,但没有进行数据拷贝,当缺页中断发生时,直接将文件从硬盘拷贝到用户空间,只进行了一次数据拷贝。
所以,采用内存映射的读写效率要比传统的read/write性能高。
总结
- MappedByteBuffer使用虚拟内存,因此分配(map)的内存大小不受JVM的-Xmx参数限制,但是也是有大小限制的。
- 如果当文件超出1.5G限制时,可以通过position参数重新map文件后面的内容。
- MappedByteBuffer在处理大文件时的确性能很高,但也存在一些问题,如内存占用、文件关闭不确定,被其打开的文件只有在垃圾回收的才会被关闭,而且这个时间点是不确定的。
javadoc中也提到:*A mapped byte buffer and the file mapping that it represents remain valid until the buffer itself is garbage-collected.**
多线程Map映射处理超大文件示例
public class CSV2Iotdb_v3 {
public AtomicInteger index = new AtomicInteger(0);
public long size;
public int buffer_size;
RandomAccessFile orign_raf;
FileChannel orignFileChannel;
FileChannel targetFileChannel;
public CSV2Iotdb_v3(File orign, int buffer_size) throws FileNotFoundException {
RandomAccessFile orign_raf = new RandomAccessFile(orign, "rw");
this.orignFileChannel = orign_raf.getChannel();
this.buffer_size = buffer_size;
this.size = orign.length();
System.out.println("构造完毕");
}
class ReadTask implements Runnable {
@Override
public void run() {
// 这个任务中需要使用cas获取到当前的 index,并且读取index+buffer值,然后将index改为
int cur_index;
System.out.println("执行");
while ((cur_index = index.get()) < size) {
int target_index = (cur_index + buffer_size) > size ? (int) size : cur_index + buffer_size;
if (index.compareAndSet(cur_index, target_index + 1)) {
//如果cas 成功就进行读写操作
System.out.println(Thread.currentThread().getName()+"读取了cur_index"+cur_index+"到"+target_index+"的缓冲数据");
byte[] content = readFile(cur_index, target_index);
// 读取到了内容可以在下面进行一些别的处理操作
// 将得到的3M内容给Scanner,这里的XXX是指Scanner解析的分隔符
Scanner scan = new Scanner(new ByteArrayInputStream(content)).useDelimiter("\\R");
while (scan.hasNext()) {
// 这里为对读取文本解析的方法
System.out.println(Thread.currentThread().getName() + "===>" + scan.next() + " ");
}
scan.close();
}
}
}
public byte[] readFile(int orign_index, int target_index) {
// 读取文件,使用一个map内存映射进行读取,可以加速读取吧
MappedByteBuffer map;
byte[] byteArr = new byte[target_index - orign_index];
try {
map = orignFileChannel.map(FileChannel.MapMode.READ_ONLY, orign_index, target_index - orign_index);
map.get(byteArr, 0, target_index - orign_index);
} catch (Exception e) {
System.out.println("读取" + orign_index + "到" + target_index + "失败");
e.printStackTrace();
}
return byteArr;
}
}
class WriteTask implements Runnable {
@Override
public void run() {
byte[] a = new byte[1024];
int cur_index;
System.out.println("执行");
while ((cur_index = index.get()) < size) {
int target_index = (cur_index + buffer_size) > size ? (int) size : cur_index + buffer_size;
if (index.compareAndSet(cur_index, target_index + 1)) {
//如果cas 成功就进行读写操作
//成功
System.out.println(Thread.currentThread().getName()+"写了cur_index"+cur_index+"到"+target_index+"的缓冲数据");
writeFile(cur_index, target_index, a);
// 读取
}
}
}
public void writeFile(int orign_index, int target_index, byte[] content) {
//然后进行写
// 读取文件,使用一个map内存映射进行读取,可以加速读取吧
MappedByteBuffer map;
byte[] byteArr = new byte[target_index - orign_index];
try {
map = targetFileChannel.map(FileChannel.MapMode.READ_ONLY, orign_index, target_index - orign_index);
map.position();
map.put(content);
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws FileNotFoundException {
File orign = new File("C:\\Users\\Administrator\\Downloads\\test.csv");
CSV2Iotdb_v3 pf = new CSV2Iotdb_v3(orign, 0x300000);
ThreadPoolExecutor poll = new ThreadPoolExecutor(4, 5, 1,
TimeUnit.HOURS, new LinkedBlockingDeque<>(10));
for (int i = 0; i < 8; i++) {
poll.execute(pf.new ReadTask());
}
}
}
上面的操作是,每个线程都操作一块一块的buffer_size缓冲区的数据,但是缓冲区太小,读取的就不是一行数据了,而是不到一行,这里设置的3M,经过scanner处理之后,得到的就是文件中的每一行数据了。
后来发现上面这句话是错的!设置的3M,处理到最后肯定有一行不是一整行,而是半行,或许前半行,也或许后半行,所以,如果多线程处理一个大文件的时候正确的处理方式如下:
- 通过scanner.next()得到下一行,判断行特征得到是否是前半行还是后半行,存到treeMap,key是按照字节位置排序的,value是半行的字符串
- 每个文件的key索引值会重,把结果打到一个treeMap中的话会乱序,多文件穿插的原因,解决办法是给key乘以一个数,把每个文件的索引key区分开,这样多线程读取的结果就是顺序的前半行,后半行,前半行,后半行。。。。
- 然后再由主线程把这些拼接成完整行进行处理即可
完整代码
import org.apache.commons.lang.StringUtils;
import org.apache.iotdb.session.pool.SessionPool;
import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
import org.apache.iotdb.tsfile.write.record.Tablet;
import org.apache.iotdb.tsfile.write.schema.MeasurementSchema;
import java.io.*;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
public class CSV2Iotdb_MultiThread {
public static final String deviceId = "root.ylyx.all";
public AtomicLong index = new AtomicLong(0);
public long size;
public int buffer_size;
public RandomAccessFile orign_raf;
public FileChannel orignFileChannel;
public static volatile TreeMap<Long, String> treeMap = new TreeMap<>(new Comparator<Long>() {
@Override
public int compare(Long o1, Long o2) {
return o1 > o2 ? 1 : -1;
}
});
public CSV2Iotdb_MultiThread(File orign, int buffer_size) throws FileNotFoundException {
RandomAccessFile orign_raf = new RandomAccessFile(orign, "rw");
this.orignFileChannel = orign_raf.getChannel();
this.buffer_size = buffer_size;
this.size = orign.length();
}
public static void main(String[] args) throws Exception {
if (args.length < 3) {
System.out.println("use command as:");
System.out.println("java -jar CSV2Redis-1.0-SNAPSHOT.jar /dir/csv2iotdb.properties /dir/csvDir /dir/log.txt thread_num(IntegerValue) buffersize(M)");
return;
}
int num = 1;
if (args.length > 3) {
try {
num = Integer.parseInt(args[3]);
} catch (NumberFormatException e) {
System.out.println("ERROR! thread nums should be Integer Value !");
}
System.out.println("workThread nums set " + num + "");
} else {
System.out.println("workThread nums non-essential default 1");
}
int bufferSize = 5;
if (args.length > 4) {
try {
bufferSize = Integer.parseInt(args[4]);
} catch (NumberFormatException e) {
System.out.println("ERROR! bufferSize should be Integer Value !");
}
System.out.println("bufferSize set " + bufferSize + "MB");
} else {
System.out.println("bufferSize non-essential default 5MB");
}
Properties propFromFile = new Properties();
InputStream inputStream = new FileInputStream(new File(args[0]));
propFromFile.load(inputStream);
String userName = propFromFile.get("iotdb.usrname").toString();
String password = propFromFile.get("iotdb.password").toString();
String ip = propFromFile.get("iotdb.ip").toString();
String port = propFromFile.get("iotdb.port").toString();
String maxsize = propFromFile.get("iotdb.maxsize").toString();
System.out.println("establish connection to IotDB...");
System.out.println("userName:" + userName);
System.out.println("password:" + password);
System.out.println("ip:" + ip);
System.out.println("port:" + port);
System.out.println("maxsize:" + maxsize);
SessionPool sessionPool = new SessionPool(ip, Integer.parseInt(port), userName, password, Integer.parseInt(maxsize));
System.out.println("establish connection to IotDB succeed!");
File dir = new File(args[1]);
if (dir == null) {
System.out.println("dir is not exists !!!");
return;
}
File[] files = dir.listFiles();
if (files.length == 0) {
System.out.println("dir has no file !!!");
return;
}
File logFile = new File(args[2]);
System.out.println("begin..." + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
Long file_index = new Long(0);
for (File orign : dir.listFiles()) {
CountDownLatch countDownLatch = new CountDownLatch(num);
System.out.println("读取到了" + orign.getName() + ",当前时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
CSV2Iotdb_MultiThread pf = new CSV2Iotdb_MultiThread(orign, bufferSize * 1024 * 1024);
for (int i = 0; i < num; i++) {
new Thread(pf.new ReadTask(countDownLatch, sessionPool, logFile, file_index)).start();
}
file_index++;
countDownLatch.await();
}
if (treeMap != null && treeMap.size() > 0) {
System.out.println("后期处理失败的行begin..." + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
String tempStr = StringUtils.EMPTY;
int temp = 0;
//创建空容器
List<Map<String, Map<Long, Double>>> containerList = new ArrayList<>();
for (Map.Entry test : treeMap.entrySet()) {
if (temp == 0) {
tempStr += test.getValue();
temp += 1;
} else {
tempStr += test.getValue();
String[] row = tempStr.trim().split(",");
if ("ts".equals(row[1].trim().replace("\"", ""))) return;
String tag_no = row[2].replaceAll("\"", "");
Long timestamp = Long.valueOf(row[5]);
Double tag_value = Double.parseDouble(row[6]);
List<String> tagNoList = containerList.stream().map(s -> s.keySet().iterator().next()).collect(Collectors.toList());
if (tagNoList.contains(tag_no)) {
for (Map<String, Map<Long, Double>> tempContainer : containerList) {
String next = tempContainer.keySet().iterator().next();
if(!StringUtils.equals(next,tag_no))continue;
Map<Long, Double> value = tempContainer.get(tag_no);
if (value != null && !value.isEmpty()) {
value.put(timestamp, tag_value);
tempContainer.put(tag_no, value);
} else {
Map<String, Map<Long, Double>> container = new HashMap<>();
Map<Long, Double> longDoubleMap = new HashMap<>();
longDoubleMap.put(timestamp, tag_value);
container.put(tag_no, longDoubleMap);
containerList.add(container);
}
}
} else {
Map<String, Map<Long, Double>> container = new HashMap<>();
Map<Long, Double> longDoubleMap = new HashMap<>();
longDoubleMap.put(timestamp, tag_value);
container.put(tag_no, longDoubleMap);
containerList.add(container);
}
tempStr = StringUtils.EMPTY;
temp = 0;
}
}
//插入数据,清空容器
for (Map<String, Map<Long, Double>> tempContainer : containerList) {
try {
for (Map.Entry<String, Map<Long, Double>> entry : tempContainer.entrySet()) {
String key = entry.getKey();
Map<Long, Double> longDoubleMap = entry.getValue();
List<MeasurementSchema> schemaList = new ArrayList<>();
schemaList.add(new MeasurementSchema(key.toString(), TSDataType.DOUBLE));
Tablet tablet = new Tablet(deviceId, schemaList, longDoubleMap.size());
if (key != null && longDoubleMap != null) {
for (Map.Entry<Long, Double> mapEntry : longDoubleMap.entrySet()) {
int rowIndex = tablet.rowSize++;
tablet.addTimestamp(rowIndex, mapEntry.getKey());
tablet.addValue(key.toString(), rowIndex, mapEntry.getValue());
try {
if (tablet.rowSize == tablet.getMaxRowNumber()) {
sessionPool.insertTablet(tablet);
tablet.reset();
log(logFile, "插入了" + longDoubleMap.size() + "行数据! " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}
} catch (Exception e) {
log(logFile, "批量插入iotdb操作失败!");
e.printStackTrace();
}
}
}
}
} catch (Exception e) {
log(logFile, "数据处理方法中发生错误!");
e.printStackTrace();
}
}
containerList.clear();
System.out.println("后期处理失败的行end!" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}
System.out.println("end!" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}
private static void log(File file, String content) {
try (FileOutputStream fop = new FileOutputStream(file, true)) {
if (!file.exists()) {
file.createNewFile();
}
byte[] contentInBytes = content.getBytes();
fop.write(contentInBytes);
fop.write("\n".getBytes());
fop.flush();
fop.close();
} catch (IOException e) {
e.printStackTrace();
}
}
class ReadTask implements Runnable {
private CountDownLatch countDownLatch;
private SessionPool sessionPool;
private File logFile;
private Long i;
public ReadTask(CountDownLatch countDownLatch, SessionPool sessionPool, File logFile, Long i) {
this.countDownLatch = countDownLatch;
this.sessionPool = sessionPool;
this.logFile = logFile;
this.i = i;
}
@Override
public void run() {
// 这个任务中需要使用cas获取到当前的 index,并且读取index+buffer值,然后将index改为
long cur_index;
while ((cur_index = index.get()) < size) {
long target_index = (cur_index + buffer_size) > size ? size : cur_index + buffer_size;
long D_value = target_index - cur_index;
if (index.compareAndSet(cur_index, target_index)) {
//创建空容器
List<Map<String, Map<Long, Double>>> containerList = new ArrayList<>();
//读数据,添加到容器
long currentIndex = cur_index;
byte[] content = readFile(cur_index, target_index, D_value);
Scanner scan = new Scanner(new ByteArrayInputStream(content)).useDelimiter("\\R");
while (scan.hasNext()) {
// 这里为对读取文本解析的方法
String next = scan.next();
if (next.indexOf("ts") != -1) continue;
currentIndex++;
//前一半
if (next.indexOf("INST") == -1) {
synchronized (treeMap) {
treeMap.put(i * 300 * 1024 * 1024 * 1024 * 1024 + currentIndex, next);
}
continue;
}
//后一半
if (next.getBytes()[0] != '\"' || next.getBytes()[5] != '-' || next.getBytes()[8] != '-' || next.getBytes()[11] != '\"') {
synchronized (treeMap) {
treeMap.put(i * 300 * 1024 * 1024 * 1024 * 1024 + currentIndex, next);
}
continue;
}
String[] row = next.trim().split(",");
if ("ts".equals(row[1].trim().replace("\"", ""))) return;
String tag_no = row[2].replaceAll("\"", "");
Long timestamp = Long.valueOf(row[5]);
Double tag_value = Double.parseDouble(row[6]);
List<String> tagNoList = containerList.stream().map(s -> s.keySet().iterator().next()).collect(Collectors.toList());
if (tagNoList.contains(tag_no)) {
for (Map<String, Map<Long, Double>> tempContainer : containerList) {
String next1 = tempContainer.keySet().iterator().next();
if(!StringUtils.equals(next1,tag_no))continue;
Map<Long, Double> value = tempContainer.get(tag_no);
if (value != null && !value.isEmpty()) {
value.put(timestamp, tag_value);
tempContainer.put(tag_no, value);
} else {
Map<String, Map<Long, Double>> container = new HashMap<>();
Map<Long, Double> longDoubleMap = new HashMap<>();
longDoubleMap.put(timestamp, tag_value);
container.put(tag_no, longDoubleMap);
containerList.add(container);
}
}
} else {
Map<String, Map<Long, Double>> container = new HashMap<>();
Map<Long, Double> longDoubleMap = new HashMap<>();
longDoubleMap.put(timestamp, tag_value);
container.put(tag_no, longDoubleMap);
containerList.add(container);
}
}
scan.close();
//插入数据,清空容器
for (Map<String, Map<Long, Double>> tempContainer : containerList) {
try {
for (Map.Entry<String, Map<Long, Double>> entry : tempContainer.entrySet()) {
String key = entry.getKey();
Map<Long, Double> longDoubleMap = entry.getValue();
List<MeasurementSchema> schemaList = new ArrayList<>();
schemaList.add(new MeasurementSchema(key.toString(), TSDataType.DOUBLE));
Tablet tablet = new Tablet(deviceId, schemaList, longDoubleMap.size());
if (key != null && longDoubleMap != null) {
for (Map.Entry<Long, Double> mapEntry : longDoubleMap.entrySet()) {
int rowIndex = tablet.rowSize++;
tablet.addTimestamp(rowIndex, mapEntry.getKey());
tablet.addValue(key.toString(), rowIndex, mapEntry.getValue());
try {
if (tablet.rowSize == tablet.getMaxRowNumber()) {
sessionPool.insertTablet(tablet);
tablet.reset();
log(logFile, "插入了" + longDoubleMap.size() + "行数据! " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}
} catch (Exception e) {
log(logFile, "批量插入iotdb操作失败!");
e.printStackTrace();
}
}
}
}
} catch (Exception e) {
log(logFile, "数据处理方法中发生错误!");
e.printStackTrace();
}
}
}
}
countDownLatch.countDown();
}
public byte[] readFile(long orign_index, long target_index, Long D_value) {
// 读取文件,使用一个map内存映射进行读取,可以加速读取吧
MappedByteBuffer map;
byte[] byteArr = new byte[D_value.intValue()];
try {
map = orignFileChannel.map(FileChannel.MapMode.READ_ONLY, orign_index, D_value);
map.get(byteArr, 0, D_value.intValue());
} catch (Exception e) {
e.printStackTrace();
}
return byteArr;
}
}
}
文章转载参考自:
https://blog.csdn.net/qq_41969879/article/details/81629469
https://my.oschina.net/u/1995545/blog/380656
https://blog.csdn.net/m0_57640408/article/details/120919806
-------------------------------------------
个性签名:独学而无友,则孤陋而寡闻。做一个灵魂有趣的人!
如果觉得这篇文章对你有小小的帮助的话,记得在右下角点个“推荐”哦,博主在此感谢!
万水千山总是情,打赏一分行不行,所以如果你心情还比较高兴,也是可以扫码打赏博主,哈哈哈(っ•̀ω•́)っ✎⁾⁾!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)
2021-07-16 项目-无侵入代码方式使用Redis实现缓存功能