遇到海量数据时,用JAVA如何在几分钟处理完30亿个数据?

本文介绍了如何在资源有限的情况下(4G内存,2核CPU),使用Java处理10G的大文件数据。通过单线程读取与统计,以及优化后的多线程分治法,利用生产者消费者模型和阻塞队列,将处理时间从3分钟降低到约103秒,显著提高了CPU利用率。

一、场景说明

现有一个10G文件的数据,里面包含了18-70之间的整数,分别表示18-70岁的人群数量统计,假设年龄范围分布均匀,分别表示系统中所有用户的年龄数,找出重复次数最多的那个数,现有一台内存为4G、2核CPU的电脑,请写一个算法实现。

23,31,42,19,60,30,36,........

二、模拟数据

Java中一个整数占4个字节,模拟10G为30亿左右个数据, 采用追加模式写入10G数据到硬盘里。

每100万个记录写一行,大概4M一行,10G大概2500行数据。

package bigdata;

import java.io.*;
import java.util.Random;

/**
 * @Desc:
 * @Author: bingbing
 * @Date: 2022/5/4 0004 19:05
 */
public class GenerateData {
    private static Random random = new Random();


    public static int generateRandomData(int start, int end) {
        return random.nextInt(end - start + 1) + start;
    }


    /**
     * 产生10G的 1-1000的数据在D盘
     */
    public void generateData() throws IOException {
        File file = new File("D:\\ User.dat");
        if (!file.exists()) {
            try {
                file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        int start = 18;
        int end = 70;
        long startTime = System.currentTimeMillis();
        BufferedWriter bos = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file, true)));
        for (long i = 1; i < Integer.MAX_VALUE * 1.7; i++) {
            String data = generateRandomData(start, end) + ",";
            bos.write(data);
            // 每100万条记录成一行,100万条数据大概4M
            if (i % 1000000 == 0) {
                bos.write("\n");
            }
        }
        System.out.println("写入完成! 共花费时间:" + (System.currentTimeMillis() - startTime) / 1000 + " s");
        bos.close();
    }


    public static void main(String[] args) {
        GenerateData generateData = new GenerateData();
        try {
            generateData.generateData();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

上述代码调整参数执行2次, 凑10个G的数据在D盘的User.dat文件里: 

准备好10G数据后,接着写如何处理这些数据

三、场景分析

10G的数据比当前拥有的运行内存大的多,不能全量加载到内存中读取,如果采用全量加载,那么内存会直接爆掉,只能按行读取,Java中的bufferedReader的readLine()按行读取文件里的内容。

四、读取数据

首先我们写一个方法单线程读完这30E数据需要多少时间,每读100行打印一次:

 private static void readData() throws IOException {

        BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(FILE_NAME), "utf-8"));
        String line;
        long start = System.currentTimeMillis();
        int count = 1;
        while ((line = br.readLine()) != null) {
            // 按行读取
//            SplitData.splitLine(line);
            if (count % 100 == 0) {
                System.out.println("读取100行,总耗时间: " + (System.currentTimeMillis() - start) / 1000 + " s");
                System.gc();
            }
            count++;
        }
        running = false;
        br.close();

    }

按行读完10G的数据大概20秒,基本每100行,1E多数据花1S,速度还挺快:

五、处理数据

思路一

