基于文本搜索的算法不按预期运行

Jux*_*hin 9 java algorithm text

更新

我已经使用SO用户建议的更新代码更新了问题,并将澄清之前存在的任何模糊文本.

更新#2

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

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

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

File- 每个人都Thread IDScript一个File.如果Thread(10)在运行myscript.scriptmyfile.file,然后,准确的线不会被再次执行.使用上述示例的成功示例将是这样的.

- - - 开始 - - -

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

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

- - - 结束 - - - -

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

- - - 开始 - - -

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

- - - 结束 - - -


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


摘要

我正在解析大型日志文件(平均需要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);
Run Code Online (Sandbox Code Playgroud)

搜索Hung Threads

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");
}
Run Code Online (Sandbox Code Playgroud)

有线程挂起

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;
}
Run Code Online (Sandbox Code Playgroud)

示例数据

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和第3个线程已经启动但未能完成(原因不是必需的,我只需要获取该行).

样本数据

09.08 15:06.53,ScriptExecThread(7),Info,###########出发

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

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

09.08 15:06.54,ScriptExecThread(13),Info,##########出发

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,##########出发

09.08 15:06.55,ScriptExecThread(8),Info,#######开始

09.08 15:06.55,ScriptExecThread(0),Info,##########出发

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,##########出发

09.08 15:06.55,ScriptExecThread(0),Info,###########出发

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"开头.当我查看代码时,这有点有意义.

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

tuc*_*uxi 6

如果我理解正确,你有大文件,并试图找到X的所有数值的形式"X开始(但没有提到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
Run Code Online (Sandbox Code Playgroud)

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

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


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


编辑3:替换实现以显示文件和行.减少内存需求(不将整个文件加载到内存中); 但是打印该行确实需要存储所有"开始"行.

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");
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

输入:上面的示例数据,作为第一个参数,称为"data.txt".另一个名为"data2.txt"的文件中的相同数据作为第二个参数(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
Run Code Online (Sandbox Code Playgroud)


Jay*_*nek 2

保留两组独立的startedfinished线程(如@tucuxi 所描述)是行不通的。如果 ID 为 5 的线程启动、运行并结束,则 5 将finished永远出现在集合中。如果 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");
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,我已经删除了该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],))
Run Code Online (Sandbox Code Playgroud)