Java - 使用 Apache POI 写入大型 Excel 文件时出现 OutOfMemoryError
Java - OutOfMemoryError when writing large Excel file with Apache POI
我收到 java 内存不足错误。我添加了必要的 java 参数,但我仍然不断收到此错误。我分享了我使用的库和函数。将大型 csv 文件(约 15mb)转换为 xlsx 文件时,函数会出现此错误。它可以在小文件上正常工作,没有任何错误。我该如何解决这个错误?谢谢。
I added these java args on Intellij Idea
I got error
I use this libraries
主要
public class Main {
public static void main(String[] args) {
convert_CSV_to_XLSX(S.CSV_PATH,S.XLSX_PATH,"Sheet");
}
}
将 CSV 转换为 XLSX
public void convert_CSV_to_XLSX(String inputFilePath, String outputFilePath, String sheetName) {
try {
ArrayList<ArrayList<Object>> csvObjectsAll = readCSV(inputFilePath);
writeXLSX_horizontally(outputFilePath, csvObjectsAll, sheetName);
} catch (Exception e) {
e.printStackTrace();
}
}
ReadCSV
public ArrayList<ArrayList<Object>> readCSV(String inputFilePath) {
ArrayList<ArrayList<Object>> gal = new ArrayList<>();
try {
String csvStr = new String(Files.readAllBytes(Paths.get(inputFilePath)), StandardCharsets.UTF_8);
for (String str : csvStr.split("\n")) {
ArrayList<Object> csvLinesSplit = new ArrayList<>();
String ss = str.replaceAll("\"", "");
if (ss.charAt(ss.length() - 1) == ',') {
ss += "$";
}
for (String s : ss.split(",")) {
if (s.equals("") || s.equals("$")) {
csvLinesSplit.add("");
} else {
csvLinesSplit.add(s);
}
}
gal.add(csvLinesSplit);
}
} catch (Exception e) {
}
return gal;
}
写入 XLSX
public void writeXLSX_horizontally(String outputFileName, ArrayList<ArrayList<Object>> gdl, String sheetName) {
XSSFWorkbook workbook = new XSSFWorkbook();
XSSFSheet sheet = workbook.createSheet(sheetName);
int rowNum = 0;
for (ArrayList<Object> objectArrList : gdl) {
Row row = sheet.createRow(rowNum++);
int cellNum = 0;
for (Object obj : objectArrList) {
Cell cell = row.createCell(cellNum++);
boolean is_double = false, is_integer = false;
try {
cell.setCellValue(Double.parseDouble(obj.toString()));
is_double = true;
} catch (Exception e) {
}
if (!is_double) {
try {
cell.setCellValue(Integer.parseInt(obj.toString()));
is_integer = true;
} catch (Exception e) {
}
}
if (!is_double && !is_integer) {
if (obj == null) {
cell.setCellValue(new String());
} else {
cell.setCellValue(obj.toString());
}
}
}
}
try {
FileOutputStream file = new FileOutputStream(outputFileName);
workbook.write(file);
file.close();
} catch (Exception e) {
e.printStackTrace();
}
}
这一行:
String csvStr = new String(Files.readAllBytes(Paths.get(inputFilePath)), StandardCharsets.UTF_8);
问题:
您正在使用 Files.readAllBytes
将整个文件加载到内存中。并且为这个程序所在的jvm处理器分配的内存运行是不够的。
可能的解决方案:
您可能希望使用像 BufferedReader 这样的 streams/buffers 开始读取文件。或者您可以查找其他允许您按位读取文件的阅读器,这样整个内存就不会被一次性消耗掉。
进一步修改:
您还必须在编写时修改您的程序,在您读取数据位后,您处理并写入文件,当再次写入文件时,您追加。
如评论中所述,问题是由于 IntelliJ 运行 配置不正确造成的。
VM 参数需要传递到 IntelliJ 中的单独字段,而不是作为“程序参数”。
不过,程序还可以改进:
- 逐行处理输入文件
- 使用SXSSFWorkbook写入输出
Streaming version of XSSFWorkbook implementing the "BigGridDemo" strategy. This allows to write very large files without running out of memory as only a configurable portion of the rows are kept in memory at any one time.
- 使用 "" 代替 new String()
- 与内存无关:正确使用泛型(您在解析的 CSV 中有字符串,而不是任意对象)
请注意,流式传输输入和输出是最佳选择。
话虽如此,按照今天的标准,15MB 输入是很小的,所以我相信稍微增加堆内存是一个不错的短期解决方案
我删除了 java 虚拟机上的共享内存:-Xms1024M -Xmx12288M
感谢@Faraz 和@Lesiak perm 解决方案,在此处写入大型 xlsx 文件:
读取 CSV
public ArrayList<ArrayList<Object>> readCSV(String inputFilePath) {
ArrayList<ArrayList<Object>> gal = new ArrayList<>();
try {
BufferedReader csvReader = new BufferedReader(new FileReader(inputFilePath));
String row;
int rowSize = 0;
ArrayList<String> columnList = new ArrayList<>();
while ((row = csvReader.readLine()) != null) {
ArrayList<Object> rowCells = new ArrayList<>();
if (rowSize == 0) {
if (row.charAt(row.length() - 1) == ',')
throw new Exception("CSV Format Error");
for (String columnName : row.split(",")) {
columnList.add(columnName);
}
}
int cellSize = 0;
for (String cell : row.split(",")) {
if (cell.equals("")) {
rowCells.add(null);
} else {
rowCells.add(cell);
}
cellSize++;
}
if (cellSize != columnList.size()) {
for (int i = 0; i < columnList.size() - cellSize; i++) {
rowCells.add(null);
}
}
gal.add(rowCells);
rowSize++;
}
} catch (Exception e) {
e.printStackTrace();
}
return gal;
}
写入 XLSX
public void writeXLSX_horizontally(String outputFileName, ArrayList<ArrayList<Object>> gdl, String sheetName) {
SXSSFWorkbook workbook = new SXSSFWorkbook();
SXSSFSheet sheet = workbook.createSheet(sheetName);
int rowNum = 0;
for (ArrayList<Object> objectArrList : gdl) {
Row row = sheet.createRow(rowNum++);
int cellNum = 0;
for (Object obj : objectArrList) {
Cell cell = row.createCell(cellNum++);
boolean is_double = false, is_integer = false;
try {
cell.setCellValue(Double.parseDouble(obj.toString()));
is_double = true;
} catch (Exception e) { }
if (!is_double)
try {
cell.setCellValue(Integer.parseInt(obj.toString()));
is_integer = true;
} catch (Exception e) { }
if (!is_double && !is_integer)
if (obj == null)
cell.setCellValue(new String());
else
cell.setCellValue(obj.toString());
}
}
try {
FileOutputStream file = new FileOutputStream(outputFileName);
workbook.write(file);
file.close();
} catch (Exception e) {
e.printStackTrace();
}
}
在此处阅读大型 xlsx 文件解决方案:How to read XLSX file of size >40MB
用于读取大型 xlsx 文件的其他重要库:
https://github.com/monitorjbl/excel-streaming-reader
约束:xlsx 文件行数必须介于 0..1048575
之间
我收到 java 内存不足错误。我添加了必要的 java 参数,但我仍然不断收到此错误。我分享了我使用的库和函数。将大型 csv 文件(约 15mb)转换为 xlsx 文件时,函数会出现此错误。它可以在小文件上正常工作,没有任何错误。我该如何解决这个错误?谢谢。
I added these java args on Intellij Idea
I got error
I use this libraries
主要
public class Main {
public static void main(String[] args) {
convert_CSV_to_XLSX(S.CSV_PATH,S.XLSX_PATH,"Sheet");
}
}
将 CSV 转换为 XLSX
public void convert_CSV_to_XLSX(String inputFilePath, String outputFilePath, String sheetName) {
try {
ArrayList<ArrayList<Object>> csvObjectsAll = readCSV(inputFilePath);
writeXLSX_horizontally(outputFilePath, csvObjectsAll, sheetName);
} catch (Exception e) {
e.printStackTrace();
}
}
ReadCSV
public ArrayList<ArrayList<Object>> readCSV(String inputFilePath) {
ArrayList<ArrayList<Object>> gal = new ArrayList<>();
try {
String csvStr = new String(Files.readAllBytes(Paths.get(inputFilePath)), StandardCharsets.UTF_8);
for (String str : csvStr.split("\n")) {
ArrayList<Object> csvLinesSplit = new ArrayList<>();
String ss = str.replaceAll("\"", "");
if (ss.charAt(ss.length() - 1) == ',') {
ss += "$";
}
for (String s : ss.split(",")) {
if (s.equals("") || s.equals("$")) {
csvLinesSplit.add("");
} else {
csvLinesSplit.add(s);
}
}
gal.add(csvLinesSplit);
}
} catch (Exception e) {
}
return gal;
}
写入 XLSX
public void writeXLSX_horizontally(String outputFileName, ArrayList<ArrayList<Object>> gdl, String sheetName) {
XSSFWorkbook workbook = new XSSFWorkbook();
XSSFSheet sheet = workbook.createSheet(sheetName);
int rowNum = 0;
for (ArrayList<Object> objectArrList : gdl) {
Row row = sheet.createRow(rowNum++);
int cellNum = 0;
for (Object obj : objectArrList) {
Cell cell = row.createCell(cellNum++);
boolean is_double = false, is_integer = false;
try {
cell.setCellValue(Double.parseDouble(obj.toString()));
is_double = true;
} catch (Exception e) {
}
if (!is_double) {
try {
cell.setCellValue(Integer.parseInt(obj.toString()));
is_integer = true;
} catch (Exception e) {
}
}
if (!is_double && !is_integer) {
if (obj == null) {
cell.setCellValue(new String());
} else {
cell.setCellValue(obj.toString());
}
}
}
}
try {
FileOutputStream file = new FileOutputStream(outputFileName);
workbook.write(file);
file.close();
} catch (Exception e) {
e.printStackTrace();
}
}
这一行:
String csvStr = new String(Files.readAllBytes(Paths.get(inputFilePath)), StandardCharsets.UTF_8);
问题:
您正在使用 Files.readAllBytes
将整个文件加载到内存中。并且为这个程序所在的jvm处理器分配的内存运行是不够的。
可能的解决方案:
您可能希望使用像 BufferedReader 这样的 streams/buffers 开始读取文件。或者您可以查找其他允许您按位读取文件的阅读器,这样整个内存就不会被一次性消耗掉。
进一步修改:
您还必须在编写时修改您的程序,在您读取数据位后,您处理并写入文件,当再次写入文件时,您追加。
如评论中所述,问题是由于 IntelliJ 运行 配置不正确造成的。
VM 参数需要传递到 IntelliJ 中的单独字段,而不是作为“程序参数”。
不过,程序还可以改进:
- 逐行处理输入文件
- 使用SXSSFWorkbook写入输出
Streaming version of XSSFWorkbook implementing the "BigGridDemo" strategy. This allows to write very large files without running out of memory as only a configurable portion of the rows are kept in memory at any one time.
- 使用 "" 代替 new String()
- 与内存无关:正确使用泛型(您在解析的 CSV 中有字符串,而不是任意对象)
请注意,流式传输输入和输出是最佳选择。 话虽如此,按照今天的标准,15MB 输入是很小的,所以我相信稍微增加堆内存是一个不错的短期解决方案
我删除了 java 虚拟机上的共享内存:-Xms1024M -Xmx12288M
感谢@Faraz 和@Lesiak perm 解决方案,在此处写入大型 xlsx 文件:
读取 CSV
public ArrayList<ArrayList<Object>> readCSV(String inputFilePath) {
ArrayList<ArrayList<Object>> gal = new ArrayList<>();
try {
BufferedReader csvReader = new BufferedReader(new FileReader(inputFilePath));
String row;
int rowSize = 0;
ArrayList<String> columnList = new ArrayList<>();
while ((row = csvReader.readLine()) != null) {
ArrayList<Object> rowCells = new ArrayList<>();
if (rowSize == 0) {
if (row.charAt(row.length() - 1) == ',')
throw new Exception("CSV Format Error");
for (String columnName : row.split(",")) {
columnList.add(columnName);
}
}
int cellSize = 0;
for (String cell : row.split(",")) {
if (cell.equals("")) {
rowCells.add(null);
} else {
rowCells.add(cell);
}
cellSize++;
}
if (cellSize != columnList.size()) {
for (int i = 0; i < columnList.size() - cellSize; i++) {
rowCells.add(null);
}
}
gal.add(rowCells);
rowSize++;
}
} catch (Exception e) {
e.printStackTrace();
}
return gal;
}
写入 XLSX
public void writeXLSX_horizontally(String outputFileName, ArrayList<ArrayList<Object>> gdl, String sheetName) {
SXSSFWorkbook workbook = new SXSSFWorkbook();
SXSSFSheet sheet = workbook.createSheet(sheetName);
int rowNum = 0;
for (ArrayList<Object> objectArrList : gdl) {
Row row = sheet.createRow(rowNum++);
int cellNum = 0;
for (Object obj : objectArrList) {
Cell cell = row.createCell(cellNum++);
boolean is_double = false, is_integer = false;
try {
cell.setCellValue(Double.parseDouble(obj.toString()));
is_double = true;
} catch (Exception e) { }
if (!is_double)
try {
cell.setCellValue(Integer.parseInt(obj.toString()));
is_integer = true;
} catch (Exception e) { }
if (!is_double && !is_integer)
if (obj == null)
cell.setCellValue(new String());
else
cell.setCellValue(obj.toString());
}
}
try {
FileOutputStream file = new FileOutputStream(outputFileName);
workbook.write(file);
file.close();
} catch (Exception e) {
e.printStackTrace();
}
}
在此处阅读大型 xlsx 文件解决方案:How to read XLSX file of size >40MB
用于读取大型 xlsx 文件的其他重要库: https://github.com/monitorjbl/excel-streaming-reader
约束:xlsx 文件行数必须介于 0..1048575
之间