Java读取600万行的txt文件,内存溢出解决方案

本文讨论了解决Java应用中一次性读取大文件导致内存溢出的方法,通过使用对象池和文件流,减少频繁new对象,提高性能。介绍了如何创建对象池并在读取文件时复用User对象。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

可能造成内存溢出的原因:

  1. 一次性把txt文件读取到内存
  2. 频繁的new对象

实体类


import lombok.Data;
import java.io.Serializable;

/**
 * @author cpf
 * @date 2024/4/13 14:40
 */
@Data
public class User implements Serializable{

    private String user;

    private String positioningTime;

    private String latitude;

    private String longitude;

    private String locationId;

    public void clear() {
        setUser(null);
        setPositioningTime(null);
        setLatitude(null);
        setLongitude(null);
        setLocationId(null);
    }
}

可以使用对象池解决频繁new对象的问题
解决一次性把文件读取到内存: 可以使用文件流方式,使用java.util.Scanner类扫描文件的内容,一行一行连续地读取



import org.example.entity.User;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.*;

/**
 * @author cpf
 * @date 2024/4/13 15:45
 */
public class TestMe {

    private static final int USER_POOL_SIZE = 100; // 根据实际情况调整对象池大小

    private static Queue<User> userPool = new LinkedList<>();

    static {
        // 预先创建对象池中的User实例
        for (int i = 0; i < USER_POOL_SIZE; i++) {
            userPool.offer(new User());
        }
    }

    public static void main(String[] args) {
     
        String filePath = "src/main/resources/Gowalla_totalCheckins.txt";
        List<User> users = readData(filePath);
        for (int i = 0; i < 20; i++) {
            // 获取一个1-6000000的随机数
            int random = (int) (Math.random() * 6000000);
            System.out.println("第"+ random +"个数据: " + users.get(random));
        }
        System.out.println(users.size());


    }

    private static List<User> readData(String filePath) {
        List<User> userList = new ArrayList<>();
        FileInputStream fis  = null;
        Scanner sc = null;
        try {
            fis = new FileInputStream(filePath);
            sc = new Scanner(fis, "UTF-8");
            while (sc.hasNextLine()) {
                String line = sc.nextLine();
                User user = parseLine(line);
                if (user != null) {
                    userList.add(user);
                }
                // 在主程序中,使用完User对象后应将其归还到对象池
                userPool.offer(user);
            }
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                fis.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            if (sc != null) {
                sc.close();
            }
        }


        return userList;
    }


    private static User parseLine(String line) {
        String[] data = line.split("\t");
        if (data.length < 5) {
            System.err.println("数据格式错误:需要至少包含5个字段。");
            return null;
        }
        // 从对象池中获取一个User实例
        User user = userPool.poll();
        if (user == null) {
            // 对象池为空时,创建新的User实例
            user = new User();
        }
        // 清除原有数据并填充新行数据
        user.clear();
        user.setUser(data[0]);
        user.setPositioningTime(data[1]);
        user.setLatitude(data[2]);
        user.setLongitude(data[3]);
        user.setLocationId(data[4]);

        return user;
    }


}

