目录
- 代码由来
- 实现过程
- 代码
代码由来
项目里需要读取最新的日志,可是最新的日志都在文件的最后几行,所以有了这个需求。
百度之后先研究了别人的代码,感觉一是逻辑不太好理解且注释比较少,二是代码偏向于演示,会有bug,没有办法拿来就用。所以自己写了这个方法,希望可以给需要的同学带来帮助。
这个方法实现的功能和Linux系统的tail命令是相同的。
实现过程
对这个方法我前前后后投入了很大精力,每改变一次实现方式,前面的工作都仿佛白做了一样。从读取功能基本实现,到解决读取空行和文档行数不足导致的bug。再到费尽心机的简化代码,到最后一次实现方式的改变直接解决之前所有问题。到考虑性能把缓存工具的更换和代码执行逻辑的优化。
这个过程让我意识到:再简单的需求,用上复杂的实现就算把人累死也得不到好的结果。作为一名程序员,拿到需求我们往往急于写代码,当看到杂乱的实现代码,我们又抓耳挠腮无从下手去改进。每当这个时候,我们一定要回头去看看我们的需求,想想有没有其他更简洁的实现方法,而不要困死在已有的代码里。
感谢 @万物皆字节 同学的建议。
如果这篇博客帮助到了你,你可给它点个赞,这将是对我莫大的鼓励,谢谢!
代码
/**
* 读取文件最后几行 <br>
* 相当于Linux系统中的tail命令 读取大小限制是2GB
*
* @param filename 文件名
* @param charset 文件编码格式,传null默认使用defaultCharset
* @param rows 读取行数
* @throws IOException IOException
*/
public static String readLastRows(String filename, Charset charset, int rows) throws IOException {
charset = charset == null ? Charset.defaultCharset() : charset;
byte[] lineSeparator = System.getProperty("").getBytes();
try (RandomAccessFile rf = new RandomAccessFile(filename, "r")) {
// 每次读取的字节数要和系统换行符大小一致
byte[] c = new byte[lineSeparator.length];
// 在获取到指定行数和读完文档之前,从文档末尾向前移动指针,遍历文档每一个字节
for (long pointer = rf.length(), lineSeparatorNum = 0; pointer >= 0 && lineSeparatorNum < rows;) {
// 移动指针
rf.seek(pointer--);
// 读取数据
int readLength = rf.read(c);
if (readLength != -1 && Arrays.equals(lineSeparator,c)) {
lineSeparatorNum++;
}
//扫描完依然没有找到足够的行数,将指针归0
if (pointer == -1 && lineSeparatorNum < rows) {
rf.seek(0);
}
}
byte[] tempbytes = new byte[(int) (rf.length() - rf.getFilePointer())];
rf.readFully(tempbytes);
return new String(tempbytes, charset);
}
}