基于文本搜索的算法表现不尽如人意

时间:2015-04-27 13:06:50

标签: java algorithm text

更新

我已经使用其他SO用户建议的更新代码更新了问题,并将澄清之前存在的任何含糊不清的文字。

更新#2

我只能访问相关应用程序生成的日志文件。因此,我被限制在日志文件的内容中工作,并且没有超出该范围的解决方案。我稍微修改了样本数据。我想指出以下关键变量

Thread ID - 范围从0..19 - 线程被多次使用。因此ScriptExecThread(2)可能会在日志中多次出现。

Script - 每个线程都会在特定文件上运行脚本。同样的脚本可能在同一个线程上运行,但不会在同一个线程AND文件上运行。

File - 每个Thread IDScript上运行File。如果Thread(10)myscript.script上运行myfile.file,则该EXACT行不会再次执行。使用上述示例的成功示例就是这样。

  

------ ------ START

     

线程(10)在myfile.file上启动myscript.script

     

线程(10)在myfile.file上完成了myscript.script

     

------ ------- END

使用上述示例的不成功示例将是:

  

------ ------ START

     

线程(10)在myfile.file上启动myscript.script

     

------ ------ END

在解决我的查询之前,我将简要介绍所使用的代码和所需的行为。

摘要

我目前正在解析大型日志文件(平均占用100k - 600k行)并尝试按特定顺序检索某些信息。我已经根据我的要求制定了布尔代数,它似乎在纸上起作用但在代码上却没有那么多(我必须错过了一些显而易见的东西)。我想提前告知代码没有任何形状或形式优化,现在我只是想让它工作。

