一,需求说明
我们有一个老系统,每天日志量挺大的(有可能是1G左右),比如要搜索某一个订单从生成到关闭完成,其中有很多步骤,而且有时候订单是批量操作的,如果某一个订单出现问题,从大量日志中排查的话,比较困难。1G的日志文件,我们拉下来取分析的话,电脑配置不行会很卡~而且为了找一个订单号要从头到尾,查找一遍,过程十分痛苦。
所以,想到能不能把日志文件拆分部分来分析,当时使用了一下“文件杀手”这个工具,不过我的电脑配置比较差,虽然分成4小部分,依然觉得卡。后来想能不能根据原文件根据订单号把有关系的部分全部展示出来,中间一大批没关系的部分剔除掉就ok!
二,实现思路
首先,我们需要用一个固定大小的集合(队列)来存放我们需要的关键字上下相关部分的内容,特点:先进先出,保持存储的是关键字上下行内容;
然后,定一个afterCount计数关键字后面紧跟的行数,行数达到阀值(关键字行处于队列中间位置)可以输出整个队列并清空;
最后,有几种特殊情况,比如两个关键字行相邻比较近,做法是将前一个关键字在队列中填充空白元素行,使前一个关键字移动到队列中间并全部输出队列,后一个关键字继续放到队列中;
三,代码部分
固定长度队列
package utils.queue;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
/**
* 固定长度队列
*
* @author gary
*/
public class LimitQueue<E> implements Queue<E> {
//队列长度
private int limit;
Queue<E> queue = new LinkedList<E>();
public LimitQueue(int limit) {
this.limit = limit;
}
/**
* 入队
*
* @param e
*/
@Override
public boolean offer(E e) {
if (queue.size() >= limit) {
//如果超出长度,入队时,先出队
queue.poll();
}
return queue.offer(e);
}
/**
* 出队
*
* @return
*/
@Override
public E poll() {
return queue.poll();
}
/**
* 获取队列
*
* @return
*/
public Queue<E> getQueue() {
return queue;
}
/**
* 获取限制大小
*
* @return
*/
public int getLimit() {
return limit;
}
@Override
public boolean add(E e) {
return queue.add(e);
}
@Override
public E element() { return queue.element(); }
@Override
public E peek() {
return queue.peek();
}
@Override
public boolean isEmpty() {
return queue.size() == 0 ? true : false;
}
@Override
public int size() {
return queue.size();
}
@Override
public E remove() {
return queue.remove();
}
@Override
public boolean addAll(Collection<? extends E> c) {
return queue.addAll(c);
}
@Override
public void clear() {
queue.clear();
}
@Override
public boolean contains(Object o) {
return queue.contains(o);
}
@Override
public boolean containsAll(Collection<?> c) {
return queue.containsAll(c);
}
@Override
public Iterator<E> iterator() {
return queue.iterator();
}
@Override
public boolean remove(Object o) {
return queue.remove(o);
}
@Override
public boolean removeAll(Collection<?> c) {
return queue.removeAll(c);
}
@Override
public boolean retainAll(Collection<?> c) {
return queue.retainAll(c);
}
@Override
public Object[] toArray() {
return queue.toArray();
}
@Override
public <T> T[] toArray(T[] a) {
return queue.toArray(a);
}
}
队列写入到新文件
public static void writeDealInfo(String newFile, LimitQueue<String> allQueue) {
try {
FileWriter fw = new FileWriter(newFile, true);
//队列已满 写入
for (String item : allQueue) {
if (item != null) {
// writeDealInfo(item, newFile); //遍历写入新文件
fw.write(item + "\r\n");
}
}
allQueue.clear(); //达到lqueue的大小后,清空一下lqueue
fw.close();
} catch (Exception e) {
System.out.println("书写日志发生错误:" + e.toString());
}
}
具体逻辑
public static void getFile(String srcFile, String newFile, Integer queueSize, String search) throws IOException {
FileInputStream inputStream = new FileInputStream(srcFile);
//设置inputStreamReader的构造方法并创建对象设置编码方式为gbk
//BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "gbk"));
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
String str = null;
//新建一个指定大小的队列,用来存储指定
int allQueueSize = queueSize * 2 + 1;
LimitQueue<String> allQueue = new LimitQueue<>(allQueueSize); //大的queue
//计行器
int afterCount = 0; //用于统计关键字行后面紧跟的添加到queue中的行数,
boolean hasKey = false; //默认队列中没有关键字的行
while ((str = bufferedReader.readLine()) != null) {
allQueue.offer(str); //前置将当前行加入queue,(这里不用管前面有多少个只用管关键字后面是否满足20个)
//1.判断是否包含关键字,
if (str.indexOf(search) > -1) {
// 这里存在可能将前面一大堆都输出情况,比如队列是2*20+1,如果队列前40个都没有关键字,41个有,此时afterCount为0,强行塞入20个空串到队列中来保持前后20行数据
//这里要将队列中之前存在的关键字行前后的数据输出
if (hasKey) {
if (afterCount < queueSize) { //如果队列中关键字后面行数不够,添加空行
addBlank(allQueue, (queueSize - afterCount));
}
//输出大队列
writeDealInfo(newFile, allQueue);
}
hasKey = true;
afterCount = 0;//归0
} else { //不包含关键字
if (hasKey) { //如果队列中有关键字才能计数加1
afterCount++;
}
}
//判断afterCount是否=20,输出
if (afterCount == queueSize) {
writeDealInfo(newFile, allQueue); //输出大队列
afterCount = 0;//归0
hasKey = false;
}
}
//关流
inputStream.close();
bufferedReader.close();
}
//给队列添加count个空串元素,直到afterCount=阀值
private static void addBlank(LimitQueue<String> allQueue, int n) {
while (n > 0) {
allQueue.add(null);
n--;
}
}
测试代码
@Test
public void test1() throws IOException {
String srcFile = "TestLog.txt"; //原日志文件
String newFile = "TestLogNew.txt"; //搜索出来的关键字行
Integer queueSize = 3; //表示截取 搜索的关键字-前后3行-的文本信息
String search = "AA"; //关键字
getFile(srcFile, newFile, queueSize, search);
}
原日志文本样式 TestLog.txt(红色部分是需要截取出来的关键字行)
1 2 3 AA 5 6 AA 8 9 10 11 12 13 14 AA 16 17 18 19 20 21 22 23 24
测试结果:

四,总结
1.多学习,从问题提出到我现在解决问题大概有几个月时间,最近一直忙于软考,从中也学习到很多解决问题的方法,比如复杂问题可以化繁为简。
2.多思考,上面的实现代码,我个人觉得可能有要优化的部分,特别是关于IO操作部分。
3.多动手,我曾将这个问题抛出给一个同学(Java),问问他能不能实现,他表示这个很麻烦,不愿意动手~ 然而个人能力的提升很重要的就是自己动手和积累!
4.上面的代码是用JAVA实现的,如果其他语言的效率更高可以试一试,比如Python。