Java日志文件大小管控:无需临时文件删除首行数据
自定义日志文件的滚动策略(删除首行控制文件大小)
需求与现状
需要实现Java日志记录功能,要求:
- 日志写入
log.log(后续可改为变量支持多文件) - 当文件大小超过1KB(1024字节)时,删除最旧的首行(一行不够则删多行),确保文件大小不超限
- 不使用临时文件替换方案,希望直接操作文件完成首行删除
当前实现代码:
public void getLogHistory(String logContent) { File logFiles = new File("log.log"); // This is just a test, I may check to a variable and save to different log files under different situation Timestamp timestamp = new Timestamp(System.currentTimeMillis()); String logStatement = "[" + timestamp.toString() + "] | " + logContent; try { logStatement += "\n"; Files.write(Paths.get("log.log"), logStatement.getBytes(), StandardOpenOption.APPEND); } catch (IOException e) { } }
解决方案与代码改进
1. 核心实现:直接读写内存删除首行
无需临时文件,通过将文件内容读入内存,删除头部行后重新写入的方式实现,针对1KB的阈值,内存占用完全可控:
private static final long MAX_LOG_SIZE = 1024; // 1KB阈值 private static final String LOG_FILE_PATH = "log.log"; public void getLogHistory(String logContent) { File logFile = new File(LOG_FILE_PATH); // 先清理超出大小的日志 trimLogFileToSize(logFile); // 写入新日志 Timestamp timestamp = new Timestamp(System.currentTimeMillis()); String logStatement = "[" + timestamp.toString() + "] | " + logContent + "\n"; try { // 添加CREATE选项,确保文件不存在时自动创建 Files.write(Paths.get(LOG_FILE_PATH), logStatement.getBytes(), StandardOpenOption.CREATE, StandardOpenOption.APPEND); } catch (IOException e) { e.printStackTrace(); // 禁止空捕获,至少打印异常信息便于排查 } } private void trimLogFileToSize(File logFile) { if (!logFile.exists() || logFile.length() <= MAX_LOG_SIZE) { return; } try { // 读取所有行到内存,指定UTF-8编码避免环境差异 List<String> lines = Files.readAllLines(logFile.toPath(), StandardCharsets.UTF_8); long currentSize = logFile.length(); // 循环删除首行,直到文件大小符合要求 while (currentSize > MAX_LOG_SIZE && !lines.isEmpty()) { String removedLine = lines.remove(0); // 计算被删除行的总字节数(包含换行符) currentSize -= (removedLine.getBytes(StandardCharsets.UTF_8).length + System.lineSeparator().getBytes(StandardCharsets.UTF_8).length); } // 将剩余行写回文件,TRUNCATE_EXISTING清空原有内容 Files.write(logFile.toPath(), lines, StandardCharsets.UTF_8, StandardOpenOption.TRUNCATE_EXISTING); } catch (IOException e) { e.printStackTrace(); } }
2. 优化方案:低内存占用的倒序读取
如果需要支持更大的阈值(或避免一次性读取全部内容),可以采用倒序读取的方式,只保留最后N行直到大小符合要求,减少内存占用:
private void trimLogFileToSizeOptimized(File logFile) throws IOException { if (!logFile.exists() || logFile.length() <= MAX_LOG_SIZE) { return; } List<String> keepLines = new ArrayList<>(); Deque<String> lineStack = new ArrayDeque<>(); long currentSize = 0; // 逐行读取日志,存入栈中实现倒序 try (BufferedReader reader = Files.newBufferedReader(logFile.toPath(), StandardCharsets.UTF_8)) { String line; while ((line = reader.readLine()) != null) { lineStack.push(line); // 累加当前行的字节数(含换行符) currentSize += line.getBytes(StandardCharsets.UTF_8).length + System.lineSeparator().getBytes(StandardCharsets.UTF_8).length; // 当总大小超过阈值时,弹出最旧的行(栈顶元素) while (currentSize > MAX_LOG_SIZE && !lineStack.isEmpty()) { lineStack.pop(); // 重新计算栈中剩余行的总大小 currentSize = 0; for (String l : lineStack) { currentSize += l.getBytes(StandardCharsets.UTF_8).length + System.lineSeparator().getBytes(StandardCharsets.UTF_8).length; } } } // 将栈中元素反转,恢复正序后存入保留列表 while (!lineStack.isEmpty()) { keepLines.add(lineStack.pop()); } } // 写回文件 Files.write(logFile.toPath(), keepLines, StandardCharsets.UTF_8, StandardOpenOption.TRUNCATE_EXISTING); }
3. 关键改进点
- 禁止空异常捕获:原代码空catch块会隐藏错误,必须添加异常打印或处理逻辑
- 统一字符编码:读取写入时指定
StandardCharsets.UTF_8,避免不同环境下的编码混乱 - 添加文件创建逻辑:写入时加入
StandardOpenOption.CREATE,确保文件不存在时自动创建 - 并发安全处理:如果是多线程写入场景,需要在日志读写方法上添加
synchronized锁,避免数据冲突
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

