Оптимизация большого количества вызовов Scanner.findWithinHorizon (pattern, 0) - PullRequest
0 голосов
/ 04 июня 2010

Я строю процесс, который извлекает данные из 6 файлов в стиле csv и двух плохо выложенных .txt отчетов и создает выходные CSV-файлы, и я полностью осознаю, что будет непростой поиск во всех этих пробелах тысяч раз, но я никогда не ожидал, что преобразование около 50 000 записей займет 12 часов.

Выдержка из моего кода соответствия вручную (я знаю, это ужасно, что я использую списки таких токенов, но это было лучшее, что я мог придумать):

public static String lookup(Pattern tokenBefore,
                             List<String> tokensAfter)
{
    String result = null;

    while(_match(tokenBefore)) { // block until all input is read
        if(id.hasNext())
        {
            result = id.next(); // capture the  next token that matches

            if(_matchImmediate(tokensAfter)) // try to match tokensAfter to this result
                return result;
        } else
            return null; // end of file; no match
    }

    return null; // no matches
}

private static boolean _match(List<String> tokens)
{
    return _match(tokens, true);
}

private static boolean _match(Pattern token)
{
    if(token != null)
    {
        return (id.findWithinHorizon(token, 0) != null);
    } else {
        return false;
    }
}

private static boolean _match(List<String> tokens, boolean block)
{
    if(tokens != null && !tokens.isEmpty()) {
        if(id.findWithinHorizon(tokens.get(0), 0) == null)
            return false;

        for(int i = 1; i <= tokens.size(); i++)
        {
            if (i == tokens.size()) { // matches all tokens
                return true;
            } else if(id.hasNext() && !id.next().matches(tokens.get(i))) {
                break; // break to blocking behaviour
            }
        }
    } else {
        return true; // empty list always matches
    }

    if(block)
        return _match(tokens); // loop until we find something or nothing
    else
        return false; // return after just one attempted match
}

private static boolean _matchImmediate(List<String> tokens)
{
    if(tokens != null) {

        for(int i = 0; i <= tokens.size(); i++)
        {
            if (i == tokens.size()) { // matches all tokens
                return true;
            } else if(!id.hasNext() || !id.next().matches(tokens.get(i))) {
                return false; // doesn't match, or end of file
            }
        }

        return false; // we have some serious problems if this ever gets called
    } else {
        return true; // empty list always matches
    }
}

Интересно, как бы я работал в эффективном поиске строк (Бойер-Мур или аналогичный). Мой сканер id сканирует java.util.String, полагая, что буферизация его в памяти уменьшит число операций ввода-вывода, поскольку поиск здесь выполняется тысячи раз для относительно небольшого файла. Увеличение производительности по сравнению со сканированием BufferedReader (FileReader (File)), вероятно, было менее 1%, процесс по-прежнему занимает ДЛИННОЕ время.

Я также отслеживал выполнение, и медленность моего общего процесса преобразования определенно находится между первым и последним, как в методе поиска. На самом деле, настолько, что я запустил процесс ярлыка для подсчета количества вхождений различных идентификаторов в файлах в стиле .csv (я использую 2 метода поиска, это только один из них), и процесс завершил индексацию примерно 4 разных идентификаторы для 50 000 записей менее чем за минуту. По сравнению с 12 часами, это мгновенно.

Некоторые заметки (обновлено 06.06.2010):

  1. Мне все еще нужно поведение сопоставления с образцом для tokensBefore.
  2. Все идентификационные номера, которые мне нужны, не обязательно начинаются с фиксированной позиции в строке, но гарантируется, что после токена ID будет имя соответствующего объекта.
  3. В идеале я хотел бы вернуть строку, а не начальную позицию результата как int или что-то еще.

Поможет все, что поможет мне, даже если оно сэкономит 1 мс на поиск, поэтому все входные данные приветствуются. Thankyou!


Сценарий использования 1: у меня есть список объектов в файле A, у которых в системе старого стиля есть идентификационный номер, которого нет в файле A. Однако, это ВОЗМОЖНО в другом файле в стиле csv (файл B ) или, возможно, все еще в отчете .txt (файл C), каждый из которых также содержит кучу другой информации, которая здесь бесполезна, и поэтому в файле B необходимо выполнить поиск полного имени объекта (1 токен, поскольку он будет находиться в пределах второй столбец любой данной строки), а затем первый столбец должен быть идентификатором. Если это не сработает, тогда мы должны разделить токен поиска по пробелам на отдельные токены, прежде чем выполнять поиск файла C по этим токенам.

Обобщенный код:

String field;
for (/* each record in file A */)
{
    /* construct the rest of this object from file A info */
    // now to find the ID, if we can
    List<String> objectName = new ArrayList<String>(1);
    objectName.add(Pattern.quote(thisObject.fullName));
    field = lookup(objectSearchToken, objectName); // search file B
    if(field == null) // not found in file B
    {
        lookupReset(false); // initialise scanner to check file C
        objectName.clear(); // not using the full name

        String[] tokens = thisObject.fullName.split(id.delimiter().pattern());
        for(String s : tokens)
            objectName.add(Pattern.quote(s));

        field = lookup(objectSearchToken, objectName); // search file C
        lookupReset(true); // back to file B
    } else {
        /* found it, file B specific processing here */
    }

    if(field != null) // found it in B or C
        thisObject.ID = field;
}

Все токены objectName - это заглавные слова с возможными дефисами или апострофами, разделенными пробелами (имя человека).

Согласно ответу aioobe, я предварительно скомпилировал регулярное выражение для моих постоянных токенов поиска, которое в данном случае составляет всего \r\n. Заметное ускорение было примерно в 20 раз в другом из процессов, где я скомпилировал [0-9]{1,3}\\.[0-9]%|\r\n|0|[A-Z'-]+, хотя это не было замечено в приведенном выше коде с \r\n. Работая в этом направлении, я задаюсь вопросом:

Было бы лучше для меня сопоставить \r\n[^ ], если единственные пригодные совпадения будут в строках, начинающихся в любом случае с непробельного символа? Это может уменьшить количество выполнений _match.

Другая возможная оптимизация заключается в следующем: объединить все токены после и поставить (.*) заранее. Это уменьшит количество регулярных выражений (которые в любом случае являются буквальными), которые будут скомпилированы примерно на 2/3, а также, я надеюсь, позволит мне извлечь текст из этой группы вместо сохранения «потенциального токена» из каждой строки с удостоверение личности на это. Это тоже стоит делать?

Приведенную выше ситуацию можно было бы разрешить, если бы я смог заставить java.util.Scanner вернуть токен, предшествующий текущему, после вызова findWithinHorizon.

1 Ответ

2 голосов
/ 04 июня 2010

С чего начать: Каждый раз, когда вы запускаете id.next().matches(tokens.get(i)), выполняется следующий код:

Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(input);
return m.matches();

Компиляция регулярного выражения нетривиальна, и вам следует рассмотреть возможность компиляции шаблонов раз и навсегда в вашей программе:

pattern[i] = Pattern.compile(tokens.get(i));

А потом просто вызвать что-то вроде

pattern[i].matcher(str).matches()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...