<think>根据用户需求,我们需要开发一个Java工具,该工具能够: 1. 允许用户选择三个文件路径:Excel文件、LMC文件和预定义字段文件(假设为JSON格式) 2. 读取Excel文件和LMC文件 3. 根据预定义字段文件(字段映射)将LMC文件中的内容更新到Excel中(以LMC为准) 4. 生成一个新的Excel文件 设计要点: 1. 使用Swing作为GUI界面,包含三个文件选择框和一个执行按钮。 2. 使用Apache POI处理Excel文件(支持xls和xlsx)。 3. 使用Jackson处理JSON格式的预定义字段文件。 4. LMC文件格式:我们假设LMC文件是一种文本文件,每一行代表一个指令,指令格式为:<行号>,<列名>,<值>。但根据用户需求,我们需要以预定义字段文件中的映射为准,所以列名可能是映射前的字段名,需要通过预定义字段文件映射到Excel中的列。 预定义字段文件(JSON)格式示例: ```json { "字段1": "Excel列名A", "字段2": "Excel列名B" } ``` 处理逻辑: 1. 读取预定义字段文件,得到字段映射关系(从LMC中的字段名到Excel列名的映射)。 2. 读取LMC文件,解析每一行指令,将其中的字段名通过映射关系转换为Excel列名,并记录行号(可能是Excel中的行号)和值。 3. 读取Excel文件,遍历每一行(根据LMC指令中的行号定位到具体行),在对应的列(根据映射后的列名)更新值。 4. 保存为新的Excel文件。 注意:LMC文件中的行号可能是Excel中的行号(从1开始),而POI中行号是从0开始,需要注意转换。 步骤详解: 步骤1:设计GUI界面,包含三个JTextField和三个JButton用于选择文件,一个执行按钮。 步骤2:实现文件选择功能。 步骤3:实现核心处理逻辑: - 读取预定义字段配置文件(JSON)到Map<String, String>,键为LMC中的字段名,值为Excel中的列名(如"A", "B", "C")。 - 读取LMC文件,按行解析,假设每行格式为:行号,字段名,值。例如:1,字段1,Hello World - 读取Excel文件,根据解析出的行号和映射后的列名,更新单元格的值。 步骤4:保存Excel文件。 代码结构: 1. Main类:程序入口,启动GUI。 2. MainFrame类:继承JFrame,构建界面。 3. FileProcessor类:负责文件处理(读取Excel、写入Excel、读取JSON、读取LMC文件)。 注意:由于LMC文件格式是自定义的,这里我们假设它是CSV格式(逗号分隔),但用户可能提供不同的分隔符,这里我们使用逗号。如果需求变化,可以扩展。 异常处理:需要捕获并处理可能出现的异常,如文件不存在、格式错误等。 实现细节: 1. 读取JSON配置文件: ```java ObjectMapper objectMapper = new ObjectMapper(); Map<String, String> fieldMap = objectMapper.readValue(configFile, new TypeReference<Map<String, String>>() {}); ``` 2. 读取LMC文件: ```java List<String> lines = Files.readAllLines(lmcFile.toPath(), StandardCharsets.UTF_8); for (String line : lines) { String[] parts = line.split(","); if (parts.length != 3) { // 处理错误 continue; } int rowNum = Integer.parseInt(parts[0]) - 1; // Excel行号从0开始,而LMC中行号从1开始 String fieldName = parts[1]; String value = parts[2]; // 根据fieldName从fieldMap中获取Excel列名 String column = fieldMap.get(fieldName); if (column == null) { // 该字段未在配置文件中定义,跳过 continue; } // 记录更新操作:行号、列名、值 } ``` 3. 更新Excel: ```java Workbook workbook = WorkbookFactory.create(excelFile); Sheet sheet = workbook.getSheetAt(0); // 假设只处理第一个sheet for (每个更新操作) { Row row = sheet.getRow(rowNum); if (row == null) { row = sheet.createRow(rowNum); } // 将列名(字母)转换为列索引 int colIndex = CellReference.convertColStringToIndex(column); Cell cell = row.getCell(colIndex, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK); cell.setCellValue(value); } ``` 4. 保存工作簿到新文件。 考虑到可能的需求变化(比如LMC指令的多样性),这里我们只实现最简单的覆盖更新。如果后续需要支持公式、格式等,可以扩展。 现在,我们开始编写代码框架: 由于篇幅限制,这里给出核心代码片段。 完整工程结构: src/ main/ java/ com/ example/ Main.java MainFrame.java FileProcessor.java resources/ test/ java/ 依赖(Maven): - poi - poi-ooxml - jackson-databind pom.xml 依赖: ```xml <dependencies> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>5.2.0</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.2.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.0</version> </dependency> <dependency> <groupId>com.formdev</groupId> <artifactId>flatlaf</artifactId> <version>2.4</version> </dependency> </dependencies> ``` 注意:为了界面美观,我们使用了FlatLaf外观。 代码实现: Main.java ```java package com.example; import javax.swing.*; public class Main { public static void main(String[] args) { SwingUtilities.invokeLater(() -> { try { UIManager.setLookAndFeel(new com.formdev.flatlaf.FlatLightLaf()); } catch (Exception ex) { ex.printStackTrace(); } new MainFrame().setVisible(true); }); } } ``` MainFrame.java ```java package com.example; import javax.swing.*; import javax.swing.filechooser.FileNameExtensionFilter; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; public class MainFrame extends JFrame { private JTextField excelPathField; private JTextField lmcPathField; private JTextField configPathField; private JButton processButton; public MainFrame() { setTitle("Excel与LMC文件同步工具"); setSize(600, 300); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocationRelativeTo(null); JPanel panel = new JPanel(new GridLayout(4, 2, 10, 10)); panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); // Excel文件选择 panel.add(new JLabel("Excel文件:")); excelPathField = new JTextField(); JButton excelBrowseButton = new JButton("浏览"); JPanel excelPanel = new JPanel(new BorderLayout()); excelPanel.add(excelPathField, BorderLayout.CENTER); excelPanel.add(excelBrowseButton, BorderLayout.EAST); panel.add(excelPanel); // LMC文件选择 panel.add(new JLabel("LMC文件:")); lmcPathField = new JTextField(); JButton lmcBrowseButton = new JButton("浏览"); JPanel lmcPanel = new JPanel(new BorderLayout()); lmcPanel.add(lmcPathField, BorderLayout.CENTER); lmcPanel.add(lmcBrowseButton, BorderLayout.EAST); panel.add(lmcPanel); // 配置文件选择 panel.add(new JLabel("预定义字段文件:")); configPathField = new JTextField(); JButton configBrowseButton = new JButton("浏览"); JPanel configPanel = new JPanel(new BorderLayout()); configPanel.add(configPathField, BorderLayout.CENTER); configPanel.add(configBrowseButton, BorderLayout.EAST); panel.add(configPanel); // 处理按钮 processButton = new JButton("开始处理"); panel.add(new JLabel()); // 占位 panel.add(processButton); add(panel, BorderLayout.CENTER); // 浏览按钮事件 excelBrowseButton.addActionListener(e -> chooseFile(excelPathField, "Excel文件", "xls", "xlsx")); lmcBrowseButton.addActionListener(e -> chooseFile(lmcPathField, "LMC文件", "lmc", "txt")); configBrowseButton.addActionListener(e -> chooseFile(configPathField, "配置文件", "json")); // 处理按钮事件 processButton.addActionListener(e -> { File excelFile = new File(excelPathField.getText()); File lmcFile = new File(lmcPathField.getText()); File configFile = new File(configPathField.getText()); File outputFile = new File(excelFile.getParent(), "result.xlsx"); FileProcessor processor = new FileProcessor(); try { processor.process(excelFile, lmcFile, configFile, outputFile); JOptionPane.showMessageDialog(this, "处理完成,结果保存为: " + outputFile.getAbsolutePath(), "成功", JOptionPane.INFORMATION_MESSAGE); } catch (Exception ex) { JOptionPane.showMessageDialog(this, "处理失败: " + ex.getMessage(), "错误", JOptionPane.ERROR_MESSAGE); ex.printStackTrace(); } }); } private void chooseFile(JTextField textField, String description, String... extensions) { JFileChooser fileChooser = new JFileChooser(); fileChooser.setFileFilter(new FileNameExtensionFilter(description, extensions)); int result = fileChooser.showOpenDialog(this); if (result == JFileChooser.APPROVE_OPTION) { textField.setText(fileChooser.getSelectedFile().getAbsolutePath()); } } } ``` FileProcessor.java ```java package com.example; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.util.CellReference; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.List; import java.util.Map; public class FileProcessor { public void process(File excelFile, File lmcFile, File configFile, File outputFile) throws Exception { // 1. 读取预定义字段配置文件 ObjectMapper objectMapper = new ObjectMapper(); Map<String, String> fieldMap = objectMapper.readValue(configFile, new TypeReference<Map<String, String>>() {}); // 2. 读取LMC文件 List<String> lines = Files.readAllLines(lmcFile.toPath(), StandardCharsets.UTF_8); // 3. 读取Excel文件 Workbook workbook; try (FileInputStream inputStream = new FileInputStream(excelFile)) { workbook = WorkbookFactory.create(inputStream); } Sheet sheet = workbook.getSheetAt(0); // 4. 处理LMC每一行指令 for (String line : lines) { String[] parts = line.split(",", 3); // 最多分成3部分 if (parts.length < 3) { continue; // 跳过无效行 } try { int rowNum = Integer.parseInt(parts[0]) - 1; // LMC行号从1开始,POI从0开始 String fieldName = parts[1]; String value = parts[2]; String columnStr = fieldMap.get(fieldName); if (columnStr == null) { // 没有找到映射,跳过 continue; } // 将列字符串(如"A")转换为列索引 int colIndex = CellReference.convertColStringToIndex(columnStr); Row row = sheet.getRow(rowNum); if (row == null) { row = sheet.createRow(rowNum); } Cell cell = row.getCell(colIndex, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK); cell.setCellValue(value); } catch (NumberFormatException e) { // 行号解析错误 System.err.println("Invalid row number in LMC line: " + line); } } // 5. 写入输出文件 try (FileOutputStream out = new FileOutputStream(outputFile)) { workbook.write(out); } workbook.close(); } } ``` 注意:上述代码中,LMC文件的解析是基于每行逗号分隔,且恰好三部分(行号,字段名,值)。如果值中包含逗号,这种简单分割就会出错。因此,实际应用中可能需要更复杂的解析(如使用CSV解析器)。但根据需求,我们假设值中不包含逗号。 使用说明: 1. 运行程序,通过浏览按钮选择三个文件。 2. 点击“开始处理”按钮,程序将在Excel文件所在目录生成result.xlsx。 相关问题: 1. 如果LMC文件中的行号在Excel中不存在,程序会创建新行吗? 2. 如何处理LMC文件中字段名在预定义字段文件中找不到映射的情况? 3. 如果Excel文件中有多个Sheet,程序如何处理? 4. 如果LMC文件中的值包含逗号,如何正确解析? 5. 如何提高程序处理大Excel文件的性能? 注意:上述实现中,我们只处理了第一个Sheet。如果需要处理多个Sheet,需要扩展LMC指令以包含Sheet信息,或者通过配置文件指定Sheet。 引用: [^1]: Apache POI官方文档 [^2]: Jackson文档</think>### Java工具开发方案:Excel与LMC文件同步工具 #### 1. 需求分析 - **输入文件**: - Excel文件(`.xlsx`或`.xls`):原始数据文件 - LMC文件(`.lmc`):包含最新数据的文本文件 - 预定义字段文件(`.json`):字段映射规则 - **输出**:以LMC文件内容为准生成的新Excel文件 - **核心逻辑**:$LMC数据 \succ 预定义字段映射 \succ Excel原始数据$ #### 2. 技术栈 | 组件 | 技术方案 | 用途 | |--------------|--------------------------|-----------------------------| | **Excel处理** | Apache POI 5.2.0 | 读写Excel文件 | | **JSON解析** | Jackson 2.15.0 | 解析预定义字段配置文件 | | **LMC解析** | 自定义解析器 | 处理LMC文件格式 | | **GUI框架** | Java Swing + FlatLaf | 用户界面 | #### 3. 系统架构 ```mermaid graph TD A[用户界面] --> B[文件选择器] B --> C[Excel读取器] B --> D[LMC解析器] B --> E[字段映射加载器] C --> F[数据同步引擎] D --> F E --> F F --> G[结果生成器] G --> H[新Excel文件] ``` #### 4. 核心实现代码 ##### (1) 主界面类 (MainFrame.java) ```java public class MainFrame extends JFrame { private JTextField excelPath, lmcPath, configPath; public MainFrame() { // 文件选择组件 JButton browseExcel = new JButton("浏览"); browseExcel.addActionListener(e -> excelPath.setText(showFileChooser("Excel文件", "xlsx", "xls"))); // 执行按钮 JButton execute = new JButton("生成结果"); execute.addActionListener(e -> { FileProcessor processor = new FileProcessor(); processor.process( new File(excelPath.getText()), new File(lmcPath.getText()), new File(configPath.getText()), new File("result.xlsx") ); }); } private String showFileChooser(String desc, String... exts) { JFileChooser fc = new JFileChooser(); fc.setFileFilter(new FileNameExtensionFilter(desc, exts)); return (fc.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) ? fc.getSelectedFile().getAbsolutePath() : ""; } } ``` ##### (2) 文件处理器 (FileProcessor.java) ```java public class FileProcessor { public void process(File excelFile, File lmcFile, File configFile, File output) { try { // 1. 加载预定义字段映射 Map<String, String> fieldMap = loadFieldConfig(configFile); // 2. 解析LMC文件 Map<Integer, Map<String, String>> lmcData = parseLMC(lmcFile, fieldMap); // 3. 处理Excel try (Workbook workbook = WorkbookFactory.create(excelFile)) { Sheet sheet = workbook.getSheetAt(0); // 应用LMC数据 lmcData.forEach((rowNum, rowData) -> { Row row = sheet.getRow(rowNum - 1); // Excel行号从0开始 if (row == null) row = sheet.createRow(rowNum - 1); rowData.forEach((colName, value) -> { int colIndex = CellReference.convertColStringToIndex(colName); Cell cell = row.getCell(colIndex, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK); cell.setCellValue(value); }); }); // 保存结果 try (FileOutputStream fos = new FileOutputStream(output)) { workbook.write(fos); } } } catch (Exception ex) { throw new RuntimeException("处理失败: " + ex.getMessage(), ex); } } private Map<String, String> loadFieldConfig(File file) throws IOException { ObjectMapper mapper = new ObjectMapper(); return mapper.readValue(file, new TypeReference<Map<String, String>>() {}); } } ``` ##### (3) LMC解析器 (LMCParser.java) ```java public class LMCParser { public Map<Integer, Map<String, String>> parse(File lmcFile, Map<String, String> fieldMap) { Map<Integer, Map<String, String>> result = new HashMap<>(); try (BufferedReader reader = new BufferedReader(new FileReader(lmcFile))) { String line; while ((line = reader.readLine()) != null) { if (line.trim().isEmpty()) continue; // 示例格式: 行号|字段名|值 String[] parts = line.split("\\|", 3); if (parts.length < 3) continue; int rowNum = Integer.parseInt(parts[0]); String rawField = parts[1]; String value = parts[2]; // 应用字段映射 String excelField = fieldMap.getOrDefault(rawField, rawField); result.computeIfAbsent(rowNum, k -> new HashMap<>()) .put(excelField, value); } } return result; } } ``` #### 5. 文件格式示例 **LMC文件内容**: ``` 5|product_id|P-10086 5|product_name|高性能处理器 8|price|299.99 ``` **预定义字段文件(JSON)**: ```json { "product_id": "B", "product_name": "C", "price": "F" } ``` #### 6. 数学公式说明 数据处理过程满足以下关系: $$ \text{最终值} = \begin{cases} \text{LMC值} & \text{当字段存在映射时} \\ \text{Excel原值} & \text{其他情况} \end{cases} $$ #### 7. 性能优化策略 1. **内存管理**: - 使用`SXSSFWorkbook`处理大文件 - 分块读取LMC文件 $$ \text{内存占用} = O(n) + O(m) \quad n=\text{Excel行数}, m=\text{LMC记录数} $$ 2. **并行处理**: ```java lmcData.entrySet().parallelStream().forEach(entry -> { // 处理每行数据 }); ``` #### 8. 异常处理机制 ```java try { // 核心处理逻辑 } catch (FileNotFoundException e) { showError("文件不存在: " + e.getMessage()); } catch (InvalidFormatException e) { showError("文件格式错误: " + e.getMessage()); } catch (IOException e) { showError("IO错误: " + e.getMessage()); } ``` #### 9. 工程结构 ``` src/ ├── main/ │ ├── java/ │ │ ├── Main.java │ │ ├── ui/ │ │ │ └── MainFrame.java │ │ ├── core/ │ │ │ ├── FileProcessor.java │ │ │ ├── LMCParser.java │ │ │ └── FieldMapper.java │ ├── resources/ ├── test/ │ └── java/ │ └── LMCParserTest.java pom.xml ``` #### 10. Maven依赖 ```xml <dependencies> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>5.2.0</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>5.2.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.0</version> </dependency> <dependency> <groupId>com.formdev</groupId> <artifactId>flatlaf</artifactId> <version>3.0</version> </dependency> </dependencies> ``` ### 相关问题 1. 如何处理LMC文件和Excel文件的字段命名不一致问题? 2. 当LMC文件包含数万条记录时如何优化处理速度? 3. 如何扩展工具以支持不同格式的配置文件(如XML/YAML)? 4. 在大型Excel文件处理中如何避免内存溢出? 5. 如何验证LMC文件与Excel文件的同步结果是否正确? 6. 能否增加冲突检测机制处理字段映射冲突?[^1] [^1]: 关于数据冲突处理,参考《数据集成模式》(Data Integration Patterns)中的冲突解决策略
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

微微一笑满城空

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值