通过单线程处理,初始化一个countMap, key为年龄,value为出现的次数, 将每行读取到的数据按照"," 进行分割,然后获取到的每一项进行保存到countMap里,如果存在,那么值key的value+1。

 for (int i = start; i <= end; i++) {
            try {
                File subFile = new File(dir + "\\" + i + ".dat");
                if (!file.exists()) {
                    subFile.createNewFile();
                }
                countMap.computeIfAbsent(i + "", integer -> new AtomicInteger(0));
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

单线程读取并统计countMap: 

  public static void splitLine(String lineData) {
            String[] arr = lineData.split(",");
            for (String str : arr) {
                if (StringUtils.isEmpty(str)) {
                    continue;
                }
                countMap.computeIfAbsent(str, s -> new AtomicInteger(0)).getAndIncrement();
            }
        }

 通过比较找出年龄数最多的年龄并打印出来:

private static void findMostAge() {
        Integer targetValue = 0;
        String targetKey = null;
        Iterator<Map.Entry<String, AtomicInteger>> entrySetIterator = countMap.entrySet().iterator();
        while (entrySetIterator.hasNext()) {
            Map.Entry<String, AtomicInteger> entry = entrySetIterator.next();
            Integer value = entry.getValue().get();
            String key = entry.getKey();
            if (value > targetValue) {
                targetValue = value;
                targetKey = key;
            }
        }
        System.out.println("数量最多的年龄为:" + targetKey + "数量为:" + targetValue);
    }

 完整代码

package bigdata;

import org.apache.commons.lang3.StringUtils;

import java.io.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;


/**
 * @Desc:
 * @Author: bingbing
 * @Date: 2022/5/4 0004 19:19
 * 单线程处理
 */
public class HandleMaxRepeatProblem_v0 {

    public static final int start = 18;
    public static final int end = 70;

    public static final String dir = "D:\\dataDir";

    public static final String FILE_NAME = "D:\\ User.dat";


    /**
     * 统计数量
     */
    private static Map<String, AtomicInteger> countMap = new ConcurrentHashMap<>();


    /**
     * 开启消费的标志
     */
    private static volatile boolean startConsumer = false;

    /**
     * 消费者运行保证
     */
    private stat
<think>嗯,用户让我介绍一下数据库IOTDB在Java数据采集项目中的作用和应用。首先,我需要理解用户的需求。他们可能在做物联网相关的项目,需要处理间序列数据,比如传感器数据。用户可能想知道IOTDB如何集成到Java项目中,具体能解决什么问题,或者有什么优势。 首先,我应该解释什么是IOTDB。它是Apache的开源数据库,专为物联网设计,处理高并发写入和查询。然后,作用方面,可能需要强调高效存储、查询、处理海量间序列数据,支持边缘计算和与大数据生态整合。 接下来是应用场景。比如工业物联网中的设备监控,智能家居数据存储,车联网的实分析。需要具体说明在这些场景中,IOTDB如何被Java应用使用,可能涉及数据采集、存储、分析的流程。 然后,可能需要给出Java中的使用示例,比如如何连接IOTDB,写入数据,查询数据。这样用户能更直观地理解。同,要提到IOTDB的优势,比如高性能、低存储成本、易扩展,以及支持SQL-like语法,方便集成。 还要考虑用户可能的疑问,比如为什么选择IOTDB而不是其他数据库,比如InfluxDB或TimescaleDB。可能需要比较一下IOTDB在物联网场景中的优化,比如针对间序列的高效压缩算法,或者与Hadoop、Spark的集成能力。 另外,用户可能关心实际项目中遇到的挑战,比如数据写入速度、查询效率、集群部署等。需要说明IOTDB在这些方面的表现,比如水平扩展,高可用性,处理亿级数据点的能力。 可能还需要提到IOTDB的架构,比如支持边缘-中心协同部署,适合分布式物联网环境。这在数据采集项目中可能很重要,尤其是边缘设备资源有限的情况下。 最后,总结一下IOTDB在Java项目中的价值,如何帮助用户高效管理数据,提升系统性能,降低存储成本。可能还要提醒一些最佳实践,比如数据分片策略,索引优化,或者结合流处理框架如Flink进行实分析。 要确保回答结构清晰,先介绍概念,再讲作用,然后应用场景,接着示例代码,最后优势总结。这样用户能逐步了解IOTDB的应用方式。同语言要简洁,避免技术术语过多,但关键点要准确。</think>Apache IoTDB 是一款专为物联网场景设计的开源数据库,它在 Java 数据采集项目中扮演着**海量数据高效存储与实分析**的核心角色。以下从作用、应用场景和实战示例三个维度为您解析: --- ### 一、IoTDB 在数据采集项目中的作用 1. **高速写入** - 支持每秒百万级数据点写入,满足传感器高频采集需求 - 特有的列式存储+间编码压缩技术(如Gorilla、RLE),降低存储成本达 90% 2. **序优化查询** - 原生间分区索引,毫秒级响应间范围查询 - 支持降采样、滑动窗口等序特有计算(`GROUP BY TIME`) 3. **边缘-云协同** - 轻量级架构(核心仅 10MB+JAR)适合部署在边缘设备 - 提供同步工具实现边缘数据向云端汇聚 4. **生态集成** - 无缝对接 Flink/Spark 实计算,支持 JDBC、MQTT 协议接入 - 兼容 Grafana 可视化,与工业协议(OPC-UA、Modbus)直连 --- ### 二、典型应用场景 #### 1. 工业设备监控 ```java // Java 示例:通过 SDK 写入传感器数据 Session session = new Session("127.0.0.1", 6667); session.open(); String device = "root.factory.machineA"; List<String> sensors = Arrays.asList("temperature", "vibration"); long timestamp = System.currentTimeMillis(); session.insertRecord(device, timestamp, sensors, Arrays.asList(38.5f, 12.7f)); // 写入温度、振动值 ``` #### 2. 智能电网分析 ```sql -- 查询变压器过去1小每5分钟的平均温度 SELECT AVG(temperature) FROM root.grid.transformers.* WHERE time > NOW() - 1h GROUP BY ([NOW() - 1h, NOW()), 5m) ``` #### 3. 车联网实预警 ```java // 结合规则引擎进行超速检测 ResultSet result = session.executeQueryStatement( "SELECT speed FROM root.car.* WHERE speed > 120 AND time > 2024-04-01"); while (result.hasNext()) { RowRecord record = result.next(); triggerAlert(record.getDeviceId(), record.getSpeed()); } ``` --- ### 三、架构优势对比 | 特性 | IoTDB | InfluxDB | TimescaleDB | |--------------------|-------------|-------------|-------------| | 原生压缩率 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ | | 边缘部署能力 | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐ | | Hadoop生态兼容性 | 直接集成HDFS| 需额外工具 | 有限支持 | | 工业协议支持 | 内置OPC-UA插件| 无原生支持 | 无 | --- ### 四、最佳实践建议 1. **数据建模** - 使用层级结构组织设备(`root.<区域>.<设备组>.<设备ID>`) - 对高频设备启用间分片(`CREATE TIMESERIES root.device.* WITH TTL=30d`) 2. **性能调优** - 批量写入启用`insertTablet`接口提升吞吐 - 调整`wal_buffer_size`防止频繁刷盘 3. **集群部署** - 采用 DataNode+ConfigNode 分离架构实现水平扩展 - 结合 Kubernetes 实现自动扩缩容 --- 通过 IoTDB 的数据专精设计,开发者能够构建起从边缘感知到云端分析的数据管道,其 Java 原生接口和丰富的生态工具链,使其成为工业物联网项目的优选数据库方案。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值