在此日志文件中,您可以看到某些线程在启动时挂起但从未完成。可能的线程ID范围的数量。这是一些伪代码:

    REGEX = "ScriptExecThread(\\([0-9]+\\)).*?(finished|starting)" //in java
    Set started, finished
    for (int i=log.size()-1; i >=0; i--) {
    if(group(2).contains("starting")
        started.add(log.get(i))
    else if(group(2).contains("finished")
        finished.add(log.get(i)    
    }
    started.removeAll(finished);

搜索已挂起的主题

Set<String> started = new HashSet<String>(), finished = new HashSet<String>();

for(int i = JAnalyzer.csvlog.size()-1; i >= 0; i--) {
    if(JAnalyzer.csvlog.get(i).contains("ScriptExecThread")) 
        JUtility.hasThreadHung(JAnalyzer.csvlog.get(i), started, finished);     
}
started.removeAll(finished);

commonTextArea.append("Number of threads hung: " + noThreadsHung + "\n");
for(String s : started) { 
    JLogger.appendLineToConsole(s);
    commonTextArea.append(s+"\n");
}

线程已挂起

public static boolean hasThreadHung(final String str, Set<String> started, Set<String> finished) {      
    Pattern r = Pattern.compile("ScriptExecThread(\\([0-9]+\\)).*?(finished|starting)");
    Matcher m = r.matcher(str);
    boolean hasHung = m.find();

        if(m.group(2).contains("starting"))
            started.add(str);
        else if (m.group(2).contains("finished"))
            finished.add(str);

        System.out.println("Started size: " + started.size());
        System.out.println("Finished size: " + finished.size());

    return hasHung;
}

示例数据

  

ScriptExecThread(1)在afile.xyz

上启动      

ScriptExecThread(2)在bfile.abc

上启动      

ScriptExecThread(3)在cfile.zyx

上启动      

ScriptExecThread(4)在dfile.zxy

上启动      

ScriptExecThread(5)在efile.yzx

上启动      

ScriptExecThread(1)在afile.xyz上完成

     

ScriptExecThread(2)在bfile.abc上完成

     

ScriptExecThread(3)在cfile.zyx上完成

     

ScriptExecThread(4)在dfile.zxy

上完成      

ScriptExecThread(5)在efile.yzy

上完成      

ScriptExecThread(1)在bfile.abc

上启动      

ScriptExecThread(2)在dfile.zxy

上启动      

ScriptExecThread(3)在afile.xyz

上启动      

ScriptExecThread(1)在bfile.abc上完成

     

结束日志

如果您举例,您会注意到2号线和2号线。 3开始但未能完成(原因没有必要,我只需要获得该线)。

样本数据

  

09.08 15:06.53,ScriptExecThread(7),Info,########### starting

     

09.08 15:06.54,ScriptExecThread(18),Info,#######################

     

09.08 15:06.54,ScriptExecThread(13),Info,########在#########

中完成      

09.08 15:06.54,ScriptExecThread(13),Info,########## starting

     

09.08 15:06.55,ScriptExecThread(9),Info,#####在########

中完成      

09.08 15:06.55,ScriptExecThread(0),Info,####在###########

中完成      

09.08 15:06.55,ScriptExecThread(19),Info,####在########

中完成      

09.08 15:06.55,ScriptExecThread(8),Info,######在2777完成#########

     

09.08 15:06.55,ScriptExecThread(19),Info,########## starting

     

09.08 15:06.55,ScriptExecThread(8),Info,####### starting

     

09.08 15:06.55,ScriptExecThread(0),Info,########## starting

     

09.08 15:06.55,ScriptExecThread(19),Info,Post ######在#####

完成      

09.08 15:06.55,ScriptExecThread(0),Info,######在#########

中完成      

09.08 15:06.55,ScriptExecThread(19),Info,########## starting

     

09.08 15:06.55,ScriptExecThread(0),Info,########### starting

     

09.08 15:06.55,ScriptExecThread(9),Info,########## starting

     

09.08 15:06.56,ScriptExecThread(1),Info,#######在########

中完成      

09.08 15:06.56,ScriptExecThread(17),Info,######在#######

中完成      

09.08 15:06.56,ScriptExecThread(17),Info,#######################

     

09.08 15:06.56,ScriptExecThread(1),Info,########## starting

目前,代码只显示整个日志文件,其中的行以&#34;开始&#34;开头。当我查看代码时,这有点有意义。

我删除了任何我不希望显示的冗余信息。如果有什么我可能遗漏的请随时告诉我,我会添加它。

4 个答案:

答案 0 :(得分:6)

如果我理解正确,你有大文件,并试图找到表格的模式&#34; X开始(但没有提到X完成)&#34;对于X的所有数值。

如果我这样做,我会使用这个伪代码:

Pattern p = Pattern.compile(
   "ScriptExecThread\\(([0-9]+).*?(finished|started)");
Set<Integer> started, finished;
Search for p; for each match m,
     int n = Integer.parseInt(m.group(1));
     if (m.group(2).equals("started")) started.add(n);
     else finished.add(n);
started.removeAll(finished); // found 'em: contains started-but-not-finished

这需要在每个文件上进行单个正则表达式传递,并且需要O(完成大小)设置减法;它应该比您当前的方法快20倍。正则表达式将使用可选(|)匹配来同时查找两个备选项,从而减少传递次数。

编辑:显式正则表达式。编译正则表达式而不是每行一次应该可以减少一些额外的运行时间。

编辑2:实现伪代码,适合我

编辑3:替换实现以显示文件&amp;线。减少内存需求(不将整个文件加载到内存中);但打印线确实需要所有&#34; start&#34;要存储的行。

public class T {

    public static Collection<String> findHung(Iterable<String> data) {
        Pattern p = Pattern.compile(   
            "ScriptExecThread\\(([0-9]+).*?(finished|starting)");
        HashMap<Integer, String> started = new HashMap<Integer, String>();
        Set<Integer> finished = new HashSet<Integer>();
        for (String d : data) {
            Matcher m = p.matcher(d);
            if (m.find()) {
                int n = Integer.parseInt(m.group(1));
                if (m.group(2).equals("starting")) started.put(n, d);
                else finished.add(n);
            }                
        }
        for (int f : finished) {
            started.remove(f);
        }
        return started.values();
    }

    static Iterable<String> readFile(String path, String encoding) throws IOException {
        final Scanner sc = new Scanner(new File(path), encoding).useDelimiter("\n");
        return new Iterable<String>() {
            public Iterator<String> iterator() { return sc; }
        };
    }

    public static void main(String[] args) throws Exception {
        for (String fileName : args) {
            for (String s : findHung(readFile(fileName, "UTF-8"))) {
                System.out.println(fileName + ": '" + s + "' unfinished");
            }
        }
    }
}

输入:上面的示例数据,作为第一个参数,称为&#34; data.txt&#34;。另一个文件中的相同数据称为&#34; data2.txt&#34;作为第二个参数(javac T.java && java T data.txt data2.txt)。输出:

data.txt: '    09.08 15:06.54, ScriptExecThread(18),Info,###################### starting' unfinished
data.txt: '    09.08 15:06.53, ScriptExecThread(7),Info,########### starting' unfinished
data2.txt: '    09.08 15:06.54, ScriptExecThread(18),Info,###################### starting' unfinished
data2.txt: '    09.08 15:06.53, ScriptExecThread(7),Info,########### starting' unfinished

答案 1 :(得分:2)

保留两组独立的startedfinished线程(如@tucuxi所述)无效。如果ID为5的线程启动,运行并完成,则finished集中将永久显示5。如果ID为5的另一个线程启动并挂起,则不会报告。

但是,假设片刻ID不会被重用。创建的每个线程都会收到一个新ID。通过保留单独的startedfinished集,您在完成时将拥有数十万个元素。这些数据结构的性能与它们在操作时的性能成正比。性能不太可能对您的用例很重要,但如果您执行的操作更昂贵,或者运行的数据要大100倍,那么它可能会。

序言,这是@ tucuxi代码的工作版本:

import java.util.*;
import java.io.*;
import java.util.regex.*;

public class T {
    public static Collection<String> findHung(Iterable<String> data) {
        Pattern p = Pattern.compile(
            "ScriptExecThread\\(([0-9]+).*?(finished|starting)");
        HashMap<Integer, String> running = new HashMap<Integer, String>();
        for (String d : data) {
            Matcher m = p.matcher(d);
            if (m.find()) {
                int n = Integer.parseInt(m.group(1));
                if (m.group(2).equals("starting"))
                    running.put(n, d);
                else
                    running.remove(n);
            }
        }
        return running.values();
    }

    static Iterable<String> readFile(String path, String encoding) throws IOException {
        final Scanner sc = new Scanner(new File(path), encoding).useDelimiter("\n");
        return new Iterable<String>() {
            public Iterator<String> iterator() { return sc; }
        };
    }

    public static void main(String[] args) throws Exception {
        for (String fileName : args) {
            for (String s : findHung(readFile(fileName, "UTF-8"))) {
                System.out.println(fileName + ": '" + s + "' unfinished");
            }
        }
    }
}

请注意,我已删除了finished集,HashMap现在称为running。当新线程开始时,它们会进入,当线程完成时,它会被拉出。这意味着HashMap将始终是当前正在运行的线程数的大小,它始终小于(或等于)运行的线程总数。因此对它的操作会更快。 (作为一个令人愉快的副作用,您现在可以按日志行跟踪日志行上运行的线程数,这可能有助于确定线程挂起的原因。)

这是我用来生成大量测试用例的Python程序:

#!/usr/bin/python

from random import random, choice
from datetime import datetime
import tempfile

all_threads = set([])
running = []
hung = []
filenames = { }

target_thread_count = 16
hang_chance = 0.001

def log(id, msg):
    now = datetime.now().strftime("%m:%d %H:%M:%S")
    print "%s, ScriptExecThread(%i),Info,%s" % (now, id, msg)

def new_thread():
    if len(all_threads)>0:
        for t in range(0, 2+max(all_threads)):
            if t not in all_threads:
                all_threads.add(t)
                return t
    else:
        all_threads.add(0)
        return 0

for i in range(0, 100000):
    if len(running) > target_thread_count:
        new_thread_chance = 0.25
    else:
        new_thread_chance = 0.75
        pass

    if random() < new_thread_chance:
        t = new_thread()
        name = next(tempfile._get_candidate_names())+".txt"
        filenames[t] = name
        log(t, "%s starting" % (name,))
        if random() < hang_chance:
            hung.append(t)
        else:
            running.append(t)
    elif len(running)>0:
        victim = choice(running)
        all_threads.remove(victim)
        running.remove(victim)
        log(t, "%s finished" % (filenames[victim],))

答案 2 :(得分:1)

value永远不会奏效 removeAll存储整个字符串 因此hasThreadHung中的值永远不会与started中的值匹配。

你想做类似的事情:

finished

然后在class ARecord { // Proper encapsulation of the members omitted for brevity String thread; String line; public ARecord (String thread, String line) { this.thread = thread; this.line = line; } public int hashcode() { return thread.hashcode(); } public boolean equals(ARecord o) { return thread.equals(o.thread); } } 中,创建hasHungThread并将其添加到ARecord
例如:

Set

started.add(new ARecord(m.group(2), str)); 中,您将从searchHungThreads检索ARecord并将其输出为:

started

答案 3 :(得分:0)

为什么不以另一种方式解决问题。如果你想要的只是挂起线程,可以通过编程方式获取线程堆栈。也可以使用外部工具但内部拥有JVM我认为最简单。然后将其作为API公开或使用线程转储定期保存带日期时间戳的文件。另一个程序只需要分析线程转储。如果相同的线程在相同的位置(相同的堆栈跟踪或相同的3-5个函数)通过​​线程转储它很有可能它挂起。

有些工具可以帮助您分析https://www.google.com/search?q=java+thread+dump+tool+